alepha 0.14.1 → 0.14.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 (402) hide show
  1. package/README.md +3 -3
  2. package/dist/api/audits/index.browser.js +5 -5
  3. package/dist/api/audits/index.browser.js.map +1 -1
  4. package/dist/api/audits/index.d.ts +784 -784
  5. package/dist/api/audits/index.d.ts.map +1 -1
  6. package/dist/api/audits/index.js +13 -13
  7. package/dist/api/audits/index.js.map +1 -1
  8. package/dist/api/files/index.browser.js +5 -5
  9. package/dist/api/files/index.browser.js.map +1 -1
  10. package/dist/api/files/index.d.ts +57 -57
  11. package/dist/api/files/index.d.ts.map +1 -1
  12. package/dist/api/files/index.js +71 -71
  13. package/dist/api/files/index.js.map +1 -1
  14. package/dist/api/jobs/index.browser.js +5 -5
  15. package/dist/api/jobs/index.browser.js.map +1 -1
  16. package/dist/api/jobs/index.d.ts +165 -165
  17. package/dist/api/jobs/index.d.ts.map +1 -1
  18. package/dist/api/jobs/index.js +10 -10
  19. package/dist/api/jobs/index.js.map +1 -1
  20. package/dist/api/notifications/index.browser.js +10 -10
  21. package/dist/api/notifications/index.browser.js.map +1 -1
  22. package/dist/api/notifications/index.d.ts +583 -171
  23. package/dist/api/notifications/index.d.ts.map +1 -1
  24. package/dist/api/notifications/index.js +12 -12
  25. package/dist/api/notifications/index.js.map +1 -1
  26. package/dist/api/parameters/index.browser.js +163 -10
  27. package/dist/api/parameters/index.browser.js.map +1 -1
  28. package/dist/api/parameters/index.d.ts +281 -276
  29. package/dist/api/parameters/index.d.ts.map +1 -1
  30. package/dist/api/parameters/index.js +196 -91
  31. package/dist/api/parameters/index.js.map +1 -1
  32. package/dist/api/users/index.browser.js +19 -19
  33. package/dist/api/users/index.browser.js.map +1 -1
  34. package/dist/api/users/index.d.ts +778 -764
  35. package/dist/api/users/index.d.ts.map +1 -1
  36. package/dist/api/users/index.js +831 -596
  37. package/dist/api/users/index.js.map +1 -1
  38. package/dist/api/verifications/index.browser.js +6 -6
  39. package/dist/api/verifications/index.browser.js.map +1 -1
  40. package/dist/api/verifications/index.d.ts +125 -125
  41. package/dist/api/verifications/index.d.ts.map +1 -1
  42. package/dist/api/verifications/index.js +6 -6
  43. package/dist/api/verifications/index.js.map +1 -1
  44. package/dist/batch/index.js.map +1 -1
  45. package/dist/bin/index.d.ts +1 -2
  46. package/dist/bin/index.js +0 -1
  47. package/dist/bin/index.js.map +1 -1
  48. package/dist/cache/core/index.js.map +1 -1
  49. package/dist/cli/index.d.ts +249 -218
  50. package/dist/cli/index.d.ts.map +1 -1
  51. package/dist/cli/index.js +951 -821
  52. package/dist/cli/index.js.map +1 -1
  53. package/dist/command/index.d.ts +40 -0
  54. package/dist/command/index.d.ts.map +1 -1
  55. package/dist/command/index.js +97 -17
  56. package/dist/command/index.js.map +1 -1
  57. package/dist/core/index.browser.js +14 -18
  58. package/dist/core/index.browser.js.map +1 -1
  59. package/dist/core/index.d.ts +29 -0
  60. package/dist/core/index.d.ts.map +1 -1
  61. package/dist/core/index.js +21 -24
  62. package/dist/core/index.js.map +1 -1
  63. package/dist/core/index.native.js +21 -24
  64. package/dist/core/index.native.js.map +1 -1
  65. package/dist/datetime/index.js.map +1 -1
  66. package/dist/fake/index.js +195 -168
  67. package/dist/fake/index.js.map +1 -1
  68. package/dist/file/index.d.ts +8 -0
  69. package/dist/file/index.d.ts.map +1 -1
  70. package/dist/file/index.js +3 -0
  71. package/dist/file/index.js.map +1 -1
  72. package/dist/lock/redis/index.js.map +1 -1
  73. package/dist/logger/index.js.map +1 -1
  74. package/dist/mcp/index.d.ts.map +1 -1
  75. package/dist/mcp/index.js.map +1 -1
  76. package/dist/orm/index.browser.js +26 -5
  77. package/dist/orm/index.browser.js.map +1 -1
  78. package/dist/orm/index.d.ts +146 -121
  79. package/dist/orm/index.d.ts.map +1 -1
  80. package/dist/orm/index.js +49 -24
  81. package/dist/orm/index.js.map +1 -1
  82. package/dist/redis/index.js.map +1 -1
  83. package/dist/retry/index.js.map +1 -1
  84. package/dist/router/index.js.map +1 -1
  85. package/dist/scheduler/index.d.ts +6 -6
  86. package/dist/scheduler/index.js.map +1 -1
  87. package/dist/security/index.d.ts +29 -29
  88. package/dist/security/index.d.ts.map +1 -1
  89. package/dist/security/index.js +1 -1
  90. package/dist/security/index.js.map +1 -1
  91. package/dist/server/auth/index.d.ts +171 -155
  92. package/dist/server/auth/index.d.ts.map +1 -1
  93. package/dist/server/auth/index.js +0 -1
  94. package/dist/server/auth/index.js.map +1 -1
  95. package/dist/server/cache/index.js.map +1 -1
  96. package/dist/server/compress/index.d.ts.map +1 -1
  97. package/dist/server/compress/index.js +2 -0
  98. package/dist/server/compress/index.js.map +1 -1
  99. package/dist/server/cookies/index.browser.js.map +1 -1
  100. package/dist/server/cookies/index.js.map +1 -1
  101. package/dist/server/core/index.browser.js.map +1 -1
  102. package/dist/server/core/index.d.ts.map +1 -1
  103. package/dist/server/core/index.js +1 -1
  104. package/dist/server/core/index.js.map +1 -1
  105. package/dist/server/health/index.d.ts +17 -17
  106. package/dist/server/helmet/index.js.map +1 -1
  107. package/dist/server/links/index.browser.js +22 -6
  108. package/dist/server/links/index.browser.js.map +1 -1
  109. package/dist/server/links/index.d.ts +46 -44
  110. package/dist/server/links/index.d.ts.map +1 -1
  111. package/dist/server/links/index.js +24 -41
  112. package/dist/server/links/index.js.map +1 -1
  113. package/dist/server/multipart/index.js.map +1 -1
  114. package/dist/server/rate-limit/index.js.map +1 -1
  115. package/dist/server/security/index.js.map +1 -1
  116. package/dist/server/swagger/index.d.ts +2 -1
  117. package/dist/server/swagger/index.d.ts.map +1 -1
  118. package/dist/server/swagger/index.js +8 -3
  119. package/dist/server/swagger/index.js.map +1 -1
  120. package/dist/thread/index.js.map +1 -1
  121. package/dist/topic/core/index.js.map +1 -1
  122. package/dist/vite/index.d.ts.map +1 -1
  123. package/dist/vite/index.js +12 -4
  124. package/dist/vite/index.js.map +1 -1
  125. package/dist/websocket/index.browser.js.map +1 -1
  126. package/dist/websocket/index.js.map +1 -1
  127. package/package.json +7 -7
  128. package/src/api/audits/controllers/{AuditController.ts → AdminAuditController.ts} +5 -6
  129. package/src/api/audits/entities/audits.ts +5 -5
  130. package/src/api/audits/index.browser.ts +1 -1
  131. package/src/api/audits/index.ts +3 -3
  132. package/src/api/audits/primitives/$audit.spec.ts +276 -0
  133. package/src/api/audits/services/AuditService.spec.ts +495 -0
  134. package/src/api/files/__tests__/$bucket.spec.ts +91 -0
  135. package/src/api/files/controllers/AdminFileStatsController.spec.ts +166 -0
  136. package/src/api/files/controllers/{StorageStatsController.ts → AdminFileStatsController.ts} +2 -2
  137. package/src/api/files/controllers/FileController.spec.ts +558 -0
  138. package/src/api/files/controllers/FileController.ts +4 -5
  139. package/src/api/files/entities/files.ts +5 -5
  140. package/src/api/files/index.browser.ts +1 -1
  141. package/src/api/files/index.ts +4 -4
  142. package/src/api/files/jobs/FileJobs.spec.ts +52 -0
  143. package/src/api/files/services/FileService.spec.ts +109 -0
  144. package/src/api/jobs/__tests__/JobController.spec.ts +343 -0
  145. package/src/api/jobs/controllers/{JobController.ts → AdminJobController.ts} +2 -2
  146. package/src/api/jobs/entities/jobExecutions.ts +5 -5
  147. package/src/api/jobs/index.ts +3 -3
  148. package/src/api/jobs/primitives/$job.spec.ts +476 -0
  149. package/src/api/notifications/controllers/{NotificationController.ts → AdminNotificationController.ts} +4 -5
  150. package/src/api/notifications/entities/notifications.ts +5 -5
  151. package/src/api/notifications/index.browser.ts +1 -1
  152. package/src/api/notifications/index.ts +4 -4
  153. package/src/api/parameters/controllers/{ConfigController.ts → AdminConfigController.ts} +46 -107
  154. package/src/api/parameters/entities/parameters.ts +7 -17
  155. package/src/api/parameters/index.ts +3 -3
  156. package/src/api/parameters/primitives/$config.spec.ts +356 -0
  157. package/src/api/parameters/schemas/activateConfigBodySchema.ts +12 -0
  158. package/src/api/parameters/schemas/checkScheduledResponseSchema.ts +8 -0
  159. package/src/api/parameters/schemas/configCurrentResponseSchema.ts +13 -0
  160. package/src/api/parameters/schemas/configHistoryResponseSchema.ts +9 -0
  161. package/src/api/parameters/schemas/configNameParamSchema.ts +10 -0
  162. package/src/api/parameters/schemas/configNamesResponseSchema.ts +8 -0
  163. package/src/api/parameters/schemas/configTreeNodeSchema.ts +13 -0
  164. package/src/api/parameters/schemas/configVersionParamSchema.ts +9 -0
  165. package/src/api/parameters/schemas/configVersionResponseSchema.ts +9 -0
  166. package/src/api/parameters/schemas/configsByStatusResponseSchema.ts +9 -0
  167. package/src/api/parameters/schemas/createConfigVersionBodySchema.ts +24 -0
  168. package/src/api/parameters/schemas/index.ts +15 -0
  169. package/src/api/parameters/schemas/parameterResponseSchema.ts +26 -0
  170. package/src/api/parameters/schemas/parameterStatusSchema.ts +13 -0
  171. package/src/api/parameters/schemas/rollbackConfigBodySchema.ts +15 -0
  172. package/src/api/parameters/schemas/statusParamSchema.ts +9 -0
  173. package/src/api/users/__tests__/EmailVerification.spec.ts +369 -0
  174. package/src/api/users/__tests__/PasswordReset.spec.ts +550 -0
  175. package/src/api/users/controllers/AdminIdentityController.spec.ts +365 -0
  176. package/src/api/users/controllers/{IdentityController.ts → AdminIdentityController.ts} +3 -4
  177. package/src/api/users/controllers/AdminSessionController.spec.ts +274 -0
  178. package/src/api/users/controllers/{SessionController.ts → AdminSessionController.ts} +3 -4
  179. package/src/api/users/controllers/AdminUserController.spec.ts +372 -0
  180. package/src/api/users/controllers/AdminUserController.ts +116 -0
  181. package/src/api/users/controllers/UserController.ts +4 -107
  182. package/src/api/users/controllers/UserRealmController.ts +3 -0
  183. package/src/api/users/entities/identities.ts +6 -6
  184. package/src/api/users/entities/sessions.ts +6 -6
  185. package/src/api/users/entities/users.ts +9 -9
  186. package/src/api/users/index.ts +13 -6
  187. package/src/api/users/primitives/$userRealm.ts +13 -8
  188. package/src/api/users/services/CredentialService.spec.ts +509 -0
  189. package/src/api/users/services/CredentialService.ts +46 -0
  190. package/src/api/users/services/IdentityService.ts +15 -0
  191. package/src/api/users/services/RegistrationService.spec.ts +630 -0
  192. package/src/api/users/services/RegistrationService.ts +18 -0
  193. package/src/api/users/services/SessionService.spec.ts +301 -0
  194. package/src/api/users/services/SessionService.ts +110 -1
  195. package/src/api/users/services/UserService.ts +67 -2
  196. package/src/api/verifications/__tests__/CodeVerification.spec.ts +318 -0
  197. package/src/api/verifications/__tests__/LinkVerification.spec.ts +279 -0
  198. package/src/api/verifications/entities/verifications.ts +6 -6
  199. package/src/api/verifications/jobs/VerificationJobs.spec.ts +50 -0
  200. package/src/batch/__tests__/startup-buffering.spec.ts +458 -0
  201. package/src/batch/primitives/$batch.spec.ts +766 -0
  202. package/src/batch/providers/BatchProvider.spec.ts +786 -0
  203. package/src/bin/index.ts +0 -1
  204. package/src/bucket/__tests__/shared.ts +194 -0
  205. package/src/bucket/primitives/$bucket.spec.ts +104 -0
  206. package/src/bucket/providers/FileStorageProvider.spec.ts +13 -0
  207. package/src/bucket/providers/LocalFileStorageProvider.spec.ts +77 -0
  208. package/src/bucket/providers/MemoryFileStorageProvider.spec.ts +82 -0
  209. package/src/cache/core/__tests__/shared.ts +377 -0
  210. package/src/cache/core/primitives/$cache.spec.ts +111 -0
  211. package/src/cache/redis/__tests__/cache-redis.spec.ts +70 -0
  212. package/src/cli/apps/AlephaCli.ts +54 -16
  213. package/src/cli/apps/AlephaPackageBuilderCli.ts +2 -1
  214. package/src/cli/assets/appRouterTs.ts +1 -1
  215. package/src/cli/commands/{ViteCommands.ts → build.ts} +2 -105
  216. package/src/cli/commands/clean.ts +14 -0
  217. package/src/cli/commands/{DrizzleCommands.ts → db.ts} +10 -117
  218. package/src/cli/commands/{DeployCommands.ts → deploy.ts} +1 -1
  219. package/src/cli/commands/dev.ts +69 -0
  220. package/src/cli/commands/format.ts +17 -0
  221. package/src/cli/commands/gen/changelog.spec.ts +315 -0
  222. package/src/cli/commands/{ChangelogCommands.ts → gen/changelog.ts} +16 -31
  223. package/src/cli/commands/gen/openapi.ts +71 -0
  224. package/src/cli/commands/gen.ts +18 -0
  225. package/src/cli/commands/{CoreCommands.ts → init.ts} +4 -40
  226. package/src/cli/commands/lint.ts +17 -0
  227. package/src/cli/commands/root.ts +41 -0
  228. package/src/cli/commands/run.ts +24 -0
  229. package/src/cli/commands/test.ts +42 -0
  230. package/src/cli/commands/typecheck.ts +24 -0
  231. package/src/cli/commands/{VerifyCommands.ts → verify.ts} +1 -13
  232. package/src/cli/defineConfig.ts +10 -1
  233. package/src/cli/index.ts +17 -7
  234. package/src/cli/services/AlephaCliUtils.ts +71 -32
  235. package/src/cli/services/GitMessageParser.ts +1 -1
  236. package/src/command/helpers/Asker.spec.ts +127 -0
  237. package/src/command/helpers/Runner.spec.ts +126 -0
  238. package/src/command/primitives/$command.spec.ts +1588 -0
  239. package/src/command/providers/CliProvider.ts +74 -24
  240. package/src/core/Alepha.ts +52 -4
  241. package/src/core/__tests__/Alepha-emit.spec.ts +22 -0
  242. package/src/core/__tests__/Alepha-graph.spec.ts +93 -0
  243. package/src/core/__tests__/Alepha-has.spec.ts +41 -0
  244. package/src/core/__tests__/Alepha-inject.spec.ts +93 -0
  245. package/src/core/__tests__/Alepha-register.spec.ts +81 -0
  246. package/src/core/__tests__/Alepha-start.spec.ts +176 -0
  247. package/src/core/__tests__/Alepha-with.spec.ts +14 -0
  248. package/src/core/__tests__/TypeBox-usecases.spec.ts +35 -0
  249. package/src/core/__tests__/TypeBoxLocale.spec.ts +15 -0
  250. package/src/core/__tests__/descriptor.spec.ts +34 -0
  251. package/src/core/__tests__/fixtures/A.ts +5 -0
  252. package/src/core/__tests__/pagination.spec.ts +77 -0
  253. package/src/core/helpers/jsonSchemaToTypeBox.ts +2 -2
  254. package/src/core/primitives/$atom.spec.ts +43 -0
  255. package/src/core/primitives/$hook.spec.ts +130 -0
  256. package/src/core/primitives/$inject.spec.ts +175 -0
  257. package/src/core/primitives/$module.spec.ts +115 -0
  258. package/src/core/providers/CodecManager.spec.ts +740 -0
  259. package/src/core/providers/EventManager.spec.ts +762 -0
  260. package/src/core/providers/EventManager.ts +4 -0
  261. package/src/core/providers/StateManager.spec.ts +365 -0
  262. package/src/core/providers/TypeProvider.spec.ts +1607 -0
  263. package/src/core/providers/TypeProvider.ts +20 -26
  264. package/src/datetime/primitives/$interval.spec.ts +103 -0
  265. package/src/datetime/providers/DateTimeProvider.spec.ts +86 -0
  266. package/src/email/primitives/$email.spec.ts +175 -0
  267. package/src/email/providers/LocalEmailProvider.spec.ts +341 -0
  268. package/src/fake/__tests__/keyName.example.ts +40 -0
  269. package/src/fake/__tests__/keyName.spec.ts +152 -0
  270. package/src/fake/__tests__/module.example.ts +32 -0
  271. package/src/fake/providers/FakeProvider.spec.ts +438 -0
  272. package/src/file/providers/FileSystemProvider.ts +8 -0
  273. package/src/file/providers/NodeFileSystemProvider.spec.ts +418 -0
  274. package/src/file/providers/NodeFileSystemProvider.ts +5 -0
  275. package/src/file/services/FileDetector.spec.ts +591 -0
  276. package/src/lock/core/__tests__/shared.ts +190 -0
  277. package/src/lock/core/providers/MemoryLockProvider.spec.ts +25 -0
  278. package/src/lock/redis/providers/RedisLockProvider.spec.ts +25 -0
  279. package/src/logger/__tests__/SimpleFormatterProvider.spec.ts +109 -0
  280. package/src/logger/primitives/$logger.spec.ts +108 -0
  281. package/src/logger/services/Logger.spec.ts +295 -0
  282. package/src/mcp/__tests__/errors.spec.ts +175 -0
  283. package/src/mcp/__tests__/integration.spec.ts +450 -0
  284. package/src/mcp/helpers/jsonrpc.spec.ts +380 -0
  285. package/src/mcp/primitives/$prompt.spec.ts +468 -0
  286. package/src/mcp/primitives/$resource.spec.ts +390 -0
  287. package/src/mcp/primitives/$tool.spec.ts +406 -0
  288. package/src/mcp/providers/McpServerProvider.spec.ts +797 -0
  289. package/src/orm/__tests__/$repository-crud.spec.ts +276 -0
  290. package/src/orm/__tests__/$repository-hooks.spec.ts +325 -0
  291. package/src/orm/__tests__/$repository-orderBy.spec.ts +128 -0
  292. package/src/orm/__tests__/$repository-pagination-sort.spec.ts +149 -0
  293. package/src/orm/__tests__/$repository-save.spec.ts +37 -0
  294. package/src/orm/__tests__/ModelBuilder-integration.spec.ts +490 -0
  295. package/src/orm/__tests__/ModelBuilder-types.spec.ts +186 -0
  296. package/src/orm/__tests__/PostgresProvider.spec.ts +46 -0
  297. package/src/orm/__tests__/delete-returning.spec.ts +256 -0
  298. package/src/orm/__tests__/deletedAt.spec.ts +80 -0
  299. package/src/orm/__tests__/enums.spec.ts +315 -0
  300. package/src/orm/__tests__/execute.spec.ts +72 -0
  301. package/src/orm/__tests__/fixtures/bigEntitySchema.ts +65 -0
  302. package/src/orm/__tests__/fixtures/userEntitySchema.ts +27 -0
  303. package/src/orm/__tests__/joins.spec.ts +1114 -0
  304. package/src/orm/__tests__/page.spec.ts +287 -0
  305. package/src/orm/__tests__/primaryKey.spec.ts +87 -0
  306. package/src/orm/__tests__/query-date-encoding.spec.ts +402 -0
  307. package/src/orm/__tests__/ref-auto-onDelete.spec.ts +156 -0
  308. package/src/orm/__tests__/references.spec.ts +102 -0
  309. package/src/orm/__tests__/security.spec.ts +710 -0
  310. package/src/orm/__tests__/sqlite.spec.ts +111 -0
  311. package/src/orm/__tests__/string-operators.spec.ts +429 -0
  312. package/src/orm/__tests__/timestamps.spec.ts +388 -0
  313. package/src/orm/__tests__/validation.spec.ts +183 -0
  314. package/src/orm/__tests__/version.spec.ts +64 -0
  315. package/src/orm/helpers/parseQueryString.spec.ts +196 -0
  316. package/src/orm/index.browser.ts +1 -1
  317. package/src/orm/index.ts +10 -6
  318. package/src/orm/primitives/$repository.spec.ts +137 -0
  319. package/src/orm/primitives/$sequence.spec.ts +29 -0
  320. package/src/orm/primitives/$transaction.spec.ts +82 -0
  321. package/src/orm/providers/{PostgresTypeProvider.ts → DatabaseTypeProvider.ts} +25 -3
  322. package/src/orm/providers/drivers/BunPostgresProvider.ts +3 -3
  323. package/src/orm/providers/drivers/BunSqliteProvider.ts +1 -1
  324. package/src/orm/providers/drivers/CloudflareD1Provider.ts +1 -1
  325. package/src/orm/providers/drivers/DatabaseProvider.ts +1 -1
  326. package/src/orm/providers/drivers/NodePostgresProvider.ts +3 -3
  327. package/src/orm/providers/drivers/NodeSqliteProvider.ts +1 -1
  328. package/src/orm/providers/drivers/PglitePostgresProvider.ts +2 -2
  329. package/src/orm/services/ModelBuilder.spec.ts +575 -0
  330. package/src/orm/services/Repository.spec.ts +137 -0
  331. package/src/queue/core/__tests__/shared.ts +143 -0
  332. package/src/queue/core/providers/MemoryQueueProvider.spec.ts +23 -0
  333. package/src/queue/core/providers/WorkerProvider.spec.ts +378 -0
  334. package/src/queue/redis/providers/RedisQueueProvider.spec.ts +23 -0
  335. package/src/redis/__tests__/redis.spec.ts +58 -0
  336. package/src/retry/primitives/$retry.spec.ts +234 -0
  337. package/src/retry/providers/RetryProvider.spec.ts +438 -0
  338. package/src/router/__tests__/match.spec.ts +252 -0
  339. package/src/router/providers/RouterProvider.spec.ts +197 -0
  340. package/src/scheduler/__tests__/$scheduler-cron.spec.ts +25 -0
  341. package/src/scheduler/__tests__/$scheduler-interval.spec.ts +25 -0
  342. package/src/scheduler/__tests__/shared.ts +77 -0
  343. package/src/security/__tests__/bug-1-wildcard-after-start.spec.ts +229 -0
  344. package/src/security/__tests__/bug-2-password-validation.spec.ts +245 -0
  345. package/src/security/__tests__/bug-3-regex-vulnerability.spec.ts +407 -0
  346. package/src/security/__tests__/bug-4-oauth2-validation.spec.ts +439 -0
  347. package/src/security/__tests__/multi-layer-permissions.spec.ts +522 -0
  348. package/src/security/primitives/$permission.spec.ts +30 -0
  349. package/src/security/primitives/$permission.ts +2 -2
  350. package/src/security/primitives/$realm.spec.ts +101 -0
  351. package/src/security/primitives/$role.spec.ts +52 -0
  352. package/src/security/primitives/$serviceAccount.spec.ts +61 -0
  353. package/src/security/providers/SecurityProvider.spec.ts +350 -0
  354. package/src/server/auth/providers/ServerAuthProvider.ts +0 -2
  355. package/src/server/cache/providers/ServerCacheProvider.spec.ts +942 -0
  356. package/src/server/compress/providers/ServerCompressProvider.spec.ts +31 -0
  357. package/src/server/compress/providers/ServerCompressProvider.ts +2 -0
  358. package/src/server/cookies/providers/ServerCookiesProvider.spec.ts +253 -0
  359. package/src/server/core/__tests__/ServerRouterProvider-getRoutes.spec.ts +334 -0
  360. package/src/server/core/__tests__/ServerRouterProvider-requestId.spec.ts +129 -0
  361. package/src/server/core/primitives/$action.spec.ts +191 -0
  362. package/src/server/core/primitives/$route.spec.ts +65 -0
  363. package/src/server/core/providers/ServerBodyParserProvider.spec.ts +93 -0
  364. package/src/server/core/providers/ServerLoggerProvider.spec.ts +100 -0
  365. package/src/server/core/providers/ServerProvider.ts +3 -1
  366. package/src/server/core/services/HttpClient.spec.ts +123 -0
  367. package/src/server/core/services/UserAgentParser.spec.ts +111 -0
  368. package/src/server/cors/providers/ServerCorsProvider.spec.ts +481 -0
  369. package/src/server/health/providers/ServerHealthProvider.spec.ts +22 -0
  370. package/src/server/helmet/providers/ServerHelmetProvider.spec.ts +105 -0
  371. package/src/server/links/__tests__/$action.spec.ts +238 -0
  372. package/src/server/links/__tests__/fixtures/CrudApp.ts +122 -0
  373. package/src/server/links/__tests__/requestId.spec.ts +120 -0
  374. package/src/server/links/primitives/$remote.spec.ts +228 -0
  375. package/src/server/links/providers/LinkProvider.spec.ts +54 -0
  376. package/src/server/links/providers/LinkProvider.ts +49 -3
  377. package/src/server/links/providers/ServerLinksProvider.ts +1 -53
  378. package/src/server/links/schemas/apiLinksResponseSchema.ts +7 -0
  379. package/src/server/metrics/providers/ServerMetricsProvider.spec.ts +25 -0
  380. package/src/server/multipart/providers/ServerMultipartProvider.spec.ts +528 -0
  381. package/src/server/proxy/primitives/$proxy.spec.ts +87 -0
  382. package/src/server/rate-limit/__tests__/ActionRateLimit.spec.ts +211 -0
  383. package/src/server/rate-limit/providers/ServerRateLimitProvider.spec.ts +344 -0
  384. package/src/server/security/__tests__/BasicAuth.spec.ts +684 -0
  385. package/src/server/security/__tests__/ServerSecurityProvider-realm.spec.ts +388 -0
  386. package/src/server/security/providers/ServerSecurityProvider.spec.ts +123 -0
  387. package/src/server/static/primitives/$serve.spec.ts +193 -0
  388. package/src/server/swagger/__tests__/ui.spec.ts +52 -0
  389. package/src/server/swagger/primitives/$swagger.spec.ts +193 -0
  390. package/src/server/swagger/providers/ServerSwaggerProvider.ts +18 -8
  391. package/src/sms/primitives/$sms.spec.ts +165 -0
  392. package/src/sms/providers/LocalSmsProvider.spec.ts +224 -0
  393. package/src/sms/providers/MemorySmsProvider.spec.ts +193 -0
  394. package/src/thread/primitives/$thread.spec.ts +186 -0
  395. package/src/topic/core/__tests__/shared.ts +144 -0
  396. package/src/topic/core/providers/MemoryTopicProvider.spec.ts +23 -0
  397. package/src/topic/redis/providers/RedisTopicProvider.spec.ts +23 -0
  398. package/src/vite/plugins/viteAlephaDev.ts +16 -4
  399. package/src/vite/tasks/runAlepha.ts +7 -1
  400. package/src/websocket/__tests__/$websocket-new.spec.ts +195 -0
  401. package/src/websocket/primitives/$channel.spec.ts +30 -0
  402. package/src/cli/commands/BiomeCommands.ts +0 -29
package/dist/cli/index.js CHANGED
@@ -1,20 +1,20 @@
1
1
  import { createRequire } from "node:module";
2
- import { join } from "node:path";
3
- import { $atom, $env, $hook, $inject, $module, $use, Alepha, AlephaError, OPTIONS, t } from "alepha";
2
+ import { $atom, $hook, $inject, $module, $use, Alepha, AlephaError, OPTIONS, t } from "alepha";
4
3
  import { FileSystemProvider } from "alepha/file";
5
- import { $command, CliProvider, EnvUtils } from "alepha/command";
6
- import { $logger } from "alepha/logger";
7
- import { exec, spawn } from "node:child_process";
8
4
  import { access, mkdir, readFile, readdir, unlink, writeFile } from "node:fs/promises";
5
+ import { join } from "node:path";
6
+ import { $command, CliProvider, EnvUtils } from "alepha/command";
7
+ import { $logger, ConsoleColorProvider } from "alepha/logger";
9
8
  import { boot, buildClient, buildServer, copyAssets, generateCloudflare, generateDocker, generateSitemap, generateVercel, prerenderPages } from "alepha/vite";
10
- import { tsImport } from "tsx/esm/api";
9
+ import { exec, spawn } from "node:child_process";
11
10
  import { readFileSync } from "node:fs";
12
11
  import { promisify } from "node:util";
12
+ import { ServerSwaggerProvider } from "alepha/server/swagger";
13
13
  import * as os from "node:os";
14
14
 
15
15
  //#region ../../src/cli/assets/appRouterTs.ts
16
16
  const appRouterTs = () => `
17
- import { $page } from "@alepha/react";
17
+ import { $page } from "@alepha/react/router";
18
18
 
19
19
  export class AppRouter {
20
20
  home = $page({
@@ -174,6 +174,7 @@ var AlephaCliUtils = class {
174
174
  log = $logger();
175
175
  fs = $inject(FileSystemProvider);
176
176
  envUtils = $inject(EnvUtils);
177
+ alepha = $inject(Alepha);
177
178
  /**
178
179
  * Execute a command using npx with inherited stdio.
179
180
  *
@@ -235,6 +236,34 @@ var AlephaCliUtils = class {
235
236
  this.log.debug(`Config file written: ${path}`);
236
237
  return path;
237
238
  }
239
+ async removeFiles(root, files) {
240
+ await Promise.all(files.map((file) => this.fs.rm(join(root, file), {
241
+ force: true,
242
+ recursive: true
243
+ })));
244
+ }
245
+ async removeYarn(root) {
246
+ await this.removeFiles(root, [
247
+ ".yarn",
248
+ ".yarnrc.yml",
249
+ ".yarn"
250
+ ]);
251
+ }
252
+ async removePnpm(root) {
253
+ await this.removeFiles(root, ["pnpm-lock.yaml", "pnpm-workspace.yaml"]);
254
+ }
255
+ async removeNpm(root) {
256
+ await this.removeFiles(root, ["package-lock.json"]);
257
+ }
258
+ async removeBun(root) {
259
+ await this.removeFiles(root, ["bun.lockb"]);
260
+ }
261
+ async removeAllPmFilesExcept(root, except) {
262
+ if (except !== "yarn") await this.removeYarn(root);
263
+ if (except !== "pnpm") await this.removePnpm(root);
264
+ if (except !== "npm") await this.removeNpm(root);
265
+ if (except !== "bun") await this.removeBun(root);
266
+ }
238
267
  /**
239
268
  * Ensure Yarn is configured in the project directory.
240
269
  *
@@ -244,26 +273,16 @@ var AlephaCliUtils = class {
244
273
  */
245
274
  async ensureYarn(root) {
246
275
  await this.ensureFileExists(root, ".yarnrc.yml", "nodeLinker: node-modules", false);
247
- await this.fs.rm(join(root, "package-lock.json"), { force: true });
248
- await this.fs.rm(join(root, "pnpm-lock.yaml"), { force: true });
276
+ await this.removeAllPmFilesExcept(root, "yarn");
277
+ }
278
+ async ensureBun(root) {
279
+ await this.removeAllPmFilesExcept(root, "bun");
249
280
  }
250
281
  async ensurePnpm(root) {
251
- await this.fs.rm(join(root, "package-lock.json"), { force: true });
252
- await this.fs.rm(join(root, "yarn.lock"), { force: true });
253
- await this.fs.rm(join(root, ".yarn"), {
254
- force: true,
255
- recursive: true
256
- });
257
- await this.fs.rm(join(root, ".yarnrc.yml"), { force: true });
282
+ await this.removeAllPmFilesExcept(root, "pnpm");
258
283
  }
259
284
  async ensureNpm(root) {
260
- await this.fs.rm(join(root, "pnpm-lock.yaml"), { force: true });
261
- await this.fs.rm(join(root, "yarn.lock"), { force: true });
262
- await this.fs.rm(join(root, ".yarn"), {
263
- force: true,
264
- recursive: true
265
- });
266
- await this.fs.rm(join(root, ".yarnrc.yml"), { force: true });
285
+ await this.removeAllPmFilesExcept(root, "npm");
267
286
  }
268
287
  /**
269
288
  * Generate package.json content with Alepha dependencies.
@@ -418,7 +437,8 @@ var AlephaCliUtils = class {
418
437
  async loadAlephaFromServerEntryFile(rootDir, explicitEntry) {
419
438
  process.env.ALEPHA_CLI_IMPORT = "true";
420
439
  const entry = await boot.getServerEntry(rootDir, explicitEntry);
421
- const mod = await tsImport(entry, { parentURL: import.meta.url });
440
+ delete global.__alepha;
441
+ const mod = await import(entry);
422
442
  this.log.debug(`Load entry: ${entry}`);
423
443
  if (mod.default instanceof Alepha) return {
424
444
  alepha: mod.default,
@@ -470,6 +490,7 @@ ${models.map((it) => `export const ${it} = models["${it}"];`).join("\n")}
470
490
  if (flags?.pnpm) return "pnpm";
471
491
  if (flags?.npm) return "npm";
472
492
  if (flags?.bun) return "bun";
493
+ if (this.alepha.isBun()) return "bun";
473
494
  if (await this.checkFileExists(root, "yarn.lock", true)) return "yarn";
474
495
  if (await this.checkFileExists(root, "pnpm-lock.yaml", true)) return "pnpm";
475
496
  return "npm";
@@ -554,6 +575,23 @@ ${models.map((it) => `export const ${it} = models["${it}"];`).join("\n")}
554
575
  async hasExpo(root) {
555
576
  return this.hasDependency(root, "expo");
556
577
  }
578
+ async getInstallCommand(root, packageName, dev = true) {
579
+ const pm = await this.getPackageManager(root);
580
+ let cmd;
581
+ switch (pm) {
582
+ case "yarn":
583
+ cmd = `yarn add ${dev ? "-D" : ""} ${packageName}`;
584
+ break;
585
+ case "pnpm":
586
+ cmd = `pnpm add ${dev ? "-D" : ""} ${packageName}`;
587
+ break;
588
+ case "bun":
589
+ cmd = `bun add ${dev ? "-d" : ""} ${packageName}`;
590
+ break;
591
+ default: cmd = `npm install ${dev ? "--save-dev" : ""} ${packageName}`;
592
+ }
593
+ return cmd.replace(/\s+/g, " ").trim();
594
+ }
557
595
  /**
558
596
  * Install a dependency if it's missing from the project.
559
597
  *
@@ -566,18 +604,7 @@ ${models.map((it) => `export const ${it} = models["${it}"];`).join("\n")}
566
604
  this.log.debug(`Dependency '${packageName}' is already installed`);
567
605
  return;
568
606
  }
569
- const pm = await this.getPackageManager(root);
570
- let cmd;
571
- switch (pm) {
572
- case "yarn":
573
- cmd = `yarn add ${dev ? "-D" : ""} ${packageName}`;
574
- break;
575
- case "pnpm":
576
- cmd = `pnpm add ${dev ? "-D" : ""} ${packageName}`;
577
- break;
578
- default: cmd = `npm install ${dev ? "--save-dev" : ""} ${packageName}`;
579
- }
580
- cmd = cmd.replace(/\s+/g, " ").trim();
607
+ const cmd = await this.getInstallCommand(root, packageName, dev);
581
608
  if (options.run) await options.run(cmd, { alias: `installing ${packageName}` });
582
609
  else {
583
610
  this.log.debug(`Installing ${packageName}`);
@@ -587,337 +614,415 @@ ${models.map((it) => `export const ${it} = models["${it}"];`).join("\n")}
587
614
  };
588
615
 
589
616
  //#endregion
590
- //#region ../../src/cli/commands/BiomeCommands.ts
591
- var BiomeCommands = class {
617
+ //#region ../../src/cli/commands/build.ts
618
+ var BuildCommand = class {
592
619
  log = $logger();
593
620
  utils = $inject(AlephaCliUtils);
594
- format = $command({
595
- name: "format",
596
- description: "Format the codebase using Biome",
597
- handler: async ({ root }) => {
598
- await this.utils.ensureConfig(root, { biomeJson: true });
599
- await this.utils.ensureDependency(root, "@biomejs/biome");
600
- await this.utils.exec(`biome format --fix`);
621
+ build = $command({
622
+ name: "build",
623
+ description: "Build the project for production",
624
+ args: t.optional(t.text({
625
+ title: "path",
626
+ description: "Filepath to build"
627
+ })),
628
+ flags: t.object({
629
+ stats: t.optional(t.boolean({ description: "Generate build stats report" })),
630
+ vercel: t.optional(t.boolean({ description: "Generate Vercel deployment configuration" })),
631
+ cloudflare: t.optional(t.boolean({ description: "Generate Cloudflare Workers configuration" })),
632
+ docker: t.optional(t.boolean({ description: "Generate Docker configuration" })),
633
+ sitemap: t.optional(t.text({ description: "Generate sitemap.xml with base URL" }))
634
+ }),
635
+ handler: async ({ flags, args, run, root }) => {
636
+ process.env.ALEPHA_BUILD_MODE = "cli";
637
+ process.env.NODE_ENV = "production";
638
+ if (await this.utils.hasExpo(root)) return;
639
+ await this.utils.ensureConfig(root, {
640
+ viteConfigTs: true,
641
+ tsconfigJson: true
642
+ });
643
+ const entry = await boot.getServerEntry(root, args);
644
+ this.log.trace("Entry file found", { entry });
645
+ const distDir = "dist";
646
+ const clientDir = "public";
647
+ await this.utils.ensureDependency(root, "vite", { run });
648
+ await run.rm("dist", { alias: "clean dist" });
649
+ const viteAlephaBuildOptions = (await createRequire(import.meta.url)("vite").resolveConfig({}, "build", "production")).plugins.find((it) => it.name === "alepha:build")?.[OPTIONS] || {};
650
+ await this.utils.loadEnv(root, [".env", ".env.production"]);
651
+ const stats = flags.stats ?? viteAlephaBuildOptions.stats ?? false;
652
+ const hasServer = viteAlephaBuildOptions.serverEntry !== false;
653
+ let hasClient = false;
654
+ try {
655
+ await access(join(root, "index.html"));
656
+ hasClient = true;
657
+ } catch {}
658
+ const clientOptions = typeof viteAlephaBuildOptions.client === "object" ? viteAlephaBuildOptions.client : {};
659
+ if (hasClient) await run({
660
+ name: "vite build client",
661
+ handler: () => buildClient({
662
+ silent: true,
663
+ dist: `${distDir}/${clientDir}`,
664
+ stats,
665
+ precompress: clientOptions.precompress
666
+ })
667
+ });
668
+ await run({
669
+ name: "vite build server",
670
+ handler: async () => {
671
+ let clientBuilt = false;
672
+ try {
673
+ await readFile(`${distDir}/${clientDir}/index.html`, "utf-8");
674
+ clientBuilt = true;
675
+ } catch {}
676
+ await buildServer({
677
+ silent: true,
678
+ entry,
679
+ distDir,
680
+ clientDir: clientBuilt ? clientDir : void 0,
681
+ stats
682
+ });
683
+ if (clientBuilt && hasServer) await unlink(`${distDir}/${clientDir}/index.html`);
684
+ }
685
+ });
686
+ await copyAssets({
687
+ root,
688
+ entry: `${distDir}/index.js`,
689
+ distDir,
690
+ run
691
+ });
692
+ if (hasClient) {
693
+ const sitemapBaseUrl = flags.sitemap ?? clientOptions.sitemap?.hostname;
694
+ if (sitemapBaseUrl) await run({
695
+ name: "add sitemap",
696
+ handler: async () => {
697
+ await writeFile(`${distDir}/${clientDir}/sitemap.xml`, await generateSitemap({
698
+ entry: `${distDir}/index.js`,
699
+ baseUrl: sitemapBaseUrl
700
+ }));
701
+ }
702
+ });
703
+ if (clientOptions.prerender) await run({
704
+ name: "pre-render pages",
705
+ handler: async () => {
706
+ await prerenderPages({
707
+ dist: `${distDir}/${clientDir}`,
708
+ entry: `${distDir}/index.js`,
709
+ compress: clientOptions.precompress
710
+ });
711
+ }
712
+ });
713
+ }
714
+ if (flags.vercel || viteAlephaBuildOptions.vercel) {
715
+ const config = typeof viteAlephaBuildOptions.vercel === "object" ? viteAlephaBuildOptions.vercel : {};
716
+ await run({
717
+ name: "add Vercel config",
718
+ handler: () => generateVercel({
719
+ distDir,
720
+ clientDir,
721
+ config
722
+ })
723
+ });
724
+ }
725
+ if (flags.cloudflare || viteAlephaBuildOptions.cloudflare) {
726
+ const config = typeof viteAlephaBuildOptions.cloudflare === "boolean" ? {} : viteAlephaBuildOptions.cloudflare;
727
+ await run({
728
+ name: "add Cloudflare config",
729
+ handler: () => generateCloudflare({
730
+ distDir,
731
+ config
732
+ })
733
+ });
734
+ }
735
+ if (flags.docker || viteAlephaBuildOptions.docker) {
736
+ const dockerConfig = typeof viteAlephaBuildOptions.docker === "object" ? viteAlephaBuildOptions.docker : {};
737
+ await run({
738
+ name: "add Docker config",
739
+ handler: () => generateDocker({
740
+ distDir,
741
+ ...dockerConfig
742
+ })
743
+ });
744
+ }
601
745
  }
602
746
  });
603
- lint = $command({
604
- name: "lint",
605
- description: "Run linter across the codebase using Biome",
606
- handler: async ({ root }) => {
607
- await this.utils.ensureConfig(root, { biomeJson: true });
608
- await this.utils.ensureDependency(root, "@biomejs/biome");
609
- await this.utils.exec(`biome check --formatter-enabled=false --fix`);
747
+ };
748
+
749
+ //#endregion
750
+ //#region ../../src/cli/commands/clean.ts
751
+ var CleanCommand = class {
752
+ /**
753
+ * Clean the project, removing the "dist" directory
754
+ */
755
+ clean = $command({
756
+ name: "clean",
757
+ description: "Clean the project",
758
+ handler: async ({ run }) => {
759
+ await run.rm("./dist");
610
760
  }
611
761
  });
612
762
  };
613
763
 
614
764
  //#endregion
615
- //#region ../../src/cli/atoms/changelogOptions.ts
616
- /**
617
- * Default scopes to ignore in changelog generation.
618
- * Commits with these scopes won't appear in release notes.
619
- */
620
- const DEFAULT_IGNORE = [
621
- "project",
622
- "release",
623
- "starter",
624
- "example",
625
- "chore",
626
- "ci",
627
- "build",
628
- "test",
629
- "style"
630
- ];
631
- /**
632
- * Changelog configuration atom.
633
- *
634
- * Configure in `alepha.config.ts`:
635
- * ```ts
636
- * import { changelogOptions } from "alepha/cli";
637
- *
638
- * alepha.set(changelogOptions, {
639
- * ignore: ["project", "release", "chore", "docs"],
640
- * });
641
- * ```
642
- */
643
- const changelogOptions = $atom({
644
- name: "alepha.changelog",
645
- schema: t.object({ ignore: t.optional(t.array(t.string())) }),
646
- default: { ignore: DEFAULT_IGNORE }
765
+ //#region ../../src/cli/commands/db.ts
766
+ const drizzleCommandFlags = t.object({
767
+ provider: t.optional(t.text({ description: "Database provider name to target (e.g., 'postgres', 'sqlite')" })),
768
+ mode: t.optional(t.text({ description: "Environment variable file(s) to load (e.g., 'production' to load .env.production) https://vite.dev/guide/env-and-mode" }))
647
769
  });
648
-
649
- //#endregion
650
- //#region ../../src/cli/services/GitMessageParser.ts
651
- /**
652
- * Service for parsing git commit messages into structured format.
653
- *
654
- * Only parses **conventional commits with a scope**:
655
- * - `feat(scope): description` → feature
656
- * - `fix(scope): description` → bug fix
657
- * - `feat(scope)!: description` → breaking change
658
- *
659
- * Commits without scope are ignored, allowing developers to commit
660
- * work-in-progress changes without polluting release notes:
661
- * - `cli: work in progress` → ignored (no type)
662
- * - `fix: quick patch` → ignored (no scope)
663
- * - `feat(cli): add command` → included
664
- */
665
- var GitMessageParser = class {
770
+ var DbCommand = class {
666
771
  log = $logger();
772
+ utils = $inject(AlephaCliUtils);
667
773
  /**
668
- * Parse a git commit line into a structured Commit object.
669
- *
670
- * **Format:** `type(scope): description` or `type(scope)!: description`
671
- *
672
- * **Supported types:** feat, fix, docs, refactor, perf, revert
673
- *
674
- * **Breaking changes:** Use `!` before `:` (e.g., `feat(api)!: remove endpoint`)
675
- *
676
- * @returns Commit object or null if not matching/ignored
774
+ * Check if database migrations are up to date.
677
775
  */
678
- parseCommit(line, config) {
679
- const match = line.match(/^([a-f0-9]+)\s+(.+)$/);
680
- if (!match) return null;
681
- const [, hash, message] = match;
682
- const ignore = config.ignore ?? DEFAULT_IGNORE;
683
- const conventionalMatch = message.match(/^(feat|fix|docs|refactor|perf|revert)\(([^)]+)\)(!)?:\s*(.+)$/i);
684
- if (!conventionalMatch) return null;
685
- const [, type, scope, breakingMark, description] = conventionalMatch;
686
- const baseScope = scope.split("/")[0];
687
- if (ignore.includes(baseScope) || ignore.includes(scope)) return null;
688
- const breaking = breakingMark === "!" || description.toLowerCase().includes("breaking");
689
- return {
690
- hash: hash.substring(0, 8),
691
- type: type.toLowerCase(),
692
- scope,
693
- description: description.trim(),
694
- breaking
695
- };
696
- }
697
- };
698
-
699
- //#endregion
700
- //#region ../../src/cli/commands/ChangelogCommands.ts
701
- const execAsync = promisify(exec);
702
- /**
703
- * Git provider for executing git commands.
704
- * Can be substituted in tests with a mock implementation.
705
- */
706
- var GitProvider = class {
707
- async exec(cmd, cwd) {
708
- const { stdout } = await execAsync(`git ${cmd}`, { cwd });
709
- return stdout;
710
- }
711
- };
712
- /**
713
- * Changelog command for generating release notes from git commits.
714
- *
715
- * Usage:
716
- * - `alepha changelog` - Show unreleased changes since latest tag to HEAD
717
- * - `alepha changelog --from=1.0.0` - Show changes from version to HEAD
718
- * - `alepha changelog --from=1.0.0 --to=1.1.0` - Show changes between two refs
719
- * - `alepha changelog | tee -a CHANGELOG.md` - Append to file
720
- */
721
- var ChangelogCommands = class {
722
- log = $logger();
723
- git = $inject(GitProvider);
724
- parser = $inject(GitMessageParser);
725
- config = $use(changelogOptions);
726
- /**
727
- * Format a single commit line.
728
- * Example: `- **cli**: add new command (\`abc1234\`)`
729
- */
730
- formatCommit(commit) {
731
- return `- **${commit.scope}**: ${commit.description} (\`${commit.hash}\`)`;
732
- }
733
- /**
734
- * Format the changelog entry with sections.
735
- */
736
- formatEntry(entry) {
737
- const sections = [];
738
- if (entry.breaking.length > 0) {
739
- sections.push("### Breaking Changes\n");
740
- for (const commit of entry.breaking) sections.push(this.formatCommit(commit));
741
- sections.push("");
742
- }
743
- if (entry.features.length > 0) {
744
- sections.push("### Features\n");
745
- for (const commit of entry.features) sections.push(this.formatCommit(commit));
746
- sections.push("");
747
- }
748
- if (entry.fixes.length > 0) {
749
- sections.push("### Bug Fixes\n");
750
- for (const commit of entry.fixes) sections.push(this.formatCommit(commit));
751
- sections.push("");
776
+ check = $command({
777
+ name: "check-migrations",
778
+ description: "Check if database migration files are up to date",
779
+ args: t.optional(t.text({
780
+ title: "path",
781
+ description: "Path to the Alepha server entry file"
782
+ })),
783
+ flags: drizzleCommandFlags,
784
+ handler: async ({ args, root }) => {
785
+ const rootDir = root;
786
+ this.log.debug(`Using project root: ${rootDir}`);
787
+ const { alepha } = await this.utils.loadAlephaFromServerEntryFile(rootDir, args);
788
+ const repositoryProvider = alepha.inject("RepositoryProvider");
789
+ const drizzleKitProvider = alepha.inject("DrizzleKitProvider");
790
+ const accepted = /* @__PURE__ */ new Set([]);
791
+ for (const primitive of repositoryProvider.getRepositories()) {
792
+ const provider = primitive.provider;
793
+ const providerName = provider.name;
794
+ if (accepted.has(providerName)) continue;
795
+ accepted.add(providerName);
796
+ const migrationDir = join(rootDir, "migrations", providerName);
797
+ const journalFile = await readFile(`${migrationDir}/meta/_journal.json`, "utf-8").catch(() => null);
798
+ if (!journalFile) {
799
+ this.log.info("No migration journal found.");
800
+ return;
801
+ }
802
+ const journal = JSON.parse(journalFile);
803
+ const lastMigration = journal.entries[journal.entries.length - 1];
804
+ const lastSnapshot = JSON.parse(await readFile(`${migrationDir}/meta/${String(lastMigration.idx).padStart(4, "0")}_snapshot.json`, "utf-8"));
805
+ const models = drizzleKitProvider.getModels(provider);
806
+ const kit = drizzleKitProvider.importDrizzleKit();
807
+ const now = kit.generateDrizzleJson(models, lastSnapshot.id);
808
+ const migrationStatements = await new Promise((resolve) => {
809
+ (async () => {
810
+ const timer = setTimeout(() => {
811
+ resolve([{ message: "Migration generation timed out." }]);
812
+ }, 5e3);
813
+ const statements = await kit.generateMigration(lastSnapshot, now);
814
+ clearTimeout(timer);
815
+ resolve(statements);
816
+ })();
817
+ });
818
+ if (migrationStatements.length === 0) {
819
+ this.log.info("No changes detected.");
820
+ return;
821
+ }
822
+ this.log.info("");
823
+ this.log.info("Detected migration statements:");
824
+ this.log.info("");
825
+ for (const stmt of migrationStatements) this.log.info(stmt);
826
+ this.log.info("");
827
+ this.log.info(`At least ${migrationStatements.length} change(s) detected.`);
828
+ this.log.info("Please, run 'alepha db:generate' to update the migration files.");
829
+ this.log.info("");
830
+ throw new AlephaError("Database migrations are not up to date.");
831
+ }
752
832
  }
753
- return sections.join("\n");
754
- }
833
+ });
755
834
  /**
756
- * Parse git log output into a changelog entry.
835
+ * Generate database migration files
757
836
  */
758
- parseCommits(commitsOutput) {
759
- const entry = {
760
- features: [],
761
- fixes: [],
762
- breaking: []
763
- };
764
- for (const line of commitsOutput.trim().split("\n")) {
765
- if (!line.trim()) continue;
766
- const commit = this.parser.parseCommit(line, this.config);
767
- if (!commit) {
768
- this.log.trace("Skipping commit", { line });
769
- continue;
770
- }
771
- this.log.trace("Parsed commit", { commit });
772
- if (commit.breaking) entry.breaking.push(commit);
773
- if (commit.type === "feat") entry.features.push(commit);
774
- else if (commit.type === "fix") entry.fixes.push(commit);
837
+ generate = $command({
838
+ name: "generate",
839
+ description: "Generate migration files based on current database schema",
840
+ summary: false,
841
+ args: t.optional(t.text({
842
+ title: "path",
843
+ description: "Path to the Alepha server entry file"
844
+ })),
845
+ flags: t.extend(drizzleCommandFlags, { custom: t.optional(t.text({ description: "Custom migration name for drizzle-kit generate --custom" })) }),
846
+ handler: async ({ args, flags, root }) => {
847
+ const commandFlags = flags.custom ? `--custom=${flags.custom}` : void 0;
848
+ await this.runDrizzleKitCommand({
849
+ root,
850
+ args,
851
+ command: "generate",
852
+ commandFlags,
853
+ provider: flags.provider,
854
+ env: flags.mode,
855
+ logMessage: (providerName, dialect) => `Generate '${providerName}' migrations (${dialect}) ...`
856
+ });
775
857
  }
776
- return entry;
777
- }
858
+ });
778
859
  /**
779
- * Check if entry has any public commits.
860
+ * Push database schema changes directly to the database
780
861
  */
781
- hasChanges(entry) {
782
- return entry.features.length > 0 || entry.fixes.length > 0 || entry.breaking.length > 0;
783
- }
862
+ push = $command({
863
+ name: "push",
864
+ description: "Push database schema changes directly to the database",
865
+ summary: false,
866
+ args: t.optional(t.text({
867
+ title: "path",
868
+ description: "Path to the Alepha server entry file"
869
+ })),
870
+ flags: drizzleCommandFlags,
871
+ handler: async ({ root, args, flags }) => {
872
+ await this.runDrizzleKitCommand({
873
+ root,
874
+ args,
875
+ command: "push",
876
+ provider: flags.provider,
877
+ env: flags.mode,
878
+ logMessage: (providerName, dialect) => `Push '${providerName}' schema (${dialect}) ...`
879
+ });
880
+ }
881
+ });
784
882
  /**
785
- * Get the latest version tag.
883
+ * Apply pending database migrations
786
884
  */
787
- async getLatestTag(git) {
788
- return (await git("tag --sort=-version:refname")).trim().split("\n").filter((tag) => tag.match(/^\d+\.\d+\.\d+$/))[0] || null;
789
- }
790
- changelog = $command({
791
- name: "changelog",
792
- description: "Generate changelog from conventional commits (outputs to stdout)",
793
- flags: t.object({
794
- from: t.optional(t.string({
795
- aliases: ["f"],
796
- description: "Starting ref (default: latest tag)"
797
- })),
798
- to: t.optional(t.string({
799
- aliases: ["t"],
800
- description: "Ending ref (default: HEAD)"
801
- }))
802
- }),
803
- handler: async ({ flags, root }) => {
804
- const git = (cmd) => this.git.exec(cmd, root);
805
- let fromRef;
806
- if (flags.from) {
807
- fromRef = flags.from;
808
- this.log.debug("Using specified from ref", { from: fromRef });
809
- } else {
810
- const latestTag = await this.getLatestTag(git);
811
- if (!latestTag) {
812
- process.stdout.write("No version tags found in repository\n");
813
- return;
814
- }
815
- fromRef = latestTag;
816
- this.log.debug("Using latest tag", { from: fromRef });
817
- }
818
- const toRef = flags.to || "HEAD";
819
- this.log.debug("Using to ref", { to: toRef });
820
- const commitsOutput = await git(`log ${fromRef}..${toRef} --oneline`);
821
- if (!commitsOutput.trim()) {
822
- process.stdout.write(`No changes in range ${fromRef}..${toRef}\n`);
823
- return;
824
- }
825
- const entry = this.parseCommits(commitsOutput);
826
- if (!this.hasChanges(entry)) {
827
- process.stdout.write(`No public changes in range ${fromRef}..${toRef}\n`);
828
- return;
829
- }
830
- process.stdout.write(this.formatEntry(entry));
885
+ migrate = $command({
886
+ name: "migrate",
887
+ description: "Apply pending database migrations",
888
+ summary: false,
889
+ args: t.optional(t.text({
890
+ title: "path",
891
+ description: "Path to the Alepha server entry file"
892
+ })),
893
+ flags: drizzleCommandFlags,
894
+ handler: async ({ root, args, flags }) => {
895
+ await this.runDrizzleKitCommand({
896
+ root,
897
+ args,
898
+ command: "migrate",
899
+ provider: flags.provider,
900
+ env: flags.mode,
901
+ logMessage: (providerName, dialect) => `Migrate '${providerName}' database (${dialect}) ...`
902
+ });
831
903
  }
832
904
  });
833
- };
834
-
835
- //#endregion
836
- //#region ../../src/cli/commands/CoreCommands.ts
837
- var CoreCommands = class {
838
- log = $logger();
839
- cli = $inject(CliProvider);
840
- utils = $inject(AlephaCliUtils);
841
905
  /**
842
- * Called when no command is provided
906
+ * Launch Drizzle Studio database browser
843
907
  */
844
- root = $command({
845
- root: true,
846
- flags: t.object({ version: t.optional(t.boolean({
847
- description: "Show Alepha CLI version",
848
- aliases: ["v"]
849
- })) }),
850
- handler: async ({ flags }) => {
851
- if (flags.version) {
852
- this.log.info(version);
853
- return;
854
- }
855
- this.cli.printHelp();
908
+ studio = $command({
909
+ name: "studio",
910
+ description: "Launch Drizzle Studio database browser",
911
+ summary: false,
912
+ args: t.optional(t.text({
913
+ title: "path",
914
+ description: "Path to the Alepha server entry file"
915
+ })),
916
+ flags: drizzleCommandFlags,
917
+ handler: async ({ root, args, flags }) => {
918
+ await this.runDrizzleKitCommand({
919
+ root,
920
+ args,
921
+ command: "studio",
922
+ provider: flags.provider,
923
+ env: flags.mode,
924
+ logMessage: (providerName, dialect) => `Launch Studio for '${providerName}' (${dialect}) ...`
925
+ });
856
926
  }
857
927
  });
858
928
  /**
859
- * Clean the project, removing the "dist" directory
929
+ * Parent command for database operations.
860
930
  */
861
- clean = $command({
862
- name: "clean",
863
- description: "Clean the project",
864
- handler: async ({ run }) => {
865
- await run.rm("./dist");
931
+ db = $command({
932
+ name: "db",
933
+ description: "Database management commands",
934
+ children: [
935
+ this.check,
936
+ this.generate,
937
+ this.push,
938
+ this.migrate,
939
+ this.studio
940
+ ],
941
+ handler: async ({ help }) => {
942
+ help();
866
943
  }
867
944
  });
868
945
  /**
869
- * Ensure the project has the necessary Alepha configuration files.
870
- * Add the correct dependencies to package.json and install them.
946
+ * Run a drizzle-kit command for all database providers in an Alepha instance.
871
947
  */
872
- init = $command({
873
- name: "init",
874
- description: "Add missing Alepha configuration files to the project",
875
- flags: t.object({
876
- yarn: t.optional(t.boolean({ description: "Use Yarn package manager" })),
877
- pnpm: t.optional(t.boolean({ description: "Use pnpm package manager" })),
878
- npm: t.optional(t.boolean({ description: "Use npm package manager" })),
879
- bun: t.optional(t.boolean({ description: "Use Bun package manager" })),
880
- react: t.optional(t.boolean({ description: "Include Alepha React dependencies" })),
881
- ui: t.optional(t.boolean({ description: "Include Alepha UI dependencies" })),
882
- test: t.optional(t.boolean({ description: "Include Vitest and create test directory" }))
883
- }),
884
- handler: async ({ run, flags, root }) => {
885
- if (flags.ui) flags.react = true;
886
- const isExpo = await this.utils.hasExpo(root);
887
- await run({
888
- name: "ensuring configuration files",
889
- handler: async () => {
890
- await this.utils.ensureConfig(root, {
891
- tsconfigJson: true,
892
- packageJson: flags,
893
- biomeJson: true,
894
- viteConfigTs: !isExpo,
895
- editorconfig: true,
896
- indexHtml: !!flags.react && !isExpo
897
- });
898
- if (!flags.react) await this.utils.ensureSrcMain(root);
899
- }
900
- });
901
- const pm = await this.utils.getPackageManager(root, flags);
902
- if (pm === "yarn") {
903
- await this.utils.ensureYarn(root);
904
- await run("yarn set version stable");
905
- } else if (pm === "pnpm") await this.utils.ensurePnpm(root);
906
- else await this.utils.ensureNpm(root);
907
- await run(`${pm} install`, { alias: `installing dependencies with ${pm}` });
908
- if (!isExpo) await this.utils.ensureDependency(root, "vite", { run });
909
- await this.utils.ensureDependency(root, "@biomejs/biome", { run });
910
- if (flags.test) {
911
- await this.utils.ensureTestDir(root);
912
- await run(`${pm} ${pm === "yarn" ? "add" : "install"} -D vitest`, { alias: "setup testing with Vitest" });
948
+ async runDrizzleKitCommand(options) {
949
+ const rootDir = options.root;
950
+ const envFiles = [".env"];
951
+ if (options.env) envFiles.push(`.env.${options.env}`);
952
+ await this.utils.loadEnv(rootDir, envFiles);
953
+ this.log.debug(`Using project root: ${rootDir}`);
954
+ const { alepha, entry } = await this.utils.loadAlephaFromServerEntryFile(rootDir, options.args);
955
+ const drizzleKitProvider = alepha.inject("DrizzleKitProvider");
956
+ const repositoryProvider = alepha.inject("RepositoryProvider");
957
+ const accepted = /* @__PURE__ */ new Set([]);
958
+ for (const primitive of repositoryProvider.getRepositories()) {
959
+ const provider = primitive.provider;
960
+ const providerName = provider.name;
961
+ const dialect = provider.dialect;
962
+ if (accepted.has(providerName)) continue;
963
+ accepted.add(providerName);
964
+ if (options.provider && options.provider !== providerName) {
965
+ this.log.debug(`Skipping provider '${providerName}' (filter: ${options.provider})`);
966
+ continue;
913
967
  }
968
+ this.log.info("");
969
+ this.log.info(options.logMessage(providerName, dialect));
970
+ const drizzleConfigJsPath = await this.prepareDrizzleConfig({
971
+ kit: drizzleKitProvider,
972
+ provider,
973
+ providerName,
974
+ providerUrl: provider.url,
975
+ dialect,
976
+ entry,
977
+ rootDir
978
+ });
979
+ const flags = options.commandFlags ? ` ${options.commandFlags}` : "";
980
+ await this.utils.exec(`drizzle-kit ${options.command} --config=${drizzleConfigJsPath}${flags}`, { env: { NODE_OPTIONS: "--import tsx" } });
914
981
  }
915
- });
982
+ }
983
+ /**
984
+ * Prepare Drizzle configuration files for a database provider.
985
+ */
986
+ async prepareDrizzleConfig(options) {
987
+ const models = Object.keys(options.kit.getModels(options.provider));
988
+ const entitiesJs = this.utils.generateEntitiesJs(options.entry, options.providerName, models);
989
+ const config = {
990
+ schema: await this.utils.writeConfigFile("entities.js", entitiesJs, options.rootDir),
991
+ out: `./migrations/${options.providerName}`,
992
+ dialect: options.dialect,
993
+ dbCredentials: { url: options.providerUrl }
994
+ };
995
+ if (options.provider.schema) config.schemaFilter = options.provider.schema;
996
+ if (options.providerName === "d1") config.driver = "d1-http";
997
+ if (options.providerName === "pglite") config.driver = "pglite";
998
+ if (options.dialect === "sqlite") if (options.providerName === "d1") {
999
+ const token = process.env.CLOUDFLARE_API_TOKEN;
1000
+ if (!token) throw new AlephaError("CLOUDFLARE_API_TOKEN environment variable is not set. https://orm.drizzle.team/docs/guides/d1-http-with-drizzle-kit");
1001
+ const accountId = process.env.CLOUDFLARE_ACCOUNT_ID;
1002
+ if (!accountId) throw new AlephaError("CLOUDFLARE_ACCOUNT_ID environment variable is not set. https://orm.drizzle.team/docs/guides/d1-http-with-drizzle-kit");
1003
+ const url = options.providerUrl;
1004
+ if (!url.startsWith("cloudflare-d1://")) throw new AlephaError("D1 provider URL must start with 'cloudflare-d1://'.");
1005
+ const [, databaseId] = url.replace("cloudflare-d1://", "").replace("cloudflare-d1:", "").split(":");
1006
+ if (!databaseId) throw new AlephaError("Database ID is missing in the D1 provider URL. Cloudflare D1 URL format: cloudflare-d1://<database_name>:<database_id>");
1007
+ config.dbCredentials = {
1008
+ accountId,
1009
+ databaseId,
1010
+ token
1011
+ };
1012
+ } else {
1013
+ let url = options.providerUrl;
1014
+ url = url.replace("sqlite://", "").replace("file://", "");
1015
+ url = join(options.rootDir, url);
1016
+ config.dbCredentials = { url };
1017
+ }
1018
+ const drizzleConfigJs = `export default ${JSON.stringify(config, null, 2)}`;
1019
+ return await this.utils.writeConfigFile("drizzle.config.js", drizzleConfigJs, options.rootDir);
1020
+ }
916
1021
  };
917
1022
 
918
1023
  //#endregion
919
- //#region ../../src/cli/commands/DeployCommands.ts
920
- var DeployCommands = class {
1024
+ //#region ../../src/cli/commands/deploy.ts
1025
+ var DeployCommand = class {
921
1026
  log = $logger();
922
1027
  utils = $inject(AlephaCliUtils);
923
1028
  /**
@@ -974,369 +1079,470 @@ var DeployCommands = class {
974
1079
  return;
975
1080
  }
976
1081
  if (await this.utils.exists(root, "dist/wrangler.jsonc")) {
977
- if (flags.migrate) {
978
- this.log.debug("Running database migrations before deployment...");
979
- await this.utils.exec(`alepha db migrate --mode=${mode}`);
980
- }
981
- await this.utils.ensureDependency(root, "wrangler", { dev: true });
982
- const command = `wrangler deploy ${mode === "production" ? "" : "--env preview"} --config=dist/wrangler.jsonc`.trim();
983
- this.log.info(`Deploying to Cloudflare with command: ${command}`);
984
- await this.utils.exec(command);
985
- return;
986
- }
987
- if (await this.utils.exists(root, "dist/public/404.html")) {
988
- await this.utils.ensureDependency(root, "surge", { dev: true });
989
- const distPath = join(root, "dist/public");
990
- this.log.debug(`Deploying to Surge from directory: ${distPath}`);
991
- await this.utils.exec(`surge ${distPath}`);
992
- return;
993
- }
994
- throw new AlephaError("No deployment configuration found in the dist folder.");
995
- }
996
- });
997
- };
998
-
999
- //#endregion
1000
- //#region ../../src/cli/commands/DrizzleCommands.ts
1001
- const drizzleCommandFlags = t.object({
1002
- provider: t.optional(t.text({ description: "Database provider name to target (e.g., 'postgres', 'sqlite')" })),
1003
- mode: t.optional(t.text({ description: "Environment variable file(s) to load (e.g., 'production' to load .env.production) https://vite.dev/guide/env-and-mode" }))
1004
- });
1005
- var DrizzleCommands = class {
1006
- log = $logger();
1007
- utils = $inject(AlephaCliUtils);
1008
- /**
1009
- * Check if database migrations are up to date.
1010
- */
1011
- check = $command({
1012
- name: "check-migrations",
1013
- description: "Check if database migration files are up to date",
1014
- args: t.optional(t.text({
1015
- title: "path",
1016
- description: "Path to the Alepha server entry file"
1017
- })),
1018
- flags: drizzleCommandFlags,
1019
- handler: async ({ args, root }) => {
1020
- const rootDir = root;
1021
- this.log.debug(`Using project root: ${rootDir}`);
1022
- const { alepha } = await this.utils.loadAlephaFromServerEntryFile(rootDir, args);
1023
- const repositoryProvider = alepha.inject("RepositoryProvider");
1024
- const drizzleKitProvider = alepha.inject("DrizzleKitProvider");
1025
- const accepted = /* @__PURE__ */ new Set([]);
1026
- for (const primitive of repositoryProvider.getRepositories()) {
1027
- const provider = primitive.provider;
1028
- const providerName = provider.name;
1029
- if (accepted.has(providerName)) continue;
1030
- accepted.add(providerName);
1031
- const migrationDir = join(rootDir, "migrations", providerName);
1032
- const journalFile = await readFile(`${migrationDir}/meta/_journal.json`, "utf-8").catch(() => null);
1033
- if (!journalFile) {
1034
- this.log.info(`No migration journal found.`);
1035
- return;
1036
- }
1037
- const journal = JSON.parse(journalFile);
1038
- const lastMigration = journal.entries[journal.entries.length - 1];
1039
- const lastSnapshot = JSON.parse(await readFile(`${migrationDir}/meta/${String(lastMigration.idx).padStart(4, "0")}_snapshot.json`, "utf-8"));
1040
- const models = drizzleKitProvider.getModels(provider);
1041
- const kit = drizzleKitProvider.importDrizzleKit();
1042
- const now = kit.generateDrizzleJson(models, lastSnapshot.id);
1043
- const migrationStatements = await new Promise((resolve) => {
1044
- (async () => {
1045
- const timer = setTimeout(() => {
1046
- resolve([{ message: "Migration generation timed out." }]);
1047
- }, 5e3);
1048
- const statements = await kit.generateMigration(lastSnapshot, now);
1049
- clearTimeout(timer);
1050
- resolve(statements);
1051
- })();
1052
- });
1053
- if (migrationStatements.length === 0) {
1054
- this.log.info("No changes detected.");
1055
- return;
1082
+ if (flags.migrate) {
1083
+ this.log.debug("Running database migrations before deployment...");
1084
+ await this.utils.exec(`alepha db migrate --mode=${mode}`);
1056
1085
  }
1057
- this.log.info("");
1058
- this.log.info("Detected migration statements:");
1059
- this.log.info("");
1060
- for (const stmt of migrationStatements) this.log.info(stmt);
1061
- this.log.info("");
1062
- this.log.info(`At least ${migrationStatements.length} change(s) detected.`);
1063
- this.log.info("Please, run 'alepha db:generate' to update the migration files.");
1064
- this.log.info("");
1065
- throw new AlephaError("Database migrations are not up to date.");
1086
+ await this.utils.ensureDependency(root, "wrangler", { dev: true });
1087
+ const command = `wrangler deploy ${mode === "production" ? "" : "--env preview"} --config=dist/wrangler.jsonc`.trim();
1088
+ this.log.info(`Deploying to Cloudflare with command: ${command}`);
1089
+ await this.utils.exec(command);
1090
+ return;
1091
+ }
1092
+ if (await this.utils.exists(root, "dist/public/404.html")) {
1093
+ await this.utils.ensureDependency(root, "surge", { dev: true });
1094
+ const distPath = join(root, "dist/public");
1095
+ this.log.debug(`Deploying to Surge from directory: ${distPath}`);
1096
+ await this.utils.exec(`surge ${distPath}`);
1097
+ return;
1066
1098
  }
1099
+ throw new AlephaError("No deployment configuration found in the dist folder.");
1067
1100
  }
1068
1101
  });
1102
+ };
1103
+
1104
+ //#endregion
1105
+ //#region ../../src/cli/commands/dev.ts
1106
+ var DevCommand = class {
1107
+ log = $logger();
1108
+ utils = $inject(AlephaCliUtils);
1109
+ alepha = $inject(Alepha);
1069
1110
  /**
1070
- * Generate database migration files
1111
+ * Will run the project in watch mode.
1071
1112
  *
1072
- * - Loads the Alepha instance from the specified entry file.
1073
- * - Retrieves all repository primitives to gather database models.
1074
- * - Creates temporary entity definitions based on the current database schema.
1075
- * - Writes these definitions to a temporary schema file. (node_modules/.db/entities.ts)
1076
- * - Invokes Drizzle Kit's CLI to generate migration files based on the current schema.
1113
+ * - If an index.html file is found in the project root, it will run Vite in dev mode.
1114
+ * - Otherwise, it will look for a server entry file and run it with tsx in watch mode.
1077
1115
  */
1078
- generate = $command({
1079
- name: "generate",
1080
- description: "Generate migration files based on current database schema",
1081
- summary: false,
1116
+ dev = $command({
1117
+ name: "dev",
1118
+ description: "Run the project in development mode",
1082
1119
  args: t.optional(t.text({
1083
1120
  title: "path",
1084
- description: "Path to the Alepha server entry file"
1121
+ description: "Filepath to run"
1085
1122
  })),
1086
- flags: t.extend(drizzleCommandFlags, { custom: t.optional(t.text({ description: "Custom migration name for drizzle-kit generate --custom" })) }),
1087
- handler: async ({ args, flags, root }) => {
1088
- const commandFlags = flags.custom ? `--custom=${flags.custom}` : void 0;
1089
- await this.runDrizzleKitCommand({
1090
- root,
1091
- args,
1092
- command: "generate",
1093
- commandFlags,
1094
- provider: flags.provider,
1095
- env: flags.mode,
1096
- logMessage: (providerName, dialect) => `Generate '${providerName}' migrations (${dialect}) ...`
1123
+ handler: async ({ args, root }) => {
1124
+ const expo = await this.utils.hasExpo(root);
1125
+ await this.utils.ensureConfig(root, {
1126
+ viteConfigTs: !expo,
1127
+ tsconfigJson: true
1097
1128
  });
1129
+ if (expo) {
1130
+ await this.utils.exec("expo start");
1131
+ return;
1132
+ }
1133
+ const entry = await boot.getServerEntry(root, args);
1134
+ this.log.trace("Entry file found", { entry });
1135
+ if (!await this.isFullstackProject(root)) {
1136
+ const exe = this.alepha.isBun() ? "bun" : "tsx";
1137
+ let cmd = `${exe} --watch`;
1138
+ if (await this.utils.exists(root, ".env")) cmd += " --env-file=./.env";
1139
+ cmd += ` ${entry}`;
1140
+ await this.utils.exec(cmd, { global: exe === "bun" });
1141
+ return;
1142
+ }
1143
+ await this.utils.ensureDependency(root, "vite");
1144
+ await this.utils.exec("vite");
1098
1145
  }
1099
1146
  });
1100
- /**
1101
- * Push database schema changes directly to the database
1102
- *
1103
- * - Loads the Alepha instance from the specified entry file.
1104
- * - Retrieves all repository primitives to gather database models.
1105
- * - Creates temporary entity definitions and Drizzle config.
1106
- * - Invokes Drizzle Kit's push command to apply schema changes directly.
1107
- */
1108
- push = $command({
1109
- name: "push",
1110
- description: "Push database schema changes directly to the database",
1111
- summary: false,
1112
- args: t.optional(t.text({
1113
- title: "path",
1114
- description: "Path to the Alepha server entry file"
1115
- })),
1116
- flags: drizzleCommandFlags,
1117
- handler: async ({ root, args, flags }) => {
1118
- await this.runDrizzleKitCommand({
1119
- root,
1120
- args,
1121
- command: "push",
1122
- provider: flags.provider,
1123
- env: flags.mode,
1124
- logMessage: (providerName, dialect) => `Push '${providerName}' schema (${dialect}) ...`
1125
- });
1147
+ async isFullstackProject(root) {
1148
+ try {
1149
+ await access(join(root, "index.html"));
1150
+ return true;
1151
+ } catch {
1152
+ return false;
1126
1153
  }
1127
- });
1128
- /**
1129
- * Apply pending database migrations
1130
- *
1131
- * - Loads the Alepha instance from the specified entry file.
1132
- * - Retrieves all repository primitives to gather database models.
1133
- * - Creates temporary entity definitions and Drizzle config.
1134
- * - Invokes Drizzle Kit's migrate command to apply pending migrations.
1135
- */
1136
- migrate = $command({
1137
- name: "migrate",
1138
- description: "Apply pending database migrations",
1139
- summary: false,
1140
- args: t.optional(t.text({
1141
- title: "path",
1142
- description: "Path to the Alepha server entry file"
1143
- })),
1144
- flags: drizzleCommandFlags,
1145
- handler: async ({ root, args, flags }) => {
1146
- await this.runDrizzleKitCommand({
1147
- root,
1148
- args,
1149
- command: "migrate",
1150
- provider: flags.provider,
1151
- env: flags.mode,
1152
- logMessage: (providerName, dialect) => `Migrate '${providerName}' database (${dialect}) ...`
1153
- });
1154
+ }
1155
+ };
1156
+
1157
+ //#endregion
1158
+ //#region ../../src/cli/commands/format.ts
1159
+ var FormatCommand = class {
1160
+ utils = $inject(AlephaCliUtils);
1161
+ format = $command({
1162
+ name: "format",
1163
+ description: "Format the codebase using Biome",
1164
+ handler: async ({ root }) => {
1165
+ await this.utils.ensureConfig(root, { biomeJson: true });
1166
+ await this.utils.ensureDependency(root, "@biomejs/biome");
1167
+ await this.utils.exec("biome format --fix");
1154
1168
  }
1155
1169
  });
1170
+ };
1171
+
1172
+ //#endregion
1173
+ //#region ../../src/cli/atoms/changelogOptions.ts
1174
+ /**
1175
+ * Default scopes to ignore in changelog generation.
1176
+ * Commits with these scopes won't appear in release notes.
1177
+ */
1178
+ const DEFAULT_IGNORE = [
1179
+ "project",
1180
+ "release",
1181
+ "starter",
1182
+ "example",
1183
+ "chore",
1184
+ "ci",
1185
+ "build",
1186
+ "test",
1187
+ "style"
1188
+ ];
1189
+ /**
1190
+ * Changelog configuration atom.
1191
+ *
1192
+ * Configure in `alepha.config.ts`:
1193
+ * ```ts
1194
+ * import { changelogOptions } from "alepha/cli";
1195
+ *
1196
+ * alepha.set(changelogOptions, {
1197
+ * ignore: ["project", "release", "chore", "docs"],
1198
+ * });
1199
+ * ```
1200
+ */
1201
+ const changelogOptions = $atom({
1202
+ name: "alepha.changelog",
1203
+ schema: t.object({ ignore: t.optional(t.array(t.string())) }),
1204
+ default: { ignore: DEFAULT_IGNORE }
1205
+ });
1206
+
1207
+ //#endregion
1208
+ //#region ../../src/cli/services/GitMessageParser.ts
1209
+ /**
1210
+ * Service for parsing git commit messages into structured format.
1211
+ *
1212
+ * Only parses **conventional commits with a scope**:
1213
+ * - `feat(scope): description` → feature
1214
+ * - `fix(scope): description` → bug fix
1215
+ * - `feat(scope)!: description` → breaking change
1216
+ *
1217
+ * Commits without scope are ignored, allowing developers to commit
1218
+ * work-in-progress changes without polluting release notes:
1219
+ * - `cli: work in progress` → ignored (no type)
1220
+ * - `fix: quick patch` → ignored (no scope)
1221
+ * - `feat(cli): add command` → included
1222
+ */
1223
+ var GitMessageParser = class {
1224
+ log = $logger();
1156
1225
  /**
1157
- * Launch Drizzle Studio database browser
1226
+ * Parse a git commit line into a structured Commit object.
1227
+ *
1228
+ * **Format:** `type(scope): description` or `type(scope)!: description`
1229
+ *
1230
+ * **Supported types:** feat, fix, docs, refactor, perf, revert
1231
+ *
1232
+ * **Breaking changes:** Use `!` before `:` (e.g., `feat(api)!: remove endpoint`)
1158
1233
  *
1159
- * - Loads the Alepha instance from the specified entry file.
1160
- * - Retrieves all repository primitives to gather database models.
1161
- * - Creates temporary entity definitions and Drizzle config.
1162
- * - Invokes Drizzle Kit's studio command to launch the web-based database browser.
1234
+ * @returns Commit object or null if not matching/ignored
1163
1235
  */
1164
- studio = $command({
1165
- name: "studio",
1166
- description: "Launch Drizzle Studio database browser",
1167
- summary: false,
1168
- args: t.optional(t.text({
1169
- title: "path",
1170
- description: "Path to the Alepha server entry file"
1171
- })),
1172
- flags: drizzleCommandFlags,
1173
- handler: async ({ root, args, flags }) => {
1174
- await this.runDrizzleKitCommand({
1175
- root,
1176
- args,
1177
- command: "studio",
1178
- provider: flags.provider,
1179
- env: flags.mode,
1180
- logMessage: (providerName, dialect) => `Launch Studio for '${providerName}' (${dialect}) ...`
1181
- });
1182
- }
1183
- });
1236
+ parseCommit(line, config) {
1237
+ const match = line.match(/^([a-f0-9]+)\s+(.+)$/);
1238
+ if (!match) return null;
1239
+ const [, hash, message] = match;
1240
+ const ignore = config.ignore ?? DEFAULT_IGNORE;
1241
+ const conventionalMatch = message.match(/^(feat|fix|docs|refactor|perf|revert)\(([^)]+)\)(!)?:\s*(.+)$/i);
1242
+ if (!conventionalMatch) return null;
1243
+ const [, type, scope, breakingMark, description] = conventionalMatch;
1244
+ const baseScope = scope.split("/")[0];
1245
+ if (ignore.includes(baseScope) || ignore.includes(scope)) return null;
1246
+ const breaking = breakingMark === "!" || description.toLowerCase().includes("breaking");
1247
+ return {
1248
+ hash: hash.substring(0, 8),
1249
+ type: type.toLowerCase(),
1250
+ scope,
1251
+ description: description.trim(),
1252
+ breaking
1253
+ };
1254
+ }
1255
+ };
1256
+
1257
+ //#endregion
1258
+ //#region ../../src/cli/commands/gen/changelog.ts
1259
+ const execAsync = promisify(exec);
1260
+ /**
1261
+ * Git provider for executing git commands.
1262
+ * Can be substituted in tests with a mock implementation.
1263
+ */
1264
+ var GitProvider = class {
1265
+ async exec(cmd, cwd) {
1266
+ const { stdout } = await execAsync(`git ${cmd}`, { cwd });
1267
+ return stdout;
1268
+ }
1269
+ };
1270
+ /**
1271
+ * Changelog command for generating release notes from git commits.
1272
+ *
1273
+ * Usage:
1274
+ * - `alepha gen changelog` - Show unreleased changes since latest tag to HEAD
1275
+ * - `alepha gen changelog --from=1.0.0` - Show changes from version to HEAD
1276
+ * - `alepha gen changelog --from=1.0.0 --to=1.1.0` - Show changes between two refs
1277
+ * - `alepha gen changelog | tee -a CHANGELOG.md` - Append to file
1278
+ */
1279
+ var ChangelogCommand = class {
1280
+ log = $logger();
1281
+ git = $inject(GitProvider);
1282
+ parser = $inject(GitMessageParser);
1283
+ config = $use(changelogOptions);
1284
+ /**
1285
+ * Format a single commit line.
1286
+ * Example: `- **cli**: add new command (\`abc1234\`)`
1287
+ * Breaking changes are flagged: `- **cli**: add new command [BREAKING] (\`abc1234\`)`
1288
+ */
1289
+ formatCommit(commit) {
1290
+ const breaking = commit.breaking ? " [BREAKING]" : "";
1291
+ return `- **${commit.scope}**: ${commit.description}${breaking} (\`${commit.hash}\`)`;
1292
+ }
1184
1293
  /**
1185
- * Parent command for database operations.
1294
+ * Format the changelog entry with sections.
1186
1295
  */
1187
- db = $command({
1188
- name: "db",
1189
- description: "Database management commands",
1190
- children: [
1191
- this.check,
1192
- this.generate,
1193
- this.push,
1194
- this.migrate,
1195
- this.studio
1196
- ],
1197
- handler: async ({ help }) => {
1198
- help();
1296
+ formatEntry(entry) {
1297
+ const sections = [];
1298
+ if (entry.features.length > 0) {
1299
+ sections.push("### Features\n");
1300
+ for (const commit of entry.features) sections.push(this.formatCommit(commit));
1301
+ sections.push("");
1199
1302
  }
1200
- });
1303
+ if (entry.fixes.length > 0) {
1304
+ sections.push("### Bug Fixes\n");
1305
+ for (const commit of entry.fixes) sections.push(this.formatCommit(commit));
1306
+ sections.push("");
1307
+ }
1308
+ return sections.join("\n");
1309
+ }
1201
1310
  /**
1202
- * Run a drizzle-kit command for all database providers in an Alepha instance.
1203
- *
1204
- * Iterates through all repository providers, prepares Drizzle config for each,
1205
- * and executes the specified drizzle-kit command.
1206
- *
1207
- * @param options - Configuration including command to run, flags, and logging
1311
+ * Parse git log output into a changelog entry.
1208
1312
  */
1209
- async runDrizzleKitCommand(options) {
1210
- const rootDir = options.root;
1211
- const envFiles = [".env"];
1212
- if (options.env) envFiles.push(`.env.${options.env}`);
1213
- await this.utils.loadEnv(rootDir, envFiles);
1214
- this.log.debug(`Using project root: ${rootDir}`);
1215
- const { alepha, entry } = await this.utils.loadAlephaFromServerEntryFile(rootDir, options.args);
1216
- const drizzleKitProvider = alepha.inject("DrizzleKitProvider");
1217
- const repositoryProvider = alepha.inject("RepositoryProvider");
1218
- const accepted = /* @__PURE__ */ new Set([]);
1219
- for (const primitive of repositoryProvider.getRepositories()) {
1220
- const provider = primitive.provider;
1221
- const providerName = provider.name;
1222
- const dialect = provider.dialect;
1223
- if (accepted.has(providerName)) continue;
1224
- accepted.add(providerName);
1225
- if (options.provider && options.provider !== providerName) {
1226
- this.log.debug(`Skipping provider '${providerName}' (filter: ${options.provider})`);
1313
+ parseCommits(commitsOutput) {
1314
+ const entry = {
1315
+ features: [],
1316
+ fixes: []
1317
+ };
1318
+ for (const line of commitsOutput.trim().split("\n")) {
1319
+ if (!line.trim()) continue;
1320
+ const commit = this.parser.parseCommit(line, this.config);
1321
+ if (!commit) {
1322
+ this.log.trace("Skipping commit", { line });
1227
1323
  continue;
1228
1324
  }
1229
- this.log.info("");
1230
- this.log.info(options.logMessage(providerName, dialect));
1231
- const drizzleConfigJsPath = await this.prepareDrizzleConfig({
1232
- kit: drizzleKitProvider,
1233
- provider,
1234
- providerName,
1235
- providerUrl: provider.url,
1236
- dialect,
1237
- entry,
1238
- rootDir
1239
- });
1240
- const flags = options.commandFlags ? ` ${options.commandFlags}` : "";
1241
- await this.utils.exec(`drizzle-kit ${options.command} --config=${drizzleConfigJsPath}${flags}`, { env: { NODE_OPTIONS: "--import tsx" } });
1325
+ this.log.trace("Parsed commit", { commit });
1326
+ if (commit.type === "feat") entry.features.push(commit);
1327
+ else if (commit.type === "fix") entry.fixes.push(commit);
1242
1328
  }
1329
+ return entry;
1243
1330
  }
1244
1331
  /**
1245
- * Prepare Drizzle configuration files for a database provider.
1246
- *
1247
- * Creates temporary entities.js and drizzle.config.js files needed
1248
- * for Drizzle Kit commands to run properly.
1249
- *
1250
- * @param options - Configuration options including kit, provider info, and paths
1251
- * @returns Path to the generated drizzle.config.js file
1332
+ * Check if entry has any public commits.
1252
1333
  */
1253
- async prepareDrizzleConfig(options) {
1254
- const models = Object.keys(options.kit.getModels(options.provider));
1255
- const entitiesJs = this.utils.generateEntitiesJs(options.entry, options.providerName, models);
1256
- const config = {
1257
- schema: await this.utils.writeConfigFile("entities.js", entitiesJs, options.rootDir),
1258
- out: `./migrations/${options.providerName}`,
1259
- dialect: options.dialect,
1260
- dbCredentials: { url: options.providerUrl }
1261
- };
1262
- if (options.provider.schema) config.schemaFilter = options.provider.schema;
1263
- if (options.providerName === "d1") config.driver = "d1-http";
1264
- if (options.providerName === "pglite") config.driver = "pglite";
1265
- if (options.dialect === "sqlite") if (options.providerName === "d1") {
1266
- const token = process.env.CLOUDFLARE_API_TOKEN;
1267
- if (!token) throw new AlephaError("CLOUDFLARE_API_TOKEN environment variable is not set. https://orm.drizzle.team/docs/guides/d1-http-with-drizzle-kit");
1268
- const accountId = process.env.CLOUDFLARE_ACCOUNT_ID;
1269
- if (!accountId) throw new AlephaError("CLOUDFLARE_ACCOUNT_ID environment variable is not set. https://orm.drizzle.team/docs/guides/d1-http-with-drizzle-kit");
1270
- const url = options.providerUrl;
1271
- if (!url.startsWith("cloudflare-d1://")) throw new AlephaError("D1 provider URL must start with 'cloudflare-d1://'.");
1272
- const [, databaseId] = url.replace("cloudflare-d1://", "").replace("cloudflare-d1:", "").split(":");
1273
- if (!databaseId) throw new AlephaError("Database ID is missing in the D1 provider URL. Cloudflare D1 URL format: cloudflare-d1://<database_name>:<database_id>");
1274
- config.dbCredentials = {
1275
- accountId,
1276
- databaseId,
1277
- token
1278
- };
1279
- } else {
1280
- let url = options.providerUrl;
1281
- url = url.replace("sqlite://", "").replace("file://", "");
1282
- url = join(options.rootDir, url);
1283
- config.dbCredentials = { url };
1284
- }
1285
- const drizzleConfigJs = `export default ${JSON.stringify(config, null, 2)}`;
1286
- return await this.utils.writeConfigFile("drizzle.config.js", drizzleConfigJs, options.rootDir);
1334
+ hasChanges(entry) {
1335
+ return entry.features.length > 0 || entry.fixes.length > 0;
1336
+ }
1337
+ /**
1338
+ * Get the latest version tag.
1339
+ */
1340
+ async getLatestTag(git) {
1341
+ return (await git("tag --sort=-version:refname")).trim().split("\n").filter((tag) => tag.match(/^\d+\.\d+\.\d+$/))[0] || null;
1287
1342
  }
1343
+ command = $command({
1344
+ name: "changelog",
1345
+ description: "Generate changelog from conventional commits (outputs to stdout)",
1346
+ flags: t.object({
1347
+ from: t.optional(t.string({
1348
+ aliases: ["f"],
1349
+ description: "Starting ref (default: latest tag)"
1350
+ })),
1351
+ to: t.optional(t.string({
1352
+ aliases: ["t"],
1353
+ description: "Ending ref (default: HEAD)"
1354
+ }))
1355
+ }),
1356
+ handler: async ({ flags, root }) => {
1357
+ const git = (cmd) => this.git.exec(cmd, root);
1358
+ let fromRef;
1359
+ if (flags.from) {
1360
+ fromRef = flags.from;
1361
+ this.log.debug("Using specified from ref", { from: fromRef });
1362
+ } else {
1363
+ const latestTag = await this.getLatestTag(git);
1364
+ if (!latestTag) {
1365
+ process.stdout.write("No version tags found in repository\n");
1366
+ return;
1367
+ }
1368
+ fromRef = latestTag;
1369
+ this.log.debug("Using latest tag", { from: fromRef });
1370
+ }
1371
+ const toRef = flags.to || "HEAD";
1372
+ this.log.debug("Using to ref", { to: toRef });
1373
+ const commitsOutput = await git(`log ${fromRef}..${toRef} --oneline`);
1374
+ if (!commitsOutput.trim()) {
1375
+ process.stdout.write(`No changes in range ${fromRef}..${toRef}\n`);
1376
+ return;
1377
+ }
1378
+ const entry = this.parseCommits(commitsOutput);
1379
+ if (!this.hasChanges(entry)) {
1380
+ process.stdout.write(`No public changes in range ${fromRef}..${toRef}\n`);
1381
+ return;
1382
+ }
1383
+ process.stdout.write(this.formatEntry(entry));
1384
+ }
1385
+ });
1386
+ };
1387
+
1388
+ //#endregion
1389
+ //#region ../../src/cli/commands/gen/openapi.ts
1390
+ var OpenApiCommand = class {
1391
+ log = $logger();
1392
+ utils = $inject(AlephaCliUtils);
1393
+ fs = $inject(FileSystemProvider);
1394
+ command = $command({
1395
+ name: "openapi",
1396
+ description: "Generate OpenAPI specification from actions",
1397
+ flags: t.object({ out: t.optional(t.text({
1398
+ aliases: ["o"],
1399
+ description: "Output file path"
1400
+ })) }),
1401
+ handler: async ({ root, flags }) => {
1402
+ const { alepha } = await this.utils.loadAlephaFromServerEntryFile(root);
1403
+ try {
1404
+ const openapiProvider = alepha.inject(ServerSwaggerProvider);
1405
+ await alepha.events.emit("configure", alepha);
1406
+ let json = openapiProvider.json;
1407
+ if (!json) json = openapiProvider.generateSwaggerDoc({ info: {
1408
+ title: "API Documentation",
1409
+ version: "1.0.0"
1410
+ } });
1411
+ if (!json) {
1412
+ this.log.error("No actions found to generate OpenAPI specification.");
1413
+ return;
1414
+ }
1415
+ if (flags.out) await this.fs.writeFile(this.fs.join(root, flags.out), JSON.stringify(json, null, 2));
1416
+ else this.log.info(JSON.stringify(json, null, 2));
1417
+ } catch (err) {
1418
+ const message = err instanceof Error ? err.message : String(err);
1419
+ if (message.includes("Service not found")) {
1420
+ this.log.error("Missing $swagger() primitive in your server configuration.");
1421
+ return;
1422
+ }
1423
+ this.log.error(`OpenAPI generation failed - ${message}`, { err });
1424
+ }
1425
+ }
1426
+ });
1427
+ };
1428
+
1429
+ //#endregion
1430
+ //#region ../../src/cli/commands/gen.ts
1431
+ var GenCommand = class {
1432
+ changelog = $inject(ChangelogCommand);
1433
+ openapi = $inject(OpenApiCommand);
1434
+ gen = $command({
1435
+ name: "gen",
1436
+ description: "Generate code, documentation, ...",
1437
+ children: [this.changelog.command, this.openapi.command],
1438
+ handler: async ({ help }) => {
1439
+ help();
1440
+ }
1441
+ });
1288
1442
  };
1289
1443
 
1290
1444
  //#endregion
1291
- //#region ../../src/cli/commands/VerifyCommands.ts
1292
- var VerifyCommands = class {
1445
+ //#region ../../src/cli/commands/init.ts
1446
+ var InitCommand = class {
1293
1447
  utils = $inject(AlephaCliUtils);
1294
1448
  /**
1295
- * Run a series of verification commands to ensure code quality and correctness.
1296
- *
1297
- * This command runs the following checks in order:
1298
- * - Clean the project
1299
- * - Format the code
1300
- * - Lint the code
1301
- * - Run tests (if Vitest is a dev dependency)
1302
- * - Check database migrations (if a migrations directory exists)
1303
- * - Type check the code
1304
- * - Build the project
1305
- * - Clean the project again
1449
+ * Ensure the project has the necessary Alepha configuration files.
1450
+ * Add the correct dependencies to package.json and install them.
1306
1451
  */
1307
- verify = $command({
1308
- name: "verify",
1309
- description: "Verify the Alepha project",
1310
- handler: async ({ root, run }) => {
1311
- await run("alepha clean");
1312
- await run("alepha format");
1313
- await run("alepha lint");
1314
- await run("alepha typecheck");
1315
- if ((await this.utils.readPackageJson(root)).devDependencies?.vitest) await run("alepha test");
1316
- if (await this.utils.exists(root, "migrations")) await run("alepha db:check-migrations");
1317
- if (!await this.utils.hasExpo(root)) await run("alepha build");
1318
- await run("alepha clean");
1452
+ init = $command({
1453
+ name: "init",
1454
+ description: "Add missing Alepha configuration files to the project",
1455
+ flags: t.object({
1456
+ yarn: t.optional(t.boolean({ description: "Use Yarn package manager" })),
1457
+ pnpm: t.optional(t.boolean({ description: "Use pnpm package manager" })),
1458
+ npm: t.optional(t.boolean({ description: "Use npm package manager" })),
1459
+ bun: t.optional(t.boolean({ description: "Use Bun package manager" })),
1460
+ react: t.optional(t.boolean({ description: "Include Alepha React dependencies" })),
1461
+ ui: t.optional(t.boolean({ description: "Include Alepha UI dependencies" })),
1462
+ test: t.optional(t.boolean({ description: "Include Vitest and create test directory" }))
1463
+ }),
1464
+ handler: async ({ run, flags, root }) => {
1465
+ if (flags.ui) flags.react = true;
1466
+ const isExpo = await this.utils.hasExpo(root);
1467
+ await run({
1468
+ name: "ensuring configuration files",
1469
+ handler: async () => {
1470
+ await this.utils.ensureConfig(root, {
1471
+ tsconfigJson: true,
1472
+ packageJson: flags,
1473
+ biomeJson: true,
1474
+ viteConfigTs: !isExpo,
1475
+ editorconfig: true,
1476
+ indexHtml: !!flags.react && !isExpo
1477
+ });
1478
+ if (!flags.react) await this.utils.ensureSrcMain(root);
1479
+ }
1480
+ });
1481
+ const pm = await this.utils.getPackageManager(root, flags);
1482
+ if (pm === "yarn") {
1483
+ await this.utils.ensureYarn(root);
1484
+ await run("yarn set version stable");
1485
+ } else if (pm === "bun") await this.utils.ensureBun(root);
1486
+ else if (pm === "pnpm") await this.utils.ensurePnpm(root);
1487
+ else await this.utils.ensureNpm(root);
1488
+ await run(`${pm} install`, { alias: `installing dependencies with ${pm}` });
1489
+ if (!isExpo) await this.utils.ensureDependency(root, "vite", { run });
1490
+ await this.utils.ensureDependency(root, "@biomejs/biome", { run });
1491
+ if (flags.test) {
1492
+ await this.utils.ensureTestDir(root);
1493
+ await run(`${pm} ${pm === "yarn" ? "add" : "install"} -D vitest`, { alias: "setup testing with Vitest" });
1494
+ }
1495
+ }
1496
+ });
1497
+ };
1498
+
1499
+ //#endregion
1500
+ //#region ../../src/cli/commands/lint.ts
1501
+ var LintCommand = class {
1502
+ utils = $inject(AlephaCliUtils);
1503
+ lint = $command({
1504
+ name: "lint",
1505
+ description: "Run linter across the codebase using Biome",
1506
+ handler: async ({ root }) => {
1507
+ await this.utils.ensureConfig(root, { biomeJson: true });
1508
+ await this.utils.ensureDependency(root, "@biomejs/biome");
1509
+ await this.utils.exec("biome check --formatter-enabled=false --fix");
1319
1510
  }
1320
1511
  });
1512
+ };
1513
+
1514
+ //#endregion
1515
+ //#region ../../src/cli/commands/root.ts
1516
+ var RootCommand = class {
1517
+ log = $logger();
1518
+ cli = $inject(CliProvider);
1519
+ alepha = $inject(Alepha);
1520
+ color = $inject(ConsoleColorProvider);
1321
1521
  /**
1322
- * Run TypeScript type checking across the codebase with no emit.
1522
+ * Called when no command is provided
1323
1523
  */
1324
- typecheck = $command({
1325
- name: "typecheck",
1326
- description: "Check TypeScript types across the codebase",
1327
- handler: async ({ root }) => {
1328
- await this.utils.ensureDependency(root, "typescript");
1329
- await this.utils.exec("tsc --noEmit");
1524
+ root = $command({
1525
+ root: true,
1526
+ flags: t.object({ version: t.optional(t.boolean({
1527
+ description: "Show Alepha CLI version",
1528
+ aliases: ["v"]
1529
+ })) }),
1530
+ handler: async ({ flags }) => {
1531
+ if (flags.version) {
1532
+ this.log.info(this.color.set("WHITE_BOLD", `Alepha v${version}`));
1533
+ if (this.alepha.isBun()) this.log.info(this.color.set("GREY_DARK", `└─ Bun v${Bun.version}`));
1534
+ else this.log.info(this.color.set("GREY_DARK", `└─ Node ${process.version}`));
1535
+ return;
1536
+ }
1537
+ this.cli.printHelp();
1330
1538
  }
1331
1539
  });
1332
1540
  };
1333
1541
 
1334
1542
  //#endregion
1335
- //#region ../../src/cli/commands/ViteCommands.ts
1336
- var ViteCommands = class {
1337
- log = $logger();
1543
+ //#region ../../src/cli/commands/run.ts
1544
+ var RunCommand = class {
1338
1545
  utils = $inject(AlephaCliUtils);
1339
- env = $env(t.object({ VITEST_ARGS: t.string({ default: "" }) }));
1340
1546
  run = $command({
1341
1547
  name: "run",
1342
1548
  hide: true,
@@ -1355,171 +1561,12 @@ var ViteCommands = class {
1355
1561
  await this.utils.exec(`tsx ${flags.watch ? "watch " : ""}${args}`);
1356
1562
  }
1357
1563
  });
1358
- /**
1359
- * Will run the project in watch mode.
1360
- *
1361
- * - If an index.html file is found in the project root, it will run Vite in dev mode.
1362
- * - Otherwise, it will look for a server entry file and run it with tsx in watch mode.
1363
- */
1364
- dev = $command({
1365
- name: "dev",
1366
- description: "Run the project in development mode",
1367
- args: t.optional(t.text({
1368
- title: "path",
1369
- description: "Filepath to run"
1370
- })),
1371
- handler: async ({ args, root }) => {
1372
- const expo = await this.utils.hasExpo(root);
1373
- await this.utils.ensureConfig(root, {
1374
- viteConfigTs: !expo,
1375
- tsconfigJson: true
1376
- });
1377
- if (expo) {
1378
- await this.utils.exec(`expo start`);
1379
- return;
1380
- }
1381
- const entry = await boot.getServerEntry(root, args);
1382
- this.log.trace("Entry file found", { entry });
1383
- try {
1384
- await access(join(root, "index.html"));
1385
- } catch {
1386
- this.log.trace("No index.html found, running entry file with tsx");
1387
- let cmd = "tsx --watch";
1388
- if (await this.utils.exists(root, ".env")) cmd += ` --env-file=./.env`;
1389
- cmd += ` ${entry}`;
1390
- await this.utils.exec(cmd);
1391
- return;
1392
- }
1393
- await this.utils.ensureDependency(root, "vite");
1394
- await this.utils.exec(`vite`);
1395
- }
1396
- });
1397
- build = $command({
1398
- name: "build",
1399
- description: "Build the project for production",
1400
- args: t.optional(t.text({
1401
- title: "path",
1402
- description: "Filepath to build"
1403
- })),
1404
- flags: t.object({
1405
- stats: t.optional(t.boolean({ description: "Generate build stats report" })),
1406
- vercel: t.optional(t.boolean({ description: "Generate Vercel deployment configuration" })),
1407
- cloudflare: t.optional(t.boolean({ description: "Generate Cloudflare Workers configuration" })),
1408
- docker: t.optional(t.boolean({ description: "Generate Docker configuration" })),
1409
- sitemap: t.optional(t.text({ description: "Generate sitemap.xml with base URL" }))
1410
- }),
1411
- handler: async ({ flags, args, run, root }) => {
1412
- process.env.ALEPHA_BUILD_MODE = "cli";
1413
- process.env.NODE_ENV = "production";
1414
- if (await this.utils.hasExpo(root)) return;
1415
- await this.utils.ensureConfig(root, {
1416
- viteConfigTs: true,
1417
- tsconfigJson: true
1418
- });
1419
- const entry = await boot.getServerEntry(root, args);
1420
- this.log.trace("Entry file found", { entry });
1421
- const distDir = "dist";
1422
- const clientDir = "public";
1423
- await this.utils.ensureDependency(root, "vite", { run });
1424
- await run.rm("dist", { alias: "clean dist" });
1425
- const viteAlephaBuildOptions = (await createRequire(import.meta.url)("vite").resolveConfig({}, "build", "production")).plugins.find((it) => it.name === "alepha:build")?.[OPTIONS] || {};
1426
- await this.utils.loadEnv(root, [".env", ".env.production"]);
1427
- const stats = flags.stats ?? viteAlephaBuildOptions.stats ?? false;
1428
- const hasServer = viteAlephaBuildOptions.serverEntry !== false;
1429
- let hasClient = false;
1430
- try {
1431
- await access(join(root, "index.html"));
1432
- hasClient = true;
1433
- } catch {}
1434
- const clientOptions = typeof viteAlephaBuildOptions.client === "object" ? viteAlephaBuildOptions.client : {};
1435
- if (hasClient) await run({
1436
- name: "vite build client",
1437
- handler: () => buildClient({
1438
- silent: true,
1439
- dist: `${distDir}/${clientDir}`,
1440
- stats,
1441
- precompress: clientOptions.precompress
1442
- })
1443
- });
1444
- await run({
1445
- name: "vite build server",
1446
- handler: async () => {
1447
- let clientBuilt = false;
1448
- try {
1449
- await readFile(`${distDir}/${clientDir}/index.html`, "utf-8");
1450
- clientBuilt = true;
1451
- } catch {}
1452
- await buildServer({
1453
- silent: true,
1454
- entry,
1455
- distDir,
1456
- clientDir: clientBuilt ? clientDir : void 0,
1457
- stats
1458
- });
1459
- if (clientBuilt && hasServer) await unlink(`${distDir}/${clientDir}/index.html`);
1460
- }
1461
- });
1462
- await copyAssets({
1463
- root,
1464
- entry: `${distDir}/index.js`,
1465
- distDir,
1466
- run
1467
- });
1468
- if (hasClient) {
1469
- const sitemapBaseUrl = flags.sitemap ?? clientOptions.sitemap?.hostname;
1470
- if (sitemapBaseUrl) await run({
1471
- name: "add sitemap",
1472
- handler: async () => {
1473
- await writeFile(`${distDir}/${clientDir}/sitemap.xml`, await generateSitemap({
1474
- entry: `${distDir}/index.js`,
1475
- baseUrl: sitemapBaseUrl
1476
- }));
1477
- }
1478
- });
1479
- if (clientOptions.prerender) await run({
1480
- name: "pre-render pages",
1481
- handler: async () => {
1482
- await prerenderPages({
1483
- dist: `${distDir}/${clientDir}`,
1484
- entry: `${distDir}/index.js`,
1485
- compress: clientOptions.precompress
1486
- });
1487
- }
1488
- });
1489
- }
1490
- if (flags.vercel || viteAlephaBuildOptions.vercel) {
1491
- const config = typeof viteAlephaBuildOptions.vercel === "object" ? viteAlephaBuildOptions.vercel : {};
1492
- await run({
1493
- name: "add Vercel config",
1494
- handler: () => generateVercel({
1495
- distDir,
1496
- clientDir,
1497
- config
1498
- })
1499
- });
1500
- }
1501
- if (flags.cloudflare || viteAlephaBuildOptions.cloudflare) {
1502
- const config = typeof viteAlephaBuildOptions.cloudflare === "boolean" ? {} : viteAlephaBuildOptions.cloudflare;
1503
- await run({
1504
- name: "add Cloudflare config",
1505
- handler: () => generateCloudflare({
1506
- distDir,
1507
- config
1508
- })
1509
- });
1510
- }
1511
- if (flags.docker || viteAlephaBuildOptions.docker) {
1512
- const dockerConfig = typeof viteAlephaBuildOptions.docker === "object" ? viteAlephaBuildOptions.docker : {};
1513
- await run({
1514
- name: "add Docker config",
1515
- handler: () => generateDocker({
1516
- distDir,
1517
- ...dockerConfig
1518
- })
1519
- });
1520
- }
1521
- }
1522
- });
1564
+ };
1565
+
1566
+ //#endregion
1567
+ //#region ../../src/cli/commands/test.ts
1568
+ var TestCommand = class {
1569
+ utils = $inject(AlephaCliUtils);
1523
1570
  test = $command({
1524
1571
  name: "test",
1525
1572
  description: "Run tests using Vitest",
@@ -1543,15 +1590,82 @@ var ViteCommands = class {
1543
1590
  });
1544
1591
  };
1545
1592
 
1593
+ //#endregion
1594
+ //#region ../../src/cli/commands/typecheck.ts
1595
+ var TypecheckCommand = class {
1596
+ utils = $inject(AlephaCliUtils);
1597
+ log = $logger();
1598
+ /**
1599
+ * Run TypeScript type checking across the codebase with no emit.
1600
+ */
1601
+ typecheck = $command({
1602
+ name: "typecheck",
1603
+ aliases: ["tc"],
1604
+ description: "Check TypeScript types across the codebase",
1605
+ handler: async ({ root }) => {
1606
+ this.log.info("Starting TypeScript type checking...");
1607
+ await this.utils.ensureDependency(root, "typescript");
1608
+ await this.utils.exec("tsc --noEmit");
1609
+ this.log.info("TypeScript type checking completed successfully.");
1610
+ }
1611
+ });
1612
+ };
1613
+
1614
+ //#endregion
1615
+ //#region ../../src/cli/commands/verify.ts
1616
+ var VerifyCommand = class {
1617
+ utils = $inject(AlephaCliUtils);
1618
+ /**
1619
+ * Run a series of verification commands to ensure code quality and correctness.
1620
+ *
1621
+ * This command runs the following checks in order:
1622
+ * - Clean the project
1623
+ * - Format the code
1624
+ * - Lint the code
1625
+ * - Run tests (if Vitest is a dev dependency)
1626
+ * - Check database migrations (if a migrations directory exists)
1627
+ * - Type check the code
1628
+ * - Build the project
1629
+ * - Clean the project again
1630
+ */
1631
+ verify = $command({
1632
+ name: "verify",
1633
+ description: "Verify the Alepha project",
1634
+ handler: async ({ root, run }) => {
1635
+ await run("alepha clean");
1636
+ await run("alepha format");
1637
+ await run("alepha lint");
1638
+ await run("alepha typecheck");
1639
+ if ((await this.utils.readPackageJson(root)).devDependencies?.vitest) await run("alepha test");
1640
+ if (await this.utils.exists(root, "migrations")) await run("alepha db:check-migrations");
1641
+ if (!await this.utils.hasExpo(root)) await run("alepha build");
1642
+ await run("alepha clean");
1643
+ }
1644
+ });
1645
+ };
1646
+
1546
1647
  //#endregion
1547
1648
  //#region ../../src/cli/apps/AlephaCli.ts
1649
+ /**
1650
+ * Register `tsx` when running in Node.js, ignore for Bun.
1651
+ *
1652
+ * It's required to have a full TypeScript support. (mostly .tsx files)
1653
+ */
1654
+ if (typeof Bun === "undefined") {
1655
+ const { register } = await import("tsx/esm/api");
1656
+ register();
1657
+ }
1658
+ /**
1659
+ * Allow to extend Alepha CLI via `alepha.config.ts` file located in the project root.
1660
+ */
1548
1661
  var AlephaCliExtension = class {
1549
1662
  alepha = $inject(Alepha);
1550
1663
  fs = $inject(FileSystemProvider);
1551
1664
  onConfigure = $hook({
1552
1665
  on: "configure",
1553
1666
  handler: async () => {
1554
- const extensionPath = join(process.cwd(), "alepha.config.ts");
1667
+ const root = process.cwd();
1668
+ const extensionPath = this.fs.join(root, "alepha.config.ts");
1555
1669
  if (!await this.fs.exists(extensionPath)) return;
1556
1670
  const { default: Extension } = await import(extensionPath);
1557
1671
  if (typeof Extension !== "function") return;
@@ -1563,13 +1677,21 @@ const AlephaCli = $module({
1563
1677
  name: "alepha.cli",
1564
1678
  services: [
1565
1679
  AlephaCliExtension,
1566
- BiomeCommands,
1567
- ChangelogCommands,
1568
- CoreCommands,
1569
- DeployCommands,
1570
- DrizzleCommands,
1571
- VerifyCommands,
1572
- ViteCommands
1680
+ BuildCommand,
1681
+ CleanCommand,
1682
+ DbCommand,
1683
+ DeployCommand,
1684
+ DevCommand,
1685
+ FormatCommand,
1686
+ InitCommand,
1687
+ LintCommand,
1688
+ RootCommand,
1689
+ RunCommand,
1690
+ TestCommand,
1691
+ TypecheckCommand,
1692
+ VerifyCommand,
1693
+ GenCommand,
1694
+ GitProvider
1573
1695
  ]
1574
1696
  });
1575
1697
 
@@ -1606,7 +1728,10 @@ var AlephaPackageBuilderCli = class {
1606
1728
  pkgData.exports["./tsconfig.base"] = "./tsconfig.base.json";
1607
1729
  pkgData.exports["./package.json"] = "./package.json";
1608
1730
  }
1609
- if (packageName === "@alepha/ui") pkgData.exports["./styles"] = "./styles.css";
1731
+ if (packageName === "@alepha/ui") {
1732
+ pkgData.exports["./styles"] = "./src/core/styles.css";
1733
+ pkgData.exports["./json/styles"] = "./src/json/styles.css";
1734
+ }
1610
1735
  await this.fs.writeFile("package.json", JSON.stringify(pkgData, null, 2));
1611
1736
  const tmpDir = join(root, "node_modules/.alepha");
1612
1737
  await this.fs.mkdir(tmpDir, { recursive: true }).catch(() => {});
@@ -1767,11 +1892,16 @@ async function analyzeModules(srcDir, packageName) {
1767
1892
  //#region ../../src/cli/defineConfig.ts
1768
1893
  const defineConfig = (config) => {
1769
1894
  return (alepha) => {
1770
- const { commands } = config(alepha);
1895
+ const { commands, services = [] } = config(alepha);
1896
+ for (const it of services) alepha.with(it);
1771
1897
  return { ...commands };
1772
1898
  };
1773
1899
  };
1900
+ /**
1901
+ * @alias defineConfig
1902
+ */
1903
+ const defineAlephaConfig = defineConfig;
1774
1904
 
1775
1905
  //#endregion
1776
- export { AlephaCli, AlephaCliUtils, AlephaPackageBuilderCli, BiomeCommands, ChangelogCommands, CoreCommands, DEFAULT_IGNORE, DeployCommands, DrizzleCommands, GitMessageParser, GitProvider, VerifyCommands, ViteCommands, analyzeModules, changelogOptions, defineConfig, version };
1906
+ export { AlephaCli, AlephaCliUtils, AlephaPackageBuilderCli, BuildCommand, ChangelogCommand, CleanCommand, DEFAULT_IGNORE, DbCommand, DeployCommand, DevCommand, FormatCommand, GitMessageParser, GitProvider, InitCommand, LintCommand, OpenApiCommand, RootCommand, RunCommand, TestCommand, TypecheckCommand, VerifyCommand, analyzeModules, changelogOptions, defineAlephaConfig, defineConfig, version };
1777
1907
  //# sourceMappingURL=index.js.map