alepha 0.14.3 → 0.15.0

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 (317) hide show
  1. package/README.md +2 -5
  2. package/dist/api/audits/index.d.ts +620 -811
  3. package/dist/api/audits/index.d.ts.map +1 -1
  4. package/dist/api/files/index.d.ts +185 -377
  5. package/dist/api/files/index.d.ts.map +1 -1
  6. package/dist/api/files/index.js +0 -1
  7. package/dist/api/files/index.js.map +1 -1
  8. package/dist/api/jobs/index.d.ts +245 -435
  9. package/dist/api/jobs/index.d.ts.map +1 -1
  10. package/dist/api/notifications/index.d.ts +238 -429
  11. package/dist/api/notifications/index.d.ts.map +1 -1
  12. package/dist/api/parameters/index.d.ts +236 -427
  13. package/dist/api/parameters/index.d.ts.map +1 -1
  14. package/dist/api/users/index.browser.js +1 -2
  15. package/dist/api/users/index.browser.js.map +1 -1
  16. package/dist/api/users/index.d.ts +1010 -1196
  17. package/dist/api/users/index.d.ts.map +1 -1
  18. package/dist/api/users/index.js +178 -151
  19. package/dist/api/users/index.js.map +1 -1
  20. package/dist/api/verifications/index.d.ts +17 -17
  21. package/dist/api/verifications/index.d.ts.map +1 -1
  22. package/dist/batch/index.d.ts +122 -122
  23. package/dist/batch/index.d.ts.map +1 -1
  24. package/dist/batch/index.js +1 -2
  25. package/dist/batch/index.js.map +1 -1
  26. package/dist/bucket/index.d.ts +163 -163
  27. package/dist/bucket/index.d.ts.map +1 -1
  28. package/dist/cache/core/index.d.ts +46 -46
  29. package/dist/cache/core/index.d.ts.map +1 -1
  30. package/dist/cache/redis/index.d.ts.map +1 -1
  31. package/dist/cli/index.d.ts +384 -285
  32. package/dist/cli/index.d.ts.map +1 -1
  33. package/dist/cli/index.js +1113 -623
  34. package/dist/cli/index.js.map +1 -1
  35. package/dist/command/index.d.ts +299 -300
  36. package/dist/command/index.d.ts.map +1 -1
  37. package/dist/command/index.js +13 -9
  38. package/dist/command/index.js.map +1 -1
  39. package/dist/core/index.browser.js +445 -103
  40. package/dist/core/index.browser.js.map +1 -1
  41. package/dist/core/index.d.ts +733 -625
  42. package/dist/core/index.d.ts.map +1 -1
  43. package/dist/core/index.js +446 -103
  44. package/dist/core/index.js.map +1 -1
  45. package/dist/core/index.native.js +445 -103
  46. package/dist/core/index.native.js.map +1 -1
  47. package/dist/datetime/index.d.ts +44 -44
  48. package/dist/datetime/index.d.ts.map +1 -1
  49. package/dist/datetime/index.js +4 -4
  50. package/dist/datetime/index.js.map +1 -1
  51. package/dist/email/index.d.ts +97 -50
  52. package/dist/email/index.d.ts.map +1 -1
  53. package/dist/email/index.js +129 -33
  54. package/dist/email/index.js.map +1 -1
  55. package/dist/fake/index.d.ts +7981 -14
  56. package/dist/fake/index.d.ts.map +1 -1
  57. package/dist/file/index.d.ts +523 -390
  58. package/dist/file/index.d.ts.map +1 -1
  59. package/dist/file/index.js +253 -1
  60. package/dist/file/index.js.map +1 -1
  61. package/dist/lock/core/index.d.ts +208 -208
  62. package/dist/lock/core/index.d.ts.map +1 -1
  63. package/dist/lock/redis/index.d.ts.map +1 -1
  64. package/dist/logger/index.d.ts +25 -26
  65. package/dist/logger/index.d.ts.map +1 -1
  66. package/dist/logger/index.js +12 -2
  67. package/dist/logger/index.js.map +1 -1
  68. package/dist/mcp/index.d.ts +197 -197
  69. package/dist/mcp/index.d.ts.map +1 -1
  70. package/dist/mcp/index.js +1 -1
  71. package/dist/mcp/index.js.map +1 -1
  72. package/dist/orm/chunk-DtkW-qnP.js +38 -0
  73. package/dist/orm/index.browser.js.map +1 -1
  74. package/dist/orm/index.bun.js +2814 -0
  75. package/dist/orm/index.bun.js.map +1 -0
  76. package/dist/orm/index.d.ts +1228 -1216
  77. package/dist/orm/index.d.ts.map +1 -1
  78. package/dist/orm/index.js +2041 -1967
  79. package/dist/orm/index.js.map +1 -1
  80. package/dist/queue/core/index.d.ts +248 -248
  81. package/dist/queue/core/index.d.ts.map +1 -1
  82. package/dist/queue/redis/index.d.ts.map +1 -1
  83. package/dist/redis/index.bun.js +285 -0
  84. package/dist/redis/index.bun.js.map +1 -0
  85. package/dist/redis/index.d.ts +118 -136
  86. package/dist/redis/index.d.ts.map +1 -1
  87. package/dist/redis/index.js +18 -38
  88. package/dist/redis/index.js.map +1 -1
  89. package/dist/retry/index.d.ts +69 -69
  90. package/dist/retry/index.d.ts.map +1 -1
  91. package/dist/router/index.d.ts +6 -6
  92. package/dist/router/index.d.ts.map +1 -1
  93. package/dist/scheduler/index.d.ts +25 -25
  94. package/dist/scheduler/index.d.ts.map +1 -1
  95. package/dist/security/index.browser.js +5 -1
  96. package/dist/security/index.browser.js.map +1 -1
  97. package/dist/security/index.d.ts +417 -254
  98. package/dist/security/index.d.ts.map +1 -1
  99. package/dist/security/index.js +386 -86
  100. package/dist/security/index.js.map +1 -1
  101. package/dist/server/auth/index.d.ts +110 -110
  102. package/dist/server/auth/index.d.ts.map +1 -1
  103. package/dist/server/auth/index.js +20 -20
  104. package/dist/server/auth/index.js.map +1 -1
  105. package/dist/server/cache/index.d.ts +62 -47
  106. package/dist/server/cache/index.d.ts.map +1 -1
  107. package/dist/server/cache/index.js +56 -3
  108. package/dist/server/cache/index.js.map +1 -1
  109. package/dist/server/compress/index.d.ts +6 -0
  110. package/dist/server/compress/index.d.ts.map +1 -1
  111. package/dist/server/compress/index.js +36 -1
  112. package/dist/server/compress/index.js.map +1 -1
  113. package/dist/server/cookies/index.d.ts +6 -6
  114. package/dist/server/cookies/index.d.ts.map +1 -1
  115. package/dist/server/cookies/index.js +3 -3
  116. package/dist/server/cookies/index.js.map +1 -1
  117. package/dist/server/core/index.browser.js +2 -2
  118. package/dist/server/core/index.browser.js.map +1 -1
  119. package/dist/server/core/index.d.ts +242 -150
  120. package/dist/server/core/index.d.ts.map +1 -1
  121. package/dist/server/core/index.js +294 -125
  122. package/dist/server/core/index.js.map +1 -1
  123. package/dist/server/cors/index.d.ts +11 -12
  124. package/dist/server/cors/index.d.ts.map +1 -1
  125. package/dist/server/health/index.d.ts +0 -1
  126. package/dist/server/health/index.d.ts.map +1 -1
  127. package/dist/server/helmet/index.d.ts +2 -2
  128. package/dist/server/helmet/index.d.ts.map +1 -1
  129. package/dist/server/links/index.browser.js.map +1 -1
  130. package/dist/server/links/index.d.ts +123 -124
  131. package/dist/server/links/index.d.ts.map +1 -1
  132. package/dist/server/links/index.js +1 -2
  133. package/dist/server/links/index.js.map +1 -1
  134. package/dist/server/metrics/index.d.ts.map +1 -1
  135. package/dist/server/multipart/index.d.ts +6 -6
  136. package/dist/server/multipart/index.d.ts.map +1 -1
  137. package/dist/server/proxy/index.d.ts +102 -103
  138. package/dist/server/proxy/index.d.ts.map +1 -1
  139. package/dist/server/rate-limit/index.d.ts +16 -16
  140. package/dist/server/rate-limit/index.d.ts.map +1 -1
  141. package/dist/server/static/index.d.ts +44 -44
  142. package/dist/server/static/index.d.ts.map +1 -1
  143. package/dist/server/static/index.js +4 -0
  144. package/dist/server/static/index.js.map +1 -1
  145. package/dist/server/swagger/index.d.ts +48 -49
  146. package/dist/server/swagger/index.d.ts.map +1 -1
  147. package/dist/server/swagger/index.js +3 -5
  148. package/dist/server/swagger/index.js.map +1 -1
  149. package/dist/sms/index.d.ts +13 -11
  150. package/dist/sms/index.d.ts.map +1 -1
  151. package/dist/sms/index.js +7 -7
  152. package/dist/sms/index.js.map +1 -1
  153. package/dist/thread/index.d.ts +71 -72
  154. package/dist/thread/index.d.ts.map +1 -1
  155. package/dist/topic/core/index.d.ts +318 -318
  156. package/dist/topic/core/index.d.ts.map +1 -1
  157. package/dist/topic/redis/index.d.ts +6 -6
  158. package/dist/topic/redis/index.d.ts.map +1 -1
  159. package/dist/vite/index.d.ts +5805 -249
  160. package/dist/vite/index.d.ts.map +1 -1
  161. package/dist/vite/index.js +599 -513
  162. package/dist/vite/index.js.map +1 -1
  163. package/dist/websocket/index.browser.js +6 -6
  164. package/dist/websocket/index.browser.js.map +1 -1
  165. package/dist/websocket/index.d.ts +247 -247
  166. package/dist/websocket/index.d.ts.map +1 -1
  167. package/dist/websocket/index.js +6 -6
  168. package/dist/websocket/index.js.map +1 -1
  169. package/package.json +9 -14
  170. package/src/api/files/controllers/AdminFileStatsController.ts +0 -1
  171. package/src/api/users/atoms/realmAuthSettingsAtom.ts +5 -0
  172. package/src/api/users/controllers/{UserRealmController.ts → RealmController.ts} +11 -11
  173. package/src/api/users/entities/users.ts +1 -1
  174. package/src/api/users/index.ts +8 -8
  175. package/src/api/users/primitives/{$userRealm.ts → $realm.ts} +17 -19
  176. package/src/api/users/providers/{UserRealmProvider.ts → RealmProvider.ts} +26 -30
  177. package/src/api/users/schemas/{userRealmConfigSchema.ts → realmConfigSchema.ts} +2 -2
  178. package/src/api/users/services/CredentialService.ts +7 -7
  179. package/src/api/users/services/IdentityService.ts +4 -4
  180. package/src/api/users/services/RegistrationService.spec.ts +25 -27
  181. package/src/api/users/services/RegistrationService.ts +38 -27
  182. package/src/api/users/services/SessionCrudService.ts +3 -3
  183. package/src/api/users/services/SessionService.spec.ts +3 -3
  184. package/src/api/users/services/SessionService.ts +28 -9
  185. package/src/api/users/services/UserService.ts +7 -7
  186. package/src/batch/providers/BatchProvider.ts +1 -2
  187. package/src/cli/apps/AlephaCli.ts +0 -2
  188. package/src/cli/apps/AlephaPackageBuilderCli.ts +38 -19
  189. package/src/cli/assets/apiHelloControllerTs.ts +18 -0
  190. package/src/cli/assets/apiIndexTs.ts +16 -0
  191. package/src/cli/assets/claudeMd.ts +303 -0
  192. package/src/cli/assets/mainBrowserTs.ts +2 -2
  193. package/src/cli/assets/mainServerTs.ts +24 -0
  194. package/src/cli/assets/webAppRouterTs.ts +15 -0
  195. package/src/cli/assets/webHelloComponentTsx.ts +16 -0
  196. package/src/cli/assets/webIndexTs.ts +16 -0
  197. package/src/cli/atoms/buildOptions.ts +88 -0
  198. package/src/cli/commands/build.ts +70 -87
  199. package/src/cli/commands/db.ts +21 -22
  200. package/src/cli/commands/deploy.ts +17 -5
  201. package/src/cli/commands/dev.ts +22 -14
  202. package/src/cli/commands/format.ts +8 -2
  203. package/src/cli/commands/gen/env.ts +53 -0
  204. package/src/cli/commands/gen/openapi.ts +1 -1
  205. package/src/cli/commands/gen/resource.ts +15 -0
  206. package/src/cli/commands/gen.ts +7 -1
  207. package/src/cli/commands/init.ts +74 -30
  208. package/src/cli/commands/lint.ts +8 -2
  209. package/src/cli/commands/test.ts +8 -3
  210. package/src/cli/commands/typecheck.ts +5 -1
  211. package/src/cli/commands/verify.ts +5 -3
  212. package/src/cli/defineConfig.ts +49 -7
  213. package/src/cli/index.ts +0 -1
  214. package/src/cli/services/AlephaCliUtils.ts +39 -589
  215. package/src/cli/services/PackageManagerUtils.ts +301 -0
  216. package/src/cli/services/ProjectScaffolder.ts +306 -0
  217. package/src/command/helpers/Runner.spec.ts +2 -2
  218. package/src/command/helpers/Runner.ts +16 -4
  219. package/src/command/primitives/$command.ts +0 -6
  220. package/src/command/providers/CliProvider.ts +1 -3
  221. package/src/core/Alepha.ts +42 -0
  222. package/src/core/__tests__/Alepha-graph.spec.ts +4 -0
  223. package/src/core/index.shared.ts +1 -0
  224. package/src/core/index.ts +2 -0
  225. package/src/core/primitives/$hook.ts +6 -2
  226. package/src/core/primitives/$module.spec.ts +4 -0
  227. package/src/core/providers/AlsProvider.ts +1 -1
  228. package/src/core/providers/CodecManager.spec.ts +12 -6
  229. package/src/core/providers/CodecManager.ts +26 -6
  230. package/src/core/providers/EventManager.ts +169 -13
  231. package/src/core/providers/KeylessJsonSchemaCodec.spec.ts +621 -0
  232. package/src/core/providers/KeylessJsonSchemaCodec.ts +407 -0
  233. package/src/core/providers/StateManager.spec.ts +27 -16
  234. package/src/email/providers/LocalEmailProvider.spec.ts +111 -87
  235. package/src/email/providers/LocalEmailProvider.ts +52 -15
  236. package/src/email/providers/NodemailerEmailProvider.ts +167 -56
  237. package/src/file/errors/FileError.ts +7 -0
  238. package/src/file/index.ts +9 -1
  239. package/src/file/providers/MemoryFileSystemProvider.ts +393 -0
  240. package/src/logger/index.ts +15 -3
  241. package/src/mcp/transports/StdioMcpTransport.ts +1 -1
  242. package/src/orm/index.browser.ts +1 -19
  243. package/src/orm/index.bun.ts +77 -0
  244. package/src/orm/index.shared-server.ts +22 -0
  245. package/src/orm/index.shared.ts +15 -0
  246. package/src/orm/index.ts +13 -39
  247. package/src/orm/providers/drivers/BunPostgresProvider.ts +3 -5
  248. package/src/orm/providers/drivers/BunSqliteProvider.ts +1 -1
  249. package/src/orm/providers/drivers/CloudflareD1Provider.ts +4 -0
  250. package/src/orm/providers/drivers/DatabaseProvider.ts +4 -0
  251. package/src/orm/providers/drivers/PglitePostgresProvider.ts +4 -0
  252. package/src/orm/services/Repository.ts +8 -0
  253. package/src/queue/core/providers/WorkerProvider.spec.ts +48 -32
  254. package/src/redis/index.bun.ts +35 -0
  255. package/src/redis/providers/BunRedisProvider.ts +12 -43
  256. package/src/redis/providers/BunRedisSubscriberProvider.ts +2 -3
  257. package/src/redis/providers/NodeRedisProvider.ts +16 -34
  258. package/src/{server/security → security}/__tests__/BasicAuth.spec.ts +11 -11
  259. package/src/{server/security → security}/__tests__/ServerSecurityProvider-realm.spec.ts +21 -16
  260. package/src/{server/security/providers → security/__tests__}/ServerSecurityProvider.spec.ts +5 -5
  261. package/src/security/index.browser.ts +5 -0
  262. package/src/security/index.ts +90 -7
  263. package/src/security/primitives/{$realm.spec.ts → $issuer.spec.ts} +11 -11
  264. package/src/security/primitives/{$realm.ts → $issuer.ts} +20 -17
  265. package/src/security/primitives/$role.ts +5 -5
  266. package/src/security/primitives/$serviceAccount.spec.ts +5 -5
  267. package/src/security/primitives/$serviceAccount.ts +3 -3
  268. package/src/{server/security → security}/providers/ServerSecurityProvider.ts +5 -7
  269. package/src/server/auth/primitives/$auth.ts +10 -10
  270. package/src/server/auth/primitives/$authCredentials.ts +3 -3
  271. package/src/server/auth/primitives/$authGithub.ts +3 -3
  272. package/src/server/auth/primitives/$authGoogle.ts +3 -3
  273. package/src/server/auth/providers/ServerAuthProvider.ts +13 -13
  274. package/src/server/cache/providers/ServerCacheProvider.spec.ts +183 -0
  275. package/src/server/cache/providers/ServerCacheProvider.ts +95 -10
  276. package/src/server/compress/providers/ServerCompressProvider.ts +61 -2
  277. package/src/server/cookies/providers/ServerCookiesProvider.ts +3 -3
  278. package/src/server/core/helpers/ServerReply.ts +2 -2
  279. package/src/server/core/providers/NodeHttpServerProvider.ts +25 -6
  280. package/src/server/core/providers/ServerBodyParserProvider.ts +19 -23
  281. package/src/server/core/providers/ServerLoggerProvider.ts +23 -19
  282. package/src/server/core/providers/ServerProvider.ts +155 -22
  283. package/src/server/core/providers/ServerRouterProvider.ts +259 -115
  284. package/src/server/core/providers/ServerTimingProvider.ts +2 -2
  285. package/src/server/links/index.ts +1 -1
  286. package/src/server/links/providers/LinkProvider.ts +1 -1
  287. package/src/server/static/providers/ServerStaticProvider.ts +10 -0
  288. package/src/server/swagger/index.ts +1 -1
  289. package/src/server/swagger/providers/ServerSwaggerProvider.ts +5 -8
  290. package/src/sms/providers/LocalSmsProvider.spec.ts +153 -111
  291. package/src/sms/providers/LocalSmsProvider.ts +8 -7
  292. package/src/vite/helpers/boot.ts +28 -17
  293. package/src/vite/helpers/importViteReact.ts +13 -0
  294. package/src/vite/index.ts +1 -21
  295. package/src/vite/plugins/viteAlephaDev.ts +16 -1
  296. package/src/vite/plugins/viteAlephaSsrPreload.ts +222 -0
  297. package/src/vite/tasks/buildClient.ts +11 -0
  298. package/src/vite/tasks/buildServer.ts +59 -4
  299. package/src/vite/tasks/devServer.ts +71 -0
  300. package/src/vite/tasks/generateCloudflare.ts +7 -0
  301. package/src/vite/tasks/index.ts +2 -1
  302. package/dist/server/security/index.browser.js +0 -13
  303. package/dist/server/security/index.browser.js.map +0 -1
  304. package/dist/server/security/index.d.ts +0 -173
  305. package/dist/server/security/index.d.ts.map +0 -1
  306. package/dist/server/security/index.js +0 -311
  307. package/dist/server/security/index.js.map +0 -1
  308. package/src/cli/assets/appRouterTs.ts +0 -9
  309. package/src/cli/assets/mainTs.ts +0 -13
  310. package/src/cli/assets/viteConfigTs.ts +0 -14
  311. package/src/cli/commands/run.ts +0 -24
  312. package/src/server/security/index.browser.ts +0 -10
  313. package/src/server/security/index.ts +0 -94
  314. package/src/vite/plugins/viteAlepha.ts +0 -37
  315. package/src/vite/plugins/viteAlephaBuild.ts +0 -281
  316. /package/src/{server/security → security}/primitives/$basicAuth.ts +0 -0
  317. /package/src/{server/security → security}/providers/ServerBasicAuthProvider.ts +0 -0
package/dist/cli/index.js CHANGED
@@ -1,28 +1,456 @@
1
- import { createRequire } from "node:module";
2
- import { $atom, $hook, $inject, $module, $use, Alepha, AlephaError, OPTIONS, t } from "alepha";
1
+ import { $atom, $hook, $inject, $module, $use, Alepha, AlephaError, t } from "alepha";
3
2
  import { FileSystemProvider } from "alepha/file";
4
- import { access, mkdir, readFile, readdir, unlink, writeFile } from "node:fs/promises";
5
- import { join } from "node:path";
6
3
  import { $command, CliProvider, EnvUtils } from "alepha/command";
7
4
  import { $logger, ConsoleColorProvider } from "alepha/logger";
8
- import { boot, buildClient, buildServer, copyAssets, generateCloudflare, generateDocker, generateSitemap, generateVercel, prerenderPages } from "alepha/vite";
5
+ import { boot, buildClient, buildServer, copyAssets, devServer, generateCloudflare, generateDocker, generateSitemap, generateVercel, prerenderPages } from "alepha/vite";
9
6
  import { exec, spawn } from "node:child_process";
10
7
  import { readFileSync } from "node:fs";
8
+ import { basename, dirname, join } from "node:path";
11
9
  import { promisify } from "node:util";
12
10
  import { ServerSwaggerProvider } from "alepha/server/swagger";
11
+ import { access, readFile, readdir } from "node:fs/promises";
13
12
  import * as os from "node:os";
14
13
 
15
- //#region ../../src/cli/assets/appRouterTs.ts
16
- const appRouterTs = () => `
17
- import { $page } from "@alepha/react/router";
14
+ //#region ../../src/core/constants/KIND.ts
15
+ /**
16
+ * Used for identifying primitives.
17
+ *
18
+ * @internal
19
+ */
20
+ const KIND = Symbol.for("Alepha.Kind");
18
21
 
19
- export class AppRouter {
20
- home = $page({
21
- component: () => "Hello World",
22
+ //#endregion
23
+ //#region ../../src/core/primitives/$atom.ts
24
+ /**
25
+ * Define an atom for state management.
26
+ *
27
+ * Atom lets you define a piece of state with a name, schema, and default value.
28
+ *
29
+ * By default, Alepha state is just a simple key-value store.
30
+ * Using atoms allows you to have type safety, validation, and default values for your state.
31
+ *
32
+ * You control how state is structured and validated.
33
+ *
34
+ * Features:
35
+ * - Set a schema for validation
36
+ * - Set a default value for initial state
37
+ * - Rules, like read-only, custom validation, etc.
38
+ * - Automatic getter access in services with {@link $use}
39
+ * - SSR support (server state automatically serialized and hydrated on client)
40
+ * - React integration (useAtom hook for automatic component re-renders)
41
+ * - Middleware
42
+ * - Persistence adapters (localStorage, Redis, database, file system, cookie, etc.)
43
+ * - State migrations (version upgrades when schema changes)
44
+ * - Documentation generation & devtools integration
45
+ *
46
+ * Common use cases:
47
+ * - user preferences
48
+ * - feature flags
49
+ * - configuration options
50
+ * - session data
51
+ *
52
+ * Atom must contain only serializable data.
53
+ * Avoid storing complex objects like class instances, functions, or DOM elements.
54
+ * If you need to store complex data, consider using identifiers or references instead.
55
+ */
56
+ const $atom$1 = (options) => {
57
+ return new Atom(options);
58
+ };
59
+ var Atom = class {
60
+ options;
61
+ get schema() {
62
+ return this.options.schema;
63
+ }
64
+ get key() {
65
+ return this.options.name;
66
+ }
67
+ constructor(options) {
68
+ this.options = options;
69
+ }
70
+ };
71
+ $atom$1[KIND] = "atom";
72
+
73
+ //#endregion
74
+ //#region ../../src/cli/atoms/buildOptions.ts
75
+ /**
76
+ * Build options atom for CLI build command.
77
+ *
78
+ * Defines the available build configuration options with their defaults.
79
+ * Options can be overridden via vite.config.ts or CLI flags.
80
+ */
81
+ const buildOptions = $atom$1({
82
+ name: "alepha.build.options",
83
+ description: "Build configuration options",
84
+ schema: t.object({
85
+ stats: t.optional(t.boolean({ default: false })),
86
+ vercel: t.optional(t.object({
87
+ projectName: t.optional(t.string()),
88
+ orgId: t.optional(t.string()),
89
+ projectId: t.optional(t.string()),
90
+ config: t.optional(t.object({ crons: t.optional(t.array(t.object({
91
+ path: t.string(),
92
+ schedule: t.string()
93
+ }))) }))
94
+ })),
95
+ cloudflare: t.optional(t.object({ config: t.optional(t.json()) })),
96
+ docker: t.optional(t.object({
97
+ image: t.optional(t.string({ default: "node:24-alpine" })),
98
+ command: t.optional(t.string({ default: "node" }))
99
+ })),
100
+ sitemap: t.optional(t.object({ hostname: t.string() }))
101
+ }),
102
+ default: {}
103
+ });
104
+
105
+ //#endregion
106
+ //#region ../../src/cli/services/AlephaCliUtils.ts
107
+ /**
108
+ * Core utility service for CLI commands.
109
+ *
110
+ * Provides:
111
+ * - Command execution
112
+ * - File editing helpers
113
+ * - Drizzle/ORM utilities
114
+ * - Environment loading
115
+ */
116
+ var AlephaCliUtils = class {
117
+ log = $logger();
118
+ fs = $inject(FileSystemProvider);
119
+ envUtils = $inject(EnvUtils);
120
+ /**
121
+ * Execute a command with inherited stdio.
122
+ */
123
+ async exec(command, options = {}) {
124
+ const root = options.root ?? process.cwd();
125
+ this.log.debug(`Executing command: ${command}`, { cwd: root });
126
+ const runExec = async (app$1, args$1) => {
127
+ const prog = spawn(app$1, args$1, {
128
+ stdio: "inherit",
129
+ cwd: root,
130
+ env: {
131
+ ...process.env,
132
+ ...options.env
133
+ }
134
+ });
135
+ await new Promise((resolve) => prog.on("exit", () => {
136
+ resolve();
137
+ }));
138
+ };
139
+ if (options.global) {
140
+ const [app$1, ...args$1] = command.split(" ");
141
+ await runExec(app$1, args$1);
142
+ return;
143
+ }
144
+ const suffix = process.platform === "win32" ? ".cmd" : "";
145
+ const [app, ...args] = command.split(" ");
146
+ let execPath = await this.checkFileExists(root, `node_modules/.bin/${app}${suffix}`);
147
+ if (!execPath) execPath = await this.checkFileExists(root, `node_modules/alepha/node_modules/.bin/${app}${suffix}`);
148
+ if (!execPath) throw new AlephaError(`Could not find executable for command '${app}'. Make sure the package is installed.`);
149
+ await runExec(execPath, args);
150
+ }
151
+ /**
152
+ * Write a configuration file to node_modules/.alepha directory.
153
+ */
154
+ async writeConfigFile(name, content, root = process.cwd()) {
155
+ const dir = this.fs.join(root, "node_modules", ".alepha");
156
+ await this.fs.mkdir(dir, { recursive: true }).catch(() => null);
157
+ const path = this.fs.join(dir, name);
158
+ await this.fs.writeFile(path, content);
159
+ this.log.debug(`Config file written: ${path}`);
160
+ return path;
161
+ }
162
+ /**
163
+ * Load Alepha instance from a server entry file.
164
+ */
165
+ async loadAlephaFromServerEntryFile(rootDir, explicitEntry) {
166
+ process.env.ALEPHA_CLI_IMPORT = "true";
167
+ const entry = await boot.getServerEntry(rootDir, explicitEntry);
168
+ delete global.__alepha;
169
+ const mod = await import(entry);
170
+ this.log.debug(`Load entry: ${entry}`);
171
+ if (mod.default instanceof Alepha) return {
172
+ alepha: mod.default,
173
+ entry
174
+ };
175
+ const g = global;
176
+ if (g.__alepha) return {
177
+ alepha: g.__alepha,
178
+ entry
179
+ };
180
+ throw new AlephaError(`Could not find Alepha instance in entry file: ${entry}`);
181
+ }
182
+ /**
183
+ * Generate JavaScript code for Drizzle entities export.
184
+ */
185
+ generateEntitiesJs(entry, provider, models = []) {
186
+ return `
187
+ import "${entry}";
188
+ import { DrizzleKitProvider, Repository } from "alepha/orm";
189
+
190
+ const alepha = globalThis.__alepha;
191
+ const kit = alepha.inject(DrizzleKitProvider);
192
+ const provider = alepha.services(Repository).find((it) => it.provider.name === "${provider}").provider;
193
+ const models = kit.getModels(provider);
194
+
195
+ ${models.map((it) => `export const ${it} = models["${it}"];`).join("\n")}
196
+
197
+ `.trim();
198
+ }
199
+ /**
200
+ * Load environment variables from a .env file.
201
+ */
202
+ async loadEnv(root, files = [".env"]) {
203
+ await this.envUtils.loadEnv(root, files);
204
+ }
205
+ async exists(root, path) {
206
+ return this.fs.exists(this.fs.join(root, path));
207
+ }
208
+ async checkFileExists(root, name) {
209
+ const configPath = this.fs.join(root, name);
210
+ if (await this.fs.exists(configPath)) return configPath;
211
+ }
212
+ };
213
+
214
+ //#endregion
215
+ //#region ../../src/cli/version.ts
216
+ const packageJson = JSON.parse(readFileSync(new URL("../../package.json", import.meta.url), "utf-8"));
217
+ const version = packageJson.version;
218
+
219
+ //#endregion
220
+ //#region ../../src/cli/services/PackageManagerUtils.ts
221
+ /**
222
+ * Utility service for package manager operations.
223
+ *
224
+ * Handles detection, installation, and cleanup for:
225
+ * - Yarn
226
+ * - npm
227
+ * - pnpm
228
+ * - Bun
229
+ */
230
+ var PackageManagerUtils = class {
231
+ log = $logger();
232
+ fs = $inject(FileSystemProvider);
233
+ alepha = $inject(Alepha);
234
+ /**
235
+ * Detect the package manager used in the project.
236
+ */
237
+ async getPackageManager(root, flags) {
238
+ if (flags?.yarn) return "yarn";
239
+ if (flags?.pnpm) return "pnpm";
240
+ if (flags?.npm) return "npm";
241
+ if (flags?.bun) return "bun";
242
+ if (this.alepha.isBun()) return "bun";
243
+ if (await this.fs.exists(this.fs.join(root, "bun.lock"))) return "bun";
244
+ if (await this.fs.exists(this.fs.join(root, "yarn.lock"))) return "yarn";
245
+ if (await this.fs.exists(this.fs.join(root, "pnpm-lock.yaml"))) return "pnpm";
246
+ return "npm";
247
+ }
248
+ /**
249
+ * Get the install command for a package.
250
+ */
251
+ async getInstallCommand(root, packageName, dev = true) {
252
+ const pm = await this.getPackageManager(root);
253
+ let cmd;
254
+ switch (pm) {
255
+ case "yarn":
256
+ cmd = `yarn add ${dev ? "-D" : ""} ${packageName}`;
257
+ break;
258
+ case "pnpm":
259
+ cmd = `pnpm add ${dev ? "-D" : ""} ${packageName}`;
260
+ break;
261
+ case "bun":
262
+ cmd = `bun add ${dev ? "-d" : ""} ${packageName}`;
263
+ break;
264
+ default: cmd = `npm install ${dev ? "--save-dev" : ""} ${packageName}`;
265
+ }
266
+ return cmd.replace(/\s+/g, " ").trim();
267
+ }
268
+ /**
269
+ * Check if a dependency is installed in the project.
270
+ */
271
+ async hasDependency(root, packageName) {
272
+ try {
273
+ const pkg = await this.readPackageJson(root);
274
+ return !!(pkg.dependencies?.[packageName] || pkg.devDependencies?.[packageName]);
275
+ } catch {
276
+ return false;
277
+ }
278
+ }
279
+ /**
280
+ * Check if Expo is present in the project.
281
+ */
282
+ async hasExpo(root) {
283
+ return this.hasDependency(root, "expo");
284
+ }
285
+ /**
286
+ * Install a dependency if it's missing from the project.
287
+ */
288
+ async ensureDependency(root, packageName, options = {}) {
289
+ const { dev = true } = options;
290
+ if (await this.hasDependency(root, packageName)) {
291
+ this.log.debug(`Dependency '${packageName}' is already installed`);
292
+ return;
293
+ }
294
+ const cmd = await this.getInstallCommand(root, packageName, dev);
295
+ if (options.run) await options.run(cmd, {
296
+ alias: `installing ${packageName}`,
297
+ root
298
+ });
299
+ else if (options.exec) {
300
+ this.log.debug(`Installing ${packageName}`);
301
+ await options.exec(cmd, {
302
+ global: true,
303
+ root
304
+ });
305
+ }
306
+ }
307
+ async ensureYarn(root) {
308
+ const yarnrcPath = this.fs.join(root, ".yarnrc.yml");
309
+ if (!await this.fs.exists(yarnrcPath)) await this.fs.writeFile(yarnrcPath, "nodeLinker: node-modules");
310
+ await this.removeAllPmFilesExcept(root, "yarn");
311
+ }
312
+ async ensureBun(root) {
313
+ await this.removeAllPmFilesExcept(root, "bun");
314
+ }
315
+ async ensurePnpm(root) {
316
+ await this.removeAllPmFilesExcept(root, "pnpm");
317
+ }
318
+ async ensureNpm(root) {
319
+ await this.removeAllPmFilesExcept(root, "npm");
320
+ }
321
+ async removeAllPmFilesExcept(root, except) {
322
+ if (except !== "yarn") await this.removeYarn(root);
323
+ if (except !== "pnpm") await this.removePnpm(root);
324
+ if (except !== "npm") await this.removeNpm(root);
325
+ if (except !== "bun") await this.removeBun(root);
326
+ }
327
+ async removeYarn(root) {
328
+ await this.removeFiles(root, [
329
+ ".yarn",
330
+ ".yarnrc.yml",
331
+ "yarn.lock"
332
+ ]);
333
+ await this.editPackageJson(root, (pkg) => {
334
+ delete pkg.packageManager;
335
+ return pkg;
336
+ });
337
+ }
338
+ async removePnpm(root) {
339
+ await this.removeFiles(root, ["pnpm-lock.yaml", "pnpm-workspace.yaml"]);
340
+ await this.editPackageJson(root, (pkg) => {
341
+ delete pkg.packageManager;
342
+ return pkg;
343
+ });
344
+ }
345
+ async removeNpm(root) {
346
+ await this.removeFiles(root, ["package-lock.json"]);
347
+ }
348
+ async removeBun(root) {
349
+ await this.removeFiles(root, ["bun.lockb", "bun.lock"]);
350
+ }
351
+ async readPackageJson(root) {
352
+ const content = await this.fs.createFile({ path: this.fs.join(root, "package.json") }).text();
353
+ return JSON.parse(content);
354
+ }
355
+ async writePackageJson(root, content) {
356
+ await this.fs.writeFile(this.fs.join(root, "package.json"), JSON.stringify(content, null, 2));
357
+ }
358
+ async editPackageJson(root, editFn) {
359
+ try {
360
+ const updated = editFn(await this.readPackageJson(root));
361
+ await this.writePackageJson(root, updated);
362
+ } catch {}
363
+ }
364
+ async ensurePackageJson(root, modes) {
365
+ const packageJsonPath = this.fs.join(root, "package.json");
366
+ if (!await this.fs.exists(packageJsonPath)) {
367
+ const content = this.generatePackageJsonContent(modes);
368
+ await this.writePackageJson(root, content);
369
+ return content;
370
+ }
371
+ const packageJson$1 = await this.readPackageJson(root);
372
+ const newContent = this.generatePackageJsonContent(modes);
373
+ packageJson$1.type = "module";
374
+ packageJson$1.dependencies ??= {};
375
+ packageJson$1.devDependencies ??= {};
376
+ packageJson$1.scripts ??= {};
377
+ Object.assign(packageJson$1.dependencies, newContent.dependencies);
378
+ Object.assign(packageJson$1.devDependencies, newContent.devDependencies);
379
+ Object.assign(packageJson$1.scripts, newContent.scripts);
380
+ await this.writePackageJson(root, packageJson$1);
381
+ return packageJson$1;
382
+ }
383
+ generatePackageJsonContent(modes) {
384
+ const dependencies = { alepha: `^${version}` };
385
+ const devDependencies = {};
386
+ const scripts = {
387
+ dev: "alepha dev",
388
+ build: "alepha build",
389
+ lint: "alepha lint",
390
+ typecheck: "alepha typecheck",
391
+ verify: "alepha verify"
392
+ };
393
+ if (modes.admin) {
394
+ dependencies["@alepha/ui"] = `^${version}`;
395
+ modes.web = true;
396
+ }
397
+ if (modes.web) {
398
+ dependencies["@alepha/react"] = `^${version}`;
399
+ dependencies.react = "^19.2.0";
400
+ dependencies["react-dom"] = "^19.2.0";
401
+ devDependencies["@types/react"] = "^19.2.0";
402
+ }
403
+ return {
404
+ type: "module",
405
+ dependencies,
406
+ devDependencies,
407
+ scripts
408
+ };
409
+ }
410
+ async removeFiles(root, files) {
411
+ await Promise.all(files.map((file) => this.fs.rm(this.fs.join(root, file), {
412
+ force: true,
413
+ recursive: true
414
+ })));
415
+ }
416
+ };
417
+
418
+ //#endregion
419
+ //#region ../../src/cli/assets/apiHelloControllerTs.ts
420
+ const apiHelloControllerTs = () => `
421
+ import { t } from "alepha";
422
+ import { $action } from "alepha/server";
423
+
424
+ export class HelloController {
425
+ hello = $action({
426
+ path: "/hello",
427
+ schema: {
428
+ response: t.object({
429
+ message: t.string(),
430
+ }),
431
+ },
432
+ handler: () => ({
433
+ message: "Hello, Alepha!",
434
+ }),
22
435
  });
23
436
  }
24
437
  `.trim();
25
438
 
439
+ //#endregion
440
+ //#region ../../src/cli/assets/apiIndexTs.ts
441
+ const apiIndexTs = (options = {}) => {
442
+ const { appName = "app" } = options;
443
+ return `
444
+ import { $module } from "alepha";
445
+ import { HelloController } from "./controllers/HelloController.ts";
446
+
447
+ export const ApiModule = $module({
448
+ name: "${appName}.api",
449
+ services: [HelloController],
450
+ });
451
+ `.trim();
452
+ };
453
+
26
454
  //#endregion
27
455
  //#region ../../src/cli/assets/biomeJson.ts
28
456
  const biomeJson = `
@@ -59,6 +487,299 @@ const biomeJson = `
59
487
  }
60
488
  `.trim();
61
489
 
490
+ //#endregion
491
+ //#region ../../src/cli/assets/claudeMd.ts
492
+ const claudeMd = (options = {}) => {
493
+ const { react = false, projectName = "my-app" } = options;
494
+ const reactSection = react ? `
495
+ ## React & Frontend
496
+
497
+ ### Pages with \`$page\`
498
+ \`\`\`tsx
499
+ import { $page } from "@alepha/react/router";
500
+ import { $client } from "alepha/server/links";
501
+ import type { UserController } from "./UserController.ts";
502
+
503
+ class AppRouter {
504
+ api = $client<UserController>();
505
+
506
+ users = $page({
507
+ path: "/users",
508
+ loader: async () => ({ users: await this.api.listUsers() }),
509
+ component: ({ users }) => (
510
+ <ul>{users.map(u => <li key={u.id}>{u.email}</li>)}</ul>
511
+ ),
512
+ });
513
+
514
+ userDetail = $page({
515
+ path: "/users/:id",
516
+ schema: { params: t.object({ id: t.uuid() }) },
517
+ loader: async ({ params }) => ({ user: await this.api.getUser({ params }) }),
518
+ lazy: () => import("./UserDetail.tsx"), // Code splitting
519
+ });
520
+ }
521
+ \`\`\`
522
+
523
+ ### React Hooks
524
+ \`\`\`typescript
525
+ import { useAlepha, useClient, useStore, useAction, useInject } from "@alepha/react";
526
+ import { useRouter, useActive } from "@alepha/react/router";
527
+ import { useForm } from "@alepha/react/form";
528
+ \`\`\`
529
+
530
+ - \`useClient<Controller>()\` - Type-safe API calls
531
+ - \`useStore(atom)\` - Global state (returns \`[value, setValue]\`)
532
+ - \`useAction({ handler })\` - Async operations with loading/error state
533
+ - \`useRouter<AppRouter>()\` - Type-safe navigation
534
+ - \`useForm({ schema, handler })\` - Type-safe forms with validation
535
+ ` : "";
536
+ const projectStructure = react ? `
537
+ \`\`\`
538
+ ${projectName}/
539
+ ├── src/
540
+ │ ├── api/ # Backend
541
+ │ │ ├── controllers/ # API controllers with $action
542
+ │ │ ├── services/ # Business logic
543
+ │ │ ├── entities/ # Database entities with $entity
544
+ │ │ ├── providers/ # External service wrappers
545
+ │ │ └── index.ts # API module definition with $module
546
+ │ ├── web/ # Frontend (React only)
547
+ │ │ ├── components/ # React components
548
+ │ │ ├── atoms/ # State atoms with $atom
549
+ │ │ ├── AppRouter.ts # Routes with $page
550
+ │ │ └── index.ts # Web module definition with $module
551
+ │ ├── main.server.ts # Server entry
552
+ │ └── main.browser.ts # Browser entry (React only)
553
+ ├── index.html # (React only)
554
+ ├── package.json
555
+ └── tsconfig.json
556
+ \`\`\`
557
+ ` : `
558
+ \`\`\`
559
+ ${projectName}/
560
+ ├── src/
561
+ │ ├── api/ # Backend
562
+ │ │ ├── controllers/ # API controllers with $action
563
+ │ │ ├── services/ # Business logic
564
+ │ │ ├── entities/ # Database entities with $entity
565
+ │ │ ├── providers/ # External service wrappers
566
+ │ │ └── index.ts # API module definition with $module
567
+ │ └── main.server.ts # Server entry (always use main.server.ts)
568
+ ├── package.json
569
+ └── tsconfig.json
570
+ \`\`\`
571
+ `;
572
+ return `# CLAUDE.md
573
+
574
+ This file provides guidance to Claude Code when working with this Alepha project.
575
+
576
+ ## Overview
577
+
578
+ This is an **Alepha** project - a convention-driven TypeScript framework for type-safe full-stack applications.
579
+
580
+ **Key Concepts:**
581
+ - **Primitives**: Features defined with \`$\`-prefixed functions (\`$action\`, \`$entity\`, \`$page\`)
582
+ - **Class-Based**: Services are classes, primitives are class properties
583
+ - **Zero-Config**: Code structure IS the configuration
584
+ - **End-to-End Types**: Types flow from database → API → ${react ? "React" : "client"}
585
+
586
+ ## Rules
587
+
588
+ - Use TypeScript strict mode
589
+ - Use Biome for formatting (\`alepha lint\`)
590
+ - Use Vitest for testing
591
+ - One file = one class
592
+ - Primitives are class properties (except \`$entity\`, \`$atom\`)
593
+ - No decorators, no Express/Fastify patterns
594
+ - No manual instantiation - use dependency injection
595
+ - Use \`protected\` instead of \`private\` for class members
596
+ - Import with file extensions: \`import { User } from "./User.ts"\`
597
+ - Use \`t\` from Alepha for schemas (not Zod)
598
+
599
+ ## Project Structure
600
+ ${projectStructure}
601
+ ## Core Primitives
602
+
603
+ ### API with \`$action\`
604
+ \`\`\`typescript
605
+ import { t } from "alepha";
606
+ import { $action } from "alepha/server";
607
+
608
+ class UserController {
609
+ getUser = $action({
610
+ path: "/users/:id", // → GET /api/users/:id
611
+ schema: {
612
+ params: t.object({ id: t.uuid() }),
613
+ response: t.object({ id: t.uuid(), email: t.email() }),
614
+ },
615
+ handler: async ({ params }) => this.userRepo.findById(params.id),
616
+ });
617
+
618
+ createUser = $action({
619
+ // POST inferred from body schema
620
+ schema: {
621
+ body: t.object({ email: t.email() }),
622
+ response: userEntity.schema,
623
+ },
624
+ handler: async ({ body }) => this.userRepo.create(body),
625
+ });
626
+ }
627
+ \`\`\`
628
+
629
+ ### Database with \`$entity\` and \`$repository\`
630
+ \`\`\`typescript
631
+ import { $entity, $repository, db } from "alepha/orm";
632
+
633
+ // Entity defined at module level (for drizzle-kit compatibility)
634
+ export const userEntity = $entity({
635
+ name: "users",
636
+ schema: t.object({
637
+ id: db.primaryKey(),
638
+ email: t.email(),
639
+ createdAt: db.createdAt(),
640
+ updatedAt: db.updatedAt(),
641
+ }),
642
+ indexes: [{ column: "email", unique: true }],
643
+ });
644
+
645
+ class UserService {
646
+ repo = $repository(userEntity);
647
+
648
+ async findById(id: string) {
649
+ return this.repo.findById(id);
650
+ }
651
+ }
652
+ \`\`\`
653
+
654
+ ### Dependency Injection
655
+ \`\`\`typescript
656
+ import { $inject } from "alepha";
657
+
658
+ class OrderService {
659
+ userService = $inject(UserService); // Within same module
660
+
661
+ async createOrder(userId: string) {
662
+ const user = await this.userService.findById(userId);
663
+ // ...
664
+ }
665
+ }
666
+
667
+ // Cross-module: use $client instead of $inject
668
+ class AppRouter {
669
+ api = $client<OrderController>(); // Type-safe HTTP client
670
+ }
671
+ \`\`\`
672
+
673
+ ### Modules with \`$module\`
674
+ \`\`\`typescript
675
+ // src/api/index.ts - Groups all API services
676
+ import { $module } from "alepha";
677
+
678
+ export const ApiModule = $module({
679
+ name: "app.api",
680
+ services: [
681
+ UserController,
682
+ OrderController,
683
+ UserService,
684
+ ],
685
+ });
686
+
687
+ // src/web/index.ts - Groups all web services (React only)
688
+ export const WebModule = $module({
689
+ name: "app.web",
690
+ services: [AppRouter, Toaster],
691
+ register(alepha) {
692
+ // Optional: configure additional services
693
+ alepha.with(SomeLibrary);
694
+ },
695
+ });
696
+ \`\`\`
697
+
698
+ ### Environment Variables
699
+ \`\`\`typescript
700
+ import { $env, t } from "alepha";
701
+
702
+ class AppConfig {
703
+ env = $env(t.object({
704
+ DATABASE_URL: t.string(),
705
+ API_KEY: t.optional(t.string()),
706
+ }));
707
+ }
708
+ \`\`\`
709
+ ${reactSection}
710
+ ## Quick Reference
711
+
712
+ | Primitive | Import | Purpose |
713
+ |-----------|--------|---------|
714
+ | \`$inject\` | \`alepha\` | Dependency injection |
715
+ | \`$env\` | \`alepha\` | Environment variables |
716
+ | \`$hook\` | \`alepha\` | Lifecycle hooks |
717
+ | \`$logger\` | \`alepha/logger\` | Structured logging |
718
+ | \`$action\` | \`alepha/server\` | REST API endpoints |
719
+ | \`$route\` | \`alepha/server\` | Low-level HTTP routes |
720
+ | \`$entity\` | \`alepha/orm\` | Database tables |
721
+ | \`$repository\` | \`alepha/orm\` | Type-safe data access |
722
+ | \`$queue\` | \`alepha/queue\` | Background jobs |
723
+ | \`$scheduler\` | \`alepha/scheduler\` | Cron tasks |
724
+ | \`$cache\` | \`alepha/cache\` | Cached computations |
725
+ | \`$bucket\` | \`alepha/bucket\` | File storage |
726
+ | \`$issuer\` | \`alepha/security\` | JWT tokens |
727
+ | \`$command\` | \`alepha/command\` | CLI commands |${react ? `
728
+ | \`$page\` | \`@alepha/react/router\` | React pages with SSR |
729
+ | \`$atom\` | \`alepha\` | Global state |` : ""}
730
+
731
+ ## Testing
732
+
733
+ \`\`\`typescript
734
+ import { describe, it, expect } from "vitest";
735
+ import { Alepha } from "alepha";
736
+
737
+ describe("UserService", () => {
738
+ it("should create user", async () => {
739
+ const alepha = Alepha.create().with(UserService);
740
+ const service = alepha.inject(UserService);
741
+
742
+ const user = await service.create({ email: "test@example.com" });
743
+ expect(user.email).toBe("test@example.com");
744
+ });
745
+
746
+ it("should mock dependencies", async () => {
747
+ const alepha = Alepha.create()
748
+ .with(OrderService)
749
+ .with({ provide: PaymentGateway, use: MockPaymentGateway });
750
+
751
+ const service = alepha.inject(OrderService);
752
+ // PaymentGateway is now mocked
753
+ });
754
+ });
755
+ \`\`\`
756
+
757
+ ## Common Mistakes
758
+
759
+ 1. **DON'T use decorators** - Use primitives (\`$action\`, not \`@Get()\`)
760
+ 2. **DON'T use Zod** - Use TypeBox via \`t\` from Alepha
761
+ 3. **DON'T use Express patterns** - No \`app.get()\`, \`router.use()\`
762
+ 4. **DON'T inject across modules** - Use \`$client\` for cross-module calls
763
+ 5. **DON'T use async constructors** - Use \`$hook({ on: "start" })\`
764
+ 6. **DON'T instantiate manually** - Let DI container manage instances
765
+
766
+ ## After Code Changes
767
+
768
+ Always run:
769
+ \`\`\`bash
770
+ alepha lint # Format and lint
771
+ alepha typecheck # Type checking
772
+ alepha test # Run tests (if applicable)
773
+ alepha build # Build the project
774
+ \`\`\`
775
+
776
+ ## Documentation
777
+
778
+ - Full docs: https://alepha.dev/llms.txt
779
+ - Detailed docs: https://alepha.dev/llms-full.txt
780
+ `.trim();
781
+ };
782
+
62
783
  //#endregion
63
784
  //#region ../../src/cli/assets/dummySpecTs.ts
64
785
  const dummySpecTs = () => `
@@ -105,30 +826,30 @@ const indexHtml = (browserEntry) => `
105
826
  //#region ../../src/cli/assets/mainBrowserTs.ts
106
827
  const mainBrowserTs = () => `
107
828
  import { Alepha, run } from "alepha";
108
- import { AppRouter } from "./AppRouter.ts";
829
+ import { WebModule } from "./web/index.ts";
109
830
 
110
831
  const alepha = Alepha.create();
111
832
 
112
- alepha.with(AppRouter);
833
+ alepha.with(WebModule);
113
834
 
114
835
  run(alepha);
115
836
  `.trim();
116
837
 
117
838
  //#endregion
118
- //#region ../../src/cli/assets/mainTs.ts
119
- const mainTs = () => `
120
- import { run } from "alepha";
121
- import { $route } from "alepha/server";
122
-
123
- class App {
124
- root = $route({
125
- path: "/",
126
- handler: () => "Hello, Alepha!",
127
- });
128
- }
839
+ //#region ../../src/cli/assets/mainServerTs.ts
840
+ const mainServerTs = (options = {}) => {
841
+ const { react = false } = options;
842
+ return `
843
+ import { Alepha, run } from "alepha";
844
+ import { ApiModule } from "./api/index.ts";
845
+ ${react ? `import { WebModule } from "./web/index.ts";\n` : ""}
846
+ const alepha = Alepha.create();
129
847
 
130
- run(App);
848
+ alepha.with(ApiModule);
849
+ ${react ? `alepha.with(WebModule);\n` : ""}
850
+ run(alepha);
131
851
  `.trim();
852
+ };
132
853
 
133
854
  //#endregion
134
855
  //#region ../../src/cli/assets/tsconfigJson.ts
@@ -139,407 +860,158 @@ const tsconfigJson = `
139
860
  `.trim();
140
861
 
141
862
  //#endregion
142
- //#region ../../src/cli/assets/viteConfigTs.ts
143
- const viteConfigTs = (serverEntry) => `
144
- import { viteAlepha } from "alepha/vite";
863
+ //#region ../../src/cli/assets/webAppRouterTs.ts
864
+ const webAppRouterTs = () => `
865
+ import { $page } from "@alepha/react/router";
866
+ import { $client } from "alepha/server/links";
867
+ import type { HelloController } from "../api/controllers/HelloController.ts";
145
868
 
146
- export default {
147
- plugins: [
148
- viteAlepha(${serverEntry ? `{ serverEntry: "${serverEntry}" }` : ""}),
149
- ],
150
- test: {
151
- globals: true,
152
- },
869
+ export class AppRouter {
870
+ api = $client<HelloController>();
871
+
872
+ home = $page({
873
+ path: "/",
874
+ lazy: () => import("./components/Hello.tsx"),
875
+ loader: () => this.api.hello(),
876
+ });
877
+ }
878
+ `.trim();
879
+
880
+ //#endregion
881
+ //#region ../../src/cli/assets/webHelloComponentTsx.ts
882
+ const webHelloComponentTsx = () => `
883
+ interface Props {
884
+ message: string;
885
+ }
886
+
887
+ const Hello = (props: Props) => {
888
+ return (
889
+ <div>
890
+ <h1>{props.message}</h1>
891
+ <p>Edit this component in src/web/components/Hello.tsx</p>
892
+ </div>
893
+ );
153
894
  };
895
+
896
+ export default Hello;
154
897
  `.trim();
155
898
 
156
899
  //#endregion
157
- //#region ../../src/cli/version.ts
158
- const packageJson = JSON.parse(readFileSync(new URL("../../package.json", import.meta.url), "utf-8"));
159
- const version = packageJson.version;
900
+ //#region ../../src/cli/assets/webIndexTs.ts
901
+ const webIndexTs = (options = {}) => {
902
+ const { appName = "app" } = options;
903
+ return `
904
+ import { $module } from "alepha";
905
+ import { AppRouter } from "./AppRouter.ts";
906
+
907
+ export const WebModule = $module({
908
+ name: "${appName}.web",
909
+ services: [AppRouter],
910
+ });
911
+ `.trim();
912
+ };
160
913
 
161
914
  //#endregion
162
- //#region ../../src/cli/services/AlephaCliUtils.ts
163
- /**
164
- * Utility service for common project operations used by CLI commands.
165
- *
166
- * This service provides helper methods for:
167
- * - Project configuration file management (tsconfig.json, package.json, etc.)
168
- * - Package manager setup (Yarn, npm, pnpm)
169
- * - Sample project downloading
170
- * - Drizzle ORM/Kit utilities
171
- * - Alepha instance loading
172
- */
173
- var AlephaCliUtils = class {
174
- log = $logger();
175
- fs = $inject(FileSystemProvider);
176
- envUtils = $inject(EnvUtils);
177
- alepha = $inject(Alepha);
178
- /**
179
- * Execute a command using npx with inherited stdio.
180
- *
181
- * @example
182
- * ```ts
183
- * const runner = alepha.inject(ProcessRunner);
184
- * await runner.exec("tsx watch src/index.ts");
185
- * ```
186
- */
187
- async exec(command, options = {}) {
188
- const root = process.cwd();
189
- this.log.debug(`Executing command: ${command}`, { cwd: root });
190
- const runExec = async (app$1, args$1) => {
191
- const prog = spawn(app$1, args$1, {
192
- stdio: "inherit",
193
- cwd: root,
194
- env: {
195
- ...process.env,
196
- ...options.env
197
- }
198
- });
199
- await new Promise((resolve) => prog.on("exit", () => {
200
- resolve();
201
- }));
202
- };
203
- if (options.global) {
204
- const [app$1, ...args$1] = command.split(" ");
205
- await runExec(app$1, args$1);
206
- return;
207
- }
208
- const suffix = process.platform === "win32" ? ".cmd" : "";
209
- const [app, ...args] = command.split(" ");
210
- let execPath = await this.checkFileExists(root, `node_modules/.bin/${app}${suffix}`, true);
211
- if (!execPath) execPath = await this.checkFileExists(root, `node_modules/alepha/node_modules/.bin/${app}${suffix}`, true);
212
- if (!execPath) throw new AlephaError(`Could not find executable for command '${app}'. Make sure the package is installed.`);
213
- await runExec(execPath, args);
214
- }
215
- /**
216
- * Write a configuration file to node_modules/.alepha directory.
217
- *
218
- * Creates the .alepha directory if it doesn't exist and writes the file with the given content.
219
- *
220
- * @param name - The name of the config file to create
221
- * @param content - The content to write to the file
222
- * @param root - The root directory (defaults to process.cwd())
223
- * @returns The absolute path to the created file
224
- *
225
- * @example
226
- * ```ts
227
- * const runner = alepha.inject(ProcessRunner);
228
- * const configPath = await runner.writeConfigFile("biome.json", biomeConfig);
229
- * ```
230
- */
231
- async writeConfigFile(name, content, root = process.cwd()) {
232
- const dir = join(root, "node_modules", ".alepha");
233
- await mkdir(dir, { recursive: true }).catch(() => null);
234
- const path = join(dir, name);
235
- await writeFile(path, content);
236
- this.log.debug(`Config file written: ${path}`);
237
- return path;
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
- }
267
- /**
268
- * Ensure Yarn is configured in the project directory.
269
- *
270
- * Creates a .yarnrc.yml file with node-modules linker if it doesn't exist.
271
- *
272
- * @param root - The root directory of the project
273
- */
274
- async ensureYarn(root) {
275
- await this.ensureFileExists(root, ".yarnrc.yml", "nodeLinker: node-modules", false);
276
- await this.removeAllPmFilesExcept(root, "yarn");
277
- }
278
- async ensureBun(root) {
279
- await this.removeAllPmFilesExcept(root, "bun");
280
- }
281
- async ensurePnpm(root) {
282
- await this.removeAllPmFilesExcept(root, "pnpm");
283
- }
284
- async ensureNpm(root) {
285
- await this.removeAllPmFilesExcept(root, "npm");
286
- }
915
+ //#region ../../src/cli/services/ProjectScaffolder.ts
916
+ /**
917
+ * Service for scaffolding new Alepha projects.
918
+ *
919
+ * Handles creation of:
920
+ * - Project structure (src/api, src/web)
921
+ * - Configuration files (tsconfig, biome, editorconfig)
922
+ * - Entry points (main.server.ts, main.browser.ts)
923
+ * - Example code (HelloController, Hello component)
924
+ */
925
+ var ProjectScaffolder = class {
926
+ log = $logger();
927
+ fs = $inject(FileSystemProvider);
928
+ pm = $inject(PackageManagerUtils);
287
929
  /**
288
- * Generate package.json content with Alepha dependencies.
930
+ * Get the app name from the directory name.
289
931
  *
290
- * @param modes - Configuration for which dependencies to include
291
- * @returns Package.json partial with dependencies, devDependencies, and scripts
932
+ * Converts the directory name to a valid module name:
933
+ * - Converts to lowercase
934
+ * - Replaces spaces, dashes, underscores with nothing
935
+ * - Falls back to "app" if empty
292
936
  */
293
- generatePackageJsonContent(modes) {
294
- const dependencies = { alepha: `^${version}` };
295
- const devDependencies = {};
296
- const scripts = {
297
- dev: "alepha dev",
298
- build: "alepha build",
299
- lint: "alepha lint",
300
- typecheck: "alepha typecheck",
301
- verify: "alepha verify"
302
- };
303
- if (modes.ui) {
304
- dependencies["@alepha/ui"] = `^${version}`;
305
- modes.react = true;
306
- }
307
- if (modes.react) {
308
- dependencies["@alepha/react"] = `^${version}`;
309
- dependencies.react = "^19.2.0";
310
- dependencies["react-dom"] = "^19.2.0";
311
- devDependencies["@types/react"] = "^19.2.0";
312
- }
313
- return {
314
- type: "module",
315
- dependencies,
316
- devDependencies,
317
- scripts
318
- };
937
+ getAppName(root) {
938
+ return basename(root).toLowerCase().replace(/[\s\-_]/g, "") || "app";
319
939
  }
320
940
  /**
321
- * Ensure package.json exists and has correct configuration.
322
- *
323
- * Creates a new package.json if none exists, or updates an existing one to:
324
- * - Set "type": "module"
325
- * - Add Alepha dependencies
326
- * - Add standard scripts
327
- *
328
- * @param root - The root directory of the project
329
- * @param modes - Configuration for which dependencies to include
941
+ * Ensure all configuration files exist.
330
942
  */
331
- async ensurePackageJson(root, modes) {
332
- const packageJsonPath = join(root, "package.json");
333
- try {
334
- await access(packageJsonPath);
335
- } catch (error) {
336
- const obj = this.generatePackageJsonContent(modes);
337
- await writeFile(packageJsonPath, JSON.stringify(obj, null, 2));
338
- return obj;
339
- }
340
- const content = await readFile(packageJsonPath, "utf8");
341
- const packageJson$1 = JSON.parse(content);
342
- const newPackageJson = this.generatePackageJsonContent(modes);
343
- packageJson$1.type = "module";
344
- packageJson$1.dependencies ??= {};
345
- packageJson$1.devDependencies ??= {};
346
- packageJson$1.scripts ??= {};
347
- Object.assign(packageJson$1.dependencies, newPackageJson.dependencies);
348
- Object.assign(packageJson$1.devDependencies, newPackageJson.devDependencies);
349
- Object.assign(packageJson$1.scripts, newPackageJson.scripts);
350
- await writeFile(packageJsonPath, JSON.stringify(packageJson$1, null, 2));
351
- return packageJson$1;
352
- }
353
943
  async ensureConfig(root, opts) {
354
944
  const tasks = [];
355
- if (opts.packageJson) tasks.push(this.ensurePackageJson(root, typeof opts.packageJson === "boolean" ? {} : opts.packageJson));
945
+ if (opts.packageJson) tasks.push(this.pm.ensurePackageJson(root, typeof opts.packageJson === "boolean" ? {} : opts.packageJson).then(() => {}));
356
946
  if (opts.tsconfigJson) tasks.push(this.ensureTsConfig(root));
357
- if (opts.viteConfigTs) tasks.push(this.ensureViteConfig(root));
358
- if (opts.indexHtml) tasks.push(this.ensureIndexHtml(root));
947
+ if (opts.indexHtml) tasks.push(this.ensureReactProject(root));
359
948
  if (opts.biomeJson) tasks.push(this.ensureBiomeConfig(root));
360
949
  if (opts.editorconfig) tasks.push(this.ensureEditorConfig(root));
361
- return await Promise.all(tasks);
950
+ if (opts.claudeMd) tasks.push(this.ensureClaudeMd(root, typeof opts.claudeMd === "boolean" ? {} : opts.claudeMd));
951
+ await Promise.all(tasks);
362
952
  }
363
- /**
364
- * Ensure tsconfig.json exists in the project.
365
- *
366
- * Creates a standard Alepha tsconfig.json if none exists.
367
- *
368
- * @param root - The root directory of the project
369
- */
370
953
  async ensureTsConfig(root) {
371
- await this.ensureFileExists(root, "tsconfig.json", tsconfigJson, true);
372
- }
373
- /**
374
- * Ensure vite.config.ts exists in the project.
375
- *
376
- * Creates a standard Alepha vite.config.ts if none exists.
377
- */
378
- async ensureViteConfig(root, serverEntry) {
379
- await this.ensureFileExists(root, "vite.config.ts", viteConfigTs(serverEntry), false);
380
- }
381
- async checkFileExists(root, name, checkParentDirectories = false) {
382
- const configPath = join(root, name);
383
- if (!checkParentDirectories) try {
384
- await access(configPath);
385
- return configPath;
386
- } catch {
387
- return;
388
- }
389
- let currentDir = root;
390
- const maxIterations = 10;
391
- let level = 0;
392
- while (level < maxIterations) {
393
- try {
394
- const maybe = join(currentDir, name);
395
- await access(maybe);
396
- return maybe;
397
- } catch {
398
- const parentDir = join(currentDir, "..");
399
- if (parentDir === currentDir) break;
400
- currentDir = parentDir;
401
- }
402
- level += 1;
403
- }
954
+ if (await this.existsInParents(root, "tsconfig.json")) return;
955
+ await this.fs.writeFile(this.fs.join(root, "tsconfig.json"), tsconfigJson);
404
956
  }
405
- async ensureFileExists(root, name, content, checkParentDirectories = false) {
406
- if (!await this.checkFileExists(root, name, checkParentDirectories)) await writeFile(join(root, name), content);
407
- }
408
- /**
409
- * Get the path to Biome configuration file.
410
- *
411
- * Looks for an existing biome.json in the project root, or creates one if it doesn't exist.
412
- */
413
957
  async ensureBiomeConfig(root) {
414
- await this.ensureFileExists(root, "biome.json", biomeJson, true);
958
+ await this.ensureFileIfNotExists(root, "biome.json", biomeJson);
415
959
  }
416
- /**
417
- * Ensure .editorconfig exists in the project.
418
- *
419
- * Creates a standard .editorconfig if none exists.
420
- *
421
- * @param root - The root directory of the project
422
- */
423
960
  async ensureEditorConfig(root) {
424
- await this.ensureFileExists(root, ".editorconfig", editorconfig, true);
425
- }
426
- /**
427
- * Load Alepha instance from a server entry file.
428
- *
429
- * Dynamically imports the server entry file and extracts the Alepha instance.
430
- * Skips the automatic start process.
431
- *
432
- * @param rootDir - The root directory of the project
433
- * @param explicitEntry - Optional explicit path to the entry file
434
- * @returns Object containing the Alepha instance and the entry file path
435
- * @throws {AlephaError} If the Alepha instance cannot be found
436
- */
437
- async loadAlephaFromServerEntryFile(rootDir, explicitEntry) {
438
- process.env.ALEPHA_CLI_IMPORT = "true";
439
- const entry = await boot.getServerEntry(rootDir, explicitEntry);
440
- delete global.__alepha;
441
- const mod = await import(entry);
442
- this.log.debug(`Load entry: ${entry}`);
443
- if (mod.default instanceof Alepha) return {
444
- alepha: mod.default,
445
- entry
446
- };
447
- const g = global;
448
- if (g.__alepha) return {
449
- alepha: g.__alepha,
450
- entry
451
- };
452
- throw new AlephaError(`Could not find Alepha instance in entry file: ${entry}`);
961
+ await this.ensureFileIfNotExists(root, ".editorconfig", editorconfig);
453
962
  }
454
- /**
455
- * Generate JavaScript code for Drizzle entities export.
456
- *
457
- * Creates a temporary entities.js file that imports from the entry file
458
- * and exports database models for Drizzle Kit to process.
459
- *
460
- * @param entry - Path to the server entry file
461
- * @param provider - Name of the database provider
462
- * @param models - Array of model names to export
463
- * @returns JavaScript code as a string
464
- */
465
- generateEntitiesJs(entry, provider, models = []) {
466
- return `
467
- import "${entry}";
468
- import { DrizzleKitProvider, Repository } from "alepha/orm";
469
-
470
- const alepha = globalThis.__alepha;
471
- const kit = alepha.inject(DrizzleKitProvider);
472
- const provider = alepha.services(Repository).find((it) => it.provider.name === "${provider}").provider;
473
- const models = kit.getModels(provider);
474
-
475
- ${models.map((it) => `export const ${it} = models["${it}"];`).join("\n")}
476
-
477
- `.trim();
963
+ async ensureClaudeMd(root, options = {}) {
964
+ const path = this.fs.join(root, "CLAUDE.md");
965
+ if (!await this.fs.exists(path)) await this.fs.writeFile(path, claudeMd(options));
478
966
  }
479
967
  /**
480
- * Load environment variables from a .env file.
968
+ * Ensure src/main.server.ts exists with full API structure.
481
969
  *
482
- * Reads the .env file in the specified root directory and sets
483
- * the environment variables in process.env.
970
+ * Creates:
971
+ * - src/main.server.ts (entry point)
972
+ * - src/api/index.ts (API module)
973
+ * - src/api/controllers/HelloController.ts (example controller)
484
974
  */
485
- async loadEnv(root, files = [".env"]) {
486
- await this.envUtils.loadEnv(root, files);
487
- }
488
- async getPackageManager(root, flags) {
489
- if (flags?.yarn) return "yarn";
490
- if (flags?.pnpm) return "pnpm";
491
- if (flags?.npm) return "npm";
492
- if (flags?.bun) return "bun";
493
- if (this.alepha.isBun()) return "bun";
494
- if (await this.checkFileExists(root, "yarn.lock", true)) return "yarn";
495
- if (await this.checkFileExists(root, "pnpm-lock.yaml", true)) return "pnpm";
496
- return "npm";
497
- }
498
- async ensureIndexHtml(root) {
499
- if (await this.fs.exists(join(root, "index.html"))) return;
500
- const serverEntry = "src/main.server.ts";
501
- const browserEntry = "src/main.browser.ts";
502
- const appRouter = "src/AppRouter.ts";
503
- await this.fs.writeFile(join(root, "index.html"), indexHtml(browserEntry));
504
- try {
505
- await this.fs.mkdir(join(root, "src"), { recursive: true });
506
- } catch {}
507
- if (!await this.fs.exists(join(root, browserEntry))) await this.fs.writeFile(join(root, browserEntry), mainBrowserTs());
508
- if (!await this.fs.exists(join(root, serverEntry))) await this.fs.writeFile(join(root, serverEntry), mainBrowserTs());
509
- if (!await this.fs.exists(join(root, appRouter))) await this.fs.writeFile(join(root, appRouter), appRouterTs());
510
- }
511
- async exists(root, dirName) {
512
- return this.fs.exists(join(root, dirName));
975
+ async ensureApiProject(root) {
976
+ const srcDir = this.fs.join(root, "src");
977
+ if (await this.fs.exists(srcDir)) {
978
+ if ((await this.fs.ls(srcDir)).length > 0) return;
979
+ }
980
+ const appName = this.getAppName(root);
981
+ await this.fs.mkdir(this.fs.join(root, "src/api/controllers"), { recursive: true });
982
+ await this.fs.writeFile(this.fs.join(srcDir, "main.server.ts"), mainServerTs());
983
+ await this.fs.writeFile(this.fs.join(srcDir, "api/index.ts"), apiIndexTs({ appName }));
984
+ await this.fs.writeFile(this.fs.join(srcDir, "api/controllers/HelloController.ts"), apiHelloControllerTs());
513
985
  }
514
986
  /**
515
- * Ensure src/main.ts exists with a minimal Alepha bootstrap.
987
+ * Ensure full React project structure exists.
516
988
  *
517
- * Creates the src directory and main.ts file if the src directory
518
- * doesn't exist or is empty.
519
- *
520
- * @param root - The root directory of the project
989
+ * Creates:
990
+ * - index.html
991
+ * - src/main.server.ts, src/main.browser.ts
992
+ * - src/api/index.ts, src/api/controllers/HelloController.ts
993
+ * - src/web/index.ts, src/web/AppRouter.ts, src/web/components/Hello.tsx
521
994
  */
522
- async ensureSrcMain(root) {
523
- const srcDir = join(root, "src");
524
- const mainPath = join(srcDir, "main.ts");
525
- if (!await this.fs.exists(srcDir)) {
526
- await this.fs.mkdir(srcDir, { recursive: true });
527
- await this.fs.writeFile(mainPath, mainTs());
528
- return;
529
- }
530
- if ((await this.fs.ls(srcDir)).length === 0) await this.fs.writeFile(mainPath, mainTs());
995
+ async ensureReactProject(root) {
996
+ if (await this.fs.exists(this.fs.join(root, "index.html"))) return;
997
+ const appName = this.getAppName(root);
998
+ await this.fs.mkdir(this.fs.join(root, "src/api/controllers"), { recursive: true });
999
+ await this.fs.mkdir(this.fs.join(root, "src/web/components"), { recursive: true });
1000
+ await this.fs.writeFile(this.fs.join(root, "index.html"), indexHtml("src/main.browser.ts"));
1001
+ await this.ensureFileIfNotExists(root, "src/api/index.ts", apiIndexTs({ appName }));
1002
+ await this.ensureFileIfNotExists(root, "src/api/controllers/HelloController.ts", apiHelloControllerTs());
1003
+ await this.ensureFileIfNotExists(root, "src/main.server.ts", mainServerTs({ react: true }));
1004
+ await this.ensureFileIfNotExists(root, "src/web/index.ts", webIndexTs({ appName }));
1005
+ await this.ensureFileIfNotExists(root, "src/web/AppRouter.ts", webAppRouterTs());
1006
+ await this.ensureFileIfNotExists(root, "src/web/components/Hello.tsx", webHelloComponentTsx());
1007
+ await this.ensureFileIfNotExists(root, "src/main.browser.ts", mainBrowserTs());
531
1008
  }
532
1009
  /**
533
1010
  * Ensure test directory exists with a dummy test file.
534
- *
535
- * Creates the test directory and a dummy.spec.ts file if the test directory
536
- * doesn't exist or is empty.
537
- *
538
- * @param root - The root directory of the project
539
1011
  */
540
1012
  async ensureTestDir(root) {
541
- const testDir = join(root, "test");
542
- const dummyPath = join(testDir, "dummy.spec.ts");
1013
+ const testDir = this.fs.join(root, "test");
1014
+ const dummyPath = this.fs.join(testDir, "dummy.spec.ts");
543
1015
  if (!await this.fs.exists(testDir)) {
544
1016
  await this.fs.mkdir(testDir, { recursive: true });
545
1017
  await this.fs.writeFile(dummyPath, dummySpecTs());
@@ -547,68 +1019,20 @@ ${models.map((it) => `export const ${it} = models["${it}"];`).join("\n")}
547
1019
  }
548
1020
  if ((await this.fs.ls(testDir)).length === 0) await this.fs.writeFile(dummyPath, dummySpecTs());
549
1021
  }
550
- async readPackageJson(root) {
551
- const packageJson$1 = await this.fs.createFile({ path: join(root, "package.json") }).text();
552
- return JSON.parse(packageJson$1);
553
- }
554
- /**
555
- * Check if a dependency is installed in the project.
556
- *
557
- * @param root - The root directory of the project
558
- * @param packageName - The name of the package to check
559
- * @returns True if the package is in dependencies or devDependencies
560
- */
561
- async hasDependency(root, packageName) {
562
- try {
563
- const pkg = await this.readPackageJson(root);
564
- return !!(pkg.dependencies?.[packageName] || pkg.devDependencies?.[packageName]);
565
- } catch {
566
- return false;
567
- }
568
- }
569
- /**
570
- * Check if Expo is present in the project.
571
- *
572
- * @param root - The root directory of the project
573
- * @returns True if expo is in dependencies or devDependencies
574
- */
575
- async hasExpo(root) {
576
- return this.hasDependency(root, "expo");
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();
1022
+ async ensureFileIfNotExists(root, relativePath, content) {
1023
+ const fullPath = this.fs.join(root, relativePath);
1024
+ if (!await this.fs.exists(fullPath)) await this.fs.writeFile(fullPath, content);
594
1025
  }
595
1026
  /**
596
- * Install a dependency if it's missing from the project.
597
- *
598
- * Automatically detects the package manager (yarn, pnpm, npm) and installs
599
- * the package as a dev dependency if not already present.
1027
+ * Check if a file exists in the given directory or any parent directory.
600
1028
  */
601
- async ensureDependency(root, packageName, options = {}) {
602
- const { dev = true } = options;
603
- if (await this.hasDependency(root, packageName)) {
604
- this.log.debug(`Dependency '${packageName}' is already installed`);
605
- return;
606
- }
607
- const cmd = await this.getInstallCommand(root, packageName, dev);
608
- if (options.run) await options.run(cmd, { alias: `installing ${packageName}` });
609
- else {
610
- this.log.debug(`Installing ${packageName}`);
611
- await this.exec(cmd, { global: true });
1029
+ async existsInParents(root, filename) {
1030
+ let current = root;
1031
+ while (true) {
1032
+ if (await this.fs.exists(this.fs.join(current, filename))) return true;
1033
+ const parent = dirname(current);
1034
+ if (parent === current) return false;
1035
+ current = parent;
612
1036
  }
613
1037
  }
614
1038
  };
@@ -617,9 +1041,14 @@ ${models.map((it) => `export const ${it} = models["${it}"];`).join("\n")}
617
1041
  //#region ../../src/cli/commands/build.ts
618
1042
  var BuildCommand = class {
619
1043
  log = $logger();
1044
+ fs = $inject(FileSystemProvider);
620
1045
  utils = $inject(AlephaCliUtils);
1046
+ pm = $inject(PackageManagerUtils);
1047
+ scaffolder = $inject(ProjectScaffolder);
1048
+ options = $use(buildOptions);
621
1049
  build = $command({
622
1050
  name: "build",
1051
+ mode: "production",
623
1052
  description: "Build the project for production",
624
1053
  args: t.optional(t.text({
625
1054
  title: "path",
@@ -630,57 +1059,53 @@ var BuildCommand = class {
630
1059
  vercel: t.optional(t.boolean({ description: "Generate Vercel deployment configuration" })),
631
1060
  cloudflare: t.optional(t.boolean({ description: "Generate Cloudflare Workers configuration" })),
632
1061
  docker: t.optional(t.boolean({ description: "Generate Docker configuration" })),
633
- sitemap: t.optional(t.text({ description: "Generate sitemap.xml with base URL" }))
1062
+ sitemap: t.optional(t.text({ description: "Generate sitemap.xml with base URL" })),
1063
+ bun: t.optional(t.boolean({ description: "Prioritize .bun.ts entry files for Bun runtime" }))
634
1064
  }),
635
1065
  handler: async ({ flags, args, run, root }) => {
636
1066
  process.env.ALEPHA_BUILD_MODE = "cli";
637
1067
  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
- });
1068
+ if (await this.pm.hasExpo(root)) return;
1069
+ await this.scaffolder.ensureConfig(root, { tsconfigJson: true });
643
1070
  const entry = await boot.getServerEntry(root, args);
644
1071
  this.log.trace("Entry file found", { entry });
645
1072
  const distDir = "dist";
646
1073
  const clientDir = "public";
647
- await this.utils.ensureDependency(root, "vite", { run });
1074
+ await this.pm.ensureDependency(root, "vite", {
1075
+ run,
1076
+ exec: (cmd, opts) => this.utils.exec(cmd, opts)
1077
+ });
648
1078
  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] || {};
1079
+ const options = this.options;
650
1080
  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 : {};
1081
+ const stats = flags.stats ?? options.stats ?? false;
1082
+ const hasClient = await this.fs.exists(this.fs.join(root, "index.html"));
659
1083
  if (hasClient) await run({
660
1084
  name: "vite build client",
661
1085
  handler: () => buildClient({
662
1086
  silent: true,
663
1087
  dist: `${distDir}/${clientDir}`,
664
1088
  stats,
665
- precompress: clientOptions.precompress
1089
+ precompress: true
666
1090
  })
667
1091
  });
668
1092
  await run({
669
1093
  name: "vite build server",
670
1094
  handler: async () => {
671
- let clientBuilt = false;
672
- try {
673
- await readFile(`${distDir}/${clientDir}/index.html`, "utf-8");
674
- clientBuilt = true;
675
- } catch {}
1095
+ const clientIndexPath = `${distDir}/${clientDir}/index.html`;
1096
+ const clientBuilt = await this.fs.exists(clientIndexPath);
1097
+ const conditions = [];
1098
+ if (flags.bun) conditions.push("bun");
1099
+ if (options.cloudflare) conditions.push("workerd");
676
1100
  await buildServer({
677
1101
  silent: true,
678
1102
  entry,
679
1103
  distDir,
680
1104
  clientDir: clientBuilt ? clientDir : void 0,
681
- stats
1105
+ stats,
1106
+ conditions
682
1107
  });
683
- if (clientBuilt && hasServer) await unlink(`${distDir}/${clientDir}/index.html`);
1108
+ if (clientBuilt) await this.fs.rm(clientIndexPath);
684
1109
  }
685
1110
  });
686
1111
  await copyAssets({
@@ -690,58 +1115,49 @@ var BuildCommand = class {
690
1115
  run
691
1116
  });
692
1117
  if (hasClient) {
693
- const sitemapBaseUrl = flags.sitemap ?? clientOptions.sitemap?.hostname;
694
- if (sitemapBaseUrl) await run({
1118
+ const sitemapHostname = flags.sitemap ?? options.sitemap?.hostname;
1119
+ if (sitemapHostname) await run({
695
1120
  name: "add sitemap",
696
1121
  handler: async () => {
697
- await writeFile(`${distDir}/${clientDir}/sitemap.xml`, await generateSitemap({
1122
+ await this.fs.writeFile(`${distDir}/${clientDir}/sitemap.xml`, await generateSitemap({
698
1123
  entry: `${distDir}/index.js`,
699
- baseUrl: sitemapBaseUrl
1124
+ baseUrl: sitemapHostname
700
1125
  }));
701
1126
  }
702
1127
  });
703
- if (clientOptions.prerender) await run({
1128
+ await run({
704
1129
  name: "pre-render pages",
705
1130
  handler: async () => {
706
1131
  await prerenderPages({
707
1132
  dist: `${distDir}/${clientDir}`,
708
1133
  entry: `${distDir}/index.js`,
709
- compress: clientOptions.precompress
1134
+ compress: true
710
1135
  });
711
1136
  }
712
1137
  });
713
1138
  }
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
- }
1139
+ if (flags.vercel || options.vercel) await run({
1140
+ name: "add Vercel config",
1141
+ handler: () => generateVercel({
1142
+ distDir,
1143
+ clientDir,
1144
+ config: options.vercel
1145
+ })
1146
+ });
1147
+ if (flags.cloudflare || options.cloudflare) await run({
1148
+ name: "add Cloudflare config",
1149
+ handler: () => generateCloudflare({
1150
+ distDir,
1151
+ config: options.cloudflare?.config
1152
+ })
1153
+ });
1154
+ if (flags.docker || options.docker) await run({
1155
+ name: "add Docker config",
1156
+ handler: () => generateDocker({
1157
+ distDir,
1158
+ ...options.docker
1159
+ })
1160
+ });
745
1161
  }
746
1162
  });
747
1163
  };
@@ -769,6 +1185,7 @@ const drizzleCommandFlags = t.object({
769
1185
  });
770
1186
  var DbCommand = class {
771
1187
  log = $logger();
1188
+ fs = $inject(FileSystemProvider);
772
1189
  utils = $inject(AlephaCliUtils);
773
1190
  /**
774
1191
  * Check if database migrations are up to date.
@@ -793,15 +1210,16 @@ var DbCommand = class {
793
1210
  const providerName = provider.name;
794
1211
  if (accepted.has(providerName)) continue;
795
1212
  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) {
1213
+ const migrationDir = this.fs.join(rootDir, "migrations", providerName);
1214
+ const journalBuffer = await this.fs.readFile(`${migrationDir}/meta/_journal.json`).catch(() => null);
1215
+ if (!journalBuffer) {
799
1216
  this.log.info("No migration journal found.");
800
1217
  return;
801
1218
  }
802
- const journal = JSON.parse(journalFile);
1219
+ const journal = JSON.parse(journalBuffer.toString("utf-8"));
803
1220
  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"));
1221
+ const snapshotBuffer = await this.fs.readFile(`${migrationDir}/meta/${String(lastMigration.idx).padStart(4, "0")}_snapshot.json`);
1222
+ const lastSnapshot = JSON.parse(snapshotBuffer.toString("utf-8"));
805
1223
  const models = drizzleKitProvider.getModels(provider);
806
1224
  const kit = drizzleKitProvider.importDrizzleKit();
807
1225
  const now = kit.generateDrizzleJson(models, lastSnapshot.id);
@@ -837,7 +1255,6 @@ var DbCommand = class {
837
1255
  generate = $command({
838
1256
  name: "generate",
839
1257
  description: "Generate migration files based on current database schema",
840
- summary: false,
841
1258
  args: t.optional(t.text({
842
1259
  title: "path",
843
1260
  description: "Path to the Alepha server entry file"
@@ -862,7 +1279,6 @@ var DbCommand = class {
862
1279
  push = $command({
863
1280
  name: "push",
864
1281
  description: "Push database schema changes directly to the database",
865
- summary: false,
866
1282
  args: t.optional(t.text({
867
1283
  title: "path",
868
1284
  description: "Path to the Alepha server entry file"
@@ -885,7 +1301,6 @@ var DbCommand = class {
885
1301
  migrate = $command({
886
1302
  name: "migrate",
887
1303
  description: "Apply pending database migrations",
888
- summary: false,
889
1304
  args: t.optional(t.text({
890
1305
  title: "path",
891
1306
  description: "Path to the Alepha server entry file"
@@ -908,7 +1323,6 @@ var DbCommand = class {
908
1323
  studio = $command({
909
1324
  name: "studio",
910
1325
  description: "Launch Drizzle Studio database browser",
911
- summary: false,
912
1326
  args: t.optional(t.text({
913
1327
  title: "path",
914
1328
  description: "Path to the Alepha server entry file"
@@ -959,6 +1373,7 @@ var DbCommand = class {
959
1373
  const provider = primitive.provider;
960
1374
  const providerName = provider.name;
961
1375
  const dialect = provider.dialect;
1376
+ if (providerName === "") continue;
962
1377
  if (accepted.has(providerName)) continue;
963
1378
  accepted.add(providerName);
964
1379
  if (options.provider && options.provider !== providerName) {
@@ -972,6 +1387,7 @@ var DbCommand = class {
972
1387
  provider,
973
1388
  providerName,
974
1389
  providerUrl: provider.url,
1390
+ providerDriver: provider.driver,
975
1391
  dialect,
976
1392
  entry,
977
1393
  rootDir
@@ -993,9 +1409,9 @@ var DbCommand = class {
993
1409
  dbCredentials: { url: options.providerUrl }
994
1410
  };
995
1411
  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") {
1412
+ if (options.providerDriver === "d1") config.driver = "d1-http";
1413
+ if (options.providerDriver === "pglite") config.driver = "pglite";
1414
+ if (options.dialect === "sqlite") if (options.providerDriver === "d1") {
999
1415
  const token = process.env.CLOUDFLARE_API_TOKEN;
1000
1416
  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
1417
  const accountId = process.env.CLOUDFLARE_ACCOUNT_ID;
@@ -1012,7 +1428,7 @@ var DbCommand = class {
1012
1428
  } else {
1013
1429
  let url = options.providerUrl;
1014
1430
  url = url.replace("sqlite://", "").replace("file://", "");
1015
- url = join(options.rootDir, url);
1431
+ url = this.fs.join(options.rootDir, url);
1016
1432
  config.dbCredentials = { url };
1017
1433
  }
1018
1434
  const drizzleConfigJs = `export default ${JSON.stringify(config, null, 2)}`;
@@ -1024,7 +1440,9 @@ var DbCommand = class {
1024
1440
  //#region ../../src/cli/commands/deploy.ts
1025
1441
  var DeployCommand = class {
1026
1442
  log = $logger();
1443
+ fs = $inject(FileSystemProvider);
1027
1444
  utils = $inject(AlephaCliUtils);
1445
+ pm = $inject(PackageManagerUtils);
1028
1446
  /**
1029
1447
  * Deploy the project to a hosting platform (e.g., Vercel, Cloudflare, Surge)
1030
1448
  *
@@ -1072,7 +1490,10 @@ var DeployCommand = class {
1072
1490
  this.log.debug("Running database migrations before deployment...");
1073
1491
  await this.utils.exec(`alepha db migrate --mode=${mode}`);
1074
1492
  }
1075
- await this.utils.ensureDependency(root, "vercel", { dev: true });
1493
+ await this.pm.ensureDependency(root, "vercel", {
1494
+ dev: true,
1495
+ exec: (cmd, opts) => this.utils.exec(cmd, opts)
1496
+ });
1076
1497
  const command = `vercel . --cwd=dist ${mode === "production" ? "--prod" : ""}`.trim();
1077
1498
  this.log.debug(`Deploying to Vercel with command: ${command}`);
1078
1499
  await this.utils.exec(command);
@@ -1083,15 +1504,21 @@ var DeployCommand = class {
1083
1504
  this.log.debug("Running database migrations before deployment...");
1084
1505
  await this.utils.exec(`alepha db migrate --mode=${mode}`);
1085
1506
  }
1086
- await this.utils.ensureDependency(root, "wrangler", { dev: true });
1507
+ await this.pm.ensureDependency(root, "wrangler", {
1508
+ dev: true,
1509
+ exec: (cmd, opts) => this.utils.exec(cmd, opts)
1510
+ });
1087
1511
  const command = `wrangler deploy ${mode === "production" ? "" : "--env preview"} --config=dist/wrangler.jsonc`.trim();
1088
1512
  this.log.info(`Deploying to Cloudflare with command: ${command}`);
1089
1513
  await this.utils.exec(command);
1090
1514
  return;
1091
1515
  }
1092
1516
  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");
1517
+ await this.pm.ensureDependency(root, "surge", {
1518
+ dev: true,
1519
+ exec: (cmd, opts) => this.utils.exec(cmd, opts)
1520
+ });
1521
+ const distPath = this.fs.join(root, "dist/public");
1095
1522
  this.log.debug(`Deploying to Surge from directory: ${distPath}`);
1096
1523
  await this.utils.exec(`surge ${distPath}`);
1097
1524
  return;
@@ -1105,7 +1532,10 @@ var DeployCommand = class {
1105
1532
  //#region ../../src/cli/commands/dev.ts
1106
1533
  var DevCommand = class {
1107
1534
  log = $logger();
1535
+ fs = $inject(FileSystemProvider);
1108
1536
  utils = $inject(AlephaCliUtils);
1537
+ pm = $inject(PackageManagerUtils);
1538
+ scaffolder = $inject(ProjectScaffolder);
1109
1539
  alepha = $inject(Alepha);
1110
1540
  /**
1111
1541
  * Will run the project in watch mode.
@@ -1121,11 +1551,8 @@ var DevCommand = class {
1121
1551
  description: "Filepath to run"
1122
1552
  })),
1123
1553
  handler: async ({ args, root }) => {
1124
- const expo = await this.utils.hasExpo(root);
1125
- await this.utils.ensureConfig(root, {
1126
- viteConfigTs: !expo,
1127
- tsconfigJson: true
1128
- });
1554
+ const expo = await this.pm.hasExpo(root);
1555
+ await this.scaffolder.ensureConfig(root, { tsconfigJson: true });
1129
1556
  if (expo) {
1130
1557
  await this.utils.exec("expo start");
1131
1558
  return;
@@ -1133,24 +1560,23 @@ var DevCommand = class {
1133
1560
  const entry = await boot.getServerEntry(root, args);
1134
1561
  this.log.trace("Entry file found", { entry });
1135
1562
  if (!await this.isFullstackProject(root)) {
1136
- const exe = this.alepha.isBun() ? "bun" : "tsx";
1563
+ const exe = await this.isBunProject(root) ? "bun" : "tsx";
1137
1564
  let cmd = `${exe} --watch`;
1138
1565
  if (await this.utils.exists(root, ".env")) cmd += " --env-file=./.env";
1139
1566
  cmd += ` ${entry}`;
1140
1567
  await this.utils.exec(cmd, { global: exe === "bun" });
1141
1568
  return;
1142
1569
  }
1143
- await this.utils.ensureDependency(root, "vite");
1144
- await this.utils.exec("vite");
1570
+ await this.pm.ensureDependency(root, "vite", { exec: (cmd, opts) => this.utils.exec(cmd, opts) });
1571
+ await devServer();
1145
1572
  }
1146
1573
  });
1574
+ async isBunProject(root) {
1575
+ if (this.alepha.isBun()) return true;
1576
+ return this.fs.exists(this.fs.join(root, "bun.lock"));
1577
+ }
1147
1578
  async isFullstackProject(root) {
1148
- try {
1149
- await access(join(root, "index.html"));
1150
- return true;
1151
- } catch {
1152
- return false;
1153
- }
1579
+ return this.fs.exists(this.fs.join(root, "index.html"));
1154
1580
  }
1155
1581
  };
1156
1582
 
@@ -1158,12 +1584,14 @@ var DevCommand = class {
1158
1584
  //#region ../../src/cli/commands/format.ts
1159
1585
  var FormatCommand = class {
1160
1586
  utils = $inject(AlephaCliUtils);
1587
+ pm = $inject(PackageManagerUtils);
1588
+ scaffolder = $inject(ProjectScaffolder);
1161
1589
  format = $command({
1162
1590
  name: "format",
1163
1591
  description: "Format the codebase using Biome",
1164
1592
  handler: async ({ root }) => {
1165
- await this.utils.ensureConfig(root, { biomeJson: true });
1166
- await this.utils.ensureDependency(root, "@biomejs/biome");
1593
+ await this.scaffolder.ensureConfig(root, { biomeJson: true });
1594
+ await this.pm.ensureDependency(root, "@biomejs/biome", { exec: (cmd, opts) => this.utils.exec(cmd, opts) });
1167
1595
  await this.utils.exec("biome format --fix");
1168
1596
  }
1169
1597
  });
@@ -1385,6 +1813,39 @@ var ChangelogCommand = class {
1385
1813
  });
1386
1814
  };
1387
1815
 
1816
+ //#endregion
1817
+ //#region ../../src/cli/commands/gen/env.ts
1818
+ var GenEnvCommand = class {
1819
+ log = $logger();
1820
+ utils = $inject(AlephaCliUtils);
1821
+ fs = $inject(FileSystemProvider);
1822
+ command = $command({
1823
+ name: "env",
1824
+ description: "Extract environment variables from server entry file",
1825
+ flags: t.object({ out: t.optional(t.text({
1826
+ aliases: ["o"],
1827
+ description: "Output file path (e.g., .env)"
1828
+ })) }),
1829
+ handler: async ({ root, flags }) => {
1830
+ const { alepha } = await this.utils.loadAlephaFromServerEntryFile(root);
1831
+ try {
1832
+ const { env } = alepha.dump();
1833
+ let dotEnvFile = "";
1834
+ for (const [key, value] of Object.entries(env)) {
1835
+ if (value.description) dotEnvFile += `# ${value.description.split("\n").join("\n# ")}\n`;
1836
+ if (value.required && !value.default) dotEnvFile += `# (required)\n`;
1837
+ if (value.enum) dotEnvFile += `# Possible values: ${value.enum.join(", ")}\n`;
1838
+ dotEnvFile += `${key}=${value.default || ""}\n\n`;
1839
+ }
1840
+ if (flags.out) await this.fs.writeFile(this.fs.join(root, flags.out), dotEnvFile);
1841
+ else this.log.info(dotEnvFile);
1842
+ } catch (err) {
1843
+ this.log.error("Failed to extract environment variables", err);
1844
+ }
1845
+ }
1846
+ });
1847
+ };
1848
+
1388
1849
  //#endregion
1389
1850
  //#region ../../src/cli/commands/gen/openapi.ts
1390
1851
  var OpenApiCommand = class {
@@ -1420,7 +1881,7 @@ var OpenApiCommand = class {
1420
1881
  this.log.error("Missing $swagger() primitive in your server configuration.");
1421
1882
  return;
1422
1883
  }
1423
- this.log.error(`OpenAPI generation failed - ${message}`, { err });
1884
+ this.log.error(`OpenAPI generation failed - ${message}`, err);
1424
1885
  }
1425
1886
  }
1426
1887
  });
@@ -1431,10 +1892,15 @@ var OpenApiCommand = class {
1431
1892
  var GenCommand = class {
1432
1893
  changelog = $inject(ChangelogCommand);
1433
1894
  openapi = $inject(OpenApiCommand);
1895
+ genEnv = $inject(GenEnvCommand);
1434
1896
  gen = $command({
1435
1897
  name: "gen",
1436
1898
  description: "Generate code, documentation, ...",
1437
- children: [this.changelog.command, this.openapi.command],
1899
+ children: [
1900
+ this.changelog.command,
1901
+ this.openapi.command,
1902
+ this.genEnv.command
1903
+ ],
1438
1904
  handler: async ({ help }) => {
1439
1905
  help();
1440
1906
  }
@@ -1445,6 +1911,9 @@ var GenCommand = class {
1445
1911
  //#region ../../src/cli/commands/init.ts
1446
1912
  var InitCommand = class {
1447
1913
  utils = $inject(AlephaCliUtils);
1914
+ pm = $inject(PackageManagerUtils);
1915
+ scaffolder = $inject(ProjectScaffolder);
1916
+ fs = $inject(FileSystemProvider);
1448
1917
  /**
1449
1918
  * Ensure the project has the necessary Alepha configuration files.
1450
1919
  * Add the correct dependencies to package.json and install them.
@@ -1452,46 +1921,78 @@ var InitCommand = class {
1452
1921
  init = $command({
1453
1922
  name: "init",
1454
1923
  description: "Add missing Alepha configuration files to the project",
1924
+ args: t.optional(t.text({
1925
+ title: "path",
1926
+ trim: true,
1927
+ lowercase: true
1928
+ })),
1455
1929
  flags: t.object({
1930
+ agent: t.optional(t.boolean({
1931
+ aliases: ["a"],
1932
+ description: "Add CLAUDE.md for Claude Code AI assistant"
1933
+ })),
1456
1934
  yarn: t.optional(t.boolean({ description: "Use Yarn package manager" })),
1457
1935
  pnpm: t.optional(t.boolean({ description: "Use pnpm package manager" })),
1458
1936
  npm: t.optional(t.boolean({ description: "Use npm package manager" })),
1459
1937
  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" })),
1938
+ web: t.optional(t.boolean({
1939
+ aliases: ["r"],
1940
+ description: "Include Alepha React dependencies"
1941
+ })),
1942
+ admin: t.optional(t.boolean({ description: "Include Alepha UI dependencies" })),
1462
1943
  test: t.optional(t.boolean({ description: "Include Vitest and create test directory" }))
1463
1944
  }),
1464
- handler: async ({ run, flags, root }) => {
1465
- if (flags.ui) flags.react = true;
1466
- const isExpo = await this.utils.hasExpo(root);
1945
+ handler: async ({ run, flags, root, args }) => {
1946
+ if (flags.admin) flags.web = true;
1947
+ if (args) {
1948
+ root = this.fs.join(root, args);
1949
+ await this.fs.mkdir(root);
1950
+ }
1951
+ const isExpo = await this.pm.hasExpo(root);
1467
1952
  await run({
1468
1953
  name: "ensuring configuration files",
1469
1954
  handler: async () => {
1470
- await this.utils.ensureConfig(root, {
1955
+ await this.scaffolder.ensureConfig(root, {
1471
1956
  tsconfigJson: true,
1472
1957
  packageJson: flags,
1473
1958
  biomeJson: true,
1474
- viteConfigTs: !isExpo,
1475
1959
  editorconfig: true,
1476
- indexHtml: !!flags.react && !isExpo
1960
+ indexHtml: !!flags.web && !isExpo,
1961
+ claudeMd: flags.agent ? {
1962
+ react: !!flags.web,
1963
+ ui: !!flags.admin
1964
+ } : false
1477
1965
  });
1478
- if (!flags.react) await this.utils.ensureSrcMain(root);
1966
+ if (!flags.web) await this.scaffolder.ensureApiProject(root);
1479
1967
  }
1480
1968
  });
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 });
1969
+ const pmName = await this.pm.getPackageManager(root, flags);
1970
+ if (pmName === "yarn") {
1971
+ await this.pm.ensureYarn(root);
1972
+ await run("yarn set version stable", { root });
1973
+ } else if (pmName === "bun") await this.pm.ensureBun(root);
1974
+ else if (pmName === "pnpm") await this.pm.ensurePnpm(root);
1975
+ else await this.pm.ensureNpm(root);
1976
+ await run(`${pmName} install`, {
1977
+ alias: `installing dependencies with ${pmName}`,
1978
+ root
1979
+ });
1980
+ if (!isExpo) await this.pm.ensureDependency(root, "vite", {
1981
+ run,
1982
+ exec: (cmd, opts) => this.utils.exec(cmd, opts)
1983
+ });
1984
+ await this.pm.ensureDependency(root, "@biomejs/biome", {
1985
+ run,
1986
+ exec: (cmd, opts) => this.utils.exec(cmd, opts)
1987
+ });
1491
1988
  if (flags.test) {
1492
- await this.utils.ensureTestDir(root);
1493
- await run(`${pm} ${pm === "yarn" ? "add" : "install"} -D vitest`, { alias: "setup testing with Vitest" });
1989
+ await this.scaffolder.ensureTestDir(root);
1990
+ await run(`${pmName} ${pmName === "yarn" ? "add" : "install"} -D vitest`, { alias: "setup testing with Vitest" });
1494
1991
  }
1992
+ await run(`${pmName} run lint`, {
1993
+ alias: "running linter",
1994
+ root
1995
+ });
1495
1996
  }
1496
1997
  });
1497
1998
  };
@@ -1500,12 +2001,14 @@ var InitCommand = class {
1500
2001
  //#region ../../src/cli/commands/lint.ts
1501
2002
  var LintCommand = class {
1502
2003
  utils = $inject(AlephaCliUtils);
2004
+ pm = $inject(PackageManagerUtils);
2005
+ scaffolder = $inject(ProjectScaffolder);
1503
2006
  lint = $command({
1504
2007
  name: "lint",
1505
2008
  description: "Run linter across the codebase using Biome",
1506
2009
  handler: async ({ root }) => {
1507
- await this.utils.ensureConfig(root, { biomeJson: true });
1508
- await this.utils.ensureDependency(root, "@biomejs/biome");
2010
+ await this.scaffolder.ensureConfig(root, { biomeJson: true });
2011
+ await this.pm.ensureDependency(root, "@biomejs/biome", { exec: (cmd, opts) => this.utils.exec(cmd, opts) });
1509
2012
  await this.utils.exec("biome check --formatter-enabled=false --fix");
1510
2013
  }
1511
2014
  });
@@ -1539,34 +2042,12 @@ var RootCommand = class {
1539
2042
  });
1540
2043
  };
1541
2044
 
1542
- //#endregion
1543
- //#region ../../src/cli/commands/run.ts
1544
- var RunCommand = class {
1545
- utils = $inject(AlephaCliUtils);
1546
- run = $command({
1547
- name: "run",
1548
- hide: true,
1549
- description: "Run a TypeScript file directly",
1550
- flags: t.object({ watch: t.optional(t.boolean({
1551
- description: "Watch file for changes",
1552
- alias: "w"
1553
- })) }),
1554
- summary: false,
1555
- args: t.text({
1556
- title: "path",
1557
- description: "Filepath to run"
1558
- }),
1559
- handler: async ({ args, flags, root }) => {
1560
- await this.utils.ensureTsConfig(root);
1561
- await this.utils.exec(`tsx ${flags.watch ? "watch " : ""}${args}`);
1562
- }
1563
- });
1564
- };
1565
-
1566
2045
  //#endregion
1567
2046
  //#region ../../src/cli/commands/test.ts
1568
2047
  var TestCommand = class {
1569
2048
  utils = $inject(AlephaCliUtils);
2049
+ pm = $inject(PackageManagerUtils);
2050
+ scaffolder = $inject(ProjectScaffolder);
1570
2051
  test = $command({
1571
2052
  name: "test",
1572
2053
  description: "Run tests using Vitest",
@@ -1579,11 +2060,8 @@ var TestCommand = class {
1579
2060
  description: "Additional arguments to pass to Vitest. E.g., --coverage"
1580
2061
  })) }),
1581
2062
  handler: async ({ root, flags, env }) => {
1582
- await this.utils.ensureConfig(root, {
1583
- tsconfigJson: true,
1584
- viteConfigTs: true
1585
- });
1586
- await this.utils.ensureDependency(root, "vitest");
2063
+ await this.scaffolder.ensureConfig(root, { tsconfigJson: true });
2064
+ await this.pm.ensureDependency(root, "vitest", { exec: (cmd, opts) => this.utils.exec(cmd, opts) });
1587
2065
  const config = flags.config ? `--config=${flags.config}` : "";
1588
2066
  await this.utils.exec(`vitest run ${config} ${env.VITEST_ARGS}`);
1589
2067
  }
@@ -1594,6 +2072,7 @@ var TestCommand = class {
1594
2072
  //#region ../../src/cli/commands/typecheck.ts
1595
2073
  var TypecheckCommand = class {
1596
2074
  utils = $inject(AlephaCliUtils);
2075
+ pm = $inject(PackageManagerUtils);
1597
2076
  log = $logger();
1598
2077
  /**
1599
2078
  * Run TypeScript type checking across the codebase with no emit.
@@ -1604,7 +2083,7 @@ var TypecheckCommand = class {
1604
2083
  description: "Check TypeScript types across the codebase",
1605
2084
  handler: async ({ root }) => {
1606
2085
  this.log.info("Starting TypeScript type checking...");
1607
- await this.utils.ensureDependency(root, "typescript");
2086
+ await this.pm.ensureDependency(root, "typescript", { exec: (cmd, opts) => this.utils.exec(cmd, opts) });
1608
2087
  await this.utils.exec("tsc --noEmit");
1609
2088
  this.log.info("TypeScript type checking completed successfully.");
1610
2089
  }
@@ -1615,6 +2094,7 @@ var TypecheckCommand = class {
1615
2094
  //#region ../../src/cli/commands/verify.ts
1616
2095
  var VerifyCommand = class {
1617
2096
  utils = $inject(AlephaCliUtils);
2097
+ pm = $inject(PackageManagerUtils);
1618
2098
  /**
1619
2099
  * Run a series of verification commands to ensure code quality and correctness.
1620
2100
  *
@@ -1636,9 +2116,9 @@ var VerifyCommand = class {
1636
2116
  await run("alepha format");
1637
2117
  await run("alepha lint");
1638
2118
  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");
2119
+ if ((await this.pm.readPackageJson(root)).devDependencies?.vitest) await run("alepha test");
2120
+ if (await this.utils.exists(root, "migrations")) await run("alepha db check-migrations");
2121
+ if (!await this.pm.hasExpo(root)) await run("alepha build");
1642
2122
  await run("alepha clean");
1643
2123
  }
1644
2124
  });
@@ -1686,7 +2166,6 @@ const AlephaCli = $module({
1686
2166
  InitCommand,
1687
2167
  LintCommand,
1688
2168
  RootCommand,
1689
- RunCommand,
1690
2169
  TestCommand,
1691
2170
  TypecheckCommand,
1692
2171
  VerifyCommand,
@@ -1705,11 +2184,11 @@ var AlephaPackageBuilderCli = class {
1705
2184
  root: true,
1706
2185
  handler: async ({ run, root }) => {
1707
2186
  const modules = [];
1708
- const pkg = await readFile("package.json", "utf-8");
1709
- const pkgData = JSON.parse(pkg);
2187
+ const pkgBuffer = await this.fs.readFile("package.json");
2188
+ const pkgData = JSON.parse(pkgBuffer.toString("utf-8"));
1710
2189
  const packageName = pkgData.name;
1711
2190
  await run("analyze modules", async () => {
1712
- modules.push(...await analyzeModules(join(root, this.src), packageName));
2191
+ modules.push(...await analyzeModules(this.fs.join(root, this.src), packageName));
1713
2192
  });
1714
2193
  pkgData.exports = {};
1715
2194
  for (const item of modules) {
@@ -1721,6 +2200,7 @@ var AlephaPackageBuilderCli = class {
1721
2200
  if (item.native) pkgData.exports[path]["react-native"] = `./src/${item.name}/index.native.ts`;
1722
2201
  else if (item.browser) pkgData.exports[path]["react-native"] = `./src/${item.name}/index.browser.ts`;
1723
2202
  if (item.browser) pkgData.exports[path].browser = `./src/${item.name}/index.browser.ts`;
2203
+ if (item.bun) pkgData.exports[path].bun = `./src/${item.name}/index.bun.ts`;
1724
2204
  pkgData.exports[path].import = `./src/${item.name}/index.ts`;
1725
2205
  pkgData.exports[path].default = `./src/${item.name}/index.ts`;
1726
2206
  }
@@ -1733,33 +2213,30 @@ var AlephaPackageBuilderCli = class {
1733
2213
  pkgData.exports["./json/styles"] = "./src/json/styles.css";
1734
2214
  }
1735
2215
  await this.fs.writeFile("package.json", JSON.stringify(pkgData, null, 2));
1736
- const tmpDir = join(root, "node_modules/.alepha");
2216
+ const tmpDir = this.fs.join(root, "node_modules/.alepha");
1737
2217
  await this.fs.mkdir(tmpDir, { recursive: true }).catch(() => {});
1738
- await this.fs.writeFile(join(tmpDir, "module-dependencies.json"), JSON.stringify(modules, null, 2));
1739
- const tsconfig = await readFile(join(root, "../../tsconfig.json"), "utf-8");
1740
- const external = Object.keys(JSON.parse(tsconfig).compilerOptions.paths);
2218
+ await this.fs.writeFile(this.fs.join(tmpDir, "module-dependencies.json"), JSON.stringify(modules, null, 2));
2219
+ const tsconfigBuffer = await this.fs.readFile(this.fs.join(root, "../../tsconfig.json"));
2220
+ const external = Object.keys(JSON.parse(tsconfigBuffer.toString("utf-8")).compilerOptions.paths);
1741
2221
  external.push("bun");
1742
2222
  external.push("bun:sqlite");
1743
2223
  await run.rm(this.dist);
1744
2224
  const build = async (item) => {
1745
2225
  const entries = [];
1746
- const src = join(root, this.src, item.name);
1747
- const dest = join(root, this.dist, item.name);
2226
+ const src = this.fs.join(root, this.src, item.name);
2227
+ const dest = this.fs.join(root, this.dist, item.name);
1748
2228
  entries.push({
1749
- entry: join(src, "index.ts"),
2229
+ entry: this.fs.join(src, "index.ts"),
1750
2230
  outDir: dest,
1751
2231
  format: ["esm"],
1752
2232
  sourcemap: true,
1753
2233
  fixedExtension: false,
1754
2234
  platform: "node",
1755
2235
  external,
1756
- dts: {
1757
- sourcemap: true,
1758
- resolve: false
1759
- }
2236
+ dts: { sourcemap: true }
1760
2237
  });
1761
2238
  if (item.native) entries.push({
1762
- entry: join(src, "index.native.ts"),
2239
+ entry: this.fs.join(src, "index.native.ts"),
1763
2240
  outDir: dest,
1764
2241
  platform: "neutral",
1765
2242
  sourcemap: true,
@@ -1767,14 +2244,23 @@ var AlephaPackageBuilderCli = class {
1767
2244
  external
1768
2245
  });
1769
2246
  if (item.browser) entries.push({
1770
- entry: join(src, "index.browser.ts"),
2247
+ entry: this.fs.join(src, "index.browser.ts"),
1771
2248
  outDir: dest,
1772
2249
  platform: "browser",
1773
2250
  sourcemap: true,
1774
2251
  dts: false,
1775
2252
  external
1776
2253
  });
1777
- const config = join(tmpDir, `tsdown-${item.name.replace("/", "-")}.config.js`);
2254
+ if (item.bun) entries.push({
2255
+ entry: this.fs.join(src, "index.bun.ts"),
2256
+ outDir: dest,
2257
+ platform: "node",
2258
+ sourcemap: true,
2259
+ fixedExtension: false,
2260
+ dts: false,
2261
+ external
2262
+ });
2263
+ const config = this.fs.join(tmpDir, `tsdown-${item.name.replace("/", "-")}.config.js`);
1778
2264
  await this.fs.writeFile(config, `export default ${JSON.stringify(entries, null, 2)};`);
1779
2265
  await run(`npx tsdown -c=${config}`);
1780
2266
  };
@@ -1848,7 +2334,7 @@ function detectCircularDependencies(modules) {
1848
2334
  }
1849
2335
  for (const module of modules) {
1850
2336
  const cycle = hasCycle(module.name);
1851
- if (cycle) throw new Error(`Circular dependency detected: ${cycle.join(" -> ")}`);
2337
+ if (cycle) throw new AlephaError(`Circular dependency detected: ${cycle.join(" -> ")}`);
1852
2338
  }
1853
2339
  }
1854
2340
  async function analyzeModules(srcDir, packageName) {
@@ -1862,6 +2348,7 @@ async function analyzeModules(srcDir, packageName) {
1862
2348
  const dependencies = /* @__PURE__ */ new Set();
1863
2349
  const hasBrowser = await fileExists(join(modulePath, "index.browser.ts"));
1864
2350
  const hasNative = await fileExists(join(modulePath, "index.native.ts"));
2351
+ const hasBun = await fileExists(join(modulePath, "index.bun.ts"));
1865
2352
  const hasNode = await fileExists(join(modulePath, "index.node.ts"));
1866
2353
  const files = await getAllFiles(modulePath);
1867
2354
  for (const file of files) {
@@ -1878,6 +2365,7 @@ async function analyzeModules(srcDir, packageName) {
1878
2365
  };
1879
2366
  if (hasNative) module.native = true;
1880
2367
  if (hasBrowser) module.browser = true;
2368
+ if (hasBun) module.bun = true;
1881
2369
  if (hasNode) module.node = true;
1882
2370
  modules.push(module);
1883
2371
  } else await scanDirectory(modulePath, moduleName);
@@ -1890,11 +2378,13 @@ async function analyzeModules(srcDir, packageName) {
1890
2378
 
1891
2379
  //#endregion
1892
2380
  //#region ../../src/cli/defineConfig.ts
1893
- const defineConfig = (config) => {
2381
+ const defineConfig = (runConfig) => {
1894
2382
  return (alepha) => {
1895
- const { commands, services = [] } = config(alepha);
1896
- for (const it of services) alepha.with(it);
1897
- return { ...commands };
2383
+ const config = typeof runConfig === "function" ? runConfig(alepha) : runConfig;
2384
+ if (config.services) for (const it of config.services) alepha.with(it);
2385
+ if (config.env) for (const [key, value] of Object.entries(config.env)) process.env[key] = String(value);
2386
+ if (config.build) alepha.set(buildOptions, config.build);
2387
+ return { ...config.commands };
1898
2388
  };
1899
2389
  };
1900
2390
  /**
@@ -1903,5 +2393,5 @@ const defineConfig = (config) => {
1903
2393
  const defineAlephaConfig = defineConfig;
1904
2394
 
1905
2395
  //#endregion
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 };
2396
+ export { AlephaCli, AlephaCliUtils, AlephaPackageBuilderCli, BuildCommand, ChangelogCommand, CleanCommand, DEFAULT_IGNORE, DbCommand, DeployCommand, DevCommand, FormatCommand, GitMessageParser, GitProvider, InitCommand, LintCommand, OpenApiCommand, RootCommand, TestCommand, TypecheckCommand, VerifyCommand, analyzeModules, changelogOptions, defineAlephaConfig, defineConfig, version };
1907
2397
  //# sourceMappingURL=index.js.map