alepha 0.15.0 → 0.15.2

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 (551) hide show
  1. package/README.md +43 -98
  2. package/dist/api/audits/index.d.ts +630 -653
  3. package/dist/api/audits/index.d.ts.map +1 -1
  4. package/dist/api/audits/index.js +12 -35
  5. package/dist/api/audits/index.js.map +1 -1
  6. package/dist/api/files/index.d.ts +365 -358
  7. package/dist/api/files/index.d.ts.map +1 -1
  8. package/dist/api/files/index.js +12 -5
  9. package/dist/api/files/index.js.map +1 -1
  10. package/dist/api/jobs/index.d.ts +255 -248
  11. package/dist/api/jobs/index.d.ts.map +1 -1
  12. package/dist/api/jobs/index.js +10 -3
  13. package/dist/api/jobs/index.js.map +1 -1
  14. package/dist/api/keys/index.d.ts +413 -0
  15. package/dist/api/keys/index.d.ts.map +1 -0
  16. package/dist/api/keys/index.js +476 -0
  17. package/dist/api/keys/index.js.map +1 -0
  18. package/dist/api/notifications/index.browser.js +4 -4
  19. package/dist/api/notifications/index.browser.js.map +1 -1
  20. package/dist/api/notifications/index.d.ts +84 -78
  21. package/dist/api/notifications/index.d.ts.map +1 -1
  22. package/dist/api/notifications/index.js +14 -8
  23. package/dist/api/notifications/index.js.map +1 -1
  24. package/dist/api/parameters/index.d.ts +528 -535
  25. package/dist/api/parameters/index.d.ts.map +1 -1
  26. package/dist/api/parameters/index.js +30 -37
  27. package/dist/api/parameters/index.js.map +1 -1
  28. package/dist/api/users/index.d.ts +1221 -910
  29. package/dist/api/users/index.d.ts.map +1 -1
  30. package/dist/api/users/index.js +2556 -248
  31. package/dist/api/users/index.js.map +1 -1
  32. package/dist/api/verifications/index.d.ts +142 -136
  33. package/dist/api/verifications/index.d.ts.map +1 -1
  34. package/dist/api/verifications/index.js +12 -4
  35. package/dist/api/verifications/index.js.map +1 -1
  36. package/dist/batch/index.d.ts +142 -162
  37. package/dist/batch/index.d.ts.map +1 -1
  38. package/dist/batch/index.js +31 -44
  39. package/dist/batch/index.js.map +1 -1
  40. package/dist/bucket/index.d.ts +595 -171
  41. package/dist/bucket/index.d.ts.map +1 -1
  42. package/dist/bucket/index.js +1856 -12
  43. package/dist/bucket/index.js.map +1 -1
  44. package/dist/cache/core/index.d.ts +225 -53
  45. package/dist/cache/core/index.d.ts.map +1 -1
  46. package/dist/cache/core/index.js +213 -7
  47. package/dist/cache/core/index.js.map +1 -1
  48. package/dist/cache/redis/index.d.ts +1 -0
  49. package/dist/cache/redis/index.d.ts.map +1 -1
  50. package/dist/cache/redis/index.js +6 -2
  51. package/dist/cache/redis/index.js.map +1 -1
  52. package/dist/cli/index.d.ts +834 -226
  53. package/dist/cli/index.d.ts.map +1 -1
  54. package/dist/cli/index.js +2872 -417
  55. package/dist/cli/index.js.map +1 -1
  56. package/dist/command/index.d.ts +458 -310
  57. package/dist/command/index.d.ts.map +1 -1
  58. package/dist/command/index.js +2011 -76
  59. package/dist/command/index.js.map +1 -1
  60. package/dist/core/index.browser.js +309 -97
  61. package/dist/core/index.browser.js.map +1 -1
  62. package/dist/core/index.d.ts +796 -701
  63. package/dist/core/index.d.ts.map +1 -1
  64. package/dist/core/index.js +329 -97
  65. package/dist/core/index.js.map +1 -1
  66. package/dist/core/index.native.js +309 -97
  67. package/dist/core/index.native.js.map +1 -1
  68. package/dist/datetime/index.d.ts +59 -44
  69. package/dist/datetime/index.d.ts.map +1 -1
  70. package/dist/datetime/index.js +15 -0
  71. package/dist/datetime/index.js.map +1 -1
  72. package/dist/email/index.d.ts +314 -19
  73. package/dist/email/index.d.ts.map +1 -1
  74. package/dist/email/index.js +1852 -7
  75. package/dist/email/index.js.map +1 -1
  76. package/dist/fake/index.d.ts +5500 -5418
  77. package/dist/fake/index.d.ts.map +1 -1
  78. package/dist/fake/index.js +113 -42
  79. package/dist/fake/index.js.map +1 -1
  80. package/dist/lock/core/index.d.ts +219 -212
  81. package/dist/lock/core/index.d.ts.map +1 -1
  82. package/dist/lock/core/index.js +11 -4
  83. package/dist/lock/core/index.js.map +1 -1
  84. package/dist/lock/redis/index.d.ts.map +1 -1
  85. package/dist/logger/index.d.ts +41 -90
  86. package/dist/logger/index.d.ts.map +1 -1
  87. package/dist/logger/index.js +15 -68
  88. package/dist/logger/index.js.map +1 -1
  89. package/dist/mcp/index.d.ts +228 -230
  90. package/dist/mcp/index.d.ts.map +1 -1
  91. package/dist/mcp/index.js +32 -31
  92. package/dist/mcp/index.js.map +1 -1
  93. package/dist/orm/index.browser.js +12 -12
  94. package/dist/orm/index.browser.js.map +1 -1
  95. package/dist/orm/index.bun.js +90 -80
  96. package/dist/orm/index.bun.js.map +1 -1
  97. package/dist/orm/index.d.ts +1434 -1459
  98. package/dist/orm/index.d.ts.map +1 -1
  99. package/dist/orm/index.js +112 -130
  100. package/dist/orm/index.js.map +1 -1
  101. package/dist/queue/core/index.d.ts +262 -254
  102. package/dist/queue/core/index.d.ts.map +1 -1
  103. package/dist/queue/core/index.js +14 -6
  104. package/dist/queue/core/index.js.map +1 -1
  105. package/dist/queue/redis/index.d.ts.map +1 -1
  106. package/dist/react/auth/index.browser.js +108 -0
  107. package/dist/react/auth/index.browser.js.map +1 -0
  108. package/dist/react/auth/index.d.ts +100 -0
  109. package/dist/react/auth/index.d.ts.map +1 -0
  110. package/dist/react/auth/index.js +145 -0
  111. package/dist/react/auth/index.js.map +1 -0
  112. package/dist/react/core/index.d.ts +469 -0
  113. package/dist/react/core/index.d.ts.map +1 -0
  114. package/dist/react/core/index.js +464 -0
  115. package/dist/react/core/index.js.map +1 -0
  116. package/dist/react/form/index.d.ts +232 -0
  117. package/dist/react/form/index.d.ts.map +1 -0
  118. package/dist/react/form/index.js +432 -0
  119. package/dist/react/form/index.js.map +1 -0
  120. package/dist/react/head/index.browser.js +423 -0
  121. package/dist/react/head/index.browser.js.map +1 -0
  122. package/dist/react/head/index.d.ts +288 -0
  123. package/dist/react/head/index.d.ts.map +1 -0
  124. package/dist/react/head/index.js +465 -0
  125. package/dist/react/head/index.js.map +1 -0
  126. package/dist/react/i18n/index.d.ts +175 -0
  127. package/dist/react/i18n/index.d.ts.map +1 -0
  128. package/dist/react/i18n/index.js +224 -0
  129. package/dist/react/i18n/index.js.map +1 -0
  130. package/dist/react/router/index.browser.js +1980 -0
  131. package/dist/react/router/index.browser.js.map +1 -0
  132. package/dist/react/router/index.d.ts +2068 -0
  133. package/dist/react/router/index.d.ts.map +1 -0
  134. package/dist/react/router/index.js +4932 -0
  135. package/dist/react/router/index.js.map +1 -0
  136. package/dist/react/websocket/index.d.ts +117 -0
  137. package/dist/react/websocket/index.d.ts.map +1 -0
  138. package/dist/react/websocket/index.js +107 -0
  139. package/dist/react/websocket/index.js.map +1 -0
  140. package/dist/redis/index.bun.js +4 -0
  141. package/dist/redis/index.bun.js.map +1 -1
  142. package/dist/redis/index.d.ts +127 -130
  143. package/dist/redis/index.d.ts.map +1 -1
  144. package/dist/redis/index.js +16 -25
  145. package/dist/redis/index.js.map +1 -1
  146. package/dist/retry/index.d.ts +80 -71
  147. package/dist/retry/index.d.ts.map +1 -1
  148. package/dist/retry/index.js +11 -2
  149. package/dist/retry/index.js.map +1 -1
  150. package/dist/router/index.d.ts +6 -6
  151. package/dist/router/index.d.ts.map +1 -1
  152. package/dist/scheduler/index.d.ts +119 -28
  153. package/dist/scheduler/index.d.ts.map +1 -1
  154. package/dist/scheduler/index.js +404 -3
  155. package/dist/scheduler/index.js.map +1 -1
  156. package/dist/security/index.d.ts +642 -228
  157. package/dist/security/index.d.ts.map +1 -1
  158. package/dist/security/index.js +1579 -37
  159. package/dist/security/index.js.map +1 -1
  160. package/dist/server/auth/index.d.ts +1141 -111
  161. package/dist/server/auth/index.d.ts.map +1 -1
  162. package/dist/server/auth/index.js +1261 -25
  163. package/dist/server/auth/index.js.map +1 -1
  164. package/dist/server/cache/index.d.ts +63 -78
  165. package/dist/server/cache/index.d.ts.map +1 -1
  166. package/dist/server/cache/index.js +7 -22
  167. package/dist/server/cache/index.js.map +1 -1
  168. package/dist/server/compress/index.d.ts +13 -5
  169. package/dist/server/compress/index.d.ts.map +1 -1
  170. package/dist/server/compress/index.js +10 -2
  171. package/dist/server/compress/index.js.map +1 -1
  172. package/dist/server/cookies/index.d.ts +46 -22
  173. package/dist/server/cookies/index.d.ts.map +1 -1
  174. package/dist/server/cookies/index.js +7 -5
  175. package/dist/server/cookies/index.js.map +1 -1
  176. package/dist/server/core/index.d.ts +307 -196
  177. package/dist/server/core/index.d.ts.map +1 -1
  178. package/dist/server/core/index.js +271 -38
  179. package/dist/server/core/index.js.map +1 -1
  180. package/dist/server/cors/index.d.ts +24 -34
  181. package/dist/server/cors/index.d.ts.map +1 -1
  182. package/dist/server/cors/index.js +7 -21
  183. package/dist/server/cors/index.js.map +1 -1
  184. package/dist/server/health/index.d.ts +25 -19
  185. package/dist/server/health/index.d.ts.map +1 -1
  186. package/dist/server/health/index.js +8 -2
  187. package/dist/server/health/index.js.map +1 -1
  188. package/dist/server/helmet/index.d.ts +13 -5
  189. package/dist/server/helmet/index.d.ts.map +1 -1
  190. package/dist/server/helmet/index.js +11 -3
  191. package/dist/server/helmet/index.js.map +1 -1
  192. package/dist/server/links/index.browser.js +9 -1
  193. package/dist/server/links/index.browser.js.map +1 -1
  194. package/dist/server/links/index.d.ts +133 -128
  195. package/dist/server/links/index.d.ts.map +1 -1
  196. package/dist/server/links/index.js +24 -11
  197. package/dist/server/links/index.js.map +1 -1
  198. package/dist/server/metrics/index.d.ts +524 -4
  199. package/dist/server/metrics/index.d.ts.map +1 -1
  200. package/dist/server/metrics/index.js +4472 -7
  201. package/dist/server/metrics/index.js.map +1 -1
  202. package/dist/server/multipart/index.d.ts +15 -9
  203. package/dist/server/multipart/index.d.ts.map +1 -1
  204. package/dist/server/multipart/index.js +9 -3
  205. package/dist/server/multipart/index.js.map +1 -1
  206. package/dist/server/proxy/index.d.ts +110 -104
  207. package/dist/server/proxy/index.d.ts.map +1 -1
  208. package/dist/server/proxy/index.js +8 -2
  209. package/dist/server/proxy/index.js.map +1 -1
  210. package/dist/server/rate-limit/index.d.ts +46 -51
  211. package/dist/server/rate-limit/index.d.ts.map +1 -1
  212. package/dist/server/rate-limit/index.js +18 -55
  213. package/dist/server/rate-limit/index.js.map +1 -1
  214. package/dist/server/static/index.d.ts +181 -48
  215. package/dist/server/static/index.d.ts.map +1 -1
  216. package/dist/server/static/index.js +1848 -5
  217. package/dist/server/static/index.js.map +1 -1
  218. package/dist/server/swagger/index.d.ts +348 -53
  219. package/dist/server/swagger/index.d.ts.map +1 -1
  220. package/dist/server/swagger/index.js +1849 -6
  221. package/dist/server/swagger/index.js.map +1 -1
  222. package/dist/sms/index.d.ts +312 -18
  223. package/dist/sms/index.d.ts.map +1 -1
  224. package/dist/sms/index.js +1854 -10
  225. package/dist/sms/index.js.map +1 -1
  226. package/dist/system/index.browser.js +496 -0
  227. package/dist/system/index.browser.js.map +1 -0
  228. package/dist/system/index.d.ts +1158 -0
  229. package/dist/system/index.d.ts.map +1 -0
  230. package/dist/{file → system}/index.js +412 -20
  231. package/dist/system/index.js.map +1 -0
  232. package/dist/thread/index.d.ts +82 -73
  233. package/dist/thread/index.d.ts.map +1 -1
  234. package/dist/thread/index.js +13 -4
  235. package/dist/thread/index.js.map +1 -1
  236. package/dist/topic/core/index.d.ts +330 -323
  237. package/dist/topic/core/index.d.ts.map +1 -1
  238. package/dist/topic/core/index.js +12 -5
  239. package/dist/topic/core/index.js.map +1 -1
  240. package/dist/topic/redis/index.d.ts +6 -6
  241. package/dist/topic/redis/index.d.ts.map +1 -1
  242. package/dist/vite/index.d.ts +163 -5825
  243. package/dist/vite/index.d.ts.map +1 -1
  244. package/dist/vite/index.js +130 -477
  245. package/dist/vite/index.js.map +1 -1
  246. package/dist/websocket/index.browser.js +3 -3
  247. package/dist/websocket/index.browser.js.map +1 -1
  248. package/dist/websocket/index.d.ts +287 -283
  249. package/dist/websocket/index.d.ts.map +1 -1
  250. package/dist/websocket/index.js +15 -11
  251. package/dist/websocket/index.js.map +1 -1
  252. package/package.json +86 -17
  253. package/src/api/audits/index.ts +10 -33
  254. package/src/api/files/__tests__/$bucket.spec.ts +1 -1
  255. package/src/api/files/controllers/AdminFileStatsController.spec.ts +1 -1
  256. package/src/api/files/controllers/FileController.spec.ts +1 -1
  257. package/src/api/files/index.ts +10 -3
  258. package/src/api/files/jobs/FileJobs.spec.ts +1 -1
  259. package/src/api/files/services/FileService.spec.ts +1 -1
  260. package/src/api/jobs/index.ts +10 -3
  261. package/src/api/keys/controllers/AdminApiKeyController.ts +75 -0
  262. package/src/api/keys/controllers/ApiKeyController.ts +103 -0
  263. package/src/api/keys/entities/apiKeyEntity.ts +41 -0
  264. package/src/api/keys/index.ts +49 -0
  265. package/src/api/keys/schemas/adminApiKeyQuerySchema.ts +7 -0
  266. package/src/api/keys/schemas/adminApiKeyResourceSchema.ts +17 -0
  267. package/src/api/keys/schemas/createApiKeyBodySchema.ts +7 -0
  268. package/src/api/keys/schemas/createApiKeyResponseSchema.ts +11 -0
  269. package/src/api/keys/schemas/listApiKeyResponseSchema.ts +15 -0
  270. package/src/api/keys/schemas/revokeApiKeyParamsSchema.ts +5 -0
  271. package/src/api/keys/schemas/revokeApiKeyResponseSchema.ts +5 -0
  272. package/src/api/keys/services/ApiKeyService.spec.ts +553 -0
  273. package/src/api/keys/services/ApiKeyService.ts +306 -0
  274. package/src/api/logs/TODO.md +52 -0
  275. package/src/api/notifications/index.ts +10 -4
  276. package/src/api/parameters/index.ts +9 -30
  277. package/src/api/parameters/primitives/$config.ts +12 -4
  278. package/src/api/parameters/services/ConfigStore.ts +9 -3
  279. package/src/api/users/__tests__/ApiKeys-integration.spec.ts +1035 -0
  280. package/src/api/users/__tests__/ApiKeys.spec.ts +401 -0
  281. package/src/api/users/index.ts +14 -3
  282. package/src/api/users/primitives/$realm.ts +33 -5
  283. package/src/api/users/providers/RealmProvider.ts +1 -12
  284. package/src/api/users/services/SessionService.ts +1 -11
  285. package/src/api/verifications/controllers/VerificationController.ts +2 -0
  286. package/src/api/verifications/index.ts +10 -4
  287. package/src/batch/index.ts +9 -36
  288. package/src/batch/primitives/$batch.ts +0 -8
  289. package/src/batch/providers/BatchProvider.ts +29 -2
  290. package/src/bucket/__tests__/shared.ts +1 -1
  291. package/src/bucket/index.ts +13 -6
  292. package/src/bucket/primitives/$bucket.ts +1 -1
  293. package/src/bucket/providers/LocalFileStorageProvider.ts +1 -1
  294. package/src/bucket/providers/MemoryFileStorageProvider.ts +1 -1
  295. package/src/cache/core/__tests__/shared.ts +30 -0
  296. package/src/cache/core/index.ts +11 -6
  297. package/src/cache/core/primitives/$cache.spec.ts +5 -0
  298. package/src/cache/core/providers/CacheProvider.ts +17 -0
  299. package/src/cache/core/providers/MemoryCacheProvider.ts +300 -1
  300. package/src/cache/redis/__tests__/cache-redis.spec.ts +5 -0
  301. package/src/cache/redis/providers/RedisCacheProvider.ts +9 -0
  302. package/src/cli/apps/AlephaCli.ts +3 -16
  303. package/src/cli/apps/AlephaPackageBuilderCli.ts +10 -2
  304. package/src/cli/atoms/appEntryOptions.ts +13 -0
  305. package/src/cli/atoms/buildOptions.ts +1 -1
  306. package/src/cli/atoms/changelogOptions.ts +1 -1
  307. package/src/cli/commands/build.ts +64 -52
  308. package/src/cli/commands/db.ts +17 -11
  309. package/src/cli/commands/deploy.ts +1 -1
  310. package/src/cli/commands/dev.ts +13 -49
  311. package/src/cli/commands/gen/env.ts +6 -3
  312. package/src/cli/commands/gen/openapi.ts +5 -2
  313. package/src/cli/commands/init.spec.ts +544 -0
  314. package/src/cli/commands/init.ts +101 -58
  315. package/src/cli/commands/lint.ts +8 -2
  316. package/src/cli/commands/typecheck.ts +11 -0
  317. package/src/cli/defineConfig.ts +9 -0
  318. package/src/cli/index.ts +2 -1
  319. package/src/cli/providers/AppEntryProvider.ts +131 -0
  320. package/src/cli/providers/ViteBuildProvider.ts +40 -0
  321. package/src/cli/providers/ViteDevServerProvider.ts +378 -0
  322. package/src/cli/services/AlephaCliUtils.ts +39 -93
  323. package/src/cli/services/PackageManagerUtils.ts +140 -17
  324. package/src/cli/services/ProjectScaffolder.ts +169 -101
  325. package/src/cli/services/ViteUtils.ts +82 -0
  326. package/src/cli/{assets/claudeMd.ts → templates/agentMd.ts} +41 -28
  327. package/src/cli/{assets → templates}/apiHelloControllerTs.ts +2 -1
  328. package/src/cli/{assets → templates}/biomeJson.ts +2 -1
  329. package/src/cli/{assets → templates}/dummySpecTs.ts +2 -1
  330. package/src/cli/{assets → templates}/editorconfig.ts +2 -1
  331. package/src/cli/templates/gitignore.ts +39 -0
  332. package/src/cli/{assets → templates}/mainBrowserTs.ts +2 -1
  333. package/src/cli/templates/mainCss.ts +33 -0
  334. package/src/cli/templates/mainServerTs.ts +33 -0
  335. package/src/cli/{assets → templates}/tsconfigJson.ts +2 -1
  336. package/src/cli/templates/webAppRouterTs.ts +50 -0
  337. package/src/cli/templates/webHelloComponentTsx.ts +20 -0
  338. package/src/command/helpers/Runner.spec.ts +4 -0
  339. package/src/command/helpers/Runner.ts +3 -21
  340. package/src/command/index.ts +12 -4
  341. package/src/command/providers/CliProvider.spec.ts +1067 -0
  342. package/src/command/providers/CliProvider.ts +203 -40
  343. package/src/core/Alepha.ts +3 -9
  344. package/src/core/__tests__/Alepha-start.spec.ts +4 -4
  345. package/src/core/helpers/jsonSchemaToTypeBox.spec.ts +771 -0
  346. package/src/core/helpers/jsonSchemaToTypeBox.ts +62 -10
  347. package/src/core/index.shared.ts +1 -0
  348. package/src/core/index.ts +20 -0
  349. package/src/core/primitives/$module.ts +12 -0
  350. package/src/core/providers/EventManager.spec.ts +0 -71
  351. package/src/core/providers/EventManager.ts +3 -15
  352. package/src/core/providers/Json.ts +2 -14
  353. package/src/core/providers/KeylessJsonSchemaCodec.spec.ts +257 -0
  354. package/src/core/providers/KeylessJsonSchemaCodec.ts +396 -14
  355. package/src/core/providers/SchemaValidator.spec.ts +236 -0
  356. package/src/datetime/index.ts +15 -0
  357. package/src/email/index.ts +10 -5
  358. package/src/email/providers/LocalEmailProvider.spec.ts +1 -1
  359. package/src/email/providers/LocalEmailProvider.ts +1 -1
  360. package/src/fake/__tests__/keyName.example.ts +1 -1
  361. package/src/fake/__tests__/keyName.spec.ts +5 -5
  362. package/src/fake/index.ts +9 -6
  363. package/src/fake/providers/FakeProvider.spec.ts +258 -40
  364. package/src/fake/providers/FakeProvider.ts +133 -19
  365. package/src/lock/core/index.ts +11 -4
  366. package/src/logger/index.ts +17 -66
  367. package/src/logger/providers/PrettyFormatterProvider.ts +0 -9
  368. package/src/mcp/errors/McpError.ts +30 -0
  369. package/src/mcp/index.ts +13 -27
  370. package/src/mcp/transports/SseMcpTransport.ts +6 -7
  371. package/src/orm/__tests__/PostgresProvider.spec.ts +2 -2
  372. package/src/orm/index.browser.ts +2 -2
  373. package/src/orm/index.bun.ts +4 -2
  374. package/src/orm/index.ts +21 -47
  375. package/src/orm/providers/DrizzleKitProvider.ts +3 -5
  376. package/src/orm/providers/drivers/BunSqliteProvider.ts +1 -0
  377. package/src/orm/services/Repository.ts +18 -3
  378. package/src/queue/core/index.ts +14 -6
  379. package/src/react/auth/__tests__/$auth.spec.ts +202 -0
  380. package/src/react/auth/hooks/useAuth.ts +32 -0
  381. package/src/react/auth/index.browser.ts +13 -0
  382. package/src/react/auth/index.shared.ts +2 -0
  383. package/src/react/auth/index.ts +48 -0
  384. package/src/react/auth/providers/ReactAuthProvider.ts +16 -0
  385. package/src/react/auth/services/ReactAuth.ts +135 -0
  386. package/src/react/core/__tests__/Router.spec.tsx +169 -0
  387. package/src/react/core/components/ClientOnly.tsx +49 -0
  388. package/src/react/core/components/ErrorBoundary.tsx +73 -0
  389. package/src/react/core/contexts/AlephaContext.ts +7 -0
  390. package/src/react/core/contexts/AlephaProvider.tsx +42 -0
  391. package/src/react/core/hooks/useAction.browser.spec.tsx +569 -0
  392. package/src/react/core/hooks/useAction.ts +480 -0
  393. package/src/react/core/hooks/useAlepha.ts +26 -0
  394. package/src/react/core/hooks/useClient.ts +17 -0
  395. package/src/react/core/hooks/useEvents.ts +51 -0
  396. package/src/react/core/hooks/useInject.ts +12 -0
  397. package/src/react/core/hooks/useStore.ts +52 -0
  398. package/src/react/core/index.ts +90 -0
  399. package/src/react/form/components/FormState.tsx +17 -0
  400. package/src/react/form/errors/FormValidationError.ts +18 -0
  401. package/src/react/form/hooks/useForm.browser.spec.tsx +366 -0
  402. package/src/react/form/hooks/useForm.ts +47 -0
  403. package/src/react/form/hooks/useFormState.ts +130 -0
  404. package/src/react/form/index.ts +44 -0
  405. package/src/react/form/services/FormModel.ts +614 -0
  406. package/src/react/head/helpers/SeoExpander.spec.ts +203 -0
  407. package/src/react/head/helpers/SeoExpander.ts +142 -0
  408. package/src/react/head/hooks/useHead.spec.tsx +288 -0
  409. package/src/react/head/hooks/useHead.ts +62 -0
  410. package/src/react/head/index.browser.ts +26 -0
  411. package/src/react/head/index.ts +44 -0
  412. package/src/react/head/interfaces/Head.ts +105 -0
  413. package/src/react/head/primitives/$head.ts +25 -0
  414. package/src/react/head/providers/BrowserHeadProvider.browser.spec.ts +196 -0
  415. package/src/react/head/providers/BrowserHeadProvider.ts +212 -0
  416. package/src/react/head/providers/HeadProvider.ts +168 -0
  417. package/src/react/head/providers/ServerHeadProvider.ts +31 -0
  418. package/src/react/i18n/__tests__/integration.spec.tsx +239 -0
  419. package/src/react/i18n/components/Localize.spec.tsx +357 -0
  420. package/src/react/i18n/components/Localize.tsx +35 -0
  421. package/src/react/i18n/hooks/useI18n.browser.spec.tsx +438 -0
  422. package/src/react/i18n/hooks/useI18n.ts +18 -0
  423. package/src/react/i18n/index.ts +41 -0
  424. package/src/react/i18n/primitives/$dictionary.ts +69 -0
  425. package/src/react/i18n/providers/I18nProvider.spec.ts +389 -0
  426. package/src/react/i18n/providers/I18nProvider.ts +278 -0
  427. package/src/react/router/__tests__/page-head-browser.browser.spec.ts +95 -0
  428. package/src/react/router/__tests__/page-head.spec.ts +48 -0
  429. package/src/react/router/__tests__/seo-head.spec.ts +125 -0
  430. package/src/react/router/atoms/ssrManifestAtom.ts +58 -0
  431. package/src/react/router/components/ErrorViewer.tsx +872 -0
  432. package/src/react/router/components/Link.tsx +23 -0
  433. package/src/react/router/components/NestedView.tsx +223 -0
  434. package/src/react/router/components/NotFound.tsx +30 -0
  435. package/src/react/router/constants/PAGE_PRELOAD_KEY.ts +6 -0
  436. package/src/react/router/contexts/RouterLayerContext.ts +12 -0
  437. package/src/react/router/errors/Redirection.ts +28 -0
  438. package/src/react/router/hooks/useActive.ts +52 -0
  439. package/src/react/router/hooks/useQueryParams.ts +63 -0
  440. package/src/react/router/hooks/useRouter.ts +20 -0
  441. package/src/react/router/hooks/useRouterState.ts +11 -0
  442. package/src/react/router/index.browser.ts +45 -0
  443. package/src/react/router/index.shared.ts +19 -0
  444. package/src/react/router/index.ts +142 -0
  445. package/src/react/router/primitives/$page.browser.spec.tsx +851 -0
  446. package/src/react/router/primitives/$page.spec.tsx +708 -0
  447. package/src/react/router/primitives/$page.ts +497 -0
  448. package/src/react/router/providers/ReactBrowserProvider.ts +309 -0
  449. package/src/react/router/providers/ReactBrowserRendererProvider.ts +25 -0
  450. package/src/react/router/providers/ReactBrowserRouterProvider.ts +168 -0
  451. package/src/react/router/providers/ReactPageProvider.ts +726 -0
  452. package/src/react/router/providers/ReactServerProvider.spec.tsx +316 -0
  453. package/src/react/router/providers/ReactServerProvider.ts +558 -0
  454. package/src/react/router/providers/ReactServerTemplateProvider.ts +979 -0
  455. package/src/react/router/providers/SSRManifestProvider.ts +334 -0
  456. package/src/react/router/services/ReactPageServerService.ts +48 -0
  457. package/src/react/router/services/ReactPageService.ts +27 -0
  458. package/src/react/router/services/ReactRouter.ts +262 -0
  459. package/src/react/websocket/hooks/useRoom.tsx +242 -0
  460. package/src/react/websocket/index.ts +7 -0
  461. package/src/redis/__tests__/redis.spec.ts +13 -0
  462. package/src/redis/index.ts +9 -25
  463. package/src/redis/providers/BunRedisProvider.ts +9 -0
  464. package/src/redis/providers/NodeRedisProvider.ts +8 -0
  465. package/src/redis/providers/RedisProvider.ts +16 -0
  466. package/src/retry/index.ts +11 -2
  467. package/src/router/index.ts +15 -0
  468. package/src/scheduler/index.ts +11 -2
  469. package/src/security/__tests__/BasicAuth.spec.ts +2 -0
  470. package/src/security/__tests__/ServerSecurityProvider.spec.ts +13 -5
  471. package/src/security/index.ts +15 -10
  472. package/src/security/interfaces/IssuerResolver.ts +27 -0
  473. package/src/security/primitives/$issuer.ts +55 -0
  474. package/src/security/providers/SecurityProvider.ts +179 -0
  475. package/src/security/providers/ServerBasicAuthProvider.ts +6 -2
  476. package/src/security/providers/ServerSecurityProvider.ts +36 -22
  477. package/src/server/auth/index.ts +12 -7
  478. package/src/server/cache/index.ts +7 -22
  479. package/src/server/compress/index.ts +10 -2
  480. package/src/server/cookies/index.ts +7 -5
  481. package/src/server/cookies/primitives/$cookie.ts +33 -11
  482. package/src/server/core/index.ts +17 -7
  483. package/src/server/core/interfaces/ServerRequest.ts +83 -1
  484. package/src/server/core/primitives/$action.spec.ts +1 -1
  485. package/src/server/core/primitives/$action.ts +8 -3
  486. package/src/server/core/providers/BunHttpServerProvider.ts +1 -1
  487. package/src/server/core/providers/NodeHttpServerProvider.spec.ts +125 -0
  488. package/src/server/core/providers/NodeHttpServerProvider.ts +77 -22
  489. package/src/server/core/providers/ServerLoggerProvider.ts +2 -2
  490. package/src/server/core/providers/ServerProvider.ts +9 -12
  491. package/src/server/core/services/ServerRequestParser.spec.ts +520 -0
  492. package/src/server/core/services/ServerRequestParser.ts +306 -13
  493. package/src/server/cors/index.ts +7 -21
  494. package/src/server/cors/primitives/$cors.ts +6 -2
  495. package/src/server/health/index.ts +8 -2
  496. package/src/server/helmet/index.ts +11 -3
  497. package/src/server/links/atoms/apiLinksAtom.ts +7 -0
  498. package/src/server/links/index.browser.ts +2 -0
  499. package/src/server/links/index.ts +13 -6
  500. package/src/server/metrics/index.ts +10 -3
  501. package/src/server/multipart/index.ts +9 -3
  502. package/src/server/proxy/index.ts +8 -2
  503. package/src/server/rate-limit/index.ts +21 -25
  504. package/src/server/rate-limit/primitives/$rateLimit.ts +6 -2
  505. package/src/server/rate-limit/providers/ServerRateLimitProvider.spec.ts +38 -14
  506. package/src/server/rate-limit/providers/ServerRateLimitProvider.ts +22 -56
  507. package/src/server/static/index.ts +8 -2
  508. package/src/server/static/providers/ServerStaticProvider.ts +1 -1
  509. package/src/server/swagger/index.ts +9 -4
  510. package/src/server/swagger/providers/ServerSwaggerProvider.ts +1 -1
  511. package/src/sms/index.ts +9 -5
  512. package/src/sms/providers/LocalSmsProvider.spec.ts +1 -1
  513. package/src/sms/providers/LocalSmsProvider.ts +1 -1
  514. package/src/system/index.browser.ts +11 -0
  515. package/src/system/index.ts +62 -0
  516. package/src/{file → system}/providers/FileSystemProvider.ts +16 -0
  517. package/src/{file → system}/providers/MemoryFileSystemProvider.ts +116 -3
  518. package/src/system/providers/MemoryShellProvider.ts +164 -0
  519. package/src/{file → system}/providers/NodeFileSystemProvider.spec.ts +2 -2
  520. package/src/{file → system}/providers/NodeFileSystemProvider.ts +36 -0
  521. package/src/system/providers/NodeShellProvider.ts +184 -0
  522. package/src/system/providers/ShellProvider.ts +74 -0
  523. package/src/{file → system}/services/FileDetector.spec.ts +2 -2
  524. package/src/thread/index.ts +11 -2
  525. package/src/topic/core/index.ts +12 -5
  526. package/src/vite/index.ts +3 -2
  527. package/src/vite/tasks/buildClient.ts +2 -8
  528. package/src/vite/tasks/buildServer.ts +84 -21
  529. package/src/vite/tasks/copyAssets.ts +5 -4
  530. package/src/vite/tasks/generateSitemap.ts +64 -23
  531. package/src/vite/tasks/index.ts +0 -2
  532. package/src/vite/tasks/prerenderPages.ts +49 -24
  533. package/src/websocket/index.ts +12 -8
  534. package/dist/file/index.d.ts +0 -839
  535. package/dist/file/index.d.ts.map +0 -1
  536. package/dist/file/index.js.map +0 -1
  537. package/src/cli/assets/indexHtml.ts +0 -15
  538. package/src/cli/assets/mainServerTs.ts +0 -24
  539. package/src/cli/assets/webAppRouterTs.ts +0 -15
  540. package/src/cli/assets/webHelloComponentTsx.ts +0 -16
  541. package/src/cli/commands/format.ts +0 -23
  542. package/src/file/index.ts +0 -43
  543. package/src/vite/helpers/boot.ts +0 -117
  544. package/src/vite/plugins/viteAlephaDev.ts +0 -177
  545. package/src/vite/tasks/devServer.ts +0 -71
  546. package/src/vite/tasks/runAlepha.ts +0 -270
  547. /package/dist/orm/{chunk-DtkW-qnP.js → chunk-DH6iiROE.js} +0 -0
  548. /package/src/cli/{assets → templates}/apiIndexTs.ts +0 -0
  549. /package/src/cli/{assets → templates}/webIndexTs.ts +0 -0
  550. /package/src/{file → system}/errors/FileError.ts +0 -0
  551. /package/src/{file → system}/services/FileDetector.ts +0 -0
package/dist/cli/index.js CHANGED
@@ -1,16 +1,1852 @@
1
- import { $atom, $hook, $inject, $module, $use, Alepha, AlephaError, t } from "alepha";
2
- import { FileSystemProvider } from "alepha/file";
3
- import { $command, CliProvider, EnvUtils } from "alepha/command";
4
- import { $logger, ConsoleColorProvider } from "alepha/logger";
5
- import { boot, buildClient, buildServer, copyAssets, devServer, generateCloudflare, generateDocker, generateSitemap, generateVercel, prerenderPages } from "alepha/vite";
6
- import { exec, spawn } from "node:child_process";
7
- import { readFileSync } from "node:fs";
1
+ import { $atom, $hook, $inject, $module, $use, Alepha, AlephaError, Json, isFileLike, t } from "alepha";
8
2
  import { basename, dirname, join } from "node:path";
3
+ import { createReadStream, readFileSync } from "node:fs";
4
+ import { access, copyFile, cp, mkdir, readFile, readdir, rename, rm, stat, writeFile } from "node:fs/promises";
5
+ import { PassThrough, Readable } from "node:stream";
6
+ import { fileURLToPath } from "node:url";
7
+ import { exec, spawn } from "node:child_process";
8
+ import { $logger, ConsoleColorProvider } from "alepha/logger";
9
+ import { $command, CliProvider, EnvUtils } from "alepha/command";
10
+ import { buildClient, buildServer, copyAssets, generateCloudflare, generateDocker, generateSitemap, generateVercel, importVite, importViteReact, prerenderPages, viteAlephaSsrPreload } from "alepha/vite";
9
11
  import { promisify } from "node:util";
10
12
  import { ServerSwaggerProvider } from "alepha/server/swagger";
11
- import { access, readFile, readdir } from "node:fs/promises";
12
13
  import * as os from "node:os";
13
14
 
15
+ //#region ../../src/system/providers/FileSystemProvider.ts
16
+ /**
17
+ * FileSystem interface providing utilities for working with files.
18
+ */
19
+ var FileSystemProvider = class {};
20
+
21
+ //#endregion
22
+ //#region ../../src/system/providers/MemoryFileSystemProvider.ts
23
+ /**
24
+ * In-memory implementation of FileSystemProvider for testing.
25
+ *
26
+ * This provider stores all files and directories in memory, making it ideal for
27
+ * unit tests that need to verify file operations without touching the real file system.
28
+ *
29
+ * @example
30
+ * ```typescript
31
+ * // In tests, substitute the real FileSystemProvider with MemoryFileSystemProvider
32
+ * const alepha = Alepha.create().with({
33
+ * provide: FileSystemProvider,
34
+ * use: MemoryFileSystemProvider,
35
+ * });
36
+ *
37
+ * // Run code that uses FileSystemProvider
38
+ * const service = alepha.inject(MyService);
39
+ * await service.saveFile("test.txt", "Hello World");
40
+ *
41
+ * // Verify the file was written
42
+ * const memoryFs = alepha.inject(MemoryFileSystemProvider);
43
+ * expect(memoryFs.files.get("test.txt")?.toString()).toBe("Hello World");
44
+ * ```
45
+ */
46
+ var MemoryFileSystemProvider = class {
47
+ json = $inject(Json);
48
+ /**
49
+ * In-memory storage for files (path -> content)
50
+ */
51
+ files = /* @__PURE__ */ new Map();
52
+ /**
53
+ * In-memory storage for directories
54
+ */
55
+ directories = /* @__PURE__ */ new Set();
56
+ /**
57
+ * Track mkdir calls for test assertions
58
+ */
59
+ mkdirCalls = [];
60
+ /**
61
+ * Track writeFile calls for test assertions
62
+ */
63
+ writeFileCalls = [];
64
+ /**
65
+ * Track readFile calls for test assertions
66
+ */
67
+ readFileCalls = [];
68
+ /**
69
+ * Track rm calls for test assertions
70
+ */
71
+ rmCalls = [];
72
+ /**
73
+ * Track join calls for test assertions
74
+ */
75
+ joinCalls = [];
76
+ /**
77
+ * Error to throw on mkdir (for testing error handling)
78
+ */
79
+ mkdirError = null;
80
+ /**
81
+ * Error to throw on writeFile (for testing error handling)
82
+ */
83
+ writeFileError = null;
84
+ /**
85
+ * Error to throw on readFile (for testing error handling)
86
+ */
87
+ readFileError = null;
88
+ constructor(options = {}) {
89
+ this.mkdirError = options.mkdirError ?? null;
90
+ this.writeFileError = options.writeFileError ?? null;
91
+ this.readFileError = options.readFileError ?? null;
92
+ }
93
+ /**
94
+ * Join path segments using forward slashes.
95
+ * Uses Node's path.join for proper normalization (handles .. and .)
96
+ */
97
+ join(...paths) {
98
+ this.joinCalls.push(paths);
99
+ return join(...paths);
100
+ }
101
+ /**
102
+ * Create a FileLike object from various sources.
103
+ */
104
+ createFile(options) {
105
+ if ("path" in options) {
106
+ const filePath = options.path;
107
+ const buffer = this.files.get(filePath);
108
+ if (buffer === void 0) throw new Error(`ENOENT: no such file or directory, open '${filePath}'`);
109
+ return {
110
+ name: options.name ?? filePath.split("/").pop() ?? "file",
111
+ type: options.type ?? "application/octet-stream",
112
+ size: buffer.byteLength,
113
+ lastModified: Date.now(),
114
+ stream: () => {
115
+ throw new Error("Stream not implemented in MemoryFileSystemProvider");
116
+ },
117
+ arrayBuffer: async () => buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength),
118
+ text: async () => buffer.toString("utf-8")
119
+ };
120
+ }
121
+ if ("buffer" in options) {
122
+ const buffer = options.buffer;
123
+ return {
124
+ name: options.name ?? "file",
125
+ type: options.type ?? "application/octet-stream",
126
+ size: buffer.byteLength,
127
+ lastModified: Date.now(),
128
+ stream: () => {
129
+ throw new Error("Stream not implemented in MemoryFileSystemProvider");
130
+ },
131
+ arrayBuffer: async () => buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength),
132
+ text: async () => buffer.toString("utf-8")
133
+ };
134
+ }
135
+ if ("text" in options) {
136
+ const buffer = Buffer.from(options.text, "utf-8");
137
+ return {
138
+ name: options.name ?? "file.txt",
139
+ type: options.type ?? "text/plain",
140
+ size: buffer.byteLength,
141
+ lastModified: Date.now(),
142
+ stream: () => {
143
+ throw new Error("Stream not implemented in MemoryFileSystemProvider");
144
+ },
145
+ arrayBuffer: async () => buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength),
146
+ text: async () => options.text
147
+ };
148
+ }
149
+ throw new Error("MemoryFileSystemProvider.createFile: unsupported options. Only buffer and text are supported.");
150
+ }
151
+ /**
152
+ * Remove a file or directory from memory.
153
+ */
154
+ async rm(path, options) {
155
+ this.rmCalls.push({
156
+ path,
157
+ options
158
+ });
159
+ if (!(this.files.has(path) || this.directories.has(path)) && !options?.force) throw new Error(`ENOENT: no such file or directory, rm '${path}'`);
160
+ if (this.directories.has(path)) if (options?.recursive) {
161
+ this.directories.delete(path);
162
+ for (const filePath of this.files.keys()) if (filePath.startsWith(`${path}/`)) this.files.delete(filePath);
163
+ for (const dirPath of this.directories) if (dirPath.startsWith(`${path}/`)) this.directories.delete(dirPath);
164
+ } else throw new Error(`EISDIR: illegal operation on a directory, rm '${path}'`);
165
+ else this.files.delete(path);
166
+ }
167
+ /**
168
+ * Copy a file or directory in memory.
169
+ */
170
+ async cp(src, dest, options) {
171
+ if (this.directories.has(src)) {
172
+ if (!options?.recursive) throw new Error(`Cannot copy directory without recursive option: ${src}`);
173
+ this.directories.add(dest);
174
+ for (const [filePath, content] of this.files) if (filePath.startsWith(`${src}/`)) {
175
+ const newPath = filePath.replace(src, dest);
176
+ this.files.set(newPath, Buffer.from(content));
177
+ }
178
+ } else if (this.files.has(src)) {
179
+ const content = this.files.get(src);
180
+ this.files.set(dest, Buffer.from(content));
181
+ } else throw new Error(`ENOENT: no such file or directory, cp '${src}'`);
182
+ }
183
+ /**
184
+ * Move/rename a file or directory in memory.
185
+ */
186
+ async mv(src, dest) {
187
+ if (this.directories.has(src)) {
188
+ this.directories.delete(src);
189
+ this.directories.add(dest);
190
+ for (const [filePath, content] of this.files) if (filePath.startsWith(`${src}/`)) {
191
+ const newPath = filePath.replace(src, dest);
192
+ this.files.delete(filePath);
193
+ this.files.set(newPath, content);
194
+ }
195
+ } else if (this.files.has(src)) {
196
+ const content = this.files.get(src);
197
+ this.files.delete(src);
198
+ this.files.set(dest, content);
199
+ } else throw new Error(`ENOENT: no such file or directory, mv '${src}'`);
200
+ }
201
+ /**
202
+ * Create a directory in memory.
203
+ */
204
+ async mkdir(path, options) {
205
+ this.mkdirCalls.push({
206
+ path,
207
+ options
208
+ });
209
+ if (this.mkdirError) throw this.mkdirError;
210
+ if (this.directories.has(path) && !options?.recursive) throw new Error(`EEXIST: file already exists, mkdir '${path}'`);
211
+ this.directories.add(path);
212
+ if (options?.recursive) {
213
+ const parts = path.split("/").filter(Boolean);
214
+ let current = "";
215
+ for (const part of parts) {
216
+ current = current ? `${current}/${part}` : part;
217
+ this.directories.add(current);
218
+ }
219
+ }
220
+ }
221
+ /**
222
+ * List files in a directory.
223
+ */
224
+ async ls(path, options) {
225
+ const normalizedPath = path.replace(/\/$/, "");
226
+ const entries = /* @__PURE__ */ new Set();
227
+ for (const filePath of this.files.keys()) if (filePath.startsWith(`${normalizedPath}/`)) {
228
+ const relativePath = filePath.slice(normalizedPath.length + 1);
229
+ const parts = relativePath.split("/");
230
+ if (options?.recursive) entries.add(relativePath);
231
+ else entries.add(parts[0]);
232
+ }
233
+ for (const dirPath of this.directories) if (dirPath.startsWith(`${normalizedPath}/`) && dirPath !== normalizedPath) {
234
+ const relativePath = dirPath.slice(normalizedPath.length + 1);
235
+ const parts = relativePath.split("/");
236
+ if (options?.recursive) entries.add(relativePath);
237
+ else if (parts.length === 1) entries.add(parts[0]);
238
+ }
239
+ let result = Array.from(entries);
240
+ if (!options?.hidden) result = result.filter((entry) => !entry.startsWith("."));
241
+ return result.sort();
242
+ }
243
+ /**
244
+ * Check if a file or directory exists in memory.
245
+ */
246
+ async exists(path) {
247
+ return this.files.has(path) || this.directories.has(path);
248
+ }
249
+ /**
250
+ * Read a file from memory.
251
+ */
252
+ async readFile(path) {
253
+ this.readFileCalls.push(path);
254
+ if (this.readFileError) throw this.readFileError;
255
+ const content = this.files.get(path);
256
+ if (!content) throw new Error(`ENOENT: no such file or directory, open '${path}'`);
257
+ return content;
258
+ }
259
+ /**
260
+ * Read a file from memory as text.
261
+ */
262
+ async readTextFile(path) {
263
+ return (await this.readFile(path)).toString("utf-8");
264
+ }
265
+ /**
266
+ * Read a file from memory as JSON.
267
+ */
268
+ async readJsonFile(path) {
269
+ const text = await this.readTextFile(path);
270
+ return this.json.parse(text);
271
+ }
272
+ /**
273
+ * Write a file to memory.
274
+ */
275
+ async writeFile(path, data) {
276
+ const dataStr = typeof data === "string" ? data : data instanceof Buffer || data instanceof Uint8Array ? data.toString("utf-8") : await data.text();
277
+ this.writeFileCalls.push({
278
+ path,
279
+ data: dataStr
280
+ });
281
+ if (this.writeFileError) throw this.writeFileError;
282
+ const buffer = typeof data === "string" ? Buffer.from(data, "utf-8") : data instanceof Buffer ? data : data instanceof Uint8Array ? Buffer.from(data) : Buffer.from(await data.text(), "utf-8");
283
+ this.files.set(path, buffer);
284
+ }
285
+ /**
286
+ * Reset all in-memory state (useful between tests).
287
+ */
288
+ reset() {
289
+ this.files.clear();
290
+ this.directories.clear();
291
+ this.mkdirCalls = [];
292
+ this.writeFileCalls = [];
293
+ this.readFileCalls = [];
294
+ this.rmCalls = [];
295
+ this.joinCalls = [];
296
+ this.mkdirError = null;
297
+ this.writeFileError = null;
298
+ this.readFileError = null;
299
+ }
300
+ /**
301
+ * Check if a file was written during the test.
302
+ *
303
+ * @example
304
+ * ```typescript
305
+ * expect(fs.wasWritten("/project/tsconfig.json")).toBe(true);
306
+ * ```
307
+ */
308
+ wasWritten(path) {
309
+ return this.writeFileCalls.some((call) => call.path === path);
310
+ }
311
+ /**
312
+ * Check if a file was written with content matching a pattern.
313
+ *
314
+ * @example
315
+ * ```typescript
316
+ * expect(fs.wasWrittenMatching("/project/tsconfig.json", /extends/)).toBe(true);
317
+ * ```
318
+ */
319
+ wasWrittenMatching(path, pattern) {
320
+ const call = this.writeFileCalls.find((c) => c.path === path);
321
+ return call ? pattern.test(call.data) : false;
322
+ }
323
+ /**
324
+ * Check if a file was read during the test.
325
+ *
326
+ * @example
327
+ * ```typescript
328
+ * expect(fs.wasRead("/project/package.json")).toBe(true);
329
+ * ```
330
+ */
331
+ wasRead(path) {
332
+ return this.readFileCalls.includes(path);
333
+ }
334
+ /**
335
+ * Check if a file was deleted during the test.
336
+ *
337
+ * @example
338
+ * ```typescript
339
+ * expect(fs.wasDeleted("/project/old-file.txt")).toBe(true);
340
+ * ```
341
+ */
342
+ wasDeleted(path) {
343
+ return this.rmCalls.some((call) => call.path === path);
344
+ }
345
+ /**
346
+ * Get the content of a file as a string (convenience method for testing).
347
+ */
348
+ getFileContent(path) {
349
+ return this.files.get(path)?.toString("utf-8");
350
+ }
351
+ };
352
+
353
+ //#endregion
354
+ //#region ../../src/system/providers/MemoryShellProvider.ts
355
+ /**
356
+ * In-memory implementation of ShellProvider for testing.
357
+ *
358
+ * Records all commands that would be executed without actually running them.
359
+ * Can be configured to return specific outputs or throw errors for testing.
360
+ *
361
+ * @example
362
+ * ```typescript
363
+ * // In tests, substitute the real ShellProvider with MemoryShellProvider
364
+ * const alepha = Alepha.create().with({
365
+ * provide: ShellProvider,
366
+ * use: MemoryShellProvider,
367
+ * });
368
+ *
369
+ * // Configure mock behavior
370
+ * const shell = alepha.inject(MemoryShellProvider);
371
+ * shell.configure({
372
+ * outputs: { "echo hello": "hello\n" },
373
+ * errors: { "failing-cmd": "Command failed" },
374
+ * });
375
+ *
376
+ * // Or use the fluent API
377
+ * shell.outputs.set("another-cmd", "output");
378
+ * shell.errors.set("another-error", "Error message");
379
+ *
380
+ * // Run code that uses ShellProvider
381
+ * const service = alepha.inject(MyService);
382
+ * await service.doSomething();
383
+ *
384
+ * // Verify commands were called
385
+ * expect(shell.calls).toHaveLength(2);
386
+ * expect(shell.calls[0].command).toBe("yarn install");
387
+ * ```
388
+ */
389
+ var MemoryShellProvider = class {
390
+ /**
391
+ * All recorded shell calls.
392
+ */
393
+ calls = [];
394
+ /**
395
+ * Simulated outputs for specific commands.
396
+ */
397
+ outputs = /* @__PURE__ */ new Map();
398
+ /**
399
+ * Commands that should throw an error.
400
+ */
401
+ errors = /* @__PURE__ */ new Map();
402
+ /**
403
+ * Commands considered installed in the system PATH.
404
+ */
405
+ installedCommands = /* @__PURE__ */ new Set();
406
+ /**
407
+ * Configure the mock with predefined outputs, errors, and installed commands.
408
+ */
409
+ configure(options) {
410
+ if (options.outputs) for (const [cmd, output] of Object.entries(options.outputs)) this.outputs.set(cmd, output);
411
+ if (options.errors) for (const [cmd, error] of Object.entries(options.errors)) this.errors.set(cmd, error);
412
+ if (options.installedCommands) for (const cmd of options.installedCommands) this.installedCommands.add(cmd);
413
+ return this;
414
+ }
415
+ /**
416
+ * Record command and return simulated output.
417
+ */
418
+ async run(command, options = {}) {
419
+ this.calls.push({
420
+ command,
421
+ options
422
+ });
423
+ const errorMsg = this.errors.get(command);
424
+ if (errorMsg) throw new Error(errorMsg);
425
+ return this.outputs.get(command) ?? "";
426
+ }
427
+ /**
428
+ * Check if a specific command was called.
429
+ */
430
+ wasCalled(command) {
431
+ return this.calls.some((call) => call.command === command);
432
+ }
433
+ /**
434
+ * Check if a command matching a pattern was called.
435
+ */
436
+ wasCalledMatching(pattern) {
437
+ return this.calls.some((call) => pattern.test(call.command));
438
+ }
439
+ /**
440
+ * Get all calls matching a pattern.
441
+ */
442
+ getCallsMatching(pattern) {
443
+ return this.calls.filter((call) => pattern.test(call.command));
444
+ }
445
+ /**
446
+ * Check if a command is installed.
447
+ */
448
+ async isInstalled(command) {
449
+ return this.installedCommands.has(command);
450
+ }
451
+ /**
452
+ * Reset all recorded state.
453
+ */
454
+ reset() {
455
+ this.calls = [];
456
+ this.outputs.clear();
457
+ this.errors.clear();
458
+ this.installedCommands.clear();
459
+ }
460
+ };
461
+
462
+ //#endregion
463
+ //#region ../../src/system/services/FileDetector.ts
464
+ /**
465
+ * Service for detecting file types and getting content types.
466
+ *
467
+ * @example
468
+ * ```typescript
469
+ * const detector = alepha.inject(FileDetector);
470
+ *
471
+ * // Get content type from filename
472
+ * const mimeType = detector.getContentType("image.png"); // "image/png"
473
+ *
474
+ * // Detect file type by magic bytes
475
+ * const stream = createReadStream('image.png');
476
+ * const result = await detector.detectFileType(stream, 'image.png');
477
+ * console.log(result.mimeType); // 'image/png'
478
+ * console.log(result.verified); // true if magic bytes match
479
+ * ```
480
+ */
481
+ var FileDetector = class FileDetector {
482
+ /**
483
+ * Magic byte signatures for common file formats.
484
+ * Each signature is represented as an array of bytes or null (wildcard).
485
+ */
486
+ static MAGIC_BYTES = {
487
+ png: [{
488
+ signature: [
489
+ 137,
490
+ 80,
491
+ 78,
492
+ 71,
493
+ 13,
494
+ 10,
495
+ 26,
496
+ 10
497
+ ],
498
+ mimeType: "image/png"
499
+ }],
500
+ jpg: [
501
+ {
502
+ signature: [
503
+ 255,
504
+ 216,
505
+ 255,
506
+ 224
507
+ ],
508
+ mimeType: "image/jpeg"
509
+ },
510
+ {
511
+ signature: [
512
+ 255,
513
+ 216,
514
+ 255,
515
+ 225
516
+ ],
517
+ mimeType: "image/jpeg"
518
+ },
519
+ {
520
+ signature: [
521
+ 255,
522
+ 216,
523
+ 255,
524
+ 226
525
+ ],
526
+ mimeType: "image/jpeg"
527
+ },
528
+ {
529
+ signature: [
530
+ 255,
531
+ 216,
532
+ 255,
533
+ 227
534
+ ],
535
+ mimeType: "image/jpeg"
536
+ },
537
+ {
538
+ signature: [
539
+ 255,
540
+ 216,
541
+ 255,
542
+ 232
543
+ ],
544
+ mimeType: "image/jpeg"
545
+ }
546
+ ],
547
+ jpeg: [
548
+ {
549
+ signature: [
550
+ 255,
551
+ 216,
552
+ 255,
553
+ 224
554
+ ],
555
+ mimeType: "image/jpeg"
556
+ },
557
+ {
558
+ signature: [
559
+ 255,
560
+ 216,
561
+ 255,
562
+ 225
563
+ ],
564
+ mimeType: "image/jpeg"
565
+ },
566
+ {
567
+ signature: [
568
+ 255,
569
+ 216,
570
+ 255,
571
+ 226
572
+ ],
573
+ mimeType: "image/jpeg"
574
+ },
575
+ {
576
+ signature: [
577
+ 255,
578
+ 216,
579
+ 255,
580
+ 227
581
+ ],
582
+ mimeType: "image/jpeg"
583
+ },
584
+ {
585
+ signature: [
586
+ 255,
587
+ 216,
588
+ 255,
589
+ 232
590
+ ],
591
+ mimeType: "image/jpeg"
592
+ }
593
+ ],
594
+ gif: [{
595
+ signature: [
596
+ 71,
597
+ 73,
598
+ 70,
599
+ 56,
600
+ 55,
601
+ 97
602
+ ],
603
+ mimeType: "image/gif"
604
+ }, {
605
+ signature: [
606
+ 71,
607
+ 73,
608
+ 70,
609
+ 56,
610
+ 57,
611
+ 97
612
+ ],
613
+ mimeType: "image/gif"
614
+ }],
615
+ webp: [{
616
+ signature: [
617
+ 82,
618
+ 73,
619
+ 70,
620
+ 70,
621
+ null,
622
+ null,
623
+ null,
624
+ null,
625
+ 87,
626
+ 69,
627
+ 66,
628
+ 80
629
+ ],
630
+ mimeType: "image/webp"
631
+ }],
632
+ bmp: [{
633
+ signature: [66, 77],
634
+ mimeType: "image/bmp"
635
+ }],
636
+ ico: [{
637
+ signature: [
638
+ 0,
639
+ 0,
640
+ 1,
641
+ 0
642
+ ],
643
+ mimeType: "image/x-icon"
644
+ }],
645
+ tiff: [{
646
+ signature: [
647
+ 73,
648
+ 73,
649
+ 42,
650
+ 0
651
+ ],
652
+ mimeType: "image/tiff"
653
+ }, {
654
+ signature: [
655
+ 77,
656
+ 77,
657
+ 0,
658
+ 42
659
+ ],
660
+ mimeType: "image/tiff"
661
+ }],
662
+ tif: [{
663
+ signature: [
664
+ 73,
665
+ 73,
666
+ 42,
667
+ 0
668
+ ],
669
+ mimeType: "image/tiff"
670
+ }, {
671
+ signature: [
672
+ 77,
673
+ 77,
674
+ 0,
675
+ 42
676
+ ],
677
+ mimeType: "image/tiff"
678
+ }],
679
+ pdf: [{
680
+ signature: [
681
+ 37,
682
+ 80,
683
+ 68,
684
+ 70,
685
+ 45
686
+ ],
687
+ mimeType: "application/pdf"
688
+ }],
689
+ zip: [
690
+ {
691
+ signature: [
692
+ 80,
693
+ 75,
694
+ 3,
695
+ 4
696
+ ],
697
+ mimeType: "application/zip"
698
+ },
699
+ {
700
+ signature: [
701
+ 80,
702
+ 75,
703
+ 5,
704
+ 6
705
+ ],
706
+ mimeType: "application/zip"
707
+ },
708
+ {
709
+ signature: [
710
+ 80,
711
+ 75,
712
+ 7,
713
+ 8
714
+ ],
715
+ mimeType: "application/zip"
716
+ }
717
+ ],
718
+ rar: [{
719
+ signature: [
720
+ 82,
721
+ 97,
722
+ 114,
723
+ 33,
724
+ 26,
725
+ 7
726
+ ],
727
+ mimeType: "application/vnd.rar"
728
+ }],
729
+ "7z": [{
730
+ signature: [
731
+ 55,
732
+ 122,
733
+ 188,
734
+ 175,
735
+ 39,
736
+ 28
737
+ ],
738
+ mimeType: "application/x-7z-compressed"
739
+ }],
740
+ tar: [{
741
+ signature: [
742
+ 117,
743
+ 115,
744
+ 116,
745
+ 97,
746
+ 114
747
+ ],
748
+ mimeType: "application/x-tar"
749
+ }],
750
+ gz: [{
751
+ signature: [31, 139],
752
+ mimeType: "application/gzip"
753
+ }],
754
+ tgz: [{
755
+ signature: [31, 139],
756
+ mimeType: "application/gzip"
757
+ }],
758
+ mp3: [
759
+ {
760
+ signature: [255, 251],
761
+ mimeType: "audio/mpeg"
762
+ },
763
+ {
764
+ signature: [255, 243],
765
+ mimeType: "audio/mpeg"
766
+ },
767
+ {
768
+ signature: [255, 242],
769
+ mimeType: "audio/mpeg"
770
+ },
771
+ {
772
+ signature: [
773
+ 73,
774
+ 68,
775
+ 51
776
+ ],
777
+ mimeType: "audio/mpeg"
778
+ }
779
+ ],
780
+ wav: [{
781
+ signature: [
782
+ 82,
783
+ 73,
784
+ 70,
785
+ 70,
786
+ null,
787
+ null,
788
+ null,
789
+ null,
790
+ 87,
791
+ 65,
792
+ 86,
793
+ 69
794
+ ],
795
+ mimeType: "audio/wav"
796
+ }],
797
+ ogg: [{
798
+ signature: [
799
+ 79,
800
+ 103,
801
+ 103,
802
+ 83
803
+ ],
804
+ mimeType: "audio/ogg"
805
+ }],
806
+ flac: [{
807
+ signature: [
808
+ 102,
809
+ 76,
810
+ 97,
811
+ 67
812
+ ],
813
+ mimeType: "audio/flac"
814
+ }],
815
+ mp4: [
816
+ {
817
+ signature: [
818
+ null,
819
+ null,
820
+ null,
821
+ null,
822
+ 102,
823
+ 116,
824
+ 121,
825
+ 112
826
+ ],
827
+ mimeType: "video/mp4"
828
+ },
829
+ {
830
+ signature: [
831
+ null,
832
+ null,
833
+ null,
834
+ null,
835
+ 102,
836
+ 116,
837
+ 121,
838
+ 112,
839
+ 105,
840
+ 115,
841
+ 111,
842
+ 109
843
+ ],
844
+ mimeType: "video/mp4"
845
+ },
846
+ {
847
+ signature: [
848
+ null,
849
+ null,
850
+ null,
851
+ null,
852
+ 102,
853
+ 116,
854
+ 121,
855
+ 112,
856
+ 109,
857
+ 112,
858
+ 52,
859
+ 50
860
+ ],
861
+ mimeType: "video/mp4"
862
+ }
863
+ ],
864
+ webm: [{
865
+ signature: [
866
+ 26,
867
+ 69,
868
+ 223,
869
+ 163
870
+ ],
871
+ mimeType: "video/webm"
872
+ }],
873
+ avi: [{
874
+ signature: [
875
+ 82,
876
+ 73,
877
+ 70,
878
+ 70,
879
+ null,
880
+ null,
881
+ null,
882
+ null,
883
+ 65,
884
+ 86,
885
+ 73,
886
+ 32
887
+ ],
888
+ mimeType: "video/x-msvideo"
889
+ }],
890
+ mov: [{
891
+ signature: [
892
+ null,
893
+ null,
894
+ null,
895
+ null,
896
+ 102,
897
+ 116,
898
+ 121,
899
+ 112,
900
+ 113,
901
+ 116,
902
+ 32,
903
+ 32
904
+ ],
905
+ mimeType: "video/quicktime"
906
+ }],
907
+ mkv: [{
908
+ signature: [
909
+ 26,
910
+ 69,
911
+ 223,
912
+ 163
913
+ ],
914
+ mimeType: "video/x-matroska"
915
+ }],
916
+ docx: [{
917
+ signature: [
918
+ 80,
919
+ 75,
920
+ 3,
921
+ 4
922
+ ],
923
+ mimeType: "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
924
+ }],
925
+ xlsx: [{
926
+ signature: [
927
+ 80,
928
+ 75,
929
+ 3,
930
+ 4
931
+ ],
932
+ mimeType: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
933
+ }],
934
+ pptx: [{
935
+ signature: [
936
+ 80,
937
+ 75,
938
+ 3,
939
+ 4
940
+ ],
941
+ mimeType: "application/vnd.openxmlformats-officedocument.presentationml.presentation"
942
+ }],
943
+ doc: [{
944
+ signature: [
945
+ 208,
946
+ 207,
947
+ 17,
948
+ 224,
949
+ 161,
950
+ 177,
951
+ 26,
952
+ 225
953
+ ],
954
+ mimeType: "application/msword"
955
+ }],
956
+ xls: [{
957
+ signature: [
958
+ 208,
959
+ 207,
960
+ 17,
961
+ 224,
962
+ 161,
963
+ 177,
964
+ 26,
965
+ 225
966
+ ],
967
+ mimeType: "application/vnd.ms-excel"
968
+ }],
969
+ ppt: [{
970
+ signature: [
971
+ 208,
972
+ 207,
973
+ 17,
974
+ 224,
975
+ 161,
976
+ 177,
977
+ 26,
978
+ 225
979
+ ],
980
+ mimeType: "application/vnd.ms-powerpoint"
981
+ }]
982
+ };
983
+ /**
984
+ * All possible format signatures for checking against actual file content
985
+ */
986
+ static ALL_SIGNATURES = Object.entries(FileDetector.MAGIC_BYTES).flatMap(([ext, signatures]) => signatures.map((sig) => ({
987
+ ext,
988
+ ...sig
989
+ })));
990
+ /**
991
+ * MIME type map for file extensions.
992
+ *
993
+ * Can be used to get the content type of file based on its extension.
994
+ * Feel free to add more mime types in your project!
995
+ */
996
+ static mimeMap = {
997
+ json: "application/json",
998
+ txt: "text/plain",
999
+ html: "text/html",
1000
+ htm: "text/html",
1001
+ xml: "application/xml",
1002
+ csv: "text/csv",
1003
+ pdf: "application/pdf",
1004
+ md: "text/markdown",
1005
+ markdown: "text/markdown",
1006
+ rtf: "application/rtf",
1007
+ css: "text/css",
1008
+ js: "application/javascript",
1009
+ mjs: "application/javascript",
1010
+ ts: "application/typescript",
1011
+ jsx: "text/jsx",
1012
+ tsx: "text/tsx",
1013
+ zip: "application/zip",
1014
+ rar: "application/vnd.rar",
1015
+ "7z": "application/x-7z-compressed",
1016
+ tar: "application/x-tar",
1017
+ gz: "application/gzip",
1018
+ tgz: "application/gzip",
1019
+ png: "image/png",
1020
+ jpg: "image/jpeg",
1021
+ jpeg: "image/jpeg",
1022
+ gif: "image/gif",
1023
+ webp: "image/webp",
1024
+ svg: "image/svg+xml",
1025
+ bmp: "image/bmp",
1026
+ ico: "image/x-icon",
1027
+ tiff: "image/tiff",
1028
+ tif: "image/tiff",
1029
+ mp3: "audio/mpeg",
1030
+ wav: "audio/wav",
1031
+ ogg: "audio/ogg",
1032
+ m4a: "audio/mp4",
1033
+ aac: "audio/aac",
1034
+ flac: "audio/flac",
1035
+ mp4: "video/mp4",
1036
+ webm: "video/webm",
1037
+ avi: "video/x-msvideo",
1038
+ mov: "video/quicktime",
1039
+ wmv: "video/x-ms-wmv",
1040
+ flv: "video/x-flv",
1041
+ mkv: "video/x-matroska",
1042
+ doc: "application/msword",
1043
+ docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
1044
+ xls: "application/vnd.ms-excel",
1045
+ xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
1046
+ ppt: "application/vnd.ms-powerpoint",
1047
+ pptx: "application/vnd.openxmlformats-officedocument.presentationml.presentation",
1048
+ woff: "font/woff",
1049
+ woff2: "font/woff2",
1050
+ ttf: "font/ttf",
1051
+ otf: "font/otf",
1052
+ eot: "application/vnd.ms-fontobject"
1053
+ };
1054
+ /**
1055
+ * Reverse MIME type map for looking up extensions from MIME types.
1056
+ * Prefers shorter, more common extensions when multiple exist.
1057
+ */
1058
+ static reverseMimeMap = (() => {
1059
+ const reverse = {};
1060
+ for (const [ext, mimeType] of Object.entries(FileDetector.mimeMap)) if (!reverse[mimeType]) reverse[mimeType] = ext;
1061
+ return reverse;
1062
+ })();
1063
+ /**
1064
+ * Returns the file extension for a given MIME type.
1065
+ *
1066
+ * @param mimeType - The MIME type to look up
1067
+ * @returns The file extension (without dot), or "bin" if not found
1068
+ *
1069
+ * @example
1070
+ * ```typescript
1071
+ * const detector = alepha.inject(FileDetector);
1072
+ * const ext = detector.getExtensionFromMimeType("image/png"); // "png"
1073
+ * const ext2 = detector.getExtensionFromMimeType("application/octet-stream"); // "bin"
1074
+ * ```
1075
+ */
1076
+ getExtensionFromMimeType(mimeType) {
1077
+ return FileDetector.reverseMimeMap[mimeType] || "bin";
1078
+ }
1079
+ /**
1080
+ * Returns the content type of file based on its filename.
1081
+ *
1082
+ * @param filename - The filename to check
1083
+ * @returns The MIME type
1084
+ *
1085
+ * @example
1086
+ * ```typescript
1087
+ * const detector = alepha.inject(FileDetector);
1088
+ * const mimeType = detector.getContentType("image.png"); // "image/png"
1089
+ * ```
1090
+ */
1091
+ getContentType(filename) {
1092
+ const ext = filename.toLowerCase().split(".").pop() || "";
1093
+ return FileDetector.mimeMap[ext] || "application/octet-stream";
1094
+ }
1095
+ /**
1096
+ * Detects the file type by checking magic bytes against the stream content.
1097
+ *
1098
+ * @param stream - The readable stream to check
1099
+ * @param filename - The filename (used to get the extension)
1100
+ * @returns File type information including MIME type, extension, and verification status
1101
+ *
1102
+ * @example
1103
+ * ```typescript
1104
+ * const detector = alepha.inject(FileDetector);
1105
+ * const stream = createReadStream('image.png');
1106
+ * const result = await detector.detectFileType(stream, 'image.png');
1107
+ * console.log(result.mimeType); // 'image/png'
1108
+ * console.log(result.verified); // true if magic bytes match
1109
+ * ```
1110
+ */
1111
+ async detectFileType(stream, filename) {
1112
+ const expectedMimeType = this.getContentType(filename);
1113
+ const lastDotIndex = filename.lastIndexOf(".");
1114
+ const ext = lastDotIndex > 0 ? filename.substring(lastDotIndex + 1).toLowerCase() : "";
1115
+ const { buffer, stream: newStream } = await this.peekBytes(stream, 16);
1116
+ const expectedSignatures = FileDetector.MAGIC_BYTES[ext];
1117
+ if (expectedSignatures) {
1118
+ for (const { signature, mimeType } of expectedSignatures) if (this.matchesSignature(buffer, signature)) return {
1119
+ mimeType,
1120
+ extension: ext,
1121
+ verified: true,
1122
+ stream: newStream
1123
+ };
1124
+ }
1125
+ for (const { ext: detectedExt, signature, mimeType } of FileDetector.ALL_SIGNATURES) if (detectedExt !== ext && this.matchesSignature(buffer, signature)) return {
1126
+ mimeType,
1127
+ extension: detectedExt,
1128
+ verified: true,
1129
+ stream: newStream
1130
+ };
1131
+ return {
1132
+ mimeType: expectedMimeType,
1133
+ extension: ext,
1134
+ verified: false,
1135
+ stream: newStream
1136
+ };
1137
+ }
1138
+ /**
1139
+ * Reads all bytes from a stream and returns the first N bytes along with a new stream containing all data.
1140
+ * This approach reads the entire stream upfront to avoid complex async handling issues.
1141
+ *
1142
+ * @protected
1143
+ */
1144
+ async peekBytes(stream, numBytes) {
1145
+ const chunks = [];
1146
+ for await (const chunk of stream) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
1147
+ const allData = Buffer.concat(chunks);
1148
+ return {
1149
+ buffer: allData.subarray(0, numBytes),
1150
+ stream: Readable.from(allData)
1151
+ };
1152
+ }
1153
+ /**
1154
+ * Checks if a buffer matches a magic byte signature.
1155
+ *
1156
+ * @protected
1157
+ */
1158
+ matchesSignature(buffer, signature) {
1159
+ if (buffer.length < signature.length) return false;
1160
+ for (let i = 0; i < signature.length; i++) if (signature[i] !== null && buffer[i] !== signature[i]) return false;
1161
+ return true;
1162
+ }
1163
+ };
1164
+
1165
+ //#endregion
1166
+ //#region ../../src/system/providers/NodeFileSystemProvider.ts
1167
+ /**
1168
+ * Node.js implementation of FileSystem interface.
1169
+ *
1170
+ * @example
1171
+ * ```typescript
1172
+ * const fs = alepha.inject(NodeFileSystemProvider);
1173
+ *
1174
+ * // Create from URL
1175
+ * const file1 = fs.createFile({ url: "file:///path/to/file.png" });
1176
+ *
1177
+ * // Create from Buffer
1178
+ * const file2 = fs.createFile({ buffer: Buffer.from("hello"), name: "hello.txt" });
1179
+ *
1180
+ * // Create from text
1181
+ * const file3 = fs.createFile({ text: "Hello, world!", name: "greeting.txt" });
1182
+ *
1183
+ * // File operations
1184
+ * await fs.mkdir("/tmp/mydir", { recursive: true });
1185
+ * await fs.cp("/src/file.txt", "/dest/file.txt");
1186
+ * await fs.mv("/old/path.txt", "/new/path.txt");
1187
+ * const files = await fs.ls("/tmp");
1188
+ * await fs.rm("/tmp/file.txt");
1189
+ * ```
1190
+ */
1191
+ var NodeFileSystemProvider = class {
1192
+ detector = $inject(FileDetector);
1193
+ json = $inject(Json);
1194
+ join(...paths) {
1195
+ return join(...paths);
1196
+ }
1197
+ /**
1198
+ * Creates a FileLike object from various sources.
1199
+ *
1200
+ * @param options - Options for creating the file
1201
+ * @returns A FileLike object
1202
+ *
1203
+ * @example
1204
+ * ```typescript
1205
+ * const fs = alepha.inject(NodeFileSystemProvider);
1206
+ *
1207
+ * // From URL
1208
+ * const file1 = fs.createFile({ url: "https://example.com/image.png" });
1209
+ *
1210
+ * // From Buffer
1211
+ * const file2 = fs.createFile({
1212
+ * buffer: Buffer.from("hello"),
1213
+ * name: "hello.txt",
1214
+ * type: "text/plain"
1215
+ * });
1216
+ *
1217
+ * // From text
1218
+ * const file3 = fs.createFile({ text: "Hello!", name: "greeting.txt" });
1219
+ *
1220
+ * // From stream with detection
1221
+ * const stream = createReadStream("/path/to/file.png");
1222
+ * const file4 = fs.createFile({ stream, name: "image.png" });
1223
+ * ```
1224
+ */
1225
+ createFile(options) {
1226
+ if ("path" in options) {
1227
+ const path = options.path;
1228
+ const filename = path.split("/").pop() || "file";
1229
+ return this.createFileFromUrl(`file://${path}`, {
1230
+ type: options.type,
1231
+ name: options.name || filename
1232
+ });
1233
+ }
1234
+ if ("url" in options) return this.createFileFromUrl(options.url, {
1235
+ type: options.type,
1236
+ name: options.name
1237
+ });
1238
+ if ("response" in options) {
1239
+ if (!options.response.body) throw new AlephaError("Response has no body stream");
1240
+ const res = options.response;
1241
+ const sizeHeader = res.headers.get("content-length");
1242
+ const size = sizeHeader ? parseInt(sizeHeader, 10) : void 0;
1243
+ let name = options.name;
1244
+ const contentDisposition = res.headers.get("content-disposition");
1245
+ if (contentDisposition && !name) {
1246
+ const match = contentDisposition.match(/filename="?([^"]+)"?/);
1247
+ if (match) name = match[1];
1248
+ }
1249
+ const type = options.type || res.headers.get("content-type") || void 0;
1250
+ return this.createFileFromStream(options.response.body, {
1251
+ type,
1252
+ name,
1253
+ size
1254
+ });
1255
+ }
1256
+ if ("file" in options) return this.createFileFromWebFile(options.file, {
1257
+ type: options.type,
1258
+ name: options.name,
1259
+ size: options.size
1260
+ });
1261
+ if ("buffer" in options) return this.createFileFromBuffer(options.buffer, {
1262
+ type: options.type,
1263
+ name: options.name
1264
+ });
1265
+ if ("arrayBuffer" in options) return this.createFileFromBuffer(Buffer.from(options.arrayBuffer), {
1266
+ type: options.type,
1267
+ name: options.name
1268
+ });
1269
+ if ("text" in options) return this.createFileFromBuffer(Buffer.from(options.text, "utf-8"), {
1270
+ type: options.type || "text/plain",
1271
+ name: options.name || "file.txt"
1272
+ });
1273
+ if ("stream" in options) return this.createFileFromStream(options.stream, {
1274
+ type: options.type,
1275
+ name: options.name,
1276
+ size: options.size
1277
+ });
1278
+ throw new AlephaError("Invalid createFile options: no valid source provided");
1279
+ }
1280
+ /**
1281
+ * Removes a file or directory.
1282
+ *
1283
+ * @param path - The path to remove
1284
+ * @param options - Remove options
1285
+ *
1286
+ * @example
1287
+ * ```typescript
1288
+ * const fs = alepha.inject(NodeFileSystemProvider);
1289
+ *
1290
+ * // Remove a file
1291
+ * await fs.rm("/tmp/file.txt");
1292
+ *
1293
+ * // Remove a directory recursively
1294
+ * await fs.rm("/tmp/mydir", { recursive: true });
1295
+ *
1296
+ * // Remove with force (no error if doesn't exist)
1297
+ * await fs.rm("/tmp/maybe-exists.txt", { force: true });
1298
+ * ```
1299
+ */
1300
+ async rm(path, options) {
1301
+ await rm(path, options);
1302
+ }
1303
+ /**
1304
+ * Copies a file or directory.
1305
+ *
1306
+ * @param src - Source path
1307
+ * @param dest - Destination path
1308
+ * @param options - Copy options
1309
+ *
1310
+ * @example
1311
+ * ```typescript
1312
+ * const fs = alepha.inject(NodeFileSystemProvider);
1313
+ *
1314
+ * // Copy a file
1315
+ * await fs.cp("/src/file.txt", "/dest/file.txt");
1316
+ *
1317
+ * // Copy a directory recursively
1318
+ * await fs.cp("/src/dir", "/dest/dir", { recursive: true });
1319
+ *
1320
+ * // Copy with force (overwrite existing)
1321
+ * await fs.cp("/src/file.txt", "/dest/file.txt", { force: true });
1322
+ * ```
1323
+ */
1324
+ async cp(src, dest, options) {
1325
+ if ((await stat(src)).isDirectory()) {
1326
+ if (!options?.recursive) throw new Error(`Cannot copy directory without recursive option: ${src}`);
1327
+ await cp(src, dest, {
1328
+ recursive: true,
1329
+ force: options?.force ?? false
1330
+ });
1331
+ } else await copyFile(src, dest);
1332
+ }
1333
+ /**
1334
+ * Moves/renames a file or directory.
1335
+ *
1336
+ * @param src - Source path
1337
+ * @param dest - Destination path
1338
+ *
1339
+ * @example
1340
+ * ```typescript
1341
+ * const fs = alepha.inject(NodeFileSystemProvider);
1342
+ *
1343
+ * // Move/rename a file
1344
+ * await fs.mv("/old/path.txt", "/new/path.txt");
1345
+ *
1346
+ * // Move a directory
1347
+ * await fs.mv("/old/dir", "/new/dir");
1348
+ * ```
1349
+ */
1350
+ async mv(src, dest) {
1351
+ await rename(src, dest);
1352
+ }
1353
+ /**
1354
+ * Creates a directory.
1355
+ *
1356
+ * @param path - The directory path to create
1357
+ * @param options - Mkdir options
1358
+ *
1359
+ * @example
1360
+ * ```typescript
1361
+ * const fs = alepha.inject(NodeFileSystemProvider);
1362
+ *
1363
+ * // Create a directory
1364
+ * await fs.mkdir("/tmp/mydir");
1365
+ *
1366
+ * // Create nested directories
1367
+ * await fs.mkdir("/tmp/path/to/dir", { recursive: true });
1368
+ *
1369
+ * // Create with specific permissions
1370
+ * await fs.mkdir("/tmp/mydir", { mode: 0o755 });
1371
+ * ```
1372
+ */
1373
+ async mkdir(path, options) {
1374
+ await mkdir(path, options);
1375
+ }
1376
+ /**
1377
+ * Lists files in a directory.
1378
+ *
1379
+ * @param path - The directory path to list
1380
+ * @param options - List options
1381
+ * @returns Array of filenames
1382
+ *
1383
+ * @example
1384
+ * ```typescript
1385
+ * const fs = alepha.inject(NodeFileSystemProvider);
1386
+ *
1387
+ * // List files in a directory
1388
+ * const files = await fs.ls("/tmp");
1389
+ * console.log(files); // ["file1.txt", "file2.txt", "subdir"]
1390
+ *
1391
+ * // List with hidden files
1392
+ * const allFiles = await fs.ls("/tmp", { hidden: true });
1393
+ *
1394
+ * // List recursively
1395
+ * const allFilesRecursive = await fs.ls("/tmp", { recursive: true });
1396
+ * ```
1397
+ */
1398
+ async ls(path, options) {
1399
+ const entries = await readdir(path);
1400
+ const filteredEntries = options?.hidden ? entries : entries.filter((e) => !e.startsWith("."));
1401
+ if (options?.recursive) {
1402
+ const allFiles = [];
1403
+ for (const entry of filteredEntries) {
1404
+ const fullPath = join(path, entry);
1405
+ if ((await stat(fullPath)).isDirectory()) {
1406
+ allFiles.push(entry);
1407
+ const subFiles = await this.ls(fullPath, options);
1408
+ allFiles.push(...subFiles.map((f) => join(entry, f)));
1409
+ } else allFiles.push(entry);
1410
+ }
1411
+ return allFiles;
1412
+ }
1413
+ return filteredEntries;
1414
+ }
1415
+ /**
1416
+ * Checks if a file or directory exists.
1417
+ *
1418
+ * @param path - The path to check
1419
+ * @returns True if the path exists, false otherwise
1420
+ *
1421
+ * @example
1422
+ * ```typescript
1423
+ * const fs = alepha.inject(NodeFileSystemProvider);
1424
+ *
1425
+ * if (await fs.exists("/tmp/file.txt")) {
1426
+ * console.log("File exists");
1427
+ * }
1428
+ * ```
1429
+ */
1430
+ async exists(path) {
1431
+ try {
1432
+ await access(path);
1433
+ return true;
1434
+ } catch {
1435
+ return false;
1436
+ }
1437
+ }
1438
+ /**
1439
+ * Reads the content of a file.
1440
+ *
1441
+ * @param path - The file path to read
1442
+ * @returns The file content as a Buffer
1443
+ *
1444
+ * @example
1445
+ * ```typescript
1446
+ * const fs = alepha.inject(NodeFileSystemProvider);
1447
+ *
1448
+ * const buffer = await fs.readFile("/tmp/file.txt");
1449
+ * console.log(buffer.toString("utf-8"));
1450
+ * ```
1451
+ */
1452
+ async readFile(path) {
1453
+ return await readFile(path);
1454
+ }
1455
+ /**
1456
+ * Writes data to a file.
1457
+ *
1458
+ * @param path - The file path to write to
1459
+ * @param data - The data to write (Buffer or string)
1460
+ *
1461
+ * @example
1462
+ * ```typescript
1463
+ * const fs = alepha.inject(NodeFileSystemProvider);
1464
+ *
1465
+ * // Write string
1466
+ * await fs.writeFile("/tmp/file.txt", "Hello, world!");
1467
+ *
1468
+ * // Write Buffer
1469
+ * await fs.writeFile("/tmp/file.bin", Buffer.from([0x01, 0x02, 0x03]));
1470
+ * ```
1471
+ */
1472
+ async writeFile(path, data) {
1473
+ if (isFileLike(data)) {
1474
+ await writeFile(path, Readable.from(data.stream()));
1475
+ return;
1476
+ }
1477
+ await writeFile(path, data);
1478
+ }
1479
+ /**
1480
+ * Reads the content of a file as a string.
1481
+ *
1482
+ * @param path - The file path to read
1483
+ * @returns The file content as a string
1484
+ *
1485
+ * @example
1486
+ * ```typescript
1487
+ * const fs = alepha.inject(NodeFileSystemProvider);
1488
+ * const content = await fs.readTextFile("/tmp/file.txt");
1489
+ * ```
1490
+ */
1491
+ async readTextFile(path) {
1492
+ return (await this.readFile(path)).toString("utf-8");
1493
+ }
1494
+ /**
1495
+ * Reads the content of a file as JSON.
1496
+ *
1497
+ * @param path - The file path to read
1498
+ * @returns The parsed JSON content
1499
+ *
1500
+ * @example
1501
+ * ```typescript
1502
+ * const fs = alepha.inject(NodeFileSystemProvider);
1503
+ * const config = await fs.readJsonFile<{ name: string }>("/tmp/config.json");
1504
+ * ```
1505
+ */
1506
+ async readJsonFile(path) {
1507
+ const text = await this.readTextFile(path);
1508
+ return this.json.parse(text);
1509
+ }
1510
+ /**
1511
+ * Creates a FileLike object from a Web File.
1512
+ *
1513
+ * @protected
1514
+ */
1515
+ createFileFromWebFile(source, options = {}) {
1516
+ const name = options.name ?? source.name;
1517
+ return {
1518
+ name,
1519
+ type: options.type ?? (source.type || this.detector.getContentType(name)),
1520
+ size: options.size ?? source.size ?? 0,
1521
+ lastModified: source.lastModified || Date.now(),
1522
+ stream: () => source.stream(),
1523
+ arrayBuffer: async () => {
1524
+ return await source.arrayBuffer();
1525
+ },
1526
+ text: async () => {
1527
+ return await source.text();
1528
+ }
1529
+ };
1530
+ }
1531
+ /**
1532
+ * Creates a FileLike object from a Buffer.
1533
+ *
1534
+ * @protected
1535
+ */
1536
+ createFileFromBuffer(source, options = {}) {
1537
+ const name = options.name ?? "file";
1538
+ return {
1539
+ name,
1540
+ type: options.type ?? this.detector.getContentType(options.name ?? name),
1541
+ size: source.byteLength,
1542
+ lastModified: Date.now(),
1543
+ stream: () => Readable.from(source),
1544
+ arrayBuffer: async () => {
1545
+ return this.bufferToArrayBuffer(source);
1546
+ },
1547
+ text: async () => {
1548
+ return source.toString("utf-8");
1549
+ }
1550
+ };
1551
+ }
1552
+ /**
1553
+ * Creates a FileLike object from a stream.
1554
+ *
1555
+ * @protected
1556
+ */
1557
+ createFileFromStream(source, options = {}) {
1558
+ let buffer = null;
1559
+ return {
1560
+ name: options.name ?? "file",
1561
+ type: options.type ?? this.detector.getContentType(options.name ?? "file"),
1562
+ size: options.size ?? 0,
1563
+ lastModified: Date.now(),
1564
+ stream: () => source,
1565
+ _buffer: null,
1566
+ arrayBuffer: async () => {
1567
+ buffer ??= await this.streamToBuffer(source);
1568
+ return this.bufferToArrayBuffer(buffer);
1569
+ },
1570
+ text: async () => {
1571
+ buffer ??= await this.streamToBuffer(source);
1572
+ return buffer.toString("utf-8");
1573
+ }
1574
+ };
1575
+ }
1576
+ /**
1577
+ * Creates a FileLike object from a URL.
1578
+ *
1579
+ * @protected
1580
+ */
1581
+ createFileFromUrl(url, options = {}) {
1582
+ const parsedUrl = new URL(url);
1583
+ const filename = options.name || parsedUrl.pathname.split("/").pop() || "file";
1584
+ let buffer = null;
1585
+ return {
1586
+ name: filename,
1587
+ type: options.type ?? this.detector.getContentType(filename),
1588
+ size: 0,
1589
+ lastModified: Date.now(),
1590
+ stream: () => this.createStreamFromUrl(url),
1591
+ arrayBuffer: async () => {
1592
+ buffer ??= await this.loadFromUrl(url);
1593
+ return this.bufferToArrayBuffer(buffer);
1594
+ },
1595
+ text: async () => {
1596
+ buffer ??= await this.loadFromUrl(url);
1597
+ return buffer.toString("utf-8");
1598
+ },
1599
+ filepath: url
1600
+ };
1601
+ }
1602
+ /**
1603
+ * Gets a streaming response from a URL.
1604
+ *
1605
+ * @protected
1606
+ */
1607
+ getStreamingResponse(url) {
1608
+ const stream = new PassThrough();
1609
+ fetch(url).then((res) => Readable.fromWeb(res.body).pipe(stream)).catch((err) => stream.destroy(err));
1610
+ return stream;
1611
+ }
1612
+ /**
1613
+ * Loads data from a URL.
1614
+ *
1615
+ * @protected
1616
+ */
1617
+ async loadFromUrl(url) {
1618
+ const parsedUrl = new URL(url);
1619
+ if (parsedUrl.protocol === "file:") return await readFile(fileURLToPath(url));
1620
+ else if (parsedUrl.protocol === "http:" || parsedUrl.protocol === "https:") {
1621
+ const response = await fetch(url);
1622
+ if (!response.ok) throw new Error(`Failed to fetch ${url}: ${response.status} ${response.statusText}`);
1623
+ const arrayBuffer = await response.arrayBuffer();
1624
+ return Buffer.from(arrayBuffer);
1625
+ } else throw new Error(`Unsupported protocol: ${parsedUrl.protocol}`);
1626
+ }
1627
+ /**
1628
+ * Creates a stream from a URL.
1629
+ *
1630
+ * @protected
1631
+ */
1632
+ createStreamFromUrl(url) {
1633
+ const parsedUrl = new URL(url);
1634
+ if (parsedUrl.protocol === "file:") return createReadStream(fileURLToPath(url));
1635
+ else if (parsedUrl.protocol === "http:" || parsedUrl.protocol === "https:") return this.getStreamingResponse(url);
1636
+ else throw new AlephaError(`Unsupported protocol: ${parsedUrl.protocol}`);
1637
+ }
1638
+ /**
1639
+ * Converts a stream-like object to a Buffer.
1640
+ *
1641
+ * @protected
1642
+ */
1643
+ async streamToBuffer(streamLike) {
1644
+ const stream = streamLike instanceof Readable ? streamLike : Readable.fromWeb(streamLike);
1645
+ return new Promise((resolve, reject) => {
1646
+ const buffer = [];
1647
+ stream.on("data", (chunk) => buffer.push(Buffer.from(chunk)));
1648
+ stream.on("end", () => resolve(Buffer.concat(buffer)));
1649
+ stream.on("error", (err) => reject(new AlephaError("Error converting stream", { cause: err })));
1650
+ });
1651
+ }
1652
+ /**
1653
+ * Converts a Node.js Buffer to an ArrayBuffer.
1654
+ *
1655
+ * @protected
1656
+ */
1657
+ bufferToArrayBuffer(buffer) {
1658
+ return buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
1659
+ }
1660
+ };
1661
+
1662
+ //#endregion
1663
+ //#region ../../src/system/providers/NodeShellProvider.ts
1664
+ /**
1665
+ * Node.js implementation of ShellProvider.
1666
+ *
1667
+ * Executes shell commands using Node.js child_process module.
1668
+ * Supports binary resolution from node_modules/.bin for local packages.
1669
+ */
1670
+ var NodeShellProvider = class {
1671
+ log = $logger();
1672
+ fs = $inject(FileSystemProvider);
1673
+ /**
1674
+ * Run a shell command or binary.
1675
+ */
1676
+ async run(command, options = {}) {
1677
+ const { resolve = false, capture = false, root, env } = options;
1678
+ const cwd = root ?? process.cwd();
1679
+ this.log.debug(`Shell: ${command}`, {
1680
+ cwd,
1681
+ resolve,
1682
+ capture
1683
+ });
1684
+ let executable;
1685
+ let args;
1686
+ if (resolve) {
1687
+ const [bin, ...rest] = command.split(" ");
1688
+ executable = await this.resolveExecutable(bin, cwd);
1689
+ args = rest;
1690
+ } else [executable, ...args] = command.split(" ");
1691
+ if (capture) return this.execCapture(command, {
1692
+ cwd,
1693
+ env
1694
+ });
1695
+ return this.execInherit(executable, args, {
1696
+ cwd,
1697
+ env
1698
+ });
1699
+ }
1700
+ /**
1701
+ * Execute command with inherited stdio (streams to terminal).
1702
+ */
1703
+ async execInherit(executable, args, options) {
1704
+ const proc = spawn(executable, args, {
1705
+ stdio: "inherit",
1706
+ cwd: options.cwd,
1707
+ env: {
1708
+ ...process.env,
1709
+ ...options.env
1710
+ }
1711
+ });
1712
+ return new Promise((resolve, reject) => {
1713
+ proc.on("exit", (code) => {
1714
+ if (code === 0 || code === null) resolve("");
1715
+ else reject(new AlephaError(`Command exited with code ${code}`));
1716
+ });
1717
+ proc.on("error", reject);
1718
+ });
1719
+ }
1720
+ /**
1721
+ * Execute command and capture stdout.
1722
+ */
1723
+ execCapture(command, options) {
1724
+ return new Promise((resolve, reject) => {
1725
+ exec(command, {
1726
+ cwd: options.cwd,
1727
+ env: {
1728
+ ...process.env,
1729
+ LOG_FORMAT: "pretty",
1730
+ ...options.env
1731
+ }
1732
+ }, (err, stdout) => {
1733
+ if (err) {
1734
+ err.stdout = stdout;
1735
+ reject(err);
1736
+ } else resolve(stdout);
1737
+ });
1738
+ });
1739
+ }
1740
+ /**
1741
+ * Resolve executable path from node_modules/.bin.
1742
+ *
1743
+ * Search order:
1744
+ * 1. Local: node_modules/.bin/
1745
+ * 2. Pnpm nested: node_modules/alepha/node_modules/.bin/
1746
+ * 3. Monorepo: Walk up to 3 parent directories
1747
+ */
1748
+ async resolveExecutable(name, root) {
1749
+ const suffix = process.platform === "win32" ? ".cmd" : "";
1750
+ let execPath = await this.findExecutable(root, `node_modules/.bin/${name}${suffix}`);
1751
+ if (!execPath) execPath = await this.findExecutable(root, `node_modules/alepha/node_modules/.bin/${name}${suffix}`);
1752
+ if (!execPath) {
1753
+ let parentDir = this.fs.join(root, "..");
1754
+ for (let i = 0; i < 3; i++) {
1755
+ execPath = await this.findExecutable(parentDir, `node_modules/.bin/${name}${suffix}`);
1756
+ if (execPath) break;
1757
+ parentDir = this.fs.join(parentDir, "..");
1758
+ }
1759
+ }
1760
+ if (!execPath) throw new AlephaError(`Could not find executable for '${name}'. Make sure the package is installed.`);
1761
+ return execPath;
1762
+ }
1763
+ /**
1764
+ * Check if executable exists at path.
1765
+ */
1766
+ async findExecutable(root, relativePath) {
1767
+ const fullPath = this.fs.join(root, relativePath);
1768
+ if (await this.fs.exists(fullPath)) return fullPath;
1769
+ }
1770
+ /**
1771
+ * Check if a command is installed and available in the system PATH.
1772
+ */
1773
+ isInstalled(command) {
1774
+ return new Promise((resolve) => {
1775
+ exec(process.platform === "win32" ? `where ${command}` : `command -v ${command}`, (error) => resolve(!error));
1776
+ });
1777
+ }
1778
+ };
1779
+
1780
+ //#endregion
1781
+ //#region ../../src/system/providers/ShellProvider.ts
1782
+ /**
1783
+ * Abstract provider for executing shell commands and binaries.
1784
+ *
1785
+ * Implementations:
1786
+ * - `NodeShellProvider` - Real shell execution using Node.js child_process
1787
+ * - `MemoryShellProvider` - In-memory mock for testing
1788
+ *
1789
+ * @example
1790
+ * ```typescript
1791
+ * class MyService {
1792
+ * protected readonly shell = $inject(ShellProvider);
1793
+ *
1794
+ * async build() {
1795
+ * // Run shell command directly
1796
+ * await this.shell.run("yarn install");
1797
+ *
1798
+ * // Run local binary with resolution
1799
+ * await this.shell.run("vite build", { resolve: true });
1800
+ *
1801
+ * // Capture output
1802
+ * const output = await this.shell.run("echo hello", { capture: true });
1803
+ * }
1804
+ * }
1805
+ * ```
1806
+ */
1807
+ var ShellProvider = class {};
1808
+
1809
+ //#endregion
1810
+ //#region ../../src/system/index.ts
1811
+ /**
1812
+ * | type | quality | stability |
1813
+ * |------|---------|-----------|
1814
+ * | tooling | standard | stable |
1815
+ *
1816
+ * System-level abstractions for portable code across runtimes.
1817
+ *
1818
+ * **Features:**
1819
+ * - File system operations (read, write, exists, etc.)
1820
+ * - Shell command execution
1821
+ * - File type detection and MIME utilities
1822
+ * - Memory implementations for testing
1823
+ *
1824
+ * @module alepha.system
1825
+ */
1826
+ const AlephaSystem = $module({
1827
+ name: "alepha.system",
1828
+ primitives: [],
1829
+ services: [
1830
+ FileDetector,
1831
+ FileSystemProvider,
1832
+ MemoryFileSystemProvider,
1833
+ NodeFileSystemProvider,
1834
+ ShellProvider,
1835
+ MemoryShellProvider,
1836
+ NodeShellProvider
1837
+ ],
1838
+ register: (alepha) => alepha.with({
1839
+ optional: true,
1840
+ provide: FileSystemProvider,
1841
+ use: NodeFileSystemProvider
1842
+ }).with({
1843
+ optional: true,
1844
+ provide: ShellProvider,
1845
+ use: alepha.isTest() ? MemoryShellProvider : NodeShellProvider
1846
+ })
1847
+ });
1848
+
1849
+ //#endregion
14
1850
  //#region ../../src/core/constants/KIND.ts
15
1851
  /**
16
1852
  * Used for identifying primitives.
@@ -79,7 +1915,7 @@ $atom$1[KIND] = "atom";
79
1915
  * Options can be overridden via vite.config.ts or CLI flags.
80
1916
  */
81
1917
  const buildOptions = $atom$1({
82
- name: "alepha.build.options",
1918
+ name: "alepha.cli.build.options",
83
1919
  description: "Build configuration options",
84
1920
  schema: t.object({
85
1921
  stats: t.optional(t.boolean({ default: false })),
@@ -102,6 +1938,187 @@ const buildOptions = $atom$1({
102
1938
  default: {}
103
1939
  });
104
1940
 
1941
+ //#endregion
1942
+ //#region ../../src/cli/atoms/appEntryOptions.ts
1943
+ const appEntryOptions = $atom({
1944
+ name: "alepha.cli.appEntry.options",
1945
+ schema: t.object({
1946
+ server: t.optional(t.text()),
1947
+ browser: t.optional(t.text()),
1948
+ style: t.optional(t.text())
1949
+ }),
1950
+ default: {}
1951
+ });
1952
+
1953
+ //#endregion
1954
+ //#region ../../src/cli/providers/AppEntryProvider.ts
1955
+ /**
1956
+ * Service for locating entry files in Alepha projects.
1957
+ *
1958
+ * Originally in alepha/vite, moved to CLI to avoid cli -> vite dependency.
1959
+ */
1960
+ var AppEntryProvider = class {
1961
+ fs = $inject(FileSystemProvider);
1962
+ options = $use(appEntryOptions);
1963
+ serverEntries = [
1964
+ "main.server.ts",
1965
+ "main.server.tsx",
1966
+ "main.ts",
1967
+ "main.tsx"
1968
+ ];
1969
+ browserEntries = [
1970
+ "main.browser.ts",
1971
+ "main.browser.tsx",
1972
+ "main.ts",
1973
+ "main.tsx"
1974
+ ];
1975
+ styleEntries = [
1976
+ "main.css",
1977
+ "styles.css",
1978
+ "style.css"
1979
+ ];
1980
+ /**
1981
+ * Get application entry points.
1982
+ *
1983
+ * Server entry is required, an error is thrown if not found.
1984
+ * Browser entry is optional.
1985
+ *
1986
+ * It will first check for custom entries in options, see appEntryOptions.
1987
+ */
1988
+ async getAppEntry(root) {
1989
+ const appEntry = {
1990
+ root,
1991
+ server: ""
1992
+ };
1993
+ if (this.options.server) {
1994
+ if (!await this.fs.exists(this.fs.join(root, this.options.server))) throw new AlephaError(`Custom server entry "${this.options.server}" not found.`);
1995
+ appEntry.server = this.options.server;
1996
+ }
1997
+ if (this.options.browser) {
1998
+ if (!await this.fs.exists(this.fs.join(root, this.options.browser))) throw new AlephaError(`Custom browser entry "${this.options.browser}" not found.`);
1999
+ appEntry.browser = this.options.browser;
2000
+ }
2001
+ if (this.options.style) {
2002
+ if (!await this.fs.exists(this.fs.join(root, this.options.style))) throw new AlephaError(`Custom style entry "${this.options.style}" not found.`);
2003
+ appEntry.style = this.options.style;
2004
+ }
2005
+ const srcFiles = await this.fs.ls(this.fs.join(root, "src"));
2006
+ if (!appEntry.server) {
2007
+ for (const entry of this.serverEntries) if (srcFiles.includes(entry)) {
2008
+ appEntry.server = this.fs.join("src", entry);
2009
+ break;
2010
+ }
2011
+ }
2012
+ 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.");
2013
+ if (!appEntry.browser) {
2014
+ for (const entry of this.browserEntries) if (srcFiles.includes(entry)) {
2015
+ appEntry.browser = this.fs.join("src", entry);
2016
+ break;
2017
+ }
2018
+ }
2019
+ if (!appEntry.style) {
2020
+ for (const entry of this.styleEntries) if (srcFiles.includes(entry)) {
2021
+ appEntry.style = this.fs.join("src", entry);
2022
+ break;
2023
+ }
2024
+ }
2025
+ return appEntry;
2026
+ }
2027
+ };
2028
+
2029
+ //#endregion
2030
+ //#region ../../src/cli/services/ViteUtils.ts
2031
+ var ViteUtils = class {
2032
+ fs = $inject(FileSystemProvider);
2033
+ viteDevServer;
2034
+ generateIndexHtml(entry) {
2035
+ const style = entry.style;
2036
+ const browser = entry.browser ?? entry.server;
2037
+ return `
2038
+ <!DOCTYPE html>
2039
+ <html lang="en">
2040
+ <head>
2041
+ <meta charset="UTF-8" />
2042
+ <title>App</title>
2043
+ <meta name="viewport" content="width=device-width, initial-scale=1"/>
2044
+ ${style ? `<link rel="stylesheet" href="/${style}" />` : ""}
2045
+ </head>
2046
+ <body>
2047
+ <div id="root"></div>
2048
+ <script type="module" src="/${browser}"><\/script>
2049
+ </body>
2050
+ </html>
2051
+ `.trim();
2052
+ }
2053
+ /**
2054
+ * We need to close the Vite dev server after build is done.
2055
+ */
2056
+ onReady = $hook({
2057
+ on: "ready",
2058
+ priority: "last",
2059
+ handler: async () => {
2060
+ await this.viteDevServer?.close();
2061
+ }
2062
+ });
2063
+ onStop = $hook({
2064
+ on: "stop",
2065
+ handler: async () => {
2066
+ await this.viteDevServer?.close();
2067
+ }
2068
+ });
2069
+ async runAlepha(opts) {
2070
+ const { createServer } = await importVite();
2071
+ process.env.NODE_ENV = opts.mode;
2072
+ process.env.ALEPHA_CLI_IMPORT = "true";
2073
+ process.env.LOG_LEVEL ??= "warn";
2074
+ /**
2075
+ * 01/26 Vite 7
2076
+ * "runnerImport" doesn't work as expected here. (e.g. build docs fail)
2077
+ * -> We still use devServer and ssrLoadModule for now.
2078
+ * -> This is clearly a bad stuff, we need to find better way.
2079
+ */
2080
+ this.viteDevServer = await createServer({
2081
+ server: { middlewareMode: true },
2082
+ appType: "custom",
2083
+ logLevel: "silent"
2084
+ });
2085
+ await this.viteDevServer.ssrLoadModule(opts.entry.server);
2086
+ const alepha = globalThis.__alepha;
2087
+ if (!alepha) throw new AlephaError("Alepha instance not found after loading entry module");
2088
+ return alepha;
2089
+ }
2090
+ };
2091
+
2092
+ //#endregion
2093
+ //#region ../../src/cli/providers/ViteBuildProvider.ts
2094
+ var ViteBuildProvider = class {
2095
+ alepha;
2096
+ appEntry;
2097
+ viteUtils = $inject(ViteUtils);
2098
+ async init(opts) {
2099
+ const alepha = await this.viteUtils.runAlepha({
2100
+ entry: opts.entry,
2101
+ mode: "production"
2102
+ });
2103
+ this.alepha = alepha;
2104
+ this.appEntry = opts.entry;
2105
+ return alepha;
2106
+ }
2107
+ hasClient() {
2108
+ if (!this.alepha) throw new AlephaError("ViteBuildProvider not initialized");
2109
+ try {
2110
+ this.alepha.inject("ReactServerProvider");
2111
+ return true;
2112
+ } catch {
2113
+ return false;
2114
+ }
2115
+ }
2116
+ generateIndexHtml() {
2117
+ if (!this.appEntry) throw new AlephaError("ViteBuildProvider not initialized");
2118
+ return this.viteUtils.generateIndexHtml(this.appEntry);
2119
+ }
2120
+ };
2121
+
105
2122
  //#endregion
106
2123
  //#region ../../src/cli/services/AlephaCliUtils.ts
107
2124
  /**
@@ -117,36 +2134,24 @@ var AlephaCliUtils = class {
117
2134
  log = $logger();
118
2135
  fs = $inject(FileSystemProvider);
119
2136
  envUtils = $inject(EnvUtils);
2137
+ boot = $inject(AppEntryProvider);
2138
+ shell = $inject(ShellProvider);
2139
+ viteUtils = $inject(ViteUtils);
120
2140
  /**
121
2141
  * Execute a command with inherited stdio.
2142
+ *
2143
+ * @param command - The command to execute
2144
+ * @param options.root - Working directory
2145
+ * @param options.env - Additional environment variables
2146
+ * @param options.global - If true, run command directly without resolving from node_modules
122
2147
  */
123
2148
  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);
2149
+ await this.shell.run(command, {
2150
+ root: options.root,
2151
+ env: options.env,
2152
+ resolve: !options.global,
2153
+ capture: false
2154
+ });
150
2155
  }
151
2156
  /**
152
2157
  * Write a configuration file to node_modules/.alepha directory.
@@ -159,25 +2164,14 @@ var AlephaCliUtils = class {
159
2164
  this.log.debug(`Config file written: ${path}`);
160
2165
  return path;
161
2166
  }
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}`);
2167
+ async loadAlephaFromServerEntryFile(opts) {
2168
+ let entry;
2169
+ if ("root" in opts) entry = await this.boot.getAppEntry(opts.root);
2170
+ else entry = opts.entry;
2171
+ return await this.viteUtils.runAlepha({
2172
+ entry,
2173
+ mode: opts.mode
2174
+ });
181
2175
  }
182
2176
  /**
183
2177
  * Generate JavaScript code for Drizzle entities export.
@@ -205,12 +2199,39 @@ ${models.map((it) => `export const ${it} = models["${it}"];`).join("\n")}
205
2199
  async exists(root, path) {
206
2200
  return this.fs.exists(this.fs.join(root, path));
207
2201
  }
208
- async checkFileExists(root, name) {
209
- const configPath = this.fs.join(root, name);
210
- if (await this.fs.exists(configPath)) return configPath;
2202
+ /**
2203
+ * Check if a command is installed and available in the system PATH.
2204
+ */
2205
+ isInstalledAsync(cmd) {
2206
+ return this.shell.isInstalled(cmd);
211
2207
  }
212
2208
  };
213
2209
 
2210
+ //#endregion
2211
+ //#region ../../package.json
2212
+ var devDependencies = {
2213
+ "@biomejs/biome": "^2.3.13",
2214
+ "@electric-sql/pglite": "^0.3.15",
2215
+ "@faker-js/faker": "^10.2.0",
2216
+ "@testing-library/dom": "^10.4.1",
2217
+ "@testing-library/react": "^16.3.2",
2218
+ "@types/node": "^25.1.0",
2219
+ "@types/nodemailer": "^7.0.9",
2220
+ "@types/react": "^19.2.10",
2221
+ "@types/react-dom": "^19.2.3",
2222
+ "@types/ws": "^8.18.1",
2223
+ "cron-schedule": "^6.0.0",
2224
+ "jose": "^6.1.3",
2225
+ "jsdom": "^27.4.0",
2226
+ "openid-client": "^6.8.1",
2227
+ "prom-client": "^15.1.3",
2228
+ "react": "^19.2.4",
2229
+ "react-dom": "^19.2.4",
2230
+ "swagger-ui-dist": "^5.31.0",
2231
+ "tsdown": "^0.20.1",
2232
+ "vitest": "^4.0.18"
2233
+ };
2234
+
214
2235
  //#endregion
215
2236
  //#region ../../src/cli/version.ts
216
2237
  const packageJson = JSON.parse(readFileSync(new URL("../../package.json", import.meta.url), "utf-8"));
@@ -234,11 +2255,8 @@ var PackageManagerUtils = class {
234
2255
  /**
235
2256
  * Detect the package manager used in the project.
236
2257
  */
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";
2258
+ async getPackageManager(root, pm) {
2259
+ if (pm) return pm;
242
2260
  if (this.alepha.isBun()) return "bun";
243
2261
  if (await this.fs.exists(this.fs.join(root, "bun.lock"))) return "bun";
244
2262
  if (await this.fs.exists(this.fs.join(root, "yarn.lock"))) return "yarn";
@@ -246,6 +2264,46 @@ var PackageManagerUtils = class {
246
2264
  return "npm";
247
2265
  }
248
2266
  /**
2267
+ * Detect workspace context when inside a monorepo package.
2268
+ *
2269
+ * Checks if we're inside a workspace package (e.g., packages/my-pkg or apps/my-app)
2270
+ * by looking 2 levels up for workspace indicators like lockfiles and config files.
2271
+ *
2272
+ * @param root - The current package directory
2273
+ * @returns Workspace context with root path, PM, and config presence
2274
+ */
2275
+ async getWorkspaceContext(root) {
2276
+ const workspaceRoot = this.fs.join(root, "..", "..");
2277
+ const [hasYarnLock, hasPnpmLock, hasNpmLock, hasBunLock] = await Promise.all([
2278
+ this.fs.exists(this.fs.join(workspaceRoot, "yarn.lock")),
2279
+ this.fs.exists(this.fs.join(workspaceRoot, "pnpm-lock.yaml")),
2280
+ this.fs.exists(this.fs.join(workspaceRoot, "package-lock.json")),
2281
+ this.fs.exists(this.fs.join(workspaceRoot, "bun.lock"))
2282
+ ]);
2283
+ const [hasBiome, hasEditorConfig, hasTsConfig, hasWorkspacePackageJson] = await Promise.all([
2284
+ this.fs.exists(this.fs.join(workspaceRoot, "biome.json")),
2285
+ this.fs.exists(this.fs.join(workspaceRoot, ".editorconfig")),
2286
+ this.fs.exists(this.fs.join(workspaceRoot, "tsconfig.json")),
2287
+ this.fs.exists(this.fs.join(workspaceRoot, "package.json"))
2288
+ ]);
2289
+ const isPackage = (hasYarnLock || hasPnpmLock || hasNpmLock || hasBunLock) && hasWorkspacePackageJson;
2290
+ let packageManager = null;
2291
+ if (hasYarnLock) packageManager = "yarn";
2292
+ else if (hasPnpmLock) packageManager = "pnpm";
2293
+ else if (hasBunLock) packageManager = "bun";
2294
+ else if (hasNpmLock) packageManager = "npm";
2295
+ return {
2296
+ isPackage,
2297
+ workspaceRoot: isPackage ? workspaceRoot : null,
2298
+ packageManager,
2299
+ config: {
2300
+ biomeJson: hasBiome,
2301
+ editorconfig: hasEditorConfig,
2302
+ tsconfigJson: hasTsConfig
2303
+ }
2304
+ };
2305
+ }
2306
+ /**
249
2307
  * Get the install command for a package.
250
2308
  */
251
2309
  async getInstallCommand(root, packageName, dev = true) {
@@ -283,17 +2341,33 @@ var PackageManagerUtils = class {
283
2341
  return this.hasDependency(root, "expo");
284
2342
  }
285
2343
  /**
2344
+ * Check if React is present in the project.
2345
+ */
2346
+ async hasReact(root) {
2347
+ return this.hasDependency(root, "react");
2348
+ }
2349
+ /**
286
2350
  * Install a dependency if it's missing from the project.
2351
+ * Optionally checks workspace root for the dependency in monorepo setups.
287
2352
  */
288
2353
  async ensureDependency(root, packageName, options = {}) {
289
- const { dev = true } = options;
2354
+ const { dev = true, checkWorkspace = false } = options;
290
2355
  if (await this.hasDependency(root, packageName)) {
291
2356
  this.log.debug(`Dependency '${packageName}' is already installed`);
292
2357
  return;
293
2358
  }
2359
+ if (checkWorkspace) {
2360
+ const workspace = await this.getWorkspaceContext(root);
2361
+ if (workspace.workspaceRoot) {
2362
+ if (await this.hasDependency(workspace.workspaceRoot, packageName)) {
2363
+ this.log.debug(`Dependency '${packageName}' is already installed in workspace root`);
2364
+ return;
2365
+ }
2366
+ }
2367
+ }
294
2368
  const cmd = await this.getInstallCommand(root, packageName, dev);
295
2369
  if (options.run) await options.run(cmd, {
296
- alias: `installing ${packageName}`,
2370
+ alias: `add ${packageName}`,
297
2371
  root
298
2372
  });
299
2373
  else if (options.exec) {
@@ -368,21 +2442,26 @@ var PackageManagerUtils = class {
368
2442
  await this.writePackageJson(root, content);
369
2443
  return content;
370
2444
  }
371
- const packageJson$1 = await this.readPackageJson(root);
2445
+ const packageJson = await this.readPackageJson(root);
372
2446
  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;
2447
+ packageJson.type = "module";
2448
+ packageJson.dependencies ??= {};
2449
+ packageJson.devDependencies ??= {};
2450
+ packageJson.scripts ??= {};
2451
+ Object.assign(packageJson.dependencies, newContent.dependencies);
2452
+ Object.assign(packageJson.devDependencies, newContent.devDependencies);
2453
+ Object.assign(packageJson.scripts, newContent.scripts);
2454
+ await this.writePackageJson(root, packageJson);
2455
+ return packageJson;
382
2456
  }
383
2457
  generatePackageJsonContent(modes) {
2458
+ const alephaDeps = devDependencies;
384
2459
  const dependencies = { alepha: `^${version}` };
385
- const devDependencies = {};
2460
+ const devDependencies$1 = {};
2461
+ if (!modes.isPackage) {
2462
+ devDependencies$1["@biomejs/biome"] = alephaDeps["@biomejs/biome"];
2463
+ if (modes.test) devDependencies$1.vitest = alephaDeps.vitest;
2464
+ }
386
2465
  const scripts = {
387
2466
  dev: "alepha dev",
388
2467
  build: "alepha build",
@@ -390,20 +2469,20 @@ var PackageManagerUtils = class {
390
2469
  typecheck: "alepha typecheck",
391
2470
  verify: "alepha verify"
392
2471
  };
393
- if (modes.admin) {
2472
+ if (modes.test) scripts.test = "vitest run";
2473
+ if (modes.ui) {
394
2474
  dependencies["@alepha/ui"] = `^${version}`;
395
- modes.web = true;
2475
+ modes.react = true;
396
2476
  }
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";
2477
+ if (modes.react) {
2478
+ dependencies.react = alephaDeps.react;
2479
+ dependencies["react-dom"] = alephaDeps["react-dom"];
2480
+ devDependencies$1["@types/react"] = alephaDeps["@types/react"];
402
2481
  }
403
2482
  return {
404
2483
  type: "module",
405
2484
  dependencies,
406
- devDependencies,
2485
+ devDependencies: devDependencies$1,
407
2486
  scripts
408
2487
  };
409
2488
  }
@@ -416,87 +2495,20 @@ var PackageManagerUtils = class {
416
2495
  };
417
2496
 
418
2497
  //#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
- }),
435
- });
436
- }
437
- `.trim();
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
- };
2498
+ //#region ../../src/cli/templates/agentMd.ts
2499
+ const agentMd = (type, options = {}) => {
2500
+ const { react = false, projectName = "my-app" } = options;
2501
+ const header = type === "claude" ? `# CLAUDE.md
453
2502
 
454
- //#endregion
455
- //#region ../../src/cli/assets/biomeJson.ts
456
- const biomeJson = `
457
- {
458
- "$schema": "https://biomejs.dev/schemas/latest/schema.json",
459
- "vcs": {
460
- "enabled": true,
461
- "clientKind": "git"
462
- },
463
- "files": {
464
- "ignoreUnknown": true,
465
- "includes": ["**", "!node_modules", "!dist"]
466
- },
467
- "formatter": {
468
- "enabled": true,
469
- "useEditorconfig": true
470
- },
471
- "linter": {
472
- "enabled": true,
473
- "rules": {
474
- "recommended": true
475
- },
476
- "domains": {
477
- "react": "recommended"
478
- }
479
- },
480
- "assist": {
481
- "actions": {
482
- "source": {
483
- "organizeImports": "on"
484
- }
485
- }
486
- }
487
- }
488
- `.trim();
2503
+ This file provides guidance to Claude Code when working with this Alepha project.` : `# AGENTS.md
489
2504
 
490
- //#endregion
491
- //#region ../../src/cli/assets/claudeMd.ts
492
- const claudeMd = (options = {}) => {
493
- const { react = false, projectName = "my-app" } = options;
2505
+ This file provides guidance to AI coding assistants when working with this Alepha project.`;
494
2506
  const reactSection = react ? `
495
2507
  ## React & Frontend
496
2508
 
497
2509
  ### Pages with \`$page\`
498
2510
  \`\`\`tsx
499
- import { $page } from "@alepha/react/router";
2511
+ import { $page } from "alepha/react/router";
500
2512
  import { $client } from "alepha/server/links";
501
2513
  import type { UserController } from "./UserController.ts";
502
2514
 
@@ -522,9 +2534,9 @@ class AppRouter {
522
2534
 
523
2535
  ### React Hooks
524
2536
  \`\`\`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";
2537
+ import { useAlepha, useClient, useStore, useAction, useInject } from "alepha/react";
2538
+ import { useRouter, useActive } from "alepha/react/router";
2539
+ import { useForm } from "alepha/react/form";
528
2540
  \`\`\`
529
2541
 
530
2542
  - \`useClient<Controller>()\` - Type-safe API calls
@@ -549,8 +2561,8 @@ ${projectName}/
549
2561
  │ │ ├── AppRouter.ts # Routes with $page
550
2562
  │ │ └── index.ts # Web module definition with $module
551
2563
  │ ├── main.server.ts # Server entry
552
- └── main.browser.ts # Browser entry (React only)
553
- ├── index.html # (React only)
2564
+ ├── main.browser.ts # Browser entry (React only)
2565
+ │ └── main.css # CSS entry (React only)
554
2566
  ├── package.json
555
2567
  └── tsconfig.json
556
2568
  \`\`\`
@@ -569,9 +2581,7 @@ ${projectName}/
569
2581
  └── tsconfig.json
570
2582
  \`\`\`
571
2583
  `;
572
- return `# CLAUDE.md
573
-
574
- This file provides guidance to Claude Code when working with this Alepha project.
2584
+ return `${header}
575
2585
 
576
2586
  ## Overview
577
2587
 
@@ -585,16 +2595,18 @@ This is an **Alepha** project - a convention-driven TypeScript framework for typ
585
2595
 
586
2596
  ## Rules
587
2597
 
588
- - Use TypeScript strict mode
2598
+ - Use TypeScript strict mode, always check types (\`alepha typecheck\`)
589
2599
  - Use Biome for formatting (\`alepha lint\`)
590
- - Use Vitest for testing
591
- - One file = one class
2600
+ - Use Vitest for testing (\`alepha test\`)
2601
+ - One file = one class, multiple interfaces/types allowed
592
2602
  - Primitives are class properties (except \`$entity\`, \`$atom\`)
593
2603
  - No decorators, no Express/Fastify patterns
594
2604
  - No manual instantiation - use dependency injection
595
2605
  - Use \`protected\` instead of \`private\` for class members
596
2606
  - Import with file extensions: \`import { User } from "./User.ts"\`
597
2607
  - Use \`t\` from Alepha for schemas (not Zod)
2608
+ - Prefer \`t.text()\` over \`t.string()\` for user input (has default max length, auto-trim, supports lowercase option)
2609
+ - One file = one schema (schemas/createUserSchema.ts)
598
2610
 
599
2611
  ## Project Structure
600
2612
  ${projectStructure}
@@ -637,17 +2649,12 @@ export const userEntity = $entity({
637
2649
  id: db.primaryKey(),
638
2650
  email: t.email(),
639
2651
  createdAt: db.createdAt(),
640
- updatedAt: db.updatedAt(),
641
2652
  }),
642
2653
  indexes: [{ column: "email", unique: true }],
643
2654
  });
644
2655
 
645
2656
  class UserService {
646
- repo = $repository(userEntity);
647
-
648
- async findById(id: string) {
649
- return this.repo.findById(id);
650
- }
2657
+ userRepository = $repository(userEntity);
651
2658
  }
652
2659
  \`\`\`
653
2660
 
@@ -657,11 +2664,6 @@ import { $inject } from "alepha";
657
2664
 
658
2665
  class OrderService {
659
2666
  userService = $inject(UserService); // Within same module
660
-
661
- async createOrder(userId: string) {
662
- const user = await this.userService.findById(userId);
663
- // ...
664
- }
665
2667
  }
666
2668
 
667
2669
  // Cross-module: use $client instead of $inject
@@ -725,7 +2727,7 @@ ${reactSection}
725
2727
  | \`$bucket\` | \`alepha/bucket\` | File storage |
726
2728
  | \`$issuer\` | \`alepha/security\` | JWT tokens |
727
2729
  | \`$command\` | \`alepha/command\` | CLI commands |${react ? `
728
- | \`$page\` | \`@alepha/react/router\` | React pages with SSR |
2730
+ | \`$page\` | \`alepha/react/router\` | React pages with SSR |
729
2731
  | \`$atom\` | \`alepha\` | Global state |` : ""}
730
2732
 
731
2733
  ## Testing
@@ -773,15 +2775,92 @@ alepha test # Run tests (if applicable)
773
2775
  alepha build # Build the project
774
2776
  \`\`\`
775
2777
 
776
- ## Documentation
2778
+ ## Documentation
2779
+
2780
+ - Full docs: https://alepha.dev/llms.txt
2781
+ - Detailed docs: https://alepha.dev/llms-full.txt
2782
+
2783
+ ## Source Code Access
2784
+
2785
+ Full framework source available at \`node_modules/alepha/src/\`.
2786
+ Read primitives directly when you need implementation details.
2787
+ `.trim();
2788
+ };
2789
+
2790
+ //#endregion
2791
+ //#region ../../src/cli/templates/apiHelloControllerTs.ts
2792
+ const apiHelloControllerTs = () => `
2793
+ import { t } from "alepha";
2794
+ import { $action } from "alepha/server";
2795
+
2796
+ export class HelloController {
2797
+ hello = $action({
2798
+ path: "/hello",
2799
+ schema: {
2800
+ response: t.object({
2801
+ message: t.string(),
2802
+ }),
2803
+ },
2804
+ handler: () => ({
2805
+ message: "Hello, Alepha!",
2806
+ }),
2807
+ });
2808
+ }
2809
+ `.trim();
2810
+
2811
+ //#endregion
2812
+ //#region ../../src/cli/templates/apiIndexTs.ts
2813
+ const apiIndexTs = (options = {}) => {
2814
+ const { appName = "app" } = options;
2815
+ return `
2816
+ import { $module } from "alepha";
2817
+ import { HelloController } from "./controllers/HelloController.ts";
777
2818
 
778
- - Full docs: https://alepha.dev/llms.txt
779
- - Detailed docs: https://alepha.dev/llms-full.txt
2819
+ export const ApiModule = $module({
2820
+ name: "${appName}.api",
2821
+ services: [HelloController],
2822
+ });
780
2823
  `.trim();
781
2824
  };
782
2825
 
783
2826
  //#endregion
784
- //#region ../../src/cli/assets/dummySpecTs.ts
2827
+ //#region ../../src/cli/templates/biomeJson.ts
2828
+ const biomeJson = () => `
2829
+ {
2830
+ "$schema": "https://biomejs.dev/schemas/latest/schema.json",
2831
+ "vcs": {
2832
+ "enabled": true,
2833
+ "clientKind": "git"
2834
+ },
2835
+ "files": {
2836
+ "ignoreUnknown": true,
2837
+ "includes": ["**", "!node_modules", "!dist"]
2838
+ },
2839
+ "formatter": {
2840
+ "enabled": true,
2841
+ "useEditorconfig": true
2842
+ },
2843
+ "linter": {
2844
+ "enabled": true,
2845
+ "rules": {
2846
+ "recommended": true
2847
+ },
2848
+ "domains": {
2849
+ "react": "recommended"
2850
+ }
2851
+ },
2852
+ "assist": {
2853
+ "actions": {
2854
+ "source": {
2855
+ "organizeImports": "on"
2856
+ }
2857
+ }
2858
+ }
2859
+ }
2860
+ `.trim();
2861
+
2862
+ //#endregion
2863
+ //#region ../../src/cli/templates/dummySpecTs.ts
785
2864
  const dummySpecTs = () => `
786
2865
  import { test, expect } from "vitest";
787
2866
 
@@ -791,8 +2870,8 @@ test("dummy test", () => {
791
2870
  `.trim();
792
2871
 
793
2872
  //#endregion
794
- //#region ../../src/cli/assets/editorconfig.ts
795
- const editorconfig = `
2873
+ //#region ../../src/cli/templates/editorconfig.ts
2874
+ const editorconfig = () => `
796
2875
  # https://editorconfig.org
797
2876
 
798
2877
  root = true
@@ -807,23 +2886,48 @@ indent_size = 2
807
2886
  `.trim();
808
2887
 
809
2888
  //#endregion
810
- //#region ../../src/cli/assets/indexHtml.ts
811
- const indexHtml = (browserEntry) => `
812
- <!DOCTYPE html>
813
- <html lang="en">
814
- <head>
815
- <meta charset="UTF-8">
816
- <title>App</title>
817
- </head>
818
- <body>
819
- <div id="root"></div>
820
- <script type="module" src="${browserEntry}"><\/script>
821
- </body>
822
- </html>
2889
+ //#region ../../src/cli/templates/gitignore.ts
2890
+ const gitignore = () => `
2891
+ # Dependencies
2892
+ node_modules/
2893
+
2894
+ # Build outputs
2895
+ dist/
2896
+ .vite/
2897
+
2898
+ # Environment files
2899
+ .env
2900
+ .env.*
2901
+ !.env.example
2902
+
2903
+ # IDE
2904
+ .idea/
2905
+ *.swp
2906
+ *.swo
2907
+
2908
+ # OS
2909
+ .DS_Store
2910
+ Thumbs.db
2911
+
2912
+ # Logs
2913
+ *.log
2914
+ logs/
2915
+
2916
+ # Test coverage
2917
+ coverage/
2918
+
2919
+ # Yarn
2920
+ .yarn/*
2921
+ !.yarn/patches
2922
+ !.yarn/plugins
2923
+ !.yarn/releases
2924
+ !.yarn/sdks
2925
+ !.yarn/versions
2926
+ .pnp.*
823
2927
  `.trim();
824
2928
 
825
2929
  //#endregion
826
- //#region ../../src/cli/assets/mainBrowserTs.ts
2930
+ //#region ../../src/cli/templates/mainBrowserTs.ts
827
2931
  const mainBrowserTs = () => `
828
2932
  import { Alepha, run } from "alepha";
829
2933
  import { WebModule } from "./web/index.ts";
@@ -836,58 +2940,115 @@ run(alepha);
836
2940
  `.trim();
837
2941
 
838
2942
  //#endregion
839
- //#region ../../src/cli/assets/mainServerTs.ts
2943
+ //#region ../../src/cli/templates/mainCss.ts
2944
+ const mainCss = (options = {}) => {
2945
+ if (options.ui) return `@import "@alepha/ui/styles";`;
2946
+ return `
2947
+ * {
2948
+ box-sizing: border-box;
2949
+ margin: 0;
2950
+ padding: 0;
2951
+ }
2952
+
2953
+ html,
2954
+ body {
2955
+ height: 100%;
2956
+ }
2957
+
2958
+ body {
2959
+ font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
2960
+ "Helvetica Neue", Arial, sans-serif;
2961
+ line-height: 1.5;
2962
+ -webkit-font-smoothing: antialiased;
2963
+ }
2964
+
2965
+ #root {
2966
+ height: 100%;
2967
+ }
2968
+ `.trim();
2969
+ };
2970
+
2971
+ //#endregion
2972
+ //#region ../../src/cli/templates/mainServerTs.ts
840
2973
  const mainServerTs = (options = {}) => {
841
- const { react = false } = options;
2974
+ const { api = false, react = false } = options;
2975
+ const imports = [];
2976
+ const withs = [];
2977
+ if (api) {
2978
+ imports.push(`import { ApiModule } from "./api/index.ts";`);
2979
+ withs.push(`alepha.with(ApiModule);`);
2980
+ }
2981
+ if (react) {
2982
+ imports.push(`import { WebModule } from "./web/index.ts";`);
2983
+ withs.push(`alepha.with(WebModule);`);
2984
+ }
842
2985
  return `
843
2986
  import { Alepha, run } from "alepha";
844
- import { ApiModule } from "./api/index.ts";
845
- ${react ? `import { WebModule } from "./web/index.ts";\n` : ""}
2987
+ ${imports.length > 0 ? `${imports.join("\n")}\n` : ""}
846
2988
  const alepha = Alepha.create();
2989
+ ${withs.length > 0 ? `\n${withs.join("\n")}` : ""}
847
2990
 
848
- alepha.with(ApiModule);
849
- ${react ? `alepha.with(WebModule);\n` : ""}
850
2991
  run(alepha);
851
2992
  `.trim();
852
2993
  };
853
2994
 
854
2995
  //#endregion
855
- //#region ../../src/cli/assets/tsconfigJson.ts
856
- const tsconfigJson = `
2996
+ //#region ../../src/cli/templates/tsconfigJson.ts
2997
+ const tsconfigJson = () => `
857
2998
  {
858
2999
  "extends": "alepha/tsconfig.base"
859
3000
  }
860
3001
  `.trim();
861
3002
 
862
3003
  //#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({
3004
+ //#region ../../src/cli/templates/webAppRouterTs.ts
3005
+ const webAppRouterTs = (options) => {
3006
+ const imports = [];
3007
+ const classMembers = [];
3008
+ if (options.ui) imports.push("import { $ui } from \"@alepha/ui\";");
3009
+ imports.push("import { $page } from \"alepha/react/router\";");
3010
+ if (options.api) {
3011
+ imports.push("import { $client } from \"alepha/server/links\";");
3012
+ imports.push("import type { HelloController } from \"../api/controllers/HelloController.ts\";");
3013
+ classMembers.push(" api = $client<HelloController>();");
3014
+ }
3015
+ if (options.ui) {
3016
+ classMembers.push(" ui = $ui();");
3017
+ classMembers.push(` layout = $page({
3018
+ parent: this.ui.root,
3019
+ children: () => [this.home],
3020
+ });`);
3021
+ }
3022
+ if (options.api) classMembers.push(` home = $page({
873
3023
  path: "/",
874
3024
  lazy: () => import("./components/Hello.tsx"),
875
3025
  loader: () => this.api.hello(),
876
- });
877
- }
878
- `.trim();
3026
+ });`);
3027
+ else classMembers.push(` home = $page({
3028
+ path: "/",
3029
+ lazy: () => import("./components/Hello.tsx"),
3030
+ });`);
3031
+ return `${imports.join("\n")}
3032
+
3033
+ export class AppRouter {
3034
+ ${classMembers.join("\n\n")}
3035
+ }`;
3036
+ };
879
3037
 
880
3038
  //#endregion
881
- //#region ../../src/cli/assets/webHelloComponentTsx.ts
882
- const webHelloComponentTsx = () => `
3039
+ //#region ../../src/cli/templates/webHelloComponentTsx.ts
3040
+ const webHelloComponentTsx = () => `import { useState } from "react";
3041
+
883
3042
  interface Props {
884
- message: string;
3043
+ message?: string;
885
3044
  }
886
3045
 
887
3046
  const Hello = (props: Props) => {
3047
+ const [message, setMessage] = useState(props.message ?? "");
888
3048
  return (
889
3049
  <div>
890
- <h1>{props.message}</h1>
3050
+ <h1>{message}</h1>
3051
+ <input value={message} onChange={e => setMessage(e.target.value)} />
891
3052
  <p>Edit this component in src/web/components/Hello.tsx</p>
892
3053
  </div>
893
3054
  );
@@ -897,7 +3058,7 @@ export default Hello;
897
3058
  `.trim();
898
3059
 
899
3060
  //#endregion
900
- //#region ../../src/cli/assets/webIndexTs.ts
3061
+ //#region ../../src/cli/templates/webIndexTs.ts
901
3062
  const webIndexTs = (options = {}) => {
902
3063
  const { appName = "app" } = options;
903
3064
  return `
@@ -926,6 +3087,7 @@ var ProjectScaffolder = class {
926
3087
  log = $logger();
927
3088
  fs = $inject(FileSystemProvider);
928
3089
  pm = $inject(PackageManagerUtils);
3090
+ utils = $inject(AlephaCliUtils);
929
3091
  /**
930
3092
  * Get the app name from the directory name.
931
3093
  *
@@ -942,69 +3104,99 @@ var ProjectScaffolder = class {
942
3104
  */
943
3105
  async ensureConfig(root, opts) {
944
3106
  const tasks = [];
3107
+ const force = opts.force ?? false;
3108
+ const checkWorkspace = opts.checkWorkspace ?? false;
945
3109
  if (opts.packageJson) tasks.push(this.pm.ensurePackageJson(root, typeof opts.packageJson === "boolean" ? {} : opts.packageJson).then(() => {}));
946
- if (opts.tsconfigJson) tasks.push(this.ensureTsConfig(root));
947
- if (opts.indexHtml) tasks.push(this.ensureReactProject(root));
948
- if (opts.biomeJson) tasks.push(this.ensureBiomeConfig(root));
949
- if (opts.editorconfig) tasks.push(this.ensureEditorConfig(root));
950
- if (opts.claudeMd) tasks.push(this.ensureClaudeMd(root, typeof opts.claudeMd === "boolean" ? {} : opts.claudeMd));
3110
+ if (opts.tsconfigJson) tasks.push(this.ensureTsConfig(root, { force }));
3111
+ if (opts.biomeJson) tasks.push(this.ensureBiomeConfig(root, {
3112
+ force,
3113
+ checkWorkspace
3114
+ }));
3115
+ if (opts.editorconfig) tasks.push(this.ensureEditorConfig(root, {
3116
+ force,
3117
+ checkWorkspace
3118
+ }));
3119
+ if (opts.agentMd) tasks.push(this.ensureAgentMd(root, {
3120
+ ...opts.agentMd,
3121
+ force
3122
+ }));
951
3123
  await Promise.all(tasks);
952
3124
  }
953
- async ensureTsConfig(root) {
954
- if (await this.existsInParents(root, "tsconfig.json")) return;
955
- await this.fs.writeFile(this.fs.join(root, "tsconfig.json"), tsconfigJson);
3125
+ async ensureTsConfig(root, opts = {}) {
3126
+ if (!opts.force && await this.existsInParents(root, "tsconfig.json")) return;
3127
+ await this.fs.writeFile(this.fs.join(root, "tsconfig.json"), tsconfigJson());
3128
+ }
3129
+ async ensureBiomeConfig(root, opts = {}) {
3130
+ if (!opts.force && opts.checkWorkspace && await this.existsInParents(root, "biome.json")) return;
3131
+ await this.ensureFile(root, "biome.json", biomeJson(), opts.force);
956
3132
  }
957
- async ensureBiomeConfig(root) {
958
- await this.ensureFileIfNotExists(root, "biome.json", biomeJson);
3133
+ async ensureEditorConfig(root, opts = {}) {
3134
+ if (!opts.force && opts.checkWorkspace && await this.existsInParents(root, ".editorconfig")) return;
3135
+ await this.ensureFile(root, ".editorconfig", editorconfig(), opts.force);
3136
+ }
3137
+ /**
3138
+ * Ensure git repository is initialized with .gitignore.
3139
+ *
3140
+ * @returns true if git was initialized, false if already exists or git unavailable
3141
+ */
3142
+ async ensureGitRepo(root, opts = {}) {
3143
+ const gitDir = this.fs.join(root, ".git");
3144
+ if (!opts.force && await this.fs.exists(gitDir)) return false;
3145
+ if (!await this.utils.isInstalledAsync("git")) return false;
3146
+ await this.utils.exec("git init", {
3147
+ root,
3148
+ global: true
3149
+ });
3150
+ await this.ensureFile(root, ".gitignore", gitignore(), opts.force);
3151
+ return true;
959
3152
  }
960
- async ensureEditorConfig(root) {
961
- await this.ensureFileIfNotExists(root, ".editorconfig", editorconfig);
3153
+ async ensureAgentMd(root, options) {
3154
+ const filename = options.type === "claude" ? "CLAUDE.md" : "AGENTS.md";
3155
+ await this.ensureFile(root, filename, agentMd(options.type, options), options.force);
962
3156
  }
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));
3157
+ /**
3158
+ * Ensure src/main.server.ts exists with correct module imports.
3159
+ */
3160
+ async ensureMainServerTs(root, opts = {}) {
3161
+ const srcDir = this.fs.join(root, "src");
3162
+ await this.fs.mkdir(srcDir, { recursive: true });
3163
+ await this.ensureFile(srcDir, "main.server.ts", mainServerTs({
3164
+ api: opts.api,
3165
+ react: opts.react
3166
+ }), opts.force);
966
3167
  }
967
3168
  /**
968
- * Ensure src/main.server.ts exists with full API structure.
3169
+ * Ensure API module structure exists.
969
3170
  *
970
3171
  * Creates:
971
- * - src/main.server.ts (entry point)
972
3172
  * - src/api/index.ts (API module)
973
3173
  * - src/api/controllers/HelloController.ts (example controller)
974
3174
  */
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
- }
3175
+ async ensureApiProject(root, opts = {}) {
980
3176
  const appName = this.getAppName(root);
981
3177
  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());
3178
+ await this.ensureFile(root, "src/api/index.ts", apiIndexTs({ appName }), opts.force);
3179
+ await this.ensureFile(root, "src/api/controllers/HelloController.ts", apiHelloControllerTs(), opts.force);
985
3180
  }
986
3181
  /**
987
- * Ensure full React project structure exists.
3182
+ * Ensure web/React project structure exists.
988
3183
  *
989
3184
  * Creates:
990
- * - index.html
991
- * - src/main.server.ts, src/main.browser.ts
992
- * - src/api/index.ts, src/api/controllers/HelloController.ts
3185
+ * - src/main.browser.ts
3186
+ * - src/main.css
993
3187
  * - src/web/index.ts, src/web/AppRouter.ts, src/web/components/Hello.tsx
994
3188
  */
995
- async ensureReactProject(root) {
996
- if (await this.fs.exists(this.fs.join(root, "index.html"))) return;
3189
+ async ensureWebProject(root, opts = {}) {
997
3190
  const appName = this.getAppName(root);
998
- await this.fs.mkdir(this.fs.join(root, "src/api/controllers"), { recursive: true });
999
3191
  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());
3192
+ await this.ensureFile(root, "src/main.css", mainCss({ ui: opts.ui }), opts.force);
3193
+ await this.ensureFile(root, "src/web/index.ts", webIndexTs({ appName }), opts.force);
3194
+ await this.ensureFile(root, "src/web/AppRouter.ts", webAppRouterTs({
3195
+ api: opts.api,
3196
+ ui: opts.ui
3197
+ }), opts.force);
3198
+ await this.ensureFile(root, "src/web/components/Hello.tsx", webHelloComponentTsx(), opts.force);
3199
+ await this.ensureFile(root, "src/main.browser.ts", mainBrowserTs(), opts.force);
1008
3200
  }
1009
3201
  /**
1010
3202
  * Ensure test directory exists with a dummy test file.
@@ -1019,9 +3211,12 @@ var ProjectScaffolder = class {
1019
3211
  }
1020
3212
  if ((await this.fs.ls(testDir)).length === 0) await this.fs.writeFile(dummyPath, dummySpecTs());
1021
3213
  }
1022
- async ensureFileIfNotExists(root, relativePath, content) {
3214
+ /**
3215
+ * Write a file, optionally overriding if it exists.
3216
+ */
3217
+ async ensureFile(root, relativePath, content, force) {
1023
3218
  const fullPath = this.fs.join(root, relativePath);
1024
- if (!await this.fs.exists(fullPath)) await this.fs.writeFile(fullPath, content);
3219
+ if (force || !await this.fs.exists(fullPath)) await this.fs.writeFile(fullPath, content);
1025
3220
  }
1026
3221
  /**
1027
3222
  * Check if a file exists in the given directory or any parent directory.
@@ -1045,15 +3240,13 @@ var BuildCommand = class {
1045
3240
  utils = $inject(AlephaCliUtils);
1046
3241
  pm = $inject(PackageManagerUtils);
1047
3242
  scaffolder = $inject(ProjectScaffolder);
3243
+ boot = $inject(AppEntryProvider);
3244
+ viteBuildProvider = $inject(ViteBuildProvider);
1048
3245
  options = $use(buildOptions);
1049
3246
  build = $command({
1050
3247
  name: "build",
1051
3248
  mode: "production",
1052
3249
  description: "Build the project for production",
1053
- args: t.optional(t.text({
1054
- title: "path",
1055
- description: "Filepath to build"
1056
- })),
1057
3250
  flags: t.object({
1058
3251
  stats: t.optional(t.boolean({ description: "Generate build stats report" })),
1059
3252
  vercel: t.optional(t.boolean({ description: "Generate Vercel deployment configuration" })),
@@ -1062,53 +3255,70 @@ var BuildCommand = class {
1062
3255
  sitemap: t.optional(t.text({ description: "Generate sitemap.xml with base URL" })),
1063
3256
  bun: t.optional(t.boolean({ description: "Prioritize .bun.ts entry files for Bun runtime" }))
1064
3257
  }),
1065
- handler: async ({ flags, args, run, root }) => {
1066
- process.env.ALEPHA_BUILD_MODE = "cli";
3258
+ handler: async ({ flags, run, root }) => {
1067
3259
  process.env.NODE_ENV = "production";
1068
3260
  if (await this.pm.hasExpo(root)) return;
1069
3261
  await this.scaffolder.ensureConfig(root, { tsconfigJson: true });
1070
- const entry = await boot.getServerEntry(root, args);
3262
+ const entry = await this.boot.getAppEntry(root);
1071
3263
  this.log.trace("Entry file found", { entry });
1072
3264
  const distDir = "dist";
1073
- const clientDir = "public";
1074
- await this.pm.ensureDependency(root, "vite", {
1075
- run,
1076
- exec: (cmd, opts) => this.utils.exec(cmd, opts)
1077
- });
3265
+ const publicDir = "public";
1078
3266
  await run.rm("dist", { alias: "clean dist" });
1079
3267
  const options = this.options;
1080
3268
  await this.utils.loadEnv(root, [".env", ".env.production"]);
1081
3269
  const stats = flags.stats ?? options.stats ?? false;
1082
- const hasClient = await this.fs.exists(this.fs.join(root, "index.html"));
1083
- if (hasClient) await run({
1084
- name: "vite build client",
1085
- handler: () => buildClient({
1086
- silent: true,
1087
- dist: `${distDir}/${clientDir}`,
1088
- stats,
1089
- precompress: true
1090
- })
3270
+ let template = "";
3271
+ let hasClient = false;
3272
+ let alepha;
3273
+ await run({
3274
+ name: "analyze app",
3275
+ handler: async () => {
3276
+ alepha = await this.viteBuildProvider.init({ entry });
3277
+ hasClient = this.viteBuildProvider.hasClient();
3278
+ if (hasClient) template = this.viteBuildProvider.generateIndexHtml();
3279
+ }
1091
3280
  });
3281
+ if (!alepha) throw new AlephaError("Alepha instance not found");
3282
+ if (hasClient) {
3283
+ const indexHtmlPath = this.fs.join(root, "index.html");
3284
+ await this.fs.writeFile(indexHtmlPath, template);
3285
+ try {
3286
+ await run({
3287
+ name: "vite build client",
3288
+ handler: () => buildClient({
3289
+ silent: true,
3290
+ dist: `${distDir}/${publicDir}`,
3291
+ stats,
3292
+ precompress: true
3293
+ })
3294
+ });
3295
+ } finally {
3296
+ await this.fs.rm(indexHtmlPath);
3297
+ }
3298
+ }
1092
3299
  await run({
1093
3300
  name: "vite build server",
1094
3301
  handler: async () => {
1095
- const clientIndexPath = `${distDir}/${clientDir}/index.html`;
3302
+ if (!alepha) throw new AlephaError("Alepha instance not found");
3303
+ const clientIndexPath = `${distDir}/${publicDir}/index.html`;
1096
3304
  const clientBuilt = await this.fs.exists(clientIndexPath);
1097
3305
  const conditions = [];
1098
3306
  if (flags.bun) conditions.push("bun");
1099
3307
  if (options.cloudflare) conditions.push("workerd");
1100
3308
  await buildServer({
1101
3309
  silent: true,
1102
- entry,
3310
+ entry: entry.server,
1103
3311
  distDir,
1104
- clientDir: clientBuilt ? clientDir : void 0,
3312
+ clientDir: clientBuilt ? publicDir : void 0,
1105
3313
  stats,
1106
- conditions
3314
+ conditions,
3315
+ alepha
1107
3316
  });
1108
3317
  if (clientBuilt) await this.fs.rm(clientIndexPath);
1109
3318
  }
1110
3319
  });
1111
3320
  await copyAssets({
3321
+ alepha,
1112
3322
  root,
1113
3323
  entry: `${distDir}/index.js`,
1114
3324
  distDir,
@@ -1116,31 +3326,24 @@ var BuildCommand = class {
1116
3326
  });
1117
3327
  if (hasClient) {
1118
3328
  const sitemapHostname = flags.sitemap ?? options.sitemap?.hostname;
1119
- if (sitemapHostname) await run({
1120
- name: "add sitemap",
1121
- handler: async () => {
1122
- await this.fs.writeFile(`${distDir}/${clientDir}/sitemap.xml`, await generateSitemap({
1123
- entry: `${distDir}/index.js`,
1124
- baseUrl: sitemapHostname
1125
- }));
1126
- }
3329
+ if (sitemapHostname) await generateSitemap({
3330
+ alepha,
3331
+ baseUrl: sitemapHostname,
3332
+ output: `${distDir}/${publicDir}/sitemap.xml`,
3333
+ run
1127
3334
  });
1128
- await run({
1129
- name: "pre-render pages",
1130
- handler: async () => {
1131
- await prerenderPages({
1132
- dist: `${distDir}/${clientDir}`,
1133
- entry: `${distDir}/index.js`,
1134
- compress: true
1135
- });
1136
- }
3335
+ await prerenderPages({
3336
+ alepha,
3337
+ dist: `${distDir}/${publicDir}`,
3338
+ compress: true,
3339
+ run
1137
3340
  });
1138
3341
  }
1139
3342
  if (flags.vercel || options.vercel) await run({
1140
3343
  name: "add Vercel config",
1141
3344
  handler: () => generateVercel({
1142
3345
  distDir,
1143
- clientDir,
3346
+ clientDir: publicDir,
1144
3347
  config: options.vercel
1145
3348
  })
1146
3349
  });
@@ -1187,6 +3390,7 @@ var DbCommand = class {
1187
3390
  log = $logger();
1188
3391
  fs = $inject(FileSystemProvider);
1189
3392
  utils = $inject(AlephaCliUtils);
3393
+ entryProvider = $inject(AppEntryProvider);
1190
3394
  /**
1191
3395
  * Check if database migrations are up to date.
1192
3396
  */
@@ -1201,7 +3405,11 @@ var DbCommand = class {
1201
3405
  handler: async ({ args, root }) => {
1202
3406
  const rootDir = root;
1203
3407
  this.log.debug(`Using project root: ${rootDir}`);
1204
- const { alepha } = await this.utils.loadAlephaFromServerEntryFile(rootDir, args);
3408
+ const entry = await this.entryProvider.getAppEntry(root);
3409
+ const alepha = await this.utils.loadAlephaFromServerEntryFile({
3410
+ mode: "development",
3411
+ entry
3412
+ });
1205
3413
  const repositoryProvider = alepha.inject("RepositoryProvider");
1206
3414
  const drizzleKitProvider = alepha.inject("DrizzleKitProvider");
1207
3415
  const accepted = /* @__PURE__ */ new Set([]);
@@ -1365,7 +3573,11 @@ var DbCommand = class {
1365
3573
  if (options.env) envFiles.push(`.env.${options.env}`);
1366
3574
  await this.utils.loadEnv(rootDir, envFiles);
1367
3575
  this.log.debug(`Using project root: ${rootDir}`);
1368
- const { alepha, entry } = await this.utils.loadAlephaFromServerEntryFile(rootDir, options.args);
3576
+ const entry = await this.entryProvider.getAppEntry(rootDir);
3577
+ const alepha = await this.utils.loadAlephaFromServerEntryFile({
3578
+ mode: "development",
3579
+ entry
3580
+ });
1369
3581
  const drizzleKitProvider = alepha.inject("DrizzleKitProvider");
1370
3582
  const repositoryProvider = alepha.inject("RepositoryProvider");
1371
3583
  const accepted = /* @__PURE__ */ new Set([]);
@@ -1389,11 +3601,11 @@ var DbCommand = class {
1389
3601
  providerUrl: provider.url,
1390
3602
  providerDriver: provider.driver,
1391
3603
  dialect,
1392
- entry,
3604
+ entry: this.fs.join(rootDir, entry.server),
1393
3605
  rootDir
1394
3606
  });
1395
3607
  const flags = options.commandFlags ? ` ${options.commandFlags}` : "";
1396
- await this.utils.exec(`drizzle-kit ${options.command} --config=${drizzleConfigJsPath}${flags}`, { env: { NODE_OPTIONS: "--import tsx" } });
3608
+ await this.utils.exec(`drizzle-kit ${options.command} --config=${drizzleConfigJsPath}${flags}`, { env: { NODE_OPTIONS: [process.env.NODE_OPTIONS, "--import tsx"].filter(Boolean).join(" ") } });
1397
3609
  }
1398
3610
  }
1399
3611
  /**
@@ -1529,70 +3741,275 @@ var DeployCommand = class {
1529
3741
  };
1530
3742
 
1531
3743
  //#endregion
1532
- //#region ../../src/cli/commands/dev.ts
1533
- var DevCommand = class {
3744
+ //#region ../../src/cli/providers/ViteDevServerProvider.ts
3745
+ /**
3746
+ * Vite development server with Alepha integration.
3747
+ *
3748
+ * Architecture:
3749
+ * - Vite runs in middleware mode (no HTTP server)
3750
+ * - Alepha is the HTTP server via server:onRequest event
3751
+ * - Request flow: Page requests → Alepha SSR, Assets → Vite middleware
3752
+ *
3753
+ * HMR Strategy:
3754
+ * - Browser-only changes (CSS, client components) → Vite HMR (React Fast Refresh)
3755
+ * - Server-only changes → Restart Alepha → Full browser reload
3756
+ * - Shared changes → Restart Alepha → Let Vite HMR propagate
3757
+ *
3758
+ * Features:
3759
+ * - Automatic .env reload detection
3760
+ * - Error recovery on next file change
3761
+ * - Optimized module invalidation (only changed files + importers)
3762
+ */
3763
+ var ViteDevServerProvider = class {
1534
3764
  log = $logger();
1535
3765
  fs = $inject(FileSystemProvider);
1536
- utils = $inject(AlephaCliUtils);
1537
- pm = $inject(PackageManagerUtils);
1538
- scaffolder = $inject(ProjectScaffolder);
1539
- alepha = $inject(Alepha);
3766
+ templateProvider = $inject(ViteUtils);
3767
+ server;
3768
+ options;
3769
+ alepha = null;
3770
+ hasError = false;
3771
+ changedFiles = /* @__PURE__ */ new Set();
3772
+ async init(options) {
3773
+ this.options = options;
3774
+ await this.createViteServer();
3775
+ return await this.loadAlepha(true);
3776
+ }
3777
+ async start() {
3778
+ await this.alepha?.start();
3779
+ }
1540
3780
  /**
1541
- * Will run the project in watch mode.
1542
- *
1543
- * - If an index.html file is found in the project root, it will run Vite in dev mode.
1544
- * - Otherwise, it will look for a server entry file and run it with tsx in watch mode.
3781
+ * Create the Vite server in middleware mode.
1545
3782
  */
1546
- dev = $command({
1547
- name: "dev",
1548
- description: "Run the project in development mode",
1549
- args: t.optional(t.text({
1550
- title: "path",
1551
- description: "Filepath to run"
1552
- })),
1553
- handler: async ({ args, root }) => {
1554
- const expo = await this.pm.hasExpo(root);
1555
- await this.scaffolder.ensureConfig(root, { tsconfigJson: true });
1556
- if (expo) {
1557
- await this.utils.exec("expo start");
1558
- return;
3783
+ async createViteServer() {
3784
+ const { createServer } = await importVite();
3785
+ const viteReact = await importViteReact();
3786
+ const plugins = [];
3787
+ if (viteReact) plugins.push(viteReact());
3788
+ plugins.push(viteAlephaSsrPreload());
3789
+ plugins.push(this.createHmrPlugin());
3790
+ this.server = await createServer({
3791
+ root: this.options.root,
3792
+ plugins,
3793
+ server: { middlewareMode: true },
3794
+ appType: "custom",
3795
+ customLogger: {
3796
+ info: () => {},
3797
+ warn: this.log.warn.bind(this.log),
3798
+ error: this.log.error.bind(this.log),
3799
+ warnOnce: this.log.warn.bind(this.log),
3800
+ clearScreen: () => {},
3801
+ hasWarned: false,
3802
+ hasErrorLogged: () => false
1559
3803
  }
1560
- const entry = await boot.getServerEntry(root, args);
1561
- this.log.trace("Entry file found", { entry });
1562
- if (!await this.isFullstackProject(root)) {
1563
- const exe = await this.isBunProject(root) ? "bun" : "tsx";
1564
- let cmd = `${exe} --watch`;
1565
- if (await this.utils.exists(root, ".env")) cmd += " --env-file=./.env";
1566
- cmd += ` ${entry}`;
1567
- await this.utils.exec(cmd, { global: exe === "bun" });
1568
- return;
3804
+ });
3805
+ this.server.restart = async () => {
3806
+ const startTime = Date.now();
3807
+ try {
3808
+ this.hasError = true;
3809
+ await this.loadAlepha(false);
3810
+ await this.alepha?.start();
3811
+ this.log.debug(`Env reloaded in ${Date.now() - startTime}ms`);
3812
+ this.sendBrowserReload();
3813
+ } catch (err) {
3814
+ this.hasError = true;
3815
+ this.log.error("Reload failed", err);
3816
+ this.log.warn("Waiting for file changes to retry...");
3817
+ this.alepha = null;
3818
+ }
3819
+ };
3820
+ }
3821
+ /**
3822
+ * Vite plugin to handle HMR for Alepha.
3823
+ */
3824
+ createHmrPlugin() {
3825
+ return {
3826
+ name: "alepha-hmr",
3827
+ handleHotUpdate: async (ctx) => {
3828
+ if (ctx.file.includes("/.idea/")) return [];
3829
+ const firstModule = ctx.modules[0];
3830
+ const isBrowserOnly = firstModule && !firstModule._ssrModule;
3831
+ const isServerOnly = firstModule && !firstModule._clientModule;
3832
+ if (isBrowserOnly) return;
3833
+ const startTime = Date.now();
3834
+ try {
3835
+ this.changedFiles.add(ctx.file);
3836
+ await this.loadAlepha(false);
3837
+ await this.alepha?.start();
3838
+ this.log.debug(`Reloaded in ${Date.now() - startTime}ms`);
3839
+ if (isServerOnly) {
3840
+ this.sendBrowserReload();
3841
+ return [];
3842
+ }
3843
+ return;
3844
+ } catch (err) {
3845
+ this.hasError = true;
3846
+ this.log.error("Reload failed", err);
3847
+ this.log.warn("Waiting for file changes to retry...");
3848
+ this.alepha = null;
3849
+ return [];
3850
+ }
1569
3851
  }
1570
- await this.pm.ensureDependency(root, "vite", { exec: (cmd, opts) => this.utils.exec(cmd, opts) });
1571
- await devServer();
3852
+ };
3853
+ }
3854
+ /**
3855
+ * Send browser reload signal via custom event.
3856
+ * Browser listens for 'alepha:reload' and does window.location.reload()
3857
+ */
3858
+ sendBrowserReload() {
3859
+ this.server.ws.send({
3860
+ type: "custom",
3861
+ event: "alepha:reload",
3862
+ data: {}
3863
+ });
3864
+ }
3865
+ /**
3866
+ * Setup environment variables for dev mode.
3867
+ */
3868
+ async setupEnvironment() {
3869
+ const { loadEnv } = await importVite();
3870
+ const env = loadEnv(process.env.NODE_ENV || "development", this.options.root, "");
3871
+ for (const [key, value] of Object.entries(env)) process.env[key] ??= value;
3872
+ process.env.NODE_ENV ??= "development";
3873
+ process.env.VITE_ALEPHA_DEV = "true";
3874
+ process.env.SERVER_HOST ??= this.options.host?.toString() ?? "localhost";
3875
+ process.env.SERVER_PORT ??= String(this.options.port ?? (process.env.SERVER_PORT ? Number(process.env.SERVER_PORT) : 3e3));
3876
+ }
3877
+ /**
3878
+ * Load or reload the Alepha instance.
3879
+ */
3880
+ async loadAlepha(isInitialLoad = false) {
3881
+ if (this.alepha) {
3882
+ await this.alepha.stop().catch((err) => this.log.warn("Error stopping Alepha", err));
3883
+ this.alepha = null;
3884
+ }
3885
+ if (isInitialLoad || this.hasError) this.server.moduleGraph.invalidateAll();
3886
+ else this.invalidateModulesWithImporters();
3887
+ this.changedFiles.clear();
3888
+ const envSnapshot = { ...process.env };
3889
+ await this.setupEnvironment();
3890
+ await this.server.ssrLoadModule(this.options.entry.server);
3891
+ const alepha = globalThis.__alepha;
3892
+ if (!alepha) throw new AlephaError("Alepha instance not found after loading entry module");
3893
+ this.alepha = alepha;
3894
+ await this.setupAlepha();
3895
+ this.hasError = false;
3896
+ process.env = envSnapshot;
3897
+ return alepha;
3898
+ }
3899
+ hasReact() {
3900
+ try {
3901
+ this.alepha?.inject("ReactServerProvider");
3902
+ return true;
3903
+ } catch {
3904
+ return false;
1572
3905
  }
1573
- });
1574
- async isBunProject(root) {
1575
- if (this.alepha.isBun()) return true;
1576
- return this.fs.exists(this.fs.join(root, "bun.lock"));
1577
3906
  }
1578
- async isFullstackProject(root) {
1579
- return this.fs.exists(this.fs.join(root, "index.html"));
3907
+ /**
3908
+ * Setup Alepha instance with Vite middleware and template.
3909
+ */
3910
+ async setupAlepha() {
3911
+ if (!this.alepha || !this.hasReact()) return;
3912
+ const template = await this.server.transformIndexHtml("/", this.templateProvider.generateIndexHtml(this.options.entry));
3913
+ this.alepha.store.set("alepha.react.server.template", template);
3914
+ this.alepha.events.on("server:onRequest", {
3915
+ priority: "first",
3916
+ callback: async ({ request }) => {
3917
+ const node = request.raw.node;
3918
+ if (!node || this.isPageRequest(node.req)) return;
3919
+ if (await this.runViteMiddleware(node.req, node.res, request)) {
3920
+ request.reply.status = node.res.statusCode || 200;
3921
+ request.reply.body = null;
3922
+ }
3923
+ }
3924
+ });
3925
+ }
3926
+ /**
3927
+ * Check if request is for an HTML page (not an asset).
3928
+ */
3929
+ isPageRequest(req) {
3930
+ const url = req.url || "/";
3931
+ if (url === "/" || url === "/index.html") return true;
3932
+ if (url.startsWith("/@") || url.startsWith("/__vite")) return false;
3933
+ if (/\.\w+$/.test(url.split("?")[0])) return false;
3934
+ return true;
3935
+ }
3936
+ /**
3937
+ * Run Vite middleware and detect if it handled the request.
3938
+ */
3939
+ async runViteMiddleware(req, res, ctx) {
3940
+ if (res.headersSent || res.writableEnded) return false;
3941
+ return new Promise((resolve) => {
3942
+ let resolved = false;
3943
+ const done = (handled) => {
3944
+ if (resolved) return;
3945
+ resolved = true;
3946
+ if (handled) ctx.metadata.vite = true;
3947
+ resolve(handled);
3948
+ };
3949
+ const originalSetHeader = res.setHeader.bind(res);
3950
+ const originalWriteHead = res.writeHead?.bind(res);
3951
+ const originalWrite = res.write.bind(res);
3952
+ const originalEnd = res.end.bind(res);
3953
+ const guardedCall = (fn, ...args) => {
3954
+ if (resolved && !ctx.metadata.vite) return;
3955
+ return fn(...args);
3956
+ };
3957
+ res.setHeader = (...args) => guardedCall(originalSetHeader, ...args);
3958
+ if (originalWriteHead) res.writeHead = (...args) => guardedCall(originalWriteHead, ...args);
3959
+ res.write = (...args) => guardedCall(originalWrite, ...args);
3960
+ res.end = (...args) => guardedCall(originalEnd, ...args);
3961
+ res.on("finish", () => done(true));
3962
+ res.on("close", () => res.headersSent && done(true));
3963
+ this.server.middlewares(req, res, () => done(false));
3964
+ setImmediate(() => {
3965
+ if (res.headersSent || res.writableEnded) done(true);
3966
+ });
3967
+ });
3968
+ }
3969
+ /**
3970
+ * Invalidate modules and all their importers.
3971
+ */
3972
+ invalidateModulesWithImporters() {
3973
+ const invalidated = /* @__PURE__ */ new Set();
3974
+ const queue = [...this.changedFiles];
3975
+ while (queue.length > 0) {
3976
+ const file = queue.pop();
3977
+ if (invalidated.has(file)) continue;
3978
+ const mod = this.server.moduleGraph.getModuleById(file);
3979
+ if (!mod) continue;
3980
+ this.server.moduleGraph.invalidateModule(mod);
3981
+ invalidated.add(file);
3982
+ for (const importer of mod.importers) if (importer.id && !invalidated.has(importer.id)) queue.push(importer.id);
3983
+ }
1580
3984
  }
1581
3985
  };
1582
3986
 
1583
3987
  //#endregion
1584
- //#region ../../src/cli/commands/format.ts
1585
- var FormatCommand = class {
3988
+ //#region ../../src/cli/commands/dev.ts
3989
+ var DevCommand = class {
3990
+ log = $logger();
3991
+ fs = $inject(FileSystemProvider);
1586
3992
  utils = $inject(AlephaCliUtils);
1587
3993
  pm = $inject(PackageManagerUtils);
1588
3994
  scaffolder = $inject(ProjectScaffolder);
1589
- format = $command({
1590
- name: "format",
1591
- description: "Format the codebase using Biome",
3995
+ alepha = $inject(Alepha);
3996
+ viteDevServer = $inject(ViteDevServerProvider);
3997
+ boot = $inject(AppEntryProvider);
3998
+ /**
3999
+ * Will run the project in watch mode.
4000
+ */
4001
+ dev = $command({
4002
+ name: "dev",
4003
+ description: "Run the project in development mode",
1592
4004
  handler: async ({ root }) => {
1593
- await this.scaffolder.ensureConfig(root, { biomeJson: true });
1594
- await this.pm.ensureDependency(root, "@biomejs/biome", { exec: (cmd, opts) => this.utils.exec(cmd, opts) });
1595
- await this.utils.exec("biome format --fix");
4005
+ await this.scaffolder.ensureConfig(root, { tsconfigJson: true });
4006
+ const entry = await this.boot.getAppEntry(root);
4007
+ this.log.debug("Entry file found", { entry });
4008
+ await this.viteDevServer.init({
4009
+ root,
4010
+ entry
4011
+ });
4012
+ await this.viteDevServer.start();
1596
4013
  }
1597
4014
  });
1598
4015
  };
@@ -1627,7 +4044,7 @@ const DEFAULT_IGNORE = [
1627
4044
  * ```
1628
4045
  */
1629
4046
  const changelogOptions = $atom({
1630
- name: "alepha.changelog",
4047
+ name: "alepha.cli.changelog.options",
1631
4048
  schema: t.object({ ignore: t.optional(t.array(t.string())) }),
1632
4049
  default: { ignore: DEFAULT_IGNORE }
1633
4050
  });
@@ -1827,7 +4244,10 @@ var GenEnvCommand = class {
1827
4244
  description: "Output file path (e.g., .env)"
1828
4245
  })) }),
1829
4246
  handler: async ({ root, flags }) => {
1830
- const { alepha } = await this.utils.loadAlephaFromServerEntryFile(root);
4247
+ const alepha = await this.utils.loadAlephaFromServerEntryFile({
4248
+ root,
4249
+ mode: "development"
4250
+ });
1831
4251
  try {
1832
4252
  const { env } = alepha.dump();
1833
4253
  let dotEnvFile = "";
@@ -1835,7 +4255,7 @@ var GenEnvCommand = class {
1835
4255
  if (value.description) dotEnvFile += `# ${value.description.split("\n").join("\n# ")}\n`;
1836
4256
  if (value.required && !value.default) dotEnvFile += `# (required)\n`;
1837
4257
  if (value.enum) dotEnvFile += `# Possible values: ${value.enum.join(", ")}\n`;
1838
- dotEnvFile += `${key}=${value.default || ""}\n\n`;
4258
+ dotEnvFile += `#${key}=${value.default || ""}\n\n`;
1839
4259
  }
1840
4260
  if (flags.out) await this.fs.writeFile(this.fs.join(root, flags.out), dotEnvFile);
1841
4261
  else this.log.info(dotEnvFile);
@@ -1860,7 +4280,10 @@ var OpenApiCommand = class {
1860
4280
  description: "Output file path"
1861
4281
  })) }),
1862
4282
  handler: async ({ root, flags }) => {
1863
- const { alepha } = await this.utils.loadAlephaFromServerEntryFile(root);
4283
+ const alepha = await this.utils.loadAlephaFromServerEntryFile({
4284
+ root,
4285
+ mode: "development"
4286
+ });
1864
4287
  try {
1865
4288
  const openapiProvider = alepha.inject(ServerSwaggerProvider);
1866
4289
  await alepha.events.emit("configure", alepha);
@@ -1929,70 +4352,91 @@ var InitCommand = class {
1929
4352
  flags: t.object({
1930
4353
  agent: t.optional(t.boolean({
1931
4354
  aliases: ["a"],
1932
- description: "Add CLAUDE.md for Claude Code AI assistant"
4355
+ description: "Add AI agent instructions (CLAUDE.md if claude CLI installed, else AGENTS.md)"
1933
4356
  })),
1934
- yarn: t.optional(t.boolean({ description: "Use Yarn package manager" })),
1935
- pnpm: t.optional(t.boolean({ description: "Use pnpm package manager" })),
1936
- npm: t.optional(t.boolean({ description: "Use npm package manager" })),
1937
- bun: t.optional(t.boolean({ description: "Use Bun package manager" })),
1938
- web: t.optional(t.boolean({
4357
+ pm: t.optional(t.enum([
4358
+ "yarn",
4359
+ "npm",
4360
+ "pnpm",
4361
+ "bun"
4362
+ ], { description: "Package manager to use" })),
4363
+ api: t.optional(t.boolean({ description: "Include API module structure (src/api/)" })),
4364
+ react: t.optional(t.boolean({
1939
4365
  aliases: ["r"],
1940
- description: "Include Alepha React dependencies"
4366
+ description: "Include React dependencies and web module (src/web/)"
1941
4367
  })),
1942
- admin: t.optional(t.boolean({ description: "Include Alepha UI dependencies" })),
1943
- test: t.optional(t.boolean({ description: "Include Vitest and create test directory" }))
4368
+ ui: t.optional(t.boolean({ description: "Include @alepha/ui (components, auth portal, admin portal)" })),
4369
+ test: t.optional(t.boolean({ description: "Include Vitest and create test directory" })),
4370
+ force: t.optional(t.boolean({
4371
+ aliases: ["f"],
4372
+ description: "Override existing files"
4373
+ }))
1944
4374
  }),
1945
4375
  handler: async ({ run, flags, root, args }) => {
1946
- if (flags.admin) flags.web = true;
1947
4376
  if (args) {
1948
4377
  root = this.fs.join(root, args);
1949
4378
  await this.fs.mkdir(root);
1950
4379
  }
4380
+ if (flags.ui) flags.react = true;
4381
+ const workspace = await this.pm.getWorkspaceContext(root);
4382
+ let agentType = false;
4383
+ if (flags.agent) agentType = await this.utils.isInstalledAsync("claude") ? "claude" : "agents";
1951
4384
  const isExpo = await this.pm.hasExpo(root);
4385
+ const force = !!flags.force;
1952
4386
  await run({
1953
4387
  name: "ensuring configuration files",
1954
4388
  handler: async () => {
1955
4389
  await this.scaffolder.ensureConfig(root, {
1956
- tsconfigJson: true,
1957
- packageJson: flags,
1958
- biomeJson: true,
1959
- editorconfig: true,
1960
- indexHtml: !!flags.web && !isExpo,
1961
- claudeMd: flags.agent ? {
1962
- react: !!flags.web,
1963
- ui: !!flags.admin
4390
+ force,
4391
+ tsconfigJson: !workspace.config.tsconfigJson,
4392
+ packageJson: {
4393
+ ...flags,
4394
+ isPackage: workspace.isPackage
4395
+ },
4396
+ biomeJson: !workspace.config.biomeJson,
4397
+ editorconfig: !workspace.config.editorconfig,
4398
+ agentMd: agentType ? {
4399
+ type: agentType,
4400
+ react: !!flags.react,
4401
+ ui: !!flags.ui
1964
4402
  } : false
1965
4403
  });
1966
- if (!flags.web) await this.scaffolder.ensureApiProject(root);
4404
+ await this.scaffolder.ensureMainServerTs(root, {
4405
+ api: !!flags.api,
4406
+ react: !!flags.react && !isExpo,
4407
+ force
4408
+ });
4409
+ if (flags.api) await this.scaffolder.ensureApiProject(root, { force });
4410
+ if (flags.react && !isExpo) await this.scaffolder.ensureWebProject(root, {
4411
+ api: !!flags.api,
4412
+ ui: !!flags.ui,
4413
+ force
4414
+ });
1967
4415
  }
1968
4416
  });
1969
- const pmName = await this.pm.getPackageManager(root, flags);
1970
- if (pmName === "yarn") {
4417
+ const pmName = await this.pm.getPackageManager(workspace.workspaceRoot ?? root, flags.pm ?? workspace.packageManager ?? void 0);
4418
+ if (!workspace.isPackage) if (pmName === "yarn") {
1971
4419
  await this.pm.ensureYarn(root);
1972
4420
  await run("yarn set version stable", { root });
1973
4421
  } else if (pmName === "bun") await this.pm.ensureBun(root);
1974
4422
  else if (pmName === "pnpm") await this.pm.ensurePnpm(root);
1975
4423
  else await this.pm.ensureNpm(root);
4424
+ const installRoot = workspace.workspaceRoot ?? root;
1976
4425
  await run(`${pmName} install`, {
1977
4426
  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)
4427
+ root: installRoot
1987
4428
  });
1988
- if (flags.test) {
1989
- await this.scaffolder.ensureTestDir(root);
1990
- await run(`${pmName} ${pmName === "yarn" ? "add" : "install"} -D vitest`, { alias: "setup testing with Vitest" });
1991
- }
4429
+ if (flags.test) await this.scaffolder.ensureTestDir(root);
1992
4430
  await run(`${pmName} run lint`, {
1993
4431
  alias: "running linter",
1994
- root
4432
+ root: installRoot
1995
4433
  });
4434
+ if (!workspace.isPackage) {
4435
+ if (await this.scaffolder.ensureGitRepo(root, { force })) await run("git add .", {
4436
+ alias: "staging generated files",
4437
+ root
4438
+ });
4439
+ }
1996
4440
  }
1997
4441
  });
1998
4442
  };
@@ -2007,9 +4451,15 @@ var LintCommand = class {
2007
4451
  name: "lint",
2008
4452
  description: "Run linter across the codebase using Biome",
2009
4453
  handler: async ({ root }) => {
2010
- await this.scaffolder.ensureConfig(root, { biomeJson: true });
2011
- await this.pm.ensureDependency(root, "@biomejs/biome", { exec: (cmd, opts) => this.utils.exec(cmd, opts) });
2012
- await this.utils.exec("biome check --formatter-enabled=false --fix");
4454
+ await this.scaffolder.ensureConfig(root, {
4455
+ biomeJson: true,
4456
+ checkWorkspace: true
4457
+ });
4458
+ await this.pm.ensureDependency(root, "@biomejs/biome", {
4459
+ checkWorkspace: true,
4460
+ exec: (cmd, opts) => this.utils.exec(cmd, opts)
4461
+ });
4462
+ await this.utils.exec("biome check --fix");
2013
4463
  }
2014
4464
  });
2015
4465
  };
@@ -2074,6 +4524,7 @@ var TypecheckCommand = class {
2074
4524
  utils = $inject(AlephaCliUtils);
2075
4525
  pm = $inject(PackageManagerUtils);
2076
4526
  log = $logger();
4527
+ scaffolder = $inject(ProjectScaffolder);
2077
4528
  /**
2078
4529
  * Run TypeScript type checking across the codebase with no emit.
2079
4530
  */
@@ -2083,7 +4534,14 @@ var TypecheckCommand = class {
2083
4534
  description: "Check TypeScript types across the codebase",
2084
4535
  handler: async ({ root }) => {
2085
4536
  this.log.info("Starting TypeScript type checking...");
2086
- await this.pm.ensureDependency(root, "typescript", { exec: (cmd, opts) => this.utils.exec(cmd, opts) });
4537
+ await this.scaffolder.ensureConfig(root, {
4538
+ tsconfigJson: true,
4539
+ checkWorkspace: true
4540
+ });
4541
+ await this.pm.ensureDependency(root, "typescript", {
4542
+ checkWorkspace: true,
4543
+ exec: (cmd, opts) => this.utils.exec(cmd, opts)
4544
+ });
2087
4545
  await this.utils.exec("tsc --noEmit");
2088
4546
  this.log.info("TypeScript type checking completed successfully.");
2089
4547
  }
@@ -2127,15 +4585,6 @@ var VerifyCommand = class {
2127
4585
  //#endregion
2128
4586
  //#region ../../src/cli/apps/AlephaCli.ts
2129
4587
  /**
2130
- * Register `tsx` when running in Node.js, ignore for Bun.
2131
- *
2132
- * It's required to have a full TypeScript support. (mostly .tsx files)
2133
- */
2134
- if (typeof Bun === "undefined") {
2135
- const { register } = await import("tsx/esm/api");
2136
- register();
2137
- }
2138
- /**
2139
4588
  * Allow to extend Alepha CLI via `alepha.config.ts` file located in the project root.
2140
4589
  */
2141
4590
  var AlephaCliExtension = class {
@@ -2162,7 +4611,6 @@ const AlephaCli = $module({
2162
4611
  DbCommand,
2163
4612
  DeployCommand,
2164
4613
  DevCommand,
2165
- FormatCommand,
2166
4614
  InitCommand,
2167
4615
  LintCommand,
2168
4616
  RootCommand,
@@ -2170,6 +4618,7 @@ const AlephaCli = $module({
2170
4618
  TypecheckCommand,
2171
4619
  VerifyCommand,
2172
4620
  GenCommand,
4621
+ AppEntryProvider,
2173
4622
  GitProvider
2174
4623
  ]
2175
4624
  });
@@ -2232,6 +4681,7 @@ var AlephaPackageBuilderCli = class {
2232
4681
  sourcemap: true,
2233
4682
  fixedExtension: false,
2234
4683
  platform: "node",
4684
+ inlineOnly: false,
2235
4685
  external,
2236
4686
  dts: { sourcemap: true }
2237
4687
  });
@@ -2241,6 +4691,7 @@ var AlephaPackageBuilderCli = class {
2241
4691
  platform: "neutral",
2242
4692
  sourcemap: true,
2243
4693
  dts: false,
4694
+ inlineOnly: false,
2244
4695
  external
2245
4696
  });
2246
4697
  if (item.browser) entries.push({
@@ -2249,6 +4700,7 @@ var AlephaPackageBuilderCli = class {
2249
4700
  platform: "browser",
2250
4701
  sourcemap: true,
2251
4702
  dts: false,
4703
+ inlineOnly: false,
2252
4704
  external
2253
4705
  });
2254
4706
  if (item.bun) entries.push({
@@ -2258,6 +4710,7 @@ var AlephaPackageBuilderCli = class {
2258
4710
  sourcemap: true,
2259
4711
  fixedExtension: false,
2260
4712
  dts: false,
4713
+ inlineOnly: false,
2261
4714
  external
2262
4715
  });
2263
4716
  const config = this.fs.join(tmpDir, `tsdown-${item.name.replace("/", "-")}.config.js`);
@@ -2281,6 +4734,7 @@ var AlephaPackageBuilderCli = class {
2281
4734
  }
2282
4735
  });
2283
4736
  };
4737
+ var AlephaPackageBuilderCli_default = AlephaPackageBuilderCli;
2284
4738
  async function getAllFiles(dir) {
2285
4739
  const files = [];
2286
4740
  async function scan(currentDir) {
@@ -2384,6 +4838,7 @@ const defineConfig = (runConfig) => {
2384
4838
  if (config.services) for (const it of config.services) alepha.with(it);
2385
4839
  if (config.env) for (const [key, value] of Object.entries(config.env)) process.env[key] = String(value);
2386
4840
  if (config.build) alepha.set(buildOptions, config.build);
4841
+ if (config.entry) alepha.set(appEntryOptions, config.entry);
2387
4842
  return { ...config.commands };
2388
4843
  };
2389
4844
  };
@@ -2393,5 +4848,5 @@ const defineConfig = (runConfig) => {
2393
4848
  const defineAlephaConfig = defineConfig;
2394
4849
 
2395
4850
  //#endregion
2396
- export { AlephaCli, AlephaCliUtils, AlephaPackageBuilderCli, BuildCommand, ChangelogCommand, CleanCommand, DEFAULT_IGNORE, DbCommand, DeployCommand, DevCommand, FormatCommand, GitMessageParser, GitProvider, InitCommand, LintCommand, OpenApiCommand, RootCommand, TestCommand, TypecheckCommand, VerifyCommand, analyzeModules, changelogOptions, defineAlephaConfig, defineConfig, version };
4851
+ 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 };
2397
4852
  //# sourceMappingURL=index.js.map