alepha 0.15.1 → 0.15.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (523) hide show
  1. package/README.md +68 -80
  2. package/dist/api/audits/index.d.ts +10 -33
  3. package/dist/api/audits/index.d.ts.map +1 -1
  4. package/dist/api/audits/index.js +10 -33
  5. package/dist/api/audits/index.js.map +1 -1
  6. package/dist/api/files/index.d.ts +10 -3
  7. package/dist/api/files/index.d.ts.map +1 -1
  8. package/dist/api/files/index.js +10 -3
  9. package/dist/api/files/index.js.map +1 -1
  10. package/dist/api/jobs/index.d.ts +162 -155
  11. package/dist/api/jobs/index.d.ts.map +1 -1
  12. package/dist/api/jobs/index.js +10 -3
  13. package/dist/api/jobs/index.js.map +1 -1
  14. package/dist/api/keys/index.d.ts +413 -0
  15. package/dist/api/keys/index.d.ts.map +1 -0
  16. package/dist/api/keys/index.js +476 -0
  17. package/dist/api/keys/index.js.map +1 -0
  18. package/dist/api/notifications/index.d.ts +10 -4
  19. package/dist/api/notifications/index.d.ts.map +1 -1
  20. package/dist/api/notifications/index.js +10 -4
  21. package/dist/api/notifications/index.js.map +1 -1
  22. package/dist/api/parameters/index.d.ts +43 -50
  23. package/dist/api/parameters/index.d.ts.map +1 -1
  24. package/dist/api/parameters/index.js +30 -37
  25. package/dist/api/parameters/index.js.map +1 -1
  26. package/dist/api/users/index.d.ts +1081 -760
  27. package/dist/api/users/index.d.ts.map +1 -1
  28. package/dist/api/users/index.js +2539 -218
  29. package/dist/api/users/index.js.map +1 -1
  30. package/dist/api/verifications/index.d.ts +138 -132
  31. package/dist/api/verifications/index.d.ts.map +1 -1
  32. package/dist/api/verifications/index.js +12 -4
  33. package/dist/api/verifications/index.js.map +1 -1
  34. package/dist/batch/index.d.ts +20 -40
  35. package/dist/batch/index.d.ts.map +1 -1
  36. package/dist/batch/index.js +31 -44
  37. package/dist/batch/index.js.map +1 -1
  38. package/dist/bucket/index.d.ts +440 -8
  39. package/dist/bucket/index.d.ts.map +1 -1
  40. package/dist/bucket/index.js +1861 -12
  41. package/dist/bucket/index.js.map +1 -1
  42. package/dist/cache/core/index.d.ts +179 -7
  43. package/dist/cache/core/index.d.ts.map +1 -1
  44. package/dist/cache/core/index.js +213 -7
  45. package/dist/cache/core/index.js.map +1 -1
  46. package/dist/cache/redis/index.d.ts +1 -0
  47. package/dist/cache/redis/index.d.ts.map +1 -1
  48. package/dist/cache/redis/index.js +4 -0
  49. package/dist/cache/redis/index.js.map +1 -1
  50. package/dist/cli/index.d.ts +638 -5645
  51. package/dist/cli/index.d.ts.map +1 -1
  52. package/dist/cli/index.js +2550 -368
  53. package/dist/cli/index.js.map +1 -1
  54. package/dist/command/index.d.ts +203 -45
  55. package/dist/command/index.d.ts.map +1 -1
  56. package/dist/command/index.js +2060 -71
  57. package/dist/command/index.js.map +1 -1
  58. package/dist/core/index.browser.js +70 -40
  59. package/dist/core/index.browser.js.map +1 -1
  60. package/dist/core/index.d.ts +34 -13
  61. package/dist/core/index.d.ts.map +1 -1
  62. package/dist/core/index.js +90 -40
  63. package/dist/core/index.js.map +1 -1
  64. package/dist/core/index.native.js +70 -40
  65. package/dist/core/index.native.js.map +1 -1
  66. package/dist/datetime/index.d.ts +15 -0
  67. package/dist/datetime/index.d.ts.map +1 -1
  68. package/dist/datetime/index.js +15 -0
  69. package/dist/datetime/index.js.map +1 -1
  70. package/dist/email/index.d.ts +323 -20
  71. package/dist/email/index.d.ts.map +1 -1
  72. package/dist/email/index.js +1857 -7
  73. package/dist/email/index.js.map +1 -1
  74. package/dist/fake/index.d.ts +90 -8
  75. package/dist/fake/index.d.ts.map +1 -1
  76. package/dist/fake/index.js +91 -20
  77. package/dist/fake/index.js.map +1 -1
  78. package/dist/lock/core/index.d.ts +11 -4
  79. package/dist/lock/core/index.d.ts.map +1 -1
  80. package/dist/lock/core/index.js +11 -4
  81. package/dist/lock/core/index.js.map +1 -1
  82. package/dist/logger/index.d.ts +17 -66
  83. package/dist/logger/index.d.ts.map +1 -1
  84. package/dist/logger/index.js +14 -63
  85. package/dist/logger/index.js.map +1 -1
  86. package/dist/mcp/index.d.ts +10 -30
  87. package/dist/mcp/index.d.ts.map +1 -1
  88. package/dist/mcp/index.js +12 -35
  89. package/dist/mcp/index.js.map +1 -1
  90. package/dist/orm/index.browser.js +3 -3
  91. package/dist/orm/index.browser.js.map +1 -1
  92. package/dist/orm/index.bun.js +39 -20
  93. package/dist/orm/index.bun.js.map +1 -1
  94. package/dist/orm/index.d.ts +517 -540
  95. package/dist/orm/index.d.ts.map +1 -1
  96. package/dist/orm/index.js +58 -71
  97. package/dist/orm/index.js.map +1 -1
  98. package/dist/queue/core/index.d.ts +18 -10
  99. package/dist/queue/core/index.d.ts.map +1 -1
  100. package/dist/queue/core/index.js +14 -6
  101. package/dist/queue/core/index.js.map +1 -1
  102. package/dist/react/auth/index.browser.js +108 -0
  103. package/dist/react/auth/index.browser.js.map +1 -0
  104. package/dist/react/auth/index.d.ts +100 -0
  105. package/dist/react/auth/index.d.ts.map +1 -0
  106. package/dist/react/auth/index.js +145 -0
  107. package/dist/react/auth/index.js.map +1 -0
  108. package/dist/react/core/index.d.ts +469 -0
  109. package/dist/react/core/index.d.ts.map +1 -0
  110. package/dist/react/core/index.js +464 -0
  111. package/dist/react/core/index.js.map +1 -0
  112. package/dist/react/form/index.d.ts +232 -0
  113. package/dist/react/form/index.d.ts.map +1 -0
  114. package/dist/react/form/index.js +432 -0
  115. package/dist/react/form/index.js.map +1 -0
  116. package/dist/react/head/index.browser.js +423 -0
  117. package/dist/react/head/index.browser.js.map +1 -0
  118. package/dist/react/head/index.d.ts +288 -0
  119. package/dist/react/head/index.d.ts.map +1 -0
  120. package/dist/react/head/index.js +465 -0
  121. package/dist/react/head/index.js.map +1 -0
  122. package/dist/react/i18n/index.d.ts +175 -0
  123. package/dist/react/i18n/index.d.ts.map +1 -0
  124. package/dist/react/i18n/index.js +224 -0
  125. package/dist/react/i18n/index.js.map +1 -0
  126. package/dist/react/router/index.browser.js +1974 -0
  127. package/dist/react/router/index.browser.js.map +1 -0
  128. package/dist/react/router/index.d.ts +1956 -0
  129. package/dist/react/router/index.d.ts.map +1 -0
  130. package/dist/react/router/index.js +4722 -0
  131. package/dist/react/router/index.js.map +1 -0
  132. package/dist/react/websocket/index.d.ts +117 -0
  133. package/dist/react/websocket/index.d.ts.map +1 -0
  134. package/dist/react/websocket/index.js +107 -0
  135. package/dist/react/websocket/index.js.map +1 -0
  136. package/dist/redis/index.bun.js +4 -0
  137. package/dist/redis/index.bun.js.map +1 -1
  138. package/dist/redis/index.d.ts +41 -44
  139. package/dist/redis/index.d.ts.map +1 -1
  140. package/dist/redis/index.js +16 -25
  141. package/dist/redis/index.js.map +1 -1
  142. package/dist/retry/index.d.ts +11 -2
  143. package/dist/retry/index.d.ts.map +1 -1
  144. package/dist/retry/index.js +11 -2
  145. package/dist/retry/index.js.map +1 -1
  146. package/dist/scheduler/index.d.ts +11 -2
  147. package/dist/scheduler/index.d.ts.map +1 -1
  148. package/dist/scheduler/index.js +11 -2
  149. package/dist/scheduler/index.js.map +1 -1
  150. package/dist/security/index.d.ts +140 -49
  151. package/dist/security/index.d.ts.map +1 -1
  152. package/dist/security/index.js +164 -32
  153. package/dist/security/index.js.map +1 -1
  154. package/dist/server/auth/index.d.ts +12 -7
  155. package/dist/server/auth/index.d.ts.map +1 -1
  156. package/dist/server/auth/index.js +12 -7
  157. package/dist/server/auth/index.js.map +1 -1
  158. package/dist/server/cache/index.d.ts +7 -22
  159. package/dist/server/cache/index.d.ts.map +1 -1
  160. package/dist/server/cache/index.js +7 -22
  161. package/dist/server/cache/index.js.map +1 -1
  162. package/dist/server/compress/index.d.ts +10 -2
  163. package/dist/server/compress/index.d.ts.map +1 -1
  164. package/dist/server/compress/index.js +10 -2
  165. package/dist/server/compress/index.js.map +1 -1
  166. package/dist/server/cookies/index.d.ts +40 -16
  167. package/dist/server/cookies/index.d.ts.map +1 -1
  168. package/dist/server/cookies/index.js +7 -5
  169. package/dist/server/cookies/index.js.map +1 -1
  170. package/dist/server/core/index.d.ts +124 -23
  171. package/dist/server/core/index.d.ts.map +1 -1
  172. package/dist/server/core/index.js +231 -14
  173. package/dist/server/core/index.js.map +1 -1
  174. package/dist/server/cors/index.d.ts +13 -23
  175. package/dist/server/cors/index.d.ts.map +1 -1
  176. package/dist/server/cors/index.js +7 -21
  177. package/dist/server/cors/index.js.map +1 -1
  178. package/dist/server/health/index.d.ts +8 -2
  179. package/dist/server/health/index.d.ts.map +1 -1
  180. package/dist/server/health/index.js +8 -2
  181. package/dist/server/health/index.js.map +1 -1
  182. package/dist/server/helmet/index.d.ts +11 -3
  183. package/dist/server/helmet/index.d.ts.map +1 -1
  184. package/dist/server/helmet/index.js +11 -3
  185. package/dist/server/helmet/index.js.map +1 -1
  186. package/dist/server/links/index.d.ts +11 -6
  187. package/dist/server/links/index.d.ts.map +1 -1
  188. package/dist/server/links/index.js +11 -6
  189. package/dist/server/links/index.js.map +1 -1
  190. package/dist/server/metrics/index.d.ts +10 -3
  191. package/dist/server/metrics/index.d.ts.map +1 -1
  192. package/dist/server/metrics/index.js +10 -3
  193. package/dist/server/metrics/index.js.map +1 -1
  194. package/dist/server/multipart/index.d.ts +9 -3
  195. package/dist/server/multipart/index.d.ts.map +1 -1
  196. package/dist/server/multipart/index.js +9 -3
  197. package/dist/server/multipart/index.js.map +1 -1
  198. package/dist/server/proxy/index.d.ts +8 -2
  199. package/dist/server/proxy/index.d.ts.map +1 -1
  200. package/dist/server/proxy/index.js +8 -2
  201. package/dist/server/proxy/index.js.map +1 -1
  202. package/dist/server/rate-limit/index.d.ts +30 -35
  203. package/dist/server/rate-limit/index.d.ts.map +1 -1
  204. package/dist/server/rate-limit/index.js +18 -55
  205. package/dist/server/rate-limit/index.js.map +1 -1
  206. package/dist/server/static/index.d.ts +137 -4
  207. package/dist/server/static/index.d.ts.map +1 -1
  208. package/dist/server/static/index.js +1853 -5
  209. package/dist/server/static/index.js.map +1 -1
  210. package/dist/server/swagger/index.d.ts +309 -6
  211. package/dist/server/swagger/index.d.ts.map +1 -1
  212. package/dist/server/swagger/index.js +1854 -6
  213. package/dist/server/swagger/index.js.map +1 -1
  214. package/dist/sms/index.d.ts +309 -7
  215. package/dist/sms/index.d.ts.map +1 -1
  216. package/dist/sms/index.js +1856 -7
  217. package/dist/sms/index.js.map +1 -1
  218. package/dist/system/index.browser.js +1218 -0
  219. package/dist/system/index.browser.js.map +1 -0
  220. package/dist/{file → system}/index.d.ts +343 -16
  221. package/dist/system/index.d.ts.map +1 -0
  222. package/dist/{file → system}/index.js +419 -22
  223. package/dist/system/index.js.map +1 -0
  224. package/dist/thread/index.d.ts +11 -2
  225. package/dist/thread/index.d.ts.map +1 -1
  226. package/dist/thread/index.js +11 -2
  227. package/dist/thread/index.js.map +1 -1
  228. package/dist/topic/core/index.d.ts +12 -5
  229. package/dist/topic/core/index.d.ts.map +1 -1
  230. package/dist/topic/core/index.js +12 -5
  231. package/dist/topic/core/index.js.map +1 -1
  232. package/dist/vite/index.d.ts +5 -6272
  233. package/dist/vite/index.d.ts.map +1 -1
  234. package/dist/vite/index.js +23 -10
  235. package/dist/vite/index.js.map +1 -1
  236. package/dist/websocket/index.d.ts +12 -8
  237. package/dist/websocket/index.d.ts.map +1 -1
  238. package/dist/websocket/index.js +12 -8
  239. package/dist/websocket/index.js.map +1 -1
  240. package/package.json +82 -11
  241. package/src/api/audits/index.ts +10 -33
  242. package/src/api/files/__tests__/$bucket.spec.ts +1 -1
  243. package/src/api/files/controllers/AdminFileStatsController.spec.ts +1 -1
  244. package/src/api/files/controllers/FileController.spec.ts +1 -1
  245. package/src/api/files/index.ts +10 -3
  246. package/src/api/files/jobs/FileJobs.spec.ts +1 -1
  247. package/src/api/files/services/FileService.spec.ts +1 -1
  248. package/src/api/jobs/index.ts +10 -3
  249. package/src/api/keys/controllers/AdminApiKeyController.ts +75 -0
  250. package/src/api/keys/controllers/ApiKeyController.ts +103 -0
  251. package/src/api/keys/entities/apiKeyEntity.ts +41 -0
  252. package/src/api/keys/index.ts +49 -0
  253. package/src/api/keys/schemas/adminApiKeyQuerySchema.ts +7 -0
  254. package/src/api/keys/schemas/adminApiKeyResourceSchema.ts +17 -0
  255. package/src/api/keys/schemas/createApiKeyBodySchema.ts +7 -0
  256. package/src/api/keys/schemas/createApiKeyResponseSchema.ts +11 -0
  257. package/src/api/keys/schemas/listApiKeyResponseSchema.ts +15 -0
  258. package/src/api/keys/schemas/revokeApiKeyParamsSchema.ts +5 -0
  259. package/src/api/keys/schemas/revokeApiKeyResponseSchema.ts +5 -0
  260. package/src/api/keys/services/ApiKeyService.spec.ts +553 -0
  261. package/src/api/keys/services/ApiKeyService.ts +306 -0
  262. package/src/api/logs/TODO.md +55 -0
  263. package/src/api/notifications/index.ts +10 -4
  264. package/src/api/parameters/index.ts +9 -30
  265. package/src/api/parameters/primitives/$config.ts +12 -4
  266. package/src/api/parameters/services/ConfigStore.ts +9 -3
  267. package/src/api/users/__tests__/ApiKeys-integration.spec.ts +1035 -0
  268. package/src/api/users/__tests__/ApiKeys.spec.ts +401 -0
  269. package/src/api/users/index.ts +14 -3
  270. package/src/api/users/primitives/$realm.ts +33 -5
  271. package/src/api/users/providers/RealmProvider.ts +1 -12
  272. package/src/api/users/services/SessionService.ts +1 -1
  273. package/src/api/verifications/controllers/VerificationController.ts +2 -0
  274. package/src/api/verifications/index.ts +10 -4
  275. package/src/batch/index.ts +9 -36
  276. package/src/batch/primitives/$batch.ts +0 -8
  277. package/src/batch/providers/BatchProvider.ts +29 -2
  278. package/src/bucket/__tests__/shared.ts +1 -1
  279. package/src/bucket/index.ts +13 -6
  280. package/src/bucket/primitives/$bucket.ts +1 -1
  281. package/src/bucket/providers/LocalFileStorageProvider.ts +1 -1
  282. package/src/bucket/providers/MemoryFileStorageProvider.ts +1 -1
  283. package/src/cache/core/__tests__/shared.ts +30 -0
  284. package/src/cache/core/index.ts +11 -6
  285. package/src/cache/core/primitives/$cache.spec.ts +5 -0
  286. package/src/cache/core/providers/CacheProvider.ts +17 -0
  287. package/src/cache/core/providers/MemoryCacheProvider.ts +300 -1
  288. package/src/cache/redis/__tests__/cache-redis.spec.ts +5 -0
  289. package/src/cache/redis/providers/RedisCacheProvider.ts +9 -0
  290. package/src/cli/apps/AlephaCli.ts +1 -14
  291. package/src/cli/apps/AlephaPackageBuilderCli.ts +10 -1
  292. package/src/cli/atoms/buildOptions.ts +99 -9
  293. package/src/cli/commands/build.ts +150 -37
  294. package/src/cli/commands/db.ts +22 -18
  295. package/src/cli/commands/deploy.ts +1 -1
  296. package/src/cli/commands/dev.ts +1 -20
  297. package/src/cli/commands/gen/env.ts +5 -2
  298. package/src/cli/commands/gen/openapi.ts +5 -2
  299. package/src/cli/commands/init.spec.ts +588 -0
  300. package/src/cli/commands/init.ts +115 -58
  301. package/src/cli/commands/lint.ts +7 -1
  302. package/src/cli/commands/typecheck.ts +11 -0
  303. package/src/cli/providers/AppEntryProvider.ts +1 -1
  304. package/src/cli/providers/ViteBuildProvider.ts +8 -50
  305. package/src/cli/providers/ViteDevServerProvider.ts +35 -16
  306. package/src/cli/services/AlephaCliUtils.ts +52 -121
  307. package/src/cli/services/PackageManagerUtils.ts +129 -11
  308. package/src/cli/services/ProjectScaffolder.spec.ts +97 -0
  309. package/src/cli/services/ProjectScaffolder.ts +148 -81
  310. package/src/cli/services/ViteUtils.ts +82 -0
  311. package/src/cli/{assets/claudeMd.ts → templates/agentMd.ts} +37 -24
  312. package/src/cli/templates/apiAppSecurityTs.ts +11 -0
  313. package/src/cli/templates/apiIndexTs.ts +30 -0
  314. package/src/cli/templates/gitignore.ts +39 -0
  315. package/src/cli/{assets → templates}/mainCss.ts +11 -2
  316. package/src/cli/templates/mainServerTs.ts +33 -0
  317. package/src/cli/templates/webAppRouterTs.ts +74 -0
  318. package/src/cli/templates/webHelloComponentTsx.ts +30 -0
  319. package/src/command/helpers/Runner.spec.ts +139 -0
  320. package/src/command/helpers/Runner.ts +7 -22
  321. package/src/command/index.ts +12 -4
  322. package/src/command/providers/CliProvider.spec.ts +1392 -0
  323. package/src/command/providers/CliProvider.ts +320 -47
  324. package/src/core/Alepha.ts +34 -27
  325. package/src/core/__tests__/Alepha-start.spec.ts +4 -4
  326. package/src/core/helpers/jsonSchemaToTypeBox.spec.ts +771 -0
  327. package/src/core/helpers/jsonSchemaToTypeBox.ts +62 -10
  328. package/src/core/index.shared.ts +1 -0
  329. package/src/core/index.ts +20 -0
  330. package/src/core/providers/EventManager.spec.ts +0 -71
  331. package/src/core/providers/EventManager.ts +3 -15
  332. package/src/core/providers/Json.ts +2 -14
  333. package/src/datetime/index.ts +15 -0
  334. package/src/email/index.ts +10 -5
  335. package/src/email/providers/LocalEmailProvider.spec.ts +1 -1
  336. package/src/email/providers/LocalEmailProvider.ts +1 -1
  337. package/src/fake/__tests__/keyName.example.ts +1 -1
  338. package/src/fake/__tests__/keyName.spec.ts +5 -5
  339. package/src/fake/index.ts +9 -6
  340. package/src/fake/providers/FakeProvider.spec.ts +258 -40
  341. package/src/fake/providers/FakeProvider.ts +133 -19
  342. package/src/lock/core/index.ts +11 -4
  343. package/src/logger/index.ts +17 -66
  344. package/src/mcp/index.ts +10 -27
  345. package/src/mcp/transports/SseMcpTransport.ts +0 -11
  346. package/src/orm/__tests__/PostgresProvider.spec.ts +2 -2
  347. package/src/orm/index.browser.ts +2 -2
  348. package/src/orm/index.bun.ts +5 -3
  349. package/src/orm/index.ts +23 -53
  350. package/src/orm/providers/drivers/BunSqliteProvider.ts +5 -1
  351. package/src/orm/providers/drivers/CloudflareD1Provider.ts +57 -30
  352. package/src/orm/providers/drivers/DatabaseProvider.ts +9 -1
  353. package/src/orm/providers/drivers/NodeSqliteProvider.ts +4 -1
  354. package/src/orm/services/Repository.ts +7 -3
  355. package/src/queue/core/index.ts +14 -6
  356. package/src/react/auth/__tests__/$auth.spec.ts +202 -0
  357. package/src/react/auth/hooks/useAuth.ts +32 -0
  358. package/src/react/auth/index.browser.ts +13 -0
  359. package/src/react/auth/index.shared.ts +2 -0
  360. package/src/react/auth/index.ts +48 -0
  361. package/src/react/auth/providers/ReactAuthProvider.ts +16 -0
  362. package/src/react/auth/services/ReactAuth.ts +135 -0
  363. package/src/react/core/__tests__/Router.spec.tsx +169 -0
  364. package/src/react/core/components/ClientOnly.tsx +49 -0
  365. package/src/react/core/components/ErrorBoundary.tsx +73 -0
  366. package/src/react/core/contexts/AlephaContext.ts +7 -0
  367. package/src/react/core/contexts/AlephaProvider.tsx +42 -0
  368. package/src/react/core/hooks/useAction.browser.spec.tsx +569 -0
  369. package/src/react/core/hooks/useAction.ts +480 -0
  370. package/src/react/core/hooks/useAlepha.ts +26 -0
  371. package/src/react/core/hooks/useClient.ts +17 -0
  372. package/src/react/core/hooks/useEvents.ts +51 -0
  373. package/src/react/core/hooks/useInject.ts +12 -0
  374. package/src/react/core/hooks/useStore.ts +52 -0
  375. package/src/react/core/index.ts +90 -0
  376. package/src/react/form/components/FormState.tsx +17 -0
  377. package/src/react/form/errors/FormValidationError.ts +18 -0
  378. package/src/react/form/hooks/useForm.browser.spec.tsx +366 -0
  379. package/src/react/form/hooks/useForm.ts +47 -0
  380. package/src/react/form/hooks/useFormState.ts +130 -0
  381. package/src/react/form/index.ts +44 -0
  382. package/src/react/form/services/FormModel.ts +614 -0
  383. package/src/react/head/helpers/SeoExpander.spec.ts +203 -0
  384. package/src/react/head/helpers/SeoExpander.ts +142 -0
  385. package/src/react/head/hooks/useHead.spec.tsx +288 -0
  386. package/src/react/head/hooks/useHead.ts +62 -0
  387. package/src/react/head/index.browser.ts +26 -0
  388. package/src/react/head/index.ts +44 -0
  389. package/src/react/head/interfaces/Head.ts +105 -0
  390. package/src/react/head/primitives/$head.ts +25 -0
  391. package/src/react/head/providers/BrowserHeadProvider.browser.spec.ts +196 -0
  392. package/src/react/head/providers/BrowserHeadProvider.ts +212 -0
  393. package/src/react/head/providers/HeadProvider.ts +168 -0
  394. package/src/react/head/providers/ServerHeadProvider.ts +31 -0
  395. package/src/react/i18n/__tests__/integration.spec.tsx +239 -0
  396. package/src/react/i18n/components/Localize.spec.tsx +357 -0
  397. package/src/react/i18n/components/Localize.tsx +35 -0
  398. package/src/react/i18n/hooks/useI18n.browser.spec.tsx +438 -0
  399. package/src/react/i18n/hooks/useI18n.ts +18 -0
  400. package/src/react/i18n/index.ts +41 -0
  401. package/src/react/i18n/primitives/$dictionary.ts +69 -0
  402. package/src/react/i18n/providers/I18nProvider.spec.ts +389 -0
  403. package/src/react/i18n/providers/I18nProvider.ts +278 -0
  404. package/src/react/router/__tests__/page-head-browser.browser.spec.ts +95 -0
  405. package/src/react/router/__tests__/page-head.spec.ts +48 -0
  406. package/src/react/router/__tests__/seo-head.spec.ts +125 -0
  407. package/src/react/router/atoms/ssrManifestAtom.ts +58 -0
  408. package/src/react/router/components/ErrorViewer.tsx +872 -0
  409. package/src/react/router/components/Link.tsx +23 -0
  410. package/src/react/router/components/NestedView.tsx +223 -0
  411. package/src/react/router/components/NotFound.tsx +30 -0
  412. package/src/react/router/constants/PAGE_PRELOAD_KEY.ts +6 -0
  413. package/src/react/router/contexts/RouterLayerContext.ts +12 -0
  414. package/src/react/router/errors/Redirection.ts +28 -0
  415. package/src/react/router/hooks/useActive.ts +52 -0
  416. package/src/react/router/hooks/useQueryParams.ts +63 -0
  417. package/src/react/router/hooks/useRouter.ts +20 -0
  418. package/src/react/router/hooks/useRouterState.ts +11 -0
  419. package/src/react/router/index.browser.ts +45 -0
  420. package/src/react/router/index.shared.ts +19 -0
  421. package/src/react/router/index.ts +146 -0
  422. package/src/react/router/primitives/$page.browser.spec.tsx +851 -0
  423. package/src/react/router/primitives/$page.spec.tsx +676 -0
  424. package/src/react/router/primitives/$page.ts +489 -0
  425. package/src/react/router/providers/ReactBrowserProvider.ts +312 -0
  426. package/src/react/router/providers/ReactBrowserRendererProvider.ts +25 -0
  427. package/src/react/router/providers/ReactBrowserRouterProvider.ts +168 -0
  428. package/src/react/router/providers/ReactPageProvider.ts +726 -0
  429. package/src/react/router/providers/ReactPreloadProvider.spec.ts +142 -0
  430. package/src/react/router/providers/ReactPreloadProvider.ts +85 -0
  431. package/src/react/router/providers/ReactServerProvider.spec.tsx +316 -0
  432. package/src/react/router/providers/ReactServerProvider.ts +487 -0
  433. package/src/react/router/providers/ReactServerTemplateProvider.spec.ts +210 -0
  434. package/src/react/router/providers/ReactServerTemplateProvider.ts +542 -0
  435. package/src/react/router/providers/SSRManifestProvider.ts +334 -0
  436. package/src/react/router/services/ReactPageServerService.ts +48 -0
  437. package/src/react/router/services/ReactPageService.ts +27 -0
  438. package/src/react/router/services/ReactRouter.ts +262 -0
  439. package/src/react/websocket/hooks/useRoom.tsx +242 -0
  440. package/src/react/websocket/index.ts +7 -0
  441. package/src/redis/__tests__/redis.spec.ts +13 -0
  442. package/src/redis/index.ts +9 -25
  443. package/src/redis/providers/BunRedisProvider.ts +9 -0
  444. package/src/redis/providers/NodeRedisProvider.ts +8 -0
  445. package/src/redis/providers/RedisProvider.ts +16 -0
  446. package/src/retry/index.ts +11 -2
  447. package/src/router/index.ts +15 -0
  448. package/src/scheduler/index.ts +11 -2
  449. package/src/security/__tests__/BasicAuth.spec.ts +2 -0
  450. package/src/security/__tests__/ServerSecurityProvider.spec.ts +90 -5
  451. package/src/security/index.ts +15 -10
  452. package/src/security/interfaces/IssuerResolver.ts +27 -0
  453. package/src/security/primitives/$issuer.ts +55 -0
  454. package/src/security/providers/SecurityProvider.ts +179 -0
  455. package/src/security/providers/ServerBasicAuthProvider.ts +6 -2
  456. package/src/security/providers/ServerSecurityProvider.ts +63 -41
  457. package/src/server/auth/index.ts +12 -7
  458. package/src/server/cache/index.ts +7 -22
  459. package/src/server/compress/index.ts +10 -2
  460. package/src/server/cookies/index.ts +7 -5
  461. package/src/server/cookies/primitives/$cookie.ts +33 -11
  462. package/src/server/core/index.ts +16 -6
  463. package/src/server/core/interfaces/ServerRequest.ts +83 -1
  464. package/src/server/core/primitives/$action.spec.ts +1 -1
  465. package/src/server/core/primitives/$action.ts +8 -3
  466. package/src/server/core/providers/NodeHttpServerProvider.spec.ts +9 -3
  467. package/src/server/core/providers/NodeHttpServerProvider.ts +9 -3
  468. package/src/server/core/services/ServerRequestParser.spec.ts +520 -0
  469. package/src/server/core/services/ServerRequestParser.ts +306 -13
  470. package/src/server/cors/index.ts +7 -21
  471. package/src/server/cors/primitives/$cors.ts +6 -2
  472. package/src/server/health/index.ts +8 -2
  473. package/src/server/helmet/index.ts +11 -3
  474. package/src/server/links/index.ts +11 -6
  475. package/src/server/metrics/index.ts +10 -3
  476. package/src/server/multipart/index.ts +9 -3
  477. package/src/server/proxy/index.ts +8 -2
  478. package/src/server/rate-limit/index.ts +21 -25
  479. package/src/server/rate-limit/primitives/$rateLimit.ts +6 -2
  480. package/src/server/rate-limit/providers/ServerRateLimitProvider.spec.ts +38 -14
  481. package/src/server/rate-limit/providers/ServerRateLimitProvider.ts +22 -56
  482. package/src/server/static/index.ts +8 -2
  483. package/src/server/static/providers/ServerStaticProvider.ts +1 -1
  484. package/src/server/swagger/index.ts +9 -4
  485. package/src/server/swagger/providers/ServerSwaggerProvider.ts +1 -1
  486. package/src/sms/index.ts +9 -5
  487. package/src/sms/providers/LocalSmsProvider.spec.ts +1 -1
  488. package/src/sms/providers/LocalSmsProvider.ts +1 -1
  489. package/src/system/index.browser.ts +36 -0
  490. package/src/system/index.ts +62 -0
  491. package/src/system/index.workerd.ts +1 -0
  492. package/src/{file → system}/providers/FileSystemProvider.ts +24 -0
  493. package/src/{file → system}/providers/MemoryFileSystemProvider.ts +116 -3
  494. package/src/system/providers/MemoryShellProvider.ts +164 -0
  495. package/src/{file → system}/providers/NodeFileSystemProvider.spec.ts +2 -2
  496. package/src/{file → system}/providers/NodeFileSystemProvider.ts +47 -2
  497. package/src/system/providers/NodeShellProvider.ts +184 -0
  498. package/src/system/providers/ShellProvider.ts +74 -0
  499. package/src/{file → system}/services/FileDetector.spec.ts +2 -2
  500. package/src/thread/index.ts +11 -2
  501. package/src/topic/core/index.ts +12 -5
  502. package/src/vite/tasks/buildClient.ts +2 -7
  503. package/src/vite/tasks/buildServer.ts +19 -13
  504. package/src/vite/tasks/generateCloudflare.ts +10 -7
  505. package/src/vite/tasks/generateDocker.ts +4 -0
  506. package/src/websocket/index.ts +12 -8
  507. package/dist/file/index.d.ts.map +0 -1
  508. package/dist/file/index.js.map +0 -1
  509. package/src/cli/assets/apiIndexTs.ts +0 -16
  510. package/src/cli/assets/mainServerTs.ts +0 -24
  511. package/src/cli/assets/webAppRouterTs.ts +0 -16
  512. package/src/cli/assets/webHelloComponentTsx.ts +0 -20
  513. package/src/cli/providers/ViteTemplateProvider.ts +0 -27
  514. package/src/file/index.ts +0 -43
  515. /package/src/cli/{assets → templates}/apiHelloControllerTs.ts +0 -0
  516. /package/src/cli/{assets → templates}/biomeJson.ts +0 -0
  517. /package/src/cli/{assets → templates}/dummySpecTs.ts +0 -0
  518. /package/src/cli/{assets → templates}/editorconfig.ts +0 -0
  519. /package/src/cli/{assets → templates}/mainBrowserTs.ts +0 -0
  520. /package/src/cli/{assets → templates}/tsconfigJson.ts +0 -0
  521. /package/src/cli/{assets → templates}/webIndexTs.ts +0 -0
  522. /package/src/{file → system}/errors/FileError.ts +0 -0
  523. /package/src/{file → system}/services/FileDetector.ts +0 -0
package/dist/cli/index.js CHANGED
@@ -1,16 +1,1857 @@
1
- import { $atom, $hook, $inject, $module, $use, Alepha, AlephaError, t } from "alepha";
2
- import { FileSystemProvider } from "alepha/file";
3
- import { $command, CliProvider, EnvUtils } from "alepha/command";
1
+ import { $atom, $hook, $inject, $module, $use, Alepha, AlephaError, Json, isFileLike, t } from "alepha";
2
+ import { basename, dirname, join } from "node:path";
3
+ import { createReadStream, readFileSync } from "node:fs";
4
+ import { access, copyFile, cp, mkdir, readFile, readdir, rename, rm, stat, writeFile } from "node:fs/promises";
5
+ import { PassThrough, Readable } from "node:stream";
6
+ import { fileURLToPath } from "node:url";
7
+ import { exec, spawn } from "node:child_process";
4
8
  import { $logger, ConsoleColorProvider } from "alepha/logger";
9
+ import { $command, CliProvider, EnvUtils } from "alepha/command";
5
10
  import { buildClient, buildServer, copyAssets, generateCloudflare, generateDocker, generateSitemap, generateVercel, importVite, importViteReact, prerenderPages, viteAlephaSsrPreload } from "alepha/vite";
6
- import { exec, spawn } from "node:child_process";
7
- import { readFileSync } from "node:fs";
8
- import { basename, dirname, join } from "node:path";
9
11
  import { promisify } from "node:util";
10
12
  import { ServerSwaggerProvider } from "alepha/server/swagger";
11
- import { access, readFile, readdir } from "node:fs/promises";
12
13
  import * as os from "node:os";
13
14
 
15
+ //#region ../../src/system/providers/FileSystemProvider.ts
16
+ /**
17
+ * FileSystem interface providing utilities for working with files.
18
+ */
19
+ var FileSystemProvider = class {};
20
+
21
+ //#endregion
22
+ //#region ../../src/system/providers/MemoryFileSystemProvider.ts
23
+ /**
24
+ * In-memory implementation of FileSystemProvider for testing.
25
+ *
26
+ * This provider stores all files and directories in memory, making it ideal for
27
+ * unit tests that need to verify file operations without touching the real file system.
28
+ *
29
+ * @example
30
+ * ```typescript
31
+ * // In tests, substitute the real FileSystemProvider with MemoryFileSystemProvider
32
+ * const alepha = Alepha.create().with({
33
+ * provide: FileSystemProvider,
34
+ * use: MemoryFileSystemProvider,
35
+ * });
36
+ *
37
+ * // Run code that uses FileSystemProvider
38
+ * const service = alepha.inject(MyService);
39
+ * await service.saveFile("test.txt", "Hello World");
40
+ *
41
+ * // Verify the file was written
42
+ * const memoryFs = alepha.inject(MemoryFileSystemProvider);
43
+ * expect(memoryFs.files.get("test.txt")?.toString()).toBe("Hello World");
44
+ * ```
45
+ */
46
+ var MemoryFileSystemProvider = class {
47
+ json = $inject(Json);
48
+ /**
49
+ * In-memory storage for files (path -> content)
50
+ */
51
+ files = /* @__PURE__ */ new Map();
52
+ /**
53
+ * In-memory storage for directories
54
+ */
55
+ directories = /* @__PURE__ */ new Set();
56
+ /**
57
+ * Track mkdir calls for test assertions
58
+ */
59
+ mkdirCalls = [];
60
+ /**
61
+ * Track writeFile calls for test assertions
62
+ */
63
+ writeFileCalls = [];
64
+ /**
65
+ * Track readFile calls for test assertions
66
+ */
67
+ readFileCalls = [];
68
+ /**
69
+ * Track rm calls for test assertions
70
+ */
71
+ rmCalls = [];
72
+ /**
73
+ * Track join calls for test assertions
74
+ */
75
+ joinCalls = [];
76
+ /**
77
+ * Error to throw on mkdir (for testing error handling)
78
+ */
79
+ mkdirError = null;
80
+ /**
81
+ * Error to throw on writeFile (for testing error handling)
82
+ */
83
+ writeFileError = null;
84
+ /**
85
+ * Error to throw on readFile (for testing error handling)
86
+ */
87
+ readFileError = null;
88
+ constructor(options = {}) {
89
+ this.mkdirError = options.mkdirError ?? null;
90
+ this.writeFileError = options.writeFileError ?? null;
91
+ this.readFileError = options.readFileError ?? null;
92
+ }
93
+ /**
94
+ * Join path segments using forward slashes.
95
+ * Uses Node's path.join for proper normalization (handles .. and .)
96
+ */
97
+ join(...paths) {
98
+ this.joinCalls.push(paths);
99
+ return join(...paths);
100
+ }
101
+ /**
102
+ * Create a FileLike object from various sources.
103
+ */
104
+ createFile(options) {
105
+ if ("path" in options) {
106
+ const filePath = options.path;
107
+ const buffer = this.files.get(filePath);
108
+ if (buffer === void 0) throw new Error(`ENOENT: no such file or directory, open '${filePath}'`);
109
+ return {
110
+ name: options.name ?? filePath.split("/").pop() ?? "file",
111
+ type: options.type ?? "application/octet-stream",
112
+ size: buffer.byteLength,
113
+ lastModified: Date.now(),
114
+ stream: () => {
115
+ throw new Error("Stream not implemented in MemoryFileSystemProvider");
116
+ },
117
+ arrayBuffer: async () => buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength),
118
+ text: async () => buffer.toString("utf-8")
119
+ };
120
+ }
121
+ if ("buffer" in options) {
122
+ const buffer = options.buffer;
123
+ return {
124
+ name: options.name ?? "file",
125
+ type: options.type ?? "application/octet-stream",
126
+ size: buffer.byteLength,
127
+ lastModified: Date.now(),
128
+ stream: () => {
129
+ throw new Error("Stream not implemented in MemoryFileSystemProvider");
130
+ },
131
+ arrayBuffer: async () => buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength),
132
+ text: async () => buffer.toString("utf-8")
133
+ };
134
+ }
135
+ if ("text" in options) {
136
+ const buffer = Buffer.from(options.text, "utf-8");
137
+ return {
138
+ name: options.name ?? "file.txt",
139
+ type: options.type ?? "text/plain",
140
+ size: buffer.byteLength,
141
+ lastModified: Date.now(),
142
+ stream: () => {
143
+ throw new Error("Stream not implemented in MemoryFileSystemProvider");
144
+ },
145
+ arrayBuffer: async () => buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength),
146
+ text: async () => options.text
147
+ };
148
+ }
149
+ throw new Error("MemoryFileSystemProvider.createFile: unsupported options. Only buffer and text are supported.");
150
+ }
151
+ /**
152
+ * Remove a file or directory from memory.
153
+ */
154
+ async rm(path, options) {
155
+ this.rmCalls.push({
156
+ path,
157
+ options
158
+ });
159
+ if (!(this.files.has(path) || this.directories.has(path)) && !options?.force) throw new Error(`ENOENT: no such file or directory, rm '${path}'`);
160
+ if (this.directories.has(path)) if (options?.recursive) {
161
+ this.directories.delete(path);
162
+ for (const filePath of this.files.keys()) if (filePath.startsWith(`${path}/`)) this.files.delete(filePath);
163
+ for (const dirPath of this.directories) if (dirPath.startsWith(`${path}/`)) this.directories.delete(dirPath);
164
+ } else throw new Error(`EISDIR: illegal operation on a directory, rm '${path}'`);
165
+ else this.files.delete(path);
166
+ }
167
+ /**
168
+ * Copy a file or directory in memory.
169
+ */
170
+ async cp(src, dest, options) {
171
+ if (this.directories.has(src)) {
172
+ if (!options?.recursive) throw new Error(`Cannot copy directory without recursive option: ${src}`);
173
+ this.directories.add(dest);
174
+ for (const [filePath, content] of this.files) if (filePath.startsWith(`${src}/`)) {
175
+ const newPath = filePath.replace(src, dest);
176
+ this.files.set(newPath, Buffer.from(content));
177
+ }
178
+ } else if (this.files.has(src)) {
179
+ const content = this.files.get(src);
180
+ this.files.set(dest, Buffer.from(content));
181
+ } else throw new Error(`ENOENT: no such file or directory, cp '${src}'`);
182
+ }
183
+ /**
184
+ * Move/rename a file or directory in memory.
185
+ */
186
+ async mv(src, dest) {
187
+ if (this.directories.has(src)) {
188
+ this.directories.delete(src);
189
+ this.directories.add(dest);
190
+ for (const [filePath, content] of this.files) if (filePath.startsWith(`${src}/`)) {
191
+ const newPath = filePath.replace(src, dest);
192
+ this.files.delete(filePath);
193
+ this.files.set(newPath, content);
194
+ }
195
+ } else if (this.files.has(src)) {
196
+ const content = this.files.get(src);
197
+ this.files.delete(src);
198
+ this.files.set(dest, content);
199
+ } else throw new Error(`ENOENT: no such file or directory, mv '${src}'`);
200
+ }
201
+ /**
202
+ * Create a directory in memory.
203
+ */
204
+ async mkdir(path, options) {
205
+ this.mkdirCalls.push({
206
+ path,
207
+ options
208
+ });
209
+ if (this.mkdirError) throw this.mkdirError;
210
+ if (this.directories.has(path) && !options?.recursive) throw new Error(`EEXIST: file already exists, mkdir '${path}'`);
211
+ this.directories.add(path);
212
+ if (options?.recursive) {
213
+ const parts = path.split("/").filter(Boolean);
214
+ let current = "";
215
+ for (const part of parts) {
216
+ current = current ? `${current}/${part}` : part;
217
+ this.directories.add(current);
218
+ }
219
+ }
220
+ }
221
+ /**
222
+ * List files in a directory.
223
+ */
224
+ async ls(path, options) {
225
+ const normalizedPath = path.replace(/\/$/, "");
226
+ const entries = /* @__PURE__ */ new Set();
227
+ for (const filePath of this.files.keys()) if (filePath.startsWith(`${normalizedPath}/`)) {
228
+ const relativePath = filePath.slice(normalizedPath.length + 1);
229
+ const parts = relativePath.split("/");
230
+ if (options?.recursive) entries.add(relativePath);
231
+ else entries.add(parts[0]);
232
+ }
233
+ for (const dirPath of this.directories) if (dirPath.startsWith(`${normalizedPath}/`) && dirPath !== normalizedPath) {
234
+ const relativePath = dirPath.slice(normalizedPath.length + 1);
235
+ const parts = relativePath.split("/");
236
+ if (options?.recursive) entries.add(relativePath);
237
+ else if (parts.length === 1) entries.add(parts[0]);
238
+ }
239
+ let result = Array.from(entries);
240
+ if (!options?.hidden) result = result.filter((entry) => !entry.startsWith("."));
241
+ return result.sort();
242
+ }
243
+ /**
244
+ * Check if a file or directory exists in memory.
245
+ */
246
+ async exists(path) {
247
+ return this.files.has(path) || this.directories.has(path);
248
+ }
249
+ /**
250
+ * Read a file from memory.
251
+ */
252
+ async readFile(path) {
253
+ this.readFileCalls.push(path);
254
+ if (this.readFileError) throw this.readFileError;
255
+ const content = this.files.get(path);
256
+ if (!content) throw new Error(`ENOENT: no such file or directory, open '${path}'`);
257
+ return content;
258
+ }
259
+ /**
260
+ * Read a file from memory as text.
261
+ */
262
+ async readTextFile(path) {
263
+ return (await this.readFile(path)).toString("utf-8");
264
+ }
265
+ /**
266
+ * Read a file from memory as JSON.
267
+ */
268
+ async readJsonFile(path) {
269
+ const text = await this.readTextFile(path);
270
+ return this.json.parse(text);
271
+ }
272
+ /**
273
+ * Write a file to memory.
274
+ */
275
+ async writeFile(path, data) {
276
+ const dataStr = typeof data === "string" ? data : data instanceof Buffer || data instanceof Uint8Array ? data.toString("utf-8") : await data.text();
277
+ this.writeFileCalls.push({
278
+ path,
279
+ data: dataStr
280
+ });
281
+ if (this.writeFileError) throw this.writeFileError;
282
+ const buffer = typeof data === "string" ? Buffer.from(data, "utf-8") : data instanceof Buffer ? data : data instanceof Uint8Array ? Buffer.from(data) : Buffer.from(await data.text(), "utf-8");
283
+ this.files.set(path, buffer);
284
+ }
285
+ /**
286
+ * Reset all in-memory state (useful between tests).
287
+ */
288
+ reset() {
289
+ this.files.clear();
290
+ this.directories.clear();
291
+ this.mkdirCalls = [];
292
+ this.writeFileCalls = [];
293
+ this.readFileCalls = [];
294
+ this.rmCalls = [];
295
+ this.joinCalls = [];
296
+ this.mkdirError = null;
297
+ this.writeFileError = null;
298
+ this.readFileError = null;
299
+ }
300
+ /**
301
+ * Check if a file was written during the test.
302
+ *
303
+ * @example
304
+ * ```typescript
305
+ * expect(fs.wasWritten("/project/tsconfig.json")).toBe(true);
306
+ * ```
307
+ */
308
+ wasWritten(path) {
309
+ return this.writeFileCalls.some((call) => call.path === path);
310
+ }
311
+ /**
312
+ * Check if a file was written with content matching a pattern.
313
+ *
314
+ * @example
315
+ * ```typescript
316
+ * expect(fs.wasWrittenMatching("/project/tsconfig.json", /extends/)).toBe(true);
317
+ * ```
318
+ */
319
+ wasWrittenMatching(path, pattern) {
320
+ const call = this.writeFileCalls.find((c) => c.path === path);
321
+ return call ? pattern.test(call.data) : false;
322
+ }
323
+ /**
324
+ * Check if a file was read during the test.
325
+ *
326
+ * @example
327
+ * ```typescript
328
+ * expect(fs.wasRead("/project/package.json")).toBe(true);
329
+ * ```
330
+ */
331
+ wasRead(path) {
332
+ return this.readFileCalls.includes(path);
333
+ }
334
+ /**
335
+ * Check if a file was deleted during the test.
336
+ *
337
+ * @example
338
+ * ```typescript
339
+ * expect(fs.wasDeleted("/project/old-file.txt")).toBe(true);
340
+ * ```
341
+ */
342
+ wasDeleted(path) {
343
+ return this.rmCalls.some((call) => call.path === path);
344
+ }
345
+ /**
346
+ * Get the content of a file as a string (convenience method for testing).
347
+ */
348
+ getFileContent(path) {
349
+ return this.files.get(path)?.toString("utf-8");
350
+ }
351
+ };
352
+
353
+ //#endregion
354
+ //#region ../../src/system/providers/MemoryShellProvider.ts
355
+ /**
356
+ * In-memory implementation of ShellProvider for testing.
357
+ *
358
+ * Records all commands that would be executed without actually running them.
359
+ * Can be configured to return specific outputs or throw errors for testing.
360
+ *
361
+ * @example
362
+ * ```typescript
363
+ * // In tests, substitute the real ShellProvider with MemoryShellProvider
364
+ * const alepha = Alepha.create().with({
365
+ * provide: ShellProvider,
366
+ * use: MemoryShellProvider,
367
+ * });
368
+ *
369
+ * // Configure mock behavior
370
+ * const shell = alepha.inject(MemoryShellProvider);
371
+ * shell.configure({
372
+ * outputs: { "echo hello": "hello\n" },
373
+ * errors: { "failing-cmd": "Command failed" },
374
+ * });
375
+ *
376
+ * // Or use the fluent API
377
+ * shell.outputs.set("another-cmd", "output");
378
+ * shell.errors.set("another-error", "Error message");
379
+ *
380
+ * // Run code that uses ShellProvider
381
+ * const service = alepha.inject(MyService);
382
+ * await service.doSomething();
383
+ *
384
+ * // Verify commands were called
385
+ * expect(shell.calls).toHaveLength(2);
386
+ * expect(shell.calls[0].command).toBe("yarn install");
387
+ * ```
388
+ */
389
+ var MemoryShellProvider = class {
390
+ /**
391
+ * All recorded shell calls.
392
+ */
393
+ calls = [];
394
+ /**
395
+ * Simulated outputs for specific commands.
396
+ */
397
+ outputs = /* @__PURE__ */ new Map();
398
+ /**
399
+ * Commands that should throw an error.
400
+ */
401
+ errors = /* @__PURE__ */ new Map();
402
+ /**
403
+ * Commands considered installed in the system PATH.
404
+ */
405
+ installedCommands = /* @__PURE__ */ new Set();
406
+ /**
407
+ * Configure the mock with predefined outputs, errors, and installed commands.
408
+ */
409
+ configure(options) {
410
+ if (options.outputs) for (const [cmd, output] of Object.entries(options.outputs)) this.outputs.set(cmd, output);
411
+ if (options.errors) for (const [cmd, error] of Object.entries(options.errors)) this.errors.set(cmd, error);
412
+ if (options.installedCommands) for (const cmd of options.installedCommands) this.installedCommands.add(cmd);
413
+ return this;
414
+ }
415
+ /**
416
+ * Record command and return simulated output.
417
+ */
418
+ async run(command, options = {}) {
419
+ this.calls.push({
420
+ command,
421
+ options
422
+ });
423
+ const errorMsg = this.errors.get(command);
424
+ if (errorMsg) throw new Error(errorMsg);
425
+ return this.outputs.get(command) ?? "";
426
+ }
427
+ /**
428
+ * Check if a specific command was called.
429
+ */
430
+ wasCalled(command) {
431
+ return this.calls.some((call) => call.command === command);
432
+ }
433
+ /**
434
+ * Check if a command matching a pattern was called.
435
+ */
436
+ wasCalledMatching(pattern) {
437
+ return this.calls.some((call) => pattern.test(call.command));
438
+ }
439
+ /**
440
+ * Get all calls matching a pattern.
441
+ */
442
+ getCallsMatching(pattern) {
443
+ return this.calls.filter((call) => pattern.test(call.command));
444
+ }
445
+ /**
446
+ * Check if a command is installed.
447
+ */
448
+ async isInstalled(command) {
449
+ return this.installedCommands.has(command);
450
+ }
451
+ /**
452
+ * Reset all recorded state.
453
+ */
454
+ reset() {
455
+ this.calls = [];
456
+ this.outputs.clear();
457
+ this.errors.clear();
458
+ this.installedCommands.clear();
459
+ }
460
+ };
461
+
462
+ //#endregion
463
+ //#region ../../src/system/services/FileDetector.ts
464
+ /**
465
+ * Service for detecting file types and getting content types.
466
+ *
467
+ * @example
468
+ * ```typescript
469
+ * const detector = alepha.inject(FileDetector);
470
+ *
471
+ * // Get content type from filename
472
+ * const mimeType = detector.getContentType("image.png"); // "image/png"
473
+ *
474
+ * // Detect file type by magic bytes
475
+ * const stream = createReadStream('image.png');
476
+ * const result = await detector.detectFileType(stream, 'image.png');
477
+ * console.log(result.mimeType); // 'image/png'
478
+ * console.log(result.verified); // true if magic bytes match
479
+ * ```
480
+ */
481
+ var FileDetector = class FileDetector {
482
+ /**
483
+ * Magic byte signatures for common file formats.
484
+ * Each signature is represented as an array of bytes or null (wildcard).
485
+ */
486
+ static MAGIC_BYTES = {
487
+ png: [{
488
+ signature: [
489
+ 137,
490
+ 80,
491
+ 78,
492
+ 71,
493
+ 13,
494
+ 10,
495
+ 26,
496
+ 10
497
+ ],
498
+ mimeType: "image/png"
499
+ }],
500
+ jpg: [
501
+ {
502
+ signature: [
503
+ 255,
504
+ 216,
505
+ 255,
506
+ 224
507
+ ],
508
+ mimeType: "image/jpeg"
509
+ },
510
+ {
511
+ signature: [
512
+ 255,
513
+ 216,
514
+ 255,
515
+ 225
516
+ ],
517
+ mimeType: "image/jpeg"
518
+ },
519
+ {
520
+ signature: [
521
+ 255,
522
+ 216,
523
+ 255,
524
+ 226
525
+ ],
526
+ mimeType: "image/jpeg"
527
+ },
528
+ {
529
+ signature: [
530
+ 255,
531
+ 216,
532
+ 255,
533
+ 227
534
+ ],
535
+ mimeType: "image/jpeg"
536
+ },
537
+ {
538
+ signature: [
539
+ 255,
540
+ 216,
541
+ 255,
542
+ 232
543
+ ],
544
+ mimeType: "image/jpeg"
545
+ }
546
+ ],
547
+ jpeg: [
548
+ {
549
+ signature: [
550
+ 255,
551
+ 216,
552
+ 255,
553
+ 224
554
+ ],
555
+ mimeType: "image/jpeg"
556
+ },
557
+ {
558
+ signature: [
559
+ 255,
560
+ 216,
561
+ 255,
562
+ 225
563
+ ],
564
+ mimeType: "image/jpeg"
565
+ },
566
+ {
567
+ signature: [
568
+ 255,
569
+ 216,
570
+ 255,
571
+ 226
572
+ ],
573
+ mimeType: "image/jpeg"
574
+ },
575
+ {
576
+ signature: [
577
+ 255,
578
+ 216,
579
+ 255,
580
+ 227
581
+ ],
582
+ mimeType: "image/jpeg"
583
+ },
584
+ {
585
+ signature: [
586
+ 255,
587
+ 216,
588
+ 255,
589
+ 232
590
+ ],
591
+ mimeType: "image/jpeg"
592
+ }
593
+ ],
594
+ gif: [{
595
+ signature: [
596
+ 71,
597
+ 73,
598
+ 70,
599
+ 56,
600
+ 55,
601
+ 97
602
+ ],
603
+ mimeType: "image/gif"
604
+ }, {
605
+ signature: [
606
+ 71,
607
+ 73,
608
+ 70,
609
+ 56,
610
+ 57,
611
+ 97
612
+ ],
613
+ mimeType: "image/gif"
614
+ }],
615
+ webp: [{
616
+ signature: [
617
+ 82,
618
+ 73,
619
+ 70,
620
+ 70,
621
+ null,
622
+ null,
623
+ null,
624
+ null,
625
+ 87,
626
+ 69,
627
+ 66,
628
+ 80
629
+ ],
630
+ mimeType: "image/webp"
631
+ }],
632
+ bmp: [{
633
+ signature: [66, 77],
634
+ mimeType: "image/bmp"
635
+ }],
636
+ ico: [{
637
+ signature: [
638
+ 0,
639
+ 0,
640
+ 1,
641
+ 0
642
+ ],
643
+ mimeType: "image/x-icon"
644
+ }],
645
+ tiff: [{
646
+ signature: [
647
+ 73,
648
+ 73,
649
+ 42,
650
+ 0
651
+ ],
652
+ mimeType: "image/tiff"
653
+ }, {
654
+ signature: [
655
+ 77,
656
+ 77,
657
+ 0,
658
+ 42
659
+ ],
660
+ mimeType: "image/tiff"
661
+ }],
662
+ tif: [{
663
+ signature: [
664
+ 73,
665
+ 73,
666
+ 42,
667
+ 0
668
+ ],
669
+ mimeType: "image/tiff"
670
+ }, {
671
+ signature: [
672
+ 77,
673
+ 77,
674
+ 0,
675
+ 42
676
+ ],
677
+ mimeType: "image/tiff"
678
+ }],
679
+ pdf: [{
680
+ signature: [
681
+ 37,
682
+ 80,
683
+ 68,
684
+ 70,
685
+ 45
686
+ ],
687
+ mimeType: "application/pdf"
688
+ }],
689
+ zip: [
690
+ {
691
+ signature: [
692
+ 80,
693
+ 75,
694
+ 3,
695
+ 4
696
+ ],
697
+ mimeType: "application/zip"
698
+ },
699
+ {
700
+ signature: [
701
+ 80,
702
+ 75,
703
+ 5,
704
+ 6
705
+ ],
706
+ mimeType: "application/zip"
707
+ },
708
+ {
709
+ signature: [
710
+ 80,
711
+ 75,
712
+ 7,
713
+ 8
714
+ ],
715
+ mimeType: "application/zip"
716
+ }
717
+ ],
718
+ rar: [{
719
+ signature: [
720
+ 82,
721
+ 97,
722
+ 114,
723
+ 33,
724
+ 26,
725
+ 7
726
+ ],
727
+ mimeType: "application/vnd.rar"
728
+ }],
729
+ "7z": [{
730
+ signature: [
731
+ 55,
732
+ 122,
733
+ 188,
734
+ 175,
735
+ 39,
736
+ 28
737
+ ],
738
+ mimeType: "application/x-7z-compressed"
739
+ }],
740
+ tar: [{
741
+ signature: [
742
+ 117,
743
+ 115,
744
+ 116,
745
+ 97,
746
+ 114
747
+ ],
748
+ mimeType: "application/x-tar"
749
+ }],
750
+ gz: [{
751
+ signature: [31, 139],
752
+ mimeType: "application/gzip"
753
+ }],
754
+ tgz: [{
755
+ signature: [31, 139],
756
+ mimeType: "application/gzip"
757
+ }],
758
+ mp3: [
759
+ {
760
+ signature: [255, 251],
761
+ mimeType: "audio/mpeg"
762
+ },
763
+ {
764
+ signature: [255, 243],
765
+ mimeType: "audio/mpeg"
766
+ },
767
+ {
768
+ signature: [255, 242],
769
+ mimeType: "audio/mpeg"
770
+ },
771
+ {
772
+ signature: [
773
+ 73,
774
+ 68,
775
+ 51
776
+ ],
777
+ mimeType: "audio/mpeg"
778
+ }
779
+ ],
780
+ wav: [{
781
+ signature: [
782
+ 82,
783
+ 73,
784
+ 70,
785
+ 70,
786
+ null,
787
+ null,
788
+ null,
789
+ null,
790
+ 87,
791
+ 65,
792
+ 86,
793
+ 69
794
+ ],
795
+ mimeType: "audio/wav"
796
+ }],
797
+ ogg: [{
798
+ signature: [
799
+ 79,
800
+ 103,
801
+ 103,
802
+ 83
803
+ ],
804
+ mimeType: "audio/ogg"
805
+ }],
806
+ flac: [{
807
+ signature: [
808
+ 102,
809
+ 76,
810
+ 97,
811
+ 67
812
+ ],
813
+ mimeType: "audio/flac"
814
+ }],
815
+ mp4: [
816
+ {
817
+ signature: [
818
+ null,
819
+ null,
820
+ null,
821
+ null,
822
+ 102,
823
+ 116,
824
+ 121,
825
+ 112
826
+ ],
827
+ mimeType: "video/mp4"
828
+ },
829
+ {
830
+ signature: [
831
+ null,
832
+ null,
833
+ null,
834
+ null,
835
+ 102,
836
+ 116,
837
+ 121,
838
+ 112,
839
+ 105,
840
+ 115,
841
+ 111,
842
+ 109
843
+ ],
844
+ mimeType: "video/mp4"
845
+ },
846
+ {
847
+ signature: [
848
+ null,
849
+ null,
850
+ null,
851
+ null,
852
+ 102,
853
+ 116,
854
+ 121,
855
+ 112,
856
+ 109,
857
+ 112,
858
+ 52,
859
+ 50
860
+ ],
861
+ mimeType: "video/mp4"
862
+ }
863
+ ],
864
+ webm: [{
865
+ signature: [
866
+ 26,
867
+ 69,
868
+ 223,
869
+ 163
870
+ ],
871
+ mimeType: "video/webm"
872
+ }],
873
+ avi: [{
874
+ signature: [
875
+ 82,
876
+ 73,
877
+ 70,
878
+ 70,
879
+ null,
880
+ null,
881
+ null,
882
+ null,
883
+ 65,
884
+ 86,
885
+ 73,
886
+ 32
887
+ ],
888
+ mimeType: "video/x-msvideo"
889
+ }],
890
+ mov: [{
891
+ signature: [
892
+ null,
893
+ null,
894
+ null,
895
+ null,
896
+ 102,
897
+ 116,
898
+ 121,
899
+ 112,
900
+ 113,
901
+ 116,
902
+ 32,
903
+ 32
904
+ ],
905
+ mimeType: "video/quicktime"
906
+ }],
907
+ mkv: [{
908
+ signature: [
909
+ 26,
910
+ 69,
911
+ 223,
912
+ 163
913
+ ],
914
+ mimeType: "video/x-matroska"
915
+ }],
916
+ docx: [{
917
+ signature: [
918
+ 80,
919
+ 75,
920
+ 3,
921
+ 4
922
+ ],
923
+ mimeType: "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
924
+ }],
925
+ xlsx: [{
926
+ signature: [
927
+ 80,
928
+ 75,
929
+ 3,
930
+ 4
931
+ ],
932
+ mimeType: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
933
+ }],
934
+ pptx: [{
935
+ signature: [
936
+ 80,
937
+ 75,
938
+ 3,
939
+ 4
940
+ ],
941
+ mimeType: "application/vnd.openxmlformats-officedocument.presentationml.presentation"
942
+ }],
943
+ doc: [{
944
+ signature: [
945
+ 208,
946
+ 207,
947
+ 17,
948
+ 224,
949
+ 161,
950
+ 177,
951
+ 26,
952
+ 225
953
+ ],
954
+ mimeType: "application/msword"
955
+ }],
956
+ xls: [{
957
+ signature: [
958
+ 208,
959
+ 207,
960
+ 17,
961
+ 224,
962
+ 161,
963
+ 177,
964
+ 26,
965
+ 225
966
+ ],
967
+ mimeType: "application/vnd.ms-excel"
968
+ }],
969
+ ppt: [{
970
+ signature: [
971
+ 208,
972
+ 207,
973
+ 17,
974
+ 224,
975
+ 161,
976
+ 177,
977
+ 26,
978
+ 225
979
+ ],
980
+ mimeType: "application/vnd.ms-powerpoint"
981
+ }]
982
+ };
983
+ /**
984
+ * All possible format signatures for checking against actual file content
985
+ */
986
+ static ALL_SIGNATURES = Object.entries(FileDetector.MAGIC_BYTES).flatMap(([ext, signatures]) => signatures.map((sig) => ({
987
+ ext,
988
+ ...sig
989
+ })));
990
+ /**
991
+ * MIME type map for file extensions.
992
+ *
993
+ * Can be used to get the content type of file based on its extension.
994
+ * Feel free to add more mime types in your project!
995
+ */
996
+ static mimeMap = {
997
+ json: "application/json",
998
+ txt: "text/plain",
999
+ html: "text/html",
1000
+ htm: "text/html",
1001
+ xml: "application/xml",
1002
+ csv: "text/csv",
1003
+ pdf: "application/pdf",
1004
+ md: "text/markdown",
1005
+ markdown: "text/markdown",
1006
+ rtf: "application/rtf",
1007
+ css: "text/css",
1008
+ js: "application/javascript",
1009
+ mjs: "application/javascript",
1010
+ ts: "application/typescript",
1011
+ jsx: "text/jsx",
1012
+ tsx: "text/tsx",
1013
+ zip: "application/zip",
1014
+ rar: "application/vnd.rar",
1015
+ "7z": "application/x-7z-compressed",
1016
+ tar: "application/x-tar",
1017
+ gz: "application/gzip",
1018
+ tgz: "application/gzip",
1019
+ png: "image/png",
1020
+ jpg: "image/jpeg",
1021
+ jpeg: "image/jpeg",
1022
+ gif: "image/gif",
1023
+ webp: "image/webp",
1024
+ svg: "image/svg+xml",
1025
+ bmp: "image/bmp",
1026
+ ico: "image/x-icon",
1027
+ tiff: "image/tiff",
1028
+ tif: "image/tiff",
1029
+ mp3: "audio/mpeg",
1030
+ wav: "audio/wav",
1031
+ ogg: "audio/ogg",
1032
+ m4a: "audio/mp4",
1033
+ aac: "audio/aac",
1034
+ flac: "audio/flac",
1035
+ mp4: "video/mp4",
1036
+ webm: "video/webm",
1037
+ avi: "video/x-msvideo",
1038
+ mov: "video/quicktime",
1039
+ wmv: "video/x-ms-wmv",
1040
+ flv: "video/x-flv",
1041
+ mkv: "video/x-matroska",
1042
+ doc: "application/msword",
1043
+ docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
1044
+ xls: "application/vnd.ms-excel",
1045
+ xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
1046
+ ppt: "application/vnd.ms-powerpoint",
1047
+ pptx: "application/vnd.openxmlformats-officedocument.presentationml.presentation",
1048
+ woff: "font/woff",
1049
+ woff2: "font/woff2",
1050
+ ttf: "font/ttf",
1051
+ otf: "font/otf",
1052
+ eot: "application/vnd.ms-fontobject"
1053
+ };
1054
+ /**
1055
+ * Reverse MIME type map for looking up extensions from MIME types.
1056
+ * Prefers shorter, more common extensions when multiple exist.
1057
+ */
1058
+ static reverseMimeMap = (() => {
1059
+ const reverse = {};
1060
+ for (const [ext, mimeType] of Object.entries(FileDetector.mimeMap)) if (!reverse[mimeType]) reverse[mimeType] = ext;
1061
+ return reverse;
1062
+ })();
1063
+ /**
1064
+ * Returns the file extension for a given MIME type.
1065
+ *
1066
+ * @param mimeType - The MIME type to look up
1067
+ * @returns The file extension (without dot), or "bin" if not found
1068
+ *
1069
+ * @example
1070
+ * ```typescript
1071
+ * const detector = alepha.inject(FileDetector);
1072
+ * const ext = detector.getExtensionFromMimeType("image/png"); // "png"
1073
+ * const ext2 = detector.getExtensionFromMimeType("application/octet-stream"); // "bin"
1074
+ * ```
1075
+ */
1076
+ getExtensionFromMimeType(mimeType) {
1077
+ return FileDetector.reverseMimeMap[mimeType] || "bin";
1078
+ }
1079
+ /**
1080
+ * Returns the content type of file based on its filename.
1081
+ *
1082
+ * @param filename - The filename to check
1083
+ * @returns The MIME type
1084
+ *
1085
+ * @example
1086
+ * ```typescript
1087
+ * const detector = alepha.inject(FileDetector);
1088
+ * const mimeType = detector.getContentType("image.png"); // "image/png"
1089
+ * ```
1090
+ */
1091
+ getContentType(filename) {
1092
+ const ext = filename.toLowerCase().split(".").pop() || "";
1093
+ return FileDetector.mimeMap[ext] || "application/octet-stream";
1094
+ }
1095
+ /**
1096
+ * Detects the file type by checking magic bytes against the stream content.
1097
+ *
1098
+ * @param stream - The readable stream to check
1099
+ * @param filename - The filename (used to get the extension)
1100
+ * @returns File type information including MIME type, extension, and verification status
1101
+ *
1102
+ * @example
1103
+ * ```typescript
1104
+ * const detector = alepha.inject(FileDetector);
1105
+ * const stream = createReadStream('image.png');
1106
+ * const result = await detector.detectFileType(stream, 'image.png');
1107
+ * console.log(result.mimeType); // 'image/png'
1108
+ * console.log(result.verified); // true if magic bytes match
1109
+ * ```
1110
+ */
1111
+ async detectFileType(stream, filename) {
1112
+ const expectedMimeType = this.getContentType(filename);
1113
+ const lastDotIndex = filename.lastIndexOf(".");
1114
+ const ext = lastDotIndex > 0 ? filename.substring(lastDotIndex + 1).toLowerCase() : "";
1115
+ const { buffer, stream: newStream } = await this.peekBytes(stream, 16);
1116
+ const expectedSignatures = FileDetector.MAGIC_BYTES[ext];
1117
+ if (expectedSignatures) {
1118
+ for (const { signature, mimeType } of expectedSignatures) if (this.matchesSignature(buffer, signature)) return {
1119
+ mimeType,
1120
+ extension: ext,
1121
+ verified: true,
1122
+ stream: newStream
1123
+ };
1124
+ }
1125
+ for (const { ext: detectedExt, signature, mimeType } of FileDetector.ALL_SIGNATURES) if (detectedExt !== ext && this.matchesSignature(buffer, signature)) return {
1126
+ mimeType,
1127
+ extension: detectedExt,
1128
+ verified: true,
1129
+ stream: newStream
1130
+ };
1131
+ return {
1132
+ mimeType: expectedMimeType,
1133
+ extension: ext,
1134
+ verified: false,
1135
+ stream: newStream
1136
+ };
1137
+ }
1138
+ /**
1139
+ * Reads all bytes from a stream and returns the first N bytes along with a new stream containing all data.
1140
+ * This approach reads the entire stream upfront to avoid complex async handling issues.
1141
+ *
1142
+ * @protected
1143
+ */
1144
+ async peekBytes(stream, numBytes) {
1145
+ const chunks = [];
1146
+ for await (const chunk of stream) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
1147
+ const allData = Buffer.concat(chunks);
1148
+ return {
1149
+ buffer: allData.subarray(0, numBytes),
1150
+ stream: Readable.from(allData)
1151
+ };
1152
+ }
1153
+ /**
1154
+ * Checks if a buffer matches a magic byte signature.
1155
+ *
1156
+ * @protected
1157
+ */
1158
+ matchesSignature(buffer, signature) {
1159
+ if (buffer.length < signature.length) return false;
1160
+ for (let i = 0; i < signature.length; i++) if (signature[i] !== null && buffer[i] !== signature[i]) return false;
1161
+ return true;
1162
+ }
1163
+ };
1164
+
1165
+ //#endregion
1166
+ //#region ../../src/system/providers/NodeFileSystemProvider.ts
1167
+ /**
1168
+ * Node.js implementation of FileSystem interface.
1169
+ *
1170
+ * @example
1171
+ * ```typescript
1172
+ * const fs = alepha.inject(NodeFileSystemProvider);
1173
+ *
1174
+ * // Create from URL
1175
+ * const file1 = fs.createFile({ url: "file:///path/to/file.png" });
1176
+ *
1177
+ * // Create from Buffer
1178
+ * const file2 = fs.createFile({ buffer: Buffer.from("hello"), name: "hello.txt" });
1179
+ *
1180
+ * // Create from text
1181
+ * const file3 = fs.createFile({ text: "Hello, world!", name: "greeting.txt" });
1182
+ *
1183
+ * // File operations
1184
+ * await fs.mkdir("/tmp/mydir", { recursive: true });
1185
+ * await fs.cp("/src/file.txt", "/dest/file.txt");
1186
+ * await fs.mv("/old/path.txt", "/new/path.txt");
1187
+ * const files = await fs.ls("/tmp");
1188
+ * await fs.rm("/tmp/file.txt");
1189
+ * ```
1190
+ */
1191
+ var NodeFileSystemProvider = class {
1192
+ detector = $inject(FileDetector);
1193
+ json = $inject(Json);
1194
+ join(...paths) {
1195
+ return join(...paths);
1196
+ }
1197
+ /**
1198
+ * Creates a FileLike object from various sources.
1199
+ *
1200
+ * @param options - Options for creating the file
1201
+ * @returns A FileLike object
1202
+ *
1203
+ * @example
1204
+ * ```typescript
1205
+ * const fs = alepha.inject(NodeFileSystemProvider);
1206
+ *
1207
+ * // From URL
1208
+ * const file1 = fs.createFile({ url: "https://example.com/image.png" });
1209
+ *
1210
+ * // From Buffer
1211
+ * const file2 = fs.createFile({
1212
+ * buffer: Buffer.from("hello"),
1213
+ * name: "hello.txt",
1214
+ * type: "text/plain"
1215
+ * });
1216
+ *
1217
+ * // From text
1218
+ * const file3 = fs.createFile({ text: "Hello!", name: "greeting.txt" });
1219
+ *
1220
+ * // From stream with detection
1221
+ * const stream = createReadStream("/path/to/file.png");
1222
+ * const file4 = fs.createFile({ stream, name: "image.png" });
1223
+ * ```
1224
+ */
1225
+ createFile(options) {
1226
+ if ("path" in options) {
1227
+ const path = options.path;
1228
+ const filename = path.split("/").pop() || "file";
1229
+ return this.createFileFromUrl(`file://${path}`, {
1230
+ type: options.type,
1231
+ name: options.name || filename
1232
+ });
1233
+ }
1234
+ if ("url" in options) return this.createFileFromUrl(options.url, {
1235
+ type: options.type,
1236
+ name: options.name
1237
+ });
1238
+ if ("response" in options) {
1239
+ if (!options.response.body) throw new AlephaError("Response has no body stream");
1240
+ const res = options.response;
1241
+ const sizeHeader = res.headers.get("content-length");
1242
+ const size = sizeHeader ? parseInt(sizeHeader, 10) : void 0;
1243
+ let name = options.name;
1244
+ const contentDisposition = res.headers.get("content-disposition");
1245
+ if (contentDisposition && !name) {
1246
+ const match = contentDisposition.match(/filename="?([^"]+)"?/);
1247
+ if (match) name = match[1];
1248
+ }
1249
+ const type = options.type || res.headers.get("content-type") || void 0;
1250
+ return this.createFileFromStream(options.response.body, {
1251
+ type,
1252
+ name,
1253
+ size
1254
+ });
1255
+ }
1256
+ if ("file" in options) return this.createFileFromWebFile(options.file, {
1257
+ type: options.type,
1258
+ name: options.name,
1259
+ size: options.size
1260
+ });
1261
+ if ("buffer" in options) return this.createFileFromBuffer(options.buffer, {
1262
+ type: options.type,
1263
+ name: options.name
1264
+ });
1265
+ if ("arrayBuffer" in options) return this.createFileFromBuffer(Buffer.from(options.arrayBuffer), {
1266
+ type: options.type,
1267
+ name: options.name
1268
+ });
1269
+ if ("text" in options) return this.createFileFromBuffer(Buffer.from(options.text, "utf-8"), {
1270
+ type: options.type || "text/plain",
1271
+ name: options.name || "file.txt"
1272
+ });
1273
+ if ("stream" in options) return this.createFileFromStream(options.stream, {
1274
+ type: options.type,
1275
+ name: options.name,
1276
+ size: options.size
1277
+ });
1278
+ throw new AlephaError("Invalid createFile options: no valid source provided");
1279
+ }
1280
+ /**
1281
+ * Removes a file or directory.
1282
+ *
1283
+ * @param path - The path to remove
1284
+ * @param options - Remove options
1285
+ *
1286
+ * @example
1287
+ * ```typescript
1288
+ * const fs = alepha.inject(NodeFileSystemProvider);
1289
+ *
1290
+ * // Remove a file
1291
+ * await fs.rm("/tmp/file.txt");
1292
+ *
1293
+ * // Remove a directory recursively
1294
+ * await fs.rm("/tmp/mydir", { recursive: true });
1295
+ *
1296
+ * // Remove with force (no error if doesn't exist)
1297
+ * await fs.rm("/tmp/maybe-exists.txt", { force: true });
1298
+ * ```
1299
+ */
1300
+ async rm(path, options) {
1301
+ await rm(path, options);
1302
+ }
1303
+ /**
1304
+ * Copies a file or directory.
1305
+ *
1306
+ * @param src - Source path
1307
+ * @param dest - Destination path
1308
+ * @param options - Copy options
1309
+ *
1310
+ * @example
1311
+ * ```typescript
1312
+ * const fs = alepha.inject(NodeFileSystemProvider);
1313
+ *
1314
+ * // Copy a file
1315
+ * await fs.cp("/src/file.txt", "/dest/file.txt");
1316
+ *
1317
+ * // Copy a directory recursively
1318
+ * await fs.cp("/src/dir", "/dest/dir", { recursive: true });
1319
+ *
1320
+ * // Copy with force (overwrite existing)
1321
+ * await fs.cp("/src/file.txt", "/dest/file.txt", { force: true });
1322
+ * ```
1323
+ */
1324
+ async cp(src, dest, options) {
1325
+ if ((await stat(src)).isDirectory()) {
1326
+ if (!options?.recursive) throw new Error(`Cannot copy directory without recursive option: ${src}`);
1327
+ await cp(src, dest, {
1328
+ recursive: true,
1329
+ force: options?.force ?? false
1330
+ });
1331
+ } else await copyFile(src, dest);
1332
+ }
1333
+ /**
1334
+ * Moves/renames a file or directory.
1335
+ *
1336
+ * @param src - Source path
1337
+ * @param dest - Destination path
1338
+ *
1339
+ * @example
1340
+ * ```typescript
1341
+ * const fs = alepha.inject(NodeFileSystemProvider);
1342
+ *
1343
+ * // Move/rename a file
1344
+ * await fs.mv("/old/path.txt", "/new/path.txt");
1345
+ *
1346
+ * // Move a directory
1347
+ * await fs.mv("/old/dir", "/new/dir");
1348
+ * ```
1349
+ */
1350
+ async mv(src, dest) {
1351
+ await rename(src, dest);
1352
+ }
1353
+ /**
1354
+ * Creates a directory.
1355
+ *
1356
+ * @param path - The directory path to create
1357
+ * @param options - Mkdir options
1358
+ *
1359
+ * @example
1360
+ * ```typescript
1361
+ * const fs = alepha.inject(NodeFileSystemProvider);
1362
+ *
1363
+ * // Create a directory
1364
+ * await fs.mkdir("/tmp/mydir");
1365
+ *
1366
+ * // Create nested directories
1367
+ * await fs.mkdir("/tmp/path/to/dir", { recursive: true });
1368
+ *
1369
+ * // Create with specific permissions
1370
+ * await fs.mkdir("/tmp/mydir", { mode: 0o755 });
1371
+ * ```
1372
+ */
1373
+ async mkdir(path, options = {}) {
1374
+ const p = mkdir(path, {
1375
+ recursive: options.recursive ?? true,
1376
+ mode: options.mode
1377
+ });
1378
+ if (options.force === false) await p;
1379
+ else await p.catch(() => {});
1380
+ }
1381
+ /**
1382
+ * Lists files in a directory.
1383
+ *
1384
+ * @param path - The directory path to list
1385
+ * @param options - List options
1386
+ * @returns Array of filenames
1387
+ *
1388
+ * @example
1389
+ * ```typescript
1390
+ * const fs = alepha.inject(NodeFileSystemProvider);
1391
+ *
1392
+ * // List files in a directory
1393
+ * const files = await fs.ls("/tmp");
1394
+ * console.log(files); // ["file1.txt", "file2.txt", "subdir"]
1395
+ *
1396
+ * // List with hidden files
1397
+ * const allFiles = await fs.ls("/tmp", { hidden: true });
1398
+ *
1399
+ * // List recursively
1400
+ * const allFilesRecursive = await fs.ls("/tmp", { recursive: true });
1401
+ * ```
1402
+ */
1403
+ async ls(path, options) {
1404
+ const entries = await readdir(path);
1405
+ const filteredEntries = options?.hidden ? entries : entries.filter((e) => !e.startsWith("."));
1406
+ if (options?.recursive) {
1407
+ const allFiles = [];
1408
+ for (const entry of filteredEntries) {
1409
+ const fullPath = join(path, entry);
1410
+ if ((await stat(fullPath)).isDirectory()) {
1411
+ allFiles.push(entry);
1412
+ const subFiles = await this.ls(fullPath, options);
1413
+ allFiles.push(...subFiles.map((f) => join(entry, f)));
1414
+ } else allFiles.push(entry);
1415
+ }
1416
+ return allFiles;
1417
+ }
1418
+ return filteredEntries;
1419
+ }
1420
+ /**
1421
+ * Checks if a file or directory exists.
1422
+ *
1423
+ * @param path - The path to check
1424
+ * @returns True if the path exists, false otherwise
1425
+ *
1426
+ * @example
1427
+ * ```typescript
1428
+ * const fs = alepha.inject(NodeFileSystemProvider);
1429
+ *
1430
+ * if (await fs.exists("/tmp/file.txt")) {
1431
+ * console.log("File exists");
1432
+ * }
1433
+ * ```
1434
+ */
1435
+ async exists(path) {
1436
+ try {
1437
+ await access(path);
1438
+ return true;
1439
+ } catch {
1440
+ return false;
1441
+ }
1442
+ }
1443
+ /**
1444
+ * Reads the content of a file.
1445
+ *
1446
+ * @param path - The file path to read
1447
+ * @returns The file content as a Buffer
1448
+ *
1449
+ * @example
1450
+ * ```typescript
1451
+ * const fs = alepha.inject(NodeFileSystemProvider);
1452
+ *
1453
+ * const buffer = await fs.readFile("/tmp/file.txt");
1454
+ * console.log(buffer.toString("utf-8"));
1455
+ * ```
1456
+ */
1457
+ async readFile(path) {
1458
+ return await readFile(path);
1459
+ }
1460
+ /**
1461
+ * Writes data to a file.
1462
+ *
1463
+ * @param path - The file path to write to
1464
+ * @param data - The data to write (Buffer or string)
1465
+ *
1466
+ * @example
1467
+ * ```typescript
1468
+ * const fs = alepha.inject(NodeFileSystemProvider);
1469
+ *
1470
+ * // Write string
1471
+ * await fs.writeFile("/tmp/file.txt", "Hello, world!");
1472
+ *
1473
+ * // Write Buffer
1474
+ * await fs.writeFile("/tmp/file.bin", Buffer.from([0x01, 0x02, 0x03]));
1475
+ * ```
1476
+ */
1477
+ async writeFile(path, data) {
1478
+ if (isFileLike(data)) {
1479
+ await writeFile(path, Readable.from(data.stream()));
1480
+ return;
1481
+ }
1482
+ await writeFile(path, data);
1483
+ }
1484
+ /**
1485
+ * Reads the content of a file as a string.
1486
+ *
1487
+ * @param path - The file path to read
1488
+ * @returns The file content as a string
1489
+ *
1490
+ * @example
1491
+ * ```typescript
1492
+ * const fs = alepha.inject(NodeFileSystemProvider);
1493
+ * const content = await fs.readTextFile("/tmp/file.txt");
1494
+ * ```
1495
+ */
1496
+ async readTextFile(path) {
1497
+ return (await this.readFile(path)).toString("utf-8");
1498
+ }
1499
+ /**
1500
+ * Reads the content of a file as JSON.
1501
+ *
1502
+ * @param path - The file path to read
1503
+ * @returns The parsed JSON content
1504
+ *
1505
+ * @example
1506
+ * ```typescript
1507
+ * const fs = alepha.inject(NodeFileSystemProvider);
1508
+ * const config = await fs.readJsonFile<{ name: string }>("/tmp/config.json");
1509
+ * ```
1510
+ */
1511
+ async readJsonFile(path) {
1512
+ const text = await this.readTextFile(path);
1513
+ return this.json.parse(text);
1514
+ }
1515
+ /**
1516
+ * Creates a FileLike object from a Web File.
1517
+ *
1518
+ * @protected
1519
+ */
1520
+ createFileFromWebFile(source, options = {}) {
1521
+ const name = options.name ?? source.name;
1522
+ return {
1523
+ name,
1524
+ type: options.type ?? (source.type || this.detector.getContentType(name)),
1525
+ size: options.size ?? source.size ?? 0,
1526
+ lastModified: source.lastModified || Date.now(),
1527
+ stream: () => source.stream(),
1528
+ arrayBuffer: async () => {
1529
+ return await source.arrayBuffer();
1530
+ },
1531
+ text: async () => {
1532
+ return await source.text();
1533
+ }
1534
+ };
1535
+ }
1536
+ /**
1537
+ * Creates a FileLike object from a Buffer.
1538
+ *
1539
+ * @protected
1540
+ */
1541
+ createFileFromBuffer(source, options = {}) {
1542
+ const name = options.name ?? "file";
1543
+ return {
1544
+ name,
1545
+ type: options.type ?? this.detector.getContentType(options.name ?? name),
1546
+ size: source.byteLength,
1547
+ lastModified: Date.now(),
1548
+ stream: () => Readable.from(source),
1549
+ arrayBuffer: async () => {
1550
+ return this.bufferToArrayBuffer(source);
1551
+ },
1552
+ text: async () => {
1553
+ return source.toString("utf-8");
1554
+ }
1555
+ };
1556
+ }
1557
+ /**
1558
+ * Creates a FileLike object from a stream.
1559
+ *
1560
+ * @protected
1561
+ */
1562
+ createFileFromStream(source, options = {}) {
1563
+ let buffer = null;
1564
+ return {
1565
+ name: options.name ?? "file",
1566
+ type: options.type ?? this.detector.getContentType(options.name ?? "file"),
1567
+ size: options.size ?? 0,
1568
+ lastModified: Date.now(),
1569
+ stream: () => source,
1570
+ _buffer: null,
1571
+ arrayBuffer: async () => {
1572
+ buffer ??= await this.streamToBuffer(source);
1573
+ return this.bufferToArrayBuffer(buffer);
1574
+ },
1575
+ text: async () => {
1576
+ buffer ??= await this.streamToBuffer(source);
1577
+ return buffer.toString("utf-8");
1578
+ }
1579
+ };
1580
+ }
1581
+ /**
1582
+ * Creates a FileLike object from a URL.
1583
+ *
1584
+ * @protected
1585
+ */
1586
+ createFileFromUrl(url, options = {}) {
1587
+ const parsedUrl = new URL(url);
1588
+ const filename = options.name || parsedUrl.pathname.split("/").pop() || "file";
1589
+ let buffer = null;
1590
+ return {
1591
+ name: filename,
1592
+ type: options.type ?? this.detector.getContentType(filename),
1593
+ size: 0,
1594
+ lastModified: Date.now(),
1595
+ stream: () => this.createStreamFromUrl(url),
1596
+ arrayBuffer: async () => {
1597
+ buffer ??= await this.loadFromUrl(url);
1598
+ return this.bufferToArrayBuffer(buffer);
1599
+ },
1600
+ text: async () => {
1601
+ buffer ??= await this.loadFromUrl(url);
1602
+ return buffer.toString("utf-8");
1603
+ },
1604
+ filepath: url
1605
+ };
1606
+ }
1607
+ /**
1608
+ * Gets a streaming response from a URL.
1609
+ *
1610
+ * @protected
1611
+ */
1612
+ getStreamingResponse(url) {
1613
+ const stream = new PassThrough();
1614
+ fetch(url).then((res) => Readable.fromWeb(res.body).pipe(stream)).catch((err) => stream.destroy(err));
1615
+ return stream;
1616
+ }
1617
+ /**
1618
+ * Loads data from a URL.
1619
+ *
1620
+ * @protected
1621
+ */
1622
+ async loadFromUrl(url) {
1623
+ const parsedUrl = new URL(url);
1624
+ if (parsedUrl.protocol === "file:") return await readFile(fileURLToPath(url));
1625
+ else if (parsedUrl.protocol === "http:" || parsedUrl.protocol === "https:") {
1626
+ const response = await fetch(url);
1627
+ if (!response.ok) throw new Error(`Failed to fetch ${url}: ${response.status} ${response.statusText}`);
1628
+ const arrayBuffer = await response.arrayBuffer();
1629
+ return Buffer.from(arrayBuffer);
1630
+ } else throw new Error(`Unsupported protocol: ${parsedUrl.protocol}`);
1631
+ }
1632
+ /**
1633
+ * Creates a stream from a URL.
1634
+ *
1635
+ * @protected
1636
+ */
1637
+ createStreamFromUrl(url) {
1638
+ const parsedUrl = new URL(url);
1639
+ if (parsedUrl.protocol === "file:") return createReadStream(fileURLToPath(url));
1640
+ else if (parsedUrl.protocol === "http:" || parsedUrl.protocol === "https:") return this.getStreamingResponse(url);
1641
+ else throw new AlephaError(`Unsupported protocol: ${parsedUrl.protocol}`);
1642
+ }
1643
+ /**
1644
+ * Converts a stream-like object to a Buffer.
1645
+ *
1646
+ * @protected
1647
+ */
1648
+ async streamToBuffer(streamLike) {
1649
+ const stream = streamLike instanceof Readable ? streamLike : Readable.fromWeb(streamLike);
1650
+ return new Promise((resolve, reject) => {
1651
+ const buffer = [];
1652
+ stream.on("data", (chunk) => buffer.push(Buffer.from(chunk)));
1653
+ stream.on("end", () => resolve(Buffer.concat(buffer)));
1654
+ stream.on("error", (err) => reject(new AlephaError("Error converting stream", { cause: err })));
1655
+ });
1656
+ }
1657
+ /**
1658
+ * Converts a Node.js Buffer to an ArrayBuffer.
1659
+ *
1660
+ * @protected
1661
+ */
1662
+ bufferToArrayBuffer(buffer) {
1663
+ return buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
1664
+ }
1665
+ };
1666
+
1667
+ //#endregion
1668
+ //#region ../../src/system/providers/NodeShellProvider.ts
1669
+ /**
1670
+ * Node.js implementation of ShellProvider.
1671
+ *
1672
+ * Executes shell commands using Node.js child_process module.
1673
+ * Supports binary resolution from node_modules/.bin for local packages.
1674
+ */
1675
+ var NodeShellProvider = class {
1676
+ log = $logger();
1677
+ fs = $inject(FileSystemProvider);
1678
+ /**
1679
+ * Run a shell command or binary.
1680
+ */
1681
+ async run(command, options = {}) {
1682
+ const { resolve = false, capture = false, root, env } = options;
1683
+ const cwd = root ?? process.cwd();
1684
+ this.log.debug(`Shell: ${command}`, {
1685
+ cwd,
1686
+ resolve,
1687
+ capture
1688
+ });
1689
+ let executable;
1690
+ let args;
1691
+ if (resolve) {
1692
+ const [bin, ...rest] = command.split(" ");
1693
+ executable = await this.resolveExecutable(bin, cwd);
1694
+ args = rest;
1695
+ } else [executable, ...args] = command.split(" ");
1696
+ if (capture) return this.execCapture(command, {
1697
+ cwd,
1698
+ env
1699
+ });
1700
+ return this.execInherit(executable, args, {
1701
+ cwd,
1702
+ env
1703
+ });
1704
+ }
1705
+ /**
1706
+ * Execute command with inherited stdio (streams to terminal).
1707
+ */
1708
+ async execInherit(executable, args, options) {
1709
+ const proc = spawn(executable, args, {
1710
+ stdio: "inherit",
1711
+ cwd: options.cwd,
1712
+ env: {
1713
+ ...process.env,
1714
+ ...options.env
1715
+ }
1716
+ });
1717
+ return new Promise((resolve, reject) => {
1718
+ proc.on("exit", (code) => {
1719
+ if (code === 0 || code === null) resolve("");
1720
+ else reject(new AlephaError(`Command exited with code ${code}`));
1721
+ });
1722
+ proc.on("error", reject);
1723
+ });
1724
+ }
1725
+ /**
1726
+ * Execute command and capture stdout.
1727
+ */
1728
+ execCapture(command, options) {
1729
+ return new Promise((resolve, reject) => {
1730
+ exec(command, {
1731
+ cwd: options.cwd,
1732
+ env: {
1733
+ ...process.env,
1734
+ LOG_FORMAT: "pretty",
1735
+ ...options.env
1736
+ }
1737
+ }, (err, stdout) => {
1738
+ if (err) {
1739
+ err.stdout = stdout;
1740
+ reject(err);
1741
+ } else resolve(stdout);
1742
+ });
1743
+ });
1744
+ }
1745
+ /**
1746
+ * Resolve executable path from node_modules/.bin.
1747
+ *
1748
+ * Search order:
1749
+ * 1. Local: node_modules/.bin/
1750
+ * 2. Pnpm nested: node_modules/alepha/node_modules/.bin/
1751
+ * 3. Monorepo: Walk up to 3 parent directories
1752
+ */
1753
+ async resolveExecutable(name, root) {
1754
+ const suffix = process.platform === "win32" ? ".cmd" : "";
1755
+ let execPath = await this.findExecutable(root, `node_modules/.bin/${name}${suffix}`);
1756
+ if (!execPath) execPath = await this.findExecutable(root, `node_modules/alepha/node_modules/.bin/${name}${suffix}`);
1757
+ if (!execPath) {
1758
+ let parentDir = this.fs.join(root, "..");
1759
+ for (let i = 0; i < 3; i++) {
1760
+ execPath = await this.findExecutable(parentDir, `node_modules/.bin/${name}${suffix}`);
1761
+ if (execPath) break;
1762
+ parentDir = this.fs.join(parentDir, "..");
1763
+ }
1764
+ }
1765
+ if (!execPath) throw new AlephaError(`Could not find executable for '${name}'. Make sure the package is installed.`);
1766
+ return execPath;
1767
+ }
1768
+ /**
1769
+ * Check if executable exists at path.
1770
+ */
1771
+ async findExecutable(root, relativePath) {
1772
+ const fullPath = this.fs.join(root, relativePath);
1773
+ if (await this.fs.exists(fullPath)) return fullPath;
1774
+ }
1775
+ /**
1776
+ * Check if a command is installed and available in the system PATH.
1777
+ */
1778
+ isInstalled(command) {
1779
+ return new Promise((resolve) => {
1780
+ exec(process.platform === "win32" ? `where ${command}` : `command -v ${command}`, (error) => resolve(!error));
1781
+ });
1782
+ }
1783
+ };
1784
+
1785
+ //#endregion
1786
+ //#region ../../src/system/providers/ShellProvider.ts
1787
+ /**
1788
+ * Abstract provider for executing shell commands and binaries.
1789
+ *
1790
+ * Implementations:
1791
+ * - `NodeShellProvider` - Real shell execution using Node.js child_process
1792
+ * - `MemoryShellProvider` - In-memory mock for testing
1793
+ *
1794
+ * @example
1795
+ * ```typescript
1796
+ * class MyService {
1797
+ * protected readonly shell = $inject(ShellProvider);
1798
+ *
1799
+ * async build() {
1800
+ * // Run shell command directly
1801
+ * await this.shell.run("yarn install");
1802
+ *
1803
+ * // Run local binary with resolution
1804
+ * await this.shell.run("vite build", { resolve: true });
1805
+ *
1806
+ * // Capture output
1807
+ * const output = await this.shell.run("echo hello", { capture: true });
1808
+ * }
1809
+ * }
1810
+ * ```
1811
+ */
1812
+ var ShellProvider = class {};
1813
+
1814
+ //#endregion
1815
+ //#region ../../src/system/index.ts
1816
+ /**
1817
+ * | type | quality | stability |
1818
+ * |------|---------|-----------|
1819
+ * | tooling | standard | stable |
1820
+ *
1821
+ * System-level abstractions for portable code across runtimes.
1822
+ *
1823
+ * **Features:**
1824
+ * - File system operations (read, write, exists, etc.)
1825
+ * - Shell command execution
1826
+ * - File type detection and MIME utilities
1827
+ * - Memory implementations for testing
1828
+ *
1829
+ * @module alepha.system
1830
+ */
1831
+ const AlephaSystem = $module({
1832
+ name: "alepha.system",
1833
+ primitives: [],
1834
+ services: [
1835
+ FileDetector,
1836
+ FileSystemProvider,
1837
+ MemoryFileSystemProvider,
1838
+ NodeFileSystemProvider,
1839
+ ShellProvider,
1840
+ MemoryShellProvider,
1841
+ NodeShellProvider
1842
+ ],
1843
+ register: (alepha) => alepha.with({
1844
+ optional: true,
1845
+ provide: FileSystemProvider,
1846
+ use: NodeFileSystemProvider
1847
+ }).with({
1848
+ optional: true,
1849
+ provide: ShellProvider,
1850
+ use: alepha.isTest() ? MemoryShellProvider : NodeShellProvider
1851
+ })
1852
+ });
1853
+
1854
+ //#endregion
14
1855
  //#region ../../src/core/constants/KIND.ts
15
1856
  /**
16
1857
  * Used for identifying primitives.
@@ -76,13 +1917,24 @@ $atom$1[KIND] = "atom";
76
1917
  * Build options atom for CLI build command.
77
1918
  *
78
1919
  * Defines the available build configuration options with their defaults.
79
- * Options can be overridden via vite.config.ts or CLI flags.
1920
+ * Options can be overridden via alepha.config.ts or CLI flags.
80
1921
  */
81
1922
  const buildOptions = $atom$1({
82
1923
  name: "alepha.cli.build.options",
83
1924
  description: "Build configuration options",
84
1925
  schema: t.object({
85
1926
  stats: t.optional(t.boolean({ default: false })),
1927
+ target: t.optional(t.enum([
1928
+ "bare",
1929
+ "docker",
1930
+ "vercel",
1931
+ "cloudflare"
1932
+ ])),
1933
+ runtime: t.optional(t.enum([
1934
+ "node",
1935
+ "bun",
1936
+ "workerd"
1937
+ ])),
86
1938
  vercel: t.optional(t.object({
87
1939
  projectName: t.optional(t.string()),
88
1940
  orgId: t.optional(t.string()),
@@ -94,8 +1946,13 @@ const buildOptions = $atom$1({
94
1946
  })),
95
1947
  cloudflare: t.optional(t.object({ config: t.optional(t.json()) })),
96
1948
  docker: t.optional(t.object({
97
- image: t.optional(t.string({ default: "node:24-alpine" })),
98
- command: t.optional(t.string({ default: "node" }))
1949
+ from: t.optional(t.string()),
1950
+ command: t.optional(t.string()),
1951
+ image: t.optional(t.object({
1952
+ tag: t.string(),
1953
+ args: t.optional(t.string()),
1954
+ oci: t.optional(t.boolean())
1955
+ }))
99
1956
  })),
100
1957
  sitemap: t.optional(t.object({ hostname: t.string() }))
101
1958
  }),
@@ -191,9 +2048,10 @@ var AppEntryProvider = class {
191
2048
  };
192
2049
 
193
2050
  //#endregion
194
- //#region ../../src/cli/providers/ViteTemplateProvider.ts
195
- var ViteTemplateProvider = class {
2051
+ //#region ../../src/cli/services/ViteUtils.ts
2052
+ var ViteUtils = class {
196
2053
  fs = $inject(FileSystemProvider);
2054
+ viteDevServer;
197
2055
  generateIndexHtml(entry) {
198
2056
  const style = entry.style;
199
2057
  const browser = entry.browser ?? entry.server;
@@ -213,15 +2071,6 @@ ${style ? `<link rel="stylesheet" href="/${style}" />` : ""}
213
2071
  </html>
214
2072
  `.trim();
215
2073
  }
216
- };
217
-
218
- //#endregion
219
- //#region ../../src/cli/providers/ViteBuildProvider.ts
220
- var ViteBuildProvider = class {
221
- alepha;
222
- appEntry;
223
- viteDevServer;
224
- templateProvider = $inject(ViteTemplateProvider);
225
2074
  /**
226
2075
  * We need to close the Vite dev server after build is done.
227
2076
  */
@@ -238,10 +2087,10 @@ var ViteBuildProvider = class {
238
2087
  await this.viteDevServer?.close();
239
2088
  }
240
2089
  });
241
- async init(opts) {
2090
+ async runAlepha(opts) {
242
2091
  const { createServer } = await importVite();
2092
+ process.env.NODE_ENV = opts.mode;
243
2093
  process.env.ALEPHA_CLI_IMPORT = "true";
244
- process.env.NODE_ENV = "production";
245
2094
  process.env.LOG_LEVEL ??= "warn";
246
2095
  /**
247
2096
  * 01/26 Vite 7
@@ -257,6 +2106,21 @@ var ViteBuildProvider = class {
257
2106
  await this.viteDevServer.ssrLoadModule(opts.entry.server);
258
2107
  const alepha = globalThis.__alepha;
259
2108
  if (!alepha) throw new AlephaError("Alepha instance not found after loading entry module");
2109
+ return alepha;
2110
+ }
2111
+ };
2112
+
2113
+ //#endregion
2114
+ //#region ../../src/cli/providers/ViteBuildProvider.ts
2115
+ var ViteBuildProvider = class {
2116
+ alepha;
2117
+ appEntry;
2118
+ viteUtils = $inject(ViteUtils);
2119
+ async init(opts) {
2120
+ const alepha = await this.viteUtils.runAlepha({
2121
+ entry: opts.entry,
2122
+ mode: "production"
2123
+ });
260
2124
  this.alepha = alepha;
261
2125
  this.appEntry = opts.entry;
262
2126
  return alepha;
@@ -272,7 +2136,7 @@ var ViteBuildProvider = class {
272
2136
  }
273
2137
  generateIndexHtml() {
274
2138
  if (!this.appEntry) throw new AlephaError("ViteBuildProvider not initialized");
275
- return this.templateProvider.generateIndexHtml(this.appEntry);
2139
+ return this.viteUtils.generateIndexHtml(this.appEntry);
276
2140
  }
277
2141
  };
278
2142
 
@@ -292,44 +2156,23 @@ var AlephaCliUtils = class {
292
2156
  fs = $inject(FileSystemProvider);
293
2157
  envUtils = $inject(EnvUtils);
294
2158
  boot = $inject(AppEntryProvider);
2159
+ shell = $inject(ShellProvider);
2160
+ viteUtils = $inject(ViteUtils);
295
2161
  /**
296
2162
  * Execute a command with inherited stdio.
2163
+ *
2164
+ * @param command - The command to execute
2165
+ * @param options.root - Working directory
2166
+ * @param options.env - Additional environment variables
2167
+ * @param options.global - If true, run command directly without resolving from node_modules
297
2168
  */
298
2169
  async exec(command, options = {}) {
299
- const root = options.root ?? process.cwd();
300
- this.log.debug(`Executing command: ${command}`, { cwd: root });
301
- const runExec = async (app, args) => {
302
- const prog = spawn(app, args, {
303
- stdio: "inherit",
304
- cwd: root,
305
- env: {
306
- ...process.env,
307
- ...options.env
308
- }
309
- });
310
- await new Promise((resolve) => prog.on("exit", () => {
311
- resolve();
312
- }));
313
- };
314
- if (options.global) {
315
- const [app, ...args] = command.split(" ");
316
- await runExec(app, args);
317
- return;
318
- }
319
- const suffix = process.platform === "win32" ? ".cmd" : "";
320
- const [app, ...args] = command.split(" ");
321
- let execPath = await this.checkFileExists(root, `node_modules/.bin/${app}${suffix}`);
322
- if (!execPath) execPath = await this.checkFileExists(root, `node_modules/alepha/node_modules/.bin/${app}${suffix}`);
323
- if (!execPath) {
324
- let parentDir = this.fs.join(root, "..");
325
- for (let i = 0; i < 3; i++) {
326
- execPath = await this.checkFileExists(parentDir, `node_modules/.bin/${app}${suffix}`);
327
- if (execPath) break;
328
- parentDir = this.fs.join(parentDir, "..");
329
- }
330
- }
331
- if (!execPath) throw new AlephaError(`Could not find executable for command '${app}'. Make sure the package is installed.`);
332
- await runExec(execPath, args);
2170
+ await this.shell.run(command, {
2171
+ root: options.root,
2172
+ env: options.env,
2173
+ resolve: !options.global,
2174
+ capture: false
2175
+ });
333
2176
  }
334
2177
  /**
335
2178
  * Write a configuration file to node_modules/.alepha directory.
@@ -342,33 +2185,14 @@ var AlephaCliUtils = class {
342
2185
  this.log.debug(`Config file written: ${path}`);
343
2186
  return path;
344
2187
  }
345
- /**
346
- * Load Alepha instance from a server entry file.
347
- */
348
- async loadAlephaFromServerEntryFile(rootDir, explicitEntry) {
349
- process.env.ALEPHA_CLI_IMPORT = "true";
350
- const root = rootDir ?? process.cwd();
2188
+ async loadAlephaFromServerEntryFile(opts) {
351
2189
  let entry;
352
- if (explicitEntry) {
353
- entry = this.fs.join(root, explicitEntry);
354
- if (!await this.fs.exists(entry)) throw new AlephaError(`Explicit server entry file "${explicitEntry}" not found.`);
355
- } else {
356
- const appEntry = await this.boot.getAppEntry(root);
357
- entry = this.fs.join(root, appEntry.server);
358
- }
359
- delete global.__alepha;
360
- const mod = await import(entry);
361
- this.log.debug(`Load entry: ${entry}`);
362
- if (mod.default instanceof Alepha) return {
363
- alepha: mod.default,
364
- entry
365
- };
366
- const g = global;
367
- if (g.__alepha) return {
368
- alepha: g.__alepha,
369
- entry
370
- };
371
- throw new AlephaError(`Could not find Alepha instance in entry file: ${entry}`);
2190
+ if ("root" in opts) entry = await this.boot.getAppEntry(opts.root);
2191
+ else entry = opts.entry;
2192
+ return await this.viteUtils.runAlepha({
2193
+ entry,
2194
+ mode: opts.mode
2195
+ });
372
2196
  }
373
2197
  /**
374
2198
  * Generate JavaScript code for Drizzle entities export.
@@ -396,12 +2220,52 @@ ${models.map((it) => `export const ${it} = models["${it}"];`).join("\n")}
396
2220
  async exists(root, path) {
397
2221
  return this.fs.exists(this.fs.join(root, path));
398
2222
  }
399
- async checkFileExists(root, name) {
400
- const configPath = this.fs.join(root, name);
401
- if (await this.fs.exists(configPath)) return configPath;
2223
+ /**
2224
+ * Check if a command is installed and available in the system PATH.
2225
+ */
2226
+ isInstalledAsync(cmd) {
2227
+ return this.shell.isInstalled(cmd);
2228
+ }
2229
+ /**
2230
+ * Get the current git revision (commit SHA).
2231
+ *
2232
+ * @returns The short commit SHA or "unknown" if not in a git repo
2233
+ */
2234
+ async getGitRevision() {
2235
+ try {
2236
+ return (await this.shell.run("git rev-parse --short HEAD", { capture: true })).trim();
2237
+ } catch {
2238
+ return "unknown";
2239
+ }
402
2240
  }
403
2241
  };
404
2242
 
2243
+ //#endregion
2244
+ //#region ../../package.json
2245
+ var devDependencies = {
2246
+ "@biomejs/biome": "^2.3.13",
2247
+ "@electric-sql/pglite": "^0.3.15",
2248
+ "@faker-js/faker": "^10.2.0",
2249
+ "@testing-library/dom": "^10.4.1",
2250
+ "@testing-library/react": "^16.3.2",
2251
+ "@types/node": "^25.1.0",
2252
+ "@types/nodemailer": "^7.0.9",
2253
+ "@types/react": "^19.2.10",
2254
+ "@types/react-dom": "^19.2.3",
2255
+ "@types/ws": "^8.18.1",
2256
+ "@vitejs/plugin-react": "^5.1.2",
2257
+ "cron-schedule": "^6.0.0",
2258
+ "jose": "^6.1.3",
2259
+ "jsdom": "^27.4.0",
2260
+ "openid-client": "^6.8.1",
2261
+ "prom-client": "^15.1.3",
2262
+ "react": "^19.2.4",
2263
+ "react-dom": "^19.2.4",
2264
+ "swagger-ui-dist": "^5.31.0",
2265
+ "tsdown": "^0.20.1",
2266
+ "vitest": "^4.0.18"
2267
+ };
2268
+
405
2269
  //#endregion
406
2270
  //#region ../../src/cli/version.ts
407
2271
  const packageJson = JSON.parse(readFileSync(new URL("../../package.json", import.meta.url), "utf-8"));
@@ -425,11 +2289,8 @@ var PackageManagerUtils = class {
425
2289
  /**
426
2290
  * Detect the package manager used in the project.
427
2291
  */
428
- async getPackageManager(root, flags) {
429
- if (flags?.yarn) return "yarn";
430
- if (flags?.pnpm) return "pnpm";
431
- if (flags?.npm) return "npm";
432
- if (flags?.bun) return "bun";
2292
+ async getPackageManager(root, pm) {
2293
+ if (pm) return pm;
433
2294
  if (this.alepha.isBun()) return "bun";
434
2295
  if (await this.fs.exists(this.fs.join(root, "bun.lock"))) return "bun";
435
2296
  if (await this.fs.exists(this.fs.join(root, "yarn.lock"))) return "yarn";
@@ -437,6 +2298,46 @@ var PackageManagerUtils = class {
437
2298
  return "npm";
438
2299
  }
439
2300
  /**
2301
+ * Detect workspace context when inside a monorepo package.
2302
+ *
2303
+ * Checks if we're inside a workspace package (e.g., packages/my-pkg or apps/my-app)
2304
+ * by looking 2 levels up for workspace indicators like lockfiles and config files.
2305
+ *
2306
+ * @param root - The current package directory
2307
+ * @returns Workspace context with root path, PM, and config presence
2308
+ */
2309
+ async getWorkspaceContext(root) {
2310
+ const workspaceRoot = this.fs.join(root, "..", "..");
2311
+ const [hasYarnLock, hasPnpmLock, hasNpmLock, hasBunLock] = await Promise.all([
2312
+ this.fs.exists(this.fs.join(workspaceRoot, "yarn.lock")),
2313
+ this.fs.exists(this.fs.join(workspaceRoot, "pnpm-lock.yaml")),
2314
+ this.fs.exists(this.fs.join(workspaceRoot, "package-lock.json")),
2315
+ this.fs.exists(this.fs.join(workspaceRoot, "bun.lock"))
2316
+ ]);
2317
+ const [hasBiome, hasEditorConfig, hasTsConfig, hasWorkspacePackageJson] = await Promise.all([
2318
+ this.fs.exists(this.fs.join(workspaceRoot, "biome.json")),
2319
+ this.fs.exists(this.fs.join(workspaceRoot, ".editorconfig")),
2320
+ this.fs.exists(this.fs.join(workspaceRoot, "tsconfig.json")),
2321
+ this.fs.exists(this.fs.join(workspaceRoot, "package.json"))
2322
+ ]);
2323
+ const isPackage = (hasYarnLock || hasPnpmLock || hasNpmLock || hasBunLock) && hasWorkspacePackageJson;
2324
+ let packageManager = null;
2325
+ if (hasYarnLock) packageManager = "yarn";
2326
+ else if (hasPnpmLock) packageManager = "pnpm";
2327
+ else if (hasBunLock) packageManager = "bun";
2328
+ else if (hasNpmLock) packageManager = "npm";
2329
+ return {
2330
+ isPackage,
2331
+ workspaceRoot: isPackage ? workspaceRoot : null,
2332
+ packageManager,
2333
+ config: {
2334
+ biomeJson: hasBiome,
2335
+ editorconfig: hasEditorConfig,
2336
+ tsconfigJson: hasTsConfig
2337
+ }
2338
+ };
2339
+ }
2340
+ /**
440
2341
  * Get the install command for a package.
441
2342
  */
442
2343
  async getInstallCommand(root, packageName, dev = true) {
@@ -481,13 +2382,23 @@ var PackageManagerUtils = class {
481
2382
  }
482
2383
  /**
483
2384
  * Install a dependency if it's missing from the project.
2385
+ * Optionally checks workspace root for the dependency in monorepo setups.
484
2386
  */
485
2387
  async ensureDependency(root, packageName, options = {}) {
486
- const { dev = true } = options;
2388
+ const { dev = true, checkWorkspace = false } = options;
487
2389
  if (await this.hasDependency(root, packageName)) {
488
2390
  this.log.debug(`Dependency '${packageName}' is already installed`);
489
2391
  return;
490
2392
  }
2393
+ if (checkWorkspace) {
2394
+ const workspace = await this.getWorkspaceContext(root);
2395
+ if (workspace.workspaceRoot) {
2396
+ if (await this.hasDependency(workspace.workspaceRoot, packageName)) {
2397
+ this.log.debug(`Dependency '${packageName}' is already installed in workspace root`);
2398
+ return;
2399
+ }
2400
+ }
2401
+ }
491
2402
  const cmd = await this.getInstallCommand(root, packageName, dev);
492
2403
  if (options.run) await options.run(cmd, {
493
2404
  alias: `add ${packageName}`,
@@ -578,122 +2489,61 @@ var PackageManagerUtils = class {
578
2489
  return packageJson;
579
2490
  }
580
2491
  generatePackageJsonContent(modes) {
2492
+ const alephaDeps = devDependencies;
581
2493
  const dependencies = { alepha: `^${version}` };
582
- const devDependencies = {};
2494
+ const devDependencies$1 = {};
2495
+ if (!modes.isPackage) {
2496
+ devDependencies$1["@biomejs/biome"] = alephaDeps["@biomejs/biome"];
2497
+ if (modes.test) devDependencies$1.vitest = alephaDeps.vitest;
2498
+ }
583
2499
  const scripts = {
584
2500
  dev: "alepha dev",
585
2501
  build: "alepha build",
586
- lint: "alepha lint",
587
- typecheck: "alepha typecheck",
588
- verify: "alepha verify"
589
- };
590
- if (modes.ui) {
591
- dependencies["@alepha/ui"] = `^${version}`;
592
- modes.react = true;
593
- }
594
- if (modes.react) {
595
- dependencies["@alepha/react"] = `^${version}`;
596
- dependencies.react = "^19.2.0";
597
- dependencies["react-dom"] = "^19.2.0";
598
- devDependencies["@types/react"] = "^19.2.0";
599
- }
600
- return {
601
- type: "module",
602
- dependencies,
603
- devDependencies,
604
- scripts
605
- };
606
- }
607
- async removeFiles(root, files) {
608
- await Promise.all(files.map((file) => this.fs.rm(this.fs.join(root, file), {
609
- force: true,
610
- recursive: true
611
- })));
612
- }
613
- };
614
-
615
- //#endregion
616
- //#region ../../src/cli/assets/apiHelloControllerTs.ts
617
- const apiHelloControllerTs = () => `
618
- import { t } from "alepha";
619
- import { $action } from "alepha/server";
620
-
621
- export class HelloController {
622
- hello = $action({
623
- path: "/hello",
624
- schema: {
625
- response: t.object({
626
- message: t.string(),
627
- }),
628
- },
629
- handler: () => ({
630
- message: "Hello, Alepha!",
631
- }),
632
- });
633
- }
634
- `.trim();
635
-
636
- //#endregion
637
- //#region ../../src/cli/assets/apiIndexTs.ts
638
- const apiIndexTs = (options = {}) => {
639
- const { appName = "app" } = options;
640
- return `
641
- import { $module } from "alepha";
642
- import { HelloController } from "./controllers/HelloController.ts";
643
-
644
- export const ApiModule = $module({
645
- name: "${appName}.api",
646
- services: [HelloController],
647
- });
648
- `.trim();
2502
+ lint: "alepha lint",
2503
+ typecheck: "alepha typecheck",
2504
+ verify: "alepha verify"
2505
+ };
2506
+ if (modes.test) scripts.test = "vitest run";
2507
+ if (modes.ui) {
2508
+ dependencies["@alepha/ui"] = `^${version}`;
2509
+ modes.react = true;
2510
+ }
2511
+ if (modes.react) {
2512
+ dependencies.react = alephaDeps.react;
2513
+ dependencies["react-dom"] = alephaDeps["react-dom"];
2514
+ devDependencies$1["@vitejs/plugin-react"] = alephaDeps["@vitejs/plugin-react"];
2515
+ devDependencies$1["@types/react"] = alephaDeps["@types/react"];
2516
+ }
2517
+ return {
2518
+ type: "module",
2519
+ dependencies,
2520
+ devDependencies: devDependencies$1,
2521
+ scripts
2522
+ };
2523
+ }
2524
+ async removeFiles(root, files) {
2525
+ await Promise.all(files.map((file) => this.fs.rm(this.fs.join(root, file), {
2526
+ force: true,
2527
+ recursive: true
2528
+ })));
2529
+ }
649
2530
  };
650
2531
 
651
2532
  //#endregion
652
- //#region ../../src/cli/assets/biomeJson.ts
653
- const biomeJson = () => `
654
- {
655
- "$schema": "https://biomejs.dev/schemas/latest/schema.json",
656
- "vcs": {
657
- "enabled": true,
658
- "clientKind": "git"
659
- },
660
- "files": {
661
- "ignoreUnknown": true,
662
- "includes": ["**", "!node_modules", "!dist"]
663
- },
664
- "formatter": {
665
- "enabled": true,
666
- "useEditorconfig": true
667
- },
668
- "linter": {
669
- "enabled": true,
670
- "rules": {
671
- "recommended": true
672
- },
673
- "domains": {
674
- "react": "recommended"
675
- }
676
- },
677
- "assist": {
678
- "actions": {
679
- "source": {
680
- "organizeImports": "on"
681
- }
682
- }
683
- }
684
- }
685
- `.trim();
686
-
687
- //#endregion
688
- //#region ../../src/cli/assets/claudeMd.ts
689
- const claudeMd = (options = {}) => {
2533
+ //#region ../../src/cli/templates/agentMd.ts
2534
+ const agentMd = (type, options = {}) => {
690
2535
  const { react = false, projectName = "my-app" } = options;
2536
+ const header = type === "claude" ? `# CLAUDE.md
2537
+
2538
+ This file provides guidance to Claude Code when working with this Alepha project.` : `# AGENTS.md
2539
+
2540
+ This file provides guidance to AI coding assistants when working with this Alepha project.`;
691
2541
  const reactSection = react ? `
692
2542
  ## React & Frontend
693
2543
 
694
2544
  ### Pages with \`$page\`
695
2545
  \`\`\`tsx
696
- import { $page } from "@alepha/react/router";
2546
+ import { $page } from "alepha/react/router";
697
2547
  import { $client } from "alepha/server/links";
698
2548
  import type { UserController } from "./UserController.ts";
699
2549
 
@@ -719,9 +2569,9 @@ class AppRouter {
719
2569
 
720
2570
  ### React Hooks
721
2571
  \`\`\`typescript
722
- import { useAlepha, useClient, useStore, useAction, useInject } from "@alepha/react";
723
- import { useRouter, useActive } from "@alepha/react/router";
724
- import { useForm } from "@alepha/react/form";
2572
+ import { useAlepha, useClient, useStore, useAction, useInject } from "alepha/react";
2573
+ import { useRouter, useActive } from "alepha/react/router";
2574
+ import { useForm } from "alepha/react/form";
725
2575
  \`\`\`
726
2576
 
727
2577
  - \`useClient<Controller>()\` - Type-safe API calls
@@ -766,9 +2616,7 @@ ${projectName}/
766
2616
  └── tsconfig.json
767
2617
  \`\`\`
768
2618
  `;
769
- return `# CLAUDE.md
770
-
771
- This file provides guidance to Claude Code when working with this Alepha project.
2619
+ return `${header}
772
2620
 
773
2621
  ## Overview
774
2622
 
@@ -782,10 +2630,10 @@ This is an **Alepha** project - a convention-driven TypeScript framework for typ
782
2630
 
783
2631
  ## Rules
784
2632
 
785
- - Use TypeScript strict mode
2633
+ - Use TypeScript strict mode, always check types (\`alepha typecheck\`)
786
2634
  - Use Biome for formatting (\`alepha lint\`)
787
- - Use Vitest for testing
788
- - One file = one class
2635
+ - Use Vitest for testing (\`alepha test\`)
2636
+ - One file = one class, multiple interfaces/types allowed
789
2637
  - Primitives are class properties (except \`$entity\`, \`$atom\`)
790
2638
  - No decorators, no Express/Fastify patterns
791
2639
  - No manual instantiation - use dependency injection
@@ -793,6 +2641,7 @@ This is an **Alepha** project - a convention-driven TypeScript framework for typ
793
2641
  - Import with file extensions: \`import { User } from "./User.ts"\`
794
2642
  - Use \`t\` from Alepha for schemas (not Zod)
795
2643
  - Prefer \`t.text()\` over \`t.string()\` for user input (has default max length, auto-trim, supports lowercase option)
2644
+ - One file = one schema (schemas/createUserSchema.ts)
796
2645
 
797
2646
  ## Project Structure
798
2647
  ${projectStructure}
@@ -835,17 +2684,12 @@ export const userEntity = $entity({
835
2684
  id: db.primaryKey(),
836
2685
  email: t.email(),
837
2686
  createdAt: db.createdAt(),
838
- updatedAt: db.updatedAt(),
839
2687
  }),
840
2688
  indexes: [{ column: "email", unique: true }],
841
2689
  });
842
2690
 
843
2691
  class UserService {
844
- repo = $repository(userEntity);
845
-
846
- async findById(id: string) {
847
- return this.repo.findById(id);
848
- }
2692
+ userRepository = $repository(userEntity);
849
2693
  }
850
2694
  \`\`\`
851
2695
 
@@ -855,11 +2699,6 @@ import { $inject } from "alepha";
855
2699
 
856
2700
  class OrderService {
857
2701
  userService = $inject(UserService); // Within same module
858
-
859
- async createOrder(userId: string) {
860
- const user = await this.userService.findById(userId);
861
- // ...
862
- }
863
2702
  }
864
2703
 
865
2704
  // Cross-module: use $client instead of $inject
@@ -923,7 +2762,7 @@ ${reactSection}
923
2762
  | \`$bucket\` | \`alepha/bucket\` | File storage |
924
2763
  | \`$issuer\` | \`alepha/security\` | JWT tokens |
925
2764
  | \`$command\` | \`alepha/command\` | CLI commands |${react ? `
926
- | \`$page\` | \`@alepha/react/router\` | React pages with SSR |
2765
+ | \`$page\` | \`alepha/react/router\` | React pages with SSR |
927
2766
  | \`$atom\` | \`alepha\` | Global state |` : ""}
928
2767
 
929
2768
  ## Testing
@@ -975,11 +2814,114 @@ alepha build # Build the project
975
2814
 
976
2815
  - Full docs: https://alepha.dev/llms.txt
977
2816
  - Detailed docs: https://alepha.dev/llms-full.txt
2817
+
2818
+ ## Source Code Access
2819
+
2820
+ Full framework source available at \`node_modules/alepha/src/\`.
2821
+
2822
+ **IMPORTANT:** When answering questions about Alepha primitives, APIs, or internals:
2823
+ 1. ALWAYS read the local source code first at \`node_modules/alepha/src/\` or \`node_modules/@alepha/ui/src/\` for UI-related questions
2824
+ 2. Use \`Glob\` to find relevant files: \`node_modules/alepha/src/**/primitives/$<name>.ts\`
2825
+ 3. Read the implementation AND the \`.spec.ts\` test files for usage examples
2826
+ 4. Use external documentation as a fallback if source code is insufficient
2827
+ `.trim();
2828
+ };
2829
+
2830
+ //#endregion
2831
+ //#region ../../src/cli/templates/apiAppSecurityTs.ts
2832
+ const apiAppSecurityTs = () => {
2833
+ return `
2834
+ import { $realm } from "alepha/api/users";
2835
+
2836
+ export class AppSecurity {
2837
+ users = $realm({
2838
+ // configure your realm here
2839
+ });
2840
+ }
2841
+ `.trim();
2842
+ };
2843
+
2844
+ //#endregion
2845
+ //#region ../../src/cli/templates/apiHelloControllerTs.ts
2846
+ const apiHelloControllerTs = () => `
2847
+ import { t } from "alepha";
2848
+ import { $action } from "alepha/server";
2849
+
2850
+ export class HelloController {
2851
+ hello = $action({
2852
+ path: "/hello",
2853
+ schema: {
2854
+ response: t.object({
2855
+ message: t.string(),
2856
+ }),
2857
+ },
2858
+ handler: () => ({
2859
+ message: "Hello, Alepha!",
2860
+ }),
2861
+ });
2862
+ }
2863
+ `.trim();
2864
+
2865
+ //#endregion
2866
+ //#region ../../src/cli/templates/apiIndexTs.ts
2867
+ const apiIndexTs = (options = {}) => {
2868
+ const { appName = "app", auth = false } = options;
2869
+ const imports = ["import { $module } from \"alepha\";"];
2870
+ const services = [];
2871
+ if (auth) {
2872
+ imports.push("import { AppSecurity } from \"./AppSecurity.ts\";");
2873
+ services.push("AppSecurity");
2874
+ }
2875
+ imports.push("import { HelloController } from \"./controllers/HelloController.ts\";");
2876
+ services.push("HelloController");
2877
+ return `
2878
+ ${imports.join("\n")}
2879
+
2880
+ export const ApiModule = $module({
2881
+ name: "${appName}.api",
2882
+ services: [${services.join(", ")}],
2883
+ });
978
2884
  `.trim();
979
2885
  };
980
2886
 
981
2887
  //#endregion
982
- //#region ../../src/cli/assets/dummySpecTs.ts
2888
+ //#region ../../src/cli/templates/biomeJson.ts
2889
+ const biomeJson = () => `
2890
+ {
2891
+ "$schema": "https://biomejs.dev/schemas/latest/schema.json",
2892
+ "vcs": {
2893
+ "enabled": true,
2894
+ "clientKind": "git"
2895
+ },
2896
+ "files": {
2897
+ "ignoreUnknown": true,
2898
+ "includes": ["**", "!node_modules", "!dist"]
2899
+ },
2900
+ "formatter": {
2901
+ "enabled": true,
2902
+ "useEditorconfig": true
2903
+ },
2904
+ "linter": {
2905
+ "enabled": true,
2906
+ "rules": {
2907
+ "recommended": true
2908
+ },
2909
+ "domains": {
2910
+ "react": "recommended"
2911
+ }
2912
+ },
2913
+ "assist": {
2914
+ "actions": {
2915
+ "source": {
2916
+ "organizeImports": "on"
2917
+ }
2918
+ }
2919
+ }
2920
+ }
2921
+ `.trim();
2922
+
2923
+ //#endregion
2924
+ //#region ../../src/cli/templates/dummySpecTs.ts
983
2925
  const dummySpecTs = () => `
984
2926
  import { test, expect } from "vitest";
985
2927
 
@@ -989,7 +2931,7 @@ test("dummy test", () => {
989
2931
  `.trim();
990
2932
 
991
2933
  //#endregion
992
- //#region ../../src/cli/assets/editorconfig.ts
2934
+ //#region ../../src/cli/templates/editorconfig.ts
993
2935
  const editorconfig = () => `
994
2936
  # https://editorconfig.org
995
2937
 
@@ -1005,7 +2947,48 @@ indent_size = 2
1005
2947
  `.trim();
1006
2948
 
1007
2949
  //#endregion
1008
- //#region ../../src/cli/assets/mainBrowserTs.ts
2950
+ //#region ../../src/cli/templates/gitignore.ts
2951
+ const gitignore = () => `
2952
+ # Dependencies
2953
+ node_modules/
2954
+
2955
+ # Build outputs
2956
+ dist/
2957
+ .vite/
2958
+
2959
+ # Environment files
2960
+ .env
2961
+ .env.*
2962
+ !.env.example
2963
+
2964
+ # IDE
2965
+ .idea/
2966
+ *.swp
2967
+ *.swo
2968
+
2969
+ # OS
2970
+ .DS_Store
2971
+ Thumbs.db
2972
+
2973
+ # Logs
2974
+ *.log
2975
+ logs/
2976
+
2977
+ # Test coverage
2978
+ coverage/
2979
+
2980
+ # Yarn
2981
+ .yarn/*
2982
+ !.yarn/patches
2983
+ !.yarn/plugins
2984
+ !.yarn/releases
2985
+ !.yarn/sdks
2986
+ !.yarn/versions
2987
+ .pnp.*
2988
+ `.trim();
2989
+
2990
+ //#endregion
2991
+ //#region ../../src/cli/templates/mainBrowserTs.ts
1009
2992
  const mainBrowserTs = () => `
1010
2993
  import { Alepha, run } from "alepha";
1011
2994
  import { WebModule } from "./web/index.ts";
@@ -1018,8 +3001,10 @@ run(alepha);
1018
3001
  `.trim();
1019
3002
 
1020
3003
  //#endregion
1021
- //#region ../../src/cli/assets/mainCss.ts
1022
- const mainCss = () => `
3004
+ //#region ../../src/cli/templates/mainCss.ts
3005
+ const mainCss = (options = {}) => {
3006
+ if (options.ui) return `@import "@alepha/ui/styles";`;
3007
+ return `
1023
3008
  * {
1024
3009
  box-sizing: border-box;
1025
3010
  margin: 0;
@@ -1042,25 +3027,34 @@ body {
1042
3027
  height: 100%;
1043
3028
  }
1044
3029
  `.trim();
3030
+ };
1045
3031
 
1046
3032
  //#endregion
1047
- //#region ../../src/cli/assets/mainServerTs.ts
3033
+ //#region ../../src/cli/templates/mainServerTs.ts
1048
3034
  const mainServerTs = (options = {}) => {
1049
- const { react = false } = options;
3035
+ const { api = false, react = false } = options;
3036
+ const imports = [];
3037
+ const withs = [];
3038
+ if (api) {
3039
+ imports.push(`import { ApiModule } from "./api/index.ts";`);
3040
+ withs.push(`alepha.with(ApiModule);`);
3041
+ }
3042
+ if (react) {
3043
+ imports.push(`import { WebModule } from "./web/index.ts";`);
3044
+ withs.push(`alepha.with(WebModule);`);
3045
+ }
1050
3046
  return `
1051
3047
  import { Alepha, run } from "alepha";
1052
- import { ApiModule } from "./api/index.ts";
1053
- ${react ? `import { WebModule } from "./web/index.ts";\n` : ""}
3048
+ ${imports.length > 0 ? `${imports.join("\n")}\n` : ""}
1054
3049
  const alepha = Alepha.create();
3050
+ ${withs.length > 0 ? `\n${withs.join("\n")}` : ""}
1055
3051
 
1056
- alepha.with(ApiModule);
1057
- ${react ? `alepha.with(WebModule);\n` : ""}
1058
3052
  run(alepha);
1059
3053
  `.trim();
1060
3054
  };
1061
3055
 
1062
3056
  //#endregion
1063
- //#region ../../src/cli/assets/tsconfigJson.ts
3057
+ //#region ../../src/cli/templates/tsconfigJson.ts
1064
3058
  const tsconfigJson = () => `
1065
3059
  {
1066
3060
  "extends": "alepha/tsconfig.base"
@@ -1068,47 +3062,74 @@ const tsconfigJson = () => `
1068
3062
  `.trim();
1069
3063
 
1070
3064
  //#endregion
1071
- //#region ../../src/cli/assets/webAppRouterTs.ts
1072
- const webAppRouterTs = () => `
1073
- import { $page } from "@alepha/react/router";
1074
- import { $client } from "alepha/server/links";
1075
- import type { HelloController } from "../api/controllers/HelloController.ts";
1076
-
1077
- export class AppRouter {
1078
- api = $client<HelloController>();
1079
-
1080
- home = $page({
3065
+ //#region ../../src/cli/templates/webAppRouterTs.ts
3066
+ const webAppRouterTs = (options) => {
3067
+ const imports = [];
3068
+ const classMembers = [];
3069
+ if (options.ui) imports.push("import { $ui } from \"@alepha/ui\";");
3070
+ if (options.auth) imports.push("import { $uiAuth } from \"@alepha/ui/auth\";");
3071
+ if (options.admin) imports.push("import { $uiAdmin } from \"@alepha/ui/admin\";");
3072
+ imports.push("import { $page } from \"alepha/react/router\";");
3073
+ if (options.api) {
3074
+ imports.push("import { $client } from \"alepha/server/links\";");
3075
+ imports.push("import type { HelloController } from \"../api/controllers/HelloController.ts\";");
3076
+ classMembers.push(" api = $client<HelloController>();");
3077
+ }
3078
+ if (options.ui) {
3079
+ classMembers.push(" ui = $ui();");
3080
+ if (options.auth) classMembers.push(" uiAuth = $uiAuth();");
3081
+ if (options.admin) classMembers.push(" uiAdmin = $uiAdmin();");
3082
+ classMembers.push(` layout = $page({
3083
+ parent: this.ui.root,
3084
+ children: () => [this.home],
3085
+ });`);
3086
+ }
3087
+ if (options.api) classMembers.push(` home = $page({
1081
3088
  path: "/",
1082
3089
  lazy: () => import("./components/Hello.tsx"),
1083
3090
  loader: () => this.api.hello(),
1084
- });
1085
- }
1086
- `.trim();
3091
+ });`);
3092
+ else classMembers.push(` home = $page({
3093
+ path: "/",
3094
+ lazy: () => import("./components/Hello.tsx"),
3095
+ });`);
3096
+ return `${imports.join("\n")}
3097
+
3098
+ export class AppRouter {
3099
+ ${classMembers.join("\n\n")}
3100
+ }`;
3101
+ };
1087
3102
 
1088
3103
  //#endregion
1089
- //#region ../../src/cli/assets/webHelloComponentTsx.ts
1090
- const webHelloComponentTsx = () => `import { useState } from "react";
3104
+ //#region ../../src/cli/templates/webHelloComponentTsx.ts
3105
+ const webHelloComponentTsx = (options = {}) => {
3106
+ const imports = [];
3107
+ if (options.auth) imports.push("import { UserButton } from \"@alepha/ui/auth\";");
3108
+ imports.push("import { useState } from \"react\";");
3109
+ const userButton = options.auth ? "\n <UserButton />" : "";
3110
+ return `${imports.join("\n")}
1091
3111
 
1092
3112
  interface Props {
1093
- message: string;
3113
+ message?: string;
1094
3114
  }
1095
3115
 
1096
3116
  const Hello = (props: Props) => {
1097
- const [message, setMessage] = useState(props.message);
3117
+ const [message, setMessage] = useState(props.message ?? "");
1098
3118
  return (
1099
3119
  <div>
1100
3120
  <h1>{message}</h1>
1101
- <input value={message} onChange={e => setMessage(e.target.value)} />
1102
- <p>Edit this component in src/web/components/Hello.tsx</p>
3121
+ <input value={message} onChange={(e) => setMessage(e.target.value)} />
3122
+ <p>Edit this component in src/web/components/Hello.tsx</p>${userButton}
1103
3123
  </div>
1104
3124
  );
1105
3125
  };
1106
3126
 
1107
3127
  export default Hello;
1108
- `.trim();
3128
+ `;
3129
+ };
1109
3130
 
1110
3131
  //#endregion
1111
- //#region ../../src/cli/assets/webIndexTs.ts
3132
+ //#region ../../src/cli/templates/webIndexTs.ts
1112
3133
  const webIndexTs = (options = {}) => {
1113
3134
  const { appName = "app" } = options;
1114
3135
  return `
@@ -1137,6 +3158,7 @@ var ProjectScaffolder = class {
1137
3158
  log = $logger();
1138
3159
  fs = $inject(FileSystemProvider);
1139
3160
  pm = $inject(PackageManagerUtils);
3161
+ utils = $inject(AlephaCliUtils);
1140
3162
  /**
1141
3163
  * Get the app name from the directory name.
1142
3164
  *
@@ -1146,7 +3168,7 @@ var ProjectScaffolder = class {
1146
3168
  * - Falls back to "app" if empty
1147
3169
  */
1148
3170
  getAppName(root) {
1149
- return basename(root).toLowerCase().replace(/[\s\-_]/g, "") || "app";
3171
+ return basename(root).toLowerCase().replace(/[\s\-_.\d]/g, "") || "app";
1150
3172
  }
1151
3173
  /**
1152
3174
  * Ensure all configuration files exist.
@@ -1154,13 +3176,19 @@ var ProjectScaffolder = class {
1154
3176
  async ensureConfig(root, opts) {
1155
3177
  const tasks = [];
1156
3178
  const force = opts.force ?? false;
3179
+ const checkWorkspace = opts.checkWorkspace ?? false;
1157
3180
  if (opts.packageJson) tasks.push(this.pm.ensurePackageJson(root, typeof opts.packageJson === "boolean" ? {} : opts.packageJson).then(() => {}));
1158
3181
  if (opts.tsconfigJson) tasks.push(this.ensureTsConfig(root, { force }));
1159
- if (opts.indexHtml) tasks.push(this.ensureReactProject(root, { force }));
1160
- if (opts.biomeJson) tasks.push(this.ensureBiomeConfig(root, { force }));
1161
- if (opts.editorconfig) tasks.push(this.ensureEditorConfig(root, { force }));
1162
- if (opts.claudeMd) tasks.push(this.ensureClaudeMd(root, typeof opts.claudeMd === "boolean" ? { force } : {
1163
- ...opts.claudeMd,
3182
+ if (opts.biomeJson) tasks.push(this.ensureBiomeConfig(root, {
3183
+ force,
3184
+ checkWorkspace
3185
+ }));
3186
+ if (opts.editorconfig) tasks.push(this.ensureEditorConfig(root, {
3187
+ force,
3188
+ checkWorkspace
3189
+ }));
3190
+ if (opts.agentMd) tasks.push(this.ensureAgentMd(root, {
3191
+ ...opts.agentMd,
1164
3192
  force
1165
3193
  }));
1166
3194
  await Promise.all(tasks);
@@ -1170,52 +3198,81 @@ var ProjectScaffolder = class {
1170
3198
  await this.fs.writeFile(this.fs.join(root, "tsconfig.json"), tsconfigJson());
1171
3199
  }
1172
3200
  async ensureBiomeConfig(root, opts = {}) {
3201
+ if (!opts.force && opts.checkWorkspace && await this.existsInParents(root, "biome.json")) return;
1173
3202
  await this.ensureFile(root, "biome.json", biomeJson(), opts.force);
1174
3203
  }
1175
3204
  async ensureEditorConfig(root, opts = {}) {
3205
+ if (!opts.force && opts.checkWorkspace && await this.existsInParents(root, ".editorconfig")) return;
1176
3206
  await this.ensureFile(root, ".editorconfig", editorconfig(), opts.force);
1177
3207
  }
1178
- async ensureClaudeMd(root, options = {}) {
1179
- await this.ensureFile(root, "CLAUDE.md", claudeMd(options), options.force);
3208
+ /**
3209
+ * Ensure git repository is initialized with .gitignore.
3210
+ *
3211
+ * @returns true if git was initialized, false if already exists or git unavailable
3212
+ */
3213
+ async ensureGitRepo(root, opts = {}) {
3214
+ const gitDir = this.fs.join(root, ".git");
3215
+ if (!opts.force && await this.fs.exists(gitDir)) return false;
3216
+ if (!await this.utils.isInstalledAsync("git")) return false;
3217
+ await this.utils.exec("git init", {
3218
+ root,
3219
+ global: true
3220
+ });
3221
+ await this.ensureFile(root, ".gitignore", gitignore(), opts.force);
3222
+ return true;
3223
+ }
3224
+ async ensureAgentMd(root, options) {
3225
+ const filename = options.type === "claude" ? "CLAUDE.md" : "AGENTS.md";
3226
+ await this.ensureFile(root, filename, agentMd(options.type, options), options.force);
3227
+ }
3228
+ /**
3229
+ * Ensure src/main.server.ts exists with correct module imports.
3230
+ */
3231
+ async ensureMainServerTs(root, opts = {}) {
3232
+ const srcDir = this.fs.join(root, "src");
3233
+ await this.fs.mkdir(srcDir, { recursive: true });
3234
+ await this.ensureFile(srcDir, "main.server.ts", mainServerTs({
3235
+ api: opts.api,
3236
+ react: opts.react
3237
+ }), opts.force);
1180
3238
  }
1181
3239
  /**
1182
- * Ensure src/main.server.ts exists with full API structure.
3240
+ * Ensure API module structure exists.
1183
3241
  *
1184
3242
  * Creates:
1185
- * - src/main.server.ts (entry point)
1186
3243
  * - src/api/index.ts (API module)
1187
3244
  * - src/api/controllers/HelloController.ts (example controller)
1188
3245
  */
1189
3246
  async ensureApiProject(root, opts = {}) {
1190
- const srcDir = this.fs.join(root, "src");
1191
- if (!opts.force && await this.fs.exists(srcDir)) {
1192
- if ((await this.fs.ls(srcDir)).length > 0) return;
1193
- }
1194
3247
  const appName = this.getAppName(root);
1195
3248
  await this.fs.mkdir(this.fs.join(root, "src/api/controllers"), { recursive: true });
1196
- await this.ensureFile(srcDir, "main.server.ts", mainServerTs(), opts.force);
1197
- await this.ensureFile(srcDir, "api/index.ts", apiIndexTs({ appName }), opts.force);
1198
- await this.ensureFile(srcDir, "api/controllers/HelloController.ts", apiHelloControllerTs(), opts.force);
3249
+ await this.ensureFile(root, "src/api/index.ts", apiIndexTs({
3250
+ appName,
3251
+ auth: opts.auth
3252
+ }), opts.force);
3253
+ await this.ensureFile(root, "src/api/controllers/HelloController.ts", apiHelloControllerTs(), opts.force);
3254
+ if (opts.auth) await this.ensureFile(root, "src/api/AppSecurity.ts", apiAppSecurityTs(), opts.force);
1199
3255
  }
1200
3256
  /**
1201
- * Ensure full React project structure exists.
3257
+ * Ensure web/React project structure exists.
1202
3258
  *
1203
3259
  * Creates:
1204
- * - src/main.server.ts, src/main.browser.ts
1205
- * - src/api/index.ts, src/api/controllers/HelloController.ts
3260
+ * - src/main.browser.ts
3261
+ * - src/main.css
1206
3262
  * - src/web/index.ts, src/web/AppRouter.ts, src/web/components/Hello.tsx
1207
3263
  */
1208
- async ensureReactProject(root, opts = {}) {
3264
+ async ensureWebProject(root, opts = {}) {
1209
3265
  const appName = this.getAppName(root);
1210
- await this.fs.mkdir(this.fs.join(root, "src/api/controllers"), { recursive: true });
1211
3266
  await this.fs.mkdir(this.fs.join(root, "src/web/components"), { recursive: true });
1212
- await this.ensureFile(root, "src/main.css", mainCss(), opts.force);
1213
- await this.ensureFile(root, "src/api/index.ts", apiIndexTs({ appName }), opts.force);
1214
- await this.ensureFile(root, "src/api/controllers/HelloController.ts", apiHelloControllerTs(), opts.force);
1215
- await this.ensureFile(root, "src/main.server.ts", mainServerTs({ react: true }), opts.force);
3267
+ await this.ensureFile(root, "src/main.css", mainCss({ ui: opts.ui }), opts.force);
1216
3268
  await this.ensureFile(root, "src/web/index.ts", webIndexTs({ appName }), opts.force);
1217
- await this.ensureFile(root, "src/web/AppRouter.ts", webAppRouterTs(), opts.force);
1218
- await this.ensureFile(root, "src/web/components/Hello.tsx", webHelloComponentTsx(), opts.force);
3269
+ await this.ensureFile(root, "src/web/AppRouter.ts", webAppRouterTs({
3270
+ api: opts.api,
3271
+ ui: opts.ui,
3272
+ auth: opts.auth,
3273
+ admin: opts.admin
3274
+ }), opts.force);
3275
+ await this.ensureFile(root, "src/web/components/Hello.tsx", webHelloComponentTsx({ auth: opts.auth }), opts.force);
1219
3276
  await this.ensureFile(root, "src/main.browser.ts", mainBrowserTs(), opts.force);
1220
3277
  }
1221
3278
  /**
@@ -1263,17 +3320,55 @@ var BuildCommand = class {
1263
3320
  boot = $inject(AppEntryProvider);
1264
3321
  viteBuildProvider = $inject(ViteBuildProvider);
1265
3322
  options = $use(buildOptions);
3323
+ /**
3324
+ * Resolve the effective runtime based on target and explicit runtime flag.
3325
+ *
3326
+ * Some targets force a specific runtime:
3327
+ * - `cloudflare` always uses `workerd`
3328
+ * - `vercel` always uses `node`
3329
+ * - `docker` and bare deployments respect the runtime flag
3330
+ *
3331
+ * @throws {AlephaError} If an incompatible runtime is specified for a target
3332
+ */
3333
+ resolveRuntime(target, runtime) {
3334
+ if (target === "cloudflare") {
3335
+ if (runtime && runtime !== "workerd") throw new AlephaError(`Target 'cloudflare' requires 'workerd' runtime, got '${runtime}'`);
3336
+ return "workerd";
3337
+ }
3338
+ if (target === "vercel") {
3339
+ if (runtime && runtime !== "node") throw new AlephaError(`Target 'vercel' requires 'node' runtime, got '${runtime}'`);
3340
+ return "node";
3341
+ }
3342
+ return runtime ?? "node";
3343
+ }
1266
3344
  build = $command({
1267
3345
  name: "build",
1268
3346
  mode: "production",
1269
3347
  description: "Build the project for production",
1270
3348
  flags: t.object({
1271
3349
  stats: t.optional(t.boolean({ description: "Generate build stats report" })),
1272
- vercel: t.optional(t.boolean({ description: "Generate Vercel deployment configuration" })),
1273
- cloudflare: t.optional(t.boolean({ description: "Generate Cloudflare Workers configuration" })),
1274
- docker: t.optional(t.boolean({ description: "Generate Docker configuration" })),
1275
- sitemap: t.optional(t.text({ description: "Generate sitemap.xml with base URL" })),
1276
- bun: t.optional(t.boolean({ description: "Prioritize .bun.ts entry files for Bun runtime" }))
3350
+ target: t.optional(t.enum([
3351
+ "bare",
3352
+ "docker",
3353
+ "vercel",
3354
+ "cloudflare"
3355
+ ], {
3356
+ aliases: ["t"],
3357
+ description: "Deployment target"
3358
+ })),
3359
+ runtime: t.optional(t.enum([
3360
+ "node",
3361
+ "bun",
3362
+ "workerd"
3363
+ ], {
3364
+ aliases: ["r"],
3365
+ description: "JavaScript runtime"
3366
+ })),
3367
+ image: t.optional(t.union([t.boolean(), t.text()], {
3368
+ aliases: ["i"],
3369
+ description: "Build Docker image. Use -i for latest, -i=<version> for specific version"
3370
+ })),
3371
+ sitemap: t.optional(t.text({ description: "Generate sitemap.xml with base URL" }))
1277
3372
  }),
1278
3373
  handler: async ({ flags, run, root }) => {
1279
3374
  process.env.NODE_ENV = "production";
@@ -1283,13 +3378,16 @@ var BuildCommand = class {
1283
3378
  this.log.trace("Entry file found", { entry });
1284
3379
  const distDir = "dist";
1285
3380
  const publicDir = "public";
1286
- await this.pm.ensureDependency(root, "vite", {
1287
- run,
1288
- exec: (cmd, opts) => this.utils.exec(cmd, opts)
1289
- });
1290
3381
  await run.rm("dist", { alias: "clean dist" });
1291
3382
  const options = this.options;
1292
3383
  await this.utils.loadEnv(root, [".env", ".env.production"]);
3384
+ const target = flags.target ?? options.target;
3385
+ const runtime = this.resolveRuntime(target, flags.runtime ?? options.runtime);
3386
+ if (flags.image && target !== "docker") throw new AlephaError(`Flag '--image' requires '--target=docker', got '${target ?? "bare"}'`);
3387
+ this.log.trace("Build configuration", {
3388
+ target,
3389
+ runtime
3390
+ });
1293
3391
  const stats = flags.stats ?? options.stats ?? false;
1294
3392
  let template = "";
1295
3393
  let hasClient = false;
@@ -1327,8 +3425,8 @@ var BuildCommand = class {
1327
3425
  const clientIndexPath = `${distDir}/${publicDir}/index.html`;
1328
3426
  const clientBuilt = await this.fs.exists(clientIndexPath);
1329
3427
  const conditions = [];
1330
- if (flags.bun) conditions.push("bun");
1331
- if (options.cloudflare) conditions.push("workerd");
3428
+ if (runtime === "bun") conditions.push("bun");
3429
+ else if (runtime === "workerd") conditions.push("workerd");
1332
3430
  await buildServer({
1333
3431
  silent: true,
1334
3432
  entry: entry.server,
@@ -1363,7 +3461,7 @@ var BuildCommand = class {
1363
3461
  run
1364
3462
  });
1365
3463
  }
1366
- if (flags.vercel || options.vercel) await run({
3464
+ if (target === "vercel") await run({
1367
3465
  name: "add Vercel config",
1368
3466
  handler: () => generateVercel({
1369
3467
  distDir,
@@ -1371,20 +3469,56 @@ var BuildCommand = class {
1371
3469
  config: options.vercel
1372
3470
  })
1373
3471
  });
1374
- if (flags.cloudflare || options.cloudflare) await run({
3472
+ if (target === "cloudflare") await run({
1375
3473
  name: "add Cloudflare config",
1376
3474
  handler: () => generateCloudflare({
1377
3475
  distDir,
1378
3476
  config: options.cloudflare?.config
1379
3477
  })
1380
3478
  });
1381
- if (flags.docker || options.docker) await run({
1382
- name: "add Docker config",
1383
- handler: () => generateDocker({
1384
- distDir,
1385
- ...options.docker
1386
- })
1387
- });
3479
+ if (target === "docker") {
3480
+ const dockerFrom = options.docker?.from ?? (runtime === "bun" ? "oven/bun:alpine" : "node:24-alpine");
3481
+ const dockerCommand = options.docker?.command ?? (runtime === "bun" ? "bun" : "node");
3482
+ await run({
3483
+ name: "add Docker config",
3484
+ handler: () => generateDocker({
3485
+ distDir,
3486
+ image: dockerFrom,
3487
+ command: dockerCommand
3488
+ })
3489
+ });
3490
+ if (flags.image) {
3491
+ const imageConfig = options.docker?.image;
3492
+ const flagValue = typeof flags.image === "string" ? flags.image : null;
3493
+ let imageTag;
3494
+ let version;
3495
+ if (!flagValue) {
3496
+ if (!imageConfig?.tag) throw new AlephaError("Flag '--image' requires 'build.docker.image.tag' in config");
3497
+ version = "latest";
3498
+ imageTag = `${imageConfig.tag}:${version}`;
3499
+ } else if (flagValue.startsWith(":")) {
3500
+ if (!imageConfig?.tag) throw new AlephaError("Flag '--image=:version' requires 'build.docker.image.tag' in config");
3501
+ version = flagValue.slice(1);
3502
+ imageTag = `${imageConfig.tag}:${version}`;
3503
+ } else if (flagValue.includes(":")) {
3504
+ imageTag = flagValue;
3505
+ version = flagValue.split(":")[1];
3506
+ } else {
3507
+ imageTag = `${flagValue}:latest`;
3508
+ version = "latest";
3509
+ }
3510
+ const args = [];
3511
+ if (imageConfig?.args) args.push(imageConfig.args);
3512
+ if (imageConfig?.oci) {
3513
+ const revision = await this.utils.getGitRevision();
3514
+ const created = (/* @__PURE__ */ new Date()).toISOString();
3515
+ args.push(`--label "org.opencontainers.image.revision=${revision}"`);
3516
+ args.push(`--label "org.opencontainers.image.created=${created}"`);
3517
+ args.push(`--label "org.opencontainers.image.version=${version}"`);
3518
+ }
3519
+ await run(`docker build ${args.length > 0 ? `${args.join(" ")} ` : ""}-t ${imageTag} ${distDir}`, { alias: `docker build ${imageTag}` });
3520
+ }
3521
+ }
1388
3522
  }
1389
3523
  });
1390
3524
  };
@@ -1414,6 +3548,7 @@ var DbCommand = class {
1414
3548
  log = $logger();
1415
3549
  fs = $inject(FileSystemProvider);
1416
3550
  utils = $inject(AlephaCliUtils);
3551
+ entryProvider = $inject(AppEntryProvider);
1417
3552
  /**
1418
3553
  * Check if database migrations are up to date.
1419
3554
  */
@@ -1428,7 +3563,11 @@ var DbCommand = class {
1428
3563
  handler: async ({ args, root }) => {
1429
3564
  const rootDir = root;
1430
3565
  this.log.debug(`Using project root: ${rootDir}`);
1431
- const { alepha } = await this.utils.loadAlephaFromServerEntryFile(rootDir, args);
3566
+ const entry = await this.entryProvider.getAppEntry(root);
3567
+ const alepha = await this.utils.loadAlephaFromServerEntryFile({
3568
+ mode: "development",
3569
+ entry
3570
+ });
1432
3571
  const repositoryProvider = alepha.inject("RepositoryProvider");
1433
3572
  const drizzleKitProvider = alepha.inject("DrizzleKitProvider");
1434
3573
  const accepted = /* @__PURE__ */ new Set([]);
@@ -1592,7 +3731,11 @@ var DbCommand = class {
1592
3731
  if (options.env) envFiles.push(`.env.${options.env}`);
1593
3732
  await this.utils.loadEnv(rootDir, envFiles);
1594
3733
  this.log.debug(`Using project root: ${rootDir}`);
1595
- const { alepha, entry } = await this.utils.loadAlephaFromServerEntryFile(rootDir, options.args);
3734
+ const entry = await this.entryProvider.getAppEntry(rootDir);
3735
+ const alepha = await this.utils.loadAlephaFromServerEntryFile({
3736
+ mode: "development",
3737
+ entry
3738
+ });
1596
3739
  const drizzleKitProvider = alepha.inject("DrizzleKitProvider");
1597
3740
  const repositoryProvider = alepha.inject("RepositoryProvider");
1598
3741
  const accepted = /* @__PURE__ */ new Set([]);
@@ -1616,11 +3759,11 @@ var DbCommand = class {
1616
3759
  providerUrl: provider.url,
1617
3760
  providerDriver: provider.driver,
1618
3761
  dialect,
1619
- entry,
3762
+ entry: this.fs.join(rootDir, entry.server),
1620
3763
  rootDir
1621
3764
  });
1622
3765
  const flags = options.commandFlags ? ` ${options.commandFlags}` : "";
1623
- await this.utils.exec(`drizzle-kit ${options.command} --config=${drizzleConfigJsPath}${flags}`, { env: { NODE_OPTIONS: "--import tsx" } });
3766
+ await this.utils.exec(`drizzle-kit ${options.command} --config=${drizzleConfigJsPath}${flags}`, { env: { NODE_OPTIONS: [process.env.NODE_OPTIONS, "--import tsx"].filter(Boolean).join(" ") } });
1624
3767
  }
1625
3768
  }
1626
3769
  /**
@@ -1644,9 +3787,9 @@ var DbCommand = class {
1644
3787
  const accountId = process.env.CLOUDFLARE_ACCOUNT_ID;
1645
3788
  if (!accountId) throw new AlephaError("CLOUDFLARE_ACCOUNT_ID environment variable is not set. https://orm.drizzle.team/docs/guides/d1-http-with-drizzle-kit");
1646
3789
  const url = options.providerUrl;
1647
- if (!url.startsWith("cloudflare-d1://")) throw new AlephaError("D1 provider URL must start with 'cloudflare-d1://'.");
1648
- const [, databaseId] = url.replace("cloudflare-d1://", "").replace("cloudflare-d1:", "").split(":");
1649
- if (!databaseId) throw new AlephaError("Database ID is missing in the D1 provider URL. Cloudflare D1 URL format: cloudflare-d1://<database_name>:<database_id>");
3790
+ if (!url.startsWith("d1://")) throw new AlephaError("D1 provider URL must start with 'd1://'.");
3791
+ const [, databaseId] = url.replace("d1://", "").replace("d1:", "").split(":");
3792
+ if (!databaseId) throw new AlephaError("Database ID is missing in the D1 provider URL. Cloudflare D1 URL format: d1://<database_name>:<database_id>");
1650
3793
  config.dbCredentials = {
1651
3794
  accountId,
1652
3795
  databaseId,
@@ -1778,7 +3921,6 @@ var DeployCommand = class {
1778
3921
  var ViteDevServerProvider = class {
1779
3922
  log = $logger();
1780
3923
  fs = $inject(FileSystemProvider);
1781
- templateProvider = $inject(ViteTemplateProvider);
1782
3924
  server;
1783
3925
  options;
1784
3926
  alepha = null;
@@ -1883,11 +4025,11 @@ var ViteDevServerProvider = class {
1883
4025
  async setupEnvironment() {
1884
4026
  const { loadEnv } = await importVite();
1885
4027
  const env = loadEnv(process.env.NODE_ENV || "development", this.options.root, "");
4028
+ for (const [key, value] of Object.entries(env)) process.env[key] ??= value;
1886
4029
  process.env.NODE_ENV ??= "development";
1887
4030
  process.env.VITE_ALEPHA_DEV = "true";
1888
4031
  process.env.SERVER_HOST ??= this.options.host?.toString() ?? "localhost";
1889
4032
  process.env.SERVER_PORT ??= String(this.options.port ?? (process.env.SERVER_PORT ? Number(process.env.SERVER_PORT) : 3e3));
1890
- for (const [key, value] of Object.entries(env)) process.env[key] ??= value;
1891
4033
  }
1892
4034
  /**
1893
4035
  * Load or reload the Alepha instance.
@@ -1920,12 +4062,10 @@ var ViteDevServerProvider = class {
1920
4062
  }
1921
4063
  }
1922
4064
  /**
1923
- * Setup Alepha instance with Vite middleware and template.
4065
+ * Setup Alepha instance with Vite middleware.
1924
4066
  */
1925
4067
  async setupAlepha() {
1926
4068
  if (!this.alepha || !this.hasReact()) return;
1927
- const template = await this.server.transformIndexHtml("/", this.templateProvider.generateIndexHtml(this.options.entry));
1928
- this.alepha.store.set("alepha.react.server.template", template);
1929
4069
  this.alepha.events.on("server:onRequest", {
1930
4070
  priority: "first",
1931
4071
  callback: async ({ request }) => {
@@ -1952,6 +4092,7 @@ var ViteDevServerProvider = class {
1952
4092
  * Run Vite middleware and detect if it handled the request.
1953
4093
  */
1954
4094
  async runViteMiddleware(req, res, ctx) {
4095
+ if (res.headersSent || res.writableEnded) return false;
1955
4096
  return new Promise((resolve) => {
1956
4097
  let resolved = false;
1957
4098
  const done = (handled) => {
@@ -1960,6 +4101,18 @@ var ViteDevServerProvider = class {
1960
4101
  if (handled) ctx.metadata.vite = true;
1961
4102
  resolve(handled);
1962
4103
  };
4104
+ const originalSetHeader = res.setHeader.bind(res);
4105
+ const originalWriteHead = res.writeHead?.bind(res);
4106
+ const originalWrite = res.write.bind(res);
4107
+ const originalEnd = res.end.bind(res);
4108
+ const guardedCall = (fn, ...args) => {
4109
+ if (resolved && !ctx.metadata.vite) return;
4110
+ return fn(...args);
4111
+ };
4112
+ res.setHeader = (...args) => guardedCall(originalSetHeader, ...args);
4113
+ if (originalWriteHead) res.writeHead = (...args) => guardedCall(originalWriteHead, ...args);
4114
+ res.write = (...args) => guardedCall(originalWrite, ...args);
4115
+ res.end = (...args) => guardedCall(originalEnd, ...args);
1963
4116
  res.on("finish", () => done(true));
1964
4117
  res.on("close", () => res.headersSent && done(true));
1965
4118
  this.server.middlewares(req, res, () => done(false));
@@ -1999,23 +4152,14 @@ var DevCommand = class {
1999
4152
  boot = $inject(AppEntryProvider);
2000
4153
  /**
2001
4154
  * Will run the project in watch mode.
2002
- *
2003
- * - If an index.html file is found in the project root, it will run Vite in dev mode.
2004
- * - Otherwise, it will look for a server entry file and run it with tsx in watch mode.
2005
4155
  */
2006
4156
  dev = $command({
2007
4157
  name: "dev",
2008
4158
  description: "Run the project in development mode",
2009
4159
  handler: async ({ root }) => {
2010
- const [expo, react] = await Promise.all([this.pm.hasExpo(root), this.pm.hasReact(root)]);
2011
4160
  await this.scaffolder.ensureConfig(root, { tsconfigJson: true });
2012
- if (expo) {
2013
- await this.utils.exec("expo start");
2014
- return;
2015
- }
2016
4161
  const entry = await this.boot.getAppEntry(root);
2017
4162
  this.log.debug("Entry file found", { entry });
2018
- await this.pm.ensureDependency(root, "vite", { exec: (cmd, opts) => this.utils.exec(cmd, opts) });
2019
4163
  await this.viteDevServer.init({
2020
4164
  root,
2021
4165
  entry
@@ -2255,7 +4399,10 @@ var GenEnvCommand = class {
2255
4399
  description: "Output file path (e.g., .env)"
2256
4400
  })) }),
2257
4401
  handler: async ({ root, flags }) => {
2258
- const { alepha } = await this.utils.loadAlephaFromServerEntryFile(root);
4402
+ const alepha = await this.utils.loadAlephaFromServerEntryFile({
4403
+ root,
4404
+ mode: "development"
4405
+ });
2259
4406
  try {
2260
4407
  const { env } = alepha.dump();
2261
4408
  let dotEnvFile = "";
@@ -2288,7 +4435,10 @@ var OpenApiCommand = class {
2288
4435
  description: "Output file path"
2289
4436
  })) }),
2290
4437
  handler: async ({ root, flags }) => {
2291
- const { alepha } = await this.utils.loadAlephaFromServerEntryFile(root);
4438
+ const alepha = await this.utils.loadAlephaFromServerEntryFile({
4439
+ root,
4440
+ mode: "development"
4441
+ });
2292
4442
  try {
2293
4443
  const openapiProvider = alepha.inject(ServerSwaggerProvider);
2294
4444
  await alepha.events.emit("configure", alepha);
@@ -2355,19 +4505,21 @@ var InitCommand = class {
2355
4505
  lowercase: true
2356
4506
  })),
2357
4507
  flags: t.object({
2358
- agent: t.optional(t.boolean({
2359
- aliases: ["a"],
2360
- description: "Add CLAUDE.md for Claude Code AI assistant"
2361
- })),
2362
- yarn: t.optional(t.boolean({ description: "Use Yarn package manager" })),
2363
- pnpm: t.optional(t.boolean({ description: "Use pnpm package manager" })),
2364
- npm: t.optional(t.boolean({ description: "Use npm package manager" })),
2365
- bun: t.optional(t.boolean({ description: "Use Bun package manager" })),
4508
+ ai: t.optional(t.boolean({ description: "Add AI agent instructions (CLAUDE.md if claude CLI installed, else AGENTS.md)" })),
4509
+ pm: t.optional(t.enum([
4510
+ "yarn",
4511
+ "npm",
4512
+ "pnpm",
4513
+ "bun"
4514
+ ], { description: "Package manager to use" })),
4515
+ api: t.optional(t.boolean({ description: "Include API module structure (src/api/)" })),
2366
4516
  react: t.optional(t.boolean({
2367
4517
  aliases: ["r"],
2368
- description: "Include Alepha React dependencies"
4518
+ description: "Include React dependencies and web module (src/web/)"
2369
4519
  })),
2370
- ui: t.optional(t.boolean({ description: "Include Alepha UI dependencies" })),
4520
+ ui: t.optional(t.boolean({ description: "Include @alepha/ui (components, auth portal, admin portal)" })),
4521
+ auth: t.optional(t.boolean({ description: "Include authentication (AppSecurity, $uiAuth). Implies --api --ui --react" })),
4522
+ admin: t.optional(t.boolean({ description: "Include admin portal ($uiAdmin). Implies --auth" })),
2371
4523
  test: t.optional(t.boolean({ description: "Include Vitest and create test directory" })),
2372
4524
  force: t.optional(t.boolean({
2373
4525
  aliases: ["f"],
@@ -2375,11 +4527,19 @@ var InitCommand = class {
2375
4527
  }))
2376
4528
  }),
2377
4529
  handler: async ({ run, flags, root, args }) => {
2378
- if (flags.react) flags.ui = true;
2379
4530
  if (args) {
2380
4531
  root = this.fs.join(root, args);
2381
- await this.fs.mkdir(root);
4532
+ await this.fs.mkdir(root, { force: true });
2382
4533
  }
4534
+ if (flags.admin) flags.auth = true;
4535
+ if (flags.auth) {
4536
+ flags.api = true;
4537
+ flags.ui = true;
4538
+ }
4539
+ if (flags.ui) flags.react = true;
4540
+ const workspace = await this.pm.getWorkspaceContext(root);
4541
+ let agentType = false;
4542
+ if (flags.ai) agentType = await this.utils.isInstalledAsync("claude") ? "claude" : "agents";
2383
4543
  const isExpo = await this.pm.hasExpo(root);
2384
4544
  const force = !!flags.force;
2385
4545
  await run({
@@ -2387,46 +4547,60 @@ var InitCommand = class {
2387
4547
  handler: async () => {
2388
4548
  await this.scaffolder.ensureConfig(root, {
2389
4549
  force,
2390
- tsconfigJson: true,
2391
- packageJson: flags,
2392
- biomeJson: true,
2393
- editorconfig: true,
2394
- indexHtml: !!flags.react && !isExpo,
2395
- claudeMd: flags.agent ? {
4550
+ tsconfigJson: !workspace.config.tsconfigJson,
4551
+ packageJson: {
4552
+ ...flags,
4553
+ isPackage: workspace.isPackage
4554
+ },
4555
+ biomeJson: !workspace.config.biomeJson,
4556
+ editorconfig: !workspace.config.editorconfig,
4557
+ agentMd: agentType ? {
4558
+ type: agentType,
2396
4559
  react: !!flags.react,
2397
4560
  ui: !!flags.ui
2398
4561
  } : false
2399
4562
  });
2400
- if (!flags.react) await this.scaffolder.ensureApiProject(root, { force });
4563
+ await this.scaffolder.ensureMainServerTs(root, {
4564
+ api: !!flags.api,
4565
+ react: !!flags.react && !isExpo,
4566
+ force
4567
+ });
4568
+ if (flags.api) await this.scaffolder.ensureApiProject(root, {
4569
+ auth: !!flags.auth,
4570
+ force
4571
+ });
4572
+ if (flags.react && !isExpo) await this.scaffolder.ensureWebProject(root, {
4573
+ api: !!flags.api,
4574
+ ui: !!flags.ui,
4575
+ auth: !!flags.auth,
4576
+ admin: !!flags.admin,
4577
+ force
4578
+ });
2401
4579
  }
2402
4580
  });
2403
- const pmName = await this.pm.getPackageManager(root, flags);
2404
- if (pmName === "yarn") {
4581
+ const pmName = await this.pm.getPackageManager(workspace.workspaceRoot ?? root, flags.pm ?? workspace.packageManager ?? void 0);
4582
+ if (!workspace.isPackage) if (pmName === "yarn") {
2405
4583
  await this.pm.ensureYarn(root);
2406
4584
  await run("yarn set version stable", { root });
2407
4585
  } else if (pmName === "bun") await this.pm.ensureBun(root);
2408
4586
  else if (pmName === "pnpm") await this.pm.ensurePnpm(root);
2409
4587
  else await this.pm.ensureNpm(root);
4588
+ const installRoot = workspace.workspaceRoot ?? root;
2410
4589
  await run(`${pmName} install`, {
2411
4590
  alias: `installing dependencies with ${pmName}`,
2412
- root
2413
- });
2414
- if (!isExpo) await this.pm.ensureDependency(root, "vite", {
2415
- run,
2416
- exec: (cmd, opts) => this.utils.exec(cmd, opts)
2417
- });
2418
- await this.pm.ensureDependency(root, "@biomejs/biome", {
2419
- run,
2420
- exec: (cmd, opts) => this.utils.exec(cmd, opts)
4591
+ root: installRoot
2421
4592
  });
2422
- if (flags.test) {
2423
- await this.scaffolder.ensureTestDir(root);
2424
- await run(`${pmName} ${pmName === "yarn" ? "add" : "install"} -D vitest`, { alias: "setup testing with Vitest" });
2425
- }
4593
+ if (flags.test) await this.scaffolder.ensureTestDir(root);
2426
4594
  await run(`${pmName} run lint`, {
2427
4595
  alias: "running linter",
2428
- root
4596
+ root: installRoot
2429
4597
  });
4598
+ if (!workspace.isPackage) {
4599
+ if (await this.scaffolder.ensureGitRepo(root, { force })) await run("git add .", {
4600
+ alias: "staging generated files",
4601
+ root
4602
+ });
4603
+ }
2430
4604
  }
2431
4605
  });
2432
4606
  };
@@ -2441,8 +4615,14 @@ var LintCommand = class {
2441
4615
  name: "lint",
2442
4616
  description: "Run linter across the codebase using Biome",
2443
4617
  handler: async ({ root }) => {
2444
- await this.scaffolder.ensureConfig(root, { biomeJson: true });
2445
- await this.pm.ensureDependency(root, "@biomejs/biome", { exec: (cmd, opts) => this.utils.exec(cmd, opts) });
4618
+ await this.scaffolder.ensureConfig(root, {
4619
+ biomeJson: true,
4620
+ checkWorkspace: true
4621
+ });
4622
+ await this.pm.ensureDependency(root, "@biomejs/biome", {
4623
+ checkWorkspace: true,
4624
+ exec: (cmd, opts) => this.utils.exec(cmd, opts)
4625
+ });
2446
4626
  await this.utils.exec("biome check --fix");
2447
4627
  }
2448
4628
  });
@@ -2508,6 +4688,7 @@ var TypecheckCommand = class {
2508
4688
  utils = $inject(AlephaCliUtils);
2509
4689
  pm = $inject(PackageManagerUtils);
2510
4690
  log = $logger();
4691
+ scaffolder = $inject(ProjectScaffolder);
2511
4692
  /**
2512
4693
  * Run TypeScript type checking across the codebase with no emit.
2513
4694
  */
@@ -2517,7 +4698,14 @@ var TypecheckCommand = class {
2517
4698
  description: "Check TypeScript types across the codebase",
2518
4699
  handler: async ({ root }) => {
2519
4700
  this.log.info("Starting TypeScript type checking...");
2520
- await this.pm.ensureDependency(root, "typescript", { exec: (cmd, opts) => this.utils.exec(cmd, opts) });
4701
+ await this.scaffolder.ensureConfig(root, {
4702
+ tsconfigJson: true,
4703
+ checkWorkspace: true
4704
+ });
4705
+ await this.pm.ensureDependency(root, "typescript", {
4706
+ checkWorkspace: true,
4707
+ exec: (cmd, opts) => this.utils.exec(cmd, opts)
4708
+ });
2521
4709
  await this.utils.exec("tsc --noEmit");
2522
4710
  this.log.info("TypeScript type checking completed successfully.");
2523
4711
  }
@@ -2561,15 +4749,6 @@ var VerifyCommand = class {
2561
4749
  //#endregion
2562
4750
  //#region ../../src/cli/apps/AlephaCli.ts
2563
4751
  /**
2564
- * Register `tsx` when running in Node.js, ignore for Bun.
2565
- *
2566
- * It's required to have a full TypeScript support. (mostly .tsx files)
2567
- */
2568
- if (typeof Bun === "undefined") {
2569
- const { register } = await import("tsx/esm/api");
2570
- register();
2571
- }
2572
- /**
2573
4752
  * Allow to extend Alepha CLI via `alepha.config.ts` file located in the project root.
2574
4753
  */
2575
4754
  var AlephaCliExtension = class {
@@ -2633,6 +4812,7 @@ var AlephaPackageBuilderCli = class {
2633
4812
  pkgData.exports[path].types = `./src/${item.name}/index.ts`;
2634
4813
  if (item.native) pkgData.exports[path]["react-native"] = `./src/${item.name}/index.native.ts`;
2635
4814
  else if (item.browser) pkgData.exports[path]["react-native"] = `./src/${item.name}/index.browser.ts`;
4815
+ if (item.workerd) pkgData.exports[path].workerd = `./src/${item.name}/index.workerd.ts`;
2636
4816
  if (item.browser) pkgData.exports[path].browser = `./src/${item.name}/index.browser.ts`;
2637
4817
  if (item.bun) pkgData.exports[path].bun = `./src/${item.name}/index.bun.ts`;
2638
4818
  pkgData.exports[path].import = `./src/${item.name}/index.ts`;
@@ -2789,6 +4969,7 @@ async function analyzeModules(srcDir, packageName) {
2789
4969
  const hasNative = await fileExists(join(modulePath, "index.native.ts"));
2790
4970
  const hasBun = await fileExists(join(modulePath, "index.bun.ts"));
2791
4971
  const hasNode = await fileExists(join(modulePath, "index.node.ts"));
4972
+ const hasEdge = await fileExists(join(modulePath, "index.workerd.ts"));
2792
4973
  const files = await getAllFiles(modulePath);
2793
4974
  for (const file of files) {
2794
4975
  const deps = extractAlephaDependencies(await readFile(file, "utf-8"), packageName, moduleName);
@@ -2803,6 +4984,7 @@ async function analyzeModules(srcDir, packageName) {
2803
4984
  dependencies: Array.from(dependencies)
2804
4985
  };
2805
4986
  if (hasNative) module.native = true;
4987
+ if (hasEdge) module.workerd = true;
2806
4988
  if (hasBrowser) module.browser = true;
2807
4989
  if (hasBun) module.bun = true;
2808
4990
  if (hasNode) module.node = true;