alepha 0.15.1 → 0.15.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (523) hide show
  1. package/README.md +68 -80
  2. package/dist/api/audits/index.d.ts +10 -33
  3. package/dist/api/audits/index.d.ts.map +1 -1
  4. package/dist/api/audits/index.js +10 -33
  5. package/dist/api/audits/index.js.map +1 -1
  6. package/dist/api/files/index.d.ts +10 -3
  7. package/dist/api/files/index.d.ts.map +1 -1
  8. package/dist/api/files/index.js +10 -3
  9. package/dist/api/files/index.js.map +1 -1
  10. package/dist/api/jobs/index.d.ts +162 -155
  11. package/dist/api/jobs/index.d.ts.map +1 -1
  12. package/dist/api/jobs/index.js +10 -3
  13. package/dist/api/jobs/index.js.map +1 -1
  14. package/dist/api/keys/index.d.ts +413 -0
  15. package/dist/api/keys/index.d.ts.map +1 -0
  16. package/dist/api/keys/index.js +476 -0
  17. package/dist/api/keys/index.js.map +1 -0
  18. package/dist/api/notifications/index.d.ts +10 -4
  19. package/dist/api/notifications/index.d.ts.map +1 -1
  20. package/dist/api/notifications/index.js +10 -4
  21. package/dist/api/notifications/index.js.map +1 -1
  22. package/dist/api/parameters/index.d.ts +43 -50
  23. package/dist/api/parameters/index.d.ts.map +1 -1
  24. package/dist/api/parameters/index.js +30 -37
  25. package/dist/api/parameters/index.js.map +1 -1
  26. package/dist/api/users/index.d.ts +1081 -760
  27. package/dist/api/users/index.d.ts.map +1 -1
  28. package/dist/api/users/index.js +2539 -218
  29. package/dist/api/users/index.js.map +1 -1
  30. package/dist/api/verifications/index.d.ts +138 -132
  31. package/dist/api/verifications/index.d.ts.map +1 -1
  32. package/dist/api/verifications/index.js +12 -4
  33. package/dist/api/verifications/index.js.map +1 -1
  34. package/dist/batch/index.d.ts +20 -40
  35. package/dist/batch/index.d.ts.map +1 -1
  36. package/dist/batch/index.js +31 -44
  37. package/dist/batch/index.js.map +1 -1
  38. package/dist/bucket/index.d.ts +440 -8
  39. package/dist/bucket/index.d.ts.map +1 -1
  40. package/dist/bucket/index.js +1861 -12
  41. package/dist/bucket/index.js.map +1 -1
  42. package/dist/cache/core/index.d.ts +179 -7
  43. package/dist/cache/core/index.d.ts.map +1 -1
  44. package/dist/cache/core/index.js +213 -7
  45. package/dist/cache/core/index.js.map +1 -1
  46. package/dist/cache/redis/index.d.ts +1 -0
  47. package/dist/cache/redis/index.d.ts.map +1 -1
  48. package/dist/cache/redis/index.js +4 -0
  49. package/dist/cache/redis/index.js.map +1 -1
  50. package/dist/cli/index.d.ts +638 -5645
  51. package/dist/cli/index.d.ts.map +1 -1
  52. package/dist/cli/index.js +2550 -368
  53. package/dist/cli/index.js.map +1 -1
  54. package/dist/command/index.d.ts +203 -45
  55. package/dist/command/index.d.ts.map +1 -1
  56. package/dist/command/index.js +2060 -71
  57. package/dist/command/index.js.map +1 -1
  58. package/dist/core/index.browser.js +70 -40
  59. package/dist/core/index.browser.js.map +1 -1
  60. package/dist/core/index.d.ts +34 -13
  61. package/dist/core/index.d.ts.map +1 -1
  62. package/dist/core/index.js +90 -40
  63. package/dist/core/index.js.map +1 -1
  64. package/dist/core/index.native.js +70 -40
  65. package/dist/core/index.native.js.map +1 -1
  66. package/dist/datetime/index.d.ts +15 -0
  67. package/dist/datetime/index.d.ts.map +1 -1
  68. package/dist/datetime/index.js +15 -0
  69. package/dist/datetime/index.js.map +1 -1
  70. package/dist/email/index.d.ts +323 -20
  71. package/dist/email/index.d.ts.map +1 -1
  72. package/dist/email/index.js +1857 -7
  73. package/dist/email/index.js.map +1 -1
  74. package/dist/fake/index.d.ts +90 -8
  75. package/dist/fake/index.d.ts.map +1 -1
  76. package/dist/fake/index.js +91 -20
  77. package/dist/fake/index.js.map +1 -1
  78. package/dist/lock/core/index.d.ts +11 -4
  79. package/dist/lock/core/index.d.ts.map +1 -1
  80. package/dist/lock/core/index.js +11 -4
  81. package/dist/lock/core/index.js.map +1 -1
  82. package/dist/logger/index.d.ts +17 -66
  83. package/dist/logger/index.d.ts.map +1 -1
  84. package/dist/logger/index.js +14 -63
  85. package/dist/logger/index.js.map +1 -1
  86. package/dist/mcp/index.d.ts +10 -30
  87. package/dist/mcp/index.d.ts.map +1 -1
  88. package/dist/mcp/index.js +12 -35
  89. package/dist/mcp/index.js.map +1 -1
  90. package/dist/orm/index.browser.js +3 -3
  91. package/dist/orm/index.browser.js.map +1 -1
  92. package/dist/orm/index.bun.js +39 -20
  93. package/dist/orm/index.bun.js.map +1 -1
  94. package/dist/orm/index.d.ts +517 -540
  95. package/dist/orm/index.d.ts.map +1 -1
  96. package/dist/orm/index.js +58 -71
  97. package/dist/orm/index.js.map +1 -1
  98. package/dist/queue/core/index.d.ts +18 -10
  99. package/dist/queue/core/index.d.ts.map +1 -1
  100. package/dist/queue/core/index.js +14 -6
  101. package/dist/queue/core/index.js.map +1 -1
  102. package/dist/react/auth/index.browser.js +108 -0
  103. package/dist/react/auth/index.browser.js.map +1 -0
  104. package/dist/react/auth/index.d.ts +100 -0
  105. package/dist/react/auth/index.d.ts.map +1 -0
  106. package/dist/react/auth/index.js +145 -0
  107. package/dist/react/auth/index.js.map +1 -0
  108. package/dist/react/core/index.d.ts +469 -0
  109. package/dist/react/core/index.d.ts.map +1 -0
  110. package/dist/react/core/index.js +464 -0
  111. package/dist/react/core/index.js.map +1 -0
  112. package/dist/react/form/index.d.ts +232 -0
  113. package/dist/react/form/index.d.ts.map +1 -0
  114. package/dist/react/form/index.js +432 -0
  115. package/dist/react/form/index.js.map +1 -0
  116. package/dist/react/head/index.browser.js +423 -0
  117. package/dist/react/head/index.browser.js.map +1 -0
  118. package/dist/react/head/index.d.ts +288 -0
  119. package/dist/react/head/index.d.ts.map +1 -0
  120. package/dist/react/head/index.js +465 -0
  121. package/dist/react/head/index.js.map +1 -0
  122. package/dist/react/i18n/index.d.ts +175 -0
  123. package/dist/react/i18n/index.d.ts.map +1 -0
  124. package/dist/react/i18n/index.js +224 -0
  125. package/dist/react/i18n/index.js.map +1 -0
  126. package/dist/react/router/index.browser.js +1974 -0
  127. package/dist/react/router/index.browser.js.map +1 -0
  128. package/dist/react/router/index.d.ts +1956 -0
  129. package/dist/react/router/index.d.ts.map +1 -0
  130. package/dist/react/router/index.js +4722 -0
  131. package/dist/react/router/index.js.map +1 -0
  132. package/dist/react/websocket/index.d.ts +117 -0
  133. package/dist/react/websocket/index.d.ts.map +1 -0
  134. package/dist/react/websocket/index.js +107 -0
  135. package/dist/react/websocket/index.js.map +1 -0
  136. package/dist/redis/index.bun.js +4 -0
  137. package/dist/redis/index.bun.js.map +1 -1
  138. package/dist/redis/index.d.ts +41 -44
  139. package/dist/redis/index.d.ts.map +1 -1
  140. package/dist/redis/index.js +16 -25
  141. package/dist/redis/index.js.map +1 -1
  142. package/dist/retry/index.d.ts +11 -2
  143. package/dist/retry/index.d.ts.map +1 -1
  144. package/dist/retry/index.js +11 -2
  145. package/dist/retry/index.js.map +1 -1
  146. package/dist/scheduler/index.d.ts +11 -2
  147. package/dist/scheduler/index.d.ts.map +1 -1
  148. package/dist/scheduler/index.js +11 -2
  149. package/dist/scheduler/index.js.map +1 -1
  150. package/dist/security/index.d.ts +140 -49
  151. package/dist/security/index.d.ts.map +1 -1
  152. package/dist/security/index.js +164 -32
  153. package/dist/security/index.js.map +1 -1
  154. package/dist/server/auth/index.d.ts +12 -7
  155. package/dist/server/auth/index.d.ts.map +1 -1
  156. package/dist/server/auth/index.js +12 -7
  157. package/dist/server/auth/index.js.map +1 -1
  158. package/dist/server/cache/index.d.ts +7 -22
  159. package/dist/server/cache/index.d.ts.map +1 -1
  160. package/dist/server/cache/index.js +7 -22
  161. package/dist/server/cache/index.js.map +1 -1
  162. package/dist/server/compress/index.d.ts +10 -2
  163. package/dist/server/compress/index.d.ts.map +1 -1
  164. package/dist/server/compress/index.js +10 -2
  165. package/dist/server/compress/index.js.map +1 -1
  166. package/dist/server/cookies/index.d.ts +40 -16
  167. package/dist/server/cookies/index.d.ts.map +1 -1
  168. package/dist/server/cookies/index.js +7 -5
  169. package/dist/server/cookies/index.js.map +1 -1
  170. package/dist/server/core/index.d.ts +124 -23
  171. package/dist/server/core/index.d.ts.map +1 -1
  172. package/dist/server/core/index.js +231 -14
  173. package/dist/server/core/index.js.map +1 -1
  174. package/dist/server/cors/index.d.ts +13 -23
  175. package/dist/server/cors/index.d.ts.map +1 -1
  176. package/dist/server/cors/index.js +7 -21
  177. package/dist/server/cors/index.js.map +1 -1
  178. package/dist/server/health/index.d.ts +8 -2
  179. package/dist/server/health/index.d.ts.map +1 -1
  180. package/dist/server/health/index.js +8 -2
  181. package/dist/server/health/index.js.map +1 -1
  182. package/dist/server/helmet/index.d.ts +11 -3
  183. package/dist/server/helmet/index.d.ts.map +1 -1
  184. package/dist/server/helmet/index.js +11 -3
  185. package/dist/server/helmet/index.js.map +1 -1
  186. package/dist/server/links/index.d.ts +11 -6
  187. package/dist/server/links/index.d.ts.map +1 -1
  188. package/dist/server/links/index.js +11 -6
  189. package/dist/server/links/index.js.map +1 -1
  190. package/dist/server/metrics/index.d.ts +10 -3
  191. package/dist/server/metrics/index.d.ts.map +1 -1
  192. package/dist/server/metrics/index.js +10 -3
  193. package/dist/server/metrics/index.js.map +1 -1
  194. package/dist/server/multipart/index.d.ts +9 -3
  195. package/dist/server/multipart/index.d.ts.map +1 -1
  196. package/dist/server/multipart/index.js +9 -3
  197. package/dist/server/multipart/index.js.map +1 -1
  198. package/dist/server/proxy/index.d.ts +8 -2
  199. package/dist/server/proxy/index.d.ts.map +1 -1
  200. package/dist/server/proxy/index.js +8 -2
  201. package/dist/server/proxy/index.js.map +1 -1
  202. package/dist/server/rate-limit/index.d.ts +30 -35
  203. package/dist/server/rate-limit/index.d.ts.map +1 -1
  204. package/dist/server/rate-limit/index.js +18 -55
  205. package/dist/server/rate-limit/index.js.map +1 -1
  206. package/dist/server/static/index.d.ts +137 -4
  207. package/dist/server/static/index.d.ts.map +1 -1
  208. package/dist/server/static/index.js +1853 -5
  209. package/dist/server/static/index.js.map +1 -1
  210. package/dist/server/swagger/index.d.ts +309 -6
  211. package/dist/server/swagger/index.d.ts.map +1 -1
  212. package/dist/server/swagger/index.js +1854 -6
  213. package/dist/server/swagger/index.js.map +1 -1
  214. package/dist/sms/index.d.ts +309 -7
  215. package/dist/sms/index.d.ts.map +1 -1
  216. package/dist/sms/index.js +1856 -7
  217. package/dist/sms/index.js.map +1 -1
  218. package/dist/system/index.browser.js +1218 -0
  219. package/dist/system/index.browser.js.map +1 -0
  220. package/dist/{file → system}/index.d.ts +343 -16
  221. package/dist/system/index.d.ts.map +1 -0
  222. package/dist/{file → system}/index.js +419 -22
  223. package/dist/system/index.js.map +1 -0
  224. package/dist/thread/index.d.ts +11 -2
  225. package/dist/thread/index.d.ts.map +1 -1
  226. package/dist/thread/index.js +11 -2
  227. package/dist/thread/index.js.map +1 -1
  228. package/dist/topic/core/index.d.ts +12 -5
  229. package/dist/topic/core/index.d.ts.map +1 -1
  230. package/dist/topic/core/index.js +12 -5
  231. package/dist/topic/core/index.js.map +1 -1
  232. package/dist/vite/index.d.ts +5 -6272
  233. package/dist/vite/index.d.ts.map +1 -1
  234. package/dist/vite/index.js +23 -10
  235. package/dist/vite/index.js.map +1 -1
  236. package/dist/websocket/index.d.ts +12 -8
  237. package/dist/websocket/index.d.ts.map +1 -1
  238. package/dist/websocket/index.js +12 -8
  239. package/dist/websocket/index.js.map +1 -1
  240. package/package.json +82 -11
  241. package/src/api/audits/index.ts +10 -33
  242. package/src/api/files/__tests__/$bucket.spec.ts +1 -1
  243. package/src/api/files/controllers/AdminFileStatsController.spec.ts +1 -1
  244. package/src/api/files/controllers/FileController.spec.ts +1 -1
  245. package/src/api/files/index.ts +10 -3
  246. package/src/api/files/jobs/FileJobs.spec.ts +1 -1
  247. package/src/api/files/services/FileService.spec.ts +1 -1
  248. package/src/api/jobs/index.ts +10 -3
  249. package/src/api/keys/controllers/AdminApiKeyController.ts +75 -0
  250. package/src/api/keys/controllers/ApiKeyController.ts +103 -0
  251. package/src/api/keys/entities/apiKeyEntity.ts +41 -0
  252. package/src/api/keys/index.ts +49 -0
  253. package/src/api/keys/schemas/adminApiKeyQuerySchema.ts +7 -0
  254. package/src/api/keys/schemas/adminApiKeyResourceSchema.ts +17 -0
  255. package/src/api/keys/schemas/createApiKeyBodySchema.ts +7 -0
  256. package/src/api/keys/schemas/createApiKeyResponseSchema.ts +11 -0
  257. package/src/api/keys/schemas/listApiKeyResponseSchema.ts +15 -0
  258. package/src/api/keys/schemas/revokeApiKeyParamsSchema.ts +5 -0
  259. package/src/api/keys/schemas/revokeApiKeyResponseSchema.ts +5 -0
  260. package/src/api/keys/services/ApiKeyService.spec.ts +553 -0
  261. package/src/api/keys/services/ApiKeyService.ts +306 -0
  262. package/src/api/logs/TODO.md +55 -0
  263. package/src/api/notifications/index.ts +10 -4
  264. package/src/api/parameters/index.ts +9 -30
  265. package/src/api/parameters/primitives/$config.ts +12 -4
  266. package/src/api/parameters/services/ConfigStore.ts +9 -3
  267. package/src/api/users/__tests__/ApiKeys-integration.spec.ts +1035 -0
  268. package/src/api/users/__tests__/ApiKeys.spec.ts +401 -0
  269. package/src/api/users/index.ts +14 -3
  270. package/src/api/users/primitives/$realm.ts +33 -5
  271. package/src/api/users/providers/RealmProvider.ts +1 -12
  272. package/src/api/users/services/SessionService.ts +1 -1
  273. package/src/api/verifications/controllers/VerificationController.ts +2 -0
  274. package/src/api/verifications/index.ts +10 -4
  275. package/src/batch/index.ts +9 -36
  276. package/src/batch/primitives/$batch.ts +0 -8
  277. package/src/batch/providers/BatchProvider.ts +29 -2
  278. package/src/bucket/__tests__/shared.ts +1 -1
  279. package/src/bucket/index.ts +13 -6
  280. package/src/bucket/primitives/$bucket.ts +1 -1
  281. package/src/bucket/providers/LocalFileStorageProvider.ts +1 -1
  282. package/src/bucket/providers/MemoryFileStorageProvider.ts +1 -1
  283. package/src/cache/core/__tests__/shared.ts +30 -0
  284. package/src/cache/core/index.ts +11 -6
  285. package/src/cache/core/primitives/$cache.spec.ts +5 -0
  286. package/src/cache/core/providers/CacheProvider.ts +17 -0
  287. package/src/cache/core/providers/MemoryCacheProvider.ts +300 -1
  288. package/src/cache/redis/__tests__/cache-redis.spec.ts +5 -0
  289. package/src/cache/redis/providers/RedisCacheProvider.ts +9 -0
  290. package/src/cli/apps/AlephaCli.ts +1 -14
  291. package/src/cli/apps/AlephaPackageBuilderCli.ts +10 -1
  292. package/src/cli/atoms/buildOptions.ts +99 -9
  293. package/src/cli/commands/build.ts +150 -37
  294. package/src/cli/commands/db.ts +22 -18
  295. package/src/cli/commands/deploy.ts +1 -1
  296. package/src/cli/commands/dev.ts +1 -20
  297. package/src/cli/commands/gen/env.ts +5 -2
  298. package/src/cli/commands/gen/openapi.ts +5 -2
  299. package/src/cli/commands/init.spec.ts +588 -0
  300. package/src/cli/commands/init.ts +115 -58
  301. package/src/cli/commands/lint.ts +7 -1
  302. package/src/cli/commands/typecheck.ts +11 -0
  303. package/src/cli/providers/AppEntryProvider.ts +1 -1
  304. package/src/cli/providers/ViteBuildProvider.ts +8 -50
  305. package/src/cli/providers/ViteDevServerProvider.ts +35 -16
  306. package/src/cli/services/AlephaCliUtils.ts +52 -121
  307. package/src/cli/services/PackageManagerUtils.ts +129 -11
  308. package/src/cli/services/ProjectScaffolder.spec.ts +97 -0
  309. package/src/cli/services/ProjectScaffolder.ts +148 -81
  310. package/src/cli/services/ViteUtils.ts +82 -0
  311. package/src/cli/{assets/claudeMd.ts → templates/agentMd.ts} +37 -24
  312. package/src/cli/templates/apiAppSecurityTs.ts +11 -0
  313. package/src/cli/templates/apiIndexTs.ts +30 -0
  314. package/src/cli/templates/gitignore.ts +39 -0
  315. package/src/cli/{assets → templates}/mainCss.ts +11 -2
  316. package/src/cli/templates/mainServerTs.ts +33 -0
  317. package/src/cli/templates/webAppRouterTs.ts +74 -0
  318. package/src/cli/templates/webHelloComponentTsx.ts +30 -0
  319. package/src/command/helpers/Runner.spec.ts +139 -0
  320. package/src/command/helpers/Runner.ts +7 -22
  321. package/src/command/index.ts +12 -4
  322. package/src/command/providers/CliProvider.spec.ts +1392 -0
  323. package/src/command/providers/CliProvider.ts +320 -47
  324. package/src/core/Alepha.ts +34 -27
  325. package/src/core/__tests__/Alepha-start.spec.ts +4 -4
  326. package/src/core/helpers/jsonSchemaToTypeBox.spec.ts +771 -0
  327. package/src/core/helpers/jsonSchemaToTypeBox.ts +62 -10
  328. package/src/core/index.shared.ts +1 -0
  329. package/src/core/index.ts +20 -0
  330. package/src/core/providers/EventManager.spec.ts +0 -71
  331. package/src/core/providers/EventManager.ts +3 -15
  332. package/src/core/providers/Json.ts +2 -14
  333. package/src/datetime/index.ts +15 -0
  334. package/src/email/index.ts +10 -5
  335. package/src/email/providers/LocalEmailProvider.spec.ts +1 -1
  336. package/src/email/providers/LocalEmailProvider.ts +1 -1
  337. package/src/fake/__tests__/keyName.example.ts +1 -1
  338. package/src/fake/__tests__/keyName.spec.ts +5 -5
  339. package/src/fake/index.ts +9 -6
  340. package/src/fake/providers/FakeProvider.spec.ts +258 -40
  341. package/src/fake/providers/FakeProvider.ts +133 -19
  342. package/src/lock/core/index.ts +11 -4
  343. package/src/logger/index.ts +17 -66
  344. package/src/mcp/index.ts +10 -27
  345. package/src/mcp/transports/SseMcpTransport.ts +0 -11
  346. package/src/orm/__tests__/PostgresProvider.spec.ts +2 -2
  347. package/src/orm/index.browser.ts +2 -2
  348. package/src/orm/index.bun.ts +5 -3
  349. package/src/orm/index.ts +23 -53
  350. package/src/orm/providers/drivers/BunSqliteProvider.ts +5 -1
  351. package/src/orm/providers/drivers/CloudflareD1Provider.ts +57 -30
  352. package/src/orm/providers/drivers/DatabaseProvider.ts +9 -1
  353. package/src/orm/providers/drivers/NodeSqliteProvider.ts +4 -1
  354. package/src/orm/services/Repository.ts +7 -3
  355. package/src/queue/core/index.ts +14 -6
  356. package/src/react/auth/__tests__/$auth.spec.ts +202 -0
  357. package/src/react/auth/hooks/useAuth.ts +32 -0
  358. package/src/react/auth/index.browser.ts +13 -0
  359. package/src/react/auth/index.shared.ts +2 -0
  360. package/src/react/auth/index.ts +48 -0
  361. package/src/react/auth/providers/ReactAuthProvider.ts +16 -0
  362. package/src/react/auth/services/ReactAuth.ts +135 -0
  363. package/src/react/core/__tests__/Router.spec.tsx +169 -0
  364. package/src/react/core/components/ClientOnly.tsx +49 -0
  365. package/src/react/core/components/ErrorBoundary.tsx +73 -0
  366. package/src/react/core/contexts/AlephaContext.ts +7 -0
  367. package/src/react/core/contexts/AlephaProvider.tsx +42 -0
  368. package/src/react/core/hooks/useAction.browser.spec.tsx +569 -0
  369. package/src/react/core/hooks/useAction.ts +480 -0
  370. package/src/react/core/hooks/useAlepha.ts +26 -0
  371. package/src/react/core/hooks/useClient.ts +17 -0
  372. package/src/react/core/hooks/useEvents.ts +51 -0
  373. package/src/react/core/hooks/useInject.ts +12 -0
  374. package/src/react/core/hooks/useStore.ts +52 -0
  375. package/src/react/core/index.ts +90 -0
  376. package/src/react/form/components/FormState.tsx +17 -0
  377. package/src/react/form/errors/FormValidationError.ts +18 -0
  378. package/src/react/form/hooks/useForm.browser.spec.tsx +366 -0
  379. package/src/react/form/hooks/useForm.ts +47 -0
  380. package/src/react/form/hooks/useFormState.ts +130 -0
  381. package/src/react/form/index.ts +44 -0
  382. package/src/react/form/services/FormModel.ts +614 -0
  383. package/src/react/head/helpers/SeoExpander.spec.ts +203 -0
  384. package/src/react/head/helpers/SeoExpander.ts +142 -0
  385. package/src/react/head/hooks/useHead.spec.tsx +288 -0
  386. package/src/react/head/hooks/useHead.ts +62 -0
  387. package/src/react/head/index.browser.ts +26 -0
  388. package/src/react/head/index.ts +44 -0
  389. package/src/react/head/interfaces/Head.ts +105 -0
  390. package/src/react/head/primitives/$head.ts +25 -0
  391. package/src/react/head/providers/BrowserHeadProvider.browser.spec.ts +196 -0
  392. package/src/react/head/providers/BrowserHeadProvider.ts +212 -0
  393. package/src/react/head/providers/HeadProvider.ts +168 -0
  394. package/src/react/head/providers/ServerHeadProvider.ts +31 -0
  395. package/src/react/i18n/__tests__/integration.spec.tsx +239 -0
  396. package/src/react/i18n/components/Localize.spec.tsx +357 -0
  397. package/src/react/i18n/components/Localize.tsx +35 -0
  398. package/src/react/i18n/hooks/useI18n.browser.spec.tsx +438 -0
  399. package/src/react/i18n/hooks/useI18n.ts +18 -0
  400. package/src/react/i18n/index.ts +41 -0
  401. package/src/react/i18n/primitives/$dictionary.ts +69 -0
  402. package/src/react/i18n/providers/I18nProvider.spec.ts +389 -0
  403. package/src/react/i18n/providers/I18nProvider.ts +278 -0
  404. package/src/react/router/__tests__/page-head-browser.browser.spec.ts +95 -0
  405. package/src/react/router/__tests__/page-head.spec.ts +48 -0
  406. package/src/react/router/__tests__/seo-head.spec.ts +125 -0
  407. package/src/react/router/atoms/ssrManifestAtom.ts +58 -0
  408. package/src/react/router/components/ErrorViewer.tsx +872 -0
  409. package/src/react/router/components/Link.tsx +23 -0
  410. package/src/react/router/components/NestedView.tsx +223 -0
  411. package/src/react/router/components/NotFound.tsx +30 -0
  412. package/src/react/router/constants/PAGE_PRELOAD_KEY.ts +6 -0
  413. package/src/react/router/contexts/RouterLayerContext.ts +12 -0
  414. package/src/react/router/errors/Redirection.ts +28 -0
  415. package/src/react/router/hooks/useActive.ts +52 -0
  416. package/src/react/router/hooks/useQueryParams.ts +63 -0
  417. package/src/react/router/hooks/useRouter.ts +20 -0
  418. package/src/react/router/hooks/useRouterState.ts +11 -0
  419. package/src/react/router/index.browser.ts +45 -0
  420. package/src/react/router/index.shared.ts +19 -0
  421. package/src/react/router/index.ts +146 -0
  422. package/src/react/router/primitives/$page.browser.spec.tsx +851 -0
  423. package/src/react/router/primitives/$page.spec.tsx +676 -0
  424. package/src/react/router/primitives/$page.ts +489 -0
  425. package/src/react/router/providers/ReactBrowserProvider.ts +312 -0
  426. package/src/react/router/providers/ReactBrowserRendererProvider.ts +25 -0
  427. package/src/react/router/providers/ReactBrowserRouterProvider.ts +168 -0
  428. package/src/react/router/providers/ReactPageProvider.ts +726 -0
  429. package/src/react/router/providers/ReactPreloadProvider.spec.ts +142 -0
  430. package/src/react/router/providers/ReactPreloadProvider.ts +85 -0
  431. package/src/react/router/providers/ReactServerProvider.spec.tsx +316 -0
  432. package/src/react/router/providers/ReactServerProvider.ts +487 -0
  433. package/src/react/router/providers/ReactServerTemplateProvider.spec.ts +210 -0
  434. package/src/react/router/providers/ReactServerTemplateProvider.ts +542 -0
  435. package/src/react/router/providers/SSRManifestProvider.ts +334 -0
  436. package/src/react/router/services/ReactPageServerService.ts +48 -0
  437. package/src/react/router/services/ReactPageService.ts +27 -0
  438. package/src/react/router/services/ReactRouter.ts +262 -0
  439. package/src/react/websocket/hooks/useRoom.tsx +242 -0
  440. package/src/react/websocket/index.ts +7 -0
  441. package/src/redis/__tests__/redis.spec.ts +13 -0
  442. package/src/redis/index.ts +9 -25
  443. package/src/redis/providers/BunRedisProvider.ts +9 -0
  444. package/src/redis/providers/NodeRedisProvider.ts +8 -0
  445. package/src/redis/providers/RedisProvider.ts +16 -0
  446. package/src/retry/index.ts +11 -2
  447. package/src/router/index.ts +15 -0
  448. package/src/scheduler/index.ts +11 -2
  449. package/src/security/__tests__/BasicAuth.spec.ts +2 -0
  450. package/src/security/__tests__/ServerSecurityProvider.spec.ts +90 -5
  451. package/src/security/index.ts +15 -10
  452. package/src/security/interfaces/IssuerResolver.ts +27 -0
  453. package/src/security/primitives/$issuer.ts +55 -0
  454. package/src/security/providers/SecurityProvider.ts +179 -0
  455. package/src/security/providers/ServerBasicAuthProvider.ts +6 -2
  456. package/src/security/providers/ServerSecurityProvider.ts +63 -41
  457. package/src/server/auth/index.ts +12 -7
  458. package/src/server/cache/index.ts +7 -22
  459. package/src/server/compress/index.ts +10 -2
  460. package/src/server/cookies/index.ts +7 -5
  461. package/src/server/cookies/primitives/$cookie.ts +33 -11
  462. package/src/server/core/index.ts +16 -6
  463. package/src/server/core/interfaces/ServerRequest.ts +83 -1
  464. package/src/server/core/primitives/$action.spec.ts +1 -1
  465. package/src/server/core/primitives/$action.ts +8 -3
  466. package/src/server/core/providers/NodeHttpServerProvider.spec.ts +9 -3
  467. package/src/server/core/providers/NodeHttpServerProvider.ts +9 -3
  468. package/src/server/core/services/ServerRequestParser.spec.ts +520 -0
  469. package/src/server/core/services/ServerRequestParser.ts +306 -13
  470. package/src/server/cors/index.ts +7 -21
  471. package/src/server/cors/primitives/$cors.ts +6 -2
  472. package/src/server/health/index.ts +8 -2
  473. package/src/server/helmet/index.ts +11 -3
  474. package/src/server/links/index.ts +11 -6
  475. package/src/server/metrics/index.ts +10 -3
  476. package/src/server/multipart/index.ts +9 -3
  477. package/src/server/proxy/index.ts +8 -2
  478. package/src/server/rate-limit/index.ts +21 -25
  479. package/src/server/rate-limit/primitives/$rateLimit.ts +6 -2
  480. package/src/server/rate-limit/providers/ServerRateLimitProvider.spec.ts +38 -14
  481. package/src/server/rate-limit/providers/ServerRateLimitProvider.ts +22 -56
  482. package/src/server/static/index.ts +8 -2
  483. package/src/server/static/providers/ServerStaticProvider.ts +1 -1
  484. package/src/server/swagger/index.ts +9 -4
  485. package/src/server/swagger/providers/ServerSwaggerProvider.ts +1 -1
  486. package/src/sms/index.ts +9 -5
  487. package/src/sms/providers/LocalSmsProvider.spec.ts +1 -1
  488. package/src/sms/providers/LocalSmsProvider.ts +1 -1
  489. package/src/system/index.browser.ts +36 -0
  490. package/src/system/index.ts +62 -0
  491. package/src/system/index.workerd.ts +1 -0
  492. package/src/{file → system}/providers/FileSystemProvider.ts +24 -0
  493. package/src/{file → system}/providers/MemoryFileSystemProvider.ts +116 -3
  494. package/src/system/providers/MemoryShellProvider.ts +164 -0
  495. package/src/{file → system}/providers/NodeFileSystemProvider.spec.ts +2 -2
  496. package/src/{file → system}/providers/NodeFileSystemProvider.ts +47 -2
  497. package/src/system/providers/NodeShellProvider.ts +184 -0
  498. package/src/system/providers/ShellProvider.ts +74 -0
  499. package/src/{file → system}/services/FileDetector.spec.ts +2 -2
  500. package/src/thread/index.ts +11 -2
  501. package/src/topic/core/index.ts +12 -5
  502. package/src/vite/tasks/buildClient.ts +2 -7
  503. package/src/vite/tasks/buildServer.ts +19 -13
  504. package/src/vite/tasks/generateCloudflare.ts +10 -7
  505. package/src/vite/tasks/generateDocker.ts +4 -0
  506. package/src/websocket/index.ts +12 -8
  507. package/dist/file/index.d.ts.map +0 -1
  508. package/dist/file/index.js.map +0 -1
  509. package/src/cli/assets/apiIndexTs.ts +0 -16
  510. package/src/cli/assets/mainServerTs.ts +0 -24
  511. package/src/cli/assets/webAppRouterTs.ts +0 -16
  512. package/src/cli/assets/webHelloComponentTsx.ts +0 -20
  513. package/src/cli/providers/ViteTemplateProvider.ts +0 -27
  514. package/src/file/index.ts +0 -43
  515. /package/src/cli/{assets → templates}/apiHelloControllerTs.ts +0 -0
  516. /package/src/cli/{assets → templates}/biomeJson.ts +0 -0
  517. /package/src/cli/{assets → templates}/dummySpecTs.ts +0 -0
  518. /package/src/cli/{assets → templates}/editorconfig.ts +0 -0
  519. /package/src/cli/{assets → templates}/mainBrowserTs.ts +0 -0
  520. /package/src/cli/{assets → templates}/tsconfigJson.ts +0 -0
  521. /package/src/cli/{assets → templates}/webIndexTs.ts +0 -0
  522. /package/src/{file → system}/errors/FileError.ts +0 -0
  523. /package/src/{file → system}/services/FileDetector.ts +0 -0
@@ -1,12 +1,15 @@
1
- import { $atom, $env, $hook, $inject, $module, $use, Alepha, AlephaError, KIND, Primitive, TypeBoxError, createPrimitive, t } from "alepha";
1
+ import { $atom, $env, $hook, $inject, $module, $use, Alepha, AlephaError, Json, KIND, Primitive, TypeBoxError, createPrimitive, isFileLike, t } from "alepha";
2
2
  import { stdin, stdout } from "node:process";
3
3
  import { createInterface } from "node:readline/promises";
4
4
  import { $logger } from "alepha/logger";
5
5
  import * as fs from "node:fs/promises";
6
- import { cp, glob, readFile, rm } from "node:fs/promises";
6
+ import { access, copyFile, cp, glob, mkdir, readFile, readdir, rename, rm, stat, writeFile } from "node:fs/promises";
7
7
  import { join } from "node:path";
8
8
  import { DateTimeProvider } from "alepha/datetime";
9
- import { exec } from "node:child_process";
9
+ import { createReadStream } from "node:fs";
10
+ import { PassThrough, Readable } from "node:stream";
11
+ import { fileURLToPath } from "node:url";
12
+ import { exec, spawn } from "node:child_process";
10
13
 
11
14
  //#region ../../src/command/helpers/Asker.ts
12
15
  var Asker = class {
@@ -225,6 +228,1846 @@ var PrettyPrint = class {
225
228
  }
226
229
  };
227
230
 
231
+ //#endregion
232
+ //#region ../../src/system/providers/FileSystemProvider.ts
233
+ /**
234
+ * FileSystem interface providing utilities for working with files.
235
+ */
236
+ var FileSystemProvider = class {};
237
+
238
+ //#endregion
239
+ //#region ../../src/system/providers/MemoryFileSystemProvider.ts
240
+ /**
241
+ * In-memory implementation of FileSystemProvider for testing.
242
+ *
243
+ * This provider stores all files and directories in memory, making it ideal for
244
+ * unit tests that need to verify file operations without touching the real file system.
245
+ *
246
+ * @example
247
+ * ```typescript
248
+ * // In tests, substitute the real FileSystemProvider with MemoryFileSystemProvider
249
+ * const alepha = Alepha.create().with({
250
+ * provide: FileSystemProvider,
251
+ * use: MemoryFileSystemProvider,
252
+ * });
253
+ *
254
+ * // Run code that uses FileSystemProvider
255
+ * const service = alepha.inject(MyService);
256
+ * await service.saveFile("test.txt", "Hello World");
257
+ *
258
+ * // Verify the file was written
259
+ * const memoryFs = alepha.inject(MemoryFileSystemProvider);
260
+ * expect(memoryFs.files.get("test.txt")?.toString()).toBe("Hello World");
261
+ * ```
262
+ */
263
+ var MemoryFileSystemProvider = class {
264
+ json = $inject(Json);
265
+ /**
266
+ * In-memory storage for files (path -> content)
267
+ */
268
+ files = /* @__PURE__ */ new Map();
269
+ /**
270
+ * In-memory storage for directories
271
+ */
272
+ directories = /* @__PURE__ */ new Set();
273
+ /**
274
+ * Track mkdir calls for test assertions
275
+ */
276
+ mkdirCalls = [];
277
+ /**
278
+ * Track writeFile calls for test assertions
279
+ */
280
+ writeFileCalls = [];
281
+ /**
282
+ * Track readFile calls for test assertions
283
+ */
284
+ readFileCalls = [];
285
+ /**
286
+ * Track rm calls for test assertions
287
+ */
288
+ rmCalls = [];
289
+ /**
290
+ * Track join calls for test assertions
291
+ */
292
+ joinCalls = [];
293
+ /**
294
+ * Error to throw on mkdir (for testing error handling)
295
+ */
296
+ mkdirError = null;
297
+ /**
298
+ * Error to throw on writeFile (for testing error handling)
299
+ */
300
+ writeFileError = null;
301
+ /**
302
+ * Error to throw on readFile (for testing error handling)
303
+ */
304
+ readFileError = null;
305
+ constructor(options = {}) {
306
+ this.mkdirError = options.mkdirError ?? null;
307
+ this.writeFileError = options.writeFileError ?? null;
308
+ this.readFileError = options.readFileError ?? null;
309
+ }
310
+ /**
311
+ * Join path segments using forward slashes.
312
+ * Uses Node's path.join for proper normalization (handles .. and .)
313
+ */
314
+ join(...paths) {
315
+ this.joinCalls.push(paths);
316
+ return join(...paths);
317
+ }
318
+ /**
319
+ * Create a FileLike object from various sources.
320
+ */
321
+ createFile(options) {
322
+ if ("path" in options) {
323
+ const filePath = options.path;
324
+ const buffer = this.files.get(filePath);
325
+ if (buffer === void 0) throw new Error(`ENOENT: no such file or directory, open '${filePath}'`);
326
+ return {
327
+ name: options.name ?? filePath.split("/").pop() ?? "file",
328
+ type: options.type ?? "application/octet-stream",
329
+ size: buffer.byteLength,
330
+ lastModified: Date.now(),
331
+ stream: () => {
332
+ throw new Error("Stream not implemented in MemoryFileSystemProvider");
333
+ },
334
+ arrayBuffer: async () => buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength),
335
+ text: async () => buffer.toString("utf-8")
336
+ };
337
+ }
338
+ if ("buffer" in options) {
339
+ const buffer = options.buffer;
340
+ return {
341
+ name: options.name ?? "file",
342
+ type: options.type ?? "application/octet-stream",
343
+ size: buffer.byteLength,
344
+ lastModified: Date.now(),
345
+ stream: () => {
346
+ throw new Error("Stream not implemented in MemoryFileSystemProvider");
347
+ },
348
+ arrayBuffer: async () => buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength),
349
+ text: async () => buffer.toString("utf-8")
350
+ };
351
+ }
352
+ if ("text" in options) {
353
+ const buffer = Buffer.from(options.text, "utf-8");
354
+ return {
355
+ name: options.name ?? "file.txt",
356
+ type: options.type ?? "text/plain",
357
+ size: buffer.byteLength,
358
+ lastModified: Date.now(),
359
+ stream: () => {
360
+ throw new Error("Stream not implemented in MemoryFileSystemProvider");
361
+ },
362
+ arrayBuffer: async () => buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength),
363
+ text: async () => options.text
364
+ };
365
+ }
366
+ throw new Error("MemoryFileSystemProvider.createFile: unsupported options. Only buffer and text are supported.");
367
+ }
368
+ /**
369
+ * Remove a file or directory from memory.
370
+ */
371
+ async rm(path, options) {
372
+ this.rmCalls.push({
373
+ path,
374
+ options
375
+ });
376
+ if (!(this.files.has(path) || this.directories.has(path)) && !options?.force) throw new Error(`ENOENT: no such file or directory, rm '${path}'`);
377
+ if (this.directories.has(path)) if (options?.recursive) {
378
+ this.directories.delete(path);
379
+ for (const filePath of this.files.keys()) if (filePath.startsWith(`${path}/`)) this.files.delete(filePath);
380
+ for (const dirPath of this.directories) if (dirPath.startsWith(`${path}/`)) this.directories.delete(dirPath);
381
+ } else throw new Error(`EISDIR: illegal operation on a directory, rm '${path}'`);
382
+ else this.files.delete(path);
383
+ }
384
+ /**
385
+ * Copy a file or directory in memory.
386
+ */
387
+ async cp(src, dest, options) {
388
+ if (this.directories.has(src)) {
389
+ if (!options?.recursive) throw new Error(`Cannot copy directory without recursive option: ${src}`);
390
+ this.directories.add(dest);
391
+ for (const [filePath, content] of this.files) if (filePath.startsWith(`${src}/`)) {
392
+ const newPath = filePath.replace(src, dest);
393
+ this.files.set(newPath, Buffer.from(content));
394
+ }
395
+ } else if (this.files.has(src)) {
396
+ const content = this.files.get(src);
397
+ this.files.set(dest, Buffer.from(content));
398
+ } else throw new Error(`ENOENT: no such file or directory, cp '${src}'`);
399
+ }
400
+ /**
401
+ * Move/rename a file or directory in memory.
402
+ */
403
+ async mv(src, dest) {
404
+ if (this.directories.has(src)) {
405
+ this.directories.delete(src);
406
+ this.directories.add(dest);
407
+ for (const [filePath, content] of this.files) if (filePath.startsWith(`${src}/`)) {
408
+ const newPath = filePath.replace(src, dest);
409
+ this.files.delete(filePath);
410
+ this.files.set(newPath, content);
411
+ }
412
+ } else if (this.files.has(src)) {
413
+ const content = this.files.get(src);
414
+ this.files.delete(src);
415
+ this.files.set(dest, content);
416
+ } else throw new Error(`ENOENT: no such file or directory, mv '${src}'`);
417
+ }
418
+ /**
419
+ * Create a directory in memory.
420
+ */
421
+ async mkdir(path, options) {
422
+ this.mkdirCalls.push({
423
+ path,
424
+ options
425
+ });
426
+ if (this.mkdirError) throw this.mkdirError;
427
+ if (this.directories.has(path) && !options?.recursive) throw new Error(`EEXIST: file already exists, mkdir '${path}'`);
428
+ this.directories.add(path);
429
+ if (options?.recursive) {
430
+ const parts = path.split("/").filter(Boolean);
431
+ let current = "";
432
+ for (const part of parts) {
433
+ current = current ? `${current}/${part}` : part;
434
+ this.directories.add(current);
435
+ }
436
+ }
437
+ }
438
+ /**
439
+ * List files in a directory.
440
+ */
441
+ async ls(path, options) {
442
+ const normalizedPath = path.replace(/\/$/, "");
443
+ const entries = /* @__PURE__ */ new Set();
444
+ for (const filePath of this.files.keys()) if (filePath.startsWith(`${normalizedPath}/`)) {
445
+ const relativePath = filePath.slice(normalizedPath.length + 1);
446
+ const parts = relativePath.split("/");
447
+ if (options?.recursive) entries.add(relativePath);
448
+ else entries.add(parts[0]);
449
+ }
450
+ for (const dirPath of this.directories) if (dirPath.startsWith(`${normalizedPath}/`) && dirPath !== normalizedPath) {
451
+ const relativePath = dirPath.slice(normalizedPath.length + 1);
452
+ const parts = relativePath.split("/");
453
+ if (options?.recursive) entries.add(relativePath);
454
+ else if (parts.length === 1) entries.add(parts[0]);
455
+ }
456
+ let result = Array.from(entries);
457
+ if (!options?.hidden) result = result.filter((entry) => !entry.startsWith("."));
458
+ return result.sort();
459
+ }
460
+ /**
461
+ * Check if a file or directory exists in memory.
462
+ */
463
+ async exists(path) {
464
+ return this.files.has(path) || this.directories.has(path);
465
+ }
466
+ /**
467
+ * Read a file from memory.
468
+ */
469
+ async readFile(path) {
470
+ this.readFileCalls.push(path);
471
+ if (this.readFileError) throw this.readFileError;
472
+ const content = this.files.get(path);
473
+ if (!content) throw new Error(`ENOENT: no such file or directory, open '${path}'`);
474
+ return content;
475
+ }
476
+ /**
477
+ * Read a file from memory as text.
478
+ */
479
+ async readTextFile(path) {
480
+ return (await this.readFile(path)).toString("utf-8");
481
+ }
482
+ /**
483
+ * Read a file from memory as JSON.
484
+ */
485
+ async readJsonFile(path) {
486
+ const text = await this.readTextFile(path);
487
+ return this.json.parse(text);
488
+ }
489
+ /**
490
+ * Write a file to memory.
491
+ */
492
+ async writeFile(path, data) {
493
+ const dataStr = typeof data === "string" ? data : data instanceof Buffer || data instanceof Uint8Array ? data.toString("utf-8") : await data.text();
494
+ this.writeFileCalls.push({
495
+ path,
496
+ data: dataStr
497
+ });
498
+ if (this.writeFileError) throw this.writeFileError;
499
+ 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");
500
+ this.files.set(path, buffer);
501
+ }
502
+ /**
503
+ * Reset all in-memory state (useful between tests).
504
+ */
505
+ reset() {
506
+ this.files.clear();
507
+ this.directories.clear();
508
+ this.mkdirCalls = [];
509
+ this.writeFileCalls = [];
510
+ this.readFileCalls = [];
511
+ this.rmCalls = [];
512
+ this.joinCalls = [];
513
+ this.mkdirError = null;
514
+ this.writeFileError = null;
515
+ this.readFileError = null;
516
+ }
517
+ /**
518
+ * Check if a file was written during the test.
519
+ *
520
+ * @example
521
+ * ```typescript
522
+ * expect(fs.wasWritten("/project/tsconfig.json")).toBe(true);
523
+ * ```
524
+ */
525
+ wasWritten(path) {
526
+ return this.writeFileCalls.some((call) => call.path === path);
527
+ }
528
+ /**
529
+ * Check if a file was written with content matching a pattern.
530
+ *
531
+ * @example
532
+ * ```typescript
533
+ * expect(fs.wasWrittenMatching("/project/tsconfig.json", /extends/)).toBe(true);
534
+ * ```
535
+ */
536
+ wasWrittenMatching(path, pattern) {
537
+ const call = this.writeFileCalls.find((c) => c.path === path);
538
+ return call ? pattern.test(call.data) : false;
539
+ }
540
+ /**
541
+ * Check if a file was read during the test.
542
+ *
543
+ * @example
544
+ * ```typescript
545
+ * expect(fs.wasRead("/project/package.json")).toBe(true);
546
+ * ```
547
+ */
548
+ wasRead(path) {
549
+ return this.readFileCalls.includes(path);
550
+ }
551
+ /**
552
+ * Check if a file was deleted during the test.
553
+ *
554
+ * @example
555
+ * ```typescript
556
+ * expect(fs.wasDeleted("/project/old-file.txt")).toBe(true);
557
+ * ```
558
+ */
559
+ wasDeleted(path) {
560
+ return this.rmCalls.some((call) => call.path === path);
561
+ }
562
+ /**
563
+ * Get the content of a file as a string (convenience method for testing).
564
+ */
565
+ getFileContent(path) {
566
+ return this.files.get(path)?.toString("utf-8");
567
+ }
568
+ };
569
+
570
+ //#endregion
571
+ //#region ../../src/system/providers/MemoryShellProvider.ts
572
+ /**
573
+ * In-memory implementation of ShellProvider for testing.
574
+ *
575
+ * Records all commands that would be executed without actually running them.
576
+ * Can be configured to return specific outputs or throw errors for testing.
577
+ *
578
+ * @example
579
+ * ```typescript
580
+ * // In tests, substitute the real ShellProvider with MemoryShellProvider
581
+ * const alepha = Alepha.create().with({
582
+ * provide: ShellProvider,
583
+ * use: MemoryShellProvider,
584
+ * });
585
+ *
586
+ * // Configure mock behavior
587
+ * const shell = alepha.inject(MemoryShellProvider);
588
+ * shell.configure({
589
+ * outputs: { "echo hello": "hello\n" },
590
+ * errors: { "failing-cmd": "Command failed" },
591
+ * });
592
+ *
593
+ * // Or use the fluent API
594
+ * shell.outputs.set("another-cmd", "output");
595
+ * shell.errors.set("another-error", "Error message");
596
+ *
597
+ * // Run code that uses ShellProvider
598
+ * const service = alepha.inject(MyService);
599
+ * await service.doSomething();
600
+ *
601
+ * // Verify commands were called
602
+ * expect(shell.calls).toHaveLength(2);
603
+ * expect(shell.calls[0].command).toBe("yarn install");
604
+ * ```
605
+ */
606
+ var MemoryShellProvider = class {
607
+ /**
608
+ * All recorded shell calls.
609
+ */
610
+ calls = [];
611
+ /**
612
+ * Simulated outputs for specific commands.
613
+ */
614
+ outputs = /* @__PURE__ */ new Map();
615
+ /**
616
+ * Commands that should throw an error.
617
+ */
618
+ errors = /* @__PURE__ */ new Map();
619
+ /**
620
+ * Commands considered installed in the system PATH.
621
+ */
622
+ installedCommands = /* @__PURE__ */ new Set();
623
+ /**
624
+ * Configure the mock with predefined outputs, errors, and installed commands.
625
+ */
626
+ configure(options) {
627
+ if (options.outputs) for (const [cmd, output] of Object.entries(options.outputs)) this.outputs.set(cmd, output);
628
+ if (options.errors) for (const [cmd, error] of Object.entries(options.errors)) this.errors.set(cmd, error);
629
+ if (options.installedCommands) for (const cmd of options.installedCommands) this.installedCommands.add(cmd);
630
+ return this;
631
+ }
632
+ /**
633
+ * Record command and return simulated output.
634
+ */
635
+ async run(command, options = {}) {
636
+ this.calls.push({
637
+ command,
638
+ options
639
+ });
640
+ const errorMsg = this.errors.get(command);
641
+ if (errorMsg) throw new Error(errorMsg);
642
+ return this.outputs.get(command) ?? "";
643
+ }
644
+ /**
645
+ * Check if a specific command was called.
646
+ */
647
+ wasCalled(command) {
648
+ return this.calls.some((call) => call.command === command);
649
+ }
650
+ /**
651
+ * Check if a command matching a pattern was called.
652
+ */
653
+ wasCalledMatching(pattern) {
654
+ return this.calls.some((call) => pattern.test(call.command));
655
+ }
656
+ /**
657
+ * Get all calls matching a pattern.
658
+ */
659
+ getCallsMatching(pattern) {
660
+ return this.calls.filter((call) => pattern.test(call.command));
661
+ }
662
+ /**
663
+ * Check if a command is installed.
664
+ */
665
+ async isInstalled(command) {
666
+ return this.installedCommands.has(command);
667
+ }
668
+ /**
669
+ * Reset all recorded state.
670
+ */
671
+ reset() {
672
+ this.calls = [];
673
+ this.outputs.clear();
674
+ this.errors.clear();
675
+ this.installedCommands.clear();
676
+ }
677
+ };
678
+
679
+ //#endregion
680
+ //#region ../../src/system/services/FileDetector.ts
681
+ /**
682
+ * Service for detecting file types and getting content types.
683
+ *
684
+ * @example
685
+ * ```typescript
686
+ * const detector = alepha.inject(FileDetector);
687
+ *
688
+ * // Get content type from filename
689
+ * const mimeType = detector.getContentType("image.png"); // "image/png"
690
+ *
691
+ * // Detect file type by magic bytes
692
+ * const stream = createReadStream('image.png');
693
+ * const result = await detector.detectFileType(stream, 'image.png');
694
+ * console.log(result.mimeType); // 'image/png'
695
+ * console.log(result.verified); // true if magic bytes match
696
+ * ```
697
+ */
698
+ var FileDetector = class FileDetector {
699
+ /**
700
+ * Magic byte signatures for common file formats.
701
+ * Each signature is represented as an array of bytes or null (wildcard).
702
+ */
703
+ static MAGIC_BYTES = {
704
+ png: [{
705
+ signature: [
706
+ 137,
707
+ 80,
708
+ 78,
709
+ 71,
710
+ 13,
711
+ 10,
712
+ 26,
713
+ 10
714
+ ],
715
+ mimeType: "image/png"
716
+ }],
717
+ jpg: [
718
+ {
719
+ signature: [
720
+ 255,
721
+ 216,
722
+ 255,
723
+ 224
724
+ ],
725
+ mimeType: "image/jpeg"
726
+ },
727
+ {
728
+ signature: [
729
+ 255,
730
+ 216,
731
+ 255,
732
+ 225
733
+ ],
734
+ mimeType: "image/jpeg"
735
+ },
736
+ {
737
+ signature: [
738
+ 255,
739
+ 216,
740
+ 255,
741
+ 226
742
+ ],
743
+ mimeType: "image/jpeg"
744
+ },
745
+ {
746
+ signature: [
747
+ 255,
748
+ 216,
749
+ 255,
750
+ 227
751
+ ],
752
+ mimeType: "image/jpeg"
753
+ },
754
+ {
755
+ signature: [
756
+ 255,
757
+ 216,
758
+ 255,
759
+ 232
760
+ ],
761
+ mimeType: "image/jpeg"
762
+ }
763
+ ],
764
+ jpeg: [
765
+ {
766
+ signature: [
767
+ 255,
768
+ 216,
769
+ 255,
770
+ 224
771
+ ],
772
+ mimeType: "image/jpeg"
773
+ },
774
+ {
775
+ signature: [
776
+ 255,
777
+ 216,
778
+ 255,
779
+ 225
780
+ ],
781
+ mimeType: "image/jpeg"
782
+ },
783
+ {
784
+ signature: [
785
+ 255,
786
+ 216,
787
+ 255,
788
+ 226
789
+ ],
790
+ mimeType: "image/jpeg"
791
+ },
792
+ {
793
+ signature: [
794
+ 255,
795
+ 216,
796
+ 255,
797
+ 227
798
+ ],
799
+ mimeType: "image/jpeg"
800
+ },
801
+ {
802
+ signature: [
803
+ 255,
804
+ 216,
805
+ 255,
806
+ 232
807
+ ],
808
+ mimeType: "image/jpeg"
809
+ }
810
+ ],
811
+ gif: [{
812
+ signature: [
813
+ 71,
814
+ 73,
815
+ 70,
816
+ 56,
817
+ 55,
818
+ 97
819
+ ],
820
+ mimeType: "image/gif"
821
+ }, {
822
+ signature: [
823
+ 71,
824
+ 73,
825
+ 70,
826
+ 56,
827
+ 57,
828
+ 97
829
+ ],
830
+ mimeType: "image/gif"
831
+ }],
832
+ webp: [{
833
+ signature: [
834
+ 82,
835
+ 73,
836
+ 70,
837
+ 70,
838
+ null,
839
+ null,
840
+ null,
841
+ null,
842
+ 87,
843
+ 69,
844
+ 66,
845
+ 80
846
+ ],
847
+ mimeType: "image/webp"
848
+ }],
849
+ bmp: [{
850
+ signature: [66, 77],
851
+ mimeType: "image/bmp"
852
+ }],
853
+ ico: [{
854
+ signature: [
855
+ 0,
856
+ 0,
857
+ 1,
858
+ 0
859
+ ],
860
+ mimeType: "image/x-icon"
861
+ }],
862
+ tiff: [{
863
+ signature: [
864
+ 73,
865
+ 73,
866
+ 42,
867
+ 0
868
+ ],
869
+ mimeType: "image/tiff"
870
+ }, {
871
+ signature: [
872
+ 77,
873
+ 77,
874
+ 0,
875
+ 42
876
+ ],
877
+ mimeType: "image/tiff"
878
+ }],
879
+ tif: [{
880
+ signature: [
881
+ 73,
882
+ 73,
883
+ 42,
884
+ 0
885
+ ],
886
+ mimeType: "image/tiff"
887
+ }, {
888
+ signature: [
889
+ 77,
890
+ 77,
891
+ 0,
892
+ 42
893
+ ],
894
+ mimeType: "image/tiff"
895
+ }],
896
+ pdf: [{
897
+ signature: [
898
+ 37,
899
+ 80,
900
+ 68,
901
+ 70,
902
+ 45
903
+ ],
904
+ mimeType: "application/pdf"
905
+ }],
906
+ zip: [
907
+ {
908
+ signature: [
909
+ 80,
910
+ 75,
911
+ 3,
912
+ 4
913
+ ],
914
+ mimeType: "application/zip"
915
+ },
916
+ {
917
+ signature: [
918
+ 80,
919
+ 75,
920
+ 5,
921
+ 6
922
+ ],
923
+ mimeType: "application/zip"
924
+ },
925
+ {
926
+ signature: [
927
+ 80,
928
+ 75,
929
+ 7,
930
+ 8
931
+ ],
932
+ mimeType: "application/zip"
933
+ }
934
+ ],
935
+ rar: [{
936
+ signature: [
937
+ 82,
938
+ 97,
939
+ 114,
940
+ 33,
941
+ 26,
942
+ 7
943
+ ],
944
+ mimeType: "application/vnd.rar"
945
+ }],
946
+ "7z": [{
947
+ signature: [
948
+ 55,
949
+ 122,
950
+ 188,
951
+ 175,
952
+ 39,
953
+ 28
954
+ ],
955
+ mimeType: "application/x-7z-compressed"
956
+ }],
957
+ tar: [{
958
+ signature: [
959
+ 117,
960
+ 115,
961
+ 116,
962
+ 97,
963
+ 114
964
+ ],
965
+ mimeType: "application/x-tar"
966
+ }],
967
+ gz: [{
968
+ signature: [31, 139],
969
+ mimeType: "application/gzip"
970
+ }],
971
+ tgz: [{
972
+ signature: [31, 139],
973
+ mimeType: "application/gzip"
974
+ }],
975
+ mp3: [
976
+ {
977
+ signature: [255, 251],
978
+ mimeType: "audio/mpeg"
979
+ },
980
+ {
981
+ signature: [255, 243],
982
+ mimeType: "audio/mpeg"
983
+ },
984
+ {
985
+ signature: [255, 242],
986
+ mimeType: "audio/mpeg"
987
+ },
988
+ {
989
+ signature: [
990
+ 73,
991
+ 68,
992
+ 51
993
+ ],
994
+ mimeType: "audio/mpeg"
995
+ }
996
+ ],
997
+ wav: [{
998
+ signature: [
999
+ 82,
1000
+ 73,
1001
+ 70,
1002
+ 70,
1003
+ null,
1004
+ null,
1005
+ null,
1006
+ null,
1007
+ 87,
1008
+ 65,
1009
+ 86,
1010
+ 69
1011
+ ],
1012
+ mimeType: "audio/wav"
1013
+ }],
1014
+ ogg: [{
1015
+ signature: [
1016
+ 79,
1017
+ 103,
1018
+ 103,
1019
+ 83
1020
+ ],
1021
+ mimeType: "audio/ogg"
1022
+ }],
1023
+ flac: [{
1024
+ signature: [
1025
+ 102,
1026
+ 76,
1027
+ 97,
1028
+ 67
1029
+ ],
1030
+ mimeType: "audio/flac"
1031
+ }],
1032
+ mp4: [
1033
+ {
1034
+ signature: [
1035
+ null,
1036
+ null,
1037
+ null,
1038
+ null,
1039
+ 102,
1040
+ 116,
1041
+ 121,
1042
+ 112
1043
+ ],
1044
+ mimeType: "video/mp4"
1045
+ },
1046
+ {
1047
+ signature: [
1048
+ null,
1049
+ null,
1050
+ null,
1051
+ null,
1052
+ 102,
1053
+ 116,
1054
+ 121,
1055
+ 112,
1056
+ 105,
1057
+ 115,
1058
+ 111,
1059
+ 109
1060
+ ],
1061
+ mimeType: "video/mp4"
1062
+ },
1063
+ {
1064
+ signature: [
1065
+ null,
1066
+ null,
1067
+ null,
1068
+ null,
1069
+ 102,
1070
+ 116,
1071
+ 121,
1072
+ 112,
1073
+ 109,
1074
+ 112,
1075
+ 52,
1076
+ 50
1077
+ ],
1078
+ mimeType: "video/mp4"
1079
+ }
1080
+ ],
1081
+ webm: [{
1082
+ signature: [
1083
+ 26,
1084
+ 69,
1085
+ 223,
1086
+ 163
1087
+ ],
1088
+ mimeType: "video/webm"
1089
+ }],
1090
+ avi: [{
1091
+ signature: [
1092
+ 82,
1093
+ 73,
1094
+ 70,
1095
+ 70,
1096
+ null,
1097
+ null,
1098
+ null,
1099
+ null,
1100
+ 65,
1101
+ 86,
1102
+ 73,
1103
+ 32
1104
+ ],
1105
+ mimeType: "video/x-msvideo"
1106
+ }],
1107
+ mov: [{
1108
+ signature: [
1109
+ null,
1110
+ null,
1111
+ null,
1112
+ null,
1113
+ 102,
1114
+ 116,
1115
+ 121,
1116
+ 112,
1117
+ 113,
1118
+ 116,
1119
+ 32,
1120
+ 32
1121
+ ],
1122
+ mimeType: "video/quicktime"
1123
+ }],
1124
+ mkv: [{
1125
+ signature: [
1126
+ 26,
1127
+ 69,
1128
+ 223,
1129
+ 163
1130
+ ],
1131
+ mimeType: "video/x-matroska"
1132
+ }],
1133
+ docx: [{
1134
+ signature: [
1135
+ 80,
1136
+ 75,
1137
+ 3,
1138
+ 4
1139
+ ],
1140
+ mimeType: "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
1141
+ }],
1142
+ xlsx: [{
1143
+ signature: [
1144
+ 80,
1145
+ 75,
1146
+ 3,
1147
+ 4
1148
+ ],
1149
+ mimeType: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
1150
+ }],
1151
+ pptx: [{
1152
+ signature: [
1153
+ 80,
1154
+ 75,
1155
+ 3,
1156
+ 4
1157
+ ],
1158
+ mimeType: "application/vnd.openxmlformats-officedocument.presentationml.presentation"
1159
+ }],
1160
+ doc: [{
1161
+ signature: [
1162
+ 208,
1163
+ 207,
1164
+ 17,
1165
+ 224,
1166
+ 161,
1167
+ 177,
1168
+ 26,
1169
+ 225
1170
+ ],
1171
+ mimeType: "application/msword"
1172
+ }],
1173
+ xls: [{
1174
+ signature: [
1175
+ 208,
1176
+ 207,
1177
+ 17,
1178
+ 224,
1179
+ 161,
1180
+ 177,
1181
+ 26,
1182
+ 225
1183
+ ],
1184
+ mimeType: "application/vnd.ms-excel"
1185
+ }],
1186
+ ppt: [{
1187
+ signature: [
1188
+ 208,
1189
+ 207,
1190
+ 17,
1191
+ 224,
1192
+ 161,
1193
+ 177,
1194
+ 26,
1195
+ 225
1196
+ ],
1197
+ mimeType: "application/vnd.ms-powerpoint"
1198
+ }]
1199
+ };
1200
+ /**
1201
+ * All possible format signatures for checking against actual file content
1202
+ */
1203
+ static ALL_SIGNATURES = Object.entries(FileDetector.MAGIC_BYTES).flatMap(([ext, signatures]) => signatures.map((sig) => ({
1204
+ ext,
1205
+ ...sig
1206
+ })));
1207
+ /**
1208
+ * MIME type map for file extensions.
1209
+ *
1210
+ * Can be used to get the content type of file based on its extension.
1211
+ * Feel free to add more mime types in your project!
1212
+ */
1213
+ static mimeMap = {
1214
+ json: "application/json",
1215
+ txt: "text/plain",
1216
+ html: "text/html",
1217
+ htm: "text/html",
1218
+ xml: "application/xml",
1219
+ csv: "text/csv",
1220
+ pdf: "application/pdf",
1221
+ md: "text/markdown",
1222
+ markdown: "text/markdown",
1223
+ rtf: "application/rtf",
1224
+ css: "text/css",
1225
+ js: "application/javascript",
1226
+ mjs: "application/javascript",
1227
+ ts: "application/typescript",
1228
+ jsx: "text/jsx",
1229
+ tsx: "text/tsx",
1230
+ zip: "application/zip",
1231
+ rar: "application/vnd.rar",
1232
+ "7z": "application/x-7z-compressed",
1233
+ tar: "application/x-tar",
1234
+ gz: "application/gzip",
1235
+ tgz: "application/gzip",
1236
+ png: "image/png",
1237
+ jpg: "image/jpeg",
1238
+ jpeg: "image/jpeg",
1239
+ gif: "image/gif",
1240
+ webp: "image/webp",
1241
+ svg: "image/svg+xml",
1242
+ bmp: "image/bmp",
1243
+ ico: "image/x-icon",
1244
+ tiff: "image/tiff",
1245
+ tif: "image/tiff",
1246
+ mp3: "audio/mpeg",
1247
+ wav: "audio/wav",
1248
+ ogg: "audio/ogg",
1249
+ m4a: "audio/mp4",
1250
+ aac: "audio/aac",
1251
+ flac: "audio/flac",
1252
+ mp4: "video/mp4",
1253
+ webm: "video/webm",
1254
+ avi: "video/x-msvideo",
1255
+ mov: "video/quicktime",
1256
+ wmv: "video/x-ms-wmv",
1257
+ flv: "video/x-flv",
1258
+ mkv: "video/x-matroska",
1259
+ doc: "application/msword",
1260
+ docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
1261
+ xls: "application/vnd.ms-excel",
1262
+ xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
1263
+ ppt: "application/vnd.ms-powerpoint",
1264
+ pptx: "application/vnd.openxmlformats-officedocument.presentationml.presentation",
1265
+ woff: "font/woff",
1266
+ woff2: "font/woff2",
1267
+ ttf: "font/ttf",
1268
+ otf: "font/otf",
1269
+ eot: "application/vnd.ms-fontobject"
1270
+ };
1271
+ /**
1272
+ * Reverse MIME type map for looking up extensions from MIME types.
1273
+ * Prefers shorter, more common extensions when multiple exist.
1274
+ */
1275
+ static reverseMimeMap = (() => {
1276
+ const reverse = {};
1277
+ for (const [ext, mimeType] of Object.entries(FileDetector.mimeMap)) if (!reverse[mimeType]) reverse[mimeType] = ext;
1278
+ return reverse;
1279
+ })();
1280
+ /**
1281
+ * Returns the file extension for a given MIME type.
1282
+ *
1283
+ * @param mimeType - The MIME type to look up
1284
+ * @returns The file extension (without dot), or "bin" if not found
1285
+ *
1286
+ * @example
1287
+ * ```typescript
1288
+ * const detector = alepha.inject(FileDetector);
1289
+ * const ext = detector.getExtensionFromMimeType("image/png"); // "png"
1290
+ * const ext2 = detector.getExtensionFromMimeType("application/octet-stream"); // "bin"
1291
+ * ```
1292
+ */
1293
+ getExtensionFromMimeType(mimeType) {
1294
+ return FileDetector.reverseMimeMap[mimeType] || "bin";
1295
+ }
1296
+ /**
1297
+ * Returns the content type of file based on its filename.
1298
+ *
1299
+ * @param filename - The filename to check
1300
+ * @returns The MIME type
1301
+ *
1302
+ * @example
1303
+ * ```typescript
1304
+ * const detector = alepha.inject(FileDetector);
1305
+ * const mimeType = detector.getContentType("image.png"); // "image/png"
1306
+ * ```
1307
+ */
1308
+ getContentType(filename) {
1309
+ const ext = filename.toLowerCase().split(".").pop() || "";
1310
+ return FileDetector.mimeMap[ext] || "application/octet-stream";
1311
+ }
1312
+ /**
1313
+ * Detects the file type by checking magic bytes against the stream content.
1314
+ *
1315
+ * @param stream - The readable stream to check
1316
+ * @param filename - The filename (used to get the extension)
1317
+ * @returns File type information including MIME type, extension, and verification status
1318
+ *
1319
+ * @example
1320
+ * ```typescript
1321
+ * const detector = alepha.inject(FileDetector);
1322
+ * const stream = createReadStream('image.png');
1323
+ * const result = await detector.detectFileType(stream, 'image.png');
1324
+ * console.log(result.mimeType); // 'image/png'
1325
+ * console.log(result.verified); // true if magic bytes match
1326
+ * ```
1327
+ */
1328
+ async detectFileType(stream, filename) {
1329
+ const expectedMimeType = this.getContentType(filename);
1330
+ const lastDotIndex = filename.lastIndexOf(".");
1331
+ const ext = lastDotIndex > 0 ? filename.substring(lastDotIndex + 1).toLowerCase() : "";
1332
+ const { buffer, stream: newStream } = await this.peekBytes(stream, 16);
1333
+ const expectedSignatures = FileDetector.MAGIC_BYTES[ext];
1334
+ if (expectedSignatures) {
1335
+ for (const { signature, mimeType } of expectedSignatures) if (this.matchesSignature(buffer, signature)) return {
1336
+ mimeType,
1337
+ extension: ext,
1338
+ verified: true,
1339
+ stream: newStream
1340
+ };
1341
+ }
1342
+ for (const { ext: detectedExt, signature, mimeType } of FileDetector.ALL_SIGNATURES) if (detectedExt !== ext && this.matchesSignature(buffer, signature)) return {
1343
+ mimeType,
1344
+ extension: detectedExt,
1345
+ verified: true,
1346
+ stream: newStream
1347
+ };
1348
+ return {
1349
+ mimeType: expectedMimeType,
1350
+ extension: ext,
1351
+ verified: false,
1352
+ stream: newStream
1353
+ };
1354
+ }
1355
+ /**
1356
+ * Reads all bytes from a stream and returns the first N bytes along with a new stream containing all data.
1357
+ * This approach reads the entire stream upfront to avoid complex async handling issues.
1358
+ *
1359
+ * @protected
1360
+ */
1361
+ async peekBytes(stream, numBytes) {
1362
+ const chunks = [];
1363
+ for await (const chunk of stream) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
1364
+ const allData = Buffer.concat(chunks);
1365
+ return {
1366
+ buffer: allData.subarray(0, numBytes),
1367
+ stream: Readable.from(allData)
1368
+ };
1369
+ }
1370
+ /**
1371
+ * Checks if a buffer matches a magic byte signature.
1372
+ *
1373
+ * @protected
1374
+ */
1375
+ matchesSignature(buffer, signature) {
1376
+ if (buffer.length < signature.length) return false;
1377
+ for (let i = 0; i < signature.length; i++) if (signature[i] !== null && buffer[i] !== signature[i]) return false;
1378
+ return true;
1379
+ }
1380
+ };
1381
+
1382
+ //#endregion
1383
+ //#region ../../src/system/providers/NodeFileSystemProvider.ts
1384
+ /**
1385
+ * Node.js implementation of FileSystem interface.
1386
+ *
1387
+ * @example
1388
+ * ```typescript
1389
+ * const fs = alepha.inject(NodeFileSystemProvider);
1390
+ *
1391
+ * // Create from URL
1392
+ * const file1 = fs.createFile({ url: "file:///path/to/file.png" });
1393
+ *
1394
+ * // Create from Buffer
1395
+ * const file2 = fs.createFile({ buffer: Buffer.from("hello"), name: "hello.txt" });
1396
+ *
1397
+ * // Create from text
1398
+ * const file3 = fs.createFile({ text: "Hello, world!", name: "greeting.txt" });
1399
+ *
1400
+ * // File operations
1401
+ * await fs.mkdir("/tmp/mydir", { recursive: true });
1402
+ * await fs.cp("/src/file.txt", "/dest/file.txt");
1403
+ * await fs.mv("/old/path.txt", "/new/path.txt");
1404
+ * const files = await fs.ls("/tmp");
1405
+ * await fs.rm("/tmp/file.txt");
1406
+ * ```
1407
+ */
1408
+ var NodeFileSystemProvider = class {
1409
+ detector = $inject(FileDetector);
1410
+ json = $inject(Json);
1411
+ join(...paths) {
1412
+ return join(...paths);
1413
+ }
1414
+ /**
1415
+ * Creates a FileLike object from various sources.
1416
+ *
1417
+ * @param options - Options for creating the file
1418
+ * @returns A FileLike object
1419
+ *
1420
+ * @example
1421
+ * ```typescript
1422
+ * const fs = alepha.inject(NodeFileSystemProvider);
1423
+ *
1424
+ * // From URL
1425
+ * const file1 = fs.createFile({ url: "https://example.com/image.png" });
1426
+ *
1427
+ * // From Buffer
1428
+ * const file2 = fs.createFile({
1429
+ * buffer: Buffer.from("hello"),
1430
+ * name: "hello.txt",
1431
+ * type: "text/plain"
1432
+ * });
1433
+ *
1434
+ * // From text
1435
+ * const file3 = fs.createFile({ text: "Hello!", name: "greeting.txt" });
1436
+ *
1437
+ * // From stream with detection
1438
+ * const stream = createReadStream("/path/to/file.png");
1439
+ * const file4 = fs.createFile({ stream, name: "image.png" });
1440
+ * ```
1441
+ */
1442
+ createFile(options) {
1443
+ if ("path" in options) {
1444
+ const path = options.path;
1445
+ const filename = path.split("/").pop() || "file";
1446
+ return this.createFileFromUrl(`file://${path}`, {
1447
+ type: options.type,
1448
+ name: options.name || filename
1449
+ });
1450
+ }
1451
+ if ("url" in options) return this.createFileFromUrl(options.url, {
1452
+ type: options.type,
1453
+ name: options.name
1454
+ });
1455
+ if ("response" in options) {
1456
+ if (!options.response.body) throw new AlephaError("Response has no body stream");
1457
+ const res = options.response;
1458
+ const sizeHeader = res.headers.get("content-length");
1459
+ const size = sizeHeader ? parseInt(sizeHeader, 10) : void 0;
1460
+ let name = options.name;
1461
+ const contentDisposition = res.headers.get("content-disposition");
1462
+ if (contentDisposition && !name) {
1463
+ const match = contentDisposition.match(/filename="?([^"]+)"?/);
1464
+ if (match) name = match[1];
1465
+ }
1466
+ const type = options.type || res.headers.get("content-type") || void 0;
1467
+ return this.createFileFromStream(options.response.body, {
1468
+ type,
1469
+ name,
1470
+ size
1471
+ });
1472
+ }
1473
+ if ("file" in options) return this.createFileFromWebFile(options.file, {
1474
+ type: options.type,
1475
+ name: options.name,
1476
+ size: options.size
1477
+ });
1478
+ if ("buffer" in options) return this.createFileFromBuffer(options.buffer, {
1479
+ type: options.type,
1480
+ name: options.name
1481
+ });
1482
+ if ("arrayBuffer" in options) return this.createFileFromBuffer(Buffer.from(options.arrayBuffer), {
1483
+ type: options.type,
1484
+ name: options.name
1485
+ });
1486
+ if ("text" in options) return this.createFileFromBuffer(Buffer.from(options.text, "utf-8"), {
1487
+ type: options.type || "text/plain",
1488
+ name: options.name || "file.txt"
1489
+ });
1490
+ if ("stream" in options) return this.createFileFromStream(options.stream, {
1491
+ type: options.type,
1492
+ name: options.name,
1493
+ size: options.size
1494
+ });
1495
+ throw new AlephaError("Invalid createFile options: no valid source provided");
1496
+ }
1497
+ /**
1498
+ * Removes a file or directory.
1499
+ *
1500
+ * @param path - The path to remove
1501
+ * @param options - Remove options
1502
+ *
1503
+ * @example
1504
+ * ```typescript
1505
+ * const fs = alepha.inject(NodeFileSystemProvider);
1506
+ *
1507
+ * // Remove a file
1508
+ * await fs.rm("/tmp/file.txt");
1509
+ *
1510
+ * // Remove a directory recursively
1511
+ * await fs.rm("/tmp/mydir", { recursive: true });
1512
+ *
1513
+ * // Remove with force (no error if doesn't exist)
1514
+ * await fs.rm("/tmp/maybe-exists.txt", { force: true });
1515
+ * ```
1516
+ */
1517
+ async rm(path, options) {
1518
+ await rm(path, options);
1519
+ }
1520
+ /**
1521
+ * Copies a file or directory.
1522
+ *
1523
+ * @param src - Source path
1524
+ * @param dest - Destination path
1525
+ * @param options - Copy options
1526
+ *
1527
+ * @example
1528
+ * ```typescript
1529
+ * const fs = alepha.inject(NodeFileSystemProvider);
1530
+ *
1531
+ * // Copy a file
1532
+ * await fs.cp("/src/file.txt", "/dest/file.txt");
1533
+ *
1534
+ * // Copy a directory recursively
1535
+ * await fs.cp("/src/dir", "/dest/dir", { recursive: true });
1536
+ *
1537
+ * // Copy with force (overwrite existing)
1538
+ * await fs.cp("/src/file.txt", "/dest/file.txt", { force: true });
1539
+ * ```
1540
+ */
1541
+ async cp(src, dest, options) {
1542
+ if ((await stat(src)).isDirectory()) {
1543
+ if (!options?.recursive) throw new Error(`Cannot copy directory without recursive option: ${src}`);
1544
+ await cp(src, dest, {
1545
+ recursive: true,
1546
+ force: options?.force ?? false
1547
+ });
1548
+ } else await copyFile(src, dest);
1549
+ }
1550
+ /**
1551
+ * Moves/renames a file or directory.
1552
+ *
1553
+ * @param src - Source path
1554
+ * @param dest - Destination path
1555
+ *
1556
+ * @example
1557
+ * ```typescript
1558
+ * const fs = alepha.inject(NodeFileSystemProvider);
1559
+ *
1560
+ * // Move/rename a file
1561
+ * await fs.mv("/old/path.txt", "/new/path.txt");
1562
+ *
1563
+ * // Move a directory
1564
+ * await fs.mv("/old/dir", "/new/dir");
1565
+ * ```
1566
+ */
1567
+ async mv(src, dest) {
1568
+ await rename(src, dest);
1569
+ }
1570
+ /**
1571
+ * Creates a directory.
1572
+ *
1573
+ * @param path - The directory path to create
1574
+ * @param options - Mkdir options
1575
+ *
1576
+ * @example
1577
+ * ```typescript
1578
+ * const fs = alepha.inject(NodeFileSystemProvider);
1579
+ *
1580
+ * // Create a directory
1581
+ * await fs.mkdir("/tmp/mydir");
1582
+ *
1583
+ * // Create nested directories
1584
+ * await fs.mkdir("/tmp/path/to/dir", { recursive: true });
1585
+ *
1586
+ * // Create with specific permissions
1587
+ * await fs.mkdir("/tmp/mydir", { mode: 0o755 });
1588
+ * ```
1589
+ */
1590
+ async mkdir(path, options = {}) {
1591
+ const p = mkdir(path, {
1592
+ recursive: options.recursive ?? true,
1593
+ mode: options.mode
1594
+ });
1595
+ if (options.force === false) await p;
1596
+ else await p.catch(() => {});
1597
+ }
1598
+ /**
1599
+ * Lists files in a directory.
1600
+ *
1601
+ * @param path - The directory path to list
1602
+ * @param options - List options
1603
+ * @returns Array of filenames
1604
+ *
1605
+ * @example
1606
+ * ```typescript
1607
+ * const fs = alepha.inject(NodeFileSystemProvider);
1608
+ *
1609
+ * // List files in a directory
1610
+ * const files = await fs.ls("/tmp");
1611
+ * console.log(files); // ["file1.txt", "file2.txt", "subdir"]
1612
+ *
1613
+ * // List with hidden files
1614
+ * const allFiles = await fs.ls("/tmp", { hidden: true });
1615
+ *
1616
+ * // List recursively
1617
+ * const allFilesRecursive = await fs.ls("/tmp", { recursive: true });
1618
+ * ```
1619
+ */
1620
+ async ls(path, options) {
1621
+ const entries = await readdir(path);
1622
+ const filteredEntries = options?.hidden ? entries : entries.filter((e) => !e.startsWith("."));
1623
+ if (options?.recursive) {
1624
+ const allFiles = [];
1625
+ for (const entry of filteredEntries) {
1626
+ const fullPath = join(path, entry);
1627
+ if ((await stat(fullPath)).isDirectory()) {
1628
+ allFiles.push(entry);
1629
+ const subFiles = await this.ls(fullPath, options);
1630
+ allFiles.push(...subFiles.map((f) => join(entry, f)));
1631
+ } else allFiles.push(entry);
1632
+ }
1633
+ return allFiles;
1634
+ }
1635
+ return filteredEntries;
1636
+ }
1637
+ /**
1638
+ * Checks if a file or directory exists.
1639
+ *
1640
+ * @param path - The path to check
1641
+ * @returns True if the path exists, false otherwise
1642
+ *
1643
+ * @example
1644
+ * ```typescript
1645
+ * const fs = alepha.inject(NodeFileSystemProvider);
1646
+ *
1647
+ * if (await fs.exists("/tmp/file.txt")) {
1648
+ * console.log("File exists");
1649
+ * }
1650
+ * ```
1651
+ */
1652
+ async exists(path) {
1653
+ try {
1654
+ await access(path);
1655
+ return true;
1656
+ } catch {
1657
+ return false;
1658
+ }
1659
+ }
1660
+ /**
1661
+ * Reads the content of a file.
1662
+ *
1663
+ * @param path - The file path to read
1664
+ * @returns The file content as a Buffer
1665
+ *
1666
+ * @example
1667
+ * ```typescript
1668
+ * const fs = alepha.inject(NodeFileSystemProvider);
1669
+ *
1670
+ * const buffer = await fs.readFile("/tmp/file.txt");
1671
+ * console.log(buffer.toString("utf-8"));
1672
+ * ```
1673
+ */
1674
+ async readFile(path) {
1675
+ return await readFile(path);
1676
+ }
1677
+ /**
1678
+ * Writes data to a file.
1679
+ *
1680
+ * @param path - The file path to write to
1681
+ * @param data - The data to write (Buffer or string)
1682
+ *
1683
+ * @example
1684
+ * ```typescript
1685
+ * const fs = alepha.inject(NodeFileSystemProvider);
1686
+ *
1687
+ * // Write string
1688
+ * await fs.writeFile("/tmp/file.txt", "Hello, world!");
1689
+ *
1690
+ * // Write Buffer
1691
+ * await fs.writeFile("/tmp/file.bin", Buffer.from([0x01, 0x02, 0x03]));
1692
+ * ```
1693
+ */
1694
+ async writeFile(path, data) {
1695
+ if (isFileLike(data)) {
1696
+ await writeFile(path, Readable.from(data.stream()));
1697
+ return;
1698
+ }
1699
+ await writeFile(path, data);
1700
+ }
1701
+ /**
1702
+ * Reads the content of a file as a string.
1703
+ *
1704
+ * @param path - The file path to read
1705
+ * @returns The file content as a string
1706
+ *
1707
+ * @example
1708
+ * ```typescript
1709
+ * const fs = alepha.inject(NodeFileSystemProvider);
1710
+ * const content = await fs.readTextFile("/tmp/file.txt");
1711
+ * ```
1712
+ */
1713
+ async readTextFile(path) {
1714
+ return (await this.readFile(path)).toString("utf-8");
1715
+ }
1716
+ /**
1717
+ * Reads the content of a file as JSON.
1718
+ *
1719
+ * @param path - The file path to read
1720
+ * @returns The parsed JSON content
1721
+ *
1722
+ * @example
1723
+ * ```typescript
1724
+ * const fs = alepha.inject(NodeFileSystemProvider);
1725
+ * const config = await fs.readJsonFile<{ name: string }>("/tmp/config.json");
1726
+ * ```
1727
+ */
1728
+ async readJsonFile(path) {
1729
+ const text = await this.readTextFile(path);
1730
+ return this.json.parse(text);
1731
+ }
1732
+ /**
1733
+ * Creates a FileLike object from a Web File.
1734
+ *
1735
+ * @protected
1736
+ */
1737
+ createFileFromWebFile(source, options = {}) {
1738
+ const name = options.name ?? source.name;
1739
+ return {
1740
+ name,
1741
+ type: options.type ?? (source.type || this.detector.getContentType(name)),
1742
+ size: options.size ?? source.size ?? 0,
1743
+ lastModified: source.lastModified || Date.now(),
1744
+ stream: () => source.stream(),
1745
+ arrayBuffer: async () => {
1746
+ return await source.arrayBuffer();
1747
+ },
1748
+ text: async () => {
1749
+ return await source.text();
1750
+ }
1751
+ };
1752
+ }
1753
+ /**
1754
+ * Creates a FileLike object from a Buffer.
1755
+ *
1756
+ * @protected
1757
+ */
1758
+ createFileFromBuffer(source, options = {}) {
1759
+ const name = options.name ?? "file";
1760
+ return {
1761
+ name,
1762
+ type: options.type ?? this.detector.getContentType(options.name ?? name),
1763
+ size: source.byteLength,
1764
+ lastModified: Date.now(),
1765
+ stream: () => Readable.from(source),
1766
+ arrayBuffer: async () => {
1767
+ return this.bufferToArrayBuffer(source);
1768
+ },
1769
+ text: async () => {
1770
+ return source.toString("utf-8");
1771
+ }
1772
+ };
1773
+ }
1774
+ /**
1775
+ * Creates a FileLike object from a stream.
1776
+ *
1777
+ * @protected
1778
+ */
1779
+ createFileFromStream(source, options = {}) {
1780
+ let buffer = null;
1781
+ return {
1782
+ name: options.name ?? "file",
1783
+ type: options.type ?? this.detector.getContentType(options.name ?? "file"),
1784
+ size: options.size ?? 0,
1785
+ lastModified: Date.now(),
1786
+ stream: () => source,
1787
+ _buffer: null,
1788
+ arrayBuffer: async () => {
1789
+ buffer ??= await this.streamToBuffer(source);
1790
+ return this.bufferToArrayBuffer(buffer);
1791
+ },
1792
+ text: async () => {
1793
+ buffer ??= await this.streamToBuffer(source);
1794
+ return buffer.toString("utf-8");
1795
+ }
1796
+ };
1797
+ }
1798
+ /**
1799
+ * Creates a FileLike object from a URL.
1800
+ *
1801
+ * @protected
1802
+ */
1803
+ createFileFromUrl(url, options = {}) {
1804
+ const parsedUrl = new URL(url);
1805
+ const filename = options.name || parsedUrl.pathname.split("/").pop() || "file";
1806
+ let buffer = null;
1807
+ return {
1808
+ name: filename,
1809
+ type: options.type ?? this.detector.getContentType(filename),
1810
+ size: 0,
1811
+ lastModified: Date.now(),
1812
+ stream: () => this.createStreamFromUrl(url),
1813
+ arrayBuffer: async () => {
1814
+ buffer ??= await this.loadFromUrl(url);
1815
+ return this.bufferToArrayBuffer(buffer);
1816
+ },
1817
+ text: async () => {
1818
+ buffer ??= await this.loadFromUrl(url);
1819
+ return buffer.toString("utf-8");
1820
+ },
1821
+ filepath: url
1822
+ };
1823
+ }
1824
+ /**
1825
+ * Gets a streaming response from a URL.
1826
+ *
1827
+ * @protected
1828
+ */
1829
+ getStreamingResponse(url) {
1830
+ const stream = new PassThrough();
1831
+ fetch(url).then((res) => Readable.fromWeb(res.body).pipe(stream)).catch((err) => stream.destroy(err));
1832
+ return stream;
1833
+ }
1834
+ /**
1835
+ * Loads data from a URL.
1836
+ *
1837
+ * @protected
1838
+ */
1839
+ async loadFromUrl(url) {
1840
+ const parsedUrl = new URL(url);
1841
+ if (parsedUrl.protocol === "file:") return await readFile(fileURLToPath(url));
1842
+ else if (parsedUrl.protocol === "http:" || parsedUrl.protocol === "https:") {
1843
+ const response = await fetch(url);
1844
+ if (!response.ok) throw new Error(`Failed to fetch ${url}: ${response.status} ${response.statusText}`);
1845
+ const arrayBuffer = await response.arrayBuffer();
1846
+ return Buffer.from(arrayBuffer);
1847
+ } else throw new Error(`Unsupported protocol: ${parsedUrl.protocol}`);
1848
+ }
1849
+ /**
1850
+ * Creates a stream from a URL.
1851
+ *
1852
+ * @protected
1853
+ */
1854
+ createStreamFromUrl(url) {
1855
+ const parsedUrl = new URL(url);
1856
+ if (parsedUrl.protocol === "file:") return createReadStream(fileURLToPath(url));
1857
+ else if (parsedUrl.protocol === "http:" || parsedUrl.protocol === "https:") return this.getStreamingResponse(url);
1858
+ else throw new AlephaError(`Unsupported protocol: ${parsedUrl.protocol}`);
1859
+ }
1860
+ /**
1861
+ * Converts a stream-like object to a Buffer.
1862
+ *
1863
+ * @protected
1864
+ */
1865
+ async streamToBuffer(streamLike) {
1866
+ const stream = streamLike instanceof Readable ? streamLike : Readable.fromWeb(streamLike);
1867
+ return new Promise((resolve, reject) => {
1868
+ const buffer = [];
1869
+ stream.on("data", (chunk) => buffer.push(Buffer.from(chunk)));
1870
+ stream.on("end", () => resolve(Buffer.concat(buffer)));
1871
+ stream.on("error", (err) => reject(new AlephaError("Error converting stream", { cause: err })));
1872
+ });
1873
+ }
1874
+ /**
1875
+ * Converts a Node.js Buffer to an ArrayBuffer.
1876
+ *
1877
+ * @protected
1878
+ */
1879
+ bufferToArrayBuffer(buffer) {
1880
+ return buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
1881
+ }
1882
+ };
1883
+
1884
+ //#endregion
1885
+ //#region ../../src/system/providers/NodeShellProvider.ts
1886
+ /**
1887
+ * Node.js implementation of ShellProvider.
1888
+ *
1889
+ * Executes shell commands using Node.js child_process module.
1890
+ * Supports binary resolution from node_modules/.bin for local packages.
1891
+ */
1892
+ var NodeShellProvider = class {
1893
+ log = $logger();
1894
+ fs = $inject(FileSystemProvider);
1895
+ /**
1896
+ * Run a shell command or binary.
1897
+ */
1898
+ async run(command, options = {}) {
1899
+ const { resolve = false, capture = false, root, env } = options;
1900
+ const cwd = root ?? process.cwd();
1901
+ this.log.debug(`Shell: ${command}`, {
1902
+ cwd,
1903
+ resolve,
1904
+ capture
1905
+ });
1906
+ let executable;
1907
+ let args;
1908
+ if (resolve) {
1909
+ const [bin, ...rest] = command.split(" ");
1910
+ executable = await this.resolveExecutable(bin, cwd);
1911
+ args = rest;
1912
+ } else [executable, ...args] = command.split(" ");
1913
+ if (capture) return this.execCapture(command, {
1914
+ cwd,
1915
+ env
1916
+ });
1917
+ return this.execInherit(executable, args, {
1918
+ cwd,
1919
+ env
1920
+ });
1921
+ }
1922
+ /**
1923
+ * Execute command with inherited stdio (streams to terminal).
1924
+ */
1925
+ async execInherit(executable, args, options) {
1926
+ const proc = spawn(executable, args, {
1927
+ stdio: "inherit",
1928
+ cwd: options.cwd,
1929
+ env: {
1930
+ ...process.env,
1931
+ ...options.env
1932
+ }
1933
+ });
1934
+ return new Promise((resolve, reject) => {
1935
+ proc.on("exit", (code) => {
1936
+ if (code === 0 || code === null) resolve("");
1937
+ else reject(new AlephaError(`Command exited with code ${code}`));
1938
+ });
1939
+ proc.on("error", reject);
1940
+ });
1941
+ }
1942
+ /**
1943
+ * Execute command and capture stdout.
1944
+ */
1945
+ execCapture(command, options) {
1946
+ return new Promise((resolve, reject) => {
1947
+ exec(command, {
1948
+ cwd: options.cwd,
1949
+ env: {
1950
+ ...process.env,
1951
+ LOG_FORMAT: "pretty",
1952
+ ...options.env
1953
+ }
1954
+ }, (err, stdout) => {
1955
+ if (err) {
1956
+ err.stdout = stdout;
1957
+ reject(err);
1958
+ } else resolve(stdout);
1959
+ });
1960
+ });
1961
+ }
1962
+ /**
1963
+ * Resolve executable path from node_modules/.bin.
1964
+ *
1965
+ * Search order:
1966
+ * 1. Local: node_modules/.bin/
1967
+ * 2. Pnpm nested: node_modules/alepha/node_modules/.bin/
1968
+ * 3. Monorepo: Walk up to 3 parent directories
1969
+ */
1970
+ async resolveExecutable(name, root) {
1971
+ const suffix = process.platform === "win32" ? ".cmd" : "";
1972
+ let execPath = await this.findExecutable(root, `node_modules/.bin/${name}${suffix}`);
1973
+ if (!execPath) execPath = await this.findExecutable(root, `node_modules/alepha/node_modules/.bin/${name}${suffix}`);
1974
+ if (!execPath) {
1975
+ let parentDir = this.fs.join(root, "..");
1976
+ for (let i = 0; i < 3; i++) {
1977
+ execPath = await this.findExecutable(parentDir, `node_modules/.bin/${name}${suffix}`);
1978
+ if (execPath) break;
1979
+ parentDir = this.fs.join(parentDir, "..");
1980
+ }
1981
+ }
1982
+ if (!execPath) throw new AlephaError(`Could not find executable for '${name}'. Make sure the package is installed.`);
1983
+ return execPath;
1984
+ }
1985
+ /**
1986
+ * Check if executable exists at path.
1987
+ */
1988
+ async findExecutable(root, relativePath) {
1989
+ const fullPath = this.fs.join(root, relativePath);
1990
+ if (await this.fs.exists(fullPath)) return fullPath;
1991
+ }
1992
+ /**
1993
+ * Check if a command is installed and available in the system PATH.
1994
+ */
1995
+ isInstalled(command) {
1996
+ return new Promise((resolve) => {
1997
+ exec(process.platform === "win32" ? `where ${command}` : `command -v ${command}`, (error) => resolve(!error));
1998
+ });
1999
+ }
2000
+ };
2001
+
2002
+ //#endregion
2003
+ //#region ../../src/system/providers/ShellProvider.ts
2004
+ /**
2005
+ * Abstract provider for executing shell commands and binaries.
2006
+ *
2007
+ * Implementations:
2008
+ * - `NodeShellProvider` - Real shell execution using Node.js child_process
2009
+ * - `MemoryShellProvider` - In-memory mock for testing
2010
+ *
2011
+ * @example
2012
+ * ```typescript
2013
+ * class MyService {
2014
+ * protected readonly shell = $inject(ShellProvider);
2015
+ *
2016
+ * async build() {
2017
+ * // Run shell command directly
2018
+ * await this.shell.run("yarn install");
2019
+ *
2020
+ * // Run local binary with resolution
2021
+ * await this.shell.run("vite build", { resolve: true });
2022
+ *
2023
+ * // Capture output
2024
+ * const output = await this.shell.run("echo hello", { capture: true });
2025
+ * }
2026
+ * }
2027
+ * ```
2028
+ */
2029
+ var ShellProvider = class {};
2030
+
2031
+ //#endregion
2032
+ //#region ../../src/system/index.ts
2033
+ /**
2034
+ * | type | quality | stability |
2035
+ * |------|---------|-----------|
2036
+ * | tooling | standard | stable |
2037
+ *
2038
+ * System-level abstractions for portable code across runtimes.
2039
+ *
2040
+ * **Features:**
2041
+ * - File system operations (read, write, exists, etc.)
2042
+ * - Shell command execution
2043
+ * - File type detection and MIME utilities
2044
+ * - Memory implementations for testing
2045
+ *
2046
+ * @module alepha.system
2047
+ */
2048
+ const AlephaSystem = $module({
2049
+ name: "alepha.system",
2050
+ primitives: [],
2051
+ services: [
2052
+ FileDetector,
2053
+ FileSystemProvider,
2054
+ MemoryFileSystemProvider,
2055
+ NodeFileSystemProvider,
2056
+ ShellProvider,
2057
+ MemoryShellProvider,
2058
+ NodeShellProvider
2059
+ ],
2060
+ register: (alepha) => alepha.with({
2061
+ optional: true,
2062
+ provide: FileSystemProvider,
2063
+ use: NodeFileSystemProvider
2064
+ }).with({
2065
+ optional: true,
2066
+ provide: ShellProvider,
2067
+ use: alepha.isTest() ? MemoryShellProvider : NodeShellProvider
2068
+ })
2069
+ });
2070
+
228
2071
  //#endregion
229
2072
  //#region ../../src/command/errors/CommandError.ts
230
2073
  var CommandError = class extends AlephaError {
@@ -239,10 +2082,12 @@ var Runner = class {
239
2082
  startTime = Date.now();
240
2083
  prettyPrint = $inject(PrettyPrint);
241
2084
  alepha = $inject(Alepha);
2085
+ shell = $inject(ShellProvider);
242
2086
  run;
243
2087
  cliName = "";
244
2088
  commandName = "";
245
2089
  firstTaskStarted = false;
2090
+ taskCounter = 0;
246
2091
  constructor() {
247
2092
  this.run = this.createRunMethod();
248
2093
  }
@@ -256,6 +2101,8 @@ var Runner = class {
256
2101
  startCommand(cliName, commandName) {
257
2102
  this.cliName = cliName;
258
2103
  this.commandName = commandName;
2104
+ this.firstTaskStarted = false;
2105
+ this.taskCounter = 0;
259
2106
  }
260
2107
  createRunMethod() {
261
2108
  const runFn = async (cmd, options) => {
@@ -305,19 +2152,9 @@ var Runner = class {
305
2152
  return runFn;
306
2153
  }
307
2154
  async exec(cmd, opts = {}) {
308
- return await new Promise((resolve, reject) => {
309
- exec(cmd, {
310
- cwd: opts.root,
311
- env: {
312
- ...process.env,
313
- LOG_FORMAT: "pretty"
314
- }
315
- }, (err, stdout) => {
316
- if (err) {
317
- err.stdout = stdout;
318
- reject(err);
319
- } else resolve(stdout);
320
- });
2155
+ return this.shell.run(cmd, {
2156
+ root: opts.root,
2157
+ capture: true
321
2158
  });
322
2159
  }
323
2160
  /**
@@ -347,7 +2184,7 @@ var Runner = class {
347
2184
  }
348
2185
  async executeTask(task) {
349
2186
  const now = Date.now();
350
- const taskId = task.name;
2187
+ const taskId = `task-${++this.taskCounter}`;
351
2188
  if (this.useDynamicLogger) this.prettyPrint.startSpinner(taskId, task.name);
352
2189
  else this.log.info(`Starting '${task.name}' ...`);
353
2190
  let stdout = "";
@@ -494,6 +2331,32 @@ const cliOptions = $atom({
494
2331
  }),
495
2332
  default: {}
496
2333
  });
2334
+ /**
2335
+ * CLI provider for parsing and executing commands.
2336
+ *
2337
+ * Handles:
2338
+ * - Command resolution (simple, nested, colon-notation)
2339
+ * - Flag and argument parsing
2340
+ * - Environment variable validation
2341
+ * - Help generation
2342
+ * - Pre/post command hooks
2343
+ *
2344
+ * @example
2345
+ * ```typescript
2346
+ * // Define a command
2347
+ * class MyCommands {
2348
+ * build = $command({
2349
+ * name: "build",
2350
+ * description: "Build the project",
2351
+ * flags: t.object({ watch: t.optional(t.boolean()) }),
2352
+ * handler: async ({ flags }) => { ... }
2353
+ * });
2354
+ * }
2355
+ *
2356
+ * // CLI automatically discovers and executes commands
2357
+ * const alepha = Alepha.create().with(MyCommands);
2358
+ * ```
2359
+ */
497
2360
  var CliProvider = class {
498
2361
  env = $env(envSchema);
499
2362
  alepha = $inject(Alepha);
@@ -512,11 +2375,18 @@ var CliProvider = class {
512
2375
  get argv() {
513
2376
  return this.options.argv || (typeof process !== "undefined" ? process.argv.slice(2) : []);
514
2377
  }
2378
+ /**
2379
+ * Global flags available to all commands.
2380
+ */
515
2381
  globalFlags = { help: {
516
2382
  aliases: ["h", "help"],
517
2383
  description: "Show this help message",
518
2384
  schema: t.boolean()
519
2385
  } };
2386
+ /**
2387
+ * Main entry point - resolves and executes the command from process.argv.
2388
+ * This is the production execution path with full lifecycle support.
2389
+ */
520
2390
  onReady = $hook({
521
2391
  on: "ready",
522
2392
  handler: async () => {
@@ -526,7 +2396,7 @@ var CliProvider = class {
526
2396
  if (this.parseFlags(argv, Object.entries(this.getAllGlobalFlags()).map(([key, value]) => ({
527
2397
  key,
528
2398
  ...value
529
- }))).help) {
2399
+ })), { strict: false }).help) {
530
2400
  this.printHelp(command);
531
2401
  return;
532
2402
  }
@@ -549,7 +2419,15 @@ var CliProvider = class {
549
2419
  }
550
2420
  });
551
2421
  /**
552
- * Execute a command with the given argv.
2422
+ * Execute a command with full lifecycle support.
2423
+ *
2424
+ * This is the production execution path that includes:
2425
+ * - Mode-based .env file loading
2426
+ * - Pre/post command hooks
2427
+ * - Runner session for pretty CLI output
2428
+ * - Alepha context wrapper for proper scoping
2429
+ *
2430
+ * @see run() for a lightweight test-only alternative
553
2431
  */
554
2432
  async executeCommand(command, argv, isRootCommand) {
555
2433
  const root = process.cwd();
@@ -559,7 +2437,7 @@ var CliProvider = class {
559
2437
  if (modeValue === void 0 && typeof command.options.mode === "string") modeValue = command.options.mode;
560
2438
  await this.loadModeEnv(root, modeValue);
561
2439
  }
562
- const commandFlags = this.parseCommandFlags(argv, command.flags);
2440
+ const commandFlags = this.parseCommandFlags(argv, command.flags, { modeEnabled: !!command.options.mode });
563
2441
  const commandArgs = this.parseCommandArgs(argv, command.options.args, isRootCommand, command.flags);
564
2442
  const commandEnv = this.parseCommandEnv(command.env, command.name);
565
2443
  await this.alepha.context.run(async () => {
@@ -598,7 +2476,7 @@ var CliProvider = class {
598
2476
  });
599
2477
  }
600
2478
  /**
601
- * Remove consumed command path arguments from argv.
2479
+ * Remove consumed command path arguments from argv (keeps flags and remaining args).
602
2480
  */
603
2481
  removeConsumedArgs(argv, consumedArgs) {
604
2482
  const result = [];
@@ -650,38 +2528,94 @@ var CliProvider = class {
650
2528
  consumedArgs
651
2529
  };
652
2530
  }
2531
+ /**
2532
+ * Get all registered commands in the application.
2533
+ */
653
2534
  get commands() {
654
2535
  return this.alepha.primitives($command);
655
2536
  }
2537
+ /**
2538
+ * Execute a command handler with given arguments.
2539
+ *
2540
+ * This is a **lightweight test helper** that directly invokes the command handler
2541
+ * without the full production lifecycle. It intentionally skips:
2542
+ * - Pre/post command hooks
2543
+ * - Runner session (pretty CLI output)
2544
+ * - Alepha context wrapper
2545
+ * - .env.{mode} file loading
2546
+ *
2547
+ * For production execution, the `onReady` hook uses `executeCommand()` which
2548
+ * provides the full lifecycle. Merging them would either make this method too
2549
+ * heavy for simple testing or require many optional parameters to toggle behaviors.
2550
+ *
2551
+ * @example
2552
+ * ```typescript
2553
+ * // In tests
2554
+ * const cli = alepha.inject(CliProvider);
2555
+ * const cmd = alepha.inject(InitCommand);
2556
+ *
2557
+ * await cli.run(cmd.init, "--agent --pm=yarn");
2558
+ * await cli.run(cmd.init, { argv: "--agent", root: "/project" });
2559
+ * ```
2560
+ */
2561
+ async run(command, options = {}) {
2562
+ const opts = typeof options === "string" || Array.isArray(options) ? { argv: options } : options;
2563
+ const args = typeof opts.argv === "string" ? opts.argv.split(" ").filter(Boolean) : opts.argv ?? [];
2564
+ const root = opts.root ?? process.cwd();
2565
+ const commandFlags = this.parseCommandFlags(args, command.flags, { modeEnabled: !!command.options.mode });
2566
+ const commandArgs = this.parseCommandArgs(args, command.options.args, true, command.flags);
2567
+ const commandEnv = this.parseCommandEnv(command.env, command.name);
2568
+ let modeValue;
2569
+ if (command.options.mode) {
2570
+ modeValue = this.parseModeFlag(args);
2571
+ if (modeValue === void 0 && typeof command.options.mode === "string") modeValue = command.options.mode;
2572
+ }
2573
+ await command.options.handler({
2574
+ flags: commandFlags,
2575
+ args: commandArgs,
2576
+ env: commandEnv,
2577
+ run: this.runner.run,
2578
+ ask: this.asker.ask,
2579
+ fs,
2580
+ glob,
2581
+ root,
2582
+ help: () => this.printHelp(command),
2583
+ mode: modeValue
2584
+ });
2585
+ }
2586
+ /** Find a command by name or alias */
656
2587
  findCommand(name) {
657
2588
  return this.commands.findLast((command) => command.name === name || command.aliases.includes(name));
658
2589
  }
659
- /**
660
- * Find all pre-hooks for a command.
661
- */
2590
+ /** Find all pre-hooks for a command (commands named `pre{commandName}`) */
662
2591
  findPreHooks(commandName) {
663
2592
  return this.commands.filter((cmd) => cmd.name === `pre${commandName}`);
664
2593
  }
665
- /**
666
- * Find all post-hooks for a command.
667
- */
2594
+ /** Find all post-hooks for a command (commands named `post{commandName}`) */
668
2595
  findPostHooks(commandName) {
669
2596
  return this.commands.filter((cmd) => cmd.name === `post${commandName}`);
670
2597
  }
671
- /**
672
- * Get global flags (help only, root command flags are NOT global).
673
- */
2598
+ /** Get global flags (help only, root command flags are NOT global) */
674
2599
  getAllGlobalFlags() {
675
2600
  return { ...this.globalFlags };
676
2601
  }
677
- parseCommandFlags(argv, schema) {
2602
+ /** Parse command flags from argv using the command's flag schema */
2603
+ parseCommandFlags(argv, schema, options = {}) {
2604
+ const { modeEnabled = false } = options;
678
2605
  const flagDefs = Object.entries(schema.properties).map(([key, value]) => ({
679
2606
  key,
680
2607
  aliases: [key, ...value.aliases ?? (value.alias ? [value.alias] : void 0) ?? []],
681
2608
  description: value.description,
682
2609
  schema: value
683
2610
  }));
2611
+ if (modeEnabled) flagDefs.push({
2612
+ key: "__mode__",
2613
+ aliases: ["mode", "m"],
2614
+ description: void 0,
2615
+ schema: t.string()
2616
+ });
684
2617
  const parsed = this.parseFlags(argv, flagDefs);
2618
+ delete parsed.__mode__;
685
2619
  for (const [key, value] of Object.entries(schema.properties)) if (!(key in parsed) && t.schema.isOptional(value)) {
686
2620
  const innerSchema = value;
687
2621
  if (innerSchema && "default" in innerSchema) parsed[key] = innerSchema.default;
@@ -693,6 +2627,7 @@ var CliProvider = class {
693
2627
  throw error;
694
2628
  }
695
2629
  }
2630
+ /** Parse and validate environment variables using the command's env schema */
696
2631
  parseCommandEnv(schema, commandName) {
697
2632
  const result = {};
698
2633
  const missing = [];
@@ -714,9 +2649,7 @@ var CliProvider = class {
714
2649
  throw error;
715
2650
  }
716
2651
  }
717
- /**
718
- * Parse --mode or -m flag from argv.
719
- */
2652
+ /** Parse --mode or -m flag from argv for environment file loading */
720
2653
  parseModeFlag(argv) {
721
2654
  for (let i = 0; i < argv.length; i++) {
722
2655
  const arg = argv[i];
@@ -728,16 +2661,16 @@ var CliProvider = class {
728
2661
  }
729
2662
  }
730
2663
  }
731
- /**
732
- * Load environment files based on mode.
733
- */
2664
+ /** Load .env and .env.{mode} files into process.env */
734
2665
  async loadModeEnv(root, mode) {
735
2666
  const envFiles = [".env"];
736
2667
  if (mode) envFiles.push(`.env.${mode}`);
737
2668
  this.log.debug(`Loading env files: ${envFiles.join(", ")}`);
738
2669
  await this.envUtils.loadEnv(root, envFiles);
739
2670
  }
740
- parseFlags(argv, flagDefs) {
2671
+ /** Low-level flag parser - extracts flag values from argv based on definitions */
2672
+ parseFlags(argv, flagDefs, options = {}) {
2673
+ const { strict = true } = options;
741
2674
  const result = {};
742
2675
  for (let i = 0; i < argv.length; i++) {
743
2676
  const arg = argv[i];
@@ -745,9 +2678,19 @@ var CliProvider = class {
745
2678
  const [rawKey, ...valueParts] = arg.replace(/^-{1,2}/, "").split("=");
746
2679
  let value = valueParts.join("=");
747
2680
  const def = flagDefs.find((d) => d.aliases.includes(rawKey));
748
- if (!def) continue;
2681
+ if (!def) {
2682
+ if (strict) throw new CommandError(`Unknown flag: --${rawKey}`);
2683
+ continue;
2684
+ }
2685
+ const isUnionWithBoolean = t.schema.isUnion(def.schema) && def.schema.anyOf.some((s) => t.schema.isBoolean(s));
749
2686
  if (t.schema.isBoolean(def.schema)) result[def.key] = true;
750
- else if (value) try {
2687
+ else if (isUnionWithBoolean && !value) {
2688
+ const nextArg = argv[i + 1];
2689
+ if (nextArg && !nextArg.startsWith("-")) {
2690
+ result[def.key] = nextArg;
2691
+ i++;
2692
+ } else result[def.key] = true;
2693
+ } else if (value) try {
751
2694
  if (t.schema.isObject(def.schema) || t.schema.isArray(def.schema)) result[def.key] = JSON.parse(value);
752
2695
  else result[def.key] = value;
753
2696
  } catch {
@@ -768,9 +2711,7 @@ var CliProvider = class {
768
2711
  }
769
2712
  return result;
770
2713
  }
771
- /**
772
- * Get indices of argv elements that are consumed by flags (including space-separated values).
773
- */
2714
+ /** Get indices of argv elements consumed by flags (for separating args from flags) */
774
2715
  getFlagConsumedIndices(argv, flagDefs) {
775
2716
  const consumed = /* @__PURE__ */ new Set();
776
2717
  for (let i = 0; i < argv.length; i++) {
@@ -781,7 +2722,11 @@ var CliProvider = class {
781
2722
  const hasEqualValue = valueParts.length > 0;
782
2723
  const def = flagDefs.find((d) => d.aliases.includes(rawKey));
783
2724
  if (!def) continue;
784
- if (!t.schema.isBoolean(def.schema) && !hasEqualValue) {
2725
+ const isUnionWithBoolean = t.schema.isUnion(def.schema) && def.schema.anyOf.some((s) => t.schema.isBoolean(s));
2726
+ if (!t.schema.isBoolean(def.schema) && !isUnionWithBoolean && !hasEqualValue) {
2727
+ const nextArg = argv[i + 1];
2728
+ if (nextArg && !nextArg.startsWith("-")) consumed.add(i + 1);
2729
+ } else if (isUnionWithBoolean && !hasEqualValue) {
785
2730
  const nextArg = argv[i + 1];
786
2731
  if (nextArg && !nextArg.startsWith("-")) consumed.add(i + 1);
787
2732
  }
@@ -821,6 +2766,7 @@ var CliProvider = class {
821
2766
  throw error;
822
2767
  }
823
2768
  }
2769
+ /** Convert a string argument value to the appropriate type based on schema */
824
2770
  parseArgumentValue(value, schema) {
825
2771
  if (t.schema.isString(schema)) return value;
826
2772
  if (t.schema.isNumber(schema) || t.schema.isInteger(schema)) {
@@ -837,6 +2783,7 @@ var CliProvider = class {
837
2783
  }
838
2784
  return value;
839
2785
  }
2786
+ /** Generate usage string for command arguments (e.g., "<path>" or "[path]") */
840
2787
  generateArgsUsage(schema) {
841
2788
  if (!schema) return "";
842
2789
  if (t.schema.isOptional(schema)) {
@@ -852,6 +2799,7 @@ var CliProvider = class {
852
2799
  const typeName = this.getTypeName(schema);
853
2800
  return ` <${"title" in schema ? schema.title : "arg1"}${typeName}>`;
854
2801
  }
2802
+ /** Get display type name for a schema (e.g., ": number", ": boolean") */
855
2803
  getTypeName(schema) {
856
2804
  if (!schema) return "";
857
2805
  if (t.schema.isString(schema)) return "";
@@ -860,6 +2808,12 @@ var CliProvider = class {
860
2808
  if (t.schema.isBoolean(schema)) return ": boolean";
861
2809
  return "";
862
2810
  }
2811
+ /**
2812
+ * Print help for a specific command or general CLI help.
2813
+ *
2814
+ * @param command - If provided, shows help for this specific command.
2815
+ * If omitted, shows general CLI help with all commands.
2816
+ */
863
2817
  printHelp(command) {
864
2818
  const cliName = this.name || "cli";
865
2819
  const c = this.color;
@@ -893,13 +2847,14 @@ var CliProvider = class {
893
2847
  ...Object.entries(command.flags.properties).map(([key, value]) => ({
894
2848
  key,
895
2849
  schema: value,
896
- aliases: value.alias ?? [key],
2850
+ aliases: [key, ...value.aliases ?? (value.alias ? [value.alias] : [])],
897
2851
  description: value.description
898
2852
  })),
899
2853
  ...command.options.mode ? [{
900
2854
  key: "mode",
901
2855
  aliases: ["m", "mode"],
902
- description: typeof command.options.mode === "string" ? `Environment mode - loads .env.{mode} (default: ${command.options.mode})` : "Environment mode (e.g., production, staging) - loads .env.{mode}"
2856
+ description: typeof command.options.mode === "string" ? `Environment mode - loads .env.{mode} (default: ${command.options.mode})` : "Environment mode (e.g., production, staging) - loads .env.{mode}",
2857
+ schema: t.string()
903
2858
  }] : [],
904
2859
  ...Object.entries(this.getAllGlobalFlags()).map(([key, value]) => ({
905
2860
  key,
@@ -907,11 +2862,14 @@ var CliProvider = class {
907
2862
  }))
908
2863
  ];
909
2864
  const maxFlagLength = this.getMaxFlagLength(flags);
910
- for (const { aliases, description } of flags) {
911
- const flagStr = (Array.isArray(aliases) ? aliases : [aliases]).map((a) => a.length === 1 ? `-${a}` : `--${a}`).join(", ");
2865
+ for (const flag of flags) {
2866
+ const { aliases, description } = flag;
2867
+ const schema = "schema" in flag ? flag.schema : void 0;
2868
+ const flagStr = (Array.isArray(aliases) ? aliases : [aliases]).slice().sort((a, b) => a.length - b.length).map((a) => a.length === 1 ? `-${a}` : `--${a}`).join(", ");
912
2869
  const coloredFlag = c.set("GREY_LIGHT", flagStr);
913
2870
  const padding = " ".repeat(Math.max(0, maxFlagLength - flagStr.length));
914
- this.log.info(` ${coloredFlag}${padding} ${description ?? ""}`);
2871
+ const formattedDesc = this.formatFlagDescription(description, schema);
2872
+ this.log.info(` ${coloredFlag}${padding} ${formattedDesc}`);
915
2873
  }
916
2874
  const envVars = Object.entries(command.env.properties);
917
2875
  if (envVars.length > 0) {
@@ -946,21 +2904,21 @@ var CliProvider = class {
946
2904
  const globalFlags = [...rootCommand ? Object.entries(rootCommand.flags.properties).map(([key, value]) => ({
947
2905
  key,
948
2906
  aliases: [key, ...value.aliases ?? (value.alias ? [value.alias] : void 0) ?? []],
949
- description: value.description
2907
+ description: value.description,
2908
+ schema: value
950
2909
  })) : [], ...Object.values(this.getAllGlobalFlags())];
951
2910
  const maxFlagLength = this.getMaxFlagLength(globalFlags);
952
- for (const { aliases, description } of globalFlags) {
2911
+ for (const { aliases, description, schema } of globalFlags) {
953
2912
  const flagStr = aliases.map((a) => a.length === 1 ? `-${a}` : `--${a}`).join(", ");
954
2913
  const coloredFlag = c.set("GREY_LIGHT", flagStr);
955
2914
  const padding = " ".repeat(Math.max(0, maxFlagLength - flagStr.length));
956
- this.log.info(` ${coloredFlag}${padding} ${description ?? ""}`);
2915
+ const formattedDesc = this.formatFlagDescription(description, schema);
2916
+ this.log.info(` ${coloredFlag}${padding} ${formattedDesc}`);
957
2917
  }
958
2918
  }
959
2919
  this.log.info("");
960
2920
  }
961
- /**
962
- * Generate colored args usage string for help display.
963
- */
2921
+ /** Generate colored usage string for command arguments (for help display) */
964
2922
  generateColoredArgsUsage(schema) {
965
2923
  if (!schema) return "";
966
2924
  const c = this.color;
@@ -979,9 +2937,7 @@ var CliProvider = class {
979
2937
  const key = "title" in schema ? schema.title : "arg1";
980
2938
  return ` ${c.set("CYAN", `<${key}${typeName}>`)}`;
981
2939
  }
982
- /**
983
- * Get the full command path (e.g., "deploy vercel" for a child command).
984
- */
2940
+ /** Get the full command path (e.g., "deploy vercel" for a nested command) */
985
2941
  getCommandPath(command) {
986
2942
  const path = [command.name];
987
2943
  let current = command;
@@ -993,49 +2949,82 @@ var CliProvider = class {
993
2949
  }
994
2950
  return path.join(" ");
995
2951
  }
996
- /**
997
- * Find the parent command of a given command.
998
- */
2952
+ /** Find the parent command of a nested command */
999
2953
  findParentCommand(command) {
1000
2954
  for (const cmd of this.commands) if (cmd.children.includes(command)) return cmd;
1001
2955
  }
1002
- /**
1003
- * Get top-level commands (commands that are not children of other commands).
1004
- */
2956
+ /** Get top-level commands (commands that are not children of other commands) */
1005
2957
  getTopLevelCommands() {
1006
2958
  const allChildren = /* @__PURE__ */ new Set();
1007
2959
  for (const command of this.commands) for (const child of command.children) allChildren.add(child);
1008
2960
  return this.commands.filter((cmd) => !allChildren.has(cmd));
1009
2961
  }
1010
- /**
1011
- * Get max length for child command display.
1012
- */
2962
+ /** Calculate max display length for child commands (for help alignment) */
1013
2963
  getMaxChildCmdLength(children) {
1014
2964
  return Math.max(...children.filter((c) => !c.options.hide).map((c) => {
1015
2965
  return `${[c.name, ...c.aliases].join(", ")}${this.generateArgsUsage(c.options.args)}`.length;
1016
2966
  }), 0);
1017
2967
  }
2968
+ /** Calculate max display length for commands (for help alignment) */
1018
2969
  getMaxCmdLength(commands) {
1019
2970
  return Math.max(...commands.filter((c) => !c.options.hide && c.name !== "").map((c) => {
1020
2971
  return `${[c.name, ...c.aliases].join(", ")}${c.hasChildren ? " <command>" : this.generateArgsUsage(c.options.args)}`.length;
1021
2972
  }));
1022
2973
  }
2974
+ /** Calculate max display length for flags (for help alignment) */
1023
2975
  getMaxFlagLength(flags) {
1024
2976
  return Math.max(...flags.map((f) => {
1025
2977
  return (Array.isArray(f.aliases) ? f.aliases : [f.aliases]).map((a) => a.length === 1 ? `-${a}` : `--${a}`).join(", ").length;
1026
2978
  }));
1027
2979
  }
2980
+ /**
2981
+ * Extract enum values from a schema if it represents an enum.
2982
+ * Returns undefined if the schema is not an enum.
2983
+ */
2984
+ getEnumValues(schema) {
2985
+ if (!schema) return void 0;
2986
+ if ("enum" in schema && Array.isArray(schema.enum) && schema.enum.every((v) => typeof v === "string")) return schema.enum;
2987
+ if (t.schema.isUnion(schema)) {
2988
+ const union = schema;
2989
+ const values = [];
2990
+ for (const variant of union.anyOf) if (t.schema.isString(variant) && "const" in variant && typeof variant.const === "string") values.push(variant.const);
2991
+ else return;
2992
+ return values.length > 0 ? values : void 0;
2993
+ }
2994
+ }
2995
+ /**
2996
+ * Format flag description with enum values if applicable.
2997
+ */
2998
+ formatFlagDescription(description, schema) {
2999
+ const baseDesc = description ?? "";
3000
+ if (!schema) return baseDesc;
3001
+ const enumValues = this.getEnumValues(schema);
3002
+ if (enumValues && enumValues.length > 0) {
3003
+ const valuesStr = enumValues.join(", ");
3004
+ const enumHint = this.color.set("GREY_DARK", `[${valuesStr}]`);
3005
+ return baseDesc ? `${baseDesc} ${enumHint}` : enumHint;
3006
+ }
3007
+ return baseDesc;
3008
+ }
1028
3009
  };
1029
3010
 
1030
3011
  //#endregion
1031
3012
  //#region ../../src/command/index.ts
1032
3013
  /**
1033
- * This module provides a powerful way to build command-line interfaces
1034
- * directly within your Alepha application, using declarative primitives.
3014
+ * | type | quality | stability |
3015
+ * |------|---------|-----------|
3016
+ * | tooling | rare | stable |
3017
+ *
3018
+ * Declarative CLI command framework.
1035
3019
  *
1036
- * It allows you to define commands using the `$command` primitive.
3020
+ * **Features:**
3021
+ * - CLI command definitions
3022
+ * - Interactive CLI prompts
3023
+ * - Command execution
3024
+ * - Formatted colored output
3025
+ * - Environment variable utilities
3026
+ * - Schema validation for CLI arguments
1037
3027
  *
1038
- * @see {@link $command}
1039
3028
  * @module alepha.command
1040
3029
  */
1041
3030
  const AlephaCommand = $module({