alepha 0.14.4 → 0.15.1

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 (322) hide show
  1. package/README.md +44 -102
  2. package/dist/api/audits/index.d.ts +331 -443
  3. package/dist/api/audits/index.d.ts.map +1 -1
  4. package/dist/api/audits/index.js +2 -2
  5. package/dist/api/audits/index.js.map +1 -1
  6. package/dist/api/files/index.d.ts +0 -113
  7. package/dist/api/files/index.d.ts.map +1 -1
  8. package/dist/api/files/index.js +2 -3
  9. package/dist/api/files/index.js.map +1 -1
  10. package/dist/api/jobs/index.d.ts +151 -262
  11. package/dist/api/jobs/index.d.ts.map +1 -1
  12. package/dist/api/notifications/index.browser.js +4 -4
  13. package/dist/api/notifications/index.browser.js.map +1 -1
  14. package/dist/api/notifications/index.d.ts +164 -276
  15. package/dist/api/notifications/index.d.ts.map +1 -1
  16. package/dist/api/notifications/index.js +4 -4
  17. package/dist/api/notifications/index.js.map +1 -1
  18. package/dist/api/parameters/index.d.ts +265 -377
  19. package/dist/api/parameters/index.d.ts.map +1 -1
  20. package/dist/api/users/index.browser.js +1 -2
  21. package/dist/api/users/index.browser.js.map +1 -1
  22. package/dist/api/users/index.d.ts +195 -301
  23. package/dist/api/users/index.d.ts.map +1 -1
  24. package/dist/api/users/index.js +203 -184
  25. package/dist/api/users/index.js.map +1 -1
  26. package/dist/api/verifications/index.d.ts.map +1 -1
  27. package/dist/batch/index.d.ts.map +1 -1
  28. package/dist/batch/index.js +1 -2
  29. package/dist/batch/index.js.map +1 -1
  30. package/dist/bucket/index.d.ts.map +1 -1
  31. package/dist/cache/core/index.d.ts.map +1 -1
  32. package/dist/cache/redis/index.d.ts.map +1 -1
  33. package/dist/cache/redis/index.js +2 -2
  34. package/dist/cache/redis/index.js.map +1 -1
  35. package/dist/cli/index.d.ts +5900 -165
  36. package/dist/cli/index.d.ts.map +1 -1
  37. package/dist/cli/index.js +1481 -639
  38. package/dist/cli/index.js.map +1 -1
  39. package/dist/command/index.d.ts +8 -4
  40. package/dist/command/index.d.ts.map +1 -1
  41. package/dist/command/index.js +29 -25
  42. package/dist/command/index.js.map +1 -1
  43. package/dist/core/index.browser.js +563 -54
  44. package/dist/core/index.browser.js.map +1 -1
  45. package/dist/core/index.d.ts +175 -8
  46. package/dist/core/index.d.ts.map +1 -1
  47. package/dist/core/index.js +564 -54
  48. package/dist/core/index.js.map +1 -1
  49. package/dist/core/index.native.js +563 -54
  50. package/dist/core/index.native.js.map +1 -1
  51. package/dist/datetime/index.d.ts.map +1 -1
  52. package/dist/datetime/index.js +4 -4
  53. package/dist/datetime/index.js.map +1 -1
  54. package/dist/email/index.d.ts +89 -42
  55. package/dist/email/index.d.ts.map +1 -1
  56. package/dist/email/index.js +129 -33
  57. package/dist/email/index.js.map +1 -1
  58. package/dist/fake/index.d.ts +7969 -2
  59. package/dist/fake/index.d.ts.map +1 -1
  60. package/dist/fake/index.js +22 -22
  61. package/dist/fake/index.js.map +1 -1
  62. package/dist/file/index.d.ts +134 -1
  63. package/dist/file/index.d.ts.map +1 -1
  64. package/dist/file/index.js +253 -1
  65. package/dist/file/index.js.map +1 -1
  66. package/dist/lock/core/index.d.ts.map +1 -1
  67. package/dist/lock/redis/index.d.ts.map +1 -1
  68. package/dist/logger/index.d.ts +1 -2
  69. package/dist/logger/index.d.ts.map +1 -1
  70. package/dist/logger/index.js +1 -5
  71. package/dist/logger/index.js.map +1 -1
  72. package/dist/mcp/index.d.ts +19 -1
  73. package/dist/mcp/index.d.ts.map +1 -1
  74. package/dist/mcp/index.js +28 -4
  75. package/dist/mcp/index.js.map +1 -1
  76. package/dist/orm/chunk-DH6iiROE.js +38 -0
  77. package/dist/orm/index.browser.js +9 -9
  78. package/dist/orm/index.browser.js.map +1 -1
  79. package/dist/orm/index.bun.js +2821 -0
  80. package/dist/orm/index.bun.js.map +1 -0
  81. package/dist/orm/index.d.ts +318 -169
  82. package/dist/orm/index.d.ts.map +1 -1
  83. package/dist/orm/index.js +2086 -1776
  84. package/dist/orm/index.js.map +1 -1
  85. package/dist/queue/core/index.d.ts +4 -4
  86. package/dist/queue/core/index.d.ts.map +1 -1
  87. package/dist/queue/redis/index.d.ts.map +1 -1
  88. package/dist/redis/index.bun.js +285 -0
  89. package/dist/redis/index.bun.js.map +1 -0
  90. package/dist/redis/index.d.ts +13 -31
  91. package/dist/redis/index.d.ts.map +1 -1
  92. package/dist/redis/index.js +18 -38
  93. package/dist/redis/index.js.map +1 -1
  94. package/dist/retry/index.d.ts.map +1 -1
  95. package/dist/router/index.d.ts.map +1 -1
  96. package/dist/scheduler/index.d.ts +83 -1
  97. package/dist/scheduler/index.d.ts.map +1 -1
  98. package/dist/scheduler/index.js +393 -1
  99. package/dist/scheduler/index.js.map +1 -1
  100. package/dist/security/index.browser.js +5 -1
  101. package/dist/security/index.browser.js.map +1 -1
  102. package/dist/security/index.d.ts +598 -112
  103. package/dist/security/index.d.ts.map +1 -1
  104. package/dist/security/index.js +1808 -97
  105. package/dist/security/index.js.map +1 -1
  106. package/dist/server/auth/index.d.ts +1200 -175
  107. package/dist/server/auth/index.d.ts.map +1 -1
  108. package/dist/server/auth/index.js +1268 -37
  109. package/dist/server/auth/index.js.map +1 -1
  110. package/dist/server/cache/index.d.ts +6 -3
  111. package/dist/server/cache/index.d.ts.map +1 -1
  112. package/dist/server/cache/index.js +1 -1
  113. package/dist/server/cache/index.js.map +1 -1
  114. package/dist/server/compress/index.d.ts.map +1 -1
  115. package/dist/server/cookies/index.d.ts.map +1 -1
  116. package/dist/server/cookies/index.js +3 -3
  117. package/dist/server/cookies/index.js.map +1 -1
  118. package/dist/server/core/index.d.ts +115 -13
  119. package/dist/server/core/index.d.ts.map +1 -1
  120. package/dist/server/core/index.js +321 -139
  121. package/dist/server/core/index.js.map +1 -1
  122. package/dist/server/cors/index.d.ts +0 -1
  123. package/dist/server/cors/index.d.ts.map +1 -1
  124. package/dist/server/health/index.d.ts +0 -1
  125. package/dist/server/health/index.d.ts.map +1 -1
  126. package/dist/server/helmet/index.d.ts.map +1 -1
  127. package/dist/server/links/index.browser.js +9 -1
  128. package/dist/server/links/index.browser.js.map +1 -1
  129. package/dist/server/links/index.d.ts +1 -2
  130. package/dist/server/links/index.d.ts.map +1 -1
  131. package/dist/server/links/index.js +14 -7
  132. package/dist/server/links/index.js.map +1 -1
  133. package/dist/server/metrics/index.d.ts +514 -1
  134. package/dist/server/metrics/index.d.ts.map +1 -1
  135. package/dist/server/metrics/index.js +4462 -4
  136. package/dist/server/metrics/index.js.map +1 -1
  137. package/dist/server/multipart/index.d.ts.map +1 -1
  138. package/dist/server/proxy/index.d.ts +0 -1
  139. package/dist/server/proxy/index.d.ts.map +1 -1
  140. package/dist/server/rate-limit/index.d.ts.map +1 -1
  141. package/dist/server/static/index.d.ts.map +1 -1
  142. package/dist/server/swagger/index.d.ts +1 -2
  143. package/dist/server/swagger/index.d.ts.map +1 -1
  144. package/dist/server/swagger/index.js +1 -2
  145. package/dist/server/swagger/index.js.map +1 -1
  146. package/dist/sms/index.d.ts +3 -1
  147. package/dist/sms/index.d.ts.map +1 -1
  148. package/dist/sms/index.js +10 -10
  149. package/dist/sms/index.js.map +1 -1
  150. package/dist/thread/index.d.ts +0 -1
  151. package/dist/thread/index.d.ts.map +1 -1
  152. package/dist/thread/index.js +2 -2
  153. package/dist/thread/index.js.map +1 -1
  154. package/dist/topic/core/index.d.ts.map +1 -1
  155. package/dist/topic/redis/index.d.ts.map +1 -1
  156. package/dist/vite/index.d.ts +6315 -149
  157. package/dist/vite/index.d.ts.map +1 -1
  158. package/dist/vite/index.js +140 -469
  159. package/dist/vite/index.js.map +1 -1
  160. package/dist/websocket/index.browser.js +9 -9
  161. package/dist/websocket/index.browser.js.map +1 -1
  162. package/dist/websocket/index.d.ts +28 -28
  163. package/dist/websocket/index.d.ts.map +1 -1
  164. package/dist/websocket/index.js +9 -9
  165. package/dist/websocket/index.js.map +1 -1
  166. package/package.json +13 -18
  167. package/src/api/files/controllers/AdminFileStatsController.ts +0 -1
  168. package/src/api/users/atoms/realmAuthSettingsAtom.ts +5 -0
  169. package/src/api/users/controllers/{UserRealmController.ts → RealmController.ts} +11 -11
  170. package/src/api/users/entities/users.ts +1 -1
  171. package/src/api/users/index.ts +8 -8
  172. package/src/api/users/primitives/{$userRealm.ts → $realm.ts} +17 -19
  173. package/src/api/users/providers/{UserRealmProvider.ts → RealmProvider.ts} +26 -30
  174. package/src/api/users/schemas/{userRealmConfigSchema.ts → realmConfigSchema.ts} +2 -2
  175. package/src/api/users/services/CredentialService.ts +7 -7
  176. package/src/api/users/services/IdentityService.ts +4 -4
  177. package/src/api/users/services/RegistrationService.spec.ts +25 -27
  178. package/src/api/users/services/RegistrationService.ts +38 -27
  179. package/src/api/users/services/SessionCrudService.ts +3 -3
  180. package/src/api/users/services/SessionService.spec.ts +3 -3
  181. package/src/api/users/services/SessionService.ts +27 -18
  182. package/src/api/users/services/UserService.ts +7 -7
  183. package/src/batch/providers/BatchProvider.ts +1 -2
  184. package/src/cli/apps/AlephaCli.ts +2 -2
  185. package/src/cli/apps/AlephaPackageBuilderCli.ts +47 -20
  186. package/src/cli/assets/apiHelloControllerTs.ts +19 -0
  187. package/src/cli/assets/apiIndexTs.ts +16 -0
  188. package/src/cli/assets/biomeJson.ts +2 -1
  189. package/src/cli/assets/claudeMd.ts +308 -0
  190. package/src/cli/assets/dummySpecTs.ts +2 -1
  191. package/src/cli/assets/editorconfig.ts +2 -1
  192. package/src/cli/assets/mainBrowserTs.ts +4 -3
  193. package/src/cli/assets/mainCss.ts +24 -0
  194. package/src/cli/assets/mainServerTs.ts +24 -0
  195. package/src/cli/assets/tsconfigJson.ts +2 -1
  196. package/src/cli/assets/webAppRouterTs.ts +16 -0
  197. package/src/cli/assets/webHelloComponentTsx.ts +20 -0
  198. package/src/cli/assets/webIndexTs.ts +16 -0
  199. package/src/cli/atoms/appEntryOptions.ts +13 -0
  200. package/src/cli/atoms/buildOptions.ts +1 -1
  201. package/src/cli/atoms/changelogOptions.ts +1 -1
  202. package/src/cli/commands/build.ts +97 -61
  203. package/src/cli/commands/db.ts +21 -18
  204. package/src/cli/commands/deploy.ts +17 -5
  205. package/src/cli/commands/dev.ts +26 -47
  206. package/src/cli/commands/gen/env.ts +1 -1
  207. package/src/cli/commands/init.ts +79 -25
  208. package/src/cli/commands/lint.ts +9 -3
  209. package/src/cli/commands/test.ts +8 -2
  210. package/src/cli/commands/typecheck.ts +5 -1
  211. package/src/cli/commands/verify.ts +4 -2
  212. package/src/cli/defineConfig.ts +9 -0
  213. package/src/cli/index.ts +2 -1
  214. package/src/cli/providers/AppEntryProvider.ts +131 -0
  215. package/src/cli/providers/ViteBuildProvider.ts +82 -0
  216. package/src/cli/providers/ViteDevServerProvider.ts +350 -0
  217. package/src/cli/providers/ViteTemplateProvider.ts +27 -0
  218. package/src/cli/services/AlephaCliUtils.ts +72 -602
  219. package/src/cli/services/PackageManagerUtils.ts +308 -0
  220. package/src/cli/services/ProjectScaffolder.ts +329 -0
  221. package/src/command/helpers/Runner.ts +15 -3
  222. package/src/core/Alepha.ts +2 -8
  223. package/src/core/__tests__/Alepha-graph.spec.ts +4 -0
  224. package/src/core/index.shared.ts +1 -0
  225. package/src/core/index.ts +2 -0
  226. package/src/core/primitives/$hook.ts +6 -2
  227. package/src/core/primitives/$module.spec.ts +4 -0
  228. package/src/core/primitives/$module.ts +12 -0
  229. package/src/core/providers/AlsProvider.ts +1 -1
  230. package/src/core/providers/CodecManager.spec.ts +12 -6
  231. package/src/core/providers/CodecManager.ts +26 -6
  232. package/src/core/providers/EventManager.ts +169 -13
  233. package/src/core/providers/KeylessJsonSchemaCodec.spec.ts +878 -0
  234. package/src/core/providers/KeylessJsonSchemaCodec.ts +789 -0
  235. package/src/core/providers/SchemaValidator.spec.ts +236 -0
  236. package/src/core/providers/StateManager.spec.ts +27 -16
  237. package/src/email/providers/LocalEmailProvider.spec.ts +111 -87
  238. package/src/email/providers/LocalEmailProvider.ts +52 -15
  239. package/src/email/providers/NodemailerEmailProvider.ts +167 -56
  240. package/src/file/errors/FileError.ts +7 -0
  241. package/src/file/index.ts +9 -1
  242. package/src/file/providers/MemoryFileSystemProvider.ts +393 -0
  243. package/src/logger/providers/PrettyFormatterProvider.ts +0 -9
  244. package/src/mcp/errors/McpError.ts +30 -0
  245. package/src/mcp/index.ts +3 -0
  246. package/src/mcp/transports/SseMcpTransport.ts +16 -6
  247. package/src/orm/index.browser.ts +1 -19
  248. package/src/orm/index.bun.ts +77 -0
  249. package/src/orm/index.shared-server.ts +22 -0
  250. package/src/orm/index.shared.ts +15 -0
  251. package/src/orm/index.ts +19 -39
  252. package/src/orm/providers/DrizzleKitProvider.ts +3 -5
  253. package/src/orm/providers/drivers/BunPostgresProvider.ts +3 -5
  254. package/src/orm/providers/drivers/BunSqliteProvider.ts +1 -1
  255. package/src/orm/providers/drivers/CloudflareD1Provider.ts +4 -0
  256. package/src/orm/providers/drivers/DatabaseProvider.ts +4 -0
  257. package/src/orm/providers/drivers/PglitePostgresProvider.ts +4 -0
  258. package/src/orm/services/Repository.ts +19 -0
  259. package/src/redis/index.bun.ts +35 -0
  260. package/src/redis/providers/BunRedisProvider.ts +12 -43
  261. package/src/redis/providers/BunRedisSubscriberProvider.ts +2 -3
  262. package/src/redis/providers/NodeRedisProvider.ts +16 -34
  263. package/src/{server/security → security}/__tests__/BasicAuth.spec.ts +11 -11
  264. package/src/{server/security → security}/__tests__/ServerSecurityProvider-realm.spec.ts +21 -16
  265. package/src/{server/security/providers → security/__tests__}/ServerSecurityProvider.spec.ts +5 -5
  266. package/src/security/index.browser.ts +5 -0
  267. package/src/security/index.ts +90 -7
  268. package/src/security/primitives/{$realm.spec.ts → $issuer.spec.ts} +11 -11
  269. package/src/security/primitives/{$realm.ts → $issuer.ts} +20 -17
  270. package/src/security/primitives/$role.ts +5 -5
  271. package/src/security/primitives/$serviceAccount.spec.ts +5 -5
  272. package/src/security/primitives/$serviceAccount.ts +3 -3
  273. package/src/{server/security → security}/providers/ServerSecurityProvider.ts +5 -7
  274. package/src/server/auth/primitives/$auth.ts +10 -10
  275. package/src/server/auth/primitives/$authCredentials.ts +3 -3
  276. package/src/server/auth/primitives/$authGithub.ts +3 -3
  277. package/src/server/auth/primitives/$authGoogle.ts +3 -3
  278. package/src/server/auth/providers/ServerAuthProvider.ts +13 -13
  279. package/src/server/cache/providers/ServerCacheProvider.ts +1 -1
  280. package/src/server/cookies/providers/ServerCookiesProvider.ts +3 -3
  281. package/src/server/core/index.ts +1 -1
  282. package/src/server/core/providers/BunHttpServerProvider.ts +1 -1
  283. package/src/server/core/providers/NodeHttpServerProvider.spec.ts +125 -0
  284. package/src/server/core/providers/NodeHttpServerProvider.ts +92 -24
  285. package/src/server/core/providers/ServerBodyParserProvider.ts +19 -23
  286. package/src/server/core/providers/ServerLoggerProvider.ts +23 -19
  287. package/src/server/core/providers/ServerProvider.ts +144 -24
  288. package/src/server/core/providers/ServerRouterProvider.ts +259 -115
  289. package/src/server/core/providers/ServerTimingProvider.ts +2 -2
  290. package/src/server/links/atoms/apiLinksAtom.ts +7 -0
  291. package/src/server/links/index.browser.ts +2 -0
  292. package/src/server/links/index.ts +3 -1
  293. package/src/server/links/providers/LinkProvider.ts +1 -1
  294. package/src/server/swagger/index.ts +1 -1
  295. package/src/sms/providers/LocalSmsProvider.spec.ts +153 -111
  296. package/src/sms/providers/LocalSmsProvider.ts +8 -7
  297. package/src/vite/index.ts +3 -2
  298. package/src/vite/tasks/buildClient.ts +0 -1
  299. package/src/vite/tasks/buildServer.ts +80 -22
  300. package/src/vite/tasks/copyAssets.ts +5 -4
  301. package/src/vite/tasks/generateCloudflare.ts +7 -0
  302. package/src/vite/tasks/generateSitemap.ts +64 -23
  303. package/src/vite/tasks/index.ts +0 -2
  304. package/src/vite/tasks/prerenderPages.ts +49 -24
  305. package/dist/server/security/index.browser.js +0 -13
  306. package/dist/server/security/index.browser.js.map +0 -1
  307. package/dist/server/security/index.d.ts +0 -173
  308. package/dist/server/security/index.d.ts.map +0 -1
  309. package/dist/server/security/index.js +0 -311
  310. package/dist/server/security/index.js.map +0 -1
  311. package/src/cli/assets/appRouterTs.ts +0 -9
  312. package/src/cli/assets/indexHtml.ts +0 -15
  313. package/src/cli/assets/mainTs.ts +0 -13
  314. package/src/cli/commands/format.ts +0 -17
  315. package/src/server/security/index.browser.ts +0 -10
  316. package/src/server/security/index.ts +0 -94
  317. package/src/vite/helpers/boot.ts +0 -106
  318. package/src/vite/plugins/viteAlephaDev.ts +0 -177
  319. package/src/vite/tasks/devServer.ts +0 -69
  320. package/src/vite/tasks/runAlepha.ts +0 -270
  321. /package/src/{server/security → security}/primitives/$basicAuth.ts +0 -0
  322. /package/src/{server/security → security}/providers/ServerBasicAuthProvider.ts +0 -0
package/dist/cli/index.js CHANGED
@@ -1,14 +1,14 @@
1
1
  import { $atom, $hook, $inject, $module, $use, Alepha, AlephaError, t } from "alepha";
2
2
  import { FileSystemProvider } from "alepha/file";
3
- import { access, mkdir, readFile, readdir, unlink, writeFile } from "node:fs/promises";
4
- import { join } from "node:path";
5
3
  import { $command, CliProvider, EnvUtils } from "alepha/command";
6
4
  import { $logger, ConsoleColorProvider } from "alepha/logger";
7
- import { boot, buildClient, buildServer, copyAssets, devServer, generateCloudflare, generateDocker, generateSitemap, generateVercel, prerenderPages } from "alepha/vite";
5
+ import { buildClient, buildServer, copyAssets, generateCloudflare, generateDocker, generateSitemap, generateVercel, importVite, importViteReact, prerenderPages, viteAlephaSsrPreload } from "alepha/vite";
8
6
  import { exec, spawn } from "node:child_process";
9
7
  import { readFileSync } from "node:fs";
8
+ import { basename, dirname, join } from "node:path";
10
9
  import { promisify } from "node:util";
11
10
  import { ServerSwaggerProvider } from "alepha/server/swagger";
11
+ import { access, readFile, readdir } from "node:fs/promises";
12
12
  import * as os from "node:os";
13
13
 
14
14
  //#region ../../src/core/constants/KIND.ts
@@ -79,7 +79,7 @@ $atom$1[KIND] = "atom";
79
79
  * Options can be overridden via vite.config.ts or CLI flags.
80
80
  */
81
81
  const buildOptions = $atom$1({
82
- name: "alepha.build.options",
82
+ name: "alepha.cli.build.options",
83
83
  description: "Build configuration options",
84
84
  schema: t.object({
85
85
  stats: t.optional(t.boolean({ default: false })),
@@ -103,168 +103,203 @@ const buildOptions = $atom$1({
103
103
  });
104
104
 
105
105
  //#endregion
106
- //#region ../../src/cli/assets/appRouterTs.ts
107
- const appRouterTs = () => `
108
- import { $page } from "@alepha/react/router";
109
-
110
- export class AppRouter {
111
- home = $page({
112
- component: () => "Hello World",
113
- });
114
- }
115
- `.trim();
116
-
117
- //#endregion
118
- //#region ../../src/cli/assets/biomeJson.ts
119
- const biomeJson = `
120
- {
121
- "$schema": "https://biomejs.dev/schemas/latest/schema.json",
122
- "vcs": {
123
- "enabled": true,
124
- "clientKind": "git"
125
- },
126
- "files": {
127
- "ignoreUnknown": true,
128
- "includes": ["**", "!node_modules", "!dist"]
129
- },
130
- "formatter": {
131
- "enabled": true,
132
- "useEditorconfig": true
133
- },
134
- "linter": {
135
- "enabled": true,
136
- "rules": {
137
- "recommended": true
138
- },
139
- "domains": {
140
- "react": "recommended"
141
- }
142
- },
143
- "assist": {
144
- "actions": {
145
- "source": {
146
- "organizeImports": "on"
147
- }
148
- }
149
- }
150
- }
151
- `.trim();
152
-
153
- //#endregion
154
- //#region ../../src/cli/assets/dummySpecTs.ts
155
- const dummySpecTs = () => `
156
- import { test, expect } from "vitest";
157
-
158
- test("dummy test", () => {
159
- expect(1 + 1).toBe(2);
106
+ //#region ../../src/cli/atoms/appEntryOptions.ts
107
+ const appEntryOptions = $atom({
108
+ name: "alepha.cli.appEntry.options",
109
+ schema: t.object({
110
+ server: t.optional(t.text()),
111
+ browser: t.optional(t.text()),
112
+ style: t.optional(t.text())
113
+ }),
114
+ default: {}
160
115
  });
161
- `.trim();
162
116
 
163
117
  //#endregion
164
- //#region ../../src/cli/assets/editorconfig.ts
165
- const editorconfig = `
166
- # https://editorconfig.org
167
-
168
- root = true
169
-
170
- [*]
171
- charset = utf-8
172
- end_of_line = lf
173
- insert_final_newline = true
174
- trim_trailing_whitespace = true
175
- indent_style = space
176
- indent_size = 2
177
- `.trim();
118
+ //#region ../../src/cli/providers/AppEntryProvider.ts
119
+ /**
120
+ * Service for locating entry files in Alepha projects.
121
+ *
122
+ * Originally in alepha/vite, moved to CLI to avoid cli -> vite dependency.
123
+ */
124
+ var AppEntryProvider = class {
125
+ fs = $inject(FileSystemProvider);
126
+ options = $use(appEntryOptions);
127
+ serverEntries = [
128
+ "main.server.ts",
129
+ "main.server.tsx",
130
+ "main.ts",
131
+ "main.tsx"
132
+ ];
133
+ browserEntries = [
134
+ "main.browser.ts",
135
+ "main.browser.tsx",
136
+ "main.ts",
137
+ "main.tsx"
138
+ ];
139
+ styleEntries = [
140
+ "main.css",
141
+ "styles.css",
142
+ "style.css"
143
+ ];
144
+ /**
145
+ * Get application entry points.
146
+ *
147
+ * Server entry is required, an error is thrown if not found.
148
+ * Browser entry is optional.
149
+ *
150
+ * It will first check for custom entries in options, see appEntryOptions.
151
+ */
152
+ async getAppEntry(root) {
153
+ const appEntry = {
154
+ root,
155
+ server: ""
156
+ };
157
+ if (this.options.server) {
158
+ if (!await this.fs.exists(this.fs.join(root, this.options.server))) throw new AlephaError(`Custom server entry "${this.options.server}" not found.`);
159
+ appEntry.server = this.options.server;
160
+ }
161
+ if (this.options.browser) {
162
+ if (!await this.fs.exists(this.fs.join(root, this.options.browser))) throw new AlephaError(`Custom browser entry "${this.options.browser}" not found.`);
163
+ appEntry.browser = this.options.browser;
164
+ }
165
+ if (this.options.style) {
166
+ if (!await this.fs.exists(this.fs.join(root, this.options.style))) throw new AlephaError(`Custom style entry "${this.options.style}" not found.`);
167
+ appEntry.style = this.options.style;
168
+ }
169
+ const srcFiles = await this.fs.ls(this.fs.join(root, "src"));
170
+ if (!appEntry.server) {
171
+ for (const entry of this.serverEntries) if (srcFiles.includes(entry)) {
172
+ appEntry.server = this.fs.join("src", entry);
173
+ break;
174
+ }
175
+ }
176
+ if (!appEntry.server) throw new AlephaError("No server entry found. Please, add a main.server.ts file in the src/ directory or configure a custom entry in alepha.config.ts.");
177
+ if (!appEntry.browser) {
178
+ for (const entry of this.browserEntries) if (srcFiles.includes(entry)) {
179
+ appEntry.browser = this.fs.join("src", entry);
180
+ break;
181
+ }
182
+ }
183
+ if (!appEntry.style) {
184
+ for (const entry of this.styleEntries) if (srcFiles.includes(entry)) {
185
+ appEntry.style = this.fs.join("src", entry);
186
+ break;
187
+ }
188
+ }
189
+ return appEntry;
190
+ }
191
+ };
178
192
 
179
193
  //#endregion
180
- //#region ../../src/cli/assets/indexHtml.ts
181
- const indexHtml = (browserEntry) => `
194
+ //#region ../../src/cli/providers/ViteTemplateProvider.ts
195
+ var ViteTemplateProvider = class {
196
+ fs = $inject(FileSystemProvider);
197
+ generateIndexHtml(entry) {
198
+ const style = entry.style;
199
+ const browser = entry.browser ?? entry.server;
200
+ return `
182
201
  <!DOCTYPE html>
183
202
  <html lang="en">
184
203
  <head>
185
- <meta charset="UTF-8">
186
- <title>App</title>
204
+ <meta charset="UTF-8" />
205
+ <title>App</title>
206
+ <meta name="viewport" content="width=device-width, initial-scale=1"/>
207
+ ${style ? `<link rel="stylesheet" href="/${style}" />` : ""}
187
208
  </head>
188
209
  <body>
189
210
  <div id="root"></div>
190
- <script type="module" src="${browserEntry}"><\/script>
211
+ <script type="module" src="/${browser}"><\/script>
191
212
  </body>
192
213
  </html>
193
214
  `.trim();
215
+ }
216
+ };
194
217
 
195
218
  //#endregion
196
- //#region ../../src/cli/assets/mainBrowserTs.ts
197
- const mainBrowserTs = () => `
198
- import { Alepha, run } from "alepha";
199
- import { AppRouter } from "./AppRouter.ts";
200
-
201
- const alepha = Alepha.create();
202
-
203
- alepha.with(AppRouter);
204
-
205
- run(alepha);
206
- `.trim();
207
-
208
- //#endregion
209
- //#region ../../src/cli/assets/mainTs.ts
210
- const mainTs = () => `
211
- import { run } from "alepha";
212
- import { $route } from "alepha/server";
213
-
214
- class App {
215
- root = $route({
216
- path: "/",
217
- handler: () => "Hello, Alepha!",
218
- });
219
- }
220
-
221
- run(App);
222
- `.trim();
223
-
224
- //#endregion
225
- //#region ../../src/cli/assets/tsconfigJson.ts
226
- const tsconfigJson = `
227
- {
228
- "extends": "alepha/tsconfig.base"
229
- }
230
- `.trim();
231
-
232
- //#endregion
233
- //#region ../../src/cli/version.ts
234
- const packageJson = JSON.parse(readFileSync(new URL("../../package.json", import.meta.url), "utf-8"));
235
- const version = packageJson.version;
219
+ //#region ../../src/cli/providers/ViteBuildProvider.ts
220
+ var ViteBuildProvider = class {
221
+ alepha;
222
+ appEntry;
223
+ viteDevServer;
224
+ templateProvider = $inject(ViteTemplateProvider);
225
+ /**
226
+ * We need to close the Vite dev server after build is done.
227
+ */
228
+ onReady = $hook({
229
+ on: "ready",
230
+ priority: "last",
231
+ handler: async () => {
232
+ await this.viteDevServer?.close();
233
+ }
234
+ });
235
+ onStop = $hook({
236
+ on: "stop",
237
+ handler: async () => {
238
+ await this.viteDevServer?.close();
239
+ }
240
+ });
241
+ async init(opts) {
242
+ const { createServer } = await importVite();
243
+ process.env.ALEPHA_CLI_IMPORT = "true";
244
+ process.env.NODE_ENV = "production";
245
+ process.env.LOG_LEVEL ??= "warn";
246
+ /**
247
+ * 01/26 Vite 7
248
+ * "runnerImport" doesn't work as expected here. (e.g. build docs fail)
249
+ * -> We still use devServer and ssrLoadModule for now.
250
+ * -> This is clearly a bad stuff, we need to find better way.
251
+ */
252
+ this.viteDevServer = await createServer({
253
+ server: { middlewareMode: true },
254
+ appType: "custom",
255
+ logLevel: "silent"
256
+ });
257
+ await this.viteDevServer.ssrLoadModule(opts.entry.server);
258
+ const alepha = globalThis.__alepha;
259
+ if (!alepha) throw new AlephaError("Alepha instance not found after loading entry module");
260
+ this.alepha = alepha;
261
+ this.appEntry = opts.entry;
262
+ return alepha;
263
+ }
264
+ hasClient() {
265
+ if (!this.alepha) throw new AlephaError("ViteBuildProvider not initialized");
266
+ try {
267
+ this.alepha.inject("ReactServerProvider");
268
+ return true;
269
+ } catch {
270
+ return false;
271
+ }
272
+ }
273
+ generateIndexHtml() {
274
+ if (!this.appEntry) throw new AlephaError("ViteBuildProvider not initialized");
275
+ return this.templateProvider.generateIndexHtml(this.appEntry);
276
+ }
277
+ };
236
278
 
237
279
  //#endregion
238
280
  //#region ../../src/cli/services/AlephaCliUtils.ts
239
281
  /**
240
- * Utility service for common project operations used by CLI commands.
282
+ * Core utility service for CLI commands.
241
283
  *
242
- * This service provides helper methods for:
243
- * - Project configuration file management (tsconfig.json, package.json, etc.)
244
- * - Package manager setup (Yarn, npm, pnpm)
245
- * - Sample project downloading
246
- * - Drizzle ORM/Kit utilities
247
- * - Alepha instance loading
284
+ * Provides:
285
+ * - Command execution
286
+ * - File editing helpers
287
+ * - Drizzle/ORM utilities
288
+ * - Environment loading
248
289
  */
249
290
  var AlephaCliUtils = class {
250
291
  log = $logger();
251
292
  fs = $inject(FileSystemProvider);
252
293
  envUtils = $inject(EnvUtils);
253
- alepha = $inject(Alepha);
294
+ boot = $inject(AppEntryProvider);
254
295
  /**
255
- * Execute a command using npx with inherited stdio.
256
- *
257
- * @example
258
- * ```ts
259
- * const runner = alepha.inject(ProcessRunner);
260
- * await runner.exec("tsx watch src/index.ts");
261
- * ```
296
+ * Execute a command with inherited stdio.
262
297
  */
263
298
  async exec(command, options = {}) {
264
- const root = process.cwd();
299
+ const root = options.root ?? process.cwd();
265
300
  this.log.debug(`Executing command: ${command}`, { cwd: root });
266
- const runExec = async (app$1, args$1) => {
267
- const prog = spawn(app$1, args$1, {
301
+ const runExec = async (app, args) => {
302
+ const prog = spawn(app, args, {
268
303
  stdio: "inherit",
269
304
  cwd: root,
270
305
  env: {
@@ -277,120 +312,276 @@ var AlephaCliUtils = class {
277
312
  }));
278
313
  };
279
314
  if (options.global) {
280
- const [app$1, ...args$1] = command.split(" ");
281
- await runExec(app$1, args$1);
315
+ const [app, ...args] = command.split(" ");
316
+ await runExec(app, args);
282
317
  return;
283
318
  }
284
319
  const suffix = process.platform === "win32" ? ".cmd" : "";
285
320
  const [app, ...args] = command.split(" ");
286
- let execPath = await this.checkFileExists(root, `node_modules/.bin/${app}${suffix}`, true);
287
- if (!execPath) execPath = await this.checkFileExists(root, `node_modules/alepha/node_modules/.bin/${app}${suffix}`, true);
321
+ let execPath = await this.checkFileExists(root, `node_modules/.bin/${app}${suffix}`);
322
+ if (!execPath) execPath = await this.checkFileExists(root, `node_modules/alepha/node_modules/.bin/${app}${suffix}`);
323
+ if (!execPath) {
324
+ let parentDir = this.fs.join(root, "..");
325
+ for (let i = 0; i < 3; i++) {
326
+ execPath = await this.checkFileExists(parentDir, `node_modules/.bin/${app}${suffix}`);
327
+ if (execPath) break;
328
+ parentDir = this.fs.join(parentDir, "..");
329
+ }
330
+ }
288
331
  if (!execPath) throw new AlephaError(`Could not find executable for command '${app}'. Make sure the package is installed.`);
289
332
  await runExec(execPath, args);
290
333
  }
291
334
  /**
292
335
  * Write a configuration file to node_modules/.alepha directory.
293
- *
294
- * Creates the .alepha directory if it doesn't exist and writes the file with the given content.
295
- *
296
- * @param name - The name of the config file to create
297
- * @param content - The content to write to the file
298
- * @param root - The root directory (defaults to process.cwd())
299
- * @returns The absolute path to the created file
300
- *
301
- * @example
302
- * ```ts
303
- * const runner = alepha.inject(ProcessRunner);
304
- * const configPath = await runner.writeConfigFile("biome.json", biomeConfig);
305
- * ```
306
336
  */
307
337
  async writeConfigFile(name, content, root = process.cwd()) {
308
- const dir = join(root, "node_modules", ".alepha");
309
- await mkdir(dir, { recursive: true }).catch(() => null);
310
- const path = join(dir, name);
311
- await writeFile(path, content);
338
+ const dir = this.fs.join(root, "node_modules", ".alepha");
339
+ await this.fs.mkdir(dir, { recursive: true }).catch(() => null);
340
+ const path = this.fs.join(dir, name);
341
+ await this.fs.writeFile(path, content);
312
342
  this.log.debug(`Config file written: ${path}`);
313
343
  return path;
314
344
  }
315
- async editFile(root, name, editFn) {
316
- const filePath = join(root, name);
317
- try {
318
- await writeFile(filePath, await editFn(await readFile(filePath, "utf8")));
319
- } catch (error) {
320
- this.log.debug("Could not edit file", error);
345
+ /**
346
+ * Load Alepha instance from a server entry file.
347
+ */
348
+ async loadAlephaFromServerEntryFile(rootDir, explicitEntry) {
349
+ process.env.ALEPHA_CLI_IMPORT = "true";
350
+ const root = rootDir ?? process.cwd();
351
+ let entry;
352
+ if (explicitEntry) {
353
+ entry = this.fs.join(root, explicitEntry);
354
+ if (!await this.fs.exists(entry)) throw new AlephaError(`Explicit server entry file "${explicitEntry}" not found.`);
355
+ } else {
356
+ const appEntry = await this.boot.getAppEntry(root);
357
+ entry = this.fs.join(root, appEntry.server);
321
358
  }
322
- }
323
- async editJsonFile(root, name, editFn) {
324
- return await this.editFile(root, name, async (content) => {
325
- const newObj = await editFn(JSON.parse(content));
326
- return JSON.stringify(newObj, null, 2);
327
- });
328
- }
329
- async removeFiles(root, files) {
330
- await Promise.all(files.map((file) => this.fs.rm(join(root, file), {
331
- force: true,
332
- recursive: true
333
- })));
334
- }
335
- async removeYarn(root) {
336
- await this.removeFiles(root, [
337
- ".yarn",
338
- ".yarnrc.yml",
339
- "yarn.lock"
340
- ]);
341
- await this.editJsonFile(root, "package.json", (pkg) => {
342
- delete pkg.packageManager;
343
- });
344
- }
345
- async removePnpm(root) {
346
- await this.removeFiles(root, ["pnpm-lock.yaml", "pnpm-workspace.yaml"]);
347
- await this.editJsonFile(root, "package.json", (pkg) => {
348
- delete pkg.packageManager;
349
- });
350
- }
351
- async removeNpm(root) {
352
- await this.removeFiles(root, ["package-lock.json"]);
353
- }
354
- async removeBun(root) {
355
- await this.removeFiles(root, ["bun.lockb", "bun.lock"]);
356
- }
357
- async removeAllPmFilesExcept(root, except) {
358
- if (except !== "yarn") await this.removeYarn(root);
359
- if (except !== "pnpm") await this.removePnpm(root);
360
- if (except !== "npm") await this.removeNpm(root);
361
- if (except !== "bun") await this.removeBun(root);
359
+ delete global.__alepha;
360
+ const mod = await import(entry);
361
+ this.log.debug(`Load entry: ${entry}`);
362
+ if (mod.default instanceof Alepha) return {
363
+ alepha: mod.default,
364
+ entry
365
+ };
366
+ const g = global;
367
+ if (g.__alepha) return {
368
+ alepha: g.__alepha,
369
+ entry
370
+ };
371
+ throw new AlephaError(`Could not find Alepha instance in entry file: ${entry}`);
362
372
  }
363
373
  /**
364
- * Ensure Yarn is configured in the project directory.
365
- *
366
- * Creates a .yarnrc.yml file with node-modules linker if it doesn't exist.
367
- *
368
- * @param root - The root directory of the project
374
+ * Generate JavaScript code for Drizzle entities export.
369
375
  */
370
- async ensureYarn(root) {
371
- await this.ensureFileExists(root, ".yarnrc.yml", "nodeLinker: node-modules", false);
372
- await this.removeAllPmFilesExcept(root, "yarn");
376
+ generateEntitiesJs(entry, provider, models = []) {
377
+ return `
378
+ import "${entry}";
379
+ import { DrizzleKitProvider, Repository } from "alepha/orm";
380
+
381
+ const alepha = globalThis.__alepha;
382
+ const kit = alepha.inject(DrizzleKitProvider);
383
+ const provider = alepha.services(Repository).find((it) => it.provider.name === "${provider}").provider;
384
+ const models = kit.getModels(provider);
385
+
386
+ ${models.map((it) => `export const ${it} = models["${it}"];`).join("\n")}
387
+
388
+ `.trim();
373
389
  }
374
- async ensureBun(root) {
375
- await this.removeAllPmFilesExcept(root, "bun");
390
+ /**
391
+ * Load environment variables from a .env file.
392
+ */
393
+ async loadEnv(root, files = [".env"]) {
394
+ await this.envUtils.loadEnv(root, files);
376
395
  }
377
- async ensurePnpm(root) {
378
- await this.removeAllPmFilesExcept(root, "pnpm");
396
+ async exists(root, path) {
397
+ return this.fs.exists(this.fs.join(root, path));
379
398
  }
380
- async ensureNpm(root) {
381
- await this.removeAllPmFilesExcept(root, "npm");
399
+ async checkFileExists(root, name) {
400
+ const configPath = this.fs.join(root, name);
401
+ if (await this.fs.exists(configPath)) return configPath;
382
402
  }
403
+ };
404
+
405
+ //#endregion
406
+ //#region ../../src/cli/version.ts
407
+ const packageJson = JSON.parse(readFileSync(new URL("../../package.json", import.meta.url), "utf-8"));
408
+ const version = packageJson.version;
409
+
410
+ //#endregion
411
+ //#region ../../src/cli/services/PackageManagerUtils.ts
412
+ /**
413
+ * Utility service for package manager operations.
414
+ *
415
+ * Handles detection, installation, and cleanup for:
416
+ * - Yarn
417
+ * - npm
418
+ * - pnpm
419
+ * - Bun
420
+ */
421
+ var PackageManagerUtils = class {
422
+ log = $logger();
423
+ fs = $inject(FileSystemProvider);
424
+ alepha = $inject(Alepha);
383
425
  /**
384
- * Generate package.json content with Alepha dependencies.
385
- *
386
- * @param modes - Configuration for which dependencies to include
387
- * @returns Package.json partial with dependencies, devDependencies, and scripts
426
+ * Detect the package manager used in the project.
388
427
  */
389
- generatePackageJsonContent(modes) {
390
- const dependencies = { alepha: `^${version}` };
391
- const devDependencies = {};
392
- const scripts = {
393
- dev: "alepha dev",
428
+ async getPackageManager(root, flags) {
429
+ if (flags?.yarn) return "yarn";
430
+ if (flags?.pnpm) return "pnpm";
431
+ if (flags?.npm) return "npm";
432
+ if (flags?.bun) return "bun";
433
+ if (this.alepha.isBun()) return "bun";
434
+ if (await this.fs.exists(this.fs.join(root, "bun.lock"))) return "bun";
435
+ if (await this.fs.exists(this.fs.join(root, "yarn.lock"))) return "yarn";
436
+ if (await this.fs.exists(this.fs.join(root, "pnpm-lock.yaml"))) return "pnpm";
437
+ return "npm";
438
+ }
439
+ /**
440
+ * Get the install command for a package.
441
+ */
442
+ async getInstallCommand(root, packageName, dev = true) {
443
+ const pm = await this.getPackageManager(root);
444
+ let cmd;
445
+ switch (pm) {
446
+ case "yarn":
447
+ cmd = `yarn add ${dev ? "-D" : ""} ${packageName}`;
448
+ break;
449
+ case "pnpm":
450
+ cmd = `pnpm add ${dev ? "-D" : ""} ${packageName}`;
451
+ break;
452
+ case "bun":
453
+ cmd = `bun add ${dev ? "-d" : ""} ${packageName}`;
454
+ break;
455
+ default: cmd = `npm install ${dev ? "--save-dev" : ""} ${packageName}`;
456
+ }
457
+ return cmd.replace(/\s+/g, " ").trim();
458
+ }
459
+ /**
460
+ * Check if a dependency is installed in the project.
461
+ */
462
+ async hasDependency(root, packageName) {
463
+ try {
464
+ const pkg = await this.readPackageJson(root);
465
+ return !!(pkg.dependencies?.[packageName] || pkg.devDependencies?.[packageName]);
466
+ } catch {
467
+ return false;
468
+ }
469
+ }
470
+ /**
471
+ * Check if Expo is present in the project.
472
+ */
473
+ async hasExpo(root) {
474
+ return this.hasDependency(root, "expo");
475
+ }
476
+ /**
477
+ * Check if React is present in the project.
478
+ */
479
+ async hasReact(root) {
480
+ return this.hasDependency(root, "react");
481
+ }
482
+ /**
483
+ * Install a dependency if it's missing from the project.
484
+ */
485
+ async ensureDependency(root, packageName, options = {}) {
486
+ const { dev = true } = options;
487
+ if (await this.hasDependency(root, packageName)) {
488
+ this.log.debug(`Dependency '${packageName}' is already installed`);
489
+ return;
490
+ }
491
+ const cmd = await this.getInstallCommand(root, packageName, dev);
492
+ if (options.run) await options.run(cmd, {
493
+ alias: `add ${packageName}`,
494
+ root
495
+ });
496
+ else if (options.exec) {
497
+ this.log.debug(`Installing ${packageName}`);
498
+ await options.exec(cmd, {
499
+ global: true,
500
+ root
501
+ });
502
+ }
503
+ }
504
+ async ensureYarn(root) {
505
+ const yarnrcPath = this.fs.join(root, ".yarnrc.yml");
506
+ if (!await this.fs.exists(yarnrcPath)) await this.fs.writeFile(yarnrcPath, "nodeLinker: node-modules");
507
+ await this.removeAllPmFilesExcept(root, "yarn");
508
+ }
509
+ async ensureBun(root) {
510
+ await this.removeAllPmFilesExcept(root, "bun");
511
+ }
512
+ async ensurePnpm(root) {
513
+ await this.removeAllPmFilesExcept(root, "pnpm");
514
+ }
515
+ async ensureNpm(root) {
516
+ await this.removeAllPmFilesExcept(root, "npm");
517
+ }
518
+ async removeAllPmFilesExcept(root, except) {
519
+ if (except !== "yarn") await this.removeYarn(root);
520
+ if (except !== "pnpm") await this.removePnpm(root);
521
+ if (except !== "npm") await this.removeNpm(root);
522
+ if (except !== "bun") await this.removeBun(root);
523
+ }
524
+ async removeYarn(root) {
525
+ await this.removeFiles(root, [
526
+ ".yarn",
527
+ ".yarnrc.yml",
528
+ "yarn.lock"
529
+ ]);
530
+ await this.editPackageJson(root, (pkg) => {
531
+ delete pkg.packageManager;
532
+ return pkg;
533
+ });
534
+ }
535
+ async removePnpm(root) {
536
+ await this.removeFiles(root, ["pnpm-lock.yaml", "pnpm-workspace.yaml"]);
537
+ await this.editPackageJson(root, (pkg) => {
538
+ delete pkg.packageManager;
539
+ return pkg;
540
+ });
541
+ }
542
+ async removeNpm(root) {
543
+ await this.removeFiles(root, ["package-lock.json"]);
544
+ }
545
+ async removeBun(root) {
546
+ await this.removeFiles(root, ["bun.lockb", "bun.lock"]);
547
+ }
548
+ async readPackageJson(root) {
549
+ const content = await this.fs.createFile({ path: this.fs.join(root, "package.json") }).text();
550
+ return JSON.parse(content);
551
+ }
552
+ async writePackageJson(root, content) {
553
+ await this.fs.writeFile(this.fs.join(root, "package.json"), JSON.stringify(content, null, 2));
554
+ }
555
+ async editPackageJson(root, editFn) {
556
+ try {
557
+ const updated = editFn(await this.readPackageJson(root));
558
+ await this.writePackageJson(root, updated);
559
+ } catch {}
560
+ }
561
+ async ensurePackageJson(root, modes) {
562
+ const packageJsonPath = this.fs.join(root, "package.json");
563
+ if (!await this.fs.exists(packageJsonPath)) {
564
+ const content = this.generatePackageJsonContent(modes);
565
+ await this.writePackageJson(root, content);
566
+ return content;
567
+ }
568
+ const packageJson = await this.readPackageJson(root);
569
+ const newContent = this.generatePackageJsonContent(modes);
570
+ packageJson.type = "module";
571
+ packageJson.dependencies ??= {};
572
+ packageJson.devDependencies ??= {};
573
+ packageJson.scripts ??= {};
574
+ Object.assign(packageJson.dependencies, newContent.dependencies);
575
+ Object.assign(packageJson.devDependencies, newContent.devDependencies);
576
+ Object.assign(packageJson.scripts, newContent.scripts);
577
+ await this.writePackageJson(root, packageJson);
578
+ return packageJson;
579
+ }
580
+ generatePackageJsonContent(modes) {
581
+ const dependencies = { alepha: `^${version}` };
582
+ const devDependencies = {};
583
+ const scripts = {
584
+ dev: "alepha dev",
394
585
  build: "alepha build",
395
586
  lint: "alepha lint",
396
587
  typecheck: "alepha typecheck",
@@ -413,220 +604,626 @@ var AlephaCliUtils = class {
413
604
  scripts
414
605
  };
415
606
  }
416
- /**
417
- * Ensure package.json exists and has correct configuration.
418
- *
419
- * Creates a new package.json if none exists, or updates an existing one to:
420
- * - Set "type": "module"
421
- * - Add Alepha dependencies
422
- * - Add standard scripts
423
- *
424
- * @param root - The root directory of the project
425
- * @param modes - Configuration for which dependencies to include
426
- */
427
- async ensurePackageJson(root, modes) {
428
- const packageJsonPath = join(root, "package.json");
429
- try {
430
- await access(packageJsonPath);
431
- } catch (error) {
432
- const obj = this.generatePackageJsonContent(modes);
433
- await writeFile(packageJsonPath, JSON.stringify(obj, null, 2));
434
- return obj;
435
- }
436
- const content = await readFile(packageJsonPath, "utf8");
437
- const packageJson$1 = JSON.parse(content);
438
- const newPackageJson = this.generatePackageJsonContent(modes);
439
- packageJson$1.type = "module";
440
- packageJson$1.dependencies ??= {};
441
- packageJson$1.devDependencies ??= {};
442
- packageJson$1.scripts ??= {};
443
- Object.assign(packageJson$1.dependencies, newPackageJson.dependencies);
444
- Object.assign(packageJson$1.devDependencies, newPackageJson.devDependencies);
445
- Object.assign(packageJson$1.scripts, newPackageJson.scripts);
446
- await writeFile(packageJsonPath, JSON.stringify(packageJson$1, null, 2));
447
- return packageJson$1;
448
- }
449
- async ensureConfig(root, opts) {
450
- const tasks = [];
451
- if (opts.packageJson) tasks.push(this.ensurePackageJson(root, typeof opts.packageJson === "boolean" ? {} : opts.packageJson));
452
- if (opts.tsconfigJson) tasks.push(this.ensureTsConfig(root));
453
- if (opts.indexHtml) tasks.push(this.ensureIndexHtml(root));
454
- if (opts.biomeJson) tasks.push(this.ensureBiomeConfig(root));
455
- if (opts.editorconfig) tasks.push(this.ensureEditorConfig(root));
456
- return await Promise.all(tasks);
457
- }
458
- /**
459
- * Ensure tsconfig.json exists in the project.
460
- *
461
- * Creates a standard Alepha tsconfig.json if none exists.
462
- *
463
- * @param root - The root directory of the project
464
- */
465
- async ensureTsConfig(root) {
466
- await this.ensureFileExists(root, "tsconfig.json", tsconfigJson, true);
467
- }
468
- async checkFileExists(root, name, checkParentDirectories = false) {
469
- const configPath = join(root, name);
470
- if (!checkParentDirectories) try {
471
- await access(configPath);
472
- return configPath;
473
- } catch {
474
- return;
475
- }
476
- let currentDir = root;
477
- const maxIterations = 10;
478
- let level = 0;
479
- while (level < maxIterations) {
480
- try {
481
- const maybe = join(currentDir, name);
482
- await access(maybe);
483
- return maybe;
484
- } catch {
485
- const parentDir = join(currentDir, "..");
486
- if (parentDir === currentDir) break;
487
- currentDir = parentDir;
488
- }
489
- level += 1;
490
- }
491
- }
492
- async ensureFileExists(root, name, content, checkParentDirectories = false) {
493
- if (!await this.checkFileExists(root, name, checkParentDirectories)) await writeFile(join(root, name), content);
607
+ async removeFiles(root, files) {
608
+ await Promise.all(files.map((file) => this.fs.rm(this.fs.join(root, file), {
609
+ force: true,
610
+ recursive: true
611
+ })));
494
612
  }
613
+ };
614
+
615
+ //#endregion
616
+ //#region ../../src/cli/assets/apiHelloControllerTs.ts
617
+ const apiHelloControllerTs = () => `
618
+ import { t } from "alepha";
619
+ import { $action } from "alepha/server";
620
+
621
+ export class HelloController {
622
+ hello = $action({
623
+ path: "/hello",
624
+ schema: {
625
+ response: t.object({
626
+ message: t.string(),
627
+ }),
628
+ },
629
+ handler: () => ({
630
+ message: "Hello, Alepha!",
631
+ }),
632
+ });
633
+ }
634
+ `.trim();
635
+
636
+ //#endregion
637
+ //#region ../../src/cli/assets/apiIndexTs.ts
638
+ const apiIndexTs = (options = {}) => {
639
+ const { appName = "app" } = options;
640
+ return `
641
+ import { $module } from "alepha";
642
+ import { HelloController } from "./controllers/HelloController.ts";
643
+
644
+ export const ApiModule = $module({
645
+ name: "${appName}.api",
646
+ services: [HelloController],
647
+ });
648
+ `.trim();
649
+ };
650
+
651
+ //#endregion
652
+ //#region ../../src/cli/assets/biomeJson.ts
653
+ const biomeJson = () => `
654
+ {
655
+ "$schema": "https://biomejs.dev/schemas/latest/schema.json",
656
+ "vcs": {
657
+ "enabled": true,
658
+ "clientKind": "git"
659
+ },
660
+ "files": {
661
+ "ignoreUnknown": true,
662
+ "includes": ["**", "!node_modules", "!dist"]
663
+ },
664
+ "formatter": {
665
+ "enabled": true,
666
+ "useEditorconfig": true
667
+ },
668
+ "linter": {
669
+ "enabled": true,
670
+ "rules": {
671
+ "recommended": true
672
+ },
673
+ "domains": {
674
+ "react": "recommended"
675
+ }
676
+ },
677
+ "assist": {
678
+ "actions": {
679
+ "source": {
680
+ "organizeImports": "on"
681
+ }
682
+ }
683
+ }
684
+ }
685
+ `.trim();
686
+
687
+ //#endregion
688
+ //#region ../../src/cli/assets/claudeMd.ts
689
+ const claudeMd = (options = {}) => {
690
+ const { react = false, projectName = "my-app" } = options;
691
+ const reactSection = react ? `
692
+ ## React & Frontend
693
+
694
+ ### Pages with \`$page\`
695
+ \`\`\`tsx
696
+ import { $page } from "@alepha/react/router";
697
+ import { $client } from "alepha/server/links";
698
+ import type { UserController } from "./UserController.ts";
699
+
700
+ class AppRouter {
701
+ api = $client<UserController>();
702
+
703
+ users = $page({
704
+ path: "/users",
705
+ loader: async () => ({ users: await this.api.listUsers() }),
706
+ component: ({ users }) => (
707
+ <ul>{users.map(u => <li key={u.id}>{u.email}</li>)}</ul>
708
+ ),
709
+ });
710
+
711
+ userDetail = $page({
712
+ path: "/users/:id",
713
+ schema: { params: t.object({ id: t.uuid() }) },
714
+ loader: async ({ params }) => ({ user: await this.api.getUser({ params }) }),
715
+ lazy: () => import("./UserDetail.tsx"), // Code splitting
716
+ });
717
+ }
718
+ \`\`\`
719
+
720
+ ### React Hooks
721
+ \`\`\`typescript
722
+ import { useAlepha, useClient, useStore, useAction, useInject } from "@alepha/react";
723
+ import { useRouter, useActive } from "@alepha/react/router";
724
+ import { useForm } from "@alepha/react/form";
725
+ \`\`\`
726
+
727
+ - \`useClient<Controller>()\` - Type-safe API calls
728
+ - \`useStore(atom)\` - Global state (returns \`[value, setValue]\`)
729
+ - \`useAction({ handler })\` - Async operations with loading/error state
730
+ - \`useRouter<AppRouter>()\` - Type-safe navigation
731
+ - \`useForm({ schema, handler })\` - Type-safe forms with validation
732
+ ` : "";
733
+ const projectStructure = react ? `
734
+ \`\`\`
735
+ ${projectName}/
736
+ ├── src/
737
+ │ ├── api/ # Backend
738
+ │ │ ├── controllers/ # API controllers with $action
739
+ │ │ ├── services/ # Business logic
740
+ │ │ ├── entities/ # Database entities with $entity
741
+ │ │ ├── providers/ # External service wrappers
742
+ │ │ └── index.ts # API module definition with $module
743
+ │ ├── web/ # Frontend (React only)
744
+ │ │ ├── components/ # React components
745
+ │ │ ├── atoms/ # State atoms with $atom
746
+ │ │ ├── AppRouter.ts # Routes with $page
747
+ │ │ └── index.ts # Web module definition with $module
748
+ │ ├── main.server.ts # Server entry
749
+ │ ├── main.browser.ts # Browser entry (React only)
750
+ │ └── main.css # CSS entry (React only)
751
+ ├── package.json
752
+ └── tsconfig.json
753
+ \`\`\`
754
+ ` : `
755
+ \`\`\`
756
+ ${projectName}/
757
+ ├── src/
758
+ │ ├── api/ # Backend
759
+ │ │ ├── controllers/ # API controllers with $action
760
+ │ │ ├── services/ # Business logic
761
+ │ │ ├── entities/ # Database entities with $entity
762
+ │ │ ├── providers/ # External service wrappers
763
+ │ │ └── index.ts # API module definition with $module
764
+ │ └── main.server.ts # Server entry (always use main.server.ts)
765
+ ├── package.json
766
+ └── tsconfig.json
767
+ \`\`\`
768
+ `;
769
+ return `# CLAUDE.md
770
+
771
+ This file provides guidance to Claude Code when working with this Alepha project.
772
+
773
+ ## Overview
774
+
775
+ This is an **Alepha** project - a convention-driven TypeScript framework for type-safe full-stack applications.
776
+
777
+ **Key Concepts:**
778
+ - **Primitives**: Features defined with \`$\`-prefixed functions (\`$action\`, \`$entity\`, \`$page\`)
779
+ - **Class-Based**: Services are classes, primitives are class properties
780
+ - **Zero-Config**: Code structure IS the configuration
781
+ - **End-to-End Types**: Types flow from database → API → ${react ? "React" : "client"}
782
+
783
+ ## Rules
784
+
785
+ - Use TypeScript strict mode
786
+ - Use Biome for formatting (\`alepha lint\`)
787
+ - Use Vitest for testing
788
+ - One file = one class
789
+ - Primitives are class properties (except \`$entity\`, \`$atom\`)
790
+ - No decorators, no Express/Fastify patterns
791
+ - No manual instantiation - use dependency injection
792
+ - Use \`protected\` instead of \`private\` for class members
793
+ - Import with file extensions: \`import { User } from "./User.ts"\`
794
+ - Use \`t\` from Alepha for schemas (not Zod)
795
+ - Prefer \`t.text()\` over \`t.string()\` for user input (has default max length, auto-trim, supports lowercase option)
796
+
797
+ ## Project Structure
798
+ ${projectStructure}
799
+ ## Core Primitives
800
+
801
+ ### API with \`$action\`
802
+ \`\`\`typescript
803
+ import { t } from "alepha";
804
+ import { $action } from "alepha/server";
805
+
806
+ class UserController {
807
+ getUser = $action({
808
+ path: "/users/:id", // → GET /api/users/:id
809
+ schema: {
810
+ params: t.object({ id: t.uuid() }),
811
+ response: t.object({ id: t.uuid(), email: t.email() }),
812
+ },
813
+ handler: async ({ params }) => this.userRepo.findById(params.id),
814
+ });
815
+
816
+ createUser = $action({
817
+ // POST inferred from body schema
818
+ schema: {
819
+ body: t.object({ email: t.email() }),
820
+ response: userEntity.schema,
821
+ },
822
+ handler: async ({ body }) => this.userRepo.create(body),
823
+ });
824
+ }
825
+ \`\`\`
826
+
827
+ ### Database with \`$entity\` and \`$repository\`
828
+ \`\`\`typescript
829
+ import { $entity, $repository, db } from "alepha/orm";
830
+
831
+ // Entity defined at module level (for drizzle-kit compatibility)
832
+ export const userEntity = $entity({
833
+ name: "users",
834
+ schema: t.object({
835
+ id: db.primaryKey(),
836
+ email: t.email(),
837
+ createdAt: db.createdAt(),
838
+ updatedAt: db.updatedAt(),
839
+ }),
840
+ indexes: [{ column: "email", unique: true }],
841
+ });
842
+
843
+ class UserService {
844
+ repo = $repository(userEntity);
845
+
846
+ async findById(id: string) {
847
+ return this.repo.findById(id);
848
+ }
849
+ }
850
+ \`\`\`
851
+
852
+ ### Dependency Injection
853
+ \`\`\`typescript
854
+ import { $inject } from "alepha";
855
+
856
+ class OrderService {
857
+ userService = $inject(UserService); // Within same module
858
+
859
+ async createOrder(userId: string) {
860
+ const user = await this.userService.findById(userId);
861
+ // ...
862
+ }
863
+ }
864
+
865
+ // Cross-module: use $client instead of $inject
866
+ class AppRouter {
867
+ api = $client<OrderController>(); // Type-safe HTTP client
868
+ }
869
+ \`\`\`
870
+
871
+ ### Modules with \`$module\`
872
+ \`\`\`typescript
873
+ // src/api/index.ts - Groups all API services
874
+ import { $module } from "alepha";
875
+
876
+ export const ApiModule = $module({
877
+ name: "app.api",
878
+ services: [
879
+ UserController,
880
+ OrderController,
881
+ UserService,
882
+ ],
883
+ });
884
+
885
+ // src/web/index.ts - Groups all web services (React only)
886
+ export const WebModule = $module({
887
+ name: "app.web",
888
+ services: [AppRouter, Toaster],
889
+ register(alepha) {
890
+ // Optional: configure additional services
891
+ alepha.with(SomeLibrary);
892
+ },
893
+ });
894
+ \`\`\`
895
+
896
+ ### Environment Variables
897
+ \`\`\`typescript
898
+ import { $env, t } from "alepha";
899
+
900
+ class AppConfig {
901
+ env = $env(t.object({
902
+ DATABASE_URL: t.string(),
903
+ API_KEY: t.optional(t.string()),
904
+ }));
905
+ }
906
+ \`\`\`
907
+ ${reactSection}
908
+ ## Quick Reference
909
+
910
+ | Primitive | Import | Purpose |
911
+ |-----------|--------|---------|
912
+ | \`$inject\` | \`alepha\` | Dependency injection |
913
+ | \`$env\` | \`alepha\` | Environment variables |
914
+ | \`$hook\` | \`alepha\` | Lifecycle hooks |
915
+ | \`$logger\` | \`alepha/logger\` | Structured logging |
916
+ | \`$action\` | \`alepha/server\` | REST API endpoints |
917
+ | \`$route\` | \`alepha/server\` | Low-level HTTP routes |
918
+ | \`$entity\` | \`alepha/orm\` | Database tables |
919
+ | \`$repository\` | \`alepha/orm\` | Type-safe data access |
920
+ | \`$queue\` | \`alepha/queue\` | Background jobs |
921
+ | \`$scheduler\` | \`alepha/scheduler\` | Cron tasks |
922
+ | \`$cache\` | \`alepha/cache\` | Cached computations |
923
+ | \`$bucket\` | \`alepha/bucket\` | File storage |
924
+ | \`$issuer\` | \`alepha/security\` | JWT tokens |
925
+ | \`$command\` | \`alepha/command\` | CLI commands |${react ? `
926
+ | \`$page\` | \`@alepha/react/router\` | React pages with SSR |
927
+ | \`$atom\` | \`alepha\` | Global state |` : ""}
928
+
929
+ ## Testing
930
+
931
+ \`\`\`typescript
932
+ import { describe, it, expect } from "vitest";
933
+ import { Alepha } from "alepha";
934
+
935
+ describe("UserService", () => {
936
+ it("should create user", async () => {
937
+ const alepha = Alepha.create().with(UserService);
938
+ const service = alepha.inject(UserService);
939
+
940
+ const user = await service.create({ email: "test@example.com" });
941
+ expect(user.email).toBe("test@example.com");
942
+ });
943
+
944
+ it("should mock dependencies", async () => {
945
+ const alepha = Alepha.create()
946
+ .with(OrderService)
947
+ .with({ provide: PaymentGateway, use: MockPaymentGateway });
948
+
949
+ const service = alepha.inject(OrderService);
950
+ // PaymentGateway is now mocked
951
+ });
952
+ });
953
+ \`\`\`
954
+
955
+ ## Common Mistakes
956
+
957
+ 1. **DON'T use decorators** - Use primitives (\`$action\`, not \`@Get()\`)
958
+ 2. **DON'T use Zod** - Use TypeBox via \`t\` from Alepha
959
+ 3. **DON'T use Express patterns** - No \`app.get()\`, \`router.use()\`
960
+ 4. **DON'T inject across modules** - Use \`$client\` for cross-module calls
961
+ 5. **DON'T use async constructors** - Use \`$hook({ on: "start" })\`
962
+ 6. **DON'T instantiate manually** - Let DI container manage instances
963
+
964
+ ## After Code Changes
965
+
966
+ Always run:
967
+ \`\`\`bash
968
+ alepha lint # Format and lint
969
+ alepha typecheck # Type checking
970
+ alepha test # Run tests (if applicable)
971
+ alepha build # Build the project
972
+ \`\`\`
973
+
974
+ ## Documentation
975
+
976
+ - Full docs: https://alepha.dev/llms.txt
977
+ - Detailed docs: https://alepha.dev/llms-full.txt
978
+ `.trim();
979
+ };
980
+
981
+ //#endregion
982
+ //#region ../../src/cli/assets/dummySpecTs.ts
983
+ const dummySpecTs = () => `
984
+ import { test, expect } from "vitest";
985
+
986
+ test("dummy test", () => {
987
+ expect(1 + 1).toBe(2);
988
+ });
989
+ `.trim();
990
+
991
+ //#endregion
992
+ //#region ../../src/cli/assets/editorconfig.ts
993
+ const editorconfig = () => `
994
+ # https://editorconfig.org
995
+
996
+ root = true
997
+
998
+ [*]
999
+ charset = utf-8
1000
+ end_of_line = lf
1001
+ insert_final_newline = true
1002
+ trim_trailing_whitespace = true
1003
+ indent_style = space
1004
+ indent_size = 2
1005
+ `.trim();
1006
+
1007
+ //#endregion
1008
+ //#region ../../src/cli/assets/mainBrowserTs.ts
1009
+ const mainBrowserTs = () => `
1010
+ import { Alepha, run } from "alepha";
1011
+ import { WebModule } from "./web/index.ts";
1012
+
1013
+ const alepha = Alepha.create();
1014
+
1015
+ alepha.with(WebModule);
1016
+
1017
+ run(alepha);
1018
+ `.trim();
1019
+
1020
+ //#endregion
1021
+ //#region ../../src/cli/assets/mainCss.ts
1022
+ const mainCss = () => `
1023
+ * {
1024
+ box-sizing: border-box;
1025
+ margin: 0;
1026
+ padding: 0;
1027
+ }
1028
+
1029
+ html,
1030
+ body {
1031
+ height: 100%;
1032
+ }
1033
+
1034
+ body {
1035
+ font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
1036
+ "Helvetica Neue", Arial, sans-serif;
1037
+ line-height: 1.5;
1038
+ -webkit-font-smoothing: antialiased;
1039
+ }
1040
+
1041
+ #root {
1042
+ height: 100%;
1043
+ }
1044
+ `.trim();
1045
+
1046
+ //#endregion
1047
+ //#region ../../src/cli/assets/mainServerTs.ts
1048
+ const mainServerTs = (options = {}) => {
1049
+ const { react = false } = options;
1050
+ return `
1051
+ import { Alepha, run } from "alepha";
1052
+ import { ApiModule } from "./api/index.ts";
1053
+ ${react ? `import { WebModule } from "./web/index.ts";\n` : ""}
1054
+ const alepha = Alepha.create();
1055
+
1056
+ alepha.with(ApiModule);
1057
+ ${react ? `alepha.with(WebModule);\n` : ""}
1058
+ run(alepha);
1059
+ `.trim();
1060
+ };
1061
+
1062
+ //#endregion
1063
+ //#region ../../src/cli/assets/tsconfigJson.ts
1064
+ const tsconfigJson = () => `
1065
+ {
1066
+ "extends": "alepha/tsconfig.base"
1067
+ }
1068
+ `.trim();
1069
+
1070
+ //#endregion
1071
+ //#region ../../src/cli/assets/webAppRouterTs.ts
1072
+ const webAppRouterTs = () => `
1073
+ import { $page } from "@alepha/react/router";
1074
+ import { $client } from "alepha/server/links";
1075
+ import type { HelloController } from "../api/controllers/HelloController.ts";
1076
+
1077
+ export class AppRouter {
1078
+ api = $client<HelloController>();
1079
+
1080
+ home = $page({
1081
+ path: "/",
1082
+ lazy: () => import("./components/Hello.tsx"),
1083
+ loader: () => this.api.hello(),
1084
+ });
1085
+ }
1086
+ `.trim();
1087
+
1088
+ //#endregion
1089
+ //#region ../../src/cli/assets/webHelloComponentTsx.ts
1090
+ const webHelloComponentTsx = () => `import { useState } from "react";
1091
+
1092
+ interface Props {
1093
+ message: string;
1094
+ }
1095
+
1096
+ const Hello = (props: Props) => {
1097
+ const [message, setMessage] = useState(props.message);
1098
+ return (
1099
+ <div>
1100
+ <h1>{message}</h1>
1101
+ <input value={message} onChange={e => setMessage(e.target.value)} />
1102
+ <p>Edit this component in src/web/components/Hello.tsx</p>
1103
+ </div>
1104
+ );
1105
+ };
1106
+
1107
+ export default Hello;
1108
+ `.trim();
1109
+
1110
+ //#endregion
1111
+ //#region ../../src/cli/assets/webIndexTs.ts
1112
+ const webIndexTs = (options = {}) => {
1113
+ const { appName = "app" } = options;
1114
+ return `
1115
+ import { $module } from "alepha";
1116
+ import { AppRouter } from "./AppRouter.ts";
1117
+
1118
+ export const WebModule = $module({
1119
+ name: "${appName}.web",
1120
+ services: [AppRouter],
1121
+ });
1122
+ `.trim();
1123
+ };
1124
+
1125
+ //#endregion
1126
+ //#region ../../src/cli/services/ProjectScaffolder.ts
1127
+ /**
1128
+ * Service for scaffolding new Alepha projects.
1129
+ *
1130
+ * Handles creation of:
1131
+ * - Project structure (src/api, src/web)
1132
+ * - Configuration files (tsconfig, biome, editorconfig)
1133
+ * - Entry points (main.server.ts, main.browser.ts)
1134
+ * - Example code (HelloController, Hello component)
1135
+ */
1136
+ var ProjectScaffolder = class {
1137
+ log = $logger();
1138
+ fs = $inject(FileSystemProvider);
1139
+ pm = $inject(PackageManagerUtils);
495
1140
  /**
496
- * Get the path to Biome configuration file.
1141
+ * Get the app name from the directory name.
497
1142
  *
498
- * Looks for an existing biome.json in the project root, or creates one if it doesn't exist.
1143
+ * Converts the directory name to a valid module name:
1144
+ * - Converts to lowercase
1145
+ * - Replaces spaces, dashes, underscores with nothing
1146
+ * - Falls back to "app" if empty
499
1147
  */
500
- async ensureBiomeConfig(root) {
501
- await this.ensureFileExists(root, "biome.json", biomeJson, true);
1148
+ getAppName(root) {
1149
+ return basename(root).toLowerCase().replace(/[\s\-_]/g, "") || "app";
502
1150
  }
503
1151
  /**
504
- * Ensure .editorconfig exists in the project.
505
- *
506
- * Creates a standard .editorconfig if none exists.
507
- *
508
- * @param root - The root directory of the project
1152
+ * Ensure all configuration files exist.
509
1153
  */
510
- async ensureEditorConfig(root) {
511
- await this.ensureFileExists(root, ".editorconfig", editorconfig, true);
1154
+ async ensureConfig(root, opts) {
1155
+ const tasks = [];
1156
+ const force = opts.force ?? false;
1157
+ if (opts.packageJson) tasks.push(this.pm.ensurePackageJson(root, typeof opts.packageJson === "boolean" ? {} : opts.packageJson).then(() => {}));
1158
+ if (opts.tsconfigJson) tasks.push(this.ensureTsConfig(root, { force }));
1159
+ if (opts.indexHtml) tasks.push(this.ensureReactProject(root, { force }));
1160
+ if (opts.biomeJson) tasks.push(this.ensureBiomeConfig(root, { force }));
1161
+ if (opts.editorconfig) tasks.push(this.ensureEditorConfig(root, { force }));
1162
+ if (opts.claudeMd) tasks.push(this.ensureClaudeMd(root, typeof opts.claudeMd === "boolean" ? { force } : {
1163
+ ...opts.claudeMd,
1164
+ force
1165
+ }));
1166
+ await Promise.all(tasks);
512
1167
  }
513
- /**
514
- * Load Alepha instance from a server entry file.
515
- *
516
- * Dynamically imports the server entry file and extracts the Alepha instance.
517
- * Skips the automatic start process.
518
- *
519
- * @param rootDir - The root directory of the project
520
- * @param explicitEntry - Optional explicit path to the entry file
521
- * @returns Object containing the Alepha instance and the entry file path
522
- * @throws {AlephaError} If the Alepha instance cannot be found
523
- */
524
- async loadAlephaFromServerEntryFile(rootDir, explicitEntry) {
525
- process.env.ALEPHA_CLI_IMPORT = "true";
526
- const entry = await boot.getServerEntry(rootDir, explicitEntry);
527
- delete global.__alepha;
528
- const mod = await import(entry);
529
- this.log.debug(`Load entry: ${entry}`);
530
- if (mod.default instanceof Alepha) return {
531
- alepha: mod.default,
532
- entry
533
- };
534
- const g = global;
535
- if (g.__alepha) return {
536
- alepha: g.__alepha,
537
- entry
538
- };
539
- throw new AlephaError(`Could not find Alepha instance in entry file: ${entry}`);
1168
+ async ensureTsConfig(root, opts = {}) {
1169
+ if (!opts.force && await this.existsInParents(root, "tsconfig.json")) return;
1170
+ await this.fs.writeFile(this.fs.join(root, "tsconfig.json"), tsconfigJson());
540
1171
  }
541
- /**
542
- * Generate JavaScript code for Drizzle entities export.
543
- *
544
- * Creates a temporary entities.js file that imports from the entry file
545
- * and exports database models for Drizzle Kit to process.
546
- *
547
- * @param entry - Path to the server entry file
548
- * @param provider - Name of the database provider
549
- * @param models - Array of model names to export
550
- * @returns JavaScript code as a string
551
- */
552
- generateEntitiesJs(entry, provider, models = []) {
553
- return `
554
- import "${entry}";
555
- import { DrizzleKitProvider, Repository } from "alepha/orm";
556
-
557
- const alepha = globalThis.__alepha;
558
- const kit = alepha.inject(DrizzleKitProvider);
559
- const provider = alepha.services(Repository).find((it) => it.provider.name === "${provider}").provider;
560
- const models = kit.getModels(provider);
561
-
562
- ${models.map((it) => `export const ${it} = models["${it}"];`).join("\n")}
563
-
564
- `.trim();
1172
+ async ensureBiomeConfig(root, opts = {}) {
1173
+ await this.ensureFile(root, "biome.json", biomeJson(), opts.force);
1174
+ }
1175
+ async ensureEditorConfig(root, opts = {}) {
1176
+ await this.ensureFile(root, ".editorconfig", editorconfig(), opts.force);
1177
+ }
1178
+ async ensureClaudeMd(root, options = {}) {
1179
+ await this.ensureFile(root, "CLAUDE.md", claudeMd(options), options.force);
565
1180
  }
566
1181
  /**
567
- * Load environment variables from a .env file.
1182
+ * Ensure src/main.server.ts exists with full API structure.
568
1183
  *
569
- * Reads the .env file in the specified root directory and sets
570
- * the environment variables in process.env.
1184
+ * Creates:
1185
+ * - src/main.server.ts (entry point)
1186
+ * - src/api/index.ts (API module)
1187
+ * - src/api/controllers/HelloController.ts (example controller)
571
1188
  */
572
- async loadEnv(root, files = [".env"]) {
573
- await this.envUtils.loadEnv(root, files);
574
- }
575
- async getPackageManager(root, flags) {
576
- if (flags?.yarn) return "yarn";
577
- if (flags?.pnpm) return "pnpm";
578
- if (flags?.npm) return "npm";
579
- if (flags?.bun) return "bun";
580
- if (this.alepha.isBun()) return "bun";
581
- if (await this.checkFileExists(root, "yarn.lock", true)) return "yarn";
582
- if (await this.checkFileExists(root, "pnpm-lock.yaml", true)) return "pnpm";
583
- return "npm";
584
- }
585
- async ensureIndexHtml(root) {
586
- if (await this.fs.exists(join(root, "index.html"))) return;
587
- const serverEntry = "src/main.server.ts";
588
- const browserEntry = "src/main.browser.ts";
589
- const appRouter = "src/AppRouter.ts";
590
- await this.fs.writeFile(join(root, "index.html"), indexHtml(browserEntry));
591
- try {
592
- await this.fs.mkdir(join(root, "src"), { recursive: true });
593
- } catch {}
594
- if (!await this.fs.exists(join(root, browserEntry))) await this.fs.writeFile(join(root, browserEntry), mainBrowserTs());
595
- if (!await this.fs.exists(join(root, serverEntry))) await this.fs.writeFile(join(root, serverEntry), mainBrowserTs());
596
- if (!await this.fs.exists(join(root, appRouter))) await this.fs.writeFile(join(root, appRouter), appRouterTs());
597
- }
598
- async exists(root, dirName) {
599
- return this.fs.exists(join(root, dirName));
1189
+ async ensureApiProject(root, opts = {}) {
1190
+ const srcDir = this.fs.join(root, "src");
1191
+ if (!opts.force && await this.fs.exists(srcDir)) {
1192
+ if ((await this.fs.ls(srcDir)).length > 0) return;
1193
+ }
1194
+ const appName = this.getAppName(root);
1195
+ await this.fs.mkdir(this.fs.join(root, "src/api/controllers"), { recursive: true });
1196
+ await this.ensureFile(srcDir, "main.server.ts", mainServerTs(), opts.force);
1197
+ await this.ensureFile(srcDir, "api/index.ts", apiIndexTs({ appName }), opts.force);
1198
+ await this.ensureFile(srcDir, "api/controllers/HelloController.ts", apiHelloControllerTs(), opts.force);
600
1199
  }
601
1200
  /**
602
- * Ensure src/main.ts exists with a minimal Alepha bootstrap.
1201
+ * Ensure full React project structure exists.
603
1202
  *
604
- * Creates the src directory and main.ts file if the src directory
605
- * doesn't exist or is empty.
606
- *
607
- * @param root - The root directory of the project
1203
+ * Creates:
1204
+ * - src/main.server.ts, src/main.browser.ts
1205
+ * - src/api/index.ts, src/api/controllers/HelloController.ts
1206
+ * - src/web/index.ts, src/web/AppRouter.ts, src/web/components/Hello.tsx
608
1207
  */
609
- async ensureSrcMain(root) {
610
- const srcDir = join(root, "src");
611
- const mainPath = join(srcDir, "main.ts");
612
- if (!await this.fs.exists(srcDir)) {
613
- await this.fs.mkdir(srcDir, { recursive: true });
614
- await this.fs.writeFile(mainPath, mainTs());
615
- return;
616
- }
617
- if ((await this.fs.ls(srcDir)).length === 0) await this.fs.writeFile(mainPath, mainTs());
1208
+ async ensureReactProject(root, opts = {}) {
1209
+ const appName = this.getAppName(root);
1210
+ await this.fs.mkdir(this.fs.join(root, "src/api/controllers"), { recursive: true });
1211
+ await this.fs.mkdir(this.fs.join(root, "src/web/components"), { recursive: true });
1212
+ await this.ensureFile(root, "src/main.css", mainCss(), opts.force);
1213
+ await this.ensureFile(root, "src/api/index.ts", apiIndexTs({ appName }), opts.force);
1214
+ await this.ensureFile(root, "src/api/controllers/HelloController.ts", apiHelloControllerTs(), opts.force);
1215
+ await this.ensureFile(root, "src/main.server.ts", mainServerTs({ react: true }), opts.force);
1216
+ await this.ensureFile(root, "src/web/index.ts", webIndexTs({ appName }), opts.force);
1217
+ await this.ensureFile(root, "src/web/AppRouter.ts", webAppRouterTs(), opts.force);
1218
+ await this.ensureFile(root, "src/web/components/Hello.tsx", webHelloComponentTsx(), opts.force);
1219
+ await this.ensureFile(root, "src/main.browser.ts", mainBrowserTs(), opts.force);
618
1220
  }
619
1221
  /**
620
1222
  * Ensure test directory exists with a dummy test file.
621
- *
622
- * Creates the test directory and a dummy.spec.ts file if the test directory
623
- * doesn't exist or is empty.
624
- *
625
- * @param root - The root directory of the project
626
1223
  */
627
1224
  async ensureTestDir(root) {
628
- const testDir = join(root, "test");
629
- const dummyPath = join(testDir, "dummy.spec.ts");
1225
+ const testDir = this.fs.join(root, "test");
1226
+ const dummyPath = this.fs.join(testDir, "dummy.spec.ts");
630
1227
  if (!await this.fs.exists(testDir)) {
631
1228
  await this.fs.mkdir(testDir, { recursive: true });
632
1229
  await this.fs.writeFile(dummyPath, dummySpecTs());
@@ -634,68 +1231,23 @@ ${models.map((it) => `export const ${it} = models["${it}"];`).join("\n")}
634
1231
  }
635
1232
  if ((await this.fs.ls(testDir)).length === 0) await this.fs.writeFile(dummyPath, dummySpecTs());
636
1233
  }
637
- async readPackageJson(root) {
638
- const packageJson$1 = await this.fs.createFile({ path: this.fs.join(root, "package.json") }).text();
639
- return JSON.parse(packageJson$1);
640
- }
641
- /**
642
- * Check if a dependency is installed in the project.
643
- *
644
- * @param root - The root directory of the project
645
- * @param packageName - The name of the package to check
646
- * @returns True if the package is in dependencies or devDependencies
647
- */
648
- async hasDependency(root, packageName) {
649
- try {
650
- const pkg = await this.readPackageJson(root);
651
- return !!(pkg.dependencies?.[packageName] || pkg.devDependencies?.[packageName]);
652
- } catch {
653
- return false;
654
- }
655
- }
656
1234
  /**
657
- * Check if Expo is present in the project.
658
- *
659
- * @param root - The root directory of the project
660
- * @returns True if expo is in dependencies or devDependencies
1235
+ * Write a file, optionally overriding if it exists.
661
1236
  */
662
- async hasExpo(root) {
663
- return this.hasDependency(root, "expo");
664
- }
665
- async getInstallCommand(root, packageName, dev = true) {
666
- const pm = await this.getPackageManager(root);
667
- let cmd;
668
- switch (pm) {
669
- case "yarn":
670
- cmd = `yarn add ${dev ? "-D" : ""} ${packageName}`;
671
- break;
672
- case "pnpm":
673
- cmd = `pnpm add ${dev ? "-D" : ""} ${packageName}`;
674
- break;
675
- case "bun":
676
- cmd = `bun add ${dev ? "-d" : ""} ${packageName}`;
677
- break;
678
- default: cmd = `npm install ${dev ? "--save-dev" : ""} ${packageName}`;
679
- }
680
- return cmd.replace(/\s+/g, " ").trim();
1237
+ async ensureFile(root, relativePath, content, force) {
1238
+ const fullPath = this.fs.join(root, relativePath);
1239
+ if (force || !await this.fs.exists(fullPath)) await this.fs.writeFile(fullPath, content);
681
1240
  }
682
1241
  /**
683
- * Install a dependency if it's missing from the project.
684
- *
685
- * Automatically detects the package manager (yarn, pnpm, npm) and installs
686
- * the package as a dev dependency if not already present.
1242
+ * Check if a file exists in the given directory or any parent directory.
687
1243
  */
688
- async ensureDependency(root, packageName, options = {}) {
689
- const { dev = true } = options;
690
- if (await this.hasDependency(root, packageName)) {
691
- this.log.debug(`Dependency '${packageName}' is already installed`);
692
- return;
693
- }
694
- const cmd = await this.getInstallCommand(root, packageName, dev);
695
- if (options.run) await options.run(cmd, { alias: `installing ${packageName}` });
696
- else {
697
- this.log.debug(`Installing ${packageName}`);
698
- await this.exec(cmd, { global: true });
1244
+ async existsInParents(root, filename) {
1245
+ let current = root;
1246
+ while (true) {
1247
+ if (await this.fs.exists(this.fs.join(current, filename))) return true;
1248
+ const parent = dirname(current);
1249
+ if (parent === current) return false;
1250
+ current = parent;
699
1251
  }
700
1252
  }
701
1253
  };
@@ -704,70 +1256,93 @@ ${models.map((it) => `export const ${it} = models["${it}"];`).join("\n")}
704
1256
  //#region ../../src/cli/commands/build.ts
705
1257
  var BuildCommand = class {
706
1258
  log = $logger();
1259
+ fs = $inject(FileSystemProvider);
707
1260
  utils = $inject(AlephaCliUtils);
1261
+ pm = $inject(PackageManagerUtils);
1262
+ scaffolder = $inject(ProjectScaffolder);
1263
+ boot = $inject(AppEntryProvider);
1264
+ viteBuildProvider = $inject(ViteBuildProvider);
708
1265
  options = $use(buildOptions);
709
1266
  build = $command({
710
1267
  name: "build",
711
1268
  mode: "production",
712
1269
  description: "Build the project for production",
713
- args: t.optional(t.text({
714
- title: "path",
715
- description: "Filepath to build"
716
- })),
717
1270
  flags: t.object({
718
1271
  stats: t.optional(t.boolean({ description: "Generate build stats report" })),
719
1272
  vercel: t.optional(t.boolean({ description: "Generate Vercel deployment configuration" })),
720
1273
  cloudflare: t.optional(t.boolean({ description: "Generate Cloudflare Workers configuration" })),
721
1274
  docker: t.optional(t.boolean({ description: "Generate Docker configuration" })),
722
- sitemap: t.optional(t.text({ description: "Generate sitemap.xml with base URL" }))
1275
+ sitemap: t.optional(t.text({ description: "Generate sitemap.xml with base URL" })),
1276
+ bun: t.optional(t.boolean({ description: "Prioritize .bun.ts entry files for Bun runtime" }))
723
1277
  }),
724
- handler: async ({ flags, args, run, root }) => {
725
- process.env.ALEPHA_BUILD_MODE = "cli";
1278
+ handler: async ({ flags, run, root }) => {
726
1279
  process.env.NODE_ENV = "production";
727
- if (await this.utils.hasExpo(root)) return;
728
- await this.utils.ensureConfig(root, { tsconfigJson: true });
729
- const entry = await boot.getServerEntry(root, args);
1280
+ if (await this.pm.hasExpo(root)) return;
1281
+ await this.scaffolder.ensureConfig(root, { tsconfigJson: true });
1282
+ const entry = await this.boot.getAppEntry(root);
730
1283
  this.log.trace("Entry file found", { entry });
731
1284
  const distDir = "dist";
732
- const clientDir = "public";
733
- await this.utils.ensureDependency(root, "vite", { run });
1285
+ const publicDir = "public";
1286
+ await this.pm.ensureDependency(root, "vite", {
1287
+ run,
1288
+ exec: (cmd, opts) => this.utils.exec(cmd, opts)
1289
+ });
734
1290
  await run.rm("dist", { alias: "clean dist" });
735
1291
  const options = this.options;
736
1292
  await this.utils.loadEnv(root, [".env", ".env.production"]);
737
1293
  const stats = flags.stats ?? options.stats ?? false;
1294
+ let template = "";
738
1295
  let hasClient = false;
739
- try {
740
- await access(join(root, "index.html"));
741
- hasClient = true;
742
- } catch {}
743
- if (hasClient) await run({
744
- name: "vite build client",
745
- handler: () => buildClient({
746
- silent: true,
747
- dist: `${distDir}/${clientDir}`,
748
- stats,
749
- precompress: true
750
- })
1296
+ let alepha;
1297
+ await run({
1298
+ name: "analyze app",
1299
+ handler: async () => {
1300
+ alepha = await this.viteBuildProvider.init({ entry });
1301
+ hasClient = this.viteBuildProvider.hasClient();
1302
+ if (hasClient) template = this.viteBuildProvider.generateIndexHtml();
1303
+ }
751
1304
  });
1305
+ if (!alepha) throw new AlephaError("Alepha instance not found");
1306
+ if (hasClient) {
1307
+ const indexHtmlPath = this.fs.join(root, "index.html");
1308
+ await this.fs.writeFile(indexHtmlPath, template);
1309
+ try {
1310
+ await run({
1311
+ name: "vite build client",
1312
+ handler: () => buildClient({
1313
+ silent: true,
1314
+ dist: `${distDir}/${publicDir}`,
1315
+ stats,
1316
+ precompress: true
1317
+ })
1318
+ });
1319
+ } finally {
1320
+ await this.fs.rm(indexHtmlPath);
1321
+ }
1322
+ }
752
1323
  await run({
753
1324
  name: "vite build server",
754
1325
  handler: async () => {
755
- let clientBuilt = false;
756
- try {
757
- await readFile(`${distDir}/${clientDir}/index.html`, "utf-8");
758
- clientBuilt = true;
759
- } catch {}
1326
+ if (!alepha) throw new AlephaError("Alepha instance not found");
1327
+ const clientIndexPath = `${distDir}/${publicDir}/index.html`;
1328
+ const clientBuilt = await this.fs.exists(clientIndexPath);
1329
+ const conditions = [];
1330
+ if (flags.bun) conditions.push("bun");
1331
+ if (options.cloudflare) conditions.push("workerd");
760
1332
  await buildServer({
761
1333
  silent: true,
762
- entry,
1334
+ entry: entry.server,
763
1335
  distDir,
764
- clientDir: clientBuilt ? clientDir : void 0,
765
- stats
1336
+ clientDir: clientBuilt ? publicDir : void 0,
1337
+ stats,
1338
+ conditions,
1339
+ alepha
766
1340
  });
767
- if (clientBuilt) await unlink(`${distDir}/${clientDir}/index.html`);
1341
+ if (clientBuilt) await this.fs.rm(clientIndexPath);
768
1342
  }
769
1343
  });
770
1344
  await copyAssets({
1345
+ alepha,
771
1346
  root,
772
1347
  entry: `${distDir}/index.js`,
773
1348
  distDir,
@@ -775,31 +1350,24 @@ var BuildCommand = class {
775
1350
  });
776
1351
  if (hasClient) {
777
1352
  const sitemapHostname = flags.sitemap ?? options.sitemap?.hostname;
778
- if (sitemapHostname) await run({
779
- name: "add sitemap",
780
- handler: async () => {
781
- await writeFile(`${distDir}/${clientDir}/sitemap.xml`, await generateSitemap({
782
- entry: `${distDir}/index.js`,
783
- baseUrl: sitemapHostname
784
- }));
785
- }
1353
+ if (sitemapHostname) await generateSitemap({
1354
+ alepha,
1355
+ baseUrl: sitemapHostname,
1356
+ output: `${distDir}/${publicDir}/sitemap.xml`,
1357
+ run
786
1358
  });
787
- await run({
788
- name: "pre-render pages",
789
- handler: async () => {
790
- await prerenderPages({
791
- dist: `${distDir}/${clientDir}`,
792
- entry: `${distDir}/index.js`,
793
- compress: true
794
- });
795
- }
1359
+ await prerenderPages({
1360
+ alepha,
1361
+ dist: `${distDir}/${publicDir}`,
1362
+ compress: true,
1363
+ run
796
1364
  });
797
1365
  }
798
1366
  if (flags.vercel || options.vercel) await run({
799
1367
  name: "add Vercel config",
800
1368
  handler: () => generateVercel({
801
1369
  distDir,
802
- clientDir,
1370
+ clientDir: publicDir,
803
1371
  config: options.vercel
804
1372
  })
805
1373
  });
@@ -844,6 +1412,7 @@ const drizzleCommandFlags = t.object({
844
1412
  });
845
1413
  var DbCommand = class {
846
1414
  log = $logger();
1415
+ fs = $inject(FileSystemProvider);
847
1416
  utils = $inject(AlephaCliUtils);
848
1417
  /**
849
1418
  * Check if database migrations are up to date.
@@ -868,15 +1437,16 @@ var DbCommand = class {
868
1437
  const providerName = provider.name;
869
1438
  if (accepted.has(providerName)) continue;
870
1439
  accepted.add(providerName);
871
- const migrationDir = join(rootDir, "migrations", providerName);
872
- const journalFile = await readFile(`${migrationDir}/meta/_journal.json`, "utf-8").catch(() => null);
873
- if (!journalFile) {
1440
+ const migrationDir = this.fs.join(rootDir, "migrations", providerName);
1441
+ const journalBuffer = await this.fs.readFile(`${migrationDir}/meta/_journal.json`).catch(() => null);
1442
+ if (!journalBuffer) {
874
1443
  this.log.info("No migration journal found.");
875
1444
  return;
876
1445
  }
877
- const journal = JSON.parse(journalFile);
1446
+ const journal = JSON.parse(journalBuffer.toString("utf-8"));
878
1447
  const lastMigration = journal.entries[journal.entries.length - 1];
879
- const lastSnapshot = JSON.parse(await readFile(`${migrationDir}/meta/${String(lastMigration.idx).padStart(4, "0")}_snapshot.json`, "utf-8"));
1448
+ const snapshotBuffer = await this.fs.readFile(`${migrationDir}/meta/${String(lastMigration.idx).padStart(4, "0")}_snapshot.json`);
1449
+ const lastSnapshot = JSON.parse(snapshotBuffer.toString("utf-8"));
880
1450
  const models = drizzleKitProvider.getModels(provider);
881
1451
  const kit = drizzleKitProvider.importDrizzleKit();
882
1452
  const now = kit.generateDrizzleJson(models, lastSnapshot.id);
@@ -1030,6 +1600,7 @@ var DbCommand = class {
1030
1600
  const provider = primitive.provider;
1031
1601
  const providerName = provider.name;
1032
1602
  const dialect = provider.dialect;
1603
+ if (providerName === "") continue;
1033
1604
  if (accepted.has(providerName)) continue;
1034
1605
  accepted.add(providerName);
1035
1606
  if (options.provider && options.provider !== providerName) {
@@ -1043,6 +1614,7 @@ var DbCommand = class {
1043
1614
  provider,
1044
1615
  providerName,
1045
1616
  providerUrl: provider.url,
1617
+ providerDriver: provider.driver,
1046
1618
  dialect,
1047
1619
  entry,
1048
1620
  rootDir
@@ -1064,9 +1636,9 @@ var DbCommand = class {
1064
1636
  dbCredentials: { url: options.providerUrl }
1065
1637
  };
1066
1638
  if (options.provider.schema) config.schemaFilter = options.provider.schema;
1067
- if (options.providerName === "d1") config.driver = "d1-http";
1068
- if (options.providerName === "pglite") config.driver = "pglite";
1069
- if (options.dialect === "sqlite") if (options.providerName === "d1") {
1639
+ if (options.providerDriver === "d1") config.driver = "d1-http";
1640
+ if (options.providerDriver === "pglite") config.driver = "pglite";
1641
+ if (options.dialect === "sqlite") if (options.providerDriver === "d1") {
1070
1642
  const token = process.env.CLOUDFLARE_API_TOKEN;
1071
1643
  if (!token) throw new AlephaError("CLOUDFLARE_API_TOKEN environment variable is not set. https://orm.drizzle.team/docs/guides/d1-http-with-drizzle-kit");
1072
1644
  const accountId = process.env.CLOUDFLARE_ACCOUNT_ID;
@@ -1083,7 +1655,7 @@ var DbCommand = class {
1083
1655
  } else {
1084
1656
  let url = options.providerUrl;
1085
1657
  url = url.replace("sqlite://", "").replace("file://", "");
1086
- url = join(options.rootDir, url);
1658
+ url = this.fs.join(options.rootDir, url);
1087
1659
  config.dbCredentials = { url };
1088
1660
  }
1089
1661
  const drizzleConfigJs = `export default ${JSON.stringify(config, null, 2)}`;
@@ -1095,7 +1667,9 @@ var DbCommand = class {
1095
1667
  //#region ../../src/cli/commands/deploy.ts
1096
1668
  var DeployCommand = class {
1097
1669
  log = $logger();
1670
+ fs = $inject(FileSystemProvider);
1098
1671
  utils = $inject(AlephaCliUtils);
1672
+ pm = $inject(PackageManagerUtils);
1099
1673
  /**
1100
1674
  * Deploy the project to a hosting platform (e.g., Vercel, Cloudflare, Surge)
1101
1675
  *
@@ -1143,7 +1717,10 @@ var DeployCommand = class {
1143
1717
  this.log.debug("Running database migrations before deployment...");
1144
1718
  await this.utils.exec(`alepha db migrate --mode=${mode}`);
1145
1719
  }
1146
- await this.utils.ensureDependency(root, "vercel", { dev: true });
1720
+ await this.pm.ensureDependency(root, "vercel", {
1721
+ dev: true,
1722
+ exec: (cmd, opts) => this.utils.exec(cmd, opts)
1723
+ });
1147
1724
  const command = `vercel . --cwd=dist ${mode === "production" ? "--prod" : ""}`.trim();
1148
1725
  this.log.debug(`Deploying to Vercel with command: ${command}`);
1149
1726
  await this.utils.exec(command);
@@ -1154,15 +1731,21 @@ var DeployCommand = class {
1154
1731
  this.log.debug("Running database migrations before deployment...");
1155
1732
  await this.utils.exec(`alepha db migrate --mode=${mode}`);
1156
1733
  }
1157
- await this.utils.ensureDependency(root, "wrangler", { dev: true });
1734
+ await this.pm.ensureDependency(root, "wrangler", {
1735
+ dev: true,
1736
+ exec: (cmd, opts) => this.utils.exec(cmd, opts)
1737
+ });
1158
1738
  const command = `wrangler deploy ${mode === "production" ? "" : "--env preview"} --config=dist/wrangler.jsonc`.trim();
1159
1739
  this.log.info(`Deploying to Cloudflare with command: ${command}`);
1160
1740
  await this.utils.exec(command);
1161
1741
  return;
1162
1742
  }
1163
1743
  if (await this.utils.exists(root, "dist/public/404.html")) {
1164
- await this.utils.ensureDependency(root, "surge", { dev: true });
1165
- const distPath = join(root, "dist/public");
1744
+ await this.pm.ensureDependency(root, "surge", {
1745
+ dev: true,
1746
+ exec: (cmd, opts) => this.utils.exec(cmd, opts)
1747
+ });
1748
+ const distPath = this.fs.join(root, "dist/public");
1166
1749
  this.log.debug(`Deploying to Surge from directory: ${distPath}`);
1167
1750
  await this.utils.exec(`surge ${distPath}`);
1168
1751
  return;
@@ -1173,75 +1756,271 @@ var DeployCommand = class {
1173
1756
  };
1174
1757
 
1175
1758
  //#endregion
1176
- //#region ../../src/cli/commands/dev.ts
1177
- var DevCommand = class {
1759
+ //#region ../../src/cli/providers/ViteDevServerProvider.ts
1760
+ /**
1761
+ * Vite development server with Alepha integration.
1762
+ *
1763
+ * Architecture:
1764
+ * - Vite runs in middleware mode (no HTTP server)
1765
+ * - Alepha is the HTTP server via server:onRequest event
1766
+ * - Request flow: Page requests → Alepha SSR, Assets → Vite middleware
1767
+ *
1768
+ * HMR Strategy:
1769
+ * - Browser-only changes (CSS, client components) → Vite HMR (React Fast Refresh)
1770
+ * - Server-only changes → Restart Alepha → Full browser reload
1771
+ * - Shared changes → Restart Alepha → Let Vite HMR propagate
1772
+ *
1773
+ * Features:
1774
+ * - Automatic .env reload detection
1775
+ * - Error recovery on next file change
1776
+ * - Optimized module invalidation (only changed files + importers)
1777
+ */
1778
+ var ViteDevServerProvider = class {
1178
1779
  log = $logger();
1179
- utils = $inject(AlephaCliUtils);
1180
- alepha = $inject(Alepha);
1780
+ fs = $inject(FileSystemProvider);
1781
+ templateProvider = $inject(ViteTemplateProvider);
1782
+ server;
1783
+ options;
1784
+ alepha = null;
1785
+ hasError = false;
1786
+ changedFiles = /* @__PURE__ */ new Set();
1787
+ async init(options) {
1788
+ this.options = options;
1789
+ await this.createViteServer();
1790
+ return await this.loadAlepha(true);
1791
+ }
1792
+ async start() {
1793
+ await this.alepha?.start();
1794
+ }
1181
1795
  /**
1182
- * Will run the project in watch mode.
1183
- *
1184
- * - If an index.html file is found in the project root, it will run Vite in dev mode.
1185
- * - Otherwise, it will look for a server entry file and run it with tsx in watch mode.
1796
+ * Create the Vite server in middleware mode.
1186
1797
  */
1187
- dev = $command({
1188
- name: "dev",
1189
- description: "Run the project in development mode",
1190
- args: t.optional(t.text({
1191
- title: "path",
1192
- description: "Filepath to run"
1193
- })),
1194
- handler: async ({ args, root }) => {
1195
- const expo = await this.utils.hasExpo(root);
1196
- await this.utils.ensureConfig(root, { tsconfigJson: true });
1197
- if (expo) {
1198
- await this.utils.exec("expo start");
1199
- return;
1798
+ async createViteServer() {
1799
+ const { createServer } = await importVite();
1800
+ const viteReact = await importViteReact();
1801
+ const plugins = [];
1802
+ if (viteReact) plugins.push(viteReact());
1803
+ plugins.push(viteAlephaSsrPreload());
1804
+ plugins.push(this.createHmrPlugin());
1805
+ this.server = await createServer({
1806
+ root: this.options.root,
1807
+ plugins,
1808
+ server: { middlewareMode: true },
1809
+ appType: "custom",
1810
+ customLogger: {
1811
+ info: () => {},
1812
+ warn: this.log.warn.bind(this.log),
1813
+ error: this.log.error.bind(this.log),
1814
+ warnOnce: this.log.warn.bind(this.log),
1815
+ clearScreen: () => {},
1816
+ hasWarned: false,
1817
+ hasErrorLogged: () => false
1200
1818
  }
1201
- const entry = await boot.getServerEntry(root, args);
1202
- this.log.trace("Entry file found", { entry });
1203
- if (!await this.isFullstackProject(root)) {
1204
- const exe = await this.isBunProject(root) ? "bun" : "tsx";
1205
- let cmd = `${exe} --watch`;
1206
- if (await this.utils.exists(root, ".env")) cmd += " --env-file=./.env";
1207
- cmd += ` ${entry}`;
1208
- await this.utils.exec(cmd, { global: exe === "bun" });
1209
- return;
1819
+ });
1820
+ this.server.restart = async () => {
1821
+ const startTime = Date.now();
1822
+ try {
1823
+ this.hasError = true;
1824
+ await this.loadAlepha(false);
1825
+ await this.alepha?.start();
1826
+ this.log.debug(`Env reloaded in ${Date.now() - startTime}ms`);
1827
+ this.sendBrowserReload();
1828
+ } catch (err) {
1829
+ this.hasError = true;
1830
+ this.log.error("Reload failed", err);
1831
+ this.log.warn("Waiting for file changes to retry...");
1832
+ this.alepha = null;
1833
+ }
1834
+ };
1835
+ }
1836
+ /**
1837
+ * Vite plugin to handle HMR for Alepha.
1838
+ */
1839
+ createHmrPlugin() {
1840
+ return {
1841
+ name: "alepha-hmr",
1842
+ handleHotUpdate: async (ctx) => {
1843
+ if (ctx.file.includes("/.idea/")) return [];
1844
+ const firstModule = ctx.modules[0];
1845
+ const isBrowserOnly = firstModule && !firstModule._ssrModule;
1846
+ const isServerOnly = firstModule && !firstModule._clientModule;
1847
+ if (isBrowserOnly) return;
1848
+ const startTime = Date.now();
1849
+ try {
1850
+ this.changedFiles.add(ctx.file);
1851
+ await this.loadAlepha(false);
1852
+ await this.alepha?.start();
1853
+ this.log.debug(`Reloaded in ${Date.now() - startTime}ms`);
1854
+ if (isServerOnly) {
1855
+ this.sendBrowserReload();
1856
+ return [];
1857
+ }
1858
+ return;
1859
+ } catch (err) {
1860
+ this.hasError = true;
1861
+ this.log.error("Reload failed", err);
1862
+ this.log.warn("Waiting for file changes to retry...");
1863
+ this.alepha = null;
1864
+ return [];
1865
+ }
1210
1866
  }
1211
- await this.utils.ensureDependency(root, "vite");
1212
- await devServer();
1867
+ };
1868
+ }
1869
+ /**
1870
+ * Send browser reload signal via custom event.
1871
+ * Browser listens for 'alepha:reload' and does window.location.reload()
1872
+ */
1873
+ sendBrowserReload() {
1874
+ this.server.ws.send({
1875
+ type: "custom",
1876
+ event: "alepha:reload",
1877
+ data: {}
1878
+ });
1879
+ }
1880
+ /**
1881
+ * Setup environment variables for dev mode.
1882
+ */
1883
+ async setupEnvironment() {
1884
+ const { loadEnv } = await importVite();
1885
+ const env = loadEnv(process.env.NODE_ENV || "development", this.options.root, "");
1886
+ process.env.NODE_ENV ??= "development";
1887
+ process.env.VITE_ALEPHA_DEV = "true";
1888
+ process.env.SERVER_HOST ??= this.options.host?.toString() ?? "localhost";
1889
+ process.env.SERVER_PORT ??= String(this.options.port ?? (process.env.SERVER_PORT ? Number(process.env.SERVER_PORT) : 3e3));
1890
+ for (const [key, value] of Object.entries(env)) process.env[key] ??= value;
1891
+ }
1892
+ /**
1893
+ * Load or reload the Alepha instance.
1894
+ */
1895
+ async loadAlepha(isInitialLoad = false) {
1896
+ if (this.alepha) {
1897
+ await this.alepha.stop().catch((err) => this.log.warn("Error stopping Alepha", err));
1898
+ this.alepha = null;
1213
1899
  }
1214
- });
1215
- async isBunProject(root) {
1216
- if (this.alepha.isBun()) return true;
1900
+ if (isInitialLoad || this.hasError) this.server.moduleGraph.invalidateAll();
1901
+ else this.invalidateModulesWithImporters();
1902
+ this.changedFiles.clear();
1903
+ const envSnapshot = { ...process.env };
1904
+ await this.setupEnvironment();
1905
+ await this.server.ssrLoadModule(this.options.entry.server);
1906
+ const alepha = globalThis.__alepha;
1907
+ if (!alepha) throw new AlephaError("Alepha instance not found after loading entry module");
1908
+ this.alepha = alepha;
1909
+ await this.setupAlepha();
1910
+ this.hasError = false;
1911
+ process.env = envSnapshot;
1912
+ return alepha;
1913
+ }
1914
+ hasReact() {
1217
1915
  try {
1218
- await access(join(root, "bun.lock"));
1916
+ this.alepha?.inject("ReactServerProvider");
1219
1917
  return true;
1220
1918
  } catch {
1221
1919
  return false;
1222
1920
  }
1223
1921
  }
1224
- async isFullstackProject(root) {
1225
- try {
1226
- await access(join(root, "index.html"));
1227
- return true;
1228
- } catch {
1229
- return false;
1922
+ /**
1923
+ * Setup Alepha instance with Vite middleware and template.
1924
+ */
1925
+ async setupAlepha() {
1926
+ if (!this.alepha || !this.hasReact()) return;
1927
+ const template = await this.server.transformIndexHtml("/", this.templateProvider.generateIndexHtml(this.options.entry));
1928
+ this.alepha.store.set("alepha.react.server.template", template);
1929
+ this.alepha.events.on("server:onRequest", {
1930
+ priority: "first",
1931
+ callback: async ({ request }) => {
1932
+ const node = request.raw.node;
1933
+ if (!node || this.isPageRequest(node.req)) return;
1934
+ if (await this.runViteMiddleware(node.req, node.res, request)) {
1935
+ request.reply.status = node.res.statusCode || 200;
1936
+ request.reply.body = null;
1937
+ }
1938
+ }
1939
+ });
1940
+ }
1941
+ /**
1942
+ * Check if request is for an HTML page (not an asset).
1943
+ */
1944
+ isPageRequest(req) {
1945
+ const url = req.url || "/";
1946
+ if (url === "/" || url === "/index.html") return true;
1947
+ if (url.startsWith("/@") || url.startsWith("/__vite")) return false;
1948
+ if (/\.\w+$/.test(url.split("?")[0])) return false;
1949
+ return true;
1950
+ }
1951
+ /**
1952
+ * Run Vite middleware and detect if it handled the request.
1953
+ */
1954
+ async runViteMiddleware(req, res, ctx) {
1955
+ return new Promise((resolve) => {
1956
+ let resolved = false;
1957
+ const done = (handled) => {
1958
+ if (resolved) return;
1959
+ resolved = true;
1960
+ if (handled) ctx.metadata.vite = true;
1961
+ resolve(handled);
1962
+ };
1963
+ res.on("finish", () => done(true));
1964
+ res.on("close", () => res.headersSent && done(true));
1965
+ this.server.middlewares(req, res, () => done(false));
1966
+ setImmediate(() => {
1967
+ if (res.headersSent || res.writableEnded) done(true);
1968
+ });
1969
+ });
1970
+ }
1971
+ /**
1972
+ * Invalidate modules and all their importers.
1973
+ */
1974
+ invalidateModulesWithImporters() {
1975
+ const invalidated = /* @__PURE__ */ new Set();
1976
+ const queue = [...this.changedFiles];
1977
+ while (queue.length > 0) {
1978
+ const file = queue.pop();
1979
+ if (invalidated.has(file)) continue;
1980
+ const mod = this.server.moduleGraph.getModuleById(file);
1981
+ if (!mod) continue;
1982
+ this.server.moduleGraph.invalidateModule(mod);
1983
+ invalidated.add(file);
1984
+ for (const importer of mod.importers) if (importer.id && !invalidated.has(importer.id)) queue.push(importer.id);
1230
1985
  }
1231
1986
  }
1232
1987
  };
1233
1988
 
1234
1989
  //#endregion
1235
- //#region ../../src/cli/commands/format.ts
1236
- var FormatCommand = class {
1990
+ //#region ../../src/cli/commands/dev.ts
1991
+ var DevCommand = class {
1992
+ log = $logger();
1993
+ fs = $inject(FileSystemProvider);
1237
1994
  utils = $inject(AlephaCliUtils);
1238
- format = $command({
1239
- name: "format",
1240
- description: "Format the codebase using Biome",
1995
+ pm = $inject(PackageManagerUtils);
1996
+ scaffolder = $inject(ProjectScaffolder);
1997
+ alepha = $inject(Alepha);
1998
+ viteDevServer = $inject(ViteDevServerProvider);
1999
+ boot = $inject(AppEntryProvider);
2000
+ /**
2001
+ * Will run the project in watch mode.
2002
+ *
2003
+ * - If an index.html file is found in the project root, it will run Vite in dev mode.
2004
+ * - Otherwise, it will look for a server entry file and run it with tsx in watch mode.
2005
+ */
2006
+ dev = $command({
2007
+ name: "dev",
2008
+ description: "Run the project in development mode",
1241
2009
  handler: async ({ root }) => {
1242
- await this.utils.ensureConfig(root, { biomeJson: true });
1243
- await this.utils.ensureDependency(root, "@biomejs/biome");
1244
- await this.utils.exec("biome format --fix");
2010
+ const [expo, react] = await Promise.all([this.pm.hasExpo(root), this.pm.hasReact(root)]);
2011
+ await this.scaffolder.ensureConfig(root, { tsconfigJson: true });
2012
+ if (expo) {
2013
+ await this.utils.exec("expo start");
2014
+ return;
2015
+ }
2016
+ const entry = await this.boot.getAppEntry(root);
2017
+ this.log.debug("Entry file found", { entry });
2018
+ await this.pm.ensureDependency(root, "vite", { exec: (cmd, opts) => this.utils.exec(cmd, opts) });
2019
+ await this.viteDevServer.init({
2020
+ root,
2021
+ entry
2022
+ });
2023
+ await this.viteDevServer.start();
1245
2024
  }
1246
2025
  });
1247
2026
  };
@@ -1276,7 +2055,7 @@ const DEFAULT_IGNORE = [
1276
2055
  * ```
1277
2056
  */
1278
2057
  const changelogOptions = $atom({
1279
- name: "alepha.changelog",
2058
+ name: "alepha.cli.changelog.options",
1280
2059
  schema: t.object({ ignore: t.optional(t.array(t.string())) }),
1281
2060
  default: { ignore: DEFAULT_IGNORE }
1282
2061
  });
@@ -1484,7 +2263,7 @@ var GenEnvCommand = class {
1484
2263
  if (value.description) dotEnvFile += `# ${value.description.split("\n").join("\n# ")}\n`;
1485
2264
  if (value.required && !value.default) dotEnvFile += `# (required)\n`;
1486
2265
  if (value.enum) dotEnvFile += `# Possible values: ${value.enum.join(", ")}\n`;
1487
- dotEnvFile += `${key}=${value.default || ""}\n\n`;
2266
+ dotEnvFile += `#${key}=${value.default || ""}\n\n`;
1488
2267
  }
1489
2268
  if (flags.out) await this.fs.writeFile(this.fs.join(root, flags.out), dotEnvFile);
1490
2269
  else this.log.info(dotEnvFile);
@@ -1560,6 +2339,9 @@ var GenCommand = class {
1560
2339
  //#region ../../src/cli/commands/init.ts
1561
2340
  var InitCommand = class {
1562
2341
  utils = $inject(AlephaCliUtils);
2342
+ pm = $inject(PackageManagerUtils);
2343
+ scaffolder = $inject(ProjectScaffolder);
2344
+ fs = $inject(FileSystemProvider);
1563
2345
  /**
1564
2346
  * Ensure the project has the necessary Alepha configuration files.
1565
2347
  * Add the correct dependencies to package.json and install them.
@@ -1567,45 +2349,84 @@ var InitCommand = class {
1567
2349
  init = $command({
1568
2350
  name: "init",
1569
2351
  description: "Add missing Alepha configuration files to the project",
2352
+ args: t.optional(t.text({
2353
+ title: "path",
2354
+ trim: true,
2355
+ lowercase: true
2356
+ })),
1570
2357
  flags: t.object({
2358
+ agent: t.optional(t.boolean({
2359
+ aliases: ["a"],
2360
+ description: "Add CLAUDE.md for Claude Code AI assistant"
2361
+ })),
1571
2362
  yarn: t.optional(t.boolean({ description: "Use Yarn package manager" })),
1572
2363
  pnpm: t.optional(t.boolean({ description: "Use pnpm package manager" })),
1573
2364
  npm: t.optional(t.boolean({ description: "Use npm package manager" })),
1574
2365
  bun: t.optional(t.boolean({ description: "Use Bun package manager" })),
1575
- react: t.optional(t.boolean({ description: "Include Alepha React dependencies" })),
2366
+ react: t.optional(t.boolean({
2367
+ aliases: ["r"],
2368
+ description: "Include Alepha React dependencies"
2369
+ })),
1576
2370
  ui: t.optional(t.boolean({ description: "Include Alepha UI dependencies" })),
1577
- test: t.optional(t.boolean({ description: "Include Vitest and create test directory" }))
2371
+ test: t.optional(t.boolean({ description: "Include Vitest and create test directory" })),
2372
+ force: t.optional(t.boolean({
2373
+ aliases: ["f"],
2374
+ description: "Override existing files"
2375
+ }))
1578
2376
  }),
1579
- handler: async ({ run, flags, root }) => {
1580
- if (flags.ui) flags.react = true;
1581
- const isExpo = await this.utils.hasExpo(root);
2377
+ handler: async ({ run, flags, root, args }) => {
2378
+ if (flags.react) flags.ui = true;
2379
+ if (args) {
2380
+ root = this.fs.join(root, args);
2381
+ await this.fs.mkdir(root);
2382
+ }
2383
+ const isExpo = await this.pm.hasExpo(root);
2384
+ const force = !!flags.force;
1582
2385
  await run({
1583
2386
  name: "ensuring configuration files",
1584
2387
  handler: async () => {
1585
- await this.utils.ensureConfig(root, {
2388
+ await this.scaffolder.ensureConfig(root, {
2389
+ force,
1586
2390
  tsconfigJson: true,
1587
2391
  packageJson: flags,
1588
2392
  biomeJson: true,
1589
2393
  editorconfig: true,
1590
- indexHtml: !!flags.react && !isExpo
2394
+ indexHtml: !!flags.react && !isExpo,
2395
+ claudeMd: flags.agent ? {
2396
+ react: !!flags.react,
2397
+ ui: !!flags.ui
2398
+ } : false
1591
2399
  });
1592
- if (!flags.react) await this.utils.ensureSrcMain(root);
2400
+ if (!flags.react) await this.scaffolder.ensureApiProject(root, { force });
1593
2401
  }
1594
2402
  });
1595
- const pm = await this.utils.getPackageManager(root, flags);
1596
- if (pm === "yarn") {
1597
- await this.utils.ensureYarn(root);
1598
- await run("yarn set version stable");
1599
- } else if (pm === "bun") await this.utils.ensureBun(root);
1600
- else if (pm === "pnpm") await this.utils.ensurePnpm(root);
1601
- else await this.utils.ensureNpm(root);
1602
- await run(`${pm} install`, { alias: `installing dependencies with ${pm}` });
1603
- if (!isExpo) await this.utils.ensureDependency(root, "vite", { run });
1604
- await this.utils.ensureDependency(root, "@biomejs/biome", { run });
2403
+ const pmName = await this.pm.getPackageManager(root, flags);
2404
+ if (pmName === "yarn") {
2405
+ await this.pm.ensureYarn(root);
2406
+ await run("yarn set version stable", { root });
2407
+ } else if (pmName === "bun") await this.pm.ensureBun(root);
2408
+ else if (pmName === "pnpm") await this.pm.ensurePnpm(root);
2409
+ else await this.pm.ensureNpm(root);
2410
+ await run(`${pmName} install`, {
2411
+ alias: `installing dependencies with ${pmName}`,
2412
+ root
2413
+ });
2414
+ if (!isExpo) await this.pm.ensureDependency(root, "vite", {
2415
+ run,
2416
+ exec: (cmd, opts) => this.utils.exec(cmd, opts)
2417
+ });
2418
+ await this.pm.ensureDependency(root, "@biomejs/biome", {
2419
+ run,
2420
+ exec: (cmd, opts) => this.utils.exec(cmd, opts)
2421
+ });
1605
2422
  if (flags.test) {
1606
- await this.utils.ensureTestDir(root);
1607
- await run(`${pm} ${pm === "yarn" ? "add" : "install"} -D vitest`, { alias: "setup testing with Vitest" });
2423
+ await this.scaffolder.ensureTestDir(root);
2424
+ await run(`${pmName} ${pmName === "yarn" ? "add" : "install"} -D vitest`, { alias: "setup testing with Vitest" });
1608
2425
  }
2426
+ await run(`${pmName} run lint`, {
2427
+ alias: "running linter",
2428
+ root
2429
+ });
1609
2430
  }
1610
2431
  });
1611
2432
  };
@@ -1614,13 +2435,15 @@ var InitCommand = class {
1614
2435
  //#region ../../src/cli/commands/lint.ts
1615
2436
  var LintCommand = class {
1616
2437
  utils = $inject(AlephaCliUtils);
2438
+ pm = $inject(PackageManagerUtils);
2439
+ scaffolder = $inject(ProjectScaffolder);
1617
2440
  lint = $command({
1618
2441
  name: "lint",
1619
2442
  description: "Run linter across the codebase using Biome",
1620
2443
  handler: async ({ root }) => {
1621
- await this.utils.ensureConfig(root, { biomeJson: true });
1622
- await this.utils.ensureDependency(root, "@biomejs/biome");
1623
- await this.utils.exec("biome check --formatter-enabled=false --fix");
2444
+ await this.scaffolder.ensureConfig(root, { biomeJson: true });
2445
+ await this.pm.ensureDependency(root, "@biomejs/biome", { exec: (cmd, opts) => this.utils.exec(cmd, opts) });
2446
+ await this.utils.exec("biome check --fix");
1624
2447
  }
1625
2448
  });
1626
2449
  };
@@ -1657,6 +2480,8 @@ var RootCommand = class {
1657
2480
  //#region ../../src/cli/commands/test.ts
1658
2481
  var TestCommand = class {
1659
2482
  utils = $inject(AlephaCliUtils);
2483
+ pm = $inject(PackageManagerUtils);
2484
+ scaffolder = $inject(ProjectScaffolder);
1660
2485
  test = $command({
1661
2486
  name: "test",
1662
2487
  description: "Run tests using Vitest",
@@ -1669,8 +2494,8 @@ var TestCommand = class {
1669
2494
  description: "Additional arguments to pass to Vitest. E.g., --coverage"
1670
2495
  })) }),
1671
2496
  handler: async ({ root, flags, env }) => {
1672
- await this.utils.ensureConfig(root, { tsconfigJson: true });
1673
- await this.utils.ensureDependency(root, "vitest");
2497
+ await this.scaffolder.ensureConfig(root, { tsconfigJson: true });
2498
+ await this.pm.ensureDependency(root, "vitest", { exec: (cmd, opts) => this.utils.exec(cmd, opts) });
1674
2499
  const config = flags.config ? `--config=${flags.config}` : "";
1675
2500
  await this.utils.exec(`vitest run ${config} ${env.VITEST_ARGS}`);
1676
2501
  }
@@ -1681,6 +2506,7 @@ var TestCommand = class {
1681
2506
  //#region ../../src/cli/commands/typecheck.ts
1682
2507
  var TypecheckCommand = class {
1683
2508
  utils = $inject(AlephaCliUtils);
2509
+ pm = $inject(PackageManagerUtils);
1684
2510
  log = $logger();
1685
2511
  /**
1686
2512
  * Run TypeScript type checking across the codebase with no emit.
@@ -1691,7 +2517,7 @@ var TypecheckCommand = class {
1691
2517
  description: "Check TypeScript types across the codebase",
1692
2518
  handler: async ({ root }) => {
1693
2519
  this.log.info("Starting TypeScript type checking...");
1694
- await this.utils.ensureDependency(root, "typescript");
2520
+ await this.pm.ensureDependency(root, "typescript", { exec: (cmd, opts) => this.utils.exec(cmd, opts) });
1695
2521
  await this.utils.exec("tsc --noEmit");
1696
2522
  this.log.info("TypeScript type checking completed successfully.");
1697
2523
  }
@@ -1702,6 +2528,7 @@ var TypecheckCommand = class {
1702
2528
  //#region ../../src/cli/commands/verify.ts
1703
2529
  var VerifyCommand = class {
1704
2530
  utils = $inject(AlephaCliUtils);
2531
+ pm = $inject(PackageManagerUtils);
1705
2532
  /**
1706
2533
  * Run a series of verification commands to ensure code quality and correctness.
1707
2534
  *
@@ -1723,9 +2550,9 @@ var VerifyCommand = class {
1723
2550
  await run("alepha format");
1724
2551
  await run("alepha lint");
1725
2552
  await run("alepha typecheck");
1726
- if ((await this.utils.readPackageJson(root)).devDependencies?.vitest) await run("alepha test");
2553
+ if ((await this.pm.readPackageJson(root)).devDependencies?.vitest) await run("alepha test");
1727
2554
  if (await this.utils.exists(root, "migrations")) await run("alepha db check-migrations");
1728
- if (!await this.utils.hasExpo(root)) await run("alepha build");
2555
+ if (!await this.pm.hasExpo(root)) await run("alepha build");
1729
2556
  await run("alepha clean");
1730
2557
  }
1731
2558
  });
@@ -1769,7 +2596,6 @@ const AlephaCli = $module({
1769
2596
  DbCommand,
1770
2597
  DeployCommand,
1771
2598
  DevCommand,
1772
- FormatCommand,
1773
2599
  InitCommand,
1774
2600
  LintCommand,
1775
2601
  RootCommand,
@@ -1777,6 +2603,7 @@ const AlephaCli = $module({
1777
2603
  TypecheckCommand,
1778
2604
  VerifyCommand,
1779
2605
  GenCommand,
2606
+ AppEntryProvider,
1780
2607
  GitProvider
1781
2608
  ]
1782
2609
  });
@@ -1791,11 +2618,11 @@ var AlephaPackageBuilderCli = class {
1791
2618
  root: true,
1792
2619
  handler: async ({ run, root }) => {
1793
2620
  const modules = [];
1794
- const pkg = await readFile("package.json", "utf-8");
1795
- const pkgData = JSON.parse(pkg);
2621
+ const pkgBuffer = await this.fs.readFile("package.json");
2622
+ const pkgData = JSON.parse(pkgBuffer.toString("utf-8"));
1796
2623
  const packageName = pkgData.name;
1797
2624
  await run("analyze modules", async () => {
1798
- modules.push(...await analyzeModules(join(root, this.src), packageName));
2625
+ modules.push(...await analyzeModules(this.fs.join(root, this.src), packageName));
1799
2626
  });
1800
2627
  pkgData.exports = {};
1801
2628
  for (const item of modules) {
@@ -1807,6 +2634,7 @@ var AlephaPackageBuilderCli = class {
1807
2634
  if (item.native) pkgData.exports[path]["react-native"] = `./src/${item.name}/index.native.ts`;
1808
2635
  else if (item.browser) pkgData.exports[path]["react-native"] = `./src/${item.name}/index.browser.ts`;
1809
2636
  if (item.browser) pkgData.exports[path].browser = `./src/${item.name}/index.browser.ts`;
2637
+ if (item.bun) pkgData.exports[path].bun = `./src/${item.name}/index.bun.ts`;
1810
2638
  pkgData.exports[path].import = `./src/${item.name}/index.ts`;
1811
2639
  pkgData.exports[path].default = `./src/${item.name}/index.ts`;
1812
2640
  }
@@ -1819,48 +2647,58 @@ var AlephaPackageBuilderCli = class {
1819
2647
  pkgData.exports["./json/styles"] = "./src/json/styles.css";
1820
2648
  }
1821
2649
  await this.fs.writeFile("package.json", JSON.stringify(pkgData, null, 2));
1822
- const tmpDir = join(root, "node_modules/.alepha");
2650
+ const tmpDir = this.fs.join(root, "node_modules/.alepha");
1823
2651
  await this.fs.mkdir(tmpDir, { recursive: true }).catch(() => {});
1824
- await this.fs.writeFile(join(tmpDir, "module-dependencies.json"), JSON.stringify(modules, null, 2));
1825
- const tsconfig = await readFile(join(root, "../../tsconfig.json"), "utf-8");
1826
- const external = Object.keys(JSON.parse(tsconfig).compilerOptions.paths);
2652
+ await this.fs.writeFile(this.fs.join(tmpDir, "module-dependencies.json"), JSON.stringify(modules, null, 2));
2653
+ const tsconfigBuffer = await this.fs.readFile(this.fs.join(root, "../../tsconfig.json"));
2654
+ const external = Object.keys(JSON.parse(tsconfigBuffer.toString("utf-8")).compilerOptions.paths);
1827
2655
  external.push("bun");
1828
2656
  external.push("bun:sqlite");
1829
2657
  await run.rm(this.dist);
1830
2658
  const build = async (item) => {
1831
2659
  const entries = [];
1832
- const src = join(root, this.src, item.name);
1833
- const dest = join(root, this.dist, item.name);
2660
+ const src = this.fs.join(root, this.src, item.name);
2661
+ const dest = this.fs.join(root, this.dist, item.name);
1834
2662
  entries.push({
1835
- entry: join(src, "index.ts"),
2663
+ entry: this.fs.join(src, "index.ts"),
1836
2664
  outDir: dest,
1837
2665
  format: ["esm"],
1838
2666
  sourcemap: true,
1839
2667
  fixedExtension: false,
1840
2668
  platform: "node",
2669
+ inlineOnly: false,
1841
2670
  external,
1842
- dts: {
1843
- sourcemap: true,
1844
- resolve: false
1845
- }
2671
+ dts: { sourcemap: true }
1846
2672
  });
1847
2673
  if (item.native) entries.push({
1848
- entry: join(src, "index.native.ts"),
2674
+ entry: this.fs.join(src, "index.native.ts"),
1849
2675
  outDir: dest,
1850
2676
  platform: "neutral",
1851
2677
  sourcemap: true,
1852
2678
  dts: false,
2679
+ inlineOnly: false,
1853
2680
  external
1854
2681
  });
1855
2682
  if (item.browser) entries.push({
1856
- entry: join(src, "index.browser.ts"),
2683
+ entry: this.fs.join(src, "index.browser.ts"),
1857
2684
  outDir: dest,
1858
2685
  platform: "browser",
1859
2686
  sourcemap: true,
1860
2687
  dts: false,
2688
+ inlineOnly: false,
2689
+ external
2690
+ });
2691
+ if (item.bun) entries.push({
2692
+ entry: this.fs.join(src, "index.bun.ts"),
2693
+ outDir: dest,
2694
+ platform: "node",
2695
+ sourcemap: true,
2696
+ fixedExtension: false,
2697
+ dts: false,
2698
+ inlineOnly: false,
1861
2699
  external
1862
2700
  });
1863
- const config = join(tmpDir, `tsdown-${item.name.replace("/", "-")}.config.js`);
2701
+ const config = this.fs.join(tmpDir, `tsdown-${item.name.replace("/", "-")}.config.js`);
1864
2702
  await this.fs.writeFile(config, `export default ${JSON.stringify(entries, null, 2)};`);
1865
2703
  await run(`npx tsdown -c=${config}`);
1866
2704
  };
@@ -1881,6 +2719,7 @@ var AlephaPackageBuilderCli = class {
1881
2719
  }
1882
2720
  });
1883
2721
  };
2722
+ var AlephaPackageBuilderCli_default = AlephaPackageBuilderCli;
1884
2723
  async function getAllFiles(dir) {
1885
2724
  const files = [];
1886
2725
  async function scan(currentDir) {
@@ -1934,7 +2773,7 @@ function detectCircularDependencies(modules) {
1934
2773
  }
1935
2774
  for (const module of modules) {
1936
2775
  const cycle = hasCycle(module.name);
1937
- if (cycle) throw new Error(`Circular dependency detected: ${cycle.join(" -> ")}`);
2776
+ if (cycle) throw new AlephaError(`Circular dependency detected: ${cycle.join(" -> ")}`);
1938
2777
  }
1939
2778
  }
1940
2779
  async function analyzeModules(srcDir, packageName) {
@@ -1948,6 +2787,7 @@ async function analyzeModules(srcDir, packageName) {
1948
2787
  const dependencies = /* @__PURE__ */ new Set();
1949
2788
  const hasBrowser = await fileExists(join(modulePath, "index.browser.ts"));
1950
2789
  const hasNative = await fileExists(join(modulePath, "index.native.ts"));
2790
+ const hasBun = await fileExists(join(modulePath, "index.bun.ts"));
1951
2791
  const hasNode = await fileExists(join(modulePath, "index.node.ts"));
1952
2792
  const files = await getAllFiles(modulePath);
1953
2793
  for (const file of files) {
@@ -1964,6 +2804,7 @@ async function analyzeModules(srcDir, packageName) {
1964
2804
  };
1965
2805
  if (hasNative) module.native = true;
1966
2806
  if (hasBrowser) module.browser = true;
2807
+ if (hasBun) module.bun = true;
1967
2808
  if (hasNode) module.node = true;
1968
2809
  modules.push(module);
1969
2810
  } else await scanDirectory(modulePath, moduleName);
@@ -1982,6 +2823,7 @@ const defineConfig = (runConfig) => {
1982
2823
  if (config.services) for (const it of config.services) alepha.with(it);
1983
2824
  if (config.env) for (const [key, value] of Object.entries(config.env)) process.env[key] = String(value);
1984
2825
  if (config.build) alepha.set(buildOptions, config.build);
2826
+ if (config.entry) alepha.set(appEntryOptions, config.entry);
1985
2827
  return { ...config.commands };
1986
2828
  };
1987
2829
  };
@@ -1991,5 +2833,5 @@ const defineConfig = (runConfig) => {
1991
2833
  const defineAlephaConfig = defineConfig;
1992
2834
 
1993
2835
  //#endregion
1994
- 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 };
2836
+ export { AlephaCli, AlephaCliUtils, AlephaPackageBuilderCli_default as AlephaPackageBuilderCli, AppEntryProvider, BuildCommand, ChangelogCommand, CleanCommand, DEFAULT_IGNORE, DbCommand, DeployCommand, DevCommand, GitMessageParser, GitProvider, InitCommand, LintCommand, OpenApiCommand, RootCommand, TestCommand, TypecheckCommand, VerifyCommand, analyzeModules, changelogOptions, defineAlephaConfig, defineConfig, version };
1995
2837
  //# sourceMappingURL=index.js.map