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
@@ -0,0 +1,1392 @@
1
+ import { Alepha, t } from "alepha";
2
+ import { describe, expect, it } from "vitest";
3
+ import { $command } from "../primitives/$command.ts";
4
+ import { CliProvider } from "./CliProvider.ts";
5
+
6
+ /**
7
+ * Test subclass that exposes protected methods for unit testing.
8
+ */
9
+ class TestCliProvider extends CliProvider {
10
+ public testParseFlags = this.parseFlags.bind(this);
11
+ public testParseCommandArgs = this.parseCommandArgs.bind(this);
12
+ public testParseArgumentValue = this.parseArgumentValue.bind(this);
13
+ public testParseModeFlag = this.parseModeFlag.bind(this);
14
+ public testResolveCommand = this.resolveCommand.bind(this);
15
+ public testRemoveConsumedArgs = this.removeConsumedArgs.bind(this);
16
+ public testGetFlagConsumedIndices = this.getFlagConsumedIndices.bind(this);
17
+ public testGenerateArgsUsage = this.generateArgsUsage.bind(this);
18
+ public testGenerateColoredArgsUsage =
19
+ this.generateColoredArgsUsage.bind(this);
20
+ public testGetTypeName = this.getTypeName.bind(this);
21
+ public testGetTopLevelCommands = this.getTopLevelCommands.bind(this);
22
+ public testFindParentCommand = this.findParentCommand.bind(this);
23
+ public testGetCommandPath = this.getCommandPath.bind(this);
24
+ public testFindCommand = this.findCommand.bind(this);
25
+ public testFindPreHooks = this.findPreHooks.bind(this);
26
+ public testFindPostHooks = this.findPostHooks.bind(this);
27
+ public testGetEnumValues = this.getEnumValues.bind(this);
28
+ public testFormatFlagDescription = this.formatFlagDescription.bind(this);
29
+
30
+ /**
31
+ * Extract flag definitions from a command's flags schema (for testing printHelp logic).
32
+ */
33
+ public testExtractFlagDefs(flagsSchema: any) {
34
+ return Object.entries(flagsSchema.properties).map(([key, value]) => ({
35
+ key,
36
+ schema: value,
37
+ aliases: [
38
+ key,
39
+ ...((value as any).aliases ??
40
+ ((value as any).alias ? [(value as any).alias] : [])),
41
+ ],
42
+ description: (value as any).description,
43
+ }));
44
+ }
45
+
46
+ /**
47
+ * Format aliases array into flag string (e.g., "-t, --target").
48
+ * Sorts by length (shorter first).
49
+ */
50
+ public testFormatFlagStr(aliases: string[]): string {
51
+ return aliases
52
+ .slice()
53
+ .sort((a, b) => a.length - b.length)
54
+ .map((a) => (a.length === 1 ? `-${a}` : `--${a}`))
55
+ .join(", ");
56
+ }
57
+ }
58
+
59
+ describe("CliProvider", () => {
60
+ const createTestCli = () => {
61
+ const alepha = Alepha.create();
62
+ return alepha.inject(TestCliProvider);
63
+ };
64
+
65
+ // ─────────────────────────────────────────────────────────────────────────────
66
+ // parseFlags
67
+ // ─────────────────────────────────────────────────────────────────────────────
68
+
69
+ describe("parseFlags", () => {
70
+ it("should parse boolean flags", () => {
71
+ const cli = createTestCli();
72
+ const flagDefs = [
73
+ { key: "verbose", aliases: ["v", "verbose"], schema: t.boolean() },
74
+ ];
75
+
76
+ const result = cli.testParseFlags(["--verbose"], flagDefs);
77
+ expect(result.verbose).toBe(true);
78
+ });
79
+
80
+ it("should parse short boolean flags", () => {
81
+ const cli = createTestCli();
82
+ const flagDefs = [
83
+ { key: "verbose", aliases: ["v", "verbose"], schema: t.boolean() },
84
+ ];
85
+
86
+ const result = cli.testParseFlags(["-v"], flagDefs);
87
+ expect(result.verbose).toBe(true);
88
+ });
89
+
90
+ it("should parse string flags with = syntax", () => {
91
+ const cli = createTestCli();
92
+ const flagDefs = [
93
+ { key: "name", aliases: ["n", "name"], schema: t.string() },
94
+ ];
95
+
96
+ const result = cli.testParseFlags(["--name=hello"], flagDefs);
97
+ expect(result.name).toBe("hello");
98
+ });
99
+
100
+ it("should parse string flags with space syntax", () => {
101
+ const cli = createTestCli();
102
+ const flagDefs = [
103
+ { key: "name", aliases: ["n", "name"], schema: t.string() },
104
+ ];
105
+
106
+ const result = cli.testParseFlags(["--name", "hello"], flagDefs);
107
+ expect(result.name).toBe("hello");
108
+ });
109
+
110
+ it("should parse JSON object flags", () => {
111
+ const cli = createTestCli();
112
+ const flagDefs = [
113
+ { key: "config", aliases: ["config"], schema: t.object({}) },
114
+ ];
115
+
116
+ const result = cli.testParseFlags(
117
+ ["--config", '{"key":"value"}'],
118
+ flagDefs,
119
+ );
120
+ expect(result.config).toEqual({ key: "value" });
121
+ });
122
+
123
+ it("should parse JSON array flags", () => {
124
+ const cli = createTestCli();
125
+ const flagDefs = [
126
+ { key: "items", aliases: ["items"], schema: t.array(t.string()) },
127
+ ];
128
+
129
+ const result = cli.testParseFlags(["--items", '["a","b"]'], flagDefs);
130
+ expect(result.items).toEqual(["a", "b"]);
131
+ });
132
+
133
+ it("should throw on unknown flags", () => {
134
+ const cli = createTestCli();
135
+ const flagDefs = [
136
+ { key: "known", aliases: ["known"], schema: t.boolean() },
137
+ ];
138
+
139
+ expect(() =>
140
+ cli.testParseFlags(["--unknown", "--known"], flagDefs),
141
+ ).toThrow("Unknown flag: --unknown");
142
+ });
143
+
144
+ it("should throw on missing required value", () => {
145
+ const cli = createTestCli();
146
+ const flagDefs = [{ key: "name", aliases: ["name"], schema: t.string() }];
147
+
148
+ expect(() => cli.testParseFlags(["--name"], flagDefs)).toThrow(
149
+ "requires a value",
150
+ );
151
+ });
152
+
153
+ it("should throw on invalid JSON", () => {
154
+ const cli = createTestCli();
155
+ const flagDefs = [
156
+ { key: "config", aliases: ["config"], schema: t.object({}) },
157
+ ];
158
+
159
+ expect(() =>
160
+ cli.testParseFlags(["--config", "{invalid}"], flagDefs),
161
+ ).toThrow("Invalid JSON");
162
+ });
163
+
164
+ it("should parse union(boolean, text) flag without value as true", () => {
165
+ const cli = createTestCli();
166
+ const flagDefs = [
167
+ {
168
+ key: "image",
169
+ aliases: ["i", "image"],
170
+ schema: t.union([t.boolean(), t.text()]),
171
+ },
172
+ ];
173
+
174
+ const result = cli.testParseFlags(["-i"], flagDefs);
175
+ expect(result.image).toBe(true);
176
+ });
177
+
178
+ it("should parse union(boolean, text) flag with = value as string", () => {
179
+ const cli = createTestCli();
180
+ const flagDefs = [
181
+ {
182
+ key: "image",
183
+ aliases: ["i", "image"],
184
+ schema: t.union([t.boolean(), t.text()]),
185
+ },
186
+ ];
187
+
188
+ const result = cli.testParseFlags(["-i=1.3.4"], flagDefs);
189
+ expect(result.image).toBe("1.3.4");
190
+ });
191
+
192
+ it("should parse union(boolean, text) flag with space value as string", () => {
193
+ const cli = createTestCli();
194
+ const flagDefs = [
195
+ {
196
+ key: "image",
197
+ aliases: ["i", "image"],
198
+ schema: t.union([t.boolean(), t.text()]),
199
+ },
200
+ ];
201
+
202
+ const result = cli.testParseFlags(["-i", "1.3.4"], flagDefs);
203
+ expect(result.image).toBe("1.3.4");
204
+ });
205
+
206
+ it("should parse union(boolean, text) flag without value when followed by another flag", () => {
207
+ const cli = createTestCli();
208
+ const flagDefs = [
209
+ {
210
+ key: "image",
211
+ aliases: ["i", "image"],
212
+ schema: t.union([t.boolean(), t.text()]),
213
+ },
214
+ { key: "verbose", aliases: ["v", "verbose"], schema: t.boolean() },
215
+ ];
216
+
217
+ const result = cli.testParseFlags(["-i", "-v"], flagDefs);
218
+ expect(result.image).toBe(true);
219
+ expect(result.verbose).toBe(true);
220
+ });
221
+
222
+ it("should parse union(boolean, text) flag with long form without value", () => {
223
+ const cli = createTestCli();
224
+ const flagDefs = [
225
+ {
226
+ key: "image",
227
+ aliases: ["i", "image"],
228
+ schema: t.union([t.boolean(), t.text()]),
229
+ },
230
+ ];
231
+
232
+ const result = cli.testParseFlags(["--image"], flagDefs);
233
+ expect(result.image).toBe(true);
234
+ });
235
+
236
+ it("should parse union(boolean, text) flag at end of argv without value", () => {
237
+ const cli = createTestCli();
238
+ const flagDefs = [
239
+ {
240
+ key: "image",
241
+ aliases: ["i", "image"],
242
+ schema: t.union([t.boolean(), t.text()]),
243
+ },
244
+ { key: "verbose", aliases: ["v", "verbose"], schema: t.boolean() },
245
+ ];
246
+
247
+ const result = cli.testParseFlags(["-v", "-i"], flagDefs);
248
+ expect(result.verbose).toBe(true);
249
+ expect(result.image).toBe(true);
250
+ });
251
+
252
+ it("should parse union(boolean, text) with empty = value as empty string", () => {
253
+ const cli = createTestCli();
254
+ const flagDefs = [
255
+ {
256
+ key: "image",
257
+ aliases: ["i", "image"],
258
+ schema: t.union([t.boolean(), t.text()]),
259
+ },
260
+ ];
261
+
262
+ // Note: --image= results in empty string value
263
+ const result = cli.testParseFlags(["--image="], flagDefs);
264
+ // Empty string is falsy, so it goes through the "no value" path → true
265
+ // This is expected behavior - use --image="" if you need empty string
266
+ expect(result.image).toBe(true);
267
+ });
268
+
269
+ it("should parse union(boolean, text) with value containing special chars", () => {
270
+ const cli = createTestCli();
271
+ const flagDefs = [
272
+ {
273
+ key: "image",
274
+ aliases: ["i", "image"],
275
+ schema: t.union([t.boolean(), t.text()]),
276
+ },
277
+ ];
278
+
279
+ const result = cli.testParseFlags(
280
+ ["--image=my-org/my-app:v1.2.3-beta"],
281
+ flagDefs,
282
+ );
283
+ expect(result.image).toBe("my-org/my-app:v1.2.3-beta");
284
+ });
285
+ });
286
+
287
+ // ─────────────────────────────────────────────────────────────────────────────
288
+ // parseArgumentValue
289
+ // ─────────────────────────────────────────────────────────────────────────────
290
+
291
+ describe("parseArgumentValue", () => {
292
+ it("should parse string values", () => {
293
+ const cli = createTestCli();
294
+ expect(cli.testParseArgumentValue("hello", t.string())).toBe("hello");
295
+ });
296
+
297
+ it("should parse number values", () => {
298
+ const cli = createTestCli();
299
+ expect(cli.testParseArgumentValue("42", t.number())).toBe(42);
300
+ expect(cli.testParseArgumentValue("3.14", t.number())).toBe(3.14);
301
+ });
302
+
303
+ it("should throw on invalid number", () => {
304
+ const cli = createTestCli();
305
+ expect(() => cli.testParseArgumentValue("abc", t.number())).toThrow(
306
+ "Expected number",
307
+ );
308
+ });
309
+
310
+ it("should parse integer values", () => {
311
+ const cli = createTestCli();
312
+ expect(cli.testParseArgumentValue("42", t.integer())).toBe(42);
313
+ });
314
+
315
+ it("should throw on non-integer for integer schema", () => {
316
+ const cli = createTestCli();
317
+ expect(() => cli.testParseArgumentValue("3.14", t.integer())).toThrow(
318
+ "Expected integer",
319
+ );
320
+ });
321
+
322
+ it("should parse boolean true values", () => {
323
+ const cli = createTestCli();
324
+ expect(cli.testParseArgumentValue("true", t.boolean())).toBe(true);
325
+ expect(cli.testParseArgumentValue("1", t.boolean())).toBe(true);
326
+ });
327
+
328
+ it("should parse boolean false values", () => {
329
+ const cli = createTestCli();
330
+ expect(cli.testParseArgumentValue("false", t.boolean())).toBe(false);
331
+ expect(cli.testParseArgumentValue("0", t.boolean())).toBe(false);
332
+ });
333
+
334
+ it("should throw on invalid boolean", () => {
335
+ const cli = createTestCli();
336
+ expect(() => cli.testParseArgumentValue("yes", t.boolean())).toThrow(
337
+ "Expected boolean",
338
+ );
339
+ });
340
+ });
341
+
342
+ // ─────────────────────────────────────────────────────────────────────────────
343
+ // parseCommandArgs
344
+ // ─────────────────────────────────────────────────────────────────────────────
345
+
346
+ describe("parseCommandArgs", () => {
347
+ it("should return undefined when no schema", () => {
348
+ const cli = createTestCli();
349
+ expect(
350
+ cli.testParseCommandArgs(["arg"], undefined, true),
351
+ ).toBeUndefined();
352
+ });
353
+
354
+ it("should parse optional args when present", () => {
355
+ const cli = createTestCli();
356
+ const schema = t.optional(t.string());
357
+ expect(cli.testParseCommandArgs(["hello"], schema, true)).toBe("hello");
358
+ });
359
+
360
+ it("should return undefined for optional args when missing", () => {
361
+ const cli = createTestCli();
362
+ const schema = t.optional(t.string());
363
+ expect(cli.testParseCommandArgs([], schema, true)).toBeUndefined();
364
+ });
365
+
366
+ it("should parse required args", () => {
367
+ const cli = createTestCli();
368
+ const schema = t.string();
369
+ expect(cli.testParseCommandArgs(["hello"], schema, true)).toBe("hello");
370
+ });
371
+
372
+ it("should throw on missing required args", () => {
373
+ const cli = createTestCli();
374
+ const schema = t.string();
375
+ expect(() => cli.testParseCommandArgs([], schema, true)).toThrow(
376
+ "Missing required argument",
377
+ );
378
+ });
379
+
380
+ it("should parse tuple args", () => {
381
+ const cli = createTestCli();
382
+ const schema = t.tuple([t.string(), t.number()]);
383
+ const result = cli.testParseCommandArgs(["hello", "42"], schema, true);
384
+ expect(result).toEqual(["hello", 42]);
385
+ });
386
+
387
+ it("should handle optional tuple items", () => {
388
+ const cli = createTestCli();
389
+ const schema = t.tuple([t.string(), t.optional(t.number())]);
390
+ const result = cli.testParseCommandArgs(["hello"], schema, true);
391
+ expect(result).toEqual(["hello", undefined]);
392
+ });
393
+
394
+ it("should skip flags when parsing args", () => {
395
+ const cli = createTestCli();
396
+ const schema = t.string();
397
+ const flags = t.object({ verbose: t.optional(t.boolean()) });
398
+ const result = cli.testParseCommandArgs(
399
+ ["--verbose", "hello"],
400
+ schema,
401
+ true,
402
+ flags,
403
+ );
404
+ expect(result).toBe("hello");
405
+ });
406
+
407
+ it("should skip flag values when parsing args", () => {
408
+ const cli = createTestCli();
409
+ const schema = t.string();
410
+ const flags = t.object({ name: t.optional(t.string()) });
411
+ const result = cli.testParseCommandArgs(
412
+ ["--name", "ignored", "actual"],
413
+ schema,
414
+ true,
415
+ flags,
416
+ );
417
+ expect(result).toBe("actual");
418
+ });
419
+ });
420
+
421
+ // ─────────────────────────────────────────────────────────────────────────────
422
+ // parseModeFlag
423
+ // ─────────────────────────────────────────────────────────────────────────────
424
+
425
+ describe("parseModeFlag", () => {
426
+ it("should parse --mode=value", () => {
427
+ const cli = createTestCli();
428
+ expect(cli.testParseModeFlag(["--mode=production"])).toBe("production");
429
+ });
430
+
431
+ it("should parse -m=value", () => {
432
+ const cli = createTestCli();
433
+ expect(cli.testParseModeFlag(["-m=staging"])).toBe("staging");
434
+ });
435
+
436
+ it("should parse --mode value", () => {
437
+ const cli = createTestCli();
438
+ expect(cli.testParseModeFlag(["--mode", "production"])).toBe(
439
+ "production",
440
+ );
441
+ });
442
+
443
+ it("should parse -m value", () => {
444
+ const cli = createTestCli();
445
+ expect(cli.testParseModeFlag(["-m", "staging"])).toBe("staging");
446
+ });
447
+
448
+ it("should return undefined when no mode flag", () => {
449
+ const cli = createTestCli();
450
+ expect(cli.testParseModeFlag(["--other", "value"])).toBeUndefined();
451
+ });
452
+
453
+ it("should throw when --mode has no value", () => {
454
+ const cli = createTestCli();
455
+ expect(() => cli.testParseModeFlag(["--mode"])).toThrow(
456
+ "requires a value",
457
+ );
458
+ });
459
+
460
+ it("should throw when --mode followed by another flag", () => {
461
+ const cli = createTestCli();
462
+ expect(() => cli.testParseModeFlag(["--mode", "--other"])).toThrow(
463
+ "requires a value",
464
+ );
465
+ });
466
+ });
467
+
468
+ // ─────────────────────────────────────────────────────────────────────────────
469
+ // resolveCommand
470
+ // ─────────────────────────────────────────────────────────────────────────────
471
+
472
+ describe("resolveCommand", () => {
473
+ it("should return undefined for empty args", () => {
474
+ const cli = createTestCli();
475
+ const result = cli.testResolveCommand([]);
476
+ expect(result.command).toBeUndefined();
477
+ expect(result.consumedArgs).toEqual([]);
478
+ });
479
+
480
+ it("should find command by name", () => {
481
+ class TestCommands {
482
+ build = $command({
483
+ name: "build",
484
+ flags: t.object({}),
485
+ handler: async () => {},
486
+ });
487
+ }
488
+
489
+ const alepha = Alepha.create().with(TestCommands);
490
+ const cli = alepha.inject(TestCliProvider);
491
+
492
+ const result = cli.testResolveCommand(["build"]);
493
+ expect(result.command?.name).toBe("build");
494
+ expect(result.consumedArgs).toEqual(["build"]);
495
+ });
496
+
497
+ it("should find command by colon notation", () => {
498
+ class TestCommands {
499
+ deployVercel = $command({
500
+ name: "deploy:vercel",
501
+ flags: t.object({}),
502
+ handler: async () => {},
503
+ });
504
+ }
505
+
506
+ const alepha = Alepha.create().with(TestCommands);
507
+ const cli = alepha.inject(TestCliProvider);
508
+
509
+ const result = cli.testResolveCommand(["deploy:vercel"]);
510
+ expect(result.command?.name).toBe("deploy:vercel");
511
+ expect(result.consumedArgs).toEqual(["deploy:vercel"]);
512
+ });
513
+
514
+ it("should return undefined for unknown command", () => {
515
+ const cli = createTestCli();
516
+ const result = cli.testResolveCommand(["unknown"]);
517
+ expect(result.command).toBeUndefined();
518
+ expect(result.consumedArgs).toEqual([]);
519
+ });
520
+ });
521
+
522
+ // ─────────────────────────────────────────────────────────────────────────────
523
+ // removeConsumedArgs
524
+ // ─────────────────────────────────────────────────────────────────────────────
525
+
526
+ describe("removeConsumedArgs", () => {
527
+ it("should remove consumed command args", () => {
528
+ const cli = createTestCli();
529
+ const result = cli.testRemoveConsumedArgs(
530
+ ["build", "--verbose", "extra"],
531
+ ["build"],
532
+ );
533
+ expect(result).toEqual(["--verbose", "extra"]);
534
+ });
535
+
536
+ it("should preserve flags", () => {
537
+ const cli = createTestCli();
538
+ const result = cli.testRemoveConsumedArgs(
539
+ ["--flag", "build", "arg"],
540
+ ["build"],
541
+ );
542
+ expect(result).toEqual(["--flag", "arg"]);
543
+ });
544
+
545
+ it("should remove multiple consumed args", () => {
546
+ const cli = createTestCli();
547
+ const result = cli.testRemoveConsumedArgs(
548
+ ["deploy", "vercel", "--prod"],
549
+ ["deploy", "vercel"],
550
+ );
551
+ expect(result).toEqual(["--prod"]);
552
+ });
553
+ });
554
+
555
+ // ─────────────────────────────────────────────────────────────────────────────
556
+ // getFlagConsumedIndices
557
+ // ─────────────────────────────────────────────────────────────────────────────
558
+
559
+ describe("getFlagConsumedIndices", () => {
560
+ it("should mark flag indices as consumed", () => {
561
+ const cli = createTestCli();
562
+ const flagDefs = [
563
+ { key: "verbose", aliases: ["v", "verbose"], schema: t.boolean() },
564
+ ];
565
+
566
+ const result = cli.testGetFlagConsumedIndices(["--verbose"], flagDefs);
567
+ expect(result.has(0)).toBe(true);
568
+ });
569
+
570
+ it("should mark flag value indices as consumed", () => {
571
+ const cli = createTestCli();
572
+ const flagDefs = [{ key: "name", aliases: ["name"], schema: t.string() }];
573
+
574
+ const result = cli.testGetFlagConsumedIndices(
575
+ ["--name", "value", "arg"],
576
+ flagDefs,
577
+ );
578
+ expect(result.has(0)).toBe(true); // --name
579
+ expect(result.has(1)).toBe(true); // value
580
+ expect(result.has(2)).toBe(false); // arg
581
+ });
582
+
583
+ it("should not consume next arg for --flag=value syntax", () => {
584
+ const cli = createTestCli();
585
+ const flagDefs = [{ key: "name", aliases: ["name"], schema: t.string() }];
586
+
587
+ const result = cli.testGetFlagConsumedIndices(
588
+ ["--name=value", "arg"],
589
+ flagDefs,
590
+ );
591
+ expect(result.has(0)).toBe(true); // --name=value
592
+ expect(result.has(1)).toBe(false); // arg
593
+ });
594
+
595
+ it("should consume value for union(boolean, text) when value is provided", () => {
596
+ const cli = createTestCli();
597
+ const flagDefs = [
598
+ {
599
+ key: "image",
600
+ aliases: ["i", "image"],
601
+ schema: t.union([t.boolean(), t.text()]),
602
+ },
603
+ ];
604
+
605
+ const result = cli.testGetFlagConsumedIndices(
606
+ ["-i", "1.3.4", "arg"],
607
+ flagDefs,
608
+ );
609
+ expect(result.has(0)).toBe(true); // -i
610
+ expect(result.has(1)).toBe(true); // 1.3.4 (consumed as value)
611
+ expect(result.has(2)).toBe(false); // arg
612
+ });
613
+
614
+ it("should not consume next arg for union(boolean, text) when next is a flag", () => {
615
+ const cli = createTestCli();
616
+ const flagDefs = [
617
+ {
618
+ key: "image",
619
+ aliases: ["i", "image"],
620
+ schema: t.union([t.boolean(), t.text()]),
621
+ },
622
+ { key: "verbose", aliases: ["v", "verbose"], schema: t.boolean() },
623
+ ];
624
+
625
+ const result = cli.testGetFlagConsumedIndices(["-i", "-v"], flagDefs);
626
+ expect(result.has(0)).toBe(true); // -i
627
+ expect(result.has(1)).toBe(true); // -v (not consumed by -i, but by itself)
628
+ });
629
+
630
+ it("should not consume next arg for union(boolean, text) at end of argv", () => {
631
+ const cli = createTestCli();
632
+ const flagDefs = [
633
+ {
634
+ key: "image",
635
+ aliases: ["i", "image"],
636
+ schema: t.union([t.boolean(), t.text()]),
637
+ },
638
+ ];
639
+
640
+ const result = cli.testGetFlagConsumedIndices(["-i"], flagDefs);
641
+ expect(result.has(0)).toBe(true); // -i
642
+ expect(result.size).toBe(1);
643
+ });
644
+ });
645
+
646
+ // ─────────────────────────────────────────────────────────────────────────────
647
+ // generateArgsUsage
648
+ // ─────────────────────────────────────────────────────────────────────────────
649
+
650
+ describe("generateArgsUsage", () => {
651
+ it("should return empty string for no schema", () => {
652
+ const cli = createTestCli();
653
+ expect(cli.testGenerateArgsUsage(undefined)).toBe("");
654
+ });
655
+
656
+ it("should generate optional arg usage", () => {
657
+ const cli = createTestCli();
658
+ const schema = t.optional(t.string({ title: "path" }));
659
+ expect(cli.testGenerateArgsUsage(schema)).toBe(" [path]");
660
+ });
661
+
662
+ it("should generate required arg usage", () => {
663
+ const cli = createTestCli();
664
+ const schema = t.string({ title: "path" });
665
+ expect(cli.testGenerateArgsUsage(schema)).toBe(" <path>");
666
+ });
667
+
668
+ it("should generate tuple arg usage", () => {
669
+ const cli = createTestCli();
670
+ const schema = t.tuple([t.string(), t.optional(t.number())]);
671
+ const result = cli.testGenerateArgsUsage(schema);
672
+ expect(result).toContain("<arg1>");
673
+ expect(result).toContain("[arg2");
674
+ });
675
+
676
+ it("should include type for numbers", () => {
677
+ const cli = createTestCli();
678
+ const schema = t.number({ title: "count" });
679
+ expect(cli.testGenerateArgsUsage(schema)).toBe(" <count: number>");
680
+ });
681
+
682
+ it("should include type for integers", () => {
683
+ const cli = createTestCli();
684
+ const schema = t.integer({ title: "port" });
685
+ expect(cli.testGenerateArgsUsage(schema)).toBe(" <port: integer>");
686
+ });
687
+
688
+ it("should include type for booleans", () => {
689
+ const cli = createTestCli();
690
+ const schema = t.boolean({ title: "force" });
691
+ expect(cli.testGenerateArgsUsage(schema)).toBe(" <force: boolean>");
692
+ });
693
+ });
694
+
695
+ // ─────────────────────────────────────────────────────────────────────────────
696
+ // getTypeName
697
+ // ─────────────────────────────────────────────────────────────────────────────
698
+
699
+ describe("getTypeName", () => {
700
+ it("should return empty for string", () => {
701
+ const cli = createTestCli();
702
+ expect(cli.testGetTypeName(t.string())).toBe("");
703
+ });
704
+
705
+ it("should return ': number' for number", () => {
706
+ const cli = createTestCli();
707
+ expect(cli.testGetTypeName(t.number())).toBe(": number");
708
+ });
709
+
710
+ it("should return ': integer' for integer", () => {
711
+ const cli = createTestCli();
712
+ expect(cli.testGetTypeName(t.integer())).toBe(": integer");
713
+ });
714
+
715
+ it("should return ': boolean' for boolean", () => {
716
+ const cli = createTestCli();
717
+ expect(cli.testGetTypeName(t.boolean())).toBe(": boolean");
718
+ });
719
+ });
720
+
721
+ // ─────────────────────────────────────────────────────────────────────────────
722
+ // generateColoredArgsUsage
723
+ // ─────────────────────────────────────────────────────────────────────────────
724
+
725
+ describe("generateColoredArgsUsage", () => {
726
+ it("should return empty string for no schema", () => {
727
+ const cli = createTestCli();
728
+ expect(cli.testGenerateColoredArgsUsage(undefined)).toBe("");
729
+ });
730
+
731
+ it("should generate colored optional arg usage", () => {
732
+ const cli = createTestCli();
733
+ const schema = t.optional(t.string({ title: "path" }));
734
+ const result = cli.testGenerateColoredArgsUsage(schema);
735
+ expect(result).toContain("[path]");
736
+ });
737
+
738
+ it("should generate colored required arg usage", () => {
739
+ const cli = createTestCli();
740
+ const schema = t.string({ title: "path" });
741
+ const result = cli.testGenerateColoredArgsUsage(schema);
742
+ expect(result).toContain("<path>");
743
+ });
744
+
745
+ it("should generate colored tuple arg usage", () => {
746
+ const cli = createTestCli();
747
+ const schema = t.tuple([t.string(), t.optional(t.number())]);
748
+ const result = cli.testGenerateColoredArgsUsage(schema);
749
+ expect(result).toContain("<arg1>");
750
+ expect(result).toContain("[arg2");
751
+ });
752
+ });
753
+
754
+ // ─────────────────────────────────────────────────────────────────────────────
755
+ // findCommand
756
+ // ─────────────────────────────────────────────────────────────────────────────
757
+
758
+ describe("findCommand", () => {
759
+ it("should return undefined for unknown command", () => {
760
+ const cli = createTestCli();
761
+ expect(cli.testFindCommand("unknown")).toBeUndefined();
762
+ });
763
+
764
+ it("should find command by name", () => {
765
+ class TestCommands {
766
+ test = $command({
767
+ name: "test",
768
+ flags: t.object({}),
769
+ handler: async () => {},
770
+ });
771
+ }
772
+
773
+ const alepha = Alepha.create().with(TestCommands);
774
+ const cli = alepha.inject(TestCliProvider);
775
+
776
+ expect(cli.testFindCommand("test")?.name).toBe("test");
777
+ });
778
+
779
+ it("should find command by alias", () => {
780
+ class TestCommands {
781
+ test = $command({
782
+ name: "test",
783
+ aliases: ["t"],
784
+ flags: t.object({}),
785
+ handler: async () => {},
786
+ });
787
+ }
788
+
789
+ const alepha = Alepha.create().with(TestCommands);
790
+ const cli = alepha.inject(TestCliProvider);
791
+
792
+ expect(cli.testFindCommand("t")?.name).toBe("test");
793
+ });
794
+ });
795
+
796
+ // ─────────────────────────────────────────────────────────────────────────────
797
+ // findPreHooks / findPostHooks
798
+ // ─────────────────────────────────────────────────────────────────────────────
799
+
800
+ describe("findPreHooks", () => {
801
+ it("should return empty array when no hooks", () => {
802
+ const cli = createTestCli();
803
+ expect(cli.testFindPreHooks("build")).toEqual([]);
804
+ });
805
+
806
+ it("should find pre-hooks for command", () => {
807
+ class TestCommands {
808
+ preBuild = $command({
809
+ name: "prebuild",
810
+ flags: t.object({}),
811
+ handler: async () => {},
812
+ });
813
+ build = $command({
814
+ name: "build",
815
+ flags: t.object({}),
816
+ handler: async () => {},
817
+ });
818
+ }
819
+
820
+ const alepha = Alepha.create().with(TestCommands);
821
+ const cli = alepha.inject(TestCliProvider);
822
+
823
+ expect(cli.testFindPreHooks("build").length).toBe(1);
824
+ });
825
+ });
826
+
827
+ describe("findPostHooks", () => {
828
+ it("should return empty array when no hooks", () => {
829
+ const cli = createTestCli();
830
+ expect(cli.testFindPostHooks("build")).toEqual([]);
831
+ });
832
+
833
+ it("should find post-hooks for command", () => {
834
+ class TestCommands {
835
+ build = $command({
836
+ name: "build",
837
+ flags: t.object({}),
838
+ handler: async () => {},
839
+ });
840
+ postBuild = $command({
841
+ name: "postbuild",
842
+ flags: t.object({}),
843
+ handler: async () => {},
844
+ });
845
+ }
846
+
847
+ const alepha = Alepha.create().with(TestCommands);
848
+ const cli = alepha.inject(TestCliProvider);
849
+
850
+ expect(cli.testFindPostHooks("build").length).toBe(1);
851
+ });
852
+ });
853
+
854
+ // ─────────────────────────────────────────────────────────────────────────────
855
+ // getTopLevelCommands
856
+ // ─────────────────────────────────────────────────────────────────────────────
857
+
858
+ describe("getTopLevelCommands", () => {
859
+ it("should return all commands when no children", () => {
860
+ class TestCommands {
861
+ build = $command({
862
+ name: "build",
863
+ flags: t.object({}),
864
+ handler: async () => {},
865
+ });
866
+ test = $command({
867
+ name: "test",
868
+ flags: t.object({}),
869
+ handler: async () => {},
870
+ });
871
+ }
872
+
873
+ const alepha = Alepha.create().with(TestCommands);
874
+ const cli = alepha.inject(TestCliProvider);
875
+
876
+ const topLevel = cli.testGetTopLevelCommands();
877
+ expect(topLevel.length).toBe(2);
878
+ });
879
+
880
+ it("should exclude child commands", () => {
881
+ class TestCommands {
882
+ deployVercel = $command({
883
+ name: "vercel",
884
+ flags: t.object({}),
885
+ handler: async () => {},
886
+ });
887
+ deploy = $command({
888
+ name: "deploy",
889
+ flags: t.object({}),
890
+ children: [this.deployVercel],
891
+ handler: async () => {},
892
+ });
893
+ }
894
+
895
+ const alepha = Alepha.create().with(TestCommands);
896
+ const cli = alepha.inject(TestCliProvider);
897
+
898
+ const topLevel = cli.testGetTopLevelCommands();
899
+ expect(topLevel.length).toBe(1);
900
+ expect(topLevel[0].name).toBe("deploy");
901
+ });
902
+ });
903
+
904
+ // ─────────────────────────────────────────────────────────────────────────────
905
+ // findParentCommand
906
+ // ─────────────────────────────────────────────────────────────────────────────
907
+
908
+ describe("findParentCommand", () => {
909
+ it("should return undefined for top-level command", () => {
910
+ class TestCommands {
911
+ build = $command({
912
+ name: "build",
913
+ flags: t.object({}),
914
+ handler: async () => {},
915
+ });
916
+ }
917
+
918
+ const alepha = Alepha.create().with(TestCommands);
919
+ const cli = alepha.inject(TestCliProvider);
920
+ const cmd = cli.testFindCommand("build")!;
921
+
922
+ expect(cli.testFindParentCommand(cmd)).toBeUndefined();
923
+ });
924
+
925
+ it("should find parent of child command", () => {
926
+ class TestCommands {
927
+ deployVercel = $command({
928
+ name: "vercel",
929
+ flags: t.object({}),
930
+ handler: async () => {},
931
+ });
932
+ deploy = $command({
933
+ name: "deploy",
934
+ flags: t.object({}),
935
+ children: [this.deployVercel],
936
+ handler: async () => {},
937
+ });
938
+ }
939
+
940
+ const alepha = Alepha.create().with(TestCommands);
941
+ const cli = alepha.inject(TestCliProvider);
942
+ const child = cli.testFindCommand("vercel")!;
943
+
944
+ expect(cli.testFindParentCommand(child)?.name).toBe("deploy");
945
+ });
946
+ });
947
+
948
+ // ─────────────────────────────────────────────────────────────────────────────
949
+ // getCommandPath
950
+ // ─────────────────────────────────────────────────────────────────────────────
951
+
952
+ describe("getCommandPath", () => {
953
+ it("should return simple name for top-level command", () => {
954
+ class TestCommands {
955
+ build = $command({
956
+ name: "build",
957
+ flags: t.object({}),
958
+ handler: async () => {},
959
+ });
960
+ }
961
+
962
+ const alepha = Alepha.create().with(TestCommands);
963
+ const cli = alepha.inject(TestCliProvider);
964
+ const cmd = cli.testFindCommand("build")!;
965
+
966
+ expect(cli.testGetCommandPath(cmd)).toBe("build");
967
+ });
968
+
969
+ it("should return full path for nested command", () => {
970
+ class TestCommands {
971
+ deployVercel = $command({
972
+ name: "vercel",
973
+ flags: t.object({}),
974
+ handler: async () => {},
975
+ });
976
+ deploy = $command({
977
+ name: "deploy",
978
+ flags: t.object({}),
979
+ children: [this.deployVercel],
980
+ handler: async () => {},
981
+ });
982
+ }
983
+
984
+ const alepha = Alepha.create().with(TestCommands);
985
+ const cli = alepha.inject(TestCliProvider);
986
+ const child = cli.testFindCommand("vercel")!;
987
+
988
+ expect(cli.testGetCommandPath(child)).toBe("deploy vercel");
989
+ });
990
+ });
991
+
992
+ // ─────────────────────────────────────────────────────────────────────────────
993
+ // printHelp
994
+ // ─────────────────────────────────────────────────────────────────────────────
995
+
996
+ describe("printHelp", () => {
997
+ it("should print general help when no command provided", () => {
998
+ class TestCommands {
999
+ build = $command({
1000
+ name: "build",
1001
+ description: "Build the project",
1002
+ flags: t.object({}),
1003
+ handler: async () => {},
1004
+ });
1005
+ }
1006
+
1007
+ const alepha = Alepha.create().with(TestCommands);
1008
+ const cli = alepha.inject(TestCliProvider);
1009
+
1010
+ // Just verify it doesn't throw
1011
+ expect(() => cli.printHelp()).not.toThrow();
1012
+ });
1013
+
1014
+ it("should print command-specific help", () => {
1015
+ class TestCommands {
1016
+ build = $command({
1017
+ name: "build",
1018
+ description: "Build the project",
1019
+ flags: t.object({
1020
+ watch: t.optional(t.boolean({ description: "Watch for changes" })),
1021
+ }),
1022
+ handler: async () => {},
1023
+ });
1024
+ }
1025
+
1026
+ const alepha = Alepha.create().with(TestCommands);
1027
+ const cli = alepha.inject(TestCliProvider);
1028
+ const cmd = cli.testFindCommand("build")!;
1029
+
1030
+ expect(() => cli.printHelp(cmd)).not.toThrow();
1031
+ });
1032
+
1033
+ it("should print help for command with children", () => {
1034
+ class TestCommands {
1035
+ deployVercel = $command({
1036
+ name: "vercel",
1037
+ description: "Deploy to Vercel",
1038
+ flags: t.object({}),
1039
+ handler: async () => {},
1040
+ });
1041
+ deploy = $command({
1042
+ name: "deploy",
1043
+ description: "Deploy commands",
1044
+ flags: t.object({}),
1045
+ children: [this.deployVercel],
1046
+ handler: async () => {},
1047
+ });
1048
+ }
1049
+
1050
+ const alepha = Alepha.create().with(TestCommands);
1051
+ const cli = alepha.inject(TestCliProvider);
1052
+ const cmd = cli.testFindCommand("deploy")!;
1053
+
1054
+ expect(() => cli.printHelp(cmd)).not.toThrow();
1055
+ });
1056
+
1057
+ it("should print help for command with env vars", () => {
1058
+ class TestCommands {
1059
+ deploy = $command({
1060
+ name: "deploy",
1061
+ description: "Deploy to production",
1062
+ flags: t.object({}),
1063
+ env: t.object({
1064
+ API_KEY: t.string({ description: "API key for deployment" }),
1065
+ REGION: t.optional(t.string({ description: "Target region" })),
1066
+ }),
1067
+ handler: async () => {},
1068
+ });
1069
+ }
1070
+
1071
+ const alepha = Alepha.create().with(TestCommands);
1072
+ const cli = alepha.inject(TestCliProvider);
1073
+ const cmd = cli.testFindCommand("deploy")!;
1074
+
1075
+ expect(() => cli.printHelp(cmd)).not.toThrow();
1076
+ });
1077
+
1078
+ it("should print help for command with mode option", () => {
1079
+ class TestCommands {
1080
+ build = $command({
1081
+ name: "build",
1082
+ description: "Build for environment",
1083
+ flags: t.object({}),
1084
+ mode: "development",
1085
+ handler: async () => {},
1086
+ });
1087
+ }
1088
+
1089
+ const alepha = Alepha.create().with(TestCommands);
1090
+ const cli = alepha.inject(TestCliProvider);
1091
+ const cmd = cli.testFindCommand("build")!;
1092
+
1093
+ expect(() => cli.printHelp(cmd)).not.toThrow();
1094
+ });
1095
+
1096
+ it("should print help for command with args", () => {
1097
+ class TestCommands {
1098
+ greet = $command({
1099
+ name: "greet",
1100
+ description: "Greet someone",
1101
+ flags: t.object({}),
1102
+ args: t.string({ title: "name" }),
1103
+ handler: async () => {},
1104
+ });
1105
+ }
1106
+
1107
+ const alepha = Alepha.create().with(TestCommands);
1108
+ const cli = alepha.inject(TestCliProvider);
1109
+ const cmd = cli.testFindCommand("greet")!;
1110
+
1111
+ expect(() => cli.printHelp(cmd)).not.toThrow();
1112
+ });
1113
+
1114
+ it("should hide commands with hide option in general help", () => {
1115
+ class TestCommands {
1116
+ visible = $command({
1117
+ name: "visible",
1118
+ description: "Visible command",
1119
+ flags: t.object({}),
1120
+ handler: async () => {},
1121
+ });
1122
+ hidden = $command({
1123
+ name: "hidden",
1124
+ description: "Hidden command",
1125
+ hide: true,
1126
+ flags: t.object({}),
1127
+ handler: async () => {},
1128
+ });
1129
+ }
1130
+
1131
+ const alepha = Alepha.create().with(TestCommands);
1132
+ const cli = alepha.inject(TestCliProvider);
1133
+
1134
+ // Just verify it doesn't throw - hidden commands filtered in getTopLevelCommands
1135
+ expect(() => cli.printHelp()).not.toThrow();
1136
+ });
1137
+
1138
+ it("should include flag aliases in help output", () => {
1139
+ const cli = createTestCli();
1140
+
1141
+ const flagsSchema = t.object({
1142
+ target: t.optional(
1143
+ t.enum(["bare", "docker", "vercel"], {
1144
+ aliases: ["t"],
1145
+ description: "Deployment target",
1146
+ }),
1147
+ ),
1148
+ runtime: t.optional(
1149
+ t.enum(["node", "bun"], {
1150
+ alias: "r", // singular alias
1151
+ description: "JavaScript runtime",
1152
+ }),
1153
+ ),
1154
+ verbose: t.optional(
1155
+ t.boolean({
1156
+ description: "Verbose output",
1157
+ }),
1158
+ ),
1159
+ });
1160
+
1161
+ const flagDefs = cli.testExtractFlagDefs(flagsSchema);
1162
+
1163
+ // target should have key + aliases array
1164
+ const targetFlag = flagDefs.find((f) => f.key === "target");
1165
+ expect(targetFlag?.aliases).toContain("target");
1166
+ expect(targetFlag?.aliases).toContain("t");
1167
+ expect(cli.testFormatFlagStr(targetFlag!.aliases)).toBe("-t, --target");
1168
+
1169
+ // runtime should have key + alias (singular)
1170
+ const runtimeFlag = flagDefs.find((f) => f.key === "runtime");
1171
+ expect(runtimeFlag?.aliases).toContain("runtime");
1172
+ expect(runtimeFlag?.aliases).toContain("r");
1173
+ expect(cli.testFormatFlagStr(runtimeFlag!.aliases)).toBe("-r, --runtime");
1174
+
1175
+ // verbose should only have key (no aliases defined)
1176
+ const verboseFlag = flagDefs.find((f) => f.key === "verbose");
1177
+ expect(verboseFlag?.aliases).toEqual(["verbose"]);
1178
+ expect(cli.testFormatFlagStr(verboseFlag!.aliases)).toBe("--verbose");
1179
+ });
1180
+ });
1181
+
1182
+ // ─────────────────────────────────────────────────────────────────────────────
1183
+ // getEnumValues
1184
+ // ─────────────────────────────────────────────────────────────────────────────
1185
+
1186
+ describe("getEnumValues", () => {
1187
+ it("should extract values from t.enum schema", () => {
1188
+ const cli = createTestCli();
1189
+ const schema = t.enum(["yarn", "npm", "pnpm", "bun"]);
1190
+
1191
+ const values = cli.testGetEnumValues(schema);
1192
+
1193
+ expect(values).toEqual(["yarn", "npm", "pnpm", "bun"]);
1194
+ });
1195
+
1196
+ it("should return undefined for non-enum schemas", () => {
1197
+ const cli = createTestCli();
1198
+
1199
+ expect(cli.testGetEnumValues(t.string())).toBeUndefined();
1200
+ expect(cli.testGetEnumValues(t.boolean())).toBeUndefined();
1201
+ expect(cli.testGetEnumValues(t.number())).toBeUndefined();
1202
+ expect(cli.testGetEnumValues(t.object({}))).toBeUndefined();
1203
+ });
1204
+
1205
+ it("should return undefined for empty or undefined schema", () => {
1206
+ const cli = createTestCli();
1207
+
1208
+ expect(cli.testGetEnumValues(undefined as any)).toBeUndefined();
1209
+ });
1210
+ });
1211
+
1212
+ // ─────────────────────────────────────────────────────────────────────────────
1213
+ // formatFlagDescription
1214
+ // ─────────────────────────────────────────────────────────────────────────────
1215
+
1216
+ describe("formatFlagDescription", () => {
1217
+ it("should append enum values to description", () => {
1218
+ const cli = createTestCli();
1219
+ const schema = t.enum(["yarn", "npm", "pnpm", "bun"]);
1220
+
1221
+ const result = cli.testFormatFlagDescription(
1222
+ "Package manager to use",
1223
+ schema,
1224
+ );
1225
+
1226
+ expect(result).toContain("Package manager to use");
1227
+ expect(result).toContain("yarn");
1228
+ expect(result).toContain("npm");
1229
+ expect(result).toContain("pnpm");
1230
+ expect(result).toContain("bun");
1231
+ });
1232
+
1233
+ it("should return only enum values when description is empty", () => {
1234
+ const cli = createTestCli();
1235
+ const schema = t.enum(["a", "b", "c"]);
1236
+
1237
+ const result = cli.testFormatFlagDescription(undefined, schema);
1238
+
1239
+ expect(result).toContain("a");
1240
+ expect(result).toContain("b");
1241
+ expect(result).toContain("c");
1242
+ });
1243
+
1244
+ it("should return original description for non-enum schemas", () => {
1245
+ const cli = createTestCli();
1246
+
1247
+ expect(cli.testFormatFlagDescription("A string flag", t.string())).toBe(
1248
+ "A string flag",
1249
+ );
1250
+ expect(cli.testFormatFlagDescription("A boolean flag", t.boolean())).toBe(
1251
+ "A boolean flag",
1252
+ );
1253
+ });
1254
+
1255
+ it("should return empty string when no description and no enum", () => {
1256
+ const cli = createTestCli();
1257
+
1258
+ expect(cli.testFormatFlagDescription(undefined, t.string())).toBe("");
1259
+ });
1260
+ });
1261
+
1262
+ // ─────────────────────────────────────────────────────────────────────────────
1263
+ // commands getter
1264
+ // ─────────────────────────────────────────────────────────────────────────────
1265
+
1266
+ describe("commands", () => {
1267
+ it("should return all registered commands", () => {
1268
+ class TestCommands {
1269
+ build = $command({
1270
+ name: "build",
1271
+ flags: t.object({}),
1272
+ handler: async () => {},
1273
+ });
1274
+ test = $command({
1275
+ name: "test",
1276
+ flags: t.object({}),
1277
+ handler: async () => {},
1278
+ });
1279
+ }
1280
+
1281
+ const alepha = Alepha.create().with(TestCommands);
1282
+ const cli = alepha.inject(TestCliProvider);
1283
+
1284
+ expect(cli.commands.length).toBe(2);
1285
+ expect(cli.commands.map((c) => c.name).sort()).toEqual(["build", "test"]);
1286
+ });
1287
+
1288
+ it("should return empty array when no commands", () => {
1289
+ const cli = createTestCli();
1290
+ expect(cli.commands).toEqual([]);
1291
+ });
1292
+ });
1293
+
1294
+ // ─────────────────────────────────────────────────────────────────────────────
1295
+ // run (test helper)
1296
+ // ─────────────────────────────────────────────────────────────────────────────
1297
+
1298
+ describe("run", () => {
1299
+ it("should execute command with string args", async () => {
1300
+ let capturedFlags: Record<string, unknown> = {};
1301
+
1302
+ class TestCommands {
1303
+ build = $command({
1304
+ name: "build",
1305
+ flags: t.object({
1306
+ watch: t.optional(t.boolean()),
1307
+ }),
1308
+ handler: async ({ flags }) => {
1309
+ capturedFlags = flags;
1310
+ },
1311
+ });
1312
+ }
1313
+
1314
+ const alepha = Alepha.create().with(TestCommands);
1315
+ const cli = alepha.inject(CliProvider);
1316
+ const cmd = alepha.inject(TestCommands);
1317
+
1318
+ await cli.run(cmd.build, "--watch");
1319
+
1320
+ expect(capturedFlags.watch).toBe(true);
1321
+ });
1322
+
1323
+ it("should execute command with array args", async () => {
1324
+ let capturedFlags: Record<string, unknown> = {};
1325
+
1326
+ class TestCommands {
1327
+ greet = $command({
1328
+ name: "greet",
1329
+ flags: t.object({
1330
+ name: t.optional(t.string()),
1331
+ }),
1332
+ handler: async ({ flags }) => {
1333
+ capturedFlags = flags;
1334
+ },
1335
+ });
1336
+ }
1337
+
1338
+ const alepha = Alepha.create().with(TestCommands);
1339
+ const cli = alepha.inject(CliProvider);
1340
+ const cmd = alepha.inject(TestCommands);
1341
+
1342
+ await cli.run(cmd.greet, ["--name", "World"]);
1343
+
1344
+ expect(capturedFlags.name).toBe("World");
1345
+ });
1346
+
1347
+ it("should execute command with options object", async () => {
1348
+ let capturedRoot = "";
1349
+
1350
+ class TestCommands {
1351
+ init = $command({
1352
+ name: "init",
1353
+ flags: t.object({}),
1354
+ handler: async ({ root }) => {
1355
+ capturedRoot = root;
1356
+ },
1357
+ });
1358
+ }
1359
+
1360
+ const alepha = Alepha.create().with(TestCommands);
1361
+ const cli = alepha.inject(CliProvider);
1362
+ const cmd = alepha.inject(TestCommands);
1363
+
1364
+ await cli.run(cmd.init, { root: "/custom/path" });
1365
+
1366
+ expect(capturedRoot).toBe("/custom/path");
1367
+ });
1368
+
1369
+ it("should parse command args", async () => {
1370
+ let capturedArgs: unknown;
1371
+
1372
+ class TestCommands {
1373
+ greet = $command({
1374
+ name: "greet",
1375
+ flags: t.object({}),
1376
+ args: t.string(),
1377
+ handler: async ({ args }) => {
1378
+ capturedArgs = args;
1379
+ },
1380
+ });
1381
+ }
1382
+
1383
+ const alepha = Alepha.create().with(TestCommands);
1384
+ const cli = alepha.inject(CliProvider);
1385
+ const cmd = alepha.inject(TestCommands);
1386
+
1387
+ await cli.run(cmd.greet, "World");
1388
+
1389
+ expect(capturedArgs).toBe("World");
1390
+ });
1391
+ });
1392
+ });