alepha 0.15.1 → 0.15.2

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 (507) hide show
  1. package/dist/api/audits/index.d.ts +342 -365
  2. package/dist/api/audits/index.d.ts.map +1 -1
  3. package/dist/api/audits/index.js +10 -33
  4. package/dist/api/audits/index.js.map +1 -1
  5. package/dist/api/files/index.d.ts +180 -173
  6. package/dist/api/files/index.d.ts.map +1 -1
  7. package/dist/api/files/index.js +10 -3
  8. package/dist/api/files/index.js.map +1 -1
  9. package/dist/api/jobs/index.d.ts +162 -155
  10. package/dist/api/jobs/index.d.ts.map +1 -1
  11. package/dist/api/jobs/index.js +10 -3
  12. package/dist/api/jobs/index.js.map +1 -1
  13. package/dist/api/keys/index.d.ts +413 -0
  14. package/dist/api/keys/index.d.ts.map +1 -0
  15. package/dist/api/keys/index.js +476 -0
  16. package/dist/api/keys/index.js.map +1 -0
  17. package/dist/api/notifications/index.d.ts +10 -4
  18. package/dist/api/notifications/index.d.ts.map +1 -1
  19. package/dist/api/notifications/index.js +10 -4
  20. package/dist/api/notifications/index.js.map +1 -1
  21. package/dist/api/parameters/index.d.ts +294 -301
  22. package/dist/api/parameters/index.d.ts.map +1 -1
  23. package/dist/api/parameters/index.js +30 -37
  24. package/dist/api/parameters/index.js.map +1 -1
  25. package/dist/api/users/index.d.ts +1079 -769
  26. package/dist/api/users/index.d.ts.map +1 -1
  27. package/dist/api/users/index.js +2534 -218
  28. package/dist/api/users/index.js.map +1 -1
  29. package/dist/api/verifications/index.d.ts +10 -4
  30. package/dist/api/verifications/index.d.ts.map +1 -1
  31. package/dist/api/verifications/index.js +12 -4
  32. package/dist/api/verifications/index.js.map +1 -1
  33. package/dist/batch/index.d.ts +20 -40
  34. package/dist/batch/index.d.ts.map +1 -1
  35. package/dist/batch/index.js +31 -44
  36. package/dist/batch/index.js.map +1 -1
  37. package/dist/bucket/index.d.ts +432 -8
  38. package/dist/bucket/index.d.ts.map +1 -1
  39. package/dist/bucket/index.js +1856 -12
  40. package/dist/bucket/index.js.map +1 -1
  41. package/dist/cache/core/index.d.ts +179 -7
  42. package/dist/cache/core/index.d.ts.map +1 -1
  43. package/dist/cache/core/index.js +213 -7
  44. package/dist/cache/core/index.js.map +1 -1
  45. package/dist/cache/redis/index.d.ts +1 -0
  46. package/dist/cache/redis/index.d.ts.map +1 -1
  47. package/dist/cache/redis/index.js +4 -0
  48. package/dist/cache/redis/index.js.map +1 -1
  49. package/dist/cli/index.d.ts +488 -5612
  50. package/dist/cli/index.d.ts.map +1 -1
  51. package/dist/cli/index.js +2326 -311
  52. package/dist/cli/index.js.map +1 -1
  53. package/dist/command/index.d.ts +194 -46
  54. package/dist/command/index.d.ts.map +1 -1
  55. package/dist/command/index.js +1995 -60
  56. package/dist/command/index.js.map +1 -1
  57. package/dist/core/index.browser.js +42 -19
  58. package/dist/core/index.browser.js.map +1 -1
  59. package/dist/core/index.d.ts +34 -13
  60. package/dist/core/index.d.ts.map +1 -1
  61. package/dist/core/index.js +62 -19
  62. package/dist/core/index.js.map +1 -1
  63. package/dist/core/index.native.js +42 -19
  64. package/dist/core/index.native.js.map +1 -1
  65. package/dist/datetime/index.d.ts +15 -0
  66. package/dist/datetime/index.d.ts.map +1 -1
  67. package/dist/datetime/index.js +15 -0
  68. package/dist/datetime/index.js.map +1 -1
  69. package/dist/email/index.d.ts +315 -20
  70. package/dist/email/index.d.ts.map +1 -1
  71. package/dist/email/index.js +1852 -7
  72. package/dist/email/index.js.map +1 -1
  73. package/dist/fake/index.d.ts +90 -8
  74. package/dist/fake/index.d.ts.map +1 -1
  75. package/dist/fake/index.js +91 -20
  76. package/dist/fake/index.js.map +1 -1
  77. package/dist/lock/core/index.d.ts +11 -4
  78. package/dist/lock/core/index.d.ts.map +1 -1
  79. package/dist/lock/core/index.js +11 -4
  80. package/dist/lock/core/index.js.map +1 -1
  81. package/dist/logger/index.d.ts +17 -66
  82. package/dist/logger/index.d.ts.map +1 -1
  83. package/dist/logger/index.js +14 -63
  84. package/dist/logger/index.js.map +1 -1
  85. package/dist/mcp/index.d.ts +15 -35
  86. package/dist/mcp/index.d.ts.map +1 -1
  87. package/dist/mcp/index.js +12 -35
  88. package/dist/mcp/index.js.map +1 -1
  89. package/dist/orm/index.browser.js +3 -3
  90. package/dist/orm/index.browser.js.map +1 -1
  91. package/dist/orm/index.bun.js +7 -4
  92. package/dist/orm/index.bun.js.map +1 -1
  93. package/dist/orm/index.d.ts +514 -540
  94. package/dist/orm/index.d.ts.map +1 -1
  95. package/dist/orm/index.js +24 -49
  96. package/dist/orm/index.js.map +1 -1
  97. package/dist/queue/core/index.d.ts +18 -10
  98. package/dist/queue/core/index.d.ts.map +1 -1
  99. package/dist/queue/core/index.js +14 -6
  100. package/dist/queue/core/index.js.map +1 -1
  101. package/dist/react/auth/index.browser.js +108 -0
  102. package/dist/react/auth/index.browser.js.map +1 -0
  103. package/dist/react/auth/index.d.ts +100 -0
  104. package/dist/react/auth/index.d.ts.map +1 -0
  105. package/dist/react/auth/index.js +145 -0
  106. package/dist/react/auth/index.js.map +1 -0
  107. package/dist/react/core/index.d.ts +469 -0
  108. package/dist/react/core/index.d.ts.map +1 -0
  109. package/dist/react/core/index.js +464 -0
  110. package/dist/react/core/index.js.map +1 -0
  111. package/dist/react/form/index.d.ts +232 -0
  112. package/dist/react/form/index.d.ts.map +1 -0
  113. package/dist/react/form/index.js +432 -0
  114. package/dist/react/form/index.js.map +1 -0
  115. package/dist/react/head/index.browser.js +423 -0
  116. package/dist/react/head/index.browser.js.map +1 -0
  117. package/dist/react/head/index.d.ts +288 -0
  118. package/dist/react/head/index.d.ts.map +1 -0
  119. package/dist/react/head/index.js +465 -0
  120. package/dist/react/head/index.js.map +1 -0
  121. package/dist/react/i18n/index.d.ts +175 -0
  122. package/dist/react/i18n/index.d.ts.map +1 -0
  123. package/dist/react/i18n/index.js +224 -0
  124. package/dist/react/i18n/index.js.map +1 -0
  125. package/dist/react/router/index.browser.js +1980 -0
  126. package/dist/react/router/index.browser.js.map +1 -0
  127. package/dist/react/router/index.d.ts +2068 -0
  128. package/dist/react/router/index.d.ts.map +1 -0
  129. package/dist/react/router/index.js +4932 -0
  130. package/dist/react/router/index.js.map +1 -0
  131. package/dist/react/websocket/index.d.ts +117 -0
  132. package/dist/react/websocket/index.d.ts.map +1 -0
  133. package/dist/react/websocket/index.js +107 -0
  134. package/dist/react/websocket/index.js.map +1 -0
  135. package/dist/redis/index.bun.js +4 -0
  136. package/dist/redis/index.bun.js.map +1 -1
  137. package/dist/redis/index.d.ts +22 -25
  138. package/dist/redis/index.d.ts.map +1 -1
  139. package/dist/redis/index.js +16 -25
  140. package/dist/redis/index.js.map +1 -1
  141. package/dist/retry/index.d.ts +11 -2
  142. package/dist/retry/index.d.ts.map +1 -1
  143. package/dist/retry/index.js +11 -2
  144. package/dist/retry/index.js.map +1 -1
  145. package/dist/scheduler/index.d.ts +11 -2
  146. package/dist/scheduler/index.d.ts.map +1 -1
  147. package/dist/scheduler/index.js +11 -2
  148. package/dist/scheduler/index.js.map +1 -1
  149. package/dist/security/index.d.ts +110 -19
  150. package/dist/security/index.d.ts.map +1 -1
  151. package/dist/security/index.js +157 -26
  152. package/dist/security/index.js.map +1 -1
  153. package/dist/server/auth/index.d.ts +179 -174
  154. package/dist/server/auth/index.d.ts.map +1 -1
  155. package/dist/server/auth/index.js +12 -7
  156. package/dist/server/auth/index.js.map +1 -1
  157. package/dist/server/cache/index.d.ts +7 -22
  158. package/dist/server/cache/index.d.ts.map +1 -1
  159. package/dist/server/cache/index.js +7 -22
  160. package/dist/server/cache/index.js.map +1 -1
  161. package/dist/server/compress/index.d.ts +10 -2
  162. package/dist/server/compress/index.d.ts.map +1 -1
  163. package/dist/server/compress/index.js +10 -2
  164. package/dist/server/compress/index.js.map +1 -1
  165. package/dist/server/cookies/index.d.ts +40 -16
  166. package/dist/server/cookies/index.d.ts.map +1 -1
  167. package/dist/server/cookies/index.js +7 -5
  168. package/dist/server/cookies/index.js.map +1 -1
  169. package/dist/server/core/index.d.ts +115 -14
  170. package/dist/server/core/index.d.ts.map +1 -1
  171. package/dist/server/core/index.js +231 -14
  172. package/dist/server/core/index.js.map +1 -1
  173. package/dist/server/cors/index.d.ts +13 -23
  174. package/dist/server/cors/index.d.ts.map +1 -1
  175. package/dist/server/cors/index.js +7 -21
  176. package/dist/server/cors/index.js.map +1 -1
  177. package/dist/server/health/index.d.ts +25 -19
  178. package/dist/server/health/index.d.ts.map +1 -1
  179. package/dist/server/health/index.js +8 -2
  180. package/dist/server/health/index.js.map +1 -1
  181. package/dist/server/helmet/index.d.ts +11 -3
  182. package/dist/server/helmet/index.d.ts.map +1 -1
  183. package/dist/server/helmet/index.js +11 -3
  184. package/dist/server/helmet/index.js.map +1 -1
  185. package/dist/server/links/index.d.ts +50 -45
  186. package/dist/server/links/index.d.ts.map +1 -1
  187. package/dist/server/links/index.js +11 -6
  188. package/dist/server/links/index.js.map +1 -1
  189. package/dist/server/metrics/index.d.ts +10 -3
  190. package/dist/server/metrics/index.d.ts.map +1 -1
  191. package/dist/server/metrics/index.js +10 -3
  192. package/dist/server/metrics/index.js.map +1 -1
  193. package/dist/server/multipart/index.d.ts +9 -3
  194. package/dist/server/multipart/index.d.ts.map +1 -1
  195. package/dist/server/multipart/index.js +9 -3
  196. package/dist/server/multipart/index.js.map +1 -1
  197. package/dist/server/proxy/index.d.ts +8 -2
  198. package/dist/server/proxy/index.d.ts.map +1 -1
  199. package/dist/server/proxy/index.js +8 -2
  200. package/dist/server/proxy/index.js.map +1 -1
  201. package/dist/server/rate-limit/index.d.ts +30 -35
  202. package/dist/server/rate-limit/index.d.ts.map +1 -1
  203. package/dist/server/rate-limit/index.js +18 -55
  204. package/dist/server/rate-limit/index.js.map +1 -1
  205. package/dist/server/static/index.d.ts +137 -4
  206. package/dist/server/static/index.d.ts.map +1 -1
  207. package/dist/server/static/index.js +1848 -5
  208. package/dist/server/static/index.js.map +1 -1
  209. package/dist/server/swagger/index.d.ts +301 -6
  210. package/dist/server/swagger/index.d.ts.map +1 -1
  211. package/dist/server/swagger/index.js +1849 -6
  212. package/dist/server/swagger/index.js.map +1 -1
  213. package/dist/sms/index.d.ts +301 -7
  214. package/dist/sms/index.d.ts.map +1 -1
  215. package/dist/sms/index.js +1851 -7
  216. package/dist/sms/index.js.map +1 -1
  217. package/dist/system/index.browser.js +496 -0
  218. package/dist/system/index.browser.js.map +1 -0
  219. package/dist/{file → system}/index.d.ts +335 -16
  220. package/dist/system/index.d.ts.map +1 -0
  221. package/dist/{file → system}/index.js +412 -20
  222. package/dist/system/index.js.map +1 -0
  223. package/dist/thread/index.d.ts +11 -2
  224. package/dist/thread/index.d.ts.map +1 -1
  225. package/dist/thread/index.js +11 -2
  226. package/dist/thread/index.js.map +1 -1
  227. package/dist/topic/core/index.d.ts +12 -5
  228. package/dist/topic/core/index.d.ts.map +1 -1
  229. package/dist/topic/core/index.js +12 -5
  230. package/dist/topic/core/index.js.map +1 -1
  231. package/dist/vite/index.d.ts +4 -6271
  232. package/dist/vite/index.d.ts.map +1 -1
  233. package/dist/vite/index.js +8 -3
  234. package/dist/vite/index.js.map +1 -1
  235. package/dist/websocket/index.d.ts +12 -8
  236. package/dist/websocket/index.d.ts.map +1 -1
  237. package/dist/websocket/index.js +12 -8
  238. package/dist/websocket/index.js.map +1 -1
  239. package/package.json +80 -11
  240. package/src/api/audits/index.ts +10 -33
  241. package/src/api/files/__tests__/$bucket.spec.ts +1 -1
  242. package/src/api/files/controllers/AdminFileStatsController.spec.ts +1 -1
  243. package/src/api/files/controllers/FileController.spec.ts +1 -1
  244. package/src/api/files/index.ts +10 -3
  245. package/src/api/files/jobs/FileJobs.spec.ts +1 -1
  246. package/src/api/files/services/FileService.spec.ts +1 -1
  247. package/src/api/jobs/index.ts +10 -3
  248. package/src/api/keys/controllers/AdminApiKeyController.ts +75 -0
  249. package/src/api/keys/controllers/ApiKeyController.ts +103 -0
  250. package/src/api/keys/entities/apiKeyEntity.ts +41 -0
  251. package/src/api/keys/index.ts +49 -0
  252. package/src/api/keys/schemas/adminApiKeyQuerySchema.ts +7 -0
  253. package/src/api/keys/schemas/adminApiKeyResourceSchema.ts +17 -0
  254. package/src/api/keys/schemas/createApiKeyBodySchema.ts +7 -0
  255. package/src/api/keys/schemas/createApiKeyResponseSchema.ts +11 -0
  256. package/src/api/keys/schemas/listApiKeyResponseSchema.ts +15 -0
  257. package/src/api/keys/schemas/revokeApiKeyParamsSchema.ts +5 -0
  258. package/src/api/keys/schemas/revokeApiKeyResponseSchema.ts +5 -0
  259. package/src/api/keys/services/ApiKeyService.spec.ts +553 -0
  260. package/src/api/keys/services/ApiKeyService.ts +306 -0
  261. package/src/api/logs/TODO.md +52 -0
  262. package/src/api/notifications/index.ts +10 -4
  263. package/src/api/parameters/index.ts +9 -30
  264. package/src/api/parameters/primitives/$config.ts +12 -4
  265. package/src/api/parameters/services/ConfigStore.ts +9 -3
  266. package/src/api/users/__tests__/ApiKeys-integration.spec.ts +1035 -0
  267. package/src/api/users/__tests__/ApiKeys.spec.ts +401 -0
  268. package/src/api/users/index.ts +14 -3
  269. package/src/api/users/primitives/$realm.ts +33 -5
  270. package/src/api/users/providers/RealmProvider.ts +1 -12
  271. package/src/api/users/services/SessionService.ts +1 -1
  272. package/src/api/verifications/controllers/VerificationController.ts +2 -0
  273. package/src/api/verifications/index.ts +10 -4
  274. package/src/batch/index.ts +9 -36
  275. package/src/batch/primitives/$batch.ts +0 -8
  276. package/src/batch/providers/BatchProvider.ts +29 -2
  277. package/src/bucket/__tests__/shared.ts +1 -1
  278. package/src/bucket/index.ts +13 -6
  279. package/src/bucket/primitives/$bucket.ts +1 -1
  280. package/src/bucket/providers/LocalFileStorageProvider.ts +1 -1
  281. package/src/bucket/providers/MemoryFileStorageProvider.ts +1 -1
  282. package/src/cache/core/__tests__/shared.ts +30 -0
  283. package/src/cache/core/index.ts +11 -6
  284. package/src/cache/core/primitives/$cache.spec.ts +5 -0
  285. package/src/cache/core/providers/CacheProvider.ts +17 -0
  286. package/src/cache/core/providers/MemoryCacheProvider.ts +300 -1
  287. package/src/cache/redis/__tests__/cache-redis.spec.ts +5 -0
  288. package/src/cache/redis/providers/RedisCacheProvider.ts +9 -0
  289. package/src/cli/apps/AlephaCli.ts +1 -14
  290. package/src/cli/apps/AlephaPackageBuilderCli.ts +1 -1
  291. package/src/cli/commands/build.ts +1 -5
  292. package/src/cli/commands/db.ts +17 -11
  293. package/src/cli/commands/deploy.ts +1 -1
  294. package/src/cli/commands/dev.ts +1 -20
  295. package/src/cli/commands/gen/env.ts +5 -2
  296. package/src/cli/commands/gen/openapi.ts +5 -2
  297. package/src/cli/commands/init.spec.ts +544 -0
  298. package/src/cli/commands/init.ts +89 -55
  299. package/src/cli/commands/lint.ts +7 -1
  300. package/src/cli/commands/typecheck.ts +11 -0
  301. package/src/cli/providers/AppEntryProvider.ts +1 -1
  302. package/src/cli/providers/ViteBuildProvider.ts +8 -50
  303. package/src/cli/providers/ViteDevServerProvider.ts +36 -8
  304. package/src/cli/services/AlephaCliUtils.ts +37 -122
  305. package/src/cli/services/PackageManagerUtils.ts +127 -11
  306. package/src/cli/services/ProjectScaffolder.ts +122 -77
  307. package/src/cli/services/ViteUtils.ts +82 -0
  308. package/src/cli/{assets/claudeMd.ts → templates/agentMd.ts} +32 -24
  309. package/src/cli/templates/gitignore.ts +39 -0
  310. package/src/cli/{assets → templates}/mainCss.ts +11 -2
  311. package/src/cli/templates/mainServerTs.ts +33 -0
  312. package/src/cli/templates/webAppRouterTs.ts +50 -0
  313. package/src/cli/{assets → templates}/webHelloComponentTsx.ts +2 -2
  314. package/src/command/helpers/Runner.spec.ts +4 -0
  315. package/src/command/helpers/Runner.ts +3 -21
  316. package/src/command/index.ts +12 -4
  317. package/src/command/providers/CliProvider.spec.ts +1067 -0
  318. package/src/command/providers/CliProvider.ts +203 -40
  319. package/src/core/Alepha.ts +2 -2
  320. package/src/core/__tests__/Alepha-start.spec.ts +4 -4
  321. package/src/core/helpers/jsonSchemaToTypeBox.spec.ts +771 -0
  322. package/src/core/helpers/jsonSchemaToTypeBox.ts +62 -10
  323. package/src/core/index.shared.ts +1 -0
  324. package/src/core/index.ts +20 -0
  325. package/src/core/providers/EventManager.spec.ts +0 -71
  326. package/src/core/providers/EventManager.ts +3 -15
  327. package/src/core/providers/Json.ts +2 -14
  328. package/src/datetime/index.ts +15 -0
  329. package/src/email/index.ts +10 -5
  330. package/src/email/providers/LocalEmailProvider.spec.ts +1 -1
  331. package/src/email/providers/LocalEmailProvider.ts +1 -1
  332. package/src/fake/__tests__/keyName.example.ts +1 -1
  333. package/src/fake/__tests__/keyName.spec.ts +5 -5
  334. package/src/fake/index.ts +9 -6
  335. package/src/fake/providers/FakeProvider.spec.ts +258 -40
  336. package/src/fake/providers/FakeProvider.ts +133 -19
  337. package/src/lock/core/index.ts +11 -4
  338. package/src/logger/index.ts +17 -66
  339. package/src/mcp/index.ts +10 -27
  340. package/src/mcp/transports/SseMcpTransport.ts +0 -11
  341. package/src/orm/__tests__/PostgresProvider.spec.ts +2 -2
  342. package/src/orm/index.browser.ts +2 -2
  343. package/src/orm/index.bun.ts +4 -2
  344. package/src/orm/index.ts +21 -47
  345. package/src/orm/providers/drivers/BunSqliteProvider.ts +1 -0
  346. package/src/orm/services/Repository.ts +7 -3
  347. package/src/queue/core/index.ts +14 -6
  348. package/src/react/auth/__tests__/$auth.spec.ts +202 -0
  349. package/src/react/auth/hooks/useAuth.ts +32 -0
  350. package/src/react/auth/index.browser.ts +13 -0
  351. package/src/react/auth/index.shared.ts +2 -0
  352. package/src/react/auth/index.ts +48 -0
  353. package/src/react/auth/providers/ReactAuthProvider.ts +16 -0
  354. package/src/react/auth/services/ReactAuth.ts +135 -0
  355. package/src/react/core/__tests__/Router.spec.tsx +169 -0
  356. package/src/react/core/components/ClientOnly.tsx +49 -0
  357. package/src/react/core/components/ErrorBoundary.tsx +73 -0
  358. package/src/react/core/contexts/AlephaContext.ts +7 -0
  359. package/src/react/core/contexts/AlephaProvider.tsx +42 -0
  360. package/src/react/core/hooks/useAction.browser.spec.tsx +569 -0
  361. package/src/react/core/hooks/useAction.ts +480 -0
  362. package/src/react/core/hooks/useAlepha.ts +26 -0
  363. package/src/react/core/hooks/useClient.ts +17 -0
  364. package/src/react/core/hooks/useEvents.ts +51 -0
  365. package/src/react/core/hooks/useInject.ts +12 -0
  366. package/src/react/core/hooks/useStore.ts +52 -0
  367. package/src/react/core/index.ts +90 -0
  368. package/src/react/form/components/FormState.tsx +17 -0
  369. package/src/react/form/errors/FormValidationError.ts +18 -0
  370. package/src/react/form/hooks/useForm.browser.spec.tsx +366 -0
  371. package/src/react/form/hooks/useForm.ts +47 -0
  372. package/src/react/form/hooks/useFormState.ts +130 -0
  373. package/src/react/form/index.ts +44 -0
  374. package/src/react/form/services/FormModel.ts +614 -0
  375. package/src/react/head/helpers/SeoExpander.spec.ts +203 -0
  376. package/src/react/head/helpers/SeoExpander.ts +142 -0
  377. package/src/react/head/hooks/useHead.spec.tsx +288 -0
  378. package/src/react/head/hooks/useHead.ts +62 -0
  379. package/src/react/head/index.browser.ts +26 -0
  380. package/src/react/head/index.ts +44 -0
  381. package/src/react/head/interfaces/Head.ts +105 -0
  382. package/src/react/head/primitives/$head.ts +25 -0
  383. package/src/react/head/providers/BrowserHeadProvider.browser.spec.ts +196 -0
  384. package/src/react/head/providers/BrowserHeadProvider.ts +212 -0
  385. package/src/react/head/providers/HeadProvider.ts +168 -0
  386. package/src/react/head/providers/ServerHeadProvider.ts +31 -0
  387. package/src/react/i18n/__tests__/integration.spec.tsx +239 -0
  388. package/src/react/i18n/components/Localize.spec.tsx +357 -0
  389. package/src/react/i18n/components/Localize.tsx +35 -0
  390. package/src/react/i18n/hooks/useI18n.browser.spec.tsx +438 -0
  391. package/src/react/i18n/hooks/useI18n.ts +18 -0
  392. package/src/react/i18n/index.ts +41 -0
  393. package/src/react/i18n/primitives/$dictionary.ts +69 -0
  394. package/src/react/i18n/providers/I18nProvider.spec.ts +389 -0
  395. package/src/react/i18n/providers/I18nProvider.ts +278 -0
  396. package/src/react/router/__tests__/page-head-browser.browser.spec.ts +95 -0
  397. package/src/react/router/__tests__/page-head.spec.ts +48 -0
  398. package/src/react/router/__tests__/seo-head.spec.ts +125 -0
  399. package/src/react/router/atoms/ssrManifestAtom.ts +58 -0
  400. package/src/react/router/components/ErrorViewer.tsx +872 -0
  401. package/src/react/router/components/Link.tsx +23 -0
  402. package/src/react/router/components/NestedView.tsx +223 -0
  403. package/src/react/router/components/NotFound.tsx +30 -0
  404. package/src/react/router/constants/PAGE_PRELOAD_KEY.ts +6 -0
  405. package/src/react/router/contexts/RouterLayerContext.ts +12 -0
  406. package/src/react/router/errors/Redirection.ts +28 -0
  407. package/src/react/router/hooks/useActive.ts +52 -0
  408. package/src/react/router/hooks/useQueryParams.ts +63 -0
  409. package/src/react/router/hooks/useRouter.ts +20 -0
  410. package/src/react/router/hooks/useRouterState.ts +11 -0
  411. package/src/react/router/index.browser.ts +45 -0
  412. package/src/react/router/index.shared.ts +19 -0
  413. package/src/react/router/index.ts +142 -0
  414. package/src/react/router/primitives/$page.browser.spec.tsx +851 -0
  415. package/src/react/router/primitives/$page.spec.tsx +708 -0
  416. package/src/react/router/primitives/$page.ts +497 -0
  417. package/src/react/router/providers/ReactBrowserProvider.ts +309 -0
  418. package/src/react/router/providers/ReactBrowserRendererProvider.ts +25 -0
  419. package/src/react/router/providers/ReactBrowserRouterProvider.ts +168 -0
  420. package/src/react/router/providers/ReactPageProvider.ts +726 -0
  421. package/src/react/router/providers/ReactServerProvider.spec.tsx +316 -0
  422. package/src/react/router/providers/ReactServerProvider.ts +558 -0
  423. package/src/react/router/providers/ReactServerTemplateProvider.ts +979 -0
  424. package/src/react/router/providers/SSRManifestProvider.ts +334 -0
  425. package/src/react/router/services/ReactPageServerService.ts +48 -0
  426. package/src/react/router/services/ReactPageService.ts +27 -0
  427. package/src/react/router/services/ReactRouter.ts +262 -0
  428. package/src/react/websocket/hooks/useRoom.tsx +242 -0
  429. package/src/react/websocket/index.ts +7 -0
  430. package/src/redis/__tests__/redis.spec.ts +13 -0
  431. package/src/redis/index.ts +9 -25
  432. package/src/redis/providers/BunRedisProvider.ts +9 -0
  433. package/src/redis/providers/NodeRedisProvider.ts +8 -0
  434. package/src/redis/providers/RedisProvider.ts +16 -0
  435. package/src/retry/index.ts +11 -2
  436. package/src/router/index.ts +15 -0
  437. package/src/scheduler/index.ts +11 -2
  438. package/src/security/__tests__/BasicAuth.spec.ts +2 -0
  439. package/src/security/__tests__/ServerSecurityProvider.spec.ts +13 -5
  440. package/src/security/index.ts +15 -10
  441. package/src/security/interfaces/IssuerResolver.ts +27 -0
  442. package/src/security/primitives/$issuer.ts +55 -0
  443. package/src/security/providers/SecurityProvider.ts +179 -0
  444. package/src/security/providers/ServerBasicAuthProvider.ts +6 -2
  445. package/src/security/providers/ServerSecurityProvider.ts +36 -22
  446. package/src/server/auth/index.ts +12 -7
  447. package/src/server/cache/index.ts +7 -22
  448. package/src/server/compress/index.ts +10 -2
  449. package/src/server/cookies/index.ts +7 -5
  450. package/src/server/cookies/primitives/$cookie.ts +33 -11
  451. package/src/server/core/index.ts +16 -6
  452. package/src/server/core/interfaces/ServerRequest.ts +83 -1
  453. package/src/server/core/primitives/$action.spec.ts +1 -1
  454. package/src/server/core/primitives/$action.ts +8 -3
  455. package/src/server/core/providers/NodeHttpServerProvider.ts +9 -3
  456. package/src/server/core/services/ServerRequestParser.spec.ts +520 -0
  457. package/src/server/core/services/ServerRequestParser.ts +306 -13
  458. package/src/server/cors/index.ts +7 -21
  459. package/src/server/cors/primitives/$cors.ts +6 -2
  460. package/src/server/health/index.ts +8 -2
  461. package/src/server/helmet/index.ts +11 -3
  462. package/src/server/links/index.ts +11 -6
  463. package/src/server/metrics/index.ts +10 -3
  464. package/src/server/multipart/index.ts +9 -3
  465. package/src/server/proxy/index.ts +8 -2
  466. package/src/server/rate-limit/index.ts +21 -25
  467. package/src/server/rate-limit/primitives/$rateLimit.ts +6 -2
  468. package/src/server/rate-limit/providers/ServerRateLimitProvider.spec.ts +38 -14
  469. package/src/server/rate-limit/providers/ServerRateLimitProvider.ts +22 -56
  470. package/src/server/static/index.ts +8 -2
  471. package/src/server/static/providers/ServerStaticProvider.ts +1 -1
  472. package/src/server/swagger/index.ts +9 -4
  473. package/src/server/swagger/providers/ServerSwaggerProvider.ts +1 -1
  474. package/src/sms/index.ts +9 -5
  475. package/src/sms/providers/LocalSmsProvider.spec.ts +1 -1
  476. package/src/sms/providers/LocalSmsProvider.ts +1 -1
  477. package/src/system/index.browser.ts +11 -0
  478. package/src/system/index.ts +62 -0
  479. package/src/{file → system}/providers/FileSystemProvider.ts +16 -0
  480. package/src/{file → system}/providers/MemoryFileSystemProvider.ts +116 -3
  481. package/src/system/providers/MemoryShellProvider.ts +164 -0
  482. package/src/{file → system}/providers/NodeFileSystemProvider.spec.ts +2 -2
  483. package/src/{file → system}/providers/NodeFileSystemProvider.ts +36 -0
  484. package/src/system/providers/NodeShellProvider.ts +184 -0
  485. package/src/system/providers/ShellProvider.ts +74 -0
  486. package/src/{file → system}/services/FileDetector.spec.ts +2 -2
  487. package/src/thread/index.ts +11 -2
  488. package/src/topic/core/index.ts +12 -5
  489. package/src/vite/tasks/buildClient.ts +2 -7
  490. package/src/vite/tasks/buildServer.ts +17 -1
  491. package/src/websocket/index.ts +12 -8
  492. package/dist/file/index.d.ts.map +0 -1
  493. package/dist/file/index.js.map +0 -1
  494. package/src/cli/assets/mainServerTs.ts +0 -24
  495. package/src/cli/assets/webAppRouterTs.ts +0 -16
  496. package/src/cli/providers/ViteTemplateProvider.ts +0 -27
  497. package/src/file/index.ts +0 -43
  498. /package/src/cli/{assets → templates}/apiHelloControllerTs.ts +0 -0
  499. /package/src/cli/{assets → templates}/apiIndexTs.ts +0 -0
  500. /package/src/cli/{assets → templates}/biomeJson.ts +0 -0
  501. /package/src/cli/{assets → templates}/dummySpecTs.ts +0 -0
  502. /package/src/cli/{assets → templates}/editorconfig.ts +0 -0
  503. /package/src/cli/{assets → templates}/mainBrowserTs.ts +0 -0
  504. /package/src/cli/{assets → templates}/tsconfigJson.ts +0 -0
  505. /package/src/cli/{assets → templates}/webIndexTs.ts +0 -0
  506. /package/src/{file → system}/errors/FileError.ts +0 -0
  507. /package/src/{file → system}/services/FileDetector.ts +0 -0
package/dist/cli/index.js CHANGED
@@ -1,16 +1,1852 @@
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
+ await mkdir(path, options);
1375
+ }
1376
+ /**
1377
+ * Lists files in a directory.
1378
+ *
1379
+ * @param path - The directory path to list
1380
+ * @param options - List options
1381
+ * @returns Array of filenames
1382
+ *
1383
+ * @example
1384
+ * ```typescript
1385
+ * const fs = alepha.inject(NodeFileSystemProvider);
1386
+ *
1387
+ * // List files in a directory
1388
+ * const files = await fs.ls("/tmp");
1389
+ * console.log(files); // ["file1.txt", "file2.txt", "subdir"]
1390
+ *
1391
+ * // List with hidden files
1392
+ * const allFiles = await fs.ls("/tmp", { hidden: true });
1393
+ *
1394
+ * // List recursively
1395
+ * const allFilesRecursive = await fs.ls("/tmp", { recursive: true });
1396
+ * ```
1397
+ */
1398
+ async ls(path, options) {
1399
+ const entries = await readdir(path);
1400
+ const filteredEntries = options?.hidden ? entries : entries.filter((e) => !e.startsWith("."));
1401
+ if (options?.recursive) {
1402
+ const allFiles = [];
1403
+ for (const entry of filteredEntries) {
1404
+ const fullPath = join(path, entry);
1405
+ if ((await stat(fullPath)).isDirectory()) {
1406
+ allFiles.push(entry);
1407
+ const subFiles = await this.ls(fullPath, options);
1408
+ allFiles.push(...subFiles.map((f) => join(entry, f)));
1409
+ } else allFiles.push(entry);
1410
+ }
1411
+ return allFiles;
1412
+ }
1413
+ return filteredEntries;
1414
+ }
1415
+ /**
1416
+ * Checks if a file or directory exists.
1417
+ *
1418
+ * @param path - The path to check
1419
+ * @returns True if the path exists, false otherwise
1420
+ *
1421
+ * @example
1422
+ * ```typescript
1423
+ * const fs = alepha.inject(NodeFileSystemProvider);
1424
+ *
1425
+ * if (await fs.exists("/tmp/file.txt")) {
1426
+ * console.log("File exists");
1427
+ * }
1428
+ * ```
1429
+ */
1430
+ async exists(path) {
1431
+ try {
1432
+ await access(path);
1433
+ return true;
1434
+ } catch {
1435
+ return false;
1436
+ }
1437
+ }
1438
+ /**
1439
+ * Reads the content of a file.
1440
+ *
1441
+ * @param path - The file path to read
1442
+ * @returns The file content as a Buffer
1443
+ *
1444
+ * @example
1445
+ * ```typescript
1446
+ * const fs = alepha.inject(NodeFileSystemProvider);
1447
+ *
1448
+ * const buffer = await fs.readFile("/tmp/file.txt");
1449
+ * console.log(buffer.toString("utf-8"));
1450
+ * ```
1451
+ */
1452
+ async readFile(path) {
1453
+ return await readFile(path);
1454
+ }
1455
+ /**
1456
+ * Writes data to a file.
1457
+ *
1458
+ * @param path - The file path to write to
1459
+ * @param data - The data to write (Buffer or string)
1460
+ *
1461
+ * @example
1462
+ * ```typescript
1463
+ * const fs = alepha.inject(NodeFileSystemProvider);
1464
+ *
1465
+ * // Write string
1466
+ * await fs.writeFile("/tmp/file.txt", "Hello, world!");
1467
+ *
1468
+ * // Write Buffer
1469
+ * await fs.writeFile("/tmp/file.bin", Buffer.from([0x01, 0x02, 0x03]));
1470
+ * ```
1471
+ */
1472
+ async writeFile(path, data) {
1473
+ if (isFileLike(data)) {
1474
+ await writeFile(path, Readable.from(data.stream()));
1475
+ return;
1476
+ }
1477
+ await writeFile(path, data);
1478
+ }
1479
+ /**
1480
+ * Reads the content of a file as a string.
1481
+ *
1482
+ * @param path - The file path to read
1483
+ * @returns The file content as a string
1484
+ *
1485
+ * @example
1486
+ * ```typescript
1487
+ * const fs = alepha.inject(NodeFileSystemProvider);
1488
+ * const content = await fs.readTextFile("/tmp/file.txt");
1489
+ * ```
1490
+ */
1491
+ async readTextFile(path) {
1492
+ return (await this.readFile(path)).toString("utf-8");
1493
+ }
1494
+ /**
1495
+ * Reads the content of a file as JSON.
1496
+ *
1497
+ * @param path - The file path to read
1498
+ * @returns The parsed JSON content
1499
+ *
1500
+ * @example
1501
+ * ```typescript
1502
+ * const fs = alepha.inject(NodeFileSystemProvider);
1503
+ * const config = await fs.readJsonFile<{ name: string }>("/tmp/config.json");
1504
+ * ```
1505
+ */
1506
+ async readJsonFile(path) {
1507
+ const text = await this.readTextFile(path);
1508
+ return this.json.parse(text);
1509
+ }
1510
+ /**
1511
+ * Creates a FileLike object from a Web File.
1512
+ *
1513
+ * @protected
1514
+ */
1515
+ createFileFromWebFile(source, options = {}) {
1516
+ const name = options.name ?? source.name;
1517
+ return {
1518
+ name,
1519
+ type: options.type ?? (source.type || this.detector.getContentType(name)),
1520
+ size: options.size ?? source.size ?? 0,
1521
+ lastModified: source.lastModified || Date.now(),
1522
+ stream: () => source.stream(),
1523
+ arrayBuffer: async () => {
1524
+ return await source.arrayBuffer();
1525
+ },
1526
+ text: async () => {
1527
+ return await source.text();
1528
+ }
1529
+ };
1530
+ }
1531
+ /**
1532
+ * Creates a FileLike object from a Buffer.
1533
+ *
1534
+ * @protected
1535
+ */
1536
+ createFileFromBuffer(source, options = {}) {
1537
+ const name = options.name ?? "file";
1538
+ return {
1539
+ name,
1540
+ type: options.type ?? this.detector.getContentType(options.name ?? name),
1541
+ size: source.byteLength,
1542
+ lastModified: Date.now(),
1543
+ stream: () => Readable.from(source),
1544
+ arrayBuffer: async () => {
1545
+ return this.bufferToArrayBuffer(source);
1546
+ },
1547
+ text: async () => {
1548
+ return source.toString("utf-8");
1549
+ }
1550
+ };
1551
+ }
1552
+ /**
1553
+ * Creates a FileLike object from a stream.
1554
+ *
1555
+ * @protected
1556
+ */
1557
+ createFileFromStream(source, options = {}) {
1558
+ let buffer = null;
1559
+ return {
1560
+ name: options.name ?? "file",
1561
+ type: options.type ?? this.detector.getContentType(options.name ?? "file"),
1562
+ size: options.size ?? 0,
1563
+ lastModified: Date.now(),
1564
+ stream: () => source,
1565
+ _buffer: null,
1566
+ arrayBuffer: async () => {
1567
+ buffer ??= await this.streamToBuffer(source);
1568
+ return this.bufferToArrayBuffer(buffer);
1569
+ },
1570
+ text: async () => {
1571
+ buffer ??= await this.streamToBuffer(source);
1572
+ return buffer.toString("utf-8");
1573
+ }
1574
+ };
1575
+ }
1576
+ /**
1577
+ * Creates a FileLike object from a URL.
1578
+ *
1579
+ * @protected
1580
+ */
1581
+ createFileFromUrl(url, options = {}) {
1582
+ const parsedUrl = new URL(url);
1583
+ const filename = options.name || parsedUrl.pathname.split("/").pop() || "file";
1584
+ let buffer = null;
1585
+ return {
1586
+ name: filename,
1587
+ type: options.type ?? this.detector.getContentType(filename),
1588
+ size: 0,
1589
+ lastModified: Date.now(),
1590
+ stream: () => this.createStreamFromUrl(url),
1591
+ arrayBuffer: async () => {
1592
+ buffer ??= await this.loadFromUrl(url);
1593
+ return this.bufferToArrayBuffer(buffer);
1594
+ },
1595
+ text: async () => {
1596
+ buffer ??= await this.loadFromUrl(url);
1597
+ return buffer.toString("utf-8");
1598
+ },
1599
+ filepath: url
1600
+ };
1601
+ }
1602
+ /**
1603
+ * Gets a streaming response from a URL.
1604
+ *
1605
+ * @protected
1606
+ */
1607
+ getStreamingResponse(url) {
1608
+ const stream = new PassThrough();
1609
+ fetch(url).then((res) => Readable.fromWeb(res.body).pipe(stream)).catch((err) => stream.destroy(err));
1610
+ return stream;
1611
+ }
1612
+ /**
1613
+ * Loads data from a URL.
1614
+ *
1615
+ * @protected
1616
+ */
1617
+ async loadFromUrl(url) {
1618
+ const parsedUrl = new URL(url);
1619
+ if (parsedUrl.protocol === "file:") return await readFile(fileURLToPath(url));
1620
+ else if (parsedUrl.protocol === "http:" || parsedUrl.protocol === "https:") {
1621
+ const response = await fetch(url);
1622
+ if (!response.ok) throw new Error(`Failed to fetch ${url}: ${response.status} ${response.statusText}`);
1623
+ const arrayBuffer = await response.arrayBuffer();
1624
+ return Buffer.from(arrayBuffer);
1625
+ } else throw new Error(`Unsupported protocol: ${parsedUrl.protocol}`);
1626
+ }
1627
+ /**
1628
+ * Creates a stream from a URL.
1629
+ *
1630
+ * @protected
1631
+ */
1632
+ createStreamFromUrl(url) {
1633
+ const parsedUrl = new URL(url);
1634
+ if (parsedUrl.protocol === "file:") return createReadStream(fileURLToPath(url));
1635
+ else if (parsedUrl.protocol === "http:" || parsedUrl.protocol === "https:") return this.getStreamingResponse(url);
1636
+ else throw new AlephaError(`Unsupported protocol: ${parsedUrl.protocol}`);
1637
+ }
1638
+ /**
1639
+ * Converts a stream-like object to a Buffer.
1640
+ *
1641
+ * @protected
1642
+ */
1643
+ async streamToBuffer(streamLike) {
1644
+ const stream = streamLike instanceof Readable ? streamLike : Readable.fromWeb(streamLike);
1645
+ return new Promise((resolve, reject) => {
1646
+ const buffer = [];
1647
+ stream.on("data", (chunk) => buffer.push(Buffer.from(chunk)));
1648
+ stream.on("end", () => resolve(Buffer.concat(buffer)));
1649
+ stream.on("error", (err) => reject(new AlephaError("Error converting stream", { cause: err })));
1650
+ });
1651
+ }
1652
+ /**
1653
+ * Converts a Node.js Buffer to an ArrayBuffer.
1654
+ *
1655
+ * @protected
1656
+ */
1657
+ bufferToArrayBuffer(buffer) {
1658
+ return buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
1659
+ }
1660
+ };
1661
+
1662
+ //#endregion
1663
+ //#region ../../src/system/providers/NodeShellProvider.ts
1664
+ /**
1665
+ * Node.js implementation of ShellProvider.
1666
+ *
1667
+ * Executes shell commands using Node.js child_process module.
1668
+ * Supports binary resolution from node_modules/.bin for local packages.
1669
+ */
1670
+ var NodeShellProvider = class {
1671
+ log = $logger();
1672
+ fs = $inject(FileSystemProvider);
1673
+ /**
1674
+ * Run a shell command or binary.
1675
+ */
1676
+ async run(command, options = {}) {
1677
+ const { resolve = false, capture = false, root, env } = options;
1678
+ const cwd = root ?? process.cwd();
1679
+ this.log.debug(`Shell: ${command}`, {
1680
+ cwd,
1681
+ resolve,
1682
+ capture
1683
+ });
1684
+ let executable;
1685
+ let args;
1686
+ if (resolve) {
1687
+ const [bin, ...rest] = command.split(" ");
1688
+ executable = await this.resolveExecutable(bin, cwd);
1689
+ args = rest;
1690
+ } else [executable, ...args] = command.split(" ");
1691
+ if (capture) return this.execCapture(command, {
1692
+ cwd,
1693
+ env
1694
+ });
1695
+ return this.execInherit(executable, args, {
1696
+ cwd,
1697
+ env
1698
+ });
1699
+ }
1700
+ /**
1701
+ * Execute command with inherited stdio (streams to terminal).
1702
+ */
1703
+ async execInherit(executable, args, options) {
1704
+ const proc = spawn(executable, args, {
1705
+ stdio: "inherit",
1706
+ cwd: options.cwd,
1707
+ env: {
1708
+ ...process.env,
1709
+ ...options.env
1710
+ }
1711
+ });
1712
+ return new Promise((resolve, reject) => {
1713
+ proc.on("exit", (code) => {
1714
+ if (code === 0 || code === null) resolve("");
1715
+ else reject(new AlephaError(`Command exited with code ${code}`));
1716
+ });
1717
+ proc.on("error", reject);
1718
+ });
1719
+ }
1720
+ /**
1721
+ * Execute command and capture stdout.
1722
+ */
1723
+ execCapture(command, options) {
1724
+ return new Promise((resolve, reject) => {
1725
+ exec(command, {
1726
+ cwd: options.cwd,
1727
+ env: {
1728
+ ...process.env,
1729
+ LOG_FORMAT: "pretty",
1730
+ ...options.env
1731
+ }
1732
+ }, (err, stdout) => {
1733
+ if (err) {
1734
+ err.stdout = stdout;
1735
+ reject(err);
1736
+ } else resolve(stdout);
1737
+ });
1738
+ });
1739
+ }
1740
+ /**
1741
+ * Resolve executable path from node_modules/.bin.
1742
+ *
1743
+ * Search order:
1744
+ * 1. Local: node_modules/.bin/
1745
+ * 2. Pnpm nested: node_modules/alepha/node_modules/.bin/
1746
+ * 3. Monorepo: Walk up to 3 parent directories
1747
+ */
1748
+ async resolveExecutable(name, root) {
1749
+ const suffix = process.platform === "win32" ? ".cmd" : "";
1750
+ let execPath = await this.findExecutable(root, `node_modules/.bin/${name}${suffix}`);
1751
+ if (!execPath) execPath = await this.findExecutable(root, `node_modules/alepha/node_modules/.bin/${name}${suffix}`);
1752
+ if (!execPath) {
1753
+ let parentDir = this.fs.join(root, "..");
1754
+ for (let i = 0; i < 3; i++) {
1755
+ execPath = await this.findExecutable(parentDir, `node_modules/.bin/${name}${suffix}`);
1756
+ if (execPath) break;
1757
+ parentDir = this.fs.join(parentDir, "..");
1758
+ }
1759
+ }
1760
+ if (!execPath) throw new AlephaError(`Could not find executable for '${name}'. Make sure the package is installed.`);
1761
+ return execPath;
1762
+ }
1763
+ /**
1764
+ * Check if executable exists at path.
1765
+ */
1766
+ async findExecutable(root, relativePath) {
1767
+ const fullPath = this.fs.join(root, relativePath);
1768
+ if (await this.fs.exists(fullPath)) return fullPath;
1769
+ }
1770
+ /**
1771
+ * Check if a command is installed and available in the system PATH.
1772
+ */
1773
+ isInstalled(command) {
1774
+ return new Promise((resolve) => {
1775
+ exec(process.platform === "win32" ? `where ${command}` : `command -v ${command}`, (error) => resolve(!error));
1776
+ });
1777
+ }
1778
+ };
1779
+
1780
+ //#endregion
1781
+ //#region ../../src/system/providers/ShellProvider.ts
1782
+ /**
1783
+ * Abstract provider for executing shell commands and binaries.
1784
+ *
1785
+ * Implementations:
1786
+ * - `NodeShellProvider` - Real shell execution using Node.js child_process
1787
+ * - `MemoryShellProvider` - In-memory mock for testing
1788
+ *
1789
+ * @example
1790
+ * ```typescript
1791
+ * class MyService {
1792
+ * protected readonly shell = $inject(ShellProvider);
1793
+ *
1794
+ * async build() {
1795
+ * // Run shell command directly
1796
+ * await this.shell.run("yarn install");
1797
+ *
1798
+ * // Run local binary with resolution
1799
+ * await this.shell.run("vite build", { resolve: true });
1800
+ *
1801
+ * // Capture output
1802
+ * const output = await this.shell.run("echo hello", { capture: true });
1803
+ * }
1804
+ * }
1805
+ * ```
1806
+ */
1807
+ var ShellProvider = class {};
1808
+
1809
+ //#endregion
1810
+ //#region ../../src/system/index.ts
1811
+ /**
1812
+ * | type | quality | stability |
1813
+ * |------|---------|-----------|
1814
+ * | tooling | standard | stable |
1815
+ *
1816
+ * System-level abstractions for portable code across runtimes.
1817
+ *
1818
+ * **Features:**
1819
+ * - File system operations (read, write, exists, etc.)
1820
+ * - Shell command execution
1821
+ * - File type detection and MIME utilities
1822
+ * - Memory implementations for testing
1823
+ *
1824
+ * @module alepha.system
1825
+ */
1826
+ const AlephaSystem = $module({
1827
+ name: "alepha.system",
1828
+ primitives: [],
1829
+ services: [
1830
+ FileDetector,
1831
+ FileSystemProvider,
1832
+ MemoryFileSystemProvider,
1833
+ NodeFileSystemProvider,
1834
+ ShellProvider,
1835
+ MemoryShellProvider,
1836
+ NodeShellProvider
1837
+ ],
1838
+ register: (alepha) => alepha.with({
1839
+ optional: true,
1840
+ provide: FileSystemProvider,
1841
+ use: NodeFileSystemProvider
1842
+ }).with({
1843
+ optional: true,
1844
+ provide: ShellProvider,
1845
+ use: alepha.isTest() ? MemoryShellProvider : NodeShellProvider
1846
+ })
1847
+ });
1848
+
1849
+ //#endregion
14
1850
  //#region ../../src/core/constants/KIND.ts
15
1851
  /**
16
1852
  * Used for identifying primitives.
@@ -191,9 +2027,10 @@ var AppEntryProvider = class {
191
2027
  };
192
2028
 
193
2029
  //#endregion
194
- //#region ../../src/cli/providers/ViteTemplateProvider.ts
195
- var ViteTemplateProvider = class {
2030
+ //#region ../../src/cli/services/ViteUtils.ts
2031
+ var ViteUtils = class {
196
2032
  fs = $inject(FileSystemProvider);
2033
+ viteDevServer;
197
2034
  generateIndexHtml(entry) {
198
2035
  const style = entry.style;
199
2036
  const browser = entry.browser ?? entry.server;
@@ -213,15 +2050,6 @@ ${style ? `<link rel="stylesheet" href="/${style}" />` : ""}
213
2050
  </html>
214
2051
  `.trim();
215
2052
  }
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
2053
  /**
226
2054
  * We need to close the Vite dev server after build is done.
227
2055
  */
@@ -238,10 +2066,10 @@ var ViteBuildProvider = class {
238
2066
  await this.viteDevServer?.close();
239
2067
  }
240
2068
  });
241
- async init(opts) {
2069
+ async runAlepha(opts) {
242
2070
  const { createServer } = await importVite();
2071
+ process.env.NODE_ENV = opts.mode;
243
2072
  process.env.ALEPHA_CLI_IMPORT = "true";
244
- process.env.NODE_ENV = "production";
245
2073
  process.env.LOG_LEVEL ??= "warn";
246
2074
  /**
247
2075
  * 01/26 Vite 7
@@ -257,6 +2085,21 @@ var ViteBuildProvider = class {
257
2085
  await this.viteDevServer.ssrLoadModule(opts.entry.server);
258
2086
  const alepha = globalThis.__alepha;
259
2087
  if (!alepha) throw new AlephaError("Alepha instance not found after loading entry module");
2088
+ return alepha;
2089
+ }
2090
+ };
2091
+
2092
+ //#endregion
2093
+ //#region ../../src/cli/providers/ViteBuildProvider.ts
2094
+ var ViteBuildProvider = class {
2095
+ alepha;
2096
+ appEntry;
2097
+ viteUtils = $inject(ViteUtils);
2098
+ async init(opts) {
2099
+ const alepha = await this.viteUtils.runAlepha({
2100
+ entry: opts.entry,
2101
+ mode: "production"
2102
+ });
260
2103
  this.alepha = alepha;
261
2104
  this.appEntry = opts.entry;
262
2105
  return alepha;
@@ -272,7 +2115,7 @@ var ViteBuildProvider = class {
272
2115
  }
273
2116
  generateIndexHtml() {
274
2117
  if (!this.appEntry) throw new AlephaError("ViteBuildProvider not initialized");
275
- return this.templateProvider.generateIndexHtml(this.appEntry);
2118
+ return this.viteUtils.generateIndexHtml(this.appEntry);
276
2119
  }
277
2120
  };
278
2121
 
@@ -292,44 +2135,23 @@ var AlephaCliUtils = class {
292
2135
  fs = $inject(FileSystemProvider);
293
2136
  envUtils = $inject(EnvUtils);
294
2137
  boot = $inject(AppEntryProvider);
2138
+ shell = $inject(ShellProvider);
2139
+ viteUtils = $inject(ViteUtils);
295
2140
  /**
296
2141
  * Execute a command with inherited stdio.
2142
+ *
2143
+ * @param command - The command to execute
2144
+ * @param options.root - Working directory
2145
+ * @param options.env - Additional environment variables
2146
+ * @param options.global - If true, run command directly without resolving from node_modules
297
2147
  */
298
2148
  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);
2149
+ await this.shell.run(command, {
2150
+ root: options.root,
2151
+ env: options.env,
2152
+ resolve: !options.global,
2153
+ capture: false
2154
+ });
333
2155
  }
334
2156
  /**
335
2157
  * Write a configuration file to node_modules/.alepha directory.
@@ -342,33 +2164,14 @@ var AlephaCliUtils = class {
342
2164
  this.log.debug(`Config file written: ${path}`);
343
2165
  return path;
344
2166
  }
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();
351
- 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}`);
2167
+ async loadAlephaFromServerEntryFile(opts) {
2168
+ let entry;
2169
+ if ("root" in opts) entry = await this.boot.getAppEntry(opts.root);
2170
+ else entry = opts.entry;
2171
+ return await this.viteUtils.runAlepha({
2172
+ entry,
2173
+ mode: opts.mode
2174
+ });
372
2175
  }
373
2176
  /**
374
2177
  * Generate JavaScript code for Drizzle entities export.
@@ -396,12 +2199,39 @@ ${models.map((it) => `export const ${it} = models["${it}"];`).join("\n")}
396
2199
  async exists(root, path) {
397
2200
  return this.fs.exists(this.fs.join(root, path));
398
2201
  }
399
- async checkFileExists(root, name) {
400
- const configPath = this.fs.join(root, name);
401
- if (await this.fs.exists(configPath)) return configPath;
2202
+ /**
2203
+ * Check if a command is installed and available in the system PATH.
2204
+ */
2205
+ isInstalledAsync(cmd) {
2206
+ return this.shell.isInstalled(cmd);
402
2207
  }
403
2208
  };
404
2209
 
2210
+ //#endregion
2211
+ //#region ../../package.json
2212
+ var devDependencies = {
2213
+ "@biomejs/biome": "^2.3.13",
2214
+ "@electric-sql/pglite": "^0.3.15",
2215
+ "@faker-js/faker": "^10.2.0",
2216
+ "@testing-library/dom": "^10.4.1",
2217
+ "@testing-library/react": "^16.3.2",
2218
+ "@types/node": "^25.1.0",
2219
+ "@types/nodemailer": "^7.0.9",
2220
+ "@types/react": "^19.2.10",
2221
+ "@types/react-dom": "^19.2.3",
2222
+ "@types/ws": "^8.18.1",
2223
+ "cron-schedule": "^6.0.0",
2224
+ "jose": "^6.1.3",
2225
+ "jsdom": "^27.4.0",
2226
+ "openid-client": "^6.8.1",
2227
+ "prom-client": "^15.1.3",
2228
+ "react": "^19.2.4",
2229
+ "react-dom": "^19.2.4",
2230
+ "swagger-ui-dist": "^5.31.0",
2231
+ "tsdown": "^0.20.1",
2232
+ "vitest": "^4.0.18"
2233
+ };
2234
+
405
2235
  //#endregion
406
2236
  //#region ../../src/cli/version.ts
407
2237
  const packageJson = JSON.parse(readFileSync(new URL("../../package.json", import.meta.url), "utf-8"));
@@ -425,11 +2255,8 @@ var PackageManagerUtils = class {
425
2255
  /**
426
2256
  * Detect the package manager used in the project.
427
2257
  */
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";
2258
+ async getPackageManager(root, pm) {
2259
+ if (pm) return pm;
433
2260
  if (this.alepha.isBun()) return "bun";
434
2261
  if (await this.fs.exists(this.fs.join(root, "bun.lock"))) return "bun";
435
2262
  if (await this.fs.exists(this.fs.join(root, "yarn.lock"))) return "yarn";
@@ -437,6 +2264,46 @@ var PackageManagerUtils = class {
437
2264
  return "npm";
438
2265
  }
439
2266
  /**
2267
+ * Detect workspace context when inside a monorepo package.
2268
+ *
2269
+ * Checks if we're inside a workspace package (e.g., packages/my-pkg or apps/my-app)
2270
+ * by looking 2 levels up for workspace indicators like lockfiles and config files.
2271
+ *
2272
+ * @param root - The current package directory
2273
+ * @returns Workspace context with root path, PM, and config presence
2274
+ */
2275
+ async getWorkspaceContext(root) {
2276
+ const workspaceRoot = this.fs.join(root, "..", "..");
2277
+ const [hasYarnLock, hasPnpmLock, hasNpmLock, hasBunLock] = await Promise.all([
2278
+ this.fs.exists(this.fs.join(workspaceRoot, "yarn.lock")),
2279
+ this.fs.exists(this.fs.join(workspaceRoot, "pnpm-lock.yaml")),
2280
+ this.fs.exists(this.fs.join(workspaceRoot, "package-lock.json")),
2281
+ this.fs.exists(this.fs.join(workspaceRoot, "bun.lock"))
2282
+ ]);
2283
+ const [hasBiome, hasEditorConfig, hasTsConfig, hasWorkspacePackageJson] = await Promise.all([
2284
+ this.fs.exists(this.fs.join(workspaceRoot, "biome.json")),
2285
+ this.fs.exists(this.fs.join(workspaceRoot, ".editorconfig")),
2286
+ this.fs.exists(this.fs.join(workspaceRoot, "tsconfig.json")),
2287
+ this.fs.exists(this.fs.join(workspaceRoot, "package.json"))
2288
+ ]);
2289
+ const isPackage = (hasYarnLock || hasPnpmLock || hasNpmLock || hasBunLock) && hasWorkspacePackageJson;
2290
+ let packageManager = null;
2291
+ if (hasYarnLock) packageManager = "yarn";
2292
+ else if (hasPnpmLock) packageManager = "pnpm";
2293
+ else if (hasBunLock) packageManager = "bun";
2294
+ else if (hasNpmLock) packageManager = "npm";
2295
+ return {
2296
+ isPackage,
2297
+ workspaceRoot: isPackage ? workspaceRoot : null,
2298
+ packageManager,
2299
+ config: {
2300
+ biomeJson: hasBiome,
2301
+ editorconfig: hasEditorConfig,
2302
+ tsconfigJson: hasTsConfig
2303
+ }
2304
+ };
2305
+ }
2306
+ /**
440
2307
  * Get the install command for a package.
441
2308
  */
442
2309
  async getInstallCommand(root, packageName, dev = true) {
@@ -481,13 +2348,23 @@ var PackageManagerUtils = class {
481
2348
  }
482
2349
  /**
483
2350
  * Install a dependency if it's missing from the project.
2351
+ * Optionally checks workspace root for the dependency in monorepo setups.
484
2352
  */
485
2353
  async ensureDependency(root, packageName, options = {}) {
486
- const { dev = true } = options;
2354
+ const { dev = true, checkWorkspace = false } = options;
487
2355
  if (await this.hasDependency(root, packageName)) {
488
2356
  this.log.debug(`Dependency '${packageName}' is already installed`);
489
2357
  return;
490
2358
  }
2359
+ if (checkWorkspace) {
2360
+ const workspace = await this.getWorkspaceContext(root);
2361
+ if (workspace.workspaceRoot) {
2362
+ if (await this.hasDependency(workspace.workspaceRoot, packageName)) {
2363
+ this.log.debug(`Dependency '${packageName}' is already installed in workspace root`);
2364
+ return;
2365
+ }
2366
+ }
2367
+ }
491
2368
  const cmd = await this.getInstallCommand(root, packageName, dev);
492
2369
  if (options.run) await options.run(cmd, {
493
2370
  alias: `add ${packageName}`,
@@ -578,8 +2455,13 @@ var PackageManagerUtils = class {
578
2455
  return packageJson;
579
2456
  }
580
2457
  generatePackageJsonContent(modes) {
2458
+ const alephaDeps = devDependencies;
581
2459
  const dependencies = { alepha: `^${version}` };
582
- const devDependencies = {};
2460
+ const devDependencies$1 = {};
2461
+ if (!modes.isPackage) {
2462
+ devDependencies$1["@biomejs/biome"] = alephaDeps["@biomejs/biome"];
2463
+ if (modes.test) devDependencies$1.vitest = alephaDeps.vitest;
2464
+ }
583
2465
  const scripts = {
584
2466
  dev: "alepha dev",
585
2467
  build: "alepha build",
@@ -587,20 +2469,20 @@ var PackageManagerUtils = class {
587
2469
  typecheck: "alepha typecheck",
588
2470
  verify: "alepha verify"
589
2471
  };
2472
+ if (modes.test) scripts.test = "vitest run";
590
2473
  if (modes.ui) {
591
2474
  dependencies["@alepha/ui"] = `^${version}`;
592
2475
  modes.react = true;
593
2476
  }
594
2477
  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";
2478
+ dependencies.react = alephaDeps.react;
2479
+ dependencies["react-dom"] = alephaDeps["react-dom"];
2480
+ devDependencies$1["@types/react"] = alephaDeps["@types/react"];
599
2481
  }
600
2482
  return {
601
2483
  type: "module",
602
2484
  dependencies,
603
- devDependencies,
2485
+ devDependencies: devDependencies$1,
604
2486
  scripts
605
2487
  };
606
2488
  }
@@ -613,87 +2495,20 @@ var PackageManagerUtils = class {
613
2495
  };
614
2496
 
615
2497
  //#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();
649
- };
2498
+ //#region ../../src/cli/templates/agentMd.ts
2499
+ const agentMd = (type, options = {}) => {
2500
+ const { react = false, projectName = "my-app" } = options;
2501
+ const header = type === "claude" ? `# CLAUDE.md
650
2502
 
651
- //#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();
2503
+ This file provides guidance to Claude Code when working with this Alepha project.` : `# AGENTS.md
686
2504
 
687
- //#endregion
688
- //#region ../../src/cli/assets/claudeMd.ts
689
- const claudeMd = (options = {}) => {
690
- const { react = false, projectName = "my-app" } = options;
2505
+ This file provides guidance to AI coding assistants when working with this Alepha project.`;
691
2506
  const reactSection = react ? `
692
2507
  ## React & Frontend
693
2508
 
694
2509
  ### Pages with \`$page\`
695
2510
  \`\`\`tsx
696
- import { $page } from "@alepha/react/router";
2511
+ import { $page } from "alepha/react/router";
697
2512
  import { $client } from "alepha/server/links";
698
2513
  import type { UserController } from "./UserController.ts";
699
2514
 
@@ -719,9 +2534,9 @@ class AppRouter {
719
2534
 
720
2535
  ### React Hooks
721
2536
  \`\`\`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";
2537
+ import { useAlepha, useClient, useStore, useAction, useInject } from "alepha/react";
2538
+ import { useRouter, useActive } from "alepha/react/router";
2539
+ import { useForm } from "alepha/react/form";
725
2540
  \`\`\`
726
2541
 
727
2542
  - \`useClient<Controller>()\` - Type-safe API calls
@@ -766,9 +2581,7 @@ ${projectName}/
766
2581
  └── tsconfig.json
767
2582
  \`\`\`
768
2583
  `;
769
- return `# CLAUDE.md
770
-
771
- This file provides guidance to Claude Code when working with this Alepha project.
2584
+ return `${header}
772
2585
 
773
2586
  ## Overview
774
2587
 
@@ -782,10 +2595,10 @@ This is an **Alepha** project - a convention-driven TypeScript framework for typ
782
2595
 
783
2596
  ## Rules
784
2597
 
785
- - Use TypeScript strict mode
2598
+ - Use TypeScript strict mode, always check types (\`alepha typecheck\`)
786
2599
  - Use Biome for formatting (\`alepha lint\`)
787
- - Use Vitest for testing
788
- - One file = one class
2600
+ - Use Vitest for testing (\`alepha test\`)
2601
+ - One file = one class, multiple interfaces/types allowed
789
2602
  - Primitives are class properties (except \`$entity\`, \`$atom\`)
790
2603
  - No decorators, no Express/Fastify patterns
791
2604
  - No manual instantiation - use dependency injection
@@ -793,6 +2606,7 @@ This is an **Alepha** project - a convention-driven TypeScript framework for typ
793
2606
  - Import with file extensions: \`import { User } from "./User.ts"\`
794
2607
  - Use \`t\` from Alepha for schemas (not Zod)
795
2608
  - Prefer \`t.text()\` over \`t.string()\` for user input (has default max length, auto-trim, supports lowercase option)
2609
+ - One file = one schema (schemas/createUserSchema.ts)
796
2610
 
797
2611
  ## Project Structure
798
2612
  ${projectStructure}
@@ -835,17 +2649,12 @@ export const userEntity = $entity({
835
2649
  id: db.primaryKey(),
836
2650
  email: t.email(),
837
2651
  createdAt: db.createdAt(),
838
- updatedAt: db.updatedAt(),
839
2652
  }),
840
2653
  indexes: [{ column: "email", unique: true }],
841
2654
  });
842
2655
 
843
2656
  class UserService {
844
- repo = $repository(userEntity);
845
-
846
- async findById(id: string) {
847
- return this.repo.findById(id);
848
- }
2657
+ userRepository = $repository(userEntity);
849
2658
  }
850
2659
  \`\`\`
851
2660
 
@@ -855,11 +2664,6 @@ import { $inject } from "alepha";
855
2664
 
856
2665
  class OrderService {
857
2666
  userService = $inject(UserService); // Within same module
858
-
859
- async createOrder(userId: string) {
860
- const user = await this.userService.findById(userId);
861
- // ...
862
- }
863
2667
  }
864
2668
 
865
2669
  // Cross-module: use $client instead of $inject
@@ -923,7 +2727,7 @@ ${reactSection}
923
2727
  | \`$bucket\` | \`alepha/bucket\` | File storage |
924
2728
  | \`$issuer\` | \`alepha/security\` | JWT tokens |
925
2729
  | \`$command\` | \`alepha/command\` | CLI commands |${react ? `
926
- | \`$page\` | \`@alepha/react/router\` | React pages with SSR |
2730
+ | \`$page\` | \`alepha/react/router\` | React pages with SSR |
927
2731
  | \`$atom\` | \`alepha\` | Global state |` : ""}
928
2732
 
929
2733
  ## Testing
@@ -975,11 +2779,88 @@ alepha build # Build the project
975
2779
 
976
2780
  - Full docs: https://alepha.dev/llms.txt
977
2781
  - Detailed docs: https://alepha.dev/llms-full.txt
2782
+
2783
+ ## Source Code Access
2784
+
2785
+ Full framework source available at \`node_modules/alepha/src/\`.
2786
+ Read primitives directly when you need implementation details.
2787
+ `.trim();
2788
+ };
2789
+
2790
+ //#endregion
2791
+ //#region ../../src/cli/templates/apiHelloControllerTs.ts
2792
+ const apiHelloControllerTs = () => `
2793
+ import { t } from "alepha";
2794
+ import { $action } from "alepha/server";
2795
+
2796
+ export class HelloController {
2797
+ hello = $action({
2798
+ path: "/hello",
2799
+ schema: {
2800
+ response: t.object({
2801
+ message: t.string(),
2802
+ }),
2803
+ },
2804
+ handler: () => ({
2805
+ message: "Hello, Alepha!",
2806
+ }),
2807
+ });
2808
+ }
2809
+ `.trim();
2810
+
2811
+ //#endregion
2812
+ //#region ../../src/cli/templates/apiIndexTs.ts
2813
+ const apiIndexTs = (options = {}) => {
2814
+ const { appName = "app" } = options;
2815
+ return `
2816
+ import { $module } from "alepha";
2817
+ import { HelloController } from "./controllers/HelloController.ts";
2818
+
2819
+ export const ApiModule = $module({
2820
+ name: "${appName}.api",
2821
+ services: [HelloController],
2822
+ });
978
2823
  `.trim();
979
2824
  };
980
2825
 
981
2826
  //#endregion
982
- //#region ../../src/cli/assets/dummySpecTs.ts
2827
+ //#region ../../src/cli/templates/biomeJson.ts
2828
+ const biomeJson = () => `
2829
+ {
2830
+ "$schema": "https://biomejs.dev/schemas/latest/schema.json",
2831
+ "vcs": {
2832
+ "enabled": true,
2833
+ "clientKind": "git"
2834
+ },
2835
+ "files": {
2836
+ "ignoreUnknown": true,
2837
+ "includes": ["**", "!node_modules", "!dist"]
2838
+ },
2839
+ "formatter": {
2840
+ "enabled": true,
2841
+ "useEditorconfig": true
2842
+ },
2843
+ "linter": {
2844
+ "enabled": true,
2845
+ "rules": {
2846
+ "recommended": true
2847
+ },
2848
+ "domains": {
2849
+ "react": "recommended"
2850
+ }
2851
+ },
2852
+ "assist": {
2853
+ "actions": {
2854
+ "source": {
2855
+ "organizeImports": "on"
2856
+ }
2857
+ }
2858
+ }
2859
+ }
2860
+ `.trim();
2861
+
2862
+ //#endregion
2863
+ //#region ../../src/cli/templates/dummySpecTs.ts
983
2864
  const dummySpecTs = () => `
984
2865
  import { test, expect } from "vitest";
985
2866
 
@@ -989,7 +2870,7 @@ test("dummy test", () => {
989
2870
  `.trim();
990
2871
 
991
2872
  //#endregion
992
- //#region ../../src/cli/assets/editorconfig.ts
2873
+ //#region ../../src/cli/templates/editorconfig.ts
993
2874
  const editorconfig = () => `
994
2875
  # https://editorconfig.org
995
2876
 
@@ -1005,7 +2886,48 @@ indent_size = 2
1005
2886
  `.trim();
1006
2887
 
1007
2888
  //#endregion
1008
- //#region ../../src/cli/assets/mainBrowserTs.ts
2889
+ //#region ../../src/cli/templates/gitignore.ts
2890
+ const gitignore = () => `
2891
+ # Dependencies
2892
+ node_modules/
2893
+
2894
+ # Build outputs
2895
+ dist/
2896
+ .vite/
2897
+
2898
+ # Environment files
2899
+ .env
2900
+ .env.*
2901
+ !.env.example
2902
+
2903
+ # IDE
2904
+ .idea/
2905
+ *.swp
2906
+ *.swo
2907
+
2908
+ # OS
2909
+ .DS_Store
2910
+ Thumbs.db
2911
+
2912
+ # Logs
2913
+ *.log
2914
+ logs/
2915
+
2916
+ # Test coverage
2917
+ coverage/
2918
+
2919
+ # Yarn
2920
+ .yarn/*
2921
+ !.yarn/patches
2922
+ !.yarn/plugins
2923
+ !.yarn/releases
2924
+ !.yarn/sdks
2925
+ !.yarn/versions
2926
+ .pnp.*
2927
+ `.trim();
2928
+
2929
+ //#endregion
2930
+ //#region ../../src/cli/templates/mainBrowserTs.ts
1009
2931
  const mainBrowserTs = () => `
1010
2932
  import { Alepha, run } from "alepha";
1011
2933
  import { WebModule } from "./web/index.ts";
@@ -1018,8 +2940,10 @@ run(alepha);
1018
2940
  `.trim();
1019
2941
 
1020
2942
  //#endregion
1021
- //#region ../../src/cli/assets/mainCss.ts
1022
- const mainCss = () => `
2943
+ //#region ../../src/cli/templates/mainCss.ts
2944
+ const mainCss = (options = {}) => {
2945
+ if (options.ui) return `@import "@alepha/ui/styles";`;
2946
+ return `
1023
2947
  * {
1024
2948
  box-sizing: border-box;
1025
2949
  margin: 0;
@@ -1042,25 +2966,34 @@ body {
1042
2966
  height: 100%;
1043
2967
  }
1044
2968
  `.trim();
2969
+ };
1045
2970
 
1046
2971
  //#endregion
1047
- //#region ../../src/cli/assets/mainServerTs.ts
2972
+ //#region ../../src/cli/templates/mainServerTs.ts
1048
2973
  const mainServerTs = (options = {}) => {
1049
- const { react = false } = options;
2974
+ const { api = false, react = false } = options;
2975
+ const imports = [];
2976
+ const withs = [];
2977
+ if (api) {
2978
+ imports.push(`import { ApiModule } from "./api/index.ts";`);
2979
+ withs.push(`alepha.with(ApiModule);`);
2980
+ }
2981
+ if (react) {
2982
+ imports.push(`import { WebModule } from "./web/index.ts";`);
2983
+ withs.push(`alepha.with(WebModule);`);
2984
+ }
1050
2985
  return `
1051
2986
  import { Alepha, run } from "alepha";
1052
- import { ApiModule } from "./api/index.ts";
1053
- ${react ? `import { WebModule } from "./web/index.ts";\n` : ""}
2987
+ ${imports.length > 0 ? `${imports.join("\n")}\n` : ""}
1054
2988
  const alepha = Alepha.create();
2989
+ ${withs.length > 0 ? `\n${withs.join("\n")}` : ""}
1055
2990
 
1056
- alepha.with(ApiModule);
1057
- ${react ? `alepha.with(WebModule);\n` : ""}
1058
2991
  run(alepha);
1059
2992
  `.trim();
1060
2993
  };
1061
2994
 
1062
2995
  //#endregion
1063
- //#region ../../src/cli/assets/tsconfigJson.ts
2996
+ //#region ../../src/cli/templates/tsconfigJson.ts
1064
2997
  const tsconfigJson = () => `
1065
2998
  {
1066
2999
  "extends": "alepha/tsconfig.base"
@@ -1068,33 +3001,50 @@ const tsconfigJson = () => `
1068
3001
  `.trim();
1069
3002
 
1070
3003
  //#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({
3004
+ //#region ../../src/cli/templates/webAppRouterTs.ts
3005
+ const webAppRouterTs = (options) => {
3006
+ const imports = [];
3007
+ const classMembers = [];
3008
+ if (options.ui) imports.push("import { $ui } from \"@alepha/ui\";");
3009
+ imports.push("import { $page } from \"alepha/react/router\";");
3010
+ if (options.api) {
3011
+ imports.push("import { $client } from \"alepha/server/links\";");
3012
+ imports.push("import type { HelloController } from \"../api/controllers/HelloController.ts\";");
3013
+ classMembers.push(" api = $client<HelloController>();");
3014
+ }
3015
+ if (options.ui) {
3016
+ classMembers.push(" ui = $ui();");
3017
+ classMembers.push(` layout = $page({
3018
+ parent: this.ui.root,
3019
+ children: () => [this.home],
3020
+ });`);
3021
+ }
3022
+ if (options.api) classMembers.push(` home = $page({
1081
3023
  path: "/",
1082
3024
  lazy: () => import("./components/Hello.tsx"),
1083
3025
  loader: () => this.api.hello(),
1084
- });
1085
- }
1086
- `.trim();
3026
+ });`);
3027
+ else classMembers.push(` home = $page({
3028
+ path: "/",
3029
+ lazy: () => import("./components/Hello.tsx"),
3030
+ });`);
3031
+ return `${imports.join("\n")}
3032
+
3033
+ export class AppRouter {
3034
+ ${classMembers.join("\n\n")}
3035
+ }`;
3036
+ };
1087
3037
 
1088
3038
  //#endregion
1089
- //#region ../../src/cli/assets/webHelloComponentTsx.ts
3039
+ //#region ../../src/cli/templates/webHelloComponentTsx.ts
1090
3040
  const webHelloComponentTsx = () => `import { useState } from "react";
1091
3041
 
1092
3042
  interface Props {
1093
- message: string;
3043
+ message?: string;
1094
3044
  }
1095
3045
 
1096
3046
  const Hello = (props: Props) => {
1097
- const [message, setMessage] = useState(props.message);
3047
+ const [message, setMessage] = useState(props.message ?? "");
1098
3048
  return (
1099
3049
  <div>
1100
3050
  <h1>{message}</h1>
@@ -1108,7 +3058,7 @@ export default Hello;
1108
3058
  `.trim();
1109
3059
 
1110
3060
  //#endregion
1111
- //#region ../../src/cli/assets/webIndexTs.ts
3061
+ //#region ../../src/cli/templates/webIndexTs.ts
1112
3062
  const webIndexTs = (options = {}) => {
1113
3063
  const { appName = "app" } = options;
1114
3064
  return `
@@ -1137,6 +3087,7 @@ var ProjectScaffolder = class {
1137
3087
  log = $logger();
1138
3088
  fs = $inject(FileSystemProvider);
1139
3089
  pm = $inject(PackageManagerUtils);
3090
+ utils = $inject(AlephaCliUtils);
1140
3091
  /**
1141
3092
  * Get the app name from the directory name.
1142
3093
  *
@@ -1154,13 +3105,19 @@ var ProjectScaffolder = class {
1154
3105
  async ensureConfig(root, opts) {
1155
3106
  const tasks = [];
1156
3107
  const force = opts.force ?? false;
3108
+ const checkWorkspace = opts.checkWorkspace ?? false;
1157
3109
  if (opts.packageJson) tasks.push(this.pm.ensurePackageJson(root, typeof opts.packageJson === "boolean" ? {} : opts.packageJson).then(() => {}));
1158
3110
  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,
3111
+ if (opts.biomeJson) tasks.push(this.ensureBiomeConfig(root, {
3112
+ force,
3113
+ checkWorkspace
3114
+ }));
3115
+ if (opts.editorconfig) tasks.push(this.ensureEditorConfig(root, {
3116
+ force,
3117
+ checkWorkspace
3118
+ }));
3119
+ if (opts.agentMd) tasks.push(this.ensureAgentMd(root, {
3120
+ ...opts.agentMd,
1164
3121
  force
1165
3122
  }));
1166
3123
  await Promise.all(tasks);
@@ -1170,51 +3127,74 @@ var ProjectScaffolder = class {
1170
3127
  await this.fs.writeFile(this.fs.join(root, "tsconfig.json"), tsconfigJson());
1171
3128
  }
1172
3129
  async ensureBiomeConfig(root, opts = {}) {
3130
+ if (!opts.force && opts.checkWorkspace && await this.existsInParents(root, "biome.json")) return;
1173
3131
  await this.ensureFile(root, "biome.json", biomeJson(), opts.force);
1174
3132
  }
1175
3133
  async ensureEditorConfig(root, opts = {}) {
3134
+ if (!opts.force && opts.checkWorkspace && await this.existsInParents(root, ".editorconfig")) return;
1176
3135
  await this.ensureFile(root, ".editorconfig", editorconfig(), opts.force);
1177
3136
  }
1178
- async ensureClaudeMd(root, options = {}) {
1179
- await this.ensureFile(root, "CLAUDE.md", claudeMd(options), options.force);
3137
+ /**
3138
+ * Ensure git repository is initialized with .gitignore.
3139
+ *
3140
+ * @returns true if git was initialized, false if already exists or git unavailable
3141
+ */
3142
+ async ensureGitRepo(root, opts = {}) {
3143
+ const gitDir = this.fs.join(root, ".git");
3144
+ if (!opts.force && await this.fs.exists(gitDir)) return false;
3145
+ if (!await this.utils.isInstalledAsync("git")) return false;
3146
+ await this.utils.exec("git init", {
3147
+ root,
3148
+ global: true
3149
+ });
3150
+ await this.ensureFile(root, ".gitignore", gitignore(), opts.force);
3151
+ return true;
3152
+ }
3153
+ async ensureAgentMd(root, options) {
3154
+ const filename = options.type === "claude" ? "CLAUDE.md" : "AGENTS.md";
3155
+ await this.ensureFile(root, filename, agentMd(options.type, options), options.force);
3156
+ }
3157
+ /**
3158
+ * Ensure src/main.server.ts exists with correct module imports.
3159
+ */
3160
+ async ensureMainServerTs(root, opts = {}) {
3161
+ const srcDir = this.fs.join(root, "src");
3162
+ await this.fs.mkdir(srcDir, { recursive: true });
3163
+ await this.ensureFile(srcDir, "main.server.ts", mainServerTs({
3164
+ api: opts.api,
3165
+ react: opts.react
3166
+ }), opts.force);
1180
3167
  }
1181
3168
  /**
1182
- * Ensure src/main.server.ts exists with full API structure.
3169
+ * Ensure API module structure exists.
1183
3170
  *
1184
3171
  * Creates:
1185
- * - src/main.server.ts (entry point)
1186
3172
  * - src/api/index.ts (API module)
1187
3173
  * - src/api/controllers/HelloController.ts (example controller)
1188
3174
  */
1189
3175
  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
3176
  const appName = this.getAppName(root);
1195
3177
  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);
3178
+ await this.ensureFile(root, "src/api/index.ts", apiIndexTs({ appName }), opts.force);
3179
+ await this.ensureFile(root, "src/api/controllers/HelloController.ts", apiHelloControllerTs(), opts.force);
1199
3180
  }
1200
3181
  /**
1201
- * Ensure full React project structure exists.
3182
+ * Ensure web/React project structure exists.
1202
3183
  *
1203
3184
  * Creates:
1204
- * - src/main.server.ts, src/main.browser.ts
1205
- * - src/api/index.ts, src/api/controllers/HelloController.ts
3185
+ * - src/main.browser.ts
3186
+ * - src/main.css
1206
3187
  * - src/web/index.ts, src/web/AppRouter.ts, src/web/components/Hello.tsx
1207
3188
  */
1208
- async ensureReactProject(root, opts = {}) {
3189
+ async ensureWebProject(root, opts = {}) {
1209
3190
  const appName = this.getAppName(root);
1210
- await this.fs.mkdir(this.fs.join(root, "src/api/controllers"), { recursive: true });
1211
3191
  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);
3192
+ await this.ensureFile(root, "src/main.css", mainCss({ ui: opts.ui }), opts.force);
1216
3193
  await this.ensureFile(root, "src/web/index.ts", webIndexTs({ appName }), opts.force);
1217
- await this.ensureFile(root, "src/web/AppRouter.ts", webAppRouterTs(), opts.force);
3194
+ await this.ensureFile(root, "src/web/AppRouter.ts", webAppRouterTs({
3195
+ api: opts.api,
3196
+ ui: opts.ui
3197
+ }), opts.force);
1218
3198
  await this.ensureFile(root, "src/web/components/Hello.tsx", webHelloComponentTsx(), opts.force);
1219
3199
  await this.ensureFile(root, "src/main.browser.ts", mainBrowserTs(), opts.force);
1220
3200
  }
@@ -1283,10 +3263,6 @@ var BuildCommand = class {
1283
3263
  this.log.trace("Entry file found", { entry });
1284
3264
  const distDir = "dist";
1285
3265
  const publicDir = "public";
1286
- await this.pm.ensureDependency(root, "vite", {
1287
- run,
1288
- exec: (cmd, opts) => this.utils.exec(cmd, opts)
1289
- });
1290
3266
  await run.rm("dist", { alias: "clean dist" });
1291
3267
  const options = this.options;
1292
3268
  await this.utils.loadEnv(root, [".env", ".env.production"]);
@@ -1414,6 +3390,7 @@ var DbCommand = class {
1414
3390
  log = $logger();
1415
3391
  fs = $inject(FileSystemProvider);
1416
3392
  utils = $inject(AlephaCliUtils);
3393
+ entryProvider = $inject(AppEntryProvider);
1417
3394
  /**
1418
3395
  * Check if database migrations are up to date.
1419
3396
  */
@@ -1428,7 +3405,11 @@ var DbCommand = class {
1428
3405
  handler: async ({ args, root }) => {
1429
3406
  const rootDir = root;
1430
3407
  this.log.debug(`Using project root: ${rootDir}`);
1431
- const { alepha } = await this.utils.loadAlephaFromServerEntryFile(rootDir, args);
3408
+ const entry = await this.entryProvider.getAppEntry(root);
3409
+ const alepha = await this.utils.loadAlephaFromServerEntryFile({
3410
+ mode: "development",
3411
+ entry
3412
+ });
1432
3413
  const repositoryProvider = alepha.inject("RepositoryProvider");
1433
3414
  const drizzleKitProvider = alepha.inject("DrizzleKitProvider");
1434
3415
  const accepted = /* @__PURE__ */ new Set([]);
@@ -1592,7 +3573,11 @@ var DbCommand = class {
1592
3573
  if (options.env) envFiles.push(`.env.${options.env}`);
1593
3574
  await this.utils.loadEnv(rootDir, envFiles);
1594
3575
  this.log.debug(`Using project root: ${rootDir}`);
1595
- const { alepha, entry } = await this.utils.loadAlephaFromServerEntryFile(rootDir, options.args);
3576
+ const entry = await this.entryProvider.getAppEntry(rootDir);
3577
+ const alepha = await this.utils.loadAlephaFromServerEntryFile({
3578
+ mode: "development",
3579
+ entry
3580
+ });
1596
3581
  const drizzleKitProvider = alepha.inject("DrizzleKitProvider");
1597
3582
  const repositoryProvider = alepha.inject("RepositoryProvider");
1598
3583
  const accepted = /* @__PURE__ */ new Set([]);
@@ -1616,11 +3601,11 @@ var DbCommand = class {
1616
3601
  providerUrl: provider.url,
1617
3602
  providerDriver: provider.driver,
1618
3603
  dialect,
1619
- entry,
3604
+ entry: this.fs.join(rootDir, entry.server),
1620
3605
  rootDir
1621
3606
  });
1622
3607
  const flags = options.commandFlags ? ` ${options.commandFlags}` : "";
1623
- await this.utils.exec(`drizzle-kit ${options.command} --config=${drizzleConfigJsPath}${flags}`, { env: { NODE_OPTIONS: "--import tsx" } });
3608
+ await this.utils.exec(`drizzle-kit ${options.command} --config=${drizzleConfigJsPath}${flags}`, { env: { NODE_OPTIONS: [process.env.NODE_OPTIONS, "--import tsx"].filter(Boolean).join(" ") } });
1624
3609
  }
1625
3610
  }
1626
3611
  /**
@@ -1778,7 +3763,7 @@ var DeployCommand = class {
1778
3763
  var ViteDevServerProvider = class {
1779
3764
  log = $logger();
1780
3765
  fs = $inject(FileSystemProvider);
1781
- templateProvider = $inject(ViteTemplateProvider);
3766
+ templateProvider = $inject(ViteUtils);
1782
3767
  server;
1783
3768
  options;
1784
3769
  alepha = null;
@@ -1883,11 +3868,11 @@ var ViteDevServerProvider = class {
1883
3868
  async setupEnvironment() {
1884
3869
  const { loadEnv } = await importVite();
1885
3870
  const env = loadEnv(process.env.NODE_ENV || "development", this.options.root, "");
3871
+ for (const [key, value] of Object.entries(env)) process.env[key] ??= value;
1886
3872
  process.env.NODE_ENV ??= "development";
1887
3873
  process.env.VITE_ALEPHA_DEV = "true";
1888
3874
  process.env.SERVER_HOST ??= this.options.host?.toString() ?? "localhost";
1889
3875
  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
3876
  }
1892
3877
  /**
1893
3878
  * Load or reload the Alepha instance.
@@ -1952,6 +3937,7 @@ var ViteDevServerProvider = class {
1952
3937
  * Run Vite middleware and detect if it handled the request.
1953
3938
  */
1954
3939
  async runViteMiddleware(req, res, ctx) {
3940
+ if (res.headersSent || res.writableEnded) return false;
1955
3941
  return new Promise((resolve) => {
1956
3942
  let resolved = false;
1957
3943
  const done = (handled) => {
@@ -1960,6 +3946,18 @@ var ViteDevServerProvider = class {
1960
3946
  if (handled) ctx.metadata.vite = true;
1961
3947
  resolve(handled);
1962
3948
  };
3949
+ const originalSetHeader = res.setHeader.bind(res);
3950
+ const originalWriteHead = res.writeHead?.bind(res);
3951
+ const originalWrite = res.write.bind(res);
3952
+ const originalEnd = res.end.bind(res);
3953
+ const guardedCall = (fn, ...args) => {
3954
+ if (resolved && !ctx.metadata.vite) return;
3955
+ return fn(...args);
3956
+ };
3957
+ res.setHeader = (...args) => guardedCall(originalSetHeader, ...args);
3958
+ if (originalWriteHead) res.writeHead = (...args) => guardedCall(originalWriteHead, ...args);
3959
+ res.write = (...args) => guardedCall(originalWrite, ...args);
3960
+ res.end = (...args) => guardedCall(originalEnd, ...args);
1963
3961
  res.on("finish", () => done(true));
1964
3962
  res.on("close", () => res.headersSent && done(true));
1965
3963
  this.server.middlewares(req, res, () => done(false));
@@ -1999,23 +3997,14 @@ var DevCommand = class {
1999
3997
  boot = $inject(AppEntryProvider);
2000
3998
  /**
2001
3999
  * 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
4000
  */
2006
4001
  dev = $command({
2007
4002
  name: "dev",
2008
4003
  description: "Run the project in development mode",
2009
4004
  handler: async ({ root }) => {
2010
- const [expo, react] = await Promise.all([this.pm.hasExpo(root), this.pm.hasReact(root)]);
2011
4005
  await this.scaffolder.ensureConfig(root, { tsconfigJson: true });
2012
- if (expo) {
2013
- await this.utils.exec("expo start");
2014
- return;
2015
- }
2016
4006
  const entry = await this.boot.getAppEntry(root);
2017
4007
  this.log.debug("Entry file found", { entry });
2018
- await this.pm.ensureDependency(root, "vite", { exec: (cmd, opts) => this.utils.exec(cmd, opts) });
2019
4008
  await this.viteDevServer.init({
2020
4009
  root,
2021
4010
  entry
@@ -2255,7 +4244,10 @@ var GenEnvCommand = class {
2255
4244
  description: "Output file path (e.g., .env)"
2256
4245
  })) }),
2257
4246
  handler: async ({ root, flags }) => {
2258
- const { alepha } = await this.utils.loadAlephaFromServerEntryFile(root);
4247
+ const alepha = await this.utils.loadAlephaFromServerEntryFile({
4248
+ root,
4249
+ mode: "development"
4250
+ });
2259
4251
  try {
2260
4252
  const { env } = alepha.dump();
2261
4253
  let dotEnvFile = "";
@@ -2288,7 +4280,10 @@ var OpenApiCommand = class {
2288
4280
  description: "Output file path"
2289
4281
  })) }),
2290
4282
  handler: async ({ root, flags }) => {
2291
- const { alepha } = await this.utils.loadAlephaFromServerEntryFile(root);
4283
+ const alepha = await this.utils.loadAlephaFromServerEntryFile({
4284
+ root,
4285
+ mode: "development"
4286
+ });
2292
4287
  try {
2293
4288
  const openapiProvider = alepha.inject(ServerSwaggerProvider);
2294
4289
  await alepha.events.emit("configure", alepha);
@@ -2357,17 +4352,20 @@ var InitCommand = class {
2357
4352
  flags: t.object({
2358
4353
  agent: t.optional(t.boolean({
2359
4354
  aliases: ["a"],
2360
- description: "Add CLAUDE.md for Claude Code AI assistant"
4355
+ description: "Add AI agent instructions (CLAUDE.md if claude CLI installed, else AGENTS.md)"
2361
4356
  })),
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" })),
4357
+ pm: t.optional(t.enum([
4358
+ "yarn",
4359
+ "npm",
4360
+ "pnpm",
4361
+ "bun"
4362
+ ], { description: "Package manager to use" })),
4363
+ api: t.optional(t.boolean({ description: "Include API module structure (src/api/)" })),
2366
4364
  react: t.optional(t.boolean({
2367
4365
  aliases: ["r"],
2368
- description: "Include Alepha React dependencies"
4366
+ description: "Include React dependencies and web module (src/web/)"
2369
4367
  })),
2370
- ui: t.optional(t.boolean({ description: "Include Alepha UI dependencies" })),
4368
+ ui: t.optional(t.boolean({ description: "Include @alepha/ui (components, auth portal, admin portal)" })),
2371
4369
  test: t.optional(t.boolean({ description: "Include Vitest and create test directory" })),
2372
4370
  force: t.optional(t.boolean({
2373
4371
  aliases: ["f"],
@@ -2375,11 +4373,14 @@ var InitCommand = class {
2375
4373
  }))
2376
4374
  }),
2377
4375
  handler: async ({ run, flags, root, args }) => {
2378
- if (flags.react) flags.ui = true;
2379
4376
  if (args) {
2380
4377
  root = this.fs.join(root, args);
2381
4378
  await this.fs.mkdir(root);
2382
4379
  }
4380
+ if (flags.ui) flags.react = true;
4381
+ const workspace = await this.pm.getWorkspaceContext(root);
4382
+ let agentType = false;
4383
+ if (flags.agent) agentType = await this.utils.isInstalledAsync("claude") ? "claude" : "agents";
2383
4384
  const isExpo = await this.pm.hasExpo(root);
2384
4385
  const force = !!flags.force;
2385
4386
  await run({
@@ -2387,46 +4388,55 @@ var InitCommand = class {
2387
4388
  handler: async () => {
2388
4389
  await this.scaffolder.ensureConfig(root, {
2389
4390
  force,
2390
- tsconfigJson: true,
2391
- packageJson: flags,
2392
- biomeJson: true,
2393
- editorconfig: true,
2394
- indexHtml: !!flags.react && !isExpo,
2395
- claudeMd: flags.agent ? {
4391
+ tsconfigJson: !workspace.config.tsconfigJson,
4392
+ packageJson: {
4393
+ ...flags,
4394
+ isPackage: workspace.isPackage
4395
+ },
4396
+ biomeJson: !workspace.config.biomeJson,
4397
+ editorconfig: !workspace.config.editorconfig,
4398
+ agentMd: agentType ? {
4399
+ type: agentType,
2396
4400
  react: !!flags.react,
2397
4401
  ui: !!flags.ui
2398
4402
  } : false
2399
4403
  });
2400
- if (!flags.react) await this.scaffolder.ensureApiProject(root, { force });
4404
+ await this.scaffolder.ensureMainServerTs(root, {
4405
+ api: !!flags.api,
4406
+ react: !!flags.react && !isExpo,
4407
+ force
4408
+ });
4409
+ if (flags.api) await this.scaffolder.ensureApiProject(root, { force });
4410
+ if (flags.react && !isExpo) await this.scaffolder.ensureWebProject(root, {
4411
+ api: !!flags.api,
4412
+ ui: !!flags.ui,
4413
+ force
4414
+ });
2401
4415
  }
2402
4416
  });
2403
- const pmName = await this.pm.getPackageManager(root, flags);
2404
- if (pmName === "yarn") {
4417
+ const pmName = await this.pm.getPackageManager(workspace.workspaceRoot ?? root, flags.pm ?? workspace.packageManager ?? void 0);
4418
+ if (!workspace.isPackage) if (pmName === "yarn") {
2405
4419
  await this.pm.ensureYarn(root);
2406
4420
  await run("yarn set version stable", { root });
2407
4421
  } else if (pmName === "bun") await this.pm.ensureBun(root);
2408
4422
  else if (pmName === "pnpm") await this.pm.ensurePnpm(root);
2409
4423
  else await this.pm.ensureNpm(root);
4424
+ const installRoot = workspace.workspaceRoot ?? root;
2410
4425
  await run(`${pmName} install`, {
2411
4426
  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)
4427
+ root: installRoot
2421
4428
  });
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
- }
4429
+ if (flags.test) await this.scaffolder.ensureTestDir(root);
2426
4430
  await run(`${pmName} run lint`, {
2427
4431
  alias: "running linter",
2428
- root
4432
+ root: installRoot
2429
4433
  });
4434
+ if (!workspace.isPackage) {
4435
+ if (await this.scaffolder.ensureGitRepo(root, { force })) await run("git add .", {
4436
+ alias: "staging generated files",
4437
+ root
4438
+ });
4439
+ }
2430
4440
  }
2431
4441
  });
2432
4442
  };
@@ -2441,8 +4451,14 @@ var LintCommand = class {
2441
4451
  name: "lint",
2442
4452
  description: "Run linter across the codebase using Biome",
2443
4453
  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) });
4454
+ await this.scaffolder.ensureConfig(root, {
4455
+ biomeJson: true,
4456
+ checkWorkspace: true
4457
+ });
4458
+ await this.pm.ensureDependency(root, "@biomejs/biome", {
4459
+ checkWorkspace: true,
4460
+ exec: (cmd, opts) => this.utils.exec(cmd, opts)
4461
+ });
2446
4462
  await this.utils.exec("biome check --fix");
2447
4463
  }
2448
4464
  });
@@ -2508,6 +4524,7 @@ var TypecheckCommand = class {
2508
4524
  utils = $inject(AlephaCliUtils);
2509
4525
  pm = $inject(PackageManagerUtils);
2510
4526
  log = $logger();
4527
+ scaffolder = $inject(ProjectScaffolder);
2511
4528
  /**
2512
4529
  * Run TypeScript type checking across the codebase with no emit.
2513
4530
  */
@@ -2517,7 +4534,14 @@ var TypecheckCommand = class {
2517
4534
  description: "Check TypeScript types across the codebase",
2518
4535
  handler: async ({ root }) => {
2519
4536
  this.log.info("Starting TypeScript type checking...");
2520
- await this.pm.ensureDependency(root, "typescript", { exec: (cmd, opts) => this.utils.exec(cmd, opts) });
4537
+ await this.scaffolder.ensureConfig(root, {
4538
+ tsconfigJson: true,
4539
+ checkWorkspace: true
4540
+ });
4541
+ await this.pm.ensureDependency(root, "typescript", {
4542
+ checkWorkspace: true,
4543
+ exec: (cmd, opts) => this.utils.exec(cmd, opts)
4544
+ });
2521
4545
  await this.utils.exec("tsc --noEmit");
2522
4546
  this.log.info("TypeScript type checking completed successfully.");
2523
4547
  }
@@ -2561,15 +4585,6 @@ var VerifyCommand = class {
2561
4585
  //#endregion
2562
4586
  //#region ../../src/cli/apps/AlephaCli.ts
2563
4587
  /**
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
4588
  * Allow to extend Alepha CLI via `alepha.config.ts` file located in the project root.
2574
4589
  */
2575
4590
  var AlephaCliExtension = class {