alepha 0.14.4 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (277) hide show
  1. package/README.md +1 -4
  2. package/dist/api/audits/index.d.ts +619 -731
  3. package/dist/api/audits/index.d.ts.map +1 -1
  4. package/dist/api/files/index.d.ts +185 -298
  5. package/dist/api/files/index.d.ts.map +1 -1
  6. package/dist/api/files/index.js +0 -1
  7. package/dist/api/files/index.js.map +1 -1
  8. package/dist/api/jobs/index.d.ts +245 -356
  9. package/dist/api/jobs/index.d.ts.map +1 -1
  10. package/dist/api/notifications/index.d.ts +238 -350
  11. package/dist/api/notifications/index.d.ts.map +1 -1
  12. package/dist/api/parameters/index.d.ts +499 -611
  13. package/dist/api/parameters/index.d.ts.map +1 -1
  14. package/dist/api/users/index.browser.js +1 -2
  15. package/dist/api/users/index.browser.js.map +1 -1
  16. package/dist/api/users/index.d.ts +1697 -1804
  17. package/dist/api/users/index.d.ts.map +1 -1
  18. package/dist/api/users/index.js +178 -151
  19. package/dist/api/users/index.js.map +1 -1
  20. package/dist/api/verifications/index.d.ts +132 -132
  21. package/dist/api/verifications/index.d.ts.map +1 -1
  22. package/dist/batch/index.d.ts +122 -122
  23. package/dist/batch/index.d.ts.map +1 -1
  24. package/dist/batch/index.js +1 -2
  25. package/dist/batch/index.js.map +1 -1
  26. package/dist/bucket/index.d.ts +163 -163
  27. package/dist/bucket/index.d.ts.map +1 -1
  28. package/dist/cache/core/index.d.ts +46 -46
  29. package/dist/cache/core/index.d.ts.map +1 -1
  30. package/dist/cache/redis/index.d.ts.map +1 -1
  31. package/dist/cli/index.d.ts +302 -299
  32. package/dist/cli/index.d.ts.map +1 -1
  33. package/dist/cli/index.js +966 -564
  34. package/dist/cli/index.js.map +1 -1
  35. package/dist/command/index.d.ts +303 -299
  36. package/dist/command/index.d.ts.map +1 -1
  37. package/dist/command/index.js +11 -7
  38. package/dist/command/index.js.map +1 -1
  39. package/dist/core/index.browser.js +419 -99
  40. package/dist/core/index.browser.js.map +1 -1
  41. package/dist/core/index.d.ts +718 -625
  42. package/dist/core/index.d.ts.map +1 -1
  43. package/dist/core/index.js +420 -99
  44. package/dist/core/index.js.map +1 -1
  45. package/dist/core/index.native.js +419 -99
  46. package/dist/core/index.native.js.map +1 -1
  47. package/dist/datetime/index.d.ts +44 -44
  48. package/dist/datetime/index.d.ts.map +1 -1
  49. package/dist/datetime/index.js +4 -4
  50. package/dist/datetime/index.js.map +1 -1
  51. package/dist/email/index.d.ts +97 -50
  52. package/dist/email/index.d.ts.map +1 -1
  53. package/dist/email/index.js +129 -33
  54. package/dist/email/index.js.map +1 -1
  55. package/dist/fake/index.d.ts +7981 -14
  56. package/dist/fake/index.d.ts.map +1 -1
  57. package/dist/file/index.d.ts +523 -390
  58. package/dist/file/index.d.ts.map +1 -1
  59. package/dist/file/index.js +253 -1
  60. package/dist/file/index.js.map +1 -1
  61. package/dist/lock/core/index.d.ts +208 -208
  62. package/dist/lock/core/index.d.ts.map +1 -1
  63. package/dist/lock/redis/index.d.ts.map +1 -1
  64. package/dist/logger/index.d.ts +25 -26
  65. package/dist/logger/index.d.ts.map +1 -1
  66. package/dist/mcp/index.d.ts +197 -197
  67. package/dist/mcp/index.d.ts.map +1 -1
  68. package/dist/orm/chunk-DtkW-qnP.js +38 -0
  69. package/dist/orm/index.browser.js.map +1 -1
  70. package/dist/orm/index.bun.js +2814 -0
  71. package/dist/orm/index.bun.js.map +1 -0
  72. package/dist/orm/index.d.ts +1205 -1057
  73. package/dist/orm/index.d.ts.map +1 -1
  74. package/dist/orm/index.js +2056 -1753
  75. package/dist/orm/index.js.map +1 -1
  76. package/dist/queue/core/index.d.ts +248 -248
  77. package/dist/queue/core/index.d.ts.map +1 -1
  78. package/dist/queue/redis/index.d.ts.map +1 -1
  79. package/dist/redis/index.bun.js +285 -0
  80. package/dist/redis/index.bun.js.map +1 -0
  81. package/dist/redis/index.d.ts +118 -136
  82. package/dist/redis/index.d.ts.map +1 -1
  83. package/dist/redis/index.js +18 -38
  84. package/dist/redis/index.js.map +1 -1
  85. package/dist/retry/index.d.ts +69 -69
  86. package/dist/retry/index.d.ts.map +1 -1
  87. package/dist/router/index.d.ts +6 -6
  88. package/dist/router/index.d.ts.map +1 -1
  89. package/dist/scheduler/index.d.ts +25 -25
  90. package/dist/scheduler/index.d.ts.map +1 -1
  91. package/dist/security/index.browser.js +5 -1
  92. package/dist/security/index.browser.js.map +1 -1
  93. package/dist/security/index.d.ts +417 -254
  94. package/dist/security/index.d.ts.map +1 -1
  95. package/dist/security/index.js +386 -86
  96. package/dist/security/index.js.map +1 -1
  97. package/dist/server/auth/index.d.ts +277 -277
  98. package/dist/server/auth/index.d.ts.map +1 -1
  99. package/dist/server/auth/index.js +20 -20
  100. package/dist/server/auth/index.js.map +1 -1
  101. package/dist/server/cache/index.d.ts +60 -57
  102. package/dist/server/cache/index.d.ts.map +1 -1
  103. package/dist/server/cache/index.js +1 -1
  104. package/dist/server/cache/index.js.map +1 -1
  105. package/dist/server/compress/index.d.ts +3 -3
  106. package/dist/server/compress/index.d.ts.map +1 -1
  107. package/dist/server/cookies/index.d.ts +6 -6
  108. package/dist/server/cookies/index.d.ts.map +1 -1
  109. package/dist/server/cookies/index.js +3 -3
  110. package/dist/server/cookies/index.js.map +1 -1
  111. package/dist/server/core/index.d.ts +242 -150
  112. package/dist/server/core/index.d.ts.map +1 -1
  113. package/dist/server/core/index.js +288 -122
  114. package/dist/server/core/index.js.map +1 -1
  115. package/dist/server/cors/index.d.ts +11 -12
  116. package/dist/server/cors/index.d.ts.map +1 -1
  117. package/dist/server/health/index.d.ts +0 -1
  118. package/dist/server/health/index.d.ts.map +1 -1
  119. package/dist/server/helmet/index.d.ts +2 -2
  120. package/dist/server/helmet/index.d.ts.map +1 -1
  121. package/dist/server/links/index.browser.js.map +1 -1
  122. package/dist/server/links/index.d.ts +84 -85
  123. package/dist/server/links/index.d.ts.map +1 -1
  124. package/dist/server/links/index.js +1 -2
  125. package/dist/server/links/index.js.map +1 -1
  126. package/dist/server/metrics/index.d.ts.map +1 -1
  127. package/dist/server/multipart/index.d.ts +6 -6
  128. package/dist/server/multipart/index.d.ts.map +1 -1
  129. package/dist/server/proxy/index.d.ts +102 -103
  130. package/dist/server/proxy/index.d.ts.map +1 -1
  131. package/dist/server/rate-limit/index.d.ts +16 -16
  132. package/dist/server/rate-limit/index.d.ts.map +1 -1
  133. package/dist/server/static/index.d.ts +44 -44
  134. package/dist/server/static/index.d.ts.map +1 -1
  135. package/dist/server/swagger/index.d.ts +48 -49
  136. package/dist/server/swagger/index.d.ts.map +1 -1
  137. package/dist/server/swagger/index.js +1 -2
  138. package/dist/server/swagger/index.js.map +1 -1
  139. package/dist/sms/index.d.ts +13 -11
  140. package/dist/sms/index.d.ts.map +1 -1
  141. package/dist/sms/index.js +7 -7
  142. package/dist/sms/index.js.map +1 -1
  143. package/dist/thread/index.d.ts +71 -72
  144. package/dist/thread/index.d.ts.map +1 -1
  145. package/dist/topic/core/index.d.ts +318 -318
  146. package/dist/topic/core/index.d.ts.map +1 -1
  147. package/dist/topic/redis/index.d.ts +6 -6
  148. package/dist/topic/redis/index.d.ts.map +1 -1
  149. package/dist/vite/index.d.ts +5720 -159
  150. package/dist/vite/index.d.ts.map +1 -1
  151. package/dist/vite/index.js +41 -18
  152. package/dist/vite/index.js.map +1 -1
  153. package/dist/websocket/index.browser.js +6 -6
  154. package/dist/websocket/index.browser.js.map +1 -1
  155. package/dist/websocket/index.d.ts +247 -247
  156. package/dist/websocket/index.d.ts.map +1 -1
  157. package/dist/websocket/index.js +6 -6
  158. package/dist/websocket/index.js.map +1 -1
  159. package/package.json +9 -14
  160. package/src/api/files/controllers/AdminFileStatsController.ts +0 -1
  161. package/src/api/users/atoms/realmAuthSettingsAtom.ts +5 -0
  162. package/src/api/users/controllers/{UserRealmController.ts → RealmController.ts} +11 -11
  163. package/src/api/users/entities/users.ts +1 -1
  164. package/src/api/users/index.ts +8 -8
  165. package/src/api/users/primitives/{$userRealm.ts → $realm.ts} +17 -19
  166. package/src/api/users/providers/{UserRealmProvider.ts → RealmProvider.ts} +26 -30
  167. package/src/api/users/schemas/{userRealmConfigSchema.ts → realmConfigSchema.ts} +2 -2
  168. package/src/api/users/services/CredentialService.ts +7 -7
  169. package/src/api/users/services/IdentityService.ts +4 -4
  170. package/src/api/users/services/RegistrationService.spec.ts +25 -27
  171. package/src/api/users/services/RegistrationService.ts +38 -27
  172. package/src/api/users/services/SessionCrudService.ts +3 -3
  173. package/src/api/users/services/SessionService.spec.ts +3 -3
  174. package/src/api/users/services/SessionService.ts +28 -9
  175. package/src/api/users/services/UserService.ts +7 -7
  176. package/src/batch/providers/BatchProvider.ts +1 -2
  177. package/src/cli/apps/AlephaPackageBuilderCli.ts +38 -19
  178. package/src/cli/assets/apiHelloControllerTs.ts +18 -0
  179. package/src/cli/assets/apiIndexTs.ts +16 -0
  180. package/src/cli/assets/claudeMd.ts +303 -0
  181. package/src/cli/assets/mainBrowserTs.ts +2 -2
  182. package/src/cli/assets/mainServerTs.ts +24 -0
  183. package/src/cli/assets/webAppRouterTs.ts +15 -0
  184. package/src/cli/assets/webHelloComponentTsx.ts +16 -0
  185. package/src/cli/assets/webIndexTs.ts +16 -0
  186. package/src/cli/commands/build.ts +41 -21
  187. package/src/cli/commands/db.ts +21 -18
  188. package/src/cli/commands/deploy.ts +17 -5
  189. package/src/cli/commands/dev.ts +13 -17
  190. package/src/cli/commands/format.ts +8 -2
  191. package/src/cli/commands/init.ts +74 -29
  192. package/src/cli/commands/lint.ts +8 -2
  193. package/src/cli/commands/test.ts +8 -2
  194. package/src/cli/commands/typecheck.ts +5 -1
  195. package/src/cli/commands/verify.ts +4 -2
  196. package/src/cli/services/AlephaCliUtils.ts +39 -600
  197. package/src/cli/services/PackageManagerUtils.ts +301 -0
  198. package/src/cli/services/ProjectScaffolder.ts +306 -0
  199. package/src/command/helpers/Runner.ts +15 -3
  200. package/src/core/__tests__/Alepha-graph.spec.ts +4 -0
  201. package/src/core/index.shared.ts +1 -0
  202. package/src/core/index.ts +2 -0
  203. package/src/core/primitives/$hook.ts +6 -2
  204. package/src/core/primitives/$module.spec.ts +4 -0
  205. package/src/core/providers/AlsProvider.ts +1 -1
  206. package/src/core/providers/CodecManager.spec.ts +12 -6
  207. package/src/core/providers/CodecManager.ts +26 -6
  208. package/src/core/providers/EventManager.ts +169 -13
  209. package/src/core/providers/KeylessJsonSchemaCodec.spec.ts +621 -0
  210. package/src/core/providers/KeylessJsonSchemaCodec.ts +407 -0
  211. package/src/core/providers/StateManager.spec.ts +27 -16
  212. package/src/email/providers/LocalEmailProvider.spec.ts +111 -87
  213. package/src/email/providers/LocalEmailProvider.ts +52 -15
  214. package/src/email/providers/NodemailerEmailProvider.ts +167 -56
  215. package/src/file/errors/FileError.ts +7 -0
  216. package/src/file/index.ts +9 -1
  217. package/src/file/providers/MemoryFileSystemProvider.ts +393 -0
  218. package/src/orm/index.browser.ts +1 -19
  219. package/src/orm/index.bun.ts +77 -0
  220. package/src/orm/index.shared-server.ts +22 -0
  221. package/src/orm/index.shared.ts +15 -0
  222. package/src/orm/index.ts +19 -39
  223. package/src/orm/providers/drivers/BunPostgresProvider.ts +3 -5
  224. package/src/orm/providers/drivers/BunSqliteProvider.ts +1 -1
  225. package/src/orm/providers/drivers/CloudflareD1Provider.ts +4 -0
  226. package/src/orm/providers/drivers/DatabaseProvider.ts +4 -0
  227. package/src/orm/providers/drivers/PglitePostgresProvider.ts +4 -0
  228. package/src/orm/services/Repository.ts +8 -0
  229. package/src/redis/index.bun.ts +35 -0
  230. package/src/redis/providers/BunRedisProvider.ts +12 -43
  231. package/src/redis/providers/BunRedisSubscriberProvider.ts +2 -3
  232. package/src/redis/providers/NodeRedisProvider.ts +16 -34
  233. package/src/{server/security → security}/__tests__/BasicAuth.spec.ts +11 -11
  234. package/src/{server/security → security}/__tests__/ServerSecurityProvider-realm.spec.ts +21 -16
  235. package/src/{server/security/providers → security/__tests__}/ServerSecurityProvider.spec.ts +5 -5
  236. package/src/security/index.browser.ts +5 -0
  237. package/src/security/index.ts +90 -7
  238. package/src/security/primitives/{$realm.spec.ts → $issuer.spec.ts} +11 -11
  239. package/src/security/primitives/{$realm.ts → $issuer.ts} +20 -17
  240. package/src/security/primitives/$role.ts +5 -5
  241. package/src/security/primitives/$serviceAccount.spec.ts +5 -5
  242. package/src/security/primitives/$serviceAccount.ts +3 -3
  243. package/src/{server/security → security}/providers/ServerSecurityProvider.ts +5 -7
  244. package/src/server/auth/primitives/$auth.ts +10 -10
  245. package/src/server/auth/primitives/$authCredentials.ts +3 -3
  246. package/src/server/auth/primitives/$authGithub.ts +3 -3
  247. package/src/server/auth/primitives/$authGoogle.ts +3 -3
  248. package/src/server/auth/providers/ServerAuthProvider.ts +13 -13
  249. package/src/server/cache/providers/ServerCacheProvider.ts +1 -1
  250. package/src/server/cookies/providers/ServerCookiesProvider.ts +3 -3
  251. package/src/server/core/providers/NodeHttpServerProvider.ts +25 -6
  252. package/src/server/core/providers/ServerBodyParserProvider.ts +19 -23
  253. package/src/server/core/providers/ServerLoggerProvider.ts +23 -19
  254. package/src/server/core/providers/ServerProvider.ts +144 -21
  255. package/src/server/core/providers/ServerRouterProvider.ts +259 -115
  256. package/src/server/core/providers/ServerTimingProvider.ts +2 -2
  257. package/src/server/links/index.ts +1 -1
  258. package/src/server/links/providers/LinkProvider.ts +1 -1
  259. package/src/server/swagger/index.ts +1 -1
  260. package/src/sms/providers/LocalSmsProvider.spec.ts +153 -111
  261. package/src/sms/providers/LocalSmsProvider.ts +8 -7
  262. package/src/vite/helpers/boot.ts +28 -17
  263. package/src/vite/tasks/buildServer.ts +12 -1
  264. package/src/vite/tasks/devServer.ts +3 -1
  265. package/src/vite/tasks/generateCloudflare.ts +7 -0
  266. package/dist/server/security/index.browser.js +0 -13
  267. package/dist/server/security/index.browser.js.map +0 -1
  268. package/dist/server/security/index.d.ts +0 -173
  269. package/dist/server/security/index.d.ts.map +0 -1
  270. package/dist/server/security/index.js +0 -311
  271. package/dist/server/security/index.js.map +0 -1
  272. package/src/cli/assets/appRouterTs.ts +0 -9
  273. package/src/cli/assets/mainTs.ts +0 -13
  274. package/src/server/security/index.browser.ts +0 -10
  275. package/src/server/security/index.ts +0 -94
  276. /package/src/{server/security → security}/primitives/$basicAuth.ts +0 -0
  277. /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
5
  import { boot, buildClient, buildServer, copyAssets, devServer, generateCloudflare, generateDocker, generateSitemap, generateVercel, prerenderPages } 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
@@ -103,17 +103,354 @@ 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";
106
+ //#region ../../src/cli/services/AlephaCliUtils.ts
107
+ /**
108
+ * Core utility service for CLI commands.
109
+ *
110
+ * Provides:
111
+ * - Command execution
112
+ * - File editing helpers
113
+ * - Drizzle/ORM utilities
114
+ * - Environment loading
115
+ */
116
+ var AlephaCliUtils = class {
117
+ log = $logger();
118
+ fs = $inject(FileSystemProvider);
119
+ envUtils = $inject(EnvUtils);
120
+ /**
121
+ * Execute a command with inherited stdio.
122
+ */
123
+ async exec(command, options = {}) {
124
+ const root = options.root ?? process.cwd();
125
+ this.log.debug(`Executing command: ${command}`, { cwd: root });
126
+ const runExec = async (app$1, args$1) => {
127
+ const prog = spawn(app$1, args$1, {
128
+ stdio: "inherit",
129
+ cwd: root,
130
+ env: {
131
+ ...process.env,
132
+ ...options.env
133
+ }
134
+ });
135
+ await new Promise((resolve) => prog.on("exit", () => {
136
+ resolve();
137
+ }));
138
+ };
139
+ if (options.global) {
140
+ const [app$1, ...args$1] = command.split(" ");
141
+ await runExec(app$1, args$1);
142
+ return;
143
+ }
144
+ const suffix = process.platform === "win32" ? ".cmd" : "";
145
+ const [app, ...args] = command.split(" ");
146
+ let execPath = await this.checkFileExists(root, `node_modules/.bin/${app}${suffix}`);
147
+ if (!execPath) execPath = await this.checkFileExists(root, `node_modules/alepha/node_modules/.bin/${app}${suffix}`);
148
+ if (!execPath) throw new AlephaError(`Could not find executable for command '${app}'. Make sure the package is installed.`);
149
+ await runExec(execPath, args);
150
+ }
151
+ /**
152
+ * Write a configuration file to node_modules/.alepha directory.
153
+ */
154
+ async writeConfigFile(name, content, root = process.cwd()) {
155
+ const dir = this.fs.join(root, "node_modules", ".alepha");
156
+ await this.fs.mkdir(dir, { recursive: true }).catch(() => null);
157
+ const path = this.fs.join(dir, name);
158
+ await this.fs.writeFile(path, content);
159
+ this.log.debug(`Config file written: ${path}`);
160
+ return path;
161
+ }
162
+ /**
163
+ * Load Alepha instance from a server entry file.
164
+ */
165
+ async loadAlephaFromServerEntryFile(rootDir, explicitEntry) {
166
+ process.env.ALEPHA_CLI_IMPORT = "true";
167
+ const entry = await boot.getServerEntry(rootDir, explicitEntry);
168
+ delete global.__alepha;
169
+ const mod = await import(entry);
170
+ this.log.debug(`Load entry: ${entry}`);
171
+ if (mod.default instanceof Alepha) return {
172
+ alepha: mod.default,
173
+ entry
174
+ };
175
+ const g = global;
176
+ if (g.__alepha) return {
177
+ alepha: g.__alepha,
178
+ entry
179
+ };
180
+ throw new AlephaError(`Could not find Alepha instance in entry file: ${entry}`);
181
+ }
182
+ /**
183
+ * Generate JavaScript code for Drizzle entities export.
184
+ */
185
+ generateEntitiesJs(entry, provider, models = []) {
186
+ return `
187
+ import "${entry}";
188
+ import { DrizzleKitProvider, Repository } from "alepha/orm";
109
189
 
110
- export class AppRouter {
111
- home = $page({
112
- component: () => "Hello World",
190
+ const alepha = globalThis.__alepha;
191
+ const kit = alepha.inject(DrizzleKitProvider);
192
+ const provider = alepha.services(Repository).find((it) => it.provider.name === "${provider}").provider;
193
+ const models = kit.getModels(provider);
194
+
195
+ ${models.map((it) => `export const ${it} = models["${it}"];`).join("\n")}
196
+
197
+ `.trim();
198
+ }
199
+ /**
200
+ * Load environment variables from a .env file.
201
+ */
202
+ async loadEnv(root, files = [".env"]) {
203
+ await this.envUtils.loadEnv(root, files);
204
+ }
205
+ async exists(root, path) {
206
+ return this.fs.exists(this.fs.join(root, path));
207
+ }
208
+ async checkFileExists(root, name) {
209
+ const configPath = this.fs.join(root, name);
210
+ if (await this.fs.exists(configPath)) return configPath;
211
+ }
212
+ };
213
+
214
+ //#endregion
215
+ //#region ../../src/cli/version.ts
216
+ const packageJson = JSON.parse(readFileSync(new URL("../../package.json", import.meta.url), "utf-8"));
217
+ const version = packageJson.version;
218
+
219
+ //#endregion
220
+ //#region ../../src/cli/services/PackageManagerUtils.ts
221
+ /**
222
+ * Utility service for package manager operations.
223
+ *
224
+ * Handles detection, installation, and cleanup for:
225
+ * - Yarn
226
+ * - npm
227
+ * - pnpm
228
+ * - Bun
229
+ */
230
+ var PackageManagerUtils = class {
231
+ log = $logger();
232
+ fs = $inject(FileSystemProvider);
233
+ alepha = $inject(Alepha);
234
+ /**
235
+ * Detect the package manager used in the project.
236
+ */
237
+ async getPackageManager(root, flags) {
238
+ if (flags?.yarn) return "yarn";
239
+ if (flags?.pnpm) return "pnpm";
240
+ if (flags?.npm) return "npm";
241
+ if (flags?.bun) return "bun";
242
+ if (this.alepha.isBun()) return "bun";
243
+ if (await this.fs.exists(this.fs.join(root, "bun.lock"))) return "bun";
244
+ if (await this.fs.exists(this.fs.join(root, "yarn.lock"))) return "yarn";
245
+ if (await this.fs.exists(this.fs.join(root, "pnpm-lock.yaml"))) return "pnpm";
246
+ return "npm";
247
+ }
248
+ /**
249
+ * Get the install command for a package.
250
+ */
251
+ async getInstallCommand(root, packageName, dev = true) {
252
+ const pm = await this.getPackageManager(root);
253
+ let cmd;
254
+ switch (pm) {
255
+ case "yarn":
256
+ cmd = `yarn add ${dev ? "-D" : ""} ${packageName}`;
257
+ break;
258
+ case "pnpm":
259
+ cmd = `pnpm add ${dev ? "-D" : ""} ${packageName}`;
260
+ break;
261
+ case "bun":
262
+ cmd = `bun add ${dev ? "-d" : ""} ${packageName}`;
263
+ break;
264
+ default: cmd = `npm install ${dev ? "--save-dev" : ""} ${packageName}`;
265
+ }
266
+ return cmd.replace(/\s+/g, " ").trim();
267
+ }
268
+ /**
269
+ * Check if a dependency is installed in the project.
270
+ */
271
+ async hasDependency(root, packageName) {
272
+ try {
273
+ const pkg = await this.readPackageJson(root);
274
+ return !!(pkg.dependencies?.[packageName] || pkg.devDependencies?.[packageName]);
275
+ } catch {
276
+ return false;
277
+ }
278
+ }
279
+ /**
280
+ * Check if Expo is present in the project.
281
+ */
282
+ async hasExpo(root) {
283
+ return this.hasDependency(root, "expo");
284
+ }
285
+ /**
286
+ * Install a dependency if it's missing from the project.
287
+ */
288
+ async ensureDependency(root, packageName, options = {}) {
289
+ const { dev = true } = options;
290
+ if (await this.hasDependency(root, packageName)) {
291
+ this.log.debug(`Dependency '${packageName}' is already installed`);
292
+ return;
293
+ }
294
+ const cmd = await this.getInstallCommand(root, packageName, dev);
295
+ if (options.run) await options.run(cmd, {
296
+ alias: `installing ${packageName}`,
297
+ root
298
+ });
299
+ else if (options.exec) {
300
+ this.log.debug(`Installing ${packageName}`);
301
+ await options.exec(cmd, {
302
+ global: true,
303
+ root
304
+ });
305
+ }
306
+ }
307
+ async ensureYarn(root) {
308
+ const yarnrcPath = this.fs.join(root, ".yarnrc.yml");
309
+ if (!await this.fs.exists(yarnrcPath)) await this.fs.writeFile(yarnrcPath, "nodeLinker: node-modules");
310
+ await this.removeAllPmFilesExcept(root, "yarn");
311
+ }
312
+ async ensureBun(root) {
313
+ await this.removeAllPmFilesExcept(root, "bun");
314
+ }
315
+ async ensurePnpm(root) {
316
+ await this.removeAllPmFilesExcept(root, "pnpm");
317
+ }
318
+ async ensureNpm(root) {
319
+ await this.removeAllPmFilesExcept(root, "npm");
320
+ }
321
+ async removeAllPmFilesExcept(root, except) {
322
+ if (except !== "yarn") await this.removeYarn(root);
323
+ if (except !== "pnpm") await this.removePnpm(root);
324
+ if (except !== "npm") await this.removeNpm(root);
325
+ if (except !== "bun") await this.removeBun(root);
326
+ }
327
+ async removeYarn(root) {
328
+ await this.removeFiles(root, [
329
+ ".yarn",
330
+ ".yarnrc.yml",
331
+ "yarn.lock"
332
+ ]);
333
+ await this.editPackageJson(root, (pkg) => {
334
+ delete pkg.packageManager;
335
+ return pkg;
336
+ });
337
+ }
338
+ async removePnpm(root) {
339
+ await this.removeFiles(root, ["pnpm-lock.yaml", "pnpm-workspace.yaml"]);
340
+ await this.editPackageJson(root, (pkg) => {
341
+ delete pkg.packageManager;
342
+ return pkg;
343
+ });
344
+ }
345
+ async removeNpm(root) {
346
+ await this.removeFiles(root, ["package-lock.json"]);
347
+ }
348
+ async removeBun(root) {
349
+ await this.removeFiles(root, ["bun.lockb", "bun.lock"]);
350
+ }
351
+ async readPackageJson(root) {
352
+ const content = await this.fs.createFile({ path: this.fs.join(root, "package.json") }).text();
353
+ return JSON.parse(content);
354
+ }
355
+ async writePackageJson(root, content) {
356
+ await this.fs.writeFile(this.fs.join(root, "package.json"), JSON.stringify(content, null, 2));
357
+ }
358
+ async editPackageJson(root, editFn) {
359
+ try {
360
+ const updated = editFn(await this.readPackageJson(root));
361
+ await this.writePackageJson(root, updated);
362
+ } catch {}
363
+ }
364
+ async ensurePackageJson(root, modes) {
365
+ const packageJsonPath = this.fs.join(root, "package.json");
366
+ if (!await this.fs.exists(packageJsonPath)) {
367
+ const content = this.generatePackageJsonContent(modes);
368
+ await this.writePackageJson(root, content);
369
+ return content;
370
+ }
371
+ const packageJson$1 = await this.readPackageJson(root);
372
+ const newContent = this.generatePackageJsonContent(modes);
373
+ packageJson$1.type = "module";
374
+ packageJson$1.dependencies ??= {};
375
+ packageJson$1.devDependencies ??= {};
376
+ packageJson$1.scripts ??= {};
377
+ Object.assign(packageJson$1.dependencies, newContent.dependencies);
378
+ Object.assign(packageJson$1.devDependencies, newContent.devDependencies);
379
+ Object.assign(packageJson$1.scripts, newContent.scripts);
380
+ await this.writePackageJson(root, packageJson$1);
381
+ return packageJson$1;
382
+ }
383
+ generatePackageJsonContent(modes) {
384
+ const dependencies = { alepha: `^${version}` };
385
+ const devDependencies = {};
386
+ const scripts = {
387
+ dev: "alepha dev",
388
+ build: "alepha build",
389
+ lint: "alepha lint",
390
+ typecheck: "alepha typecheck",
391
+ verify: "alepha verify"
392
+ };
393
+ if (modes.admin) {
394
+ dependencies["@alepha/ui"] = `^${version}`;
395
+ modes.web = true;
396
+ }
397
+ if (modes.web) {
398
+ dependencies["@alepha/react"] = `^${version}`;
399
+ dependencies.react = "^19.2.0";
400
+ dependencies["react-dom"] = "^19.2.0";
401
+ devDependencies["@types/react"] = "^19.2.0";
402
+ }
403
+ return {
404
+ type: "module",
405
+ dependencies,
406
+ devDependencies,
407
+ scripts
408
+ };
409
+ }
410
+ async removeFiles(root, files) {
411
+ await Promise.all(files.map((file) => this.fs.rm(this.fs.join(root, file), {
412
+ force: true,
413
+ recursive: true
414
+ })));
415
+ }
416
+ };
417
+
418
+ //#endregion
419
+ //#region ../../src/cli/assets/apiHelloControllerTs.ts
420
+ const apiHelloControllerTs = () => `
421
+ import { t } from "alepha";
422
+ import { $action } from "alepha/server";
423
+
424
+ export class HelloController {
425
+ hello = $action({
426
+ path: "/hello",
427
+ schema: {
428
+ response: t.object({
429
+ message: t.string(),
430
+ }),
431
+ },
432
+ handler: () => ({
433
+ message: "Hello, Alepha!",
434
+ }),
113
435
  });
114
436
  }
115
437
  `.trim();
116
438
 
439
+ //#endregion
440
+ //#region ../../src/cli/assets/apiIndexTs.ts
441
+ const apiIndexTs = (options = {}) => {
442
+ const { appName = "app" } = options;
443
+ return `
444
+ import { $module } from "alepha";
445
+ import { HelloController } from "./controllers/HelloController.ts";
446
+
447
+ export const ApiModule = $module({
448
+ name: "${appName}.api",
449
+ services: [HelloController],
450
+ });
451
+ `.trim();
452
+ };
453
+
117
454
  //#endregion
118
455
  //#region ../../src/cli/assets/biomeJson.ts
119
456
  const biomeJson = `
@@ -150,6 +487,299 @@ const biomeJson = `
150
487
  }
151
488
  `.trim();
152
489
 
490
+ //#endregion
491
+ //#region ../../src/cli/assets/claudeMd.ts
492
+ const claudeMd = (options = {}) => {
493
+ const { react = false, projectName = "my-app" } = options;
494
+ const reactSection = react ? `
495
+ ## React & Frontend
496
+
497
+ ### Pages with \`$page\`
498
+ \`\`\`tsx
499
+ import { $page } from "@alepha/react/router";
500
+ import { $client } from "alepha/server/links";
501
+ import type { UserController } from "./UserController.ts";
502
+
503
+ class AppRouter {
504
+ api = $client<UserController>();
505
+
506
+ users = $page({
507
+ path: "/users",
508
+ loader: async () => ({ users: await this.api.listUsers() }),
509
+ component: ({ users }) => (
510
+ <ul>{users.map(u => <li key={u.id}>{u.email}</li>)}</ul>
511
+ ),
512
+ });
513
+
514
+ userDetail = $page({
515
+ path: "/users/:id",
516
+ schema: { params: t.object({ id: t.uuid() }) },
517
+ loader: async ({ params }) => ({ user: await this.api.getUser({ params }) }),
518
+ lazy: () => import("./UserDetail.tsx"), // Code splitting
519
+ });
520
+ }
521
+ \`\`\`
522
+
523
+ ### React Hooks
524
+ \`\`\`typescript
525
+ import { useAlepha, useClient, useStore, useAction, useInject } from "@alepha/react";
526
+ import { useRouter, useActive } from "@alepha/react/router";
527
+ import { useForm } from "@alepha/react/form";
528
+ \`\`\`
529
+
530
+ - \`useClient<Controller>()\` - Type-safe API calls
531
+ - \`useStore(atom)\` - Global state (returns \`[value, setValue]\`)
532
+ - \`useAction({ handler })\` - Async operations with loading/error state
533
+ - \`useRouter<AppRouter>()\` - Type-safe navigation
534
+ - \`useForm({ schema, handler })\` - Type-safe forms with validation
535
+ ` : "";
536
+ const projectStructure = react ? `
537
+ \`\`\`
538
+ ${projectName}/
539
+ ├── src/
540
+ │ ├── api/ # Backend
541
+ │ │ ├── controllers/ # API controllers with $action
542
+ │ │ ├── services/ # Business logic
543
+ │ │ ├── entities/ # Database entities with $entity
544
+ │ │ ├── providers/ # External service wrappers
545
+ │ │ └── index.ts # API module definition with $module
546
+ │ ├── web/ # Frontend (React only)
547
+ │ │ ├── components/ # React components
548
+ │ │ ├── atoms/ # State atoms with $atom
549
+ │ │ ├── AppRouter.ts # Routes with $page
550
+ │ │ └── index.ts # Web module definition with $module
551
+ │ ├── main.server.ts # Server entry
552
+ │ └── main.browser.ts # Browser entry (React only)
553
+ ├── index.html # (React only)
554
+ ├── package.json
555
+ └── tsconfig.json
556
+ \`\`\`
557
+ ` : `
558
+ \`\`\`
559
+ ${projectName}/
560
+ ├── src/
561
+ │ ├── api/ # Backend
562
+ │ │ ├── controllers/ # API controllers with $action
563
+ │ │ ├── services/ # Business logic
564
+ │ │ ├── entities/ # Database entities with $entity
565
+ │ │ ├── providers/ # External service wrappers
566
+ │ │ └── index.ts # API module definition with $module
567
+ │ └── main.server.ts # Server entry (always use main.server.ts)
568
+ ├── package.json
569
+ └── tsconfig.json
570
+ \`\`\`
571
+ `;
572
+ return `# CLAUDE.md
573
+
574
+ This file provides guidance to Claude Code when working with this Alepha project.
575
+
576
+ ## Overview
577
+
578
+ This is an **Alepha** project - a convention-driven TypeScript framework for type-safe full-stack applications.
579
+
580
+ **Key Concepts:**
581
+ - **Primitives**: Features defined with \`$\`-prefixed functions (\`$action\`, \`$entity\`, \`$page\`)
582
+ - **Class-Based**: Services are classes, primitives are class properties
583
+ - **Zero-Config**: Code structure IS the configuration
584
+ - **End-to-End Types**: Types flow from database → API → ${react ? "React" : "client"}
585
+
586
+ ## Rules
587
+
588
+ - Use TypeScript strict mode
589
+ - Use Biome for formatting (\`alepha lint\`)
590
+ - Use Vitest for testing
591
+ - One file = one class
592
+ - Primitives are class properties (except \`$entity\`, \`$atom\`)
593
+ - No decorators, no Express/Fastify patterns
594
+ - No manual instantiation - use dependency injection
595
+ - Use \`protected\` instead of \`private\` for class members
596
+ - Import with file extensions: \`import { User } from "./User.ts"\`
597
+ - Use \`t\` from Alepha for schemas (not Zod)
598
+
599
+ ## Project Structure
600
+ ${projectStructure}
601
+ ## Core Primitives
602
+
603
+ ### API with \`$action\`
604
+ \`\`\`typescript
605
+ import { t } from "alepha";
606
+ import { $action } from "alepha/server";
607
+
608
+ class UserController {
609
+ getUser = $action({
610
+ path: "/users/:id", // → GET /api/users/:id
611
+ schema: {
612
+ params: t.object({ id: t.uuid() }),
613
+ response: t.object({ id: t.uuid(), email: t.email() }),
614
+ },
615
+ handler: async ({ params }) => this.userRepo.findById(params.id),
616
+ });
617
+
618
+ createUser = $action({
619
+ // POST inferred from body schema
620
+ schema: {
621
+ body: t.object({ email: t.email() }),
622
+ response: userEntity.schema,
623
+ },
624
+ handler: async ({ body }) => this.userRepo.create(body),
625
+ });
626
+ }
627
+ \`\`\`
628
+
629
+ ### Database with \`$entity\` and \`$repository\`
630
+ \`\`\`typescript
631
+ import { $entity, $repository, db } from "alepha/orm";
632
+
633
+ // Entity defined at module level (for drizzle-kit compatibility)
634
+ export const userEntity = $entity({
635
+ name: "users",
636
+ schema: t.object({
637
+ id: db.primaryKey(),
638
+ email: t.email(),
639
+ createdAt: db.createdAt(),
640
+ updatedAt: db.updatedAt(),
641
+ }),
642
+ indexes: [{ column: "email", unique: true }],
643
+ });
644
+
645
+ class UserService {
646
+ repo = $repository(userEntity);
647
+
648
+ async findById(id: string) {
649
+ return this.repo.findById(id);
650
+ }
651
+ }
652
+ \`\`\`
653
+
654
+ ### Dependency Injection
655
+ \`\`\`typescript
656
+ import { $inject } from "alepha";
657
+
658
+ class OrderService {
659
+ userService = $inject(UserService); // Within same module
660
+
661
+ async createOrder(userId: string) {
662
+ const user = await this.userService.findById(userId);
663
+ // ...
664
+ }
665
+ }
666
+
667
+ // Cross-module: use $client instead of $inject
668
+ class AppRouter {
669
+ api = $client<OrderController>(); // Type-safe HTTP client
670
+ }
671
+ \`\`\`
672
+
673
+ ### Modules with \`$module\`
674
+ \`\`\`typescript
675
+ // src/api/index.ts - Groups all API services
676
+ import { $module } from "alepha";
677
+
678
+ export const ApiModule = $module({
679
+ name: "app.api",
680
+ services: [
681
+ UserController,
682
+ OrderController,
683
+ UserService,
684
+ ],
685
+ });
686
+
687
+ // src/web/index.ts - Groups all web services (React only)
688
+ export const WebModule = $module({
689
+ name: "app.web",
690
+ services: [AppRouter, Toaster],
691
+ register(alepha) {
692
+ // Optional: configure additional services
693
+ alepha.with(SomeLibrary);
694
+ },
695
+ });
696
+ \`\`\`
697
+
698
+ ### Environment Variables
699
+ \`\`\`typescript
700
+ import { $env, t } from "alepha";
701
+
702
+ class AppConfig {
703
+ env = $env(t.object({
704
+ DATABASE_URL: t.string(),
705
+ API_KEY: t.optional(t.string()),
706
+ }));
707
+ }
708
+ \`\`\`
709
+ ${reactSection}
710
+ ## Quick Reference
711
+
712
+ | Primitive | Import | Purpose |
713
+ |-----------|--------|---------|
714
+ | \`$inject\` | \`alepha\` | Dependency injection |
715
+ | \`$env\` | \`alepha\` | Environment variables |
716
+ | \`$hook\` | \`alepha\` | Lifecycle hooks |
717
+ | \`$logger\` | \`alepha/logger\` | Structured logging |
718
+ | \`$action\` | \`alepha/server\` | REST API endpoints |
719
+ | \`$route\` | \`alepha/server\` | Low-level HTTP routes |
720
+ | \`$entity\` | \`alepha/orm\` | Database tables |
721
+ | \`$repository\` | \`alepha/orm\` | Type-safe data access |
722
+ | \`$queue\` | \`alepha/queue\` | Background jobs |
723
+ | \`$scheduler\` | \`alepha/scheduler\` | Cron tasks |
724
+ | \`$cache\` | \`alepha/cache\` | Cached computations |
725
+ | \`$bucket\` | \`alepha/bucket\` | File storage |
726
+ | \`$issuer\` | \`alepha/security\` | JWT tokens |
727
+ | \`$command\` | \`alepha/command\` | CLI commands |${react ? `
728
+ | \`$page\` | \`@alepha/react/router\` | React pages with SSR |
729
+ | \`$atom\` | \`alepha\` | Global state |` : ""}
730
+
731
+ ## Testing
732
+
733
+ \`\`\`typescript
734
+ import { describe, it, expect } from "vitest";
735
+ import { Alepha } from "alepha";
736
+
737
+ describe("UserService", () => {
738
+ it("should create user", async () => {
739
+ const alepha = Alepha.create().with(UserService);
740
+ const service = alepha.inject(UserService);
741
+
742
+ const user = await service.create({ email: "test@example.com" });
743
+ expect(user.email).toBe("test@example.com");
744
+ });
745
+
746
+ it("should mock dependencies", async () => {
747
+ const alepha = Alepha.create()
748
+ .with(OrderService)
749
+ .with({ provide: PaymentGateway, use: MockPaymentGateway });
750
+
751
+ const service = alepha.inject(OrderService);
752
+ // PaymentGateway is now mocked
753
+ });
754
+ });
755
+ \`\`\`
756
+
757
+ ## Common Mistakes
758
+
759
+ 1. **DON'T use decorators** - Use primitives (\`$action\`, not \`@Get()\`)
760
+ 2. **DON'T use Zod** - Use TypeBox via \`t\` from Alepha
761
+ 3. **DON'T use Express patterns** - No \`app.get()\`, \`router.use()\`
762
+ 4. **DON'T inject across modules** - Use \`$client\` for cross-module calls
763
+ 5. **DON'T use async constructors** - Use \`$hook({ on: "start" })\`
764
+ 6. **DON'T instantiate manually** - Let DI container manage instances
765
+
766
+ ## After Code Changes
767
+
768
+ Always run:
769
+ \`\`\`bash
770
+ alepha lint # Format and lint
771
+ alepha typecheck # Type checking
772
+ alepha test # Run tests (if applicable)
773
+ alepha build # Build the project
774
+ \`\`\`
775
+
776
+ ## Documentation
777
+
778
+ - Full docs: https://alepha.dev/llms.txt
779
+ - Detailed docs: https://alepha.dev/llms-full.txt
780
+ `.trim();
781
+ };
782
+
153
783
  //#endregion
154
784
  //#region ../../src/cli/assets/dummySpecTs.ts
155
785
  const dummySpecTs = () => `
@@ -191,442 +821,197 @@ const indexHtml = (browserEntry) => `
191
821
  </body>
192
822
  </html>
193
823
  `.trim();
194
-
195
- //#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;
236
-
237
- //#endregion
238
- //#region ../../src/cli/services/AlephaCliUtils.ts
239
- /**
240
- * Utility service for common project operations used by CLI commands.
241
- *
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
248
- */
249
- var AlephaCliUtils = class {
250
- log = $logger();
251
- fs = $inject(FileSystemProvider);
252
- envUtils = $inject(EnvUtils);
253
- alepha = $inject(Alepha);
254
- /**
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
- * ```
262
- */
263
- async exec(command, options = {}) {
264
- const root = process.cwd();
265
- this.log.debug(`Executing command: ${command}`, { cwd: root });
266
- const runExec = async (app$1, args$1) => {
267
- const prog = spawn(app$1, args$1, {
268
- stdio: "inherit",
269
- cwd: root,
270
- env: {
271
- ...process.env,
272
- ...options.env
273
- }
274
- });
275
- await new Promise((resolve) => prog.on("exit", () => {
276
- resolve();
277
- }));
278
- };
279
- if (options.global) {
280
- const [app$1, ...args$1] = command.split(" ");
281
- await runExec(app$1, args$1);
282
- return;
283
- }
284
- const suffix = process.platform === "win32" ? ".cmd" : "";
285
- 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);
288
- if (!execPath) throw new AlephaError(`Could not find executable for command '${app}'. Make sure the package is installed.`);
289
- await runExec(execPath, args);
290
- }
291
- /**
292
- * 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
- */
307
- 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);
312
- this.log.debug(`Config file written: ${path}`);
313
- return path;
314
- }
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);
321
- }
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);
362
- }
363
- /**
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
369
- */
370
- async ensureYarn(root) {
371
- await this.ensureFileExists(root, ".yarnrc.yml", "nodeLinker: node-modules", false);
372
- await this.removeAllPmFilesExcept(root, "yarn");
373
- }
374
- async ensureBun(root) {
375
- await this.removeAllPmFilesExcept(root, "bun");
376
- }
377
- async ensurePnpm(root) {
378
- await this.removeAllPmFilesExcept(root, "pnpm");
379
- }
380
- async ensureNpm(root) {
381
- await this.removeAllPmFilesExcept(root, "npm");
382
- }
824
+
825
+ //#endregion
826
+ //#region ../../src/cli/assets/mainBrowserTs.ts
827
+ const mainBrowserTs = () => `
828
+ import { Alepha, run } from "alepha";
829
+ import { WebModule } from "./web/index.ts";
830
+
831
+ const alepha = Alepha.create();
832
+
833
+ alepha.with(WebModule);
834
+
835
+ run(alepha);
836
+ `.trim();
837
+
838
+ //#endregion
839
+ //#region ../../src/cli/assets/mainServerTs.ts
840
+ const mainServerTs = (options = {}) => {
841
+ const { react = false } = options;
842
+ return `
843
+ import { Alepha, run } from "alepha";
844
+ import { ApiModule } from "./api/index.ts";
845
+ ${react ? `import { WebModule } from "./web/index.ts";\n` : ""}
846
+ const alepha = Alepha.create();
847
+
848
+ alepha.with(ApiModule);
849
+ ${react ? `alepha.with(WebModule);\n` : ""}
850
+ run(alepha);
851
+ `.trim();
852
+ };
853
+
854
+ //#endregion
855
+ //#region ../../src/cli/assets/tsconfigJson.ts
856
+ const tsconfigJson = `
857
+ {
858
+ "extends": "alepha/tsconfig.base"
859
+ }
860
+ `.trim();
861
+
862
+ //#endregion
863
+ //#region ../../src/cli/assets/webAppRouterTs.ts
864
+ const webAppRouterTs = () => `
865
+ import { $page } from "@alepha/react/router";
866
+ import { $client } from "alepha/server/links";
867
+ import type { HelloController } from "../api/controllers/HelloController.ts";
868
+
869
+ export class AppRouter {
870
+ api = $client<HelloController>();
871
+
872
+ home = $page({
873
+ path: "/",
874
+ lazy: () => import("./components/Hello.tsx"),
875
+ loader: () => this.api.hello(),
876
+ });
877
+ }
878
+ `.trim();
879
+
880
+ //#endregion
881
+ //#region ../../src/cli/assets/webHelloComponentTsx.ts
882
+ const webHelloComponentTsx = () => `
883
+ interface Props {
884
+ message: string;
885
+ }
886
+
887
+ const Hello = (props: Props) => {
888
+ return (
889
+ <div>
890
+ <h1>{props.message}</h1>
891
+ <p>Edit this component in src/web/components/Hello.tsx</p>
892
+ </div>
893
+ );
894
+ };
895
+
896
+ export default Hello;
897
+ `.trim();
898
+
899
+ //#endregion
900
+ //#region ../../src/cli/assets/webIndexTs.ts
901
+ const webIndexTs = (options = {}) => {
902
+ const { appName = "app" } = options;
903
+ return `
904
+ import { $module } from "alepha";
905
+ import { AppRouter } from "./AppRouter.ts";
906
+
907
+ export const WebModule = $module({
908
+ name: "${appName}.web",
909
+ services: [AppRouter],
910
+ });
911
+ `.trim();
912
+ };
913
+
914
+ //#endregion
915
+ //#region ../../src/cli/services/ProjectScaffolder.ts
916
+ /**
917
+ * Service for scaffolding new Alepha projects.
918
+ *
919
+ * Handles creation of:
920
+ * - Project structure (src/api, src/web)
921
+ * - Configuration files (tsconfig, biome, editorconfig)
922
+ * - Entry points (main.server.ts, main.browser.ts)
923
+ * - Example code (HelloController, Hello component)
924
+ */
925
+ var ProjectScaffolder = class {
926
+ log = $logger();
927
+ fs = $inject(FileSystemProvider);
928
+ pm = $inject(PackageManagerUtils);
383
929
  /**
384
- * Generate package.json content with Alepha dependencies.
930
+ * Get the app name from the directory name.
385
931
  *
386
- * @param modes - Configuration for which dependencies to include
387
- * @returns Package.json partial with dependencies, devDependencies, and scripts
932
+ * Converts the directory name to a valid module name:
933
+ * - Converts to lowercase
934
+ * - Replaces spaces, dashes, underscores with nothing
935
+ * - Falls back to "app" if empty
388
936
  */
389
- generatePackageJsonContent(modes) {
390
- const dependencies = { alepha: `^${version}` };
391
- const devDependencies = {};
392
- const scripts = {
393
- dev: "alepha dev",
394
- build: "alepha build",
395
- lint: "alepha lint",
396
- typecheck: "alepha typecheck",
397
- verify: "alepha verify"
398
- };
399
- if (modes.ui) {
400
- dependencies["@alepha/ui"] = `^${version}`;
401
- modes.react = true;
402
- }
403
- if (modes.react) {
404
- dependencies["@alepha/react"] = `^${version}`;
405
- dependencies.react = "^19.2.0";
406
- dependencies["react-dom"] = "^19.2.0";
407
- devDependencies["@types/react"] = "^19.2.0";
408
- }
409
- return {
410
- type: "module",
411
- dependencies,
412
- devDependencies,
413
- scripts
414
- };
937
+ getAppName(root) {
938
+ return basename(root).toLowerCase().replace(/[\s\-_]/g, "") || "app";
415
939
  }
416
940
  /**
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
941
+ * Ensure all configuration files exist.
426
942
  */
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
943
  async ensureConfig(root, opts) {
450
944
  const tasks = [];
451
- if (opts.packageJson) tasks.push(this.ensurePackageJson(root, typeof opts.packageJson === "boolean" ? {} : opts.packageJson));
945
+ if (opts.packageJson) tasks.push(this.pm.ensurePackageJson(root, typeof opts.packageJson === "boolean" ? {} : opts.packageJson).then(() => {}));
452
946
  if (opts.tsconfigJson) tasks.push(this.ensureTsConfig(root));
453
- if (opts.indexHtml) tasks.push(this.ensureIndexHtml(root));
947
+ if (opts.indexHtml) tasks.push(this.ensureReactProject(root));
454
948
  if (opts.biomeJson) tasks.push(this.ensureBiomeConfig(root));
455
949
  if (opts.editorconfig) tasks.push(this.ensureEditorConfig(root));
456
- return await Promise.all(tasks);
950
+ if (opts.claudeMd) tasks.push(this.ensureClaudeMd(root, typeof opts.claudeMd === "boolean" ? {} : opts.claudeMd));
951
+ await Promise.all(tasks);
457
952
  }
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
953
  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
- }
954
+ if (await this.existsInParents(root, "tsconfig.json")) return;
955
+ await this.fs.writeFile(this.fs.join(root, "tsconfig.json"), tsconfigJson);
491
956
  }
492
- async ensureFileExists(root, name, content, checkParentDirectories = false) {
493
- if (!await this.checkFileExists(root, name, checkParentDirectories)) await writeFile(join(root, name), content);
494
- }
495
- /**
496
- * Get the path to Biome configuration file.
497
- *
498
- * Looks for an existing biome.json in the project root, or creates one if it doesn't exist.
499
- */
500
957
  async ensureBiomeConfig(root) {
501
- await this.ensureFileExists(root, "biome.json", biomeJson, true);
958
+ await this.ensureFileIfNotExists(root, "biome.json", biomeJson);
502
959
  }
503
- /**
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
509
- */
510
960
  async ensureEditorConfig(root) {
511
- await this.ensureFileExists(root, ".editorconfig", editorconfig, true);
512
- }
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}`);
961
+ await this.ensureFileIfNotExists(root, ".editorconfig", editorconfig);
540
962
  }
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();
963
+ async ensureClaudeMd(root, options = {}) {
964
+ const path = this.fs.join(root, "CLAUDE.md");
965
+ if (!await this.fs.exists(path)) await this.fs.writeFile(path, claudeMd(options));
565
966
  }
566
967
  /**
567
- * Load environment variables from a .env file.
968
+ * Ensure src/main.server.ts exists with full API structure.
568
969
  *
569
- * Reads the .env file in the specified root directory and sets
570
- * the environment variables in process.env.
970
+ * Creates:
971
+ * - src/main.server.ts (entry point)
972
+ * - src/api/index.ts (API module)
973
+ * - src/api/controllers/HelloController.ts (example controller)
571
974
  */
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));
975
+ async ensureApiProject(root) {
976
+ const srcDir = this.fs.join(root, "src");
977
+ if (await this.fs.exists(srcDir)) {
978
+ if ((await this.fs.ls(srcDir)).length > 0) return;
979
+ }
980
+ const appName = this.getAppName(root);
981
+ await this.fs.mkdir(this.fs.join(root, "src/api/controllers"), { recursive: true });
982
+ await this.fs.writeFile(this.fs.join(srcDir, "main.server.ts"), mainServerTs());
983
+ await this.fs.writeFile(this.fs.join(srcDir, "api/index.ts"), apiIndexTs({ appName }));
984
+ await this.fs.writeFile(this.fs.join(srcDir, "api/controllers/HelloController.ts"), apiHelloControllerTs());
600
985
  }
601
986
  /**
602
- * Ensure src/main.ts exists with a minimal Alepha bootstrap.
987
+ * Ensure full React project structure exists.
603
988
  *
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
989
+ * Creates:
990
+ * - index.html
991
+ * - src/main.server.ts, src/main.browser.ts
992
+ * - src/api/index.ts, src/api/controllers/HelloController.ts
993
+ * - src/web/index.ts, src/web/AppRouter.ts, src/web/components/Hello.tsx
608
994
  */
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());
995
+ async ensureReactProject(root) {
996
+ if (await this.fs.exists(this.fs.join(root, "index.html"))) return;
997
+ const appName = this.getAppName(root);
998
+ await this.fs.mkdir(this.fs.join(root, "src/api/controllers"), { recursive: true });
999
+ await this.fs.mkdir(this.fs.join(root, "src/web/components"), { recursive: true });
1000
+ await this.fs.writeFile(this.fs.join(root, "index.html"), indexHtml("src/main.browser.ts"));
1001
+ await this.ensureFileIfNotExists(root, "src/api/index.ts", apiIndexTs({ appName }));
1002
+ await this.ensureFileIfNotExists(root, "src/api/controllers/HelloController.ts", apiHelloControllerTs());
1003
+ await this.ensureFileIfNotExists(root, "src/main.server.ts", mainServerTs({ react: true }));
1004
+ await this.ensureFileIfNotExists(root, "src/web/index.ts", webIndexTs({ appName }));
1005
+ await this.ensureFileIfNotExists(root, "src/web/AppRouter.ts", webAppRouterTs());
1006
+ await this.ensureFileIfNotExists(root, "src/web/components/Hello.tsx", webHelloComponentTsx());
1007
+ await this.ensureFileIfNotExists(root, "src/main.browser.ts", mainBrowserTs());
618
1008
  }
619
1009
  /**
620
1010
  * 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
1011
  */
627
1012
  async ensureTestDir(root) {
628
- const testDir = join(root, "test");
629
- const dummyPath = join(testDir, "dummy.spec.ts");
1013
+ const testDir = this.fs.join(root, "test");
1014
+ const dummyPath = this.fs.join(testDir, "dummy.spec.ts");
630
1015
  if (!await this.fs.exists(testDir)) {
631
1016
  await this.fs.mkdir(testDir, { recursive: true });
632
1017
  await this.fs.writeFile(dummyPath, dummySpecTs());
@@ -634,68 +1019,20 @@ ${models.map((it) => `export const ${it} = models["${it}"];`).join("\n")}
634
1019
  }
635
1020
  if ((await this.fs.ls(testDir)).length === 0) await this.fs.writeFile(dummyPath, dummySpecTs());
636
1021
  }
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
- /**
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
661
- */
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();
1022
+ async ensureFileIfNotExists(root, relativePath, content) {
1023
+ const fullPath = this.fs.join(root, relativePath);
1024
+ if (!await this.fs.exists(fullPath)) await this.fs.writeFile(fullPath, content);
681
1025
  }
682
1026
  /**
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.
1027
+ * Check if a file exists in the given directory or any parent directory.
687
1028
  */
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 });
1029
+ async existsInParents(root, filename) {
1030
+ let current = root;
1031
+ while (true) {
1032
+ if (await this.fs.exists(this.fs.join(current, filename))) return true;
1033
+ const parent = dirname(current);
1034
+ if (parent === current) return false;
1035
+ current = parent;
699
1036
  }
700
1037
  }
701
1038
  };
@@ -704,7 +1041,10 @@ ${models.map((it) => `export const ${it} = models["${it}"];`).join("\n")}
704
1041
  //#region ../../src/cli/commands/build.ts
705
1042
  var BuildCommand = class {
706
1043
  log = $logger();
1044
+ fs = $inject(FileSystemProvider);
707
1045
  utils = $inject(AlephaCliUtils);
1046
+ pm = $inject(PackageManagerUtils);
1047
+ scaffolder = $inject(ProjectScaffolder);
708
1048
  options = $use(buildOptions);
709
1049
  build = $command({
710
1050
  name: "build",
@@ -719,27 +1059,27 @@ var BuildCommand = class {
719
1059
  vercel: t.optional(t.boolean({ description: "Generate Vercel deployment configuration" })),
720
1060
  cloudflare: t.optional(t.boolean({ description: "Generate Cloudflare Workers configuration" })),
721
1061
  docker: t.optional(t.boolean({ description: "Generate Docker configuration" })),
722
- sitemap: t.optional(t.text({ description: "Generate sitemap.xml with base URL" }))
1062
+ sitemap: t.optional(t.text({ description: "Generate sitemap.xml with base URL" })),
1063
+ bun: t.optional(t.boolean({ description: "Prioritize .bun.ts entry files for Bun runtime" }))
723
1064
  }),
724
1065
  handler: async ({ flags, args, run, root }) => {
725
1066
  process.env.ALEPHA_BUILD_MODE = "cli";
726
1067
  process.env.NODE_ENV = "production";
727
- if (await this.utils.hasExpo(root)) return;
728
- await this.utils.ensureConfig(root, { tsconfigJson: true });
1068
+ if (await this.pm.hasExpo(root)) return;
1069
+ await this.scaffolder.ensureConfig(root, { tsconfigJson: true });
729
1070
  const entry = await boot.getServerEntry(root, args);
730
1071
  this.log.trace("Entry file found", { entry });
731
1072
  const distDir = "dist";
732
1073
  const clientDir = "public";
733
- await this.utils.ensureDependency(root, "vite", { run });
1074
+ await this.pm.ensureDependency(root, "vite", {
1075
+ run,
1076
+ exec: (cmd, opts) => this.utils.exec(cmd, opts)
1077
+ });
734
1078
  await run.rm("dist", { alias: "clean dist" });
735
1079
  const options = this.options;
736
1080
  await this.utils.loadEnv(root, [".env", ".env.production"]);
737
1081
  const stats = flags.stats ?? options.stats ?? false;
738
- let hasClient = false;
739
- try {
740
- await access(join(root, "index.html"));
741
- hasClient = true;
742
- } catch {}
1082
+ const hasClient = await this.fs.exists(this.fs.join(root, "index.html"));
743
1083
  if (hasClient) await run({
744
1084
  name: "vite build client",
745
1085
  handler: () => buildClient({
@@ -752,19 +1092,20 @@ var BuildCommand = class {
752
1092
  await run({
753
1093
  name: "vite build server",
754
1094
  handler: async () => {
755
- let clientBuilt = false;
756
- try {
757
- await readFile(`${distDir}/${clientDir}/index.html`, "utf-8");
758
- clientBuilt = true;
759
- } catch {}
1095
+ const clientIndexPath = `${distDir}/${clientDir}/index.html`;
1096
+ const clientBuilt = await this.fs.exists(clientIndexPath);
1097
+ const conditions = [];
1098
+ if (flags.bun) conditions.push("bun");
1099
+ if (options.cloudflare) conditions.push("workerd");
760
1100
  await buildServer({
761
1101
  silent: true,
762
1102
  entry,
763
1103
  distDir,
764
1104
  clientDir: clientBuilt ? clientDir : void 0,
765
- stats
1105
+ stats,
1106
+ conditions
766
1107
  });
767
- if (clientBuilt) await unlink(`${distDir}/${clientDir}/index.html`);
1108
+ if (clientBuilt) await this.fs.rm(clientIndexPath);
768
1109
  }
769
1110
  });
770
1111
  await copyAssets({
@@ -778,7 +1119,7 @@ var BuildCommand = class {
778
1119
  if (sitemapHostname) await run({
779
1120
  name: "add sitemap",
780
1121
  handler: async () => {
781
- await writeFile(`${distDir}/${clientDir}/sitemap.xml`, await generateSitemap({
1122
+ await this.fs.writeFile(`${distDir}/${clientDir}/sitemap.xml`, await generateSitemap({
782
1123
  entry: `${distDir}/index.js`,
783
1124
  baseUrl: sitemapHostname
784
1125
  }));
@@ -844,6 +1185,7 @@ const drizzleCommandFlags = t.object({
844
1185
  });
845
1186
  var DbCommand = class {
846
1187
  log = $logger();
1188
+ fs = $inject(FileSystemProvider);
847
1189
  utils = $inject(AlephaCliUtils);
848
1190
  /**
849
1191
  * Check if database migrations are up to date.
@@ -868,15 +1210,16 @@ var DbCommand = class {
868
1210
  const providerName = provider.name;
869
1211
  if (accepted.has(providerName)) continue;
870
1212
  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) {
1213
+ const migrationDir = this.fs.join(rootDir, "migrations", providerName);
1214
+ const journalBuffer = await this.fs.readFile(`${migrationDir}/meta/_journal.json`).catch(() => null);
1215
+ if (!journalBuffer) {
874
1216
  this.log.info("No migration journal found.");
875
1217
  return;
876
1218
  }
877
- const journal = JSON.parse(journalFile);
1219
+ const journal = JSON.parse(journalBuffer.toString("utf-8"));
878
1220
  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"));
1221
+ const snapshotBuffer = await this.fs.readFile(`${migrationDir}/meta/${String(lastMigration.idx).padStart(4, "0")}_snapshot.json`);
1222
+ const lastSnapshot = JSON.parse(snapshotBuffer.toString("utf-8"));
880
1223
  const models = drizzleKitProvider.getModels(provider);
881
1224
  const kit = drizzleKitProvider.importDrizzleKit();
882
1225
  const now = kit.generateDrizzleJson(models, lastSnapshot.id);
@@ -1030,6 +1373,7 @@ var DbCommand = class {
1030
1373
  const provider = primitive.provider;
1031
1374
  const providerName = provider.name;
1032
1375
  const dialect = provider.dialect;
1376
+ if (providerName === "") continue;
1033
1377
  if (accepted.has(providerName)) continue;
1034
1378
  accepted.add(providerName);
1035
1379
  if (options.provider && options.provider !== providerName) {
@@ -1043,6 +1387,7 @@ var DbCommand = class {
1043
1387
  provider,
1044
1388
  providerName,
1045
1389
  providerUrl: provider.url,
1390
+ providerDriver: provider.driver,
1046
1391
  dialect,
1047
1392
  entry,
1048
1393
  rootDir
@@ -1064,9 +1409,9 @@ var DbCommand = class {
1064
1409
  dbCredentials: { url: options.providerUrl }
1065
1410
  };
1066
1411
  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") {
1412
+ if (options.providerDriver === "d1") config.driver = "d1-http";
1413
+ if (options.providerDriver === "pglite") config.driver = "pglite";
1414
+ if (options.dialect === "sqlite") if (options.providerDriver === "d1") {
1070
1415
  const token = process.env.CLOUDFLARE_API_TOKEN;
1071
1416
  if (!token) throw new AlephaError("CLOUDFLARE_API_TOKEN environment variable is not set. https://orm.drizzle.team/docs/guides/d1-http-with-drizzle-kit");
1072
1417
  const accountId = process.env.CLOUDFLARE_ACCOUNT_ID;
@@ -1083,7 +1428,7 @@ var DbCommand = class {
1083
1428
  } else {
1084
1429
  let url = options.providerUrl;
1085
1430
  url = url.replace("sqlite://", "").replace("file://", "");
1086
- url = join(options.rootDir, url);
1431
+ url = this.fs.join(options.rootDir, url);
1087
1432
  config.dbCredentials = { url };
1088
1433
  }
1089
1434
  const drizzleConfigJs = `export default ${JSON.stringify(config, null, 2)}`;
@@ -1095,7 +1440,9 @@ var DbCommand = class {
1095
1440
  //#region ../../src/cli/commands/deploy.ts
1096
1441
  var DeployCommand = class {
1097
1442
  log = $logger();
1443
+ fs = $inject(FileSystemProvider);
1098
1444
  utils = $inject(AlephaCliUtils);
1445
+ pm = $inject(PackageManagerUtils);
1099
1446
  /**
1100
1447
  * Deploy the project to a hosting platform (e.g., Vercel, Cloudflare, Surge)
1101
1448
  *
@@ -1143,7 +1490,10 @@ var DeployCommand = class {
1143
1490
  this.log.debug("Running database migrations before deployment...");
1144
1491
  await this.utils.exec(`alepha db migrate --mode=${mode}`);
1145
1492
  }
1146
- await this.utils.ensureDependency(root, "vercel", { dev: true });
1493
+ await this.pm.ensureDependency(root, "vercel", {
1494
+ dev: true,
1495
+ exec: (cmd, opts) => this.utils.exec(cmd, opts)
1496
+ });
1147
1497
  const command = `vercel . --cwd=dist ${mode === "production" ? "--prod" : ""}`.trim();
1148
1498
  this.log.debug(`Deploying to Vercel with command: ${command}`);
1149
1499
  await this.utils.exec(command);
@@ -1154,15 +1504,21 @@ var DeployCommand = class {
1154
1504
  this.log.debug("Running database migrations before deployment...");
1155
1505
  await this.utils.exec(`alepha db migrate --mode=${mode}`);
1156
1506
  }
1157
- await this.utils.ensureDependency(root, "wrangler", { dev: true });
1507
+ await this.pm.ensureDependency(root, "wrangler", {
1508
+ dev: true,
1509
+ exec: (cmd, opts) => this.utils.exec(cmd, opts)
1510
+ });
1158
1511
  const command = `wrangler deploy ${mode === "production" ? "" : "--env preview"} --config=dist/wrangler.jsonc`.trim();
1159
1512
  this.log.info(`Deploying to Cloudflare with command: ${command}`);
1160
1513
  await this.utils.exec(command);
1161
1514
  return;
1162
1515
  }
1163
1516
  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");
1517
+ await this.pm.ensureDependency(root, "surge", {
1518
+ dev: true,
1519
+ exec: (cmd, opts) => this.utils.exec(cmd, opts)
1520
+ });
1521
+ const distPath = this.fs.join(root, "dist/public");
1166
1522
  this.log.debug(`Deploying to Surge from directory: ${distPath}`);
1167
1523
  await this.utils.exec(`surge ${distPath}`);
1168
1524
  return;
@@ -1176,7 +1532,10 @@ var DeployCommand = class {
1176
1532
  //#region ../../src/cli/commands/dev.ts
1177
1533
  var DevCommand = class {
1178
1534
  log = $logger();
1535
+ fs = $inject(FileSystemProvider);
1179
1536
  utils = $inject(AlephaCliUtils);
1537
+ pm = $inject(PackageManagerUtils);
1538
+ scaffolder = $inject(ProjectScaffolder);
1180
1539
  alepha = $inject(Alepha);
1181
1540
  /**
1182
1541
  * Will run the project in watch mode.
@@ -1192,8 +1551,8 @@ var DevCommand = class {
1192
1551
  description: "Filepath to run"
1193
1552
  })),
1194
1553
  handler: async ({ args, root }) => {
1195
- const expo = await this.utils.hasExpo(root);
1196
- await this.utils.ensureConfig(root, { tsconfigJson: true });
1554
+ const expo = await this.pm.hasExpo(root);
1555
+ await this.scaffolder.ensureConfig(root, { tsconfigJson: true });
1197
1556
  if (expo) {
1198
1557
  await this.utils.exec("expo start");
1199
1558
  return;
@@ -1208,26 +1567,16 @@ var DevCommand = class {
1208
1567
  await this.utils.exec(cmd, { global: exe === "bun" });
1209
1568
  return;
1210
1569
  }
1211
- await this.utils.ensureDependency(root, "vite");
1570
+ await this.pm.ensureDependency(root, "vite", { exec: (cmd, opts) => this.utils.exec(cmd, opts) });
1212
1571
  await devServer();
1213
1572
  }
1214
1573
  });
1215
1574
  async isBunProject(root) {
1216
1575
  if (this.alepha.isBun()) return true;
1217
- try {
1218
- await access(join(root, "bun.lock"));
1219
- return true;
1220
- } catch {
1221
- return false;
1222
- }
1576
+ return this.fs.exists(this.fs.join(root, "bun.lock"));
1223
1577
  }
1224
1578
  async isFullstackProject(root) {
1225
- try {
1226
- await access(join(root, "index.html"));
1227
- return true;
1228
- } catch {
1229
- return false;
1230
- }
1579
+ return this.fs.exists(this.fs.join(root, "index.html"));
1231
1580
  }
1232
1581
  };
1233
1582
 
@@ -1235,12 +1584,14 @@ var DevCommand = class {
1235
1584
  //#region ../../src/cli/commands/format.ts
1236
1585
  var FormatCommand = class {
1237
1586
  utils = $inject(AlephaCliUtils);
1587
+ pm = $inject(PackageManagerUtils);
1588
+ scaffolder = $inject(ProjectScaffolder);
1238
1589
  format = $command({
1239
1590
  name: "format",
1240
1591
  description: "Format the codebase using Biome",
1241
1592
  handler: async ({ root }) => {
1242
- await this.utils.ensureConfig(root, { biomeJson: true });
1243
- await this.utils.ensureDependency(root, "@biomejs/biome");
1593
+ await this.scaffolder.ensureConfig(root, { biomeJson: true });
1594
+ await this.pm.ensureDependency(root, "@biomejs/biome", { exec: (cmd, opts) => this.utils.exec(cmd, opts) });
1244
1595
  await this.utils.exec("biome format --fix");
1245
1596
  }
1246
1597
  });
@@ -1560,6 +1911,9 @@ var GenCommand = class {
1560
1911
  //#region ../../src/cli/commands/init.ts
1561
1912
  var InitCommand = class {
1562
1913
  utils = $inject(AlephaCliUtils);
1914
+ pm = $inject(PackageManagerUtils);
1915
+ scaffolder = $inject(ProjectScaffolder);
1916
+ fs = $inject(FileSystemProvider);
1563
1917
  /**
1564
1918
  * Ensure the project has the necessary Alepha configuration files.
1565
1919
  * Add the correct dependencies to package.json and install them.
@@ -1567,45 +1921,78 @@ var InitCommand = class {
1567
1921
  init = $command({
1568
1922
  name: "init",
1569
1923
  description: "Add missing Alepha configuration files to the project",
1924
+ args: t.optional(t.text({
1925
+ title: "path",
1926
+ trim: true,
1927
+ lowercase: true
1928
+ })),
1570
1929
  flags: t.object({
1930
+ agent: t.optional(t.boolean({
1931
+ aliases: ["a"],
1932
+ description: "Add CLAUDE.md for Claude Code AI assistant"
1933
+ })),
1571
1934
  yarn: t.optional(t.boolean({ description: "Use Yarn package manager" })),
1572
1935
  pnpm: t.optional(t.boolean({ description: "Use pnpm package manager" })),
1573
1936
  npm: t.optional(t.boolean({ description: "Use npm package manager" })),
1574
1937
  bun: t.optional(t.boolean({ description: "Use Bun package manager" })),
1575
- react: t.optional(t.boolean({ description: "Include Alepha React dependencies" })),
1576
- ui: t.optional(t.boolean({ description: "Include Alepha UI dependencies" })),
1938
+ web: t.optional(t.boolean({
1939
+ aliases: ["r"],
1940
+ description: "Include Alepha React dependencies"
1941
+ })),
1942
+ admin: t.optional(t.boolean({ description: "Include Alepha UI dependencies" })),
1577
1943
  test: t.optional(t.boolean({ description: "Include Vitest and create test directory" }))
1578
1944
  }),
1579
- handler: async ({ run, flags, root }) => {
1580
- if (flags.ui) flags.react = true;
1581
- const isExpo = await this.utils.hasExpo(root);
1945
+ handler: async ({ run, flags, root, args }) => {
1946
+ if (flags.admin) flags.web = true;
1947
+ if (args) {
1948
+ root = this.fs.join(root, args);
1949
+ await this.fs.mkdir(root);
1950
+ }
1951
+ const isExpo = await this.pm.hasExpo(root);
1582
1952
  await run({
1583
1953
  name: "ensuring configuration files",
1584
1954
  handler: async () => {
1585
- await this.utils.ensureConfig(root, {
1955
+ await this.scaffolder.ensureConfig(root, {
1586
1956
  tsconfigJson: true,
1587
1957
  packageJson: flags,
1588
1958
  biomeJson: true,
1589
1959
  editorconfig: true,
1590
- indexHtml: !!flags.react && !isExpo
1960
+ indexHtml: !!flags.web && !isExpo,
1961
+ claudeMd: flags.agent ? {
1962
+ react: !!flags.web,
1963
+ ui: !!flags.admin
1964
+ } : false
1591
1965
  });
1592
- if (!flags.react) await this.utils.ensureSrcMain(root);
1966
+ if (!flags.web) await this.scaffolder.ensureApiProject(root);
1593
1967
  }
1594
1968
  });
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 });
1969
+ const pmName = await this.pm.getPackageManager(root, flags);
1970
+ if (pmName === "yarn") {
1971
+ await this.pm.ensureYarn(root);
1972
+ await run("yarn set version stable", { root });
1973
+ } else if (pmName === "bun") await this.pm.ensureBun(root);
1974
+ else if (pmName === "pnpm") await this.pm.ensurePnpm(root);
1975
+ else await this.pm.ensureNpm(root);
1976
+ await run(`${pmName} install`, {
1977
+ alias: `installing dependencies with ${pmName}`,
1978
+ root
1979
+ });
1980
+ if (!isExpo) await this.pm.ensureDependency(root, "vite", {
1981
+ run,
1982
+ exec: (cmd, opts) => this.utils.exec(cmd, opts)
1983
+ });
1984
+ await this.pm.ensureDependency(root, "@biomejs/biome", {
1985
+ run,
1986
+ exec: (cmd, opts) => this.utils.exec(cmd, opts)
1987
+ });
1605
1988
  if (flags.test) {
1606
- await this.utils.ensureTestDir(root);
1607
- await run(`${pm} ${pm === "yarn" ? "add" : "install"} -D vitest`, { alias: "setup testing with Vitest" });
1989
+ await this.scaffolder.ensureTestDir(root);
1990
+ await run(`${pmName} ${pmName === "yarn" ? "add" : "install"} -D vitest`, { alias: "setup testing with Vitest" });
1608
1991
  }
1992
+ await run(`${pmName} run lint`, {
1993
+ alias: "running linter",
1994
+ root
1995
+ });
1609
1996
  }
1610
1997
  });
1611
1998
  };
@@ -1614,12 +2001,14 @@ var InitCommand = class {
1614
2001
  //#region ../../src/cli/commands/lint.ts
1615
2002
  var LintCommand = class {
1616
2003
  utils = $inject(AlephaCliUtils);
2004
+ pm = $inject(PackageManagerUtils);
2005
+ scaffolder = $inject(ProjectScaffolder);
1617
2006
  lint = $command({
1618
2007
  name: "lint",
1619
2008
  description: "Run linter across the codebase using Biome",
1620
2009
  handler: async ({ root }) => {
1621
- await this.utils.ensureConfig(root, { biomeJson: true });
1622
- await this.utils.ensureDependency(root, "@biomejs/biome");
2010
+ await this.scaffolder.ensureConfig(root, { biomeJson: true });
2011
+ await this.pm.ensureDependency(root, "@biomejs/biome", { exec: (cmd, opts) => this.utils.exec(cmd, opts) });
1623
2012
  await this.utils.exec("biome check --formatter-enabled=false --fix");
1624
2013
  }
1625
2014
  });
@@ -1657,6 +2046,8 @@ var RootCommand = class {
1657
2046
  //#region ../../src/cli/commands/test.ts
1658
2047
  var TestCommand = class {
1659
2048
  utils = $inject(AlephaCliUtils);
2049
+ pm = $inject(PackageManagerUtils);
2050
+ scaffolder = $inject(ProjectScaffolder);
1660
2051
  test = $command({
1661
2052
  name: "test",
1662
2053
  description: "Run tests using Vitest",
@@ -1669,8 +2060,8 @@ var TestCommand = class {
1669
2060
  description: "Additional arguments to pass to Vitest. E.g., --coverage"
1670
2061
  })) }),
1671
2062
  handler: async ({ root, flags, env }) => {
1672
- await this.utils.ensureConfig(root, { tsconfigJson: true });
1673
- await this.utils.ensureDependency(root, "vitest");
2063
+ await this.scaffolder.ensureConfig(root, { tsconfigJson: true });
2064
+ await this.pm.ensureDependency(root, "vitest", { exec: (cmd, opts) => this.utils.exec(cmd, opts) });
1674
2065
  const config = flags.config ? `--config=${flags.config}` : "";
1675
2066
  await this.utils.exec(`vitest run ${config} ${env.VITEST_ARGS}`);
1676
2067
  }
@@ -1681,6 +2072,7 @@ var TestCommand = class {
1681
2072
  //#region ../../src/cli/commands/typecheck.ts
1682
2073
  var TypecheckCommand = class {
1683
2074
  utils = $inject(AlephaCliUtils);
2075
+ pm = $inject(PackageManagerUtils);
1684
2076
  log = $logger();
1685
2077
  /**
1686
2078
  * Run TypeScript type checking across the codebase with no emit.
@@ -1691,7 +2083,7 @@ var TypecheckCommand = class {
1691
2083
  description: "Check TypeScript types across the codebase",
1692
2084
  handler: async ({ root }) => {
1693
2085
  this.log.info("Starting TypeScript type checking...");
1694
- await this.utils.ensureDependency(root, "typescript");
2086
+ await this.pm.ensureDependency(root, "typescript", { exec: (cmd, opts) => this.utils.exec(cmd, opts) });
1695
2087
  await this.utils.exec("tsc --noEmit");
1696
2088
  this.log.info("TypeScript type checking completed successfully.");
1697
2089
  }
@@ -1702,6 +2094,7 @@ var TypecheckCommand = class {
1702
2094
  //#region ../../src/cli/commands/verify.ts
1703
2095
  var VerifyCommand = class {
1704
2096
  utils = $inject(AlephaCliUtils);
2097
+ pm = $inject(PackageManagerUtils);
1705
2098
  /**
1706
2099
  * Run a series of verification commands to ensure code quality and correctness.
1707
2100
  *
@@ -1723,9 +2116,9 @@ var VerifyCommand = class {
1723
2116
  await run("alepha format");
1724
2117
  await run("alepha lint");
1725
2118
  await run("alepha typecheck");
1726
- if ((await this.utils.readPackageJson(root)).devDependencies?.vitest) await run("alepha test");
2119
+ if ((await this.pm.readPackageJson(root)).devDependencies?.vitest) await run("alepha test");
1727
2120
  if (await this.utils.exists(root, "migrations")) await run("alepha db check-migrations");
1728
- if (!await this.utils.hasExpo(root)) await run("alepha build");
2121
+ if (!await this.pm.hasExpo(root)) await run("alepha build");
1729
2122
  await run("alepha clean");
1730
2123
  }
1731
2124
  });
@@ -1791,11 +2184,11 @@ var AlephaPackageBuilderCli = class {
1791
2184
  root: true,
1792
2185
  handler: async ({ run, root }) => {
1793
2186
  const modules = [];
1794
- const pkg = await readFile("package.json", "utf-8");
1795
- const pkgData = JSON.parse(pkg);
2187
+ const pkgBuffer = await this.fs.readFile("package.json");
2188
+ const pkgData = JSON.parse(pkgBuffer.toString("utf-8"));
1796
2189
  const packageName = pkgData.name;
1797
2190
  await run("analyze modules", async () => {
1798
- modules.push(...await analyzeModules(join(root, this.src), packageName));
2191
+ modules.push(...await analyzeModules(this.fs.join(root, this.src), packageName));
1799
2192
  });
1800
2193
  pkgData.exports = {};
1801
2194
  for (const item of modules) {
@@ -1807,6 +2200,7 @@ var AlephaPackageBuilderCli = class {
1807
2200
  if (item.native) pkgData.exports[path]["react-native"] = `./src/${item.name}/index.native.ts`;
1808
2201
  else if (item.browser) pkgData.exports[path]["react-native"] = `./src/${item.name}/index.browser.ts`;
1809
2202
  if (item.browser) pkgData.exports[path].browser = `./src/${item.name}/index.browser.ts`;
2203
+ if (item.bun) pkgData.exports[path].bun = `./src/${item.name}/index.bun.ts`;
1810
2204
  pkgData.exports[path].import = `./src/${item.name}/index.ts`;
1811
2205
  pkgData.exports[path].default = `./src/${item.name}/index.ts`;
1812
2206
  }
@@ -1819,33 +2213,30 @@ var AlephaPackageBuilderCli = class {
1819
2213
  pkgData.exports["./json/styles"] = "./src/json/styles.css";
1820
2214
  }
1821
2215
  await this.fs.writeFile("package.json", JSON.stringify(pkgData, null, 2));
1822
- const tmpDir = join(root, "node_modules/.alepha");
2216
+ const tmpDir = this.fs.join(root, "node_modules/.alepha");
1823
2217
  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);
2218
+ await this.fs.writeFile(this.fs.join(tmpDir, "module-dependencies.json"), JSON.stringify(modules, null, 2));
2219
+ const tsconfigBuffer = await this.fs.readFile(this.fs.join(root, "../../tsconfig.json"));
2220
+ const external = Object.keys(JSON.parse(tsconfigBuffer.toString("utf-8")).compilerOptions.paths);
1827
2221
  external.push("bun");
1828
2222
  external.push("bun:sqlite");
1829
2223
  await run.rm(this.dist);
1830
2224
  const build = async (item) => {
1831
2225
  const entries = [];
1832
- const src = join(root, this.src, item.name);
1833
- const dest = join(root, this.dist, item.name);
2226
+ const src = this.fs.join(root, this.src, item.name);
2227
+ const dest = this.fs.join(root, this.dist, item.name);
1834
2228
  entries.push({
1835
- entry: join(src, "index.ts"),
2229
+ entry: this.fs.join(src, "index.ts"),
1836
2230
  outDir: dest,
1837
2231
  format: ["esm"],
1838
2232
  sourcemap: true,
1839
2233
  fixedExtension: false,
1840
2234
  platform: "node",
1841
2235
  external,
1842
- dts: {
1843
- sourcemap: true,
1844
- resolve: false
1845
- }
2236
+ dts: { sourcemap: true }
1846
2237
  });
1847
2238
  if (item.native) entries.push({
1848
- entry: join(src, "index.native.ts"),
2239
+ entry: this.fs.join(src, "index.native.ts"),
1849
2240
  outDir: dest,
1850
2241
  platform: "neutral",
1851
2242
  sourcemap: true,
@@ -1853,14 +2244,23 @@ var AlephaPackageBuilderCli = class {
1853
2244
  external
1854
2245
  });
1855
2246
  if (item.browser) entries.push({
1856
- entry: join(src, "index.browser.ts"),
2247
+ entry: this.fs.join(src, "index.browser.ts"),
1857
2248
  outDir: dest,
1858
2249
  platform: "browser",
1859
2250
  sourcemap: true,
1860
2251
  dts: false,
1861
2252
  external
1862
2253
  });
1863
- const config = join(tmpDir, `tsdown-${item.name.replace("/", "-")}.config.js`);
2254
+ if (item.bun) entries.push({
2255
+ entry: this.fs.join(src, "index.bun.ts"),
2256
+ outDir: dest,
2257
+ platform: "node",
2258
+ sourcemap: true,
2259
+ fixedExtension: false,
2260
+ dts: false,
2261
+ external
2262
+ });
2263
+ const config = this.fs.join(tmpDir, `tsdown-${item.name.replace("/", "-")}.config.js`);
1864
2264
  await this.fs.writeFile(config, `export default ${JSON.stringify(entries, null, 2)};`);
1865
2265
  await run(`npx tsdown -c=${config}`);
1866
2266
  };
@@ -1934,7 +2334,7 @@ function detectCircularDependencies(modules) {
1934
2334
  }
1935
2335
  for (const module of modules) {
1936
2336
  const cycle = hasCycle(module.name);
1937
- if (cycle) throw new Error(`Circular dependency detected: ${cycle.join(" -> ")}`);
2337
+ if (cycle) throw new AlephaError(`Circular dependency detected: ${cycle.join(" -> ")}`);
1938
2338
  }
1939
2339
  }
1940
2340
  async function analyzeModules(srcDir, packageName) {
@@ -1948,6 +2348,7 @@ async function analyzeModules(srcDir, packageName) {
1948
2348
  const dependencies = /* @__PURE__ */ new Set();
1949
2349
  const hasBrowser = await fileExists(join(modulePath, "index.browser.ts"));
1950
2350
  const hasNative = await fileExists(join(modulePath, "index.native.ts"));
2351
+ const hasBun = await fileExists(join(modulePath, "index.bun.ts"));
1951
2352
  const hasNode = await fileExists(join(modulePath, "index.node.ts"));
1952
2353
  const files = await getAllFiles(modulePath);
1953
2354
  for (const file of files) {
@@ -1964,6 +2365,7 @@ async function analyzeModules(srcDir, packageName) {
1964
2365
  };
1965
2366
  if (hasNative) module.native = true;
1966
2367
  if (hasBrowser) module.browser = true;
2368
+ if (hasBun) module.bun = true;
1967
2369
  if (hasNode) module.node = true;
1968
2370
  modules.push(module);
1969
2371
  } else await scanDirectory(modulePath, moduleName);