alepha 0.20.2 → 0.20.4

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 (304) hide show
  1. package/README.md +0 -1
  2. package/assets/swagger-ui/swagger-ui-bundle.js +1 -1
  3. package/assets/swagger-ui/swagger-ui.css +1 -1
  4. package/dist/api/audits/index.browser.js +49 -0
  5. package/dist/api/audits/index.browser.js.map +1 -1
  6. package/dist/api/audits/index.js +49 -0
  7. package/dist/api/audits/index.js.map +1 -1
  8. package/dist/api/files/index.js.map +1 -1
  9. package/dist/api/jobs/index.d.ts +2 -61
  10. package/dist/api/jobs/index.d.ts.map +1 -1
  11. package/dist/api/jobs/index.js.map +1 -1
  12. package/dist/api/keys/index.d.ts +4 -4
  13. package/dist/api/keys/index.js.map +1 -1
  14. package/dist/api/notifications/index.d.ts +1 -10
  15. package/dist/api/notifications/index.d.ts.map +1 -1
  16. package/dist/api/parameters/index.browser.js +37 -0
  17. package/dist/api/parameters/index.browser.js.map +1 -1
  18. package/dist/api/parameters/index.d.ts +12 -68
  19. package/dist/api/parameters/index.d.ts.map +1 -1
  20. package/dist/api/parameters/index.js +57 -4
  21. package/dist/api/parameters/index.js.map +1 -1
  22. package/dist/api/payments/index.js.map +1 -1
  23. package/dist/api/users/index.browser.js +6 -0
  24. package/dist/api/users/index.browser.js.map +1 -1
  25. package/dist/api/users/index.d.ts +148 -227
  26. package/dist/api/users/index.d.ts.map +1 -1
  27. package/dist/api/users/index.js +60 -14
  28. package/dist/api/users/index.js.map +1 -1
  29. package/dist/api/verifications/index.d.ts.map +1 -1
  30. package/dist/api/verifications/index.js +2 -1
  31. package/dist/api/verifications/index.js.map +1 -1
  32. package/dist/bucket/index.d.ts +77 -107
  33. package/dist/bucket/index.d.ts.map +1 -1
  34. package/dist/bucket/index.js +153 -5
  35. package/dist/bucket/index.js.map +1 -1
  36. package/dist/bucket/index.workerd.js +12 -2
  37. package/dist/bucket/index.workerd.js.map +1 -1
  38. package/dist/cache/core/index.d.ts +26 -0
  39. package/dist/cache/core/index.d.ts.map +1 -1
  40. package/dist/cache/core/index.js +11 -1
  41. package/dist/cache/core/index.js.map +1 -1
  42. package/dist/cache/core/index.workerd.js +11 -1
  43. package/dist/cache/core/index.workerd.js.map +1 -1
  44. package/dist/captcha/index.js.map +1 -1
  45. package/dist/cli/config/index.d.ts +7 -5
  46. package/dist/cli/config/index.d.ts.map +1 -1
  47. package/dist/cli/config/index.js +2 -3
  48. package/dist/cli/config/index.js.map +1 -1
  49. package/dist/cli/core/index.d.ts +637 -11660
  50. package/dist/cli/core/index.d.ts.map +1 -1
  51. package/dist/cli/core/index.js +707 -532
  52. package/dist/cli/core/index.js.map +1 -1
  53. package/dist/cli/devtools/index.d.ts +4 -8
  54. package/dist/cli/devtools/index.d.ts.map +1 -1
  55. package/dist/cli/devtools/index.js +20 -16
  56. package/dist/cli/devtools/index.js.map +1 -1
  57. package/dist/cli/platform/index.d.ts +51 -77
  58. package/dist/cli/platform/index.d.ts.map +1 -1
  59. package/dist/cli/platform/index.js +65 -15
  60. package/dist/cli/platform/index.js.map +1 -1
  61. package/dist/cli/vendor/index.d.ts +10 -13
  62. package/dist/cli/vendor/index.d.ts.map +1 -1
  63. package/dist/cli/vendor/index.js +30 -12
  64. package/dist/cli/vendor/index.js.map +1 -1
  65. package/dist/command/index.js +1 -1
  66. package/dist/command/index.js.map +1 -1
  67. package/dist/core/index.browser.js +27 -3
  68. package/dist/core/index.browser.js.map +1 -1
  69. package/dist/core/index.d.ts +8 -11
  70. package/dist/core/index.d.ts.map +1 -1
  71. package/dist/core/index.js +27 -3
  72. package/dist/core/index.js.map +1 -1
  73. package/dist/core/index.native.js +27 -3
  74. package/dist/core/index.native.js.map +1 -1
  75. package/dist/core/index.workerd.js +27 -3
  76. package/dist/core/index.workerd.js.map +1 -1
  77. package/dist/crypto/index.js.map +1 -1
  78. package/dist/datetime/index.d.ts +69 -10
  79. package/dist/datetime/index.d.ts.map +1 -1
  80. package/dist/datetime/index.js +135 -13
  81. package/dist/datetime/index.js.map +1 -1
  82. package/dist/email/core/index.js.map +1 -1
  83. package/dist/email/smtp/index.js +130 -16
  84. package/dist/email/smtp/index.js.map +1 -1
  85. package/dist/fake/index.js.map +1 -1
  86. package/dist/lock/core/index.d.ts +30 -2
  87. package/dist/lock/core/index.d.ts.map +1 -1
  88. package/dist/lock/core/index.js +35 -12
  89. package/dist/lock/core/index.js.map +1 -1
  90. package/dist/lock/redis/index.js.map +1 -1
  91. package/dist/logger/index.js +32 -1
  92. package/dist/logger/index.js.map +1 -1
  93. package/dist/mcp/index.d.ts +238 -31
  94. package/dist/mcp/index.d.ts.map +1 -1
  95. package/dist/mcp/index.js +198 -67
  96. package/dist/mcp/index.js.map +1 -1
  97. package/dist/orm/core/index.browser.js +2 -362
  98. package/dist/orm/core/index.browser.js.map +1 -1
  99. package/dist/orm/core/index.bun.js +18 -409
  100. package/dist/orm/core/index.bun.js.map +1 -1
  101. package/dist/orm/core/index.d.ts +41 -194
  102. package/dist/orm/core/index.d.ts.map +1 -1
  103. package/dist/orm/core/index.js +27 -422
  104. package/dist/orm/core/index.js.map +1 -1
  105. package/dist/orm/postgres/index.bun.js +17 -20
  106. package/dist/orm/postgres/index.bun.js.map +1 -1
  107. package/dist/orm/postgres/index.d.ts +1 -5
  108. package/dist/orm/postgres/index.d.ts.map +1 -1
  109. package/dist/orm/postgres/index.js +17 -20
  110. package/dist/orm/postgres/index.js.map +1 -1
  111. package/dist/react/core/index.d.ts +102 -1
  112. package/dist/react/core/index.d.ts.map +1 -1
  113. package/dist/react/core/index.js +65 -1
  114. package/dist/react/core/index.js.map +1 -1
  115. package/dist/react/form/index.d.ts +6 -0
  116. package/dist/react/form/index.d.ts.map +1 -1
  117. package/dist/react/form/index.js +7 -7
  118. package/dist/react/form/index.js.map +1 -1
  119. package/dist/react/i18n/index.d.ts +7 -1
  120. package/dist/react/i18n/index.d.ts.map +1 -1
  121. package/dist/react/i18n/index.js +6 -0
  122. package/dist/react/i18n/index.js.map +1 -1
  123. package/dist/react/intro/index.js +22 -17
  124. package/dist/react/intro/index.js.map +1 -1
  125. package/dist/react/router/index.browser.js +98 -4
  126. package/dist/react/router/index.browser.js.map +1 -1
  127. package/dist/react/router/index.d.ts +58 -5
  128. package/dist/react/router/index.d.ts.map +1 -1
  129. package/dist/react/router/index.js +122 -6
  130. package/dist/react/router/index.js.map +1 -1
  131. package/dist/react/testing/{chunk-DBEY4PJZ.js → chunk-6Ep1yQYe.js} +1 -1
  132. package/dist/react/testing/index.js +1 -1
  133. package/dist/react/testing/index.js.map +1 -1
  134. package/dist/react/ui/index.d.ts +195 -1
  135. package/dist/react/ui/index.d.ts.map +1 -1
  136. package/dist/react/ui/index.js +64 -1
  137. package/dist/react/ui/index.js.map +1 -1
  138. package/dist/react/websocket/index.js.map +1 -1
  139. package/dist/redis/index.js.map +1 -1
  140. package/dist/scheduler/index.d.ts +1 -2
  141. package/dist/scheduler/index.d.ts.map +1 -1
  142. package/dist/scheduler/index.js +1 -1
  143. package/dist/scheduler/index.js.map +1 -1
  144. package/dist/scheduler/index.workerd.js +1 -1
  145. package/dist/scheduler/index.workerd.js.map +1 -1
  146. package/dist/security/index.browser.js.map +1 -1
  147. package/dist/security/index.d.ts.map +1 -1
  148. package/dist/security/index.js +2 -2
  149. package/dist/security/index.js.map +1 -1
  150. package/dist/server/auth/index.d.ts.map +1 -1
  151. package/dist/server/auth/index.js +24 -10
  152. package/dist/server/auth/index.js.map +1 -1
  153. package/dist/server/cookies/index.js.map +1 -1
  154. package/dist/server/core/index.browser.js +10 -3
  155. package/dist/server/core/index.browser.js.map +1 -1
  156. package/dist/server/core/index.d.ts +1 -4
  157. package/dist/server/core/index.d.ts.map +1 -1
  158. package/dist/server/core/index.js +47 -9
  159. package/dist/server/core/index.js.map +1 -1
  160. package/dist/server/links/index.browser.js.map +1 -1
  161. package/dist/server/links/index.js.map +1 -1
  162. package/dist/server/metrics/index.js +19 -1
  163. package/dist/server/metrics/index.js.map +1 -1
  164. package/dist/server/rate-limit/index.js.map +1 -1
  165. package/dist/server/static/index.js.map +1 -1
  166. package/dist/server/swagger/index.d.ts.map +1 -1
  167. package/dist/server/swagger/index.js +4 -5
  168. package/dist/server/swagger/index.js.map +1 -1
  169. package/dist/sms/index.js.map +1 -1
  170. package/dist/system/index.browser.js.map +1 -1
  171. package/dist/system/index.js.map +1 -1
  172. package/dist/system/index.workerd.js.map +1 -1
  173. package/dist/topic/core/index.js.map +1 -1
  174. package/dist/websocket/index.browser.js +32 -5
  175. package/dist/websocket/index.browser.js.map +1 -1
  176. package/dist/websocket/index.d.ts +3 -1
  177. package/dist/websocket/index.d.ts.map +1 -1
  178. package/dist/websocket/index.js +42 -6
  179. package/dist/websocket/index.js.map +1 -1
  180. package/package.json +685 -274
  181. package/src/api/files/__tests__/FileController.spec.ts +1 -1
  182. package/src/api/jobs/__tests__/$job.spec.ts +5 -1
  183. package/src/api/parameters/services/ParameterProvider.ts +21 -4
  184. package/src/api/users/__tests__/SessionService.spec.ts +99 -0
  185. package/src/api/users/__tests__/UserJobs.spec.ts +67 -0
  186. package/src/api/users/atoms/realmAuthSettingsAtom.ts +15 -0
  187. package/src/api/users/entities/sessions.ts +6 -0
  188. package/src/api/users/jobs/UserJobs.ts +44 -17
  189. package/src/api/users/providers/RealmProvider.ts +4 -0
  190. package/src/api/users/schemas/userQuerySchema.ts +0 -1
  191. package/src/api/users/services/SessionService.ts +27 -0
  192. package/src/api/users/services/UserService.ts +1 -5
  193. package/src/api/verifications/__tests__/CodeVerification.spec.ts +14 -0
  194. package/src/api/verifications/__tests__/LinkVerification.spec.ts +14 -0
  195. package/src/api/verifications/services/VerificationService.ts +1 -0
  196. package/src/bucket/__tests__/NodeS3BucketProvider.spec.ts +74 -0
  197. package/src/bucket/index.ts +19 -2
  198. package/src/bucket/primitives/$bucket.ts +9 -1
  199. package/src/bucket/providers/CloudflareR2Provider.ts +2 -137
  200. package/src/bucket/providers/NodeS3BucketProvider.ts +218 -0
  201. package/src/cache/core/index.ts +29 -0
  202. package/src/cache/core/primitives/$cache.ts +14 -1
  203. package/src/cli/config/defineConfig.ts +13 -15
  204. package/src/cli/core/__tests__/init.spec.ts +214 -7
  205. package/src/cli/core/commands/init.ts +12 -0
  206. package/src/cli/core/services/PackageManagerUtils.ts +23 -6
  207. package/src/cli/core/services/ProjectScaffolder.ts +315 -33
  208. package/src/cli/core/tasks/BuildCloudflareTask.ts +5 -0
  209. package/src/cli/core/tasks/BuildDockerTask.ts +9 -10
  210. package/src/cli/core/tasks/BuildServerTask.ts +8 -0
  211. package/src/cli/core/templates/agentMd.ts +2 -10
  212. package/src/cli/core/templates/apiIndexTs.ts +23 -1
  213. package/src/cli/core/templates/componentsJsonTs.ts +39 -0
  214. package/src/cli/core/templates/mainCss.ts +1 -0
  215. package/src/cli/core/templates/saasAdminLayoutTsx.ts +77 -0
  216. package/src/cli/core/templates/saasAdminPagesTsx.ts +26 -0
  217. package/src/cli/core/templates/saasAuthLayoutTsx.ts +20 -0
  218. package/src/cli/core/templates/saasAuthPagesTsx.ts +62 -0
  219. package/src/cli/core/templates/saasRealmProviderTs.ts +46 -0
  220. package/src/cli/core/templates/webAppRouterTs.ts +104 -1
  221. package/src/cli/core/templates/webIndexTs.ts +23 -1
  222. package/src/cli/devtools/index.ts +12 -26
  223. package/src/cli/platform/__tests__/SecretsCommand.spec.ts +2 -0
  224. package/src/cli/platform/index.ts +15 -24
  225. package/src/cli/vendor/atoms/vendorOptions.ts +1 -1
  226. package/src/cli/vendor/index.ts +14 -23
  227. package/src/command/providers/CliProvider.ts +1 -1
  228. package/src/core/Alepha.ts +11 -1
  229. package/src/core/helpers/ref.ts +18 -0
  230. package/src/core/index.shared.ts +1 -0
  231. package/src/core/interfaces/Service.ts +3 -1
  232. package/src/core/providers/SchemaValidator.ts +9 -1
  233. package/src/core/providers/TypeProvider.ts +2 -3
  234. package/src/datetime/REFACTORING.md +118 -0
  235. package/src/datetime/providers/DateTimeProvider.ts +203 -24
  236. package/src/lock/core/index.ts +31 -0
  237. package/src/lock/core/primitives/$lock.ts +14 -1
  238. package/src/logger/services/Logger.ts +1 -1
  239. package/src/mcp/__tests__/$resource.spec.ts +1 -1
  240. package/src/mcp/__tests__/$tool.spec.ts +1 -1
  241. package/src/mcp/__tests__/McpServerProvider.spec.ts +1 -1
  242. package/src/mcp/__tests__/jsonrpc.spec.ts +1 -1
  243. package/src/mcp/helpers/jsonrpc.ts +26 -1
  244. package/src/mcp/index.ts +10 -5
  245. package/src/mcp/interfaces/McpTypes.ts +83 -6
  246. package/src/mcp/primitives/$prompt.ts +18 -1
  247. package/src/mcp/primitives/$resource.ts +18 -1
  248. package/src/mcp/primitives/$tool.ts +83 -7
  249. package/src/mcp/providers/McpServerProvider.ts +74 -16
  250. package/src/mcp/transports/StreamableHttpMcpTransport.ts +226 -0
  251. package/src/orm/REFACTORING.md +330 -0
  252. package/src/orm/__tests__/$repository-tests.ts +1 -0
  253. package/src/orm/__tests__/orm-next-tests.ts +2 -67
  254. package/src/orm/__tests__/orm-next.spec.ts +0 -21
  255. package/src/orm/core/index.shared.ts +0 -2
  256. package/src/orm/core/index.ts +1 -2
  257. package/src/orm/core/primitives/$repository.ts +3 -6
  258. package/src/orm/core/primitives/$transactional.ts +11 -0
  259. package/src/orm/core/providers/drivers/DatabaseProvider.ts +0 -5
  260. package/src/orm/core/providers/drivers/NodeSqliteProvider.ts +11 -13
  261. package/src/orm/core/schemas/updateSchema.ts +1 -1
  262. package/src/orm/core/services/ModelBuilder.ts +1 -13
  263. package/src/orm/core/services/PgRelationManager.ts +4 -2
  264. package/src/orm/core/services/Repository.ts +1 -42
  265. package/src/orm/core/services/SqliteModelBuilder.ts +2 -33
  266. package/src/orm/postgres/services/PostgresModelBuilder.ts +10 -45
  267. package/src/react/core/__tests__/useQuery.browser.spec.tsx +86 -0
  268. package/src/react/core/hooks/useQuery.ts +153 -0
  269. package/src/react/core/index.ts +1 -0
  270. package/src/react/form/services/FormModel.ts +15 -6
  271. package/src/react/form/services/parseField.ts +8 -0
  272. package/src/react/i18n/providers/I18nProvider.ts +8 -2
  273. package/src/react/intro/components/GettingStartedAuthSlide.tsx +11 -4
  274. package/src/react/router/__tests__/$page.spec.tsx +0 -16
  275. package/src/react/router/__tests__/ReactBrowserProvider.browser.spec.ts +213 -2
  276. package/src/react/router/__tests__/ssr.spec.tsx +339 -0
  277. package/src/react/router/primitives/$page.ts +28 -4
  278. package/src/react/router/providers/ReactBrowserProvider.ts +73 -0
  279. package/src/react/router/providers/ReactBrowserRouterProvider.ts +1 -1
  280. package/src/react/router/providers/ReactPageProvider.ts +27 -9
  281. package/src/react/router/providers/ReactPreloadProvider.ts +1 -1
  282. package/src/react/router/providers/ReactServerProvider.ts +1 -0
  283. package/src/react/ui/atoms/uiThemeListAtom.ts +36 -0
  284. package/src/react/ui/index.ts +6 -0
  285. package/src/react/ui/services/SchemaControl.ts +209 -0
  286. package/src/scheduler/providers/CronProvider.ts +1 -1
  287. package/src/security/primitives/$basicAuth.ts +1 -1
  288. package/src/security/primitives/$issuer.ts +6 -3
  289. package/src/server/auth/providers/ServerAuthProvider.ts +5 -1
  290. package/src/server/core/__tests__/ServerRouterProvider-serializationError.spec.ts +75 -0
  291. package/src/server/core/__tests__/ServerRouterProvider-validationError.spec.ts +306 -0
  292. package/src/server/core/errors/ValidationError.ts +13 -1
  293. package/src/server/core/interfaces/ServerRequest.ts +1 -0
  294. package/src/server/core/primitives/$action.ts +16 -5
  295. package/src/server/core/providers/ServerProvider.ts +1 -1
  296. package/src/server/core/providers/ServerRouterProvider.ts +28 -6
  297. package/src/server/core/services/HttpClient.ts +1 -1
  298. package/src/server/swagger/providers/ServerSwaggerProvider.ts +6 -8
  299. package/src/websocket/providers/NodeWebSocketServerProvider.ts +10 -4
  300. package/src/websocket/services/WebSocketClient.ts +11 -5
  301. package/src/mcp/transports/SseMcpTransport.ts +0 -182
  302. package/src/orm/core/__tests__/parseQueryString.spec.ts +0 -196
  303. package/src/orm/core/helpers/parseQueryString.ts +0 -502
  304. package/src/orm/core/primitives/$view.ts +0 -88
@@ -7,6 +7,7 @@ import { createHash } from "node:crypto";
7
7
  import { existsSync, mkdirSync, writeFileSync } from "node:fs";
8
8
  import { readFile, readdir, stat } from "node:fs/promises";
9
9
  import { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
10
+ import pkg from "alepha/package.json" with { type: "json" };
10
11
  import { analyzer } from "vite-bundle-analyzer";
11
12
  import { KV_DEFAULT_BINDING } from "alepha/cache";
12
13
  import { QUEUE_DEFAULT_BINDING } from "alepha/queue";
@@ -37,7 +38,20 @@ const buildOptions = $atom({
37
38
  name: "alepha.cli.build.options",
38
39
  description: "Build configuration options",
39
40
  schema: t.object({
41
+ /**
42
+ * Generate build stats report.
43
+ *
44
+ * - `true` - Generate a static HTML report
45
+ * - `"json"` - Generate a JSON report
46
+ */
40
47
  stats: t.optional(t.union([t.boolean(), t.enum(["json"])])),
48
+ /**
49
+ * Deployment target for the build output.
50
+ *
51
+ * - `docker` - Generate Dockerfile for containerized deployment
52
+ * - `vercel` - Generate Vercel deployment configuration (forces node runtime)
53
+ * - `cloudflare` - Generate Cloudflare Workers configuration (forces workerd runtime)
54
+ */
41
55
  target: t.optional(t.enum([
42
56
  "bare",
43
57
  "docker",
@@ -45,15 +59,45 @@ const buildOptions = $atom({
45
59
  "cloudflare",
46
60
  "static"
47
61
  ])),
62
+ /**
63
+ * JavaScript runtime for the build output.
64
+ *
65
+ * - `node` - Node.js runtime (default)
66
+ * - `bun` - Bun runtime (uses bun export conditions)
67
+ * - `workerd` - Cloudflare Workers runtime (auto-set with cloudflare target)
68
+ *
69
+ * Note: Some targets force a specific runtime:
70
+ * - `cloudflare` always uses `workerd`
71
+ * - `vercel` always uses `node`
72
+ */
48
73
  runtime: t.optional(t.enum([
49
74
  "node",
50
75
  "bun",
51
76
  "workerd"
52
77
  ])),
78
+ /**
79
+ * Output directory configuration.
80
+ */
53
81
  output: t.optional(t.object({
82
+ /**
83
+ * Root dist directory.
84
+ *
85
+ * @default "dist"
86
+ */
54
87
  dist: t.optional(t.string({ default: "dist" })),
88
+ /**
89
+ * Public/client subdirectory.
90
+ *
91
+ * @default "public"
92
+ */
55
93
  public: t.optional(t.string({ default: "public" }))
56
94
  })),
95
+ /**
96
+ * Vercel-specific deployment configuration.
97
+ *
98
+ * Note: Set `target: "vercel"` to enable Vercel deployment.
99
+ * This object is only for additional configuration.
100
+ */
57
101
  vercel: t.optional(t.object({
58
102
  projectName: t.optional(t.string()),
59
103
  orgId: t.optional(t.string()),
@@ -63,31 +107,143 @@ const buildOptions = $atom({
63
107
  schedule: t.string()
64
108
  }))) }))
65
109
  })),
110
+ /**
111
+ * Cloudflare-specific deployment configuration.
112
+ *
113
+ * Note: Set `target: "cloudflare"` to enable Cloudflare deployment.
114
+ * This object is only for additional configuration.
115
+ */
66
116
  cloudflare: t.optional(t.object({ config: t.optional(t.json()) })),
117
+ /**
118
+ * Docker-specific deployment configuration.
119
+ *
120
+ * Note: Set `target: "docker"` to enable Docker deployment.
121
+ * This object is only for additional configuration.
122
+ */
67
123
  docker: t.optional(t.object({
124
+ /**
125
+ * Base image for the Dockerfile (FROM instruction).
126
+ *
127
+ * @default "node:24-alpine" for node runtime
128
+ * @default "oven/bun:alpine" for bun runtime
129
+ */
68
130
  from: t.optional(t.string()),
131
+ /**
132
+ * Command to run in the Docker container.
133
+ *
134
+ * @default "node" for node runtime
135
+ * @default "bun" for bun runtime
136
+ */
69
137
  command: t.optional(t.string()),
138
+ /**
139
+ * Docker build options (used when --image flag is passed).
140
+ */
70
141
  image: t.optional(t.object({
142
+ /**
143
+ * Default image tag (name without version).
144
+ *
145
+ * Used when --image is provided without a full override:
146
+ * - `--image` → `tag:latest`
147
+ * - `--image=1.3.4` → `tag:1.3.4`
148
+ * - `--image=other/img:v1` → `other/img:v1` (full override)
149
+ *
150
+ * @example "myproject/myapp"
151
+ * @example "ghcr.io/myorg/myapp"
152
+ */
71
153
  tag: t.string(),
154
+ /**
155
+ * Additional arguments to pass to `docker build`.
156
+ *
157
+ * @example '--platform linux/amd64 --no-cache'
158
+ */
72
159
  args: t.optional(t.string()),
160
+ /**
161
+ * Auto-add OCI standard labels (revision, created, version).
162
+ *
163
+ * Adds:
164
+ * - org.opencontainers.image.revision (git commit SHA)
165
+ * - org.opencontainers.image.created (build timestamp)
166
+ * - org.opencontainers.image.version (from image tag)
167
+ */
73
168
  oci: t.optional(t.boolean())
74
169
  }))
75
170
  })),
76
- static: t.optional(t.object({ domain: t.optional(t.string()) })),
171
+ /**
172
+ * Static site deployment configuration.
173
+ *
174
+ * Note: Set `target: "static"` to enable static site generation.
175
+ */
176
+ static: t.optional(t.object({
177
+ /**
178
+ * Surge domain for deployment.
179
+ *
180
+ * If set, a CNAME file is written to dist/public/.
181
+ * If not set, a domain is auto-generated from package.json name.
182
+ *
183
+ * @example "my-app.surge.sh"
184
+ * @example "my-custom-domain.com"
185
+ */
186
+ domain: t.optional(t.string()) })),
187
+ /**
188
+ * PWA (Progressive Web App) configuration.
189
+ *
190
+ * Generates a web app manifest and enables installability.
191
+ * Requires a client-side bundle (React).
192
+ */
77
193
  pwa: t.optional(t.object({
194
+ /**
195
+ * Full application name displayed on the splash screen
196
+ * and in the OS app switcher.
197
+ */
78
198
  name: t.string(),
199
+ /**
200
+ * Short name displayed on the home screen icon.
201
+ * Falls back to `name` if omitted.
202
+ */
79
203
  shortName: t.optional(t.string()),
204
+ /**
205
+ * Theme color used for the browser toolbar and OS chrome.
206
+ *
207
+ * @default "#ffffff"
208
+ */
80
209
  themeColor: t.optional(t.string()),
210
+ /**
211
+ * Background color for the splash screen.
212
+ *
213
+ * @default "#ffffff"
214
+ */
81
215
  backgroundColor: t.optional(t.string()),
216
+ /**
217
+ * Display mode for the installed PWA.
218
+ *
219
+ * - `standalone` - Looks like a native app (default)
220
+ * - `fullscreen` - Uses entire screen (games, immersive)
221
+ * - `minimal-ui` - Like standalone with minimal browser UI
222
+ * - `browser` - Standard browser tab
223
+ *
224
+ * @default "standalone"
225
+ */
82
226
  display: t.optional(t.enum([
83
227
  "standalone",
84
228
  "fullscreen",
85
229
  "minimal-ui",
86
230
  "browser"
87
231
  ])),
232
+ /**
233
+ * Enable offline support via service worker.
234
+ *
235
+ * TODO: Not yet implemented.
236
+ */
88
237
  offline: t.optional(t.boolean())
89
238
  })),
90
- sitemap: t.optional(t.object({ hostname: t.string() }))
239
+ /**
240
+ * Sitemap generation configuration.
241
+ */
242
+ sitemap: t.optional(t.object({
243
+ /**
244
+ * Base URL for sitemap entries.
245
+ */
246
+ hostname: t.string() }))
91
247
  }),
92
248
  default: {}
93
249
  });
@@ -102,7 +258,11 @@ const buildOptions = $atom({
102
258
  const devOptions = $atom({
103
259
  name: "alepha.cli.dev.options",
104
260
  description: "Dev configuration options",
105
- schema: t.object({ noViteReactPlugin: t.optional(t.boolean({ default: false })) }),
261
+ schema: t.object({
262
+ /**
263
+ * Disable Vite React plugin.
264
+ */
265
+ noViteReactPlugin: t.optional(t.boolean({ default: false })) }),
106
266
  default: {}
107
267
  });
108
268
  //#endregion
@@ -597,500 +757,9 @@ var AlephaCliUtils = class {
597
757
  }
598
758
  };
599
759
  //#endregion
600
- //#region ../../package.json
601
- var version$1 = "0.20.2";
602
- //#endregion
603
760
  //#region ../../src/cli/core/alephaPackageJson.ts
604
- const alephaPackageJson = {
605
- name: "alepha",
606
- description: "Easy-to-use modern TypeScript framework for building many kind of applications.",
607
- author: "Feunard",
608
- version: version$1,
609
- type: "module",
610
- engines: { "node": ">=22.0.0" },
611
- license: "MIT",
612
- bin: "./src/bin/index.ts",
613
- main: "./src/core/index.ts",
614
- types: "./src/core/index.ts",
615
- files: [
616
- "dist",
617
- "src",
618
- "assets",
619
- "tsconfig.base.json",
620
- "AGENTS.md",
621
- "CLAUDE.md"
622
- ],
623
- dependencies: {
624
- "@redis/client": "^5.12.1",
625
- "@vitejs/plugin-react": "^6.0.1",
626
- "dayjs": "^1.11.20",
627
- "drizzle-orm": "^0.45.2",
628
- "postgres": "^3.4.9",
629
- "tsx": "^4.21.0",
630
- "typebox": "^1.1.23",
631
- "typescript": "^6.0.2",
632
- "vite-bundle-analyzer": "^1.3.7",
633
- "ws": "^8.20.0"
634
- },
635
- devDependencies: {
636
- "@biomejs/biome": "^2.4.12",
637
- "@electric-sql/pglite": "^0.4.4",
638
- "@faker-js/faker": "^10.4.0",
639
- "@testing-library/dom": "^10.4.1",
640
- "@testing-library/react": "^16.3.2",
641
- "@types/bun": "^1.3.12",
642
- "@types/node": "^25.6.0",
643
- "@types/nodemailer": "^8.0.0",
644
- "@types/react": "^19.2.14",
645
- "@types/react-dom": "^19.2.3",
646
- "@types/ws": "^8.18.1",
647
- "cron-schedule": "^6.0.0",
648
- "drizzle-kit": "^0.31.10",
649
- "jose": "^6.2.2",
650
- "jsdom": "^29.0.2",
651
- "nodemailer": "^8.0.5",
652
- "openid-client": "^6.8.3",
653
- "prom-client": "^15.1.3",
654
- "react": "^19.2.5",
655
- "react-dom": "^19.2.5",
656
- "swagger-ui-dist": "^5.32.4",
657
- "tsdown": "^0.21.8",
658
- "vite": "^8.0.8",
659
- "vitest": "^4.1.4"
660
- },
661
- peerDependencies: {
662
- "react": "^19",
663
- "react-dom": "^19"
664
- },
665
- peerDependenciesMeta: {
666
- "react": { "optional": true },
667
- "react-dom": { "optional": true }
668
- },
669
- scarfSettings: { "enabled": false },
670
- scripts: {
671
- "lint": "biome check --fix && yarn build:check",
672
- "typecheck": "tsc --noEmit",
673
- "test": "vitest run",
674
- "build": "node scripts/build.ts",
675
- "build:check": "yarn build --check",
676
- "copy": "node scripts/copy-swagger.ts"
677
- },
678
- homepage: "https://github.com/feunard/alepha",
679
- repository: {
680
- "type": "git",
681
- "url": "git+https://github.com/feunard/alepha.git"
682
- },
683
- keywords: [
684
- "alepha",
685
- "aleph",
686
- "framework",
687
- "serverless",
688
- "react",
689
- "api"
690
- ],
691
- exports: {
692
- "./api/audits": {
693
- "types": "./src/api/audits/index.ts",
694
- "react-native": "./src/api/audits/index.browser.ts",
695
- "browser": "./src/api/audits/index.browser.ts",
696
- "import": "./src/api/audits/index.ts",
697
- "default": "./src/api/audits/index.ts"
698
- },
699
- "./api/files": {
700
- "types": "./src/api/files/index.ts",
701
- "react-native": "./src/api/files/index.browser.ts",
702
- "browser": "./src/api/files/index.browser.ts",
703
- "import": "./src/api/files/index.ts",
704
- "default": "./src/api/files/index.ts"
705
- },
706
- "./api/jobs": {
707
- "types": "./src/api/jobs/index.ts",
708
- "react-native": "./src/api/jobs/index.browser.ts",
709
- "browser": "./src/api/jobs/index.browser.ts",
710
- "import": "./src/api/jobs/index.ts",
711
- "default": "./src/api/jobs/index.ts"
712
- },
713
- "./api/keys": {
714
- "types": "./src/api/keys/index.ts",
715
- "import": "./src/api/keys/index.ts",
716
- "default": "./src/api/keys/index.ts"
717
- },
718
- "./api/notifications": {
719
- "types": "./src/api/notifications/index.ts",
720
- "react-native": "./src/api/notifications/index.browser.ts",
721
- "browser": "./src/api/notifications/index.browser.ts",
722
- "import": "./src/api/notifications/index.ts",
723
- "default": "./src/api/notifications/index.ts"
724
- },
725
- "./api/organizations": {
726
- "types": "./src/api/organizations/index.ts",
727
- "react-native": "./src/api/organizations/index.browser.ts",
728
- "browser": "./src/api/organizations/index.browser.ts",
729
- "import": "./src/api/organizations/index.ts",
730
- "default": "./src/api/organizations/index.ts"
731
- },
732
- "./api/parameters": {
733
- "types": "./src/api/parameters/index.ts",
734
- "react-native": "./src/api/parameters/index.browser.ts",
735
- "browser": "./src/api/parameters/index.browser.ts",
736
- "import": "./src/api/parameters/index.ts",
737
- "default": "./src/api/parameters/index.ts"
738
- },
739
- "./api/payments": {
740
- "types": "./src/api/payments/index.ts",
741
- "import": "./src/api/payments/index.ts",
742
- "default": "./src/api/payments/index.ts"
743
- },
744
- "./api/users": {
745
- "types": "./src/api/users/index.ts",
746
- "react-native": "./src/api/users/index.browser.ts",
747
- "browser": "./src/api/users/index.browser.ts",
748
- "import": "./src/api/users/index.ts",
749
- "default": "./src/api/users/index.ts"
750
- },
751
- "./api/verifications": {
752
- "types": "./src/api/verifications/index.ts",
753
- "react-native": "./src/api/verifications/index.browser.ts",
754
- "browser": "./src/api/verifications/index.browser.ts",
755
- "import": "./src/api/verifications/index.ts",
756
- "default": "./src/api/verifications/index.ts"
757
- },
758
- "./batch": {
759
- "types": "./src/batch/index.ts",
760
- "import": "./src/batch/index.ts",
761
- "default": "./src/batch/index.ts"
762
- },
763
- "./bin": {
764
- "types": "./src/bin/index.ts",
765
- "import": "./src/bin/index.ts",
766
- "default": "./src/bin/index.ts"
767
- },
768
- "./bucket": {
769
- "types": "./src/bucket/index.ts",
770
- "workerd": "./src/bucket/index.workerd.ts",
771
- "import": "./src/bucket/index.ts",
772
- "default": "./src/bucket/index.ts"
773
- },
774
- "./cache": {
775
- "types": "./src/cache/core/index.ts",
776
- "workerd": "./src/cache/core/index.workerd.ts",
777
- "import": "./src/cache/core/index.ts",
778
- "default": "./src/cache/core/index.ts"
779
- },
780
- "./cache/redis": {
781
- "types": "./src/cache/redis/index.ts",
782
- "import": "./src/cache/redis/index.ts",
783
- "default": "./src/cache/redis/index.ts"
784
- },
785
- "./captcha": {
786
- "types": "./src/captcha/index.ts",
787
- "import": "./src/captcha/index.ts",
788
- "default": "./src/captcha/index.ts"
789
- },
790
- "./cli/config": {
791
- "types": "./src/cli/config/index.ts",
792
- "import": "./src/cli/config/index.ts",
793
- "default": "./src/cli/config/index.ts"
794
- },
795
- "./cli": {
796
- "types": "./src/cli/core/index.ts",
797
- "import": "./src/cli/core/index.ts",
798
- "default": "./src/cli/core/index.ts"
799
- },
800
- "./cli/devtools": {
801
- "types": "./src/cli/devtools/index.ts",
802
- "import": "./src/cli/devtools/index.ts",
803
- "default": "./src/cli/devtools/index.ts"
804
- },
805
- "./cli/platform": {
806
- "types": "./src/cli/platform/index.ts",
807
- "import": "./src/cli/platform/index.ts",
808
- "default": "./src/cli/platform/index.ts"
809
- },
810
- "./cli/vendor": {
811
- "types": "./src/cli/vendor/index.ts",
812
- "import": "./src/cli/vendor/index.ts",
813
- "default": "./src/cli/vendor/index.ts"
814
- },
815
- "./command": {
816
- "types": "./src/command/index.ts",
817
- "import": "./src/command/index.ts",
818
- "default": "./src/command/index.ts"
819
- },
820
- ".": {
821
- "types": "./src/core/index.ts",
822
- "react-native": "./src/core/index.native.ts",
823
- "workerd": "./src/core/index.workerd.ts",
824
- "browser": "./src/core/index.browser.ts",
825
- "import": "./src/core/index.ts",
826
- "default": "./src/core/index.ts"
827
- },
828
- "./crypto": {
829
- "types": "./src/crypto/index.ts",
830
- "react-native": "./src/crypto/index.browser.ts",
831
- "browser": "./src/crypto/index.browser.ts",
832
- "import": "./src/crypto/index.ts",
833
- "default": "./src/crypto/index.ts"
834
- },
835
- "./datetime": {
836
- "types": "./src/datetime/index.ts",
837
- "import": "./src/datetime/index.ts",
838
- "default": "./src/datetime/index.ts"
839
- },
840
- "./email/brevo": {
841
- "types": "./src/email/brevo/index.ts",
842
- "import": "./src/email/brevo/index.ts",
843
- "default": "./src/email/brevo/index.ts"
844
- },
845
- "./email": {
846
- "types": "./src/email/core/index.ts",
847
- "workerd": "./src/email/core/index.workerd.ts",
848
- "import": "./src/email/core/index.ts",
849
- "default": "./src/email/core/index.ts"
850
- },
851
- "./email/smtp": {
852
- "types": "./src/email/smtp/index.ts",
853
- "import": "./src/email/smtp/index.ts",
854
- "default": "./src/email/smtp/index.ts"
855
- },
856
- "./fake": {
857
- "types": "./src/fake/index.ts",
858
- "import": "./src/fake/index.ts",
859
- "default": "./src/fake/index.ts"
860
- },
861
- "./lock": {
862
- "types": "./src/lock/core/index.ts",
863
- "import": "./src/lock/core/index.ts",
864
- "default": "./src/lock/core/index.ts"
865
- },
866
- "./lock/redis": {
867
- "types": "./src/lock/redis/index.ts",
868
- "import": "./src/lock/redis/index.ts",
869
- "default": "./src/lock/redis/index.ts"
870
- },
871
- "./logger": {
872
- "types": "./src/logger/index.ts",
873
- "import": "./src/logger/index.ts",
874
- "default": "./src/logger/index.ts"
875
- },
876
- "./mcp": {
877
- "types": "./src/mcp/index.ts",
878
- "import": "./src/mcp/index.ts",
879
- "default": "./src/mcp/index.ts"
880
- },
881
- "./orm": {
882
- "types": "./src/orm/core/index.ts",
883
- "react-native": "./src/orm/core/index.browser.ts",
884
- "browser": "./src/orm/core/index.browser.ts",
885
- "bun": "./src/orm/core/index.bun.ts",
886
- "import": "./src/orm/core/index.ts",
887
- "default": "./src/orm/core/index.ts"
888
- },
889
- "./orm/postgres": {
890
- "types": "./src/orm/postgres/index.ts",
891
- "bun": "./src/orm/postgres/index.bun.ts",
892
- "import": "./src/orm/postgres/index.ts",
893
- "default": "./src/orm/postgres/index.ts"
894
- },
895
- "./queue": {
896
- "types": "./src/queue/core/index.ts",
897
- "workerd": "./src/queue/core/index.workerd.ts",
898
- "import": "./src/queue/core/index.ts",
899
- "default": "./src/queue/core/index.ts"
900
- },
901
- "./queue/redis": {
902
- "types": "./src/queue/redis/index.ts",
903
- "import": "./src/queue/redis/index.ts",
904
- "default": "./src/queue/redis/index.ts"
905
- },
906
- "./react/auth": {
907
- "types": "./src/react/auth/index.ts",
908
- "react-native": "./src/react/auth/index.browser.ts",
909
- "browser": "./src/react/auth/index.browser.ts",
910
- "import": "./src/react/auth/index.ts",
911
- "default": "./src/react/auth/index.ts"
912
- },
913
- "./react": {
914
- "types": "./src/react/core/index.ts",
915
- "import": "./src/react/core/index.ts",
916
- "default": "./src/react/core/index.ts"
917
- },
918
- "./react/form": {
919
- "types": "./src/react/form/index.ts",
920
- "import": "./src/react/form/index.ts",
921
- "default": "./src/react/form/index.ts"
922
- },
923
- "./react/head": {
924
- "types": "./src/react/head/index.ts",
925
- "react-native": "./src/react/head/index.browser.ts",
926
- "browser": "./src/react/head/index.browser.ts",
927
- "import": "./src/react/head/index.ts",
928
- "default": "./src/react/head/index.ts"
929
- },
930
- "./react/i18n": {
931
- "types": "./src/react/i18n/index.ts",
932
- "import": "./src/react/i18n/index.ts",
933
- "default": "./src/react/i18n/index.ts"
934
- },
935
- "./react/intro": {
936
- "types": "./src/react/intro/index.ts",
937
- "import": "./src/react/intro/index.ts",
938
- "default": "./src/react/intro/index.ts"
939
- },
940
- "./react/router": {
941
- "types": "./src/react/router/index.ts",
942
- "react-native": "./src/react/router/index.browser.ts",
943
- "browser": "./src/react/router/index.browser.ts",
944
- "import": "./src/react/router/index.ts",
945
- "default": "./src/react/router/index.ts"
946
- },
947
- "./react/testing": {
948
- "types": "./src/react/testing/index.ts",
949
- "import": "./src/react/testing/index.ts",
950
- "default": "./src/react/testing/index.ts"
951
- },
952
- "./react/ui": {
953
- "types": "./src/react/ui/index.ts",
954
- "import": "./src/react/ui/index.ts",
955
- "default": "./src/react/ui/index.ts"
956
- },
957
- "./react/websocket": {
958
- "types": "./src/react/websocket/index.ts",
959
- "import": "./src/react/websocket/index.ts",
960
- "default": "./src/react/websocket/index.ts"
961
- },
962
- "./redis": {
963
- "types": "./src/redis/index.ts",
964
- "bun": "./src/redis/index.bun.ts",
965
- "import": "./src/redis/index.ts",
966
- "default": "./src/redis/index.ts"
967
- },
968
- "./retry": {
969
- "types": "./src/retry/index.ts",
970
- "import": "./src/retry/index.ts",
971
- "default": "./src/retry/index.ts"
972
- },
973
- "./router": {
974
- "types": "./src/router/index.ts",
975
- "import": "./src/router/index.ts",
976
- "default": "./src/router/index.ts"
977
- },
978
- "./scheduler": {
979
- "types": "./src/scheduler/index.ts",
980
- "workerd": "./src/scheduler/index.workerd.ts",
981
- "import": "./src/scheduler/index.ts",
982
- "default": "./src/scheduler/index.ts"
983
- },
984
- "./security": {
985
- "types": "./src/security/index.ts",
986
- "react-native": "./src/security/index.browser.ts",
987
- "browser": "./src/security/index.browser.ts",
988
- "import": "./src/security/index.ts",
989
- "default": "./src/security/index.ts"
990
- },
991
- "./server/auth": {
992
- "types": "./src/server/auth/index.ts",
993
- "react-native": "./src/server/auth/index.browser.ts",
994
- "browser": "./src/server/auth/index.browser.ts",
995
- "import": "./src/server/auth/index.ts",
996
- "default": "./src/server/auth/index.ts"
997
- },
998
- "./server/cookies": {
999
- "types": "./src/server/cookies/index.ts",
1000
- "react-native": "./src/server/cookies/index.browser.ts",
1001
- "browser": "./src/server/cookies/index.browser.ts",
1002
- "import": "./src/server/cookies/index.ts",
1003
- "default": "./src/server/cookies/index.ts"
1004
- },
1005
- "./server": {
1006
- "types": "./src/server/core/index.ts",
1007
- "react-native": "./src/server/core/index.browser.ts",
1008
- "browser": "./src/server/core/index.browser.ts",
1009
- "import": "./src/server/core/index.ts",
1010
- "default": "./src/server/core/index.ts"
1011
- },
1012
- "./server/cors": {
1013
- "types": "./src/server/cors/index.ts",
1014
- "import": "./src/server/cors/index.ts",
1015
- "default": "./src/server/cors/index.ts"
1016
- },
1017
- "./server/etag": {
1018
- "types": "./src/server/etag/index.ts",
1019
- "import": "./src/server/etag/index.ts",
1020
- "default": "./src/server/etag/index.ts"
1021
- },
1022
- "./server/health": {
1023
- "types": "./src/server/health/index.ts",
1024
- "import": "./src/server/health/index.ts",
1025
- "default": "./src/server/health/index.ts"
1026
- },
1027
- "./server/links": {
1028
- "types": "./src/server/links/index.ts",
1029
- "react-native": "./src/server/links/index.browser.ts",
1030
- "browser": "./src/server/links/index.browser.ts",
1031
- "import": "./src/server/links/index.ts",
1032
- "default": "./src/server/links/index.ts"
1033
- },
1034
- "./server/metrics": {
1035
- "types": "./src/server/metrics/index.ts",
1036
- "import": "./src/server/metrics/index.ts",
1037
- "default": "./src/server/metrics/index.ts"
1038
- },
1039
- "./server/proxy": {
1040
- "types": "./src/server/proxy/index.ts",
1041
- "import": "./src/server/proxy/index.ts",
1042
- "default": "./src/server/proxy/index.ts"
1043
- },
1044
- "./server/rate-limit": {
1045
- "types": "./src/server/rate-limit/index.ts",
1046
- "import": "./src/server/rate-limit/index.ts",
1047
- "default": "./src/server/rate-limit/index.ts"
1048
- },
1049
- "./server/static": {
1050
- "types": "./src/server/static/index.ts",
1051
- "import": "./src/server/static/index.ts",
1052
- "default": "./src/server/static/index.ts"
1053
- },
1054
- "./server/swagger": {
1055
- "types": "./src/server/swagger/index.ts",
1056
- "import": "./src/server/swagger/index.ts",
1057
- "default": "./src/server/swagger/index.ts"
1058
- },
1059
- "./sms": {
1060
- "types": "./src/sms/index.ts",
1061
- "import": "./src/sms/index.ts",
1062
- "default": "./src/sms/index.ts"
1063
- },
1064
- "./system": {
1065
- "types": "./src/system/index.ts",
1066
- "react-native": "./src/system/index.browser.ts",
1067
- "workerd": "./src/system/index.workerd.ts",
1068
- "browser": "./src/system/index.browser.ts",
1069
- "import": "./src/system/index.ts",
1070
- "default": "./src/system/index.ts"
1071
- },
1072
- "./topic": {
1073
- "types": "./src/topic/core/index.ts",
1074
- "import": "./src/topic/core/index.ts",
1075
- "default": "./src/topic/core/index.ts"
1076
- },
1077
- "./topic/redis": {
1078
- "types": "./src/topic/redis/index.ts",
1079
- "import": "./src/topic/redis/index.ts",
1080
- "default": "./src/topic/redis/index.ts"
1081
- },
1082
- "./websocket": {
1083
- "types": "./src/websocket/index.ts",
1084
- "react-native": "./src/websocket/index.browser.ts",
1085
- "browser": "./src/websocket/index.browser.ts",
1086
- "import": "./src/websocket/index.ts",
1087
- "default": "./src/websocket/index.ts"
1088
- },
1089
- "./tsconfig.base": "./tsconfig.base.json",
1090
- "./package.json": "./package.json"
1091
- }
1092
- };
1093
- const version = version$1;
761
+ const alephaPackageJson = pkg;
762
+ const version = pkg.version;
1094
763
  //#endregion
1095
764
  //#region ../../src/cli/core/services/PackageManagerUtils.ts
1096
765
  /**
@@ -1285,14 +954,14 @@ var PackageManagerUtils = class {
1285
954
  "yarn.lock"
1286
955
  ]);
1287
956
  await this.editPackageJson(root, (pkg) => {
1288
- delete pkg.packageManager;
957
+ pkg.packageManager = void 0;
1289
958
  return pkg;
1290
959
  });
1291
960
  }
1292
961
  async removePnpm(root) {
1293
962
  await this.removeFiles(root, ["pnpm-lock.yaml", "pnpm-workspace.yaml"]);
1294
963
  await this.editPackageJson(root, (pkg) => {
1295
- delete pkg.packageManager;
964
+ pkg.packageManager = void 0;
1296
965
  return pkg;
1297
966
  });
1298
967
  }
@@ -1342,7 +1011,7 @@ var PackageManagerUtils = class {
1342
1011
  const alephaDeps = alephaPackageJson.devDependencies;
1343
1012
  const dependencies = { alepha: `^${version}` };
1344
1013
  const devDependencies = { vite: alephaDeps.vite };
1345
- if (!modes.react) devDependencies["drizzle-kit"] = alephaDeps["drizzle-kit"];
1014
+ if (!modes.react || modes.saas) devDependencies["drizzle-kit"] = alephaDeps["drizzle-kit"];
1346
1015
  if (!modes.isPackage) {
1347
1016
  devDependencies["@biomejs/biome"] = alephaDeps["@biomejs/biome"];
1348
1017
  if (modes.test) devDependencies.vitest = alephaDeps.vitest;
@@ -1356,9 +1025,10 @@ var PackageManagerUtils = class {
1356
1025
  };
1357
1026
  if (modes.test) scripts.test = "vitest run";
1358
1027
  if (modes.tailwind) {
1359
- devDependencies.tailwindcss = "^4.2.0";
1360
- devDependencies["@tailwindcss/vite"] = "^4.2.0";
1028
+ devDependencies.tailwindcss = alephaDeps.tailwindcss;
1029
+ devDependencies["@tailwindcss/vite"] = alephaDeps["@tailwindcss/vite"];
1361
1030
  }
1031
+ if (modes.shadcn) devDependencies.shadcn = alephaDeps.shadcn;
1362
1032
  if (modes.react) {
1363
1033
  dependencies.react = alephaDeps.react;
1364
1034
  dependencies["react-dom"] = alephaDeps["react-dom"];
@@ -1380,8 +1050,8 @@ var PackageManagerUtils = class {
1380
1050
  };
1381
1051
  //#endregion
1382
1052
  //#region ../../src/cli/core/templates/agentMd.ts
1383
- const agentMd = (options) => {
1384
- return `${options.type === "claude" ? `# CLAUDE.md` : `# AGENTS.md`}
1053
+ const agentMd = () => {
1054
+ return `# AGENTS.md
1385
1055
 
1386
1056
  This is an **Alepha** project.
1387
1057
 
@@ -1472,7 +1142,19 @@ export type HelloResponse = Static<typeof helloResponseSchema>;
1472
1142
  //#endregion
1473
1143
  //#region ../../src/cli/core/templates/apiIndexTs.ts
1474
1144
  const apiIndexTs = (options = {}) => {
1475
- const { appName = "app" } = options;
1145
+ const { appName = "app", saas = false } = options;
1146
+ if (saas) return `
1147
+ import { $module } from "alepha";
1148
+ import { AlephaApiUsers } from "alepha/api/users";
1149
+ import { HelloController } from "./controllers/HelloController.ts";
1150
+ import { RealmProvider } from "./providers/RealmProvider.ts";
1151
+
1152
+ export const ApiModule = $module({
1153
+ name: "${appName}.api",
1154
+ services: [HelloController, RealmProvider],
1155
+ imports: [AlephaApiUsers],
1156
+ });
1157
+ `.trim();
1476
1158
  return `
1477
1159
  import { $module } from "alepha";
1478
1160
  import { HelloController } from "./controllers/HelloController.ts";
@@ -1519,6 +1201,46 @@ const biomeJson = () => `
1519
1201
  }
1520
1202
  `.trim();
1521
1203
  //#endregion
1204
+ //#region ../../src/cli/core/templates/componentsJsonTs.ts
1205
+ /**
1206
+ * `components.json` is the shadcn CLI's project config — it tells
1207
+ * `shadcn add` where to drop primitives, which tailwind tokens to use,
1208
+ * which icon library to wire up, and which custom registries to resolve.
1209
+ *
1210
+ * Aliases follow shadcn's defaults (`@/components`, `@/lib/utils`) so the
1211
+ * CLI honors them across `init` + `add` calls. Alepha app code lives at
1212
+ * `src/web/` (Home, AppRouter, …) and the shadcn primitives live at
1213
+ * `src/components/` — kept separate to make the registry components
1214
+ * trivially upgradable via `shadcn add --overwrite`.
1215
+ *
1216
+ * The `registries` block pre-wires the public Alepha registry — consumers
1217
+ * can immediately run e.g. `shadcn add @alepha/auth-login`.
1218
+ */
1219
+ const componentsJsonTs = () => `{
1220
+ "$schema": "https://ui.shadcn.com/schema.json",
1221
+ "style": "new-york",
1222
+ "rsc": false,
1223
+ "tsx": true,
1224
+ "tailwind": {
1225
+ "config": "",
1226
+ "css": "src/main.css",
1227
+ "baseColor": "neutral",
1228
+ "cssVariables": true
1229
+ },
1230
+ "aliases": {
1231
+ "components": "@/components",
1232
+ "utils": "@/lib/utils",
1233
+ "ui": "@/components/ui",
1234
+ "lib": "@/lib",
1235
+ "hooks": "@/hooks"
1236
+ },
1237
+ "iconLibrary": "lucide",
1238
+ "registries": {
1239
+ "@alepha": "https://alepha.dev/r/{name}.json"
1240
+ }
1241
+ }
1242
+ `;
1243
+ //#endregion
1522
1244
  //#region ../../src/cli/core/templates/dummySpecTs.ts
1523
1245
  const dummySpecTs = () => `
1524
1246
  import { test, expect } from "vitest";
@@ -1660,6 +1382,7 @@ const mainCss = (opts = {}) => {
1660
1382
  *
1661
1383
  * Options:
1662
1384
  * - Tailwind CSS: Use \`alepha init --tailwind\` to add Tailwind CSS
1385
+ * - shadcn/ui: Use \`alepha init --shadcn\` to add shadcn/ui setup
1663
1386
  * - Raw CSS: Write your own styles below
1664
1387
  */
1665
1388
 
@@ -1690,6 +1413,225 @@ run(alepha);
1690
1413
  `.trim();
1691
1414
  };
1692
1415
  //#endregion
1416
+ //#region ../../src/cli/core/templates/saasAdminLayoutTsx.ts
1417
+ /**
1418
+ * SaaS admin layout — full AppShell on /admin with a sidebar, breadcrumbs,
1419
+ * a Sonner toaster, and a confirm provider. The page list grows with
1420
+ * whatever `admin-*` registry components the user adds.
1421
+ *
1422
+ * All UI primitives come from `src/components/*` where `shadcn add` drops
1423
+ * them; alepha app code lives in `src/web/*` and references them via the
1424
+ * `@/components/*` alias.
1425
+ */
1426
+ const saasAdminLayoutTsx = () => `import { AppShell } from "@/components/app-shell";
1427
+ import { Toaster } from "@/components/ui/sonner";
1428
+ import { TooltipProvider } from "@/components/ui/tooltip";
1429
+ import { DialogProvider } from "@/components/use-dialog";
1430
+ import { NestedView, useRouterState } from "alepha/react/router";
1431
+ import { ShieldCheck, Users } from "lucide-react";
1432
+
1433
+ const NAV = [
1434
+ {
1435
+ label: "Identity",
1436
+ items: [
1437
+ { href: "/admin/users", label: "Users", icon: Users },
1438
+ { href: "/admin/sessions", label: "Sessions", icon: ShieldCheck },
1439
+ ],
1440
+ },
1441
+ ] as const;
1442
+
1443
+ const findCrumbs = (pathname: string): { label: string; href?: string }[] => {
1444
+ for (const group of NAV) {
1445
+ const match = group.items.find((it) => it.href === pathname);
1446
+ if (match) return [{ label: group.label }, { label: match.label }];
1447
+ }
1448
+ return [];
1449
+ };
1450
+
1451
+ const AdminLayout = () => {
1452
+ const state = useRouterState();
1453
+ const crumbs = findCrumbs(state.url.pathname);
1454
+
1455
+ return (
1456
+ <TooltipProvider>
1457
+ <DialogProvider>
1458
+ <AppShell
1459
+ brand={
1460
+ <a
1461
+ href="/admin"
1462
+ className="flex items-center gap-2 px-2 py-2 font-semibold group-data-[collapsible=icon]:justify-center group-data-[collapsible=icon]:px-0"
1463
+ >
1464
+ <span className="bg-primary text-primary-foreground flex size-7 shrink-0 items-center justify-center rounded">
1465
+ α
1466
+ </span>
1467
+ <span className="truncate group-data-[collapsible=icon]:hidden">
1468
+ Admin
1469
+ </span>
1470
+ </a>
1471
+ }
1472
+ nav={NAV.map((group) => ({
1473
+ label: group.label,
1474
+ items: group.items.map((it) => ({
1475
+ href: it.href,
1476
+ label: it.label,
1477
+ icon: it.icon,
1478
+ active: it.href === state.url.pathname,
1479
+ })),
1480
+ }))}
1481
+ breadcrumbs={crumbs.length ? crumbs : undefined}
1482
+ >
1483
+ <NestedView />
1484
+ </AppShell>
1485
+ <Toaster />
1486
+ </DialogProvider>
1487
+ </TooltipProvider>
1488
+ );
1489
+ };
1490
+
1491
+ export default AdminLayout;
1492
+ `;
1493
+ //#endregion
1494
+ //#region ../../src/cli/core/templates/saasAdminPagesTsx.ts
1495
+ /**
1496
+ * Admin pages — each is a thin wrapper around the matching `admin-*`
1497
+ * registry component (placed at `src/components/admin/*`). The starter
1498
+ * ships with Users + Sessions; add more by `shadcn add @alepha/admin-…`
1499
+ * and a matching `$page(...)` in AppRouter.
1500
+ */
1501
+ const saasAdminUsersTsx = () => `import { AdminUsers } from "@/components/admin/admin-users";
1502
+
1503
+ const AdminUsersPage = () => {
1504
+ return <AdminUsers />;
1505
+ };
1506
+
1507
+ export default AdminUsersPage;
1508
+ `;
1509
+ const saasAdminSessionsTsx = () => `import { AdminSessions } from "@/components/admin/admin-sessions";
1510
+
1511
+ const AdminSessionsPage = () => {
1512
+ return <AdminSessions />;
1513
+ };
1514
+
1515
+ export default AdminSessionsPage;
1516
+ `;
1517
+ //#endregion
1518
+ //#region ../../src/cli/core/templates/saasAuthLayoutTsx.ts
1519
+ /**
1520
+ * SaaS auth layout — wraps every /auth/* page with a centered card.
1521
+ * Routes (login, register, reset-password, verify-email) are mounted as
1522
+ * children so they share this shell.
1523
+ */
1524
+ const saasAuthLayoutTsx = () => `import { NestedView } from "alepha/react/router";
1525
+
1526
+ const AuthLayout = () => {
1527
+ return (
1528
+ <div className="bg-background flex min-h-screen items-center justify-center p-4">
1529
+ <div className="w-full max-w-md">
1530
+ <NestedView />
1531
+ </div>
1532
+ </div>
1533
+ );
1534
+ };
1535
+
1536
+ export default AuthLayout;
1537
+ `;
1538
+ //#endregion
1539
+ //#region ../../src/cli/core/templates/saasAuthPagesTsx.ts
1540
+ /**
1541
+ * Per-page wrapper around the registry components. The registry component
1542
+ * receives the realm config from the page loader; the page itself stays a
1543
+ * thin shell so apps can layer their branding around it.
1544
+ *
1545
+ * Registry components land at `src/components/auth/*` (shadcn defaults).
1546
+ */
1547
+ const saasAuthLoginTsx = () => `import { AuthLogin } from "@/components/auth/auth-login";
1548
+ import type { RealmConfig } from "alepha/api/users";
1549
+
1550
+ export interface AuthLoginPageProps {
1551
+ realmConfig: RealmConfig;
1552
+ }
1553
+
1554
+ const AuthLoginPage = (props: AuthLoginPageProps) => {
1555
+ return <AuthLogin realmConfig={props.realmConfig} />;
1556
+ };
1557
+
1558
+ export default AuthLoginPage;
1559
+ `;
1560
+ const saasAuthRegisterTsx = () => `import { AuthRegister } from "@/components/auth/auth-register";
1561
+ import type { RealmConfig } from "alepha/api/users";
1562
+
1563
+ export interface AuthRegisterPageProps {
1564
+ realmConfig: RealmConfig;
1565
+ }
1566
+
1567
+ const AuthRegisterPage = (props: AuthRegisterPageProps) => {
1568
+ return <AuthRegister realmConfig={props.realmConfig} />;
1569
+ };
1570
+
1571
+ export default AuthRegisterPage;
1572
+ `;
1573
+ const saasAuthResetPasswordTsx = () => `import { AuthResetPassword } from "@/components/auth/auth-reset-password";
1574
+ import type { RealmConfig } from "alepha/api/users";
1575
+
1576
+ export interface AuthResetPasswordPageProps {
1577
+ realmConfig: RealmConfig;
1578
+ }
1579
+
1580
+ const AuthResetPasswordPage = (props: AuthResetPasswordPageProps) => {
1581
+ return <AuthResetPassword realmConfig={props.realmConfig} />;
1582
+ };
1583
+
1584
+ export default AuthResetPasswordPage;
1585
+ `;
1586
+ const saasAuthVerifyEmailTsx = () => `import { AuthVerifyEmail } from "@/components/auth/auth-verify-email";
1587
+
1588
+ const AuthVerifyEmailPage = () => {
1589
+ return <AuthVerifyEmail />;
1590
+ };
1591
+
1592
+ export default AuthVerifyEmailPage;
1593
+ `;
1594
+ //#endregion
1595
+ //#region ../../src/cli/core/templates/saasRealmProviderTs.ts
1596
+ /**
1597
+ * Realm provider scaffolded by `alepha init --saas`.
1598
+ *
1599
+ * Minimal hello-world setup: credentials login with email, one admin seeded
1600
+ * with the developer's git email at scaffold time, and an `admin:ui`
1601
+ * permission used by the AppRouter to gate `/admin/*`. The default `admin`
1602
+ * role grants `*` (so it inherits `admin:ui`); the default `user` role
1603
+ * excludes `admin:*` (so non-admins get a 403 before the shell renders).
1604
+ *
1605
+ * Add `$env`, more permissions, or stricter settings as the project grows.
1606
+ */
1607
+ const saasRealmProviderTs = (options = {}) => {
1608
+ const adminEmail = options.adminEmail ?? "admin@example.com";
1609
+ return `import { $realm } from "alepha/api/users";
1610
+ import { $permission } from "alepha/security";
1611
+
1612
+ export class RealmProvider {
1613
+ /**
1614
+ * Permission required to open the admin UI. Wired into AppRouter.adminLayout
1615
+ * via \`$secure({ permissions: ["admin:ui"] })\`.
1616
+ */
1617
+ adminUi = $permission({
1618
+ group: "admin",
1619
+ name: "ui",
1620
+ description: "Access to the admin UI shell",
1621
+ });
1622
+
1623
+ realm = $realm({
1624
+ settings: {
1625
+ adminEmails: [${JSON.stringify(adminEmail)}],
1626
+ },
1627
+ identities: {
1628
+ credentials: true,
1629
+ },
1630
+ });
1631
+ }
1632
+ `;
1633
+ };
1634
+ //#endregion
1693
1635
  //#region ../../src/cli/core/templates/tsconfigJson.ts
1694
1636
  const tsconfigJson = () => `
1695
1637
  {
@@ -1733,6 +1675,10 @@ export default defineConfig({
1733
1675
  //#endregion
1734
1676
  //#region ../../src/cli/core/templates/webAppRouterTs.ts
1735
1677
  const webAppRouterTs = (options) => {
1678
+ if (options.saas) return saasAppRouterTs();
1679
+ return basicAppRouterTs(options);
1680
+ };
1681
+ const basicAppRouterTs = (options) => {
1736
1682
  const imports = ["import { $page } from \"alepha/react/router\";"];
1737
1683
  const classMembers = [];
1738
1684
  if (options.api) {
@@ -1754,6 +1700,95 @@ export class AppRouter {
1754
1700
  ${classMembers.join("\n\n")}
1755
1701
  }`;
1756
1702
  };
1703
+ /**
1704
+ * SaaS router wires three trees onto the app:
1705
+ * / → Home
1706
+ * /auth/* → AuthLayout + login / register / reset / verify
1707
+ * /admin/* → AdminLayout + users / sessions / api-keys / parameters / audits
1708
+ *
1709
+ * Each auth page resolves the realm config from its loader, so the registry
1710
+ * components render with everything they need on first paint.
1711
+ */
1712
+ const saasAppRouterTs = () => `import type { RealmController } from "alepha/api/users";
1713
+ import { $page, NotFound } from "alepha/react/router";
1714
+ import { $secure } from "alepha/security";
1715
+ import { $client } from "alepha/server/links";
1716
+ import type { HelloController } from "../api/controllers/HelloController.ts";
1717
+
1718
+ export class AppRouter {
1719
+ protected readonly api = $client<HelloController>();
1720
+ protected readonly realmApi = $client<RealmController>();
1721
+
1722
+ home = $page({
1723
+ path: "/",
1724
+ lazy: () => import("./components/Home.tsx"),
1725
+ loader: () => this.api.hello(),
1726
+ });
1727
+
1728
+ // ── /auth — login, register, reset, verify ─────────────────────────────
1729
+ authLayout = $page({
1730
+ path: "/auth",
1731
+ lazy: () => import("./components/auth/AuthLayout.tsx"),
1732
+ });
1733
+
1734
+ login = $page({
1735
+ parent: this.authLayout,
1736
+ path: "/login",
1737
+ name: "login",
1738
+ head: { title: "Sign in" },
1739
+ lazy: () => import("./components/auth/Login.tsx"),
1740
+ loader: async () => ({ realmConfig: await this.realmApi.getRealmConfig() }),
1741
+ });
1742
+
1743
+ register = $page({
1744
+ parent: this.authLayout,
1745
+ path: "/register",
1746
+ name: "register",
1747
+ head: { title: "Sign up" },
1748
+ lazy: () => import("./components/auth/Register.tsx"),
1749
+ loader: async () => ({ realmConfig: await this.realmApi.getRealmConfig() }),
1750
+ });
1751
+
1752
+ resetPassword = $page({
1753
+ parent: this.authLayout,
1754
+ path: "/reset-password",
1755
+ head: { title: "Reset password" },
1756
+ lazy: () => import("./components/auth/ResetPassword.tsx"),
1757
+ loader: async () => ({ realmConfig: await this.realmApi.getRealmConfig() }),
1758
+ });
1759
+
1760
+ verifyEmail = $page({
1761
+ parent: this.authLayout,
1762
+ path: "/verify-email",
1763
+ head: { title: "Verify email" },
1764
+ lazy: () => import("./components/auth/VerifyEmail.tsx"),
1765
+ });
1766
+
1767
+ // ── /admin — gated by 'admin:ui' permission, declared in RealmProvider.
1768
+ // Children inherit the gate via the parent chain.
1769
+ adminLayout = $page({
1770
+ path: "/admin",
1771
+ use: [$secure({ permissions: ["admin:ui"] })],
1772
+ lazy: () => import("./components/admin/AdminLayout.tsx"),
1773
+ });
1774
+
1775
+ adminUsers = $page({
1776
+ parent: this.adminLayout,
1777
+ path: "/users",
1778
+ head: { title: "Users" },
1779
+ lazy: () => import("./components/admin/Users.tsx"),
1780
+ });
1781
+
1782
+ adminSessions = $page({
1783
+ parent: this.adminLayout,
1784
+ path: "/sessions",
1785
+ head: { title: "Sessions" },
1786
+ lazy: () => import("./components/admin/Sessions.tsx"),
1787
+ });
1788
+
1789
+ notFound = $page({ path: "/*", component: NotFound });
1790
+ }
1791
+ `;
1757
1792
  //#endregion
1758
1793
  //#region ../../src/cli/core/templates/webHomeComponentTsx.ts
1759
1794
  const webHomeComponentTsx = (options = {}) => {
@@ -1782,7 +1817,19 @@ export default Home;
1782
1817
  //#endregion
1783
1818
  //#region ../../src/cli/core/templates/webIndexTs.ts
1784
1819
  const webIndexTs = (options = {}) => {
1785
- const { appName = "app" } = options;
1820
+ const { appName = "app", saas = false } = options;
1821
+ if (saas) return `
1822
+ import { $module } from "alepha";
1823
+ import { AlephaReactAuth } from "alepha/react/auth";
1824
+ import { AlephaReactI18n } from "alepha/react/i18n";
1825
+ import { AppRouter } from "./AppRouter.ts";
1826
+
1827
+ export const WebModule = $module({
1828
+ name: "${appName}.web",
1829
+ services: [AppRouter],
1830
+ imports: [AlephaReactAuth, AlephaReactI18n],
1831
+ });
1832
+ `.trim();
1786
1833
  return `
1787
1834
  import { $module } from "alepha";
1788
1835
  import { AppRouter } from "./AppRouter.ts";
@@ -1808,6 +1855,7 @@ var ProjectScaffolder = class {
1808
1855
  log = $logger();
1809
1856
  colors = $inject(ConsoleColorProvider);
1810
1857
  fs = $inject(FileSystemProvider);
1858
+ shell = $inject(ShellProvider);
1811
1859
  pm = $inject(PackageManagerUtils);
1812
1860
  utils = $inject(AlephaCliUtils);
1813
1861
  /**
@@ -1829,7 +1877,10 @@ var ProjectScaffolder = class {
1829
1877
  const force = opts.force ?? false;
1830
1878
  const checkWorkspace = opts.checkWorkspace ?? false;
1831
1879
  if (opts.packageJson) tasks.push(this.pm.ensurePackageJson(root, typeof opts.packageJson === "boolean" ? {} : opts.packageJson).then(() => {}));
1832
- if (opts.tsconfigJson) tasks.push(this.ensureTsConfig(root, { force }));
1880
+ if (opts.tsconfigJson) tasks.push(this.ensureTsConfig(root, {
1881
+ force,
1882
+ localOnly: opts.tsconfigJson === "local"
1883
+ }));
1833
1884
  if (opts.biomeJson) tasks.push(this.ensureBiomeConfig(root, {
1834
1885
  force,
1835
1886
  checkWorkspace
@@ -1838,14 +1889,12 @@ var ProjectScaffolder = class {
1838
1889
  force,
1839
1890
  checkWorkspace
1840
1891
  }));
1841
- if (opts.agentMd) tasks.push(this.ensureAgentMd(root, {
1842
- ...opts.agentMd,
1843
- force
1844
- }));
1892
+ if (opts.agentMd) tasks.push(this.ensureAgentMd(root, { force }));
1845
1893
  await Promise.all(tasks);
1846
1894
  }
1847
1895
  async ensureTsConfig(root, opts = {}) {
1848
- if (!opts.force && await this.existsInParents(root, "tsconfig.json")) return;
1896
+ const exists = opts.localOnly ? await this.fs.exists(this.fs.join(root, "tsconfig.json")) : await this.existsInParents(root, "tsconfig.json");
1897
+ if (!opts.force && exists) return;
1849
1898
  await this.fs.writeFile(this.fs.join(root, "tsconfig.json"), tsconfigJson());
1850
1899
  }
1851
1900
  async ensureBiomeConfig(root, opts = {}) {
@@ -1872,9 +1921,13 @@ var ProjectScaffolder = class {
1872
1921
  await this.ensureFile(root, ".gitignore", gitignore(), opts.force);
1873
1922
  return true;
1874
1923
  }
1875
- async ensureAgentMd(root, options) {
1876
- const filename = options.type === "claude" ? "CLAUDE.md" : "AGENTS.md";
1877
- await this.ensureFile(root, filename, agentMd(options), options.force);
1924
+ /**
1925
+ * Ensure AGENTS.md (cross-tool standard, canonical source) exists, with a
1926
+ * CLAUDE.md stub that imports it via Claude Code's `@` syntax. Single
1927
+ * source of truth, cross-platform, no symlink needed.
1928
+ */
1929
+ async ensureAgentMd(root, options = {}) {
1930
+ await Promise.all([this.ensureFile(root, "AGENTS.md", agentMd(), options.force), this.ensureFile(root, "CLAUDE.md", "@AGENTS.md\n", options.force)]);
1878
1931
  }
1879
1932
  /**
1880
1933
  * Ensure alepha.config.ts exists with documented options.
@@ -1904,9 +1957,32 @@ var ProjectScaffolder = class {
1904
1957
  const appName = this.getAppName(root);
1905
1958
  await this.fs.mkdir(this.fs.join(root, "src/api/controllers"), { recursive: true });
1906
1959
  await this.fs.mkdir(this.fs.join(root, "src/api/schemas"), { recursive: true });
1907
- await this.ensureFile(root, "src/api/index.ts", apiIndexTs({ appName }), opts.force);
1960
+ await this.ensureFile(root, "src/api/index.ts", apiIndexTs({
1961
+ appName,
1962
+ saas: opts.saas
1963
+ }), opts.force);
1908
1964
  await this.ensureFile(root, "src/api/controllers/HelloController.ts", apiHelloControllerTs({ appName }), opts.force);
1909
1965
  await this.ensureFile(root, "src/api/schemas/helloResponseSchema.ts", apiHelloResponseSchemaTs(), opts.force);
1966
+ if (opts.saas) {
1967
+ await this.fs.mkdir(this.fs.join(root, "src/api/providers"), { recursive: true });
1968
+ const adminEmail = await this.detectGitEmail();
1969
+ await this.ensureFile(root, "src/api/providers/RealmProvider.ts", saasRealmProviderTs({ adminEmail }), opts.force);
1970
+ }
1971
+ }
1972
+ /**
1973
+ * Best-effort lookup for the developer's git email (used as the seeded
1974
+ * `adminEmails` entry in the SaaS realm). Returns undefined if git isn't
1975
+ * available or if `user.email` isn't configured — the template falls back
1976
+ * to `admin@example.com` in that case.
1977
+ */
1978
+ async detectGitEmail() {
1979
+ try {
1980
+ const email = (await this.shell.run("git config --get user.email", { capture: true }) ?? "").trim();
1981
+ if (!email || !email.includes("@")) return void 0;
1982
+ return email;
1983
+ } catch {
1984
+ return;
1985
+ }
1910
1986
  }
1911
1987
  /**
1912
1988
  * Ensure web/React project structure exists.
@@ -1919,14 +1995,35 @@ var ProjectScaffolder = class {
1919
1995
  async ensureWebProject(root, opts = {}) {
1920
1996
  const appName = this.getAppName(root);
1921
1997
  await this.fs.mkdir(this.fs.join(root, "src/web/components"), { recursive: true });
1998
+ if (opts.saas) {
1999
+ await this.fs.mkdir(this.fs.join(root, "src/web/components/auth"), { recursive: true });
2000
+ await this.fs.mkdir(this.fs.join(root, "src/web/components/admin"), { recursive: true });
2001
+ }
1922
2002
  await this.fs.mkdir(this.fs.join(root, "public"), { recursive: true });
1923
2003
  await this.ensureFile(root, "public/favicon.svg", logoSvg, opts.force);
1924
2004
  await this.ensureFile(root, "src/main.css", mainCss({ tailwind: opts.tailwind }), opts.force);
1925
2005
  if (opts.tailwind) await this.ensureFile(root, "vite.config.ts", viteConfigTs(), opts.force);
1926
- await this.ensureFile(root, "src/web/index.ts", webIndexTs({ appName }), opts.force);
1927
- await this.ensureFile(root, "src/web/AppRouter.ts", webAppRouterTs({ api: opts.api }), opts.force);
2006
+ if (opts.shadcn) await this.ensureFile(root, "components.json", componentsJsonTs(), opts.force);
2007
+ await this.ensureFile(root, "src/web/index.ts", webIndexTs({
2008
+ appName,
2009
+ saas: opts.saas
2010
+ }), opts.force);
2011
+ await this.ensureFile(root, "src/web/AppRouter.ts", webAppRouterTs({
2012
+ api: opts.api,
2013
+ saas: opts.saas
2014
+ }), opts.force);
1928
2015
  await this.ensureFile(root, "src/web/components/Home.tsx", webHomeComponentTsx({ api: opts.api }), opts.force);
1929
2016
  await this.ensureFile(root, "src/main.browser.ts", mainBrowserTs(), opts.force);
2017
+ if (opts.saas) {
2018
+ await this.ensureFile(root, "src/web/components/auth/AuthLayout.tsx", saasAuthLayoutTsx(), opts.force);
2019
+ await this.ensureFile(root, "src/web/components/auth/Login.tsx", saasAuthLoginTsx(), opts.force);
2020
+ await this.ensureFile(root, "src/web/components/auth/Register.tsx", saasAuthRegisterTsx(), opts.force);
2021
+ await this.ensureFile(root, "src/web/components/auth/ResetPassword.tsx", saasAuthResetPasswordTsx(), opts.force);
2022
+ await this.ensureFile(root, "src/web/components/auth/VerifyEmail.tsx", saasAuthVerifyEmailTsx(), opts.force);
2023
+ await this.ensureFile(root, "src/web/components/admin/AdminLayout.tsx", saasAdminLayoutTsx(), opts.force);
2024
+ await this.ensureFile(root, "src/web/components/admin/Users.tsx", saasAdminUsersTsx(), opts.force);
2025
+ await this.ensureFile(root, "src/web/components/admin/Sessions.tsx", saasAdminSessionsTsx(), opts.force);
2026
+ }
1930
2027
  }
1931
2028
  /**
1932
2029
  * Ensure test directory exists with a dummy test file + a self-contained
@@ -1953,13 +2050,21 @@ var ProjectScaffolder = class {
1953
2050
  root = this.fs.join(root, args);
1954
2051
  await this.fs.mkdir(root, { force: true });
1955
2052
  }
2053
+ const shadcnPreset = typeof flags.saas === "string" && flags.saas || typeof flags.shadcn === "string" && flags.shadcn || "b0";
2054
+ const f = flags;
2055
+ f.shadcn = !!flags.shadcn;
2056
+ f.saas = !!flags.saas;
2057
+ if (f.saas) {
2058
+ f.shadcn = true;
2059
+ f.api = true;
2060
+ }
2061
+ if (f.shadcn) f.tailwind = true;
1956
2062
  if (flags.tailwind) flags.react = true;
1957
- if ((flags.api || flags.react || flags.tailwind) && !flags.force) {
2063
+ if ((flags.api || flags.react || flags.tailwind || flags.shadcn || flags.saas) && !flags.force) {
1958
2064
  if ((await this.fs.ls(root)).filter((f) => f !== "package.json").length > 0) throw new AlephaError(`Target directory is not empty (${root}). Use --force to overwrite existing files.`);
1959
2065
  }
1960
2066
  const workspace = await this.pm.getWorkspaceContext(root);
1961
- let agentType = false;
1962
- if (!workspace.isPackage) agentType = await this.utils.isInstalledAsync("claude") ? "claude" : "agents";
2067
+ const writeAgentMd = !workspace.isPackage;
1963
2068
  const isExpo = await this.pm.hasExpo(root);
1964
2069
  const force = !!flags.force;
1965
2070
  await run({
@@ -1968,13 +2073,13 @@ var ProjectScaffolder = class {
1968
2073
  await this.ensureConfig(root, {
1969
2074
  force,
1970
2075
  packageJson: {
1971
- ...flags,
2076
+ ...f,
1972
2077
  isPackage: workspace.isPackage
1973
2078
  },
1974
- tsconfigJson: !workspace.config.tsconfigJson,
2079
+ tsconfigJson: f.shadcn ? "local" : !workspace.config.tsconfigJson,
1975
2080
  biomeJson: true,
1976
2081
  editorconfig: !workspace.config.editorconfig,
1977
- agentMd: agentType ? { type: agentType } : false
2082
+ agentMd: writeAgentMd
1978
2083
  });
1979
2084
  await this.ensureAlephaConfig(root, { force });
1980
2085
  await this.ensureMainServerTs(root, {
@@ -1982,10 +2087,15 @@ var ProjectScaffolder = class {
1982
2087
  react: !!flags.react && !isExpo,
1983
2088
  force
1984
2089
  });
1985
- if (flags.api) await this.ensureApiProject(root, { force });
2090
+ if (flags.api) await this.ensureApiProject(root, {
2091
+ saas: !!flags.saas,
2092
+ force
2093
+ });
1986
2094
  if (flags.react && !isExpo) await this.ensureWebProject(root, {
1987
2095
  api: !!flags.api,
1988
2096
  tailwind: !!flags.tailwind,
2097
+ shadcn: !!flags.shadcn,
2098
+ saas: !!flags.saas,
1989
2099
  force
1990
2100
  });
1991
2101
  }
@@ -2003,10 +2113,26 @@ var ProjectScaffolder = class {
2003
2113
  root: installRoot
2004
2114
  });
2005
2115
  if (flags.test) await this.ensureTestDir(root);
2006
- await run(`${pmName} run lint`, {
2007
- alias: "running linter",
2116
+ const exec = pmExecPrefix(pmName);
2117
+ if (flags.shadcn) {
2118
+ await run(`${exec} shadcn init --no-monorepo --base radix -t vite --yes --force --reinstall --preset ${escapeShellArg(shadcnPreset)}`, {
2119
+ alias: `running shadcn init (preset ${shadcnPreset})`,
2120
+ root
2121
+ });
2122
+ await this.fs.writeFile(this.fs.join(root, "components.json"), componentsJsonTs());
2123
+ }
2124
+ if (flags.saas) await run(`${exec} shadcn add @alepha/saas --yes --overwrite`, {
2125
+ alias: "adding alepha saas registry bundle",
2008
2126
  root
2009
2127
  });
2128
+ try {
2129
+ await run(`${pmName} run lint`, {
2130
+ alias: "running linter",
2131
+ root
2132
+ });
2133
+ } catch (err) {
2134
+ this.log.warn("Linter reported issues during init — continuing. Run `lint` again later to inspect.", { error: err instanceof Error ? err.message : String(err) });
2135
+ }
2010
2136
  if (!workspace.isPackage) {
2011
2137
  if (await this.ensureGitRepo(root, { force })) await run("git add .", {
2012
2138
  alias: "staging generated files",
@@ -2045,6 +2171,30 @@ var ProjectScaffolder = class {
2045
2171
  }
2046
2172
  }
2047
2173
  };
2174
+ /**
2175
+ * Map a package manager name to the command that runs a project-local binary.
2176
+ *
2177
+ * - npm: `npx`
2178
+ * - yarn: `yarn` (yarn auto-resolves binary names; `yarn shadcn ...` works)
2179
+ * - pnpm: `pnpm exec`
2180
+ * - bun: `bunx`
2181
+ *
2182
+ * Used to invoke `shadcn init` / `shadcn add` regardless of the user's PM —
2183
+ * `npm shadcn ...` is invalid (it tries to run a script named `shadcn`).
2184
+ */
2185
+ /** Quote a value so it survives shell parsing. */
2186
+ const escapeShellArg = (value) => {
2187
+ if (/^[A-Za-z0-9_./@:-]+$/.test(value)) return value;
2188
+ return `'${value.replace(/'/g, "'\\''")}'`;
2189
+ };
2190
+ const pmExecPrefix = (pmName) => {
2191
+ switch (pmName) {
2192
+ case "npm": return "npx";
2193
+ case "pnpm": return "pnpm exec";
2194
+ case "bun": return "bunx";
2195
+ default: return "yarn";
2196
+ }
2197
+ };
2048
2198
  //#endregion
2049
2199
  //#region ../../src/cli/core/tasks/BuildTask.ts
2050
2200
  /**
@@ -2222,6 +2372,10 @@ var BuildCloudflareTask = class BuildCloudflareTask extends BuildTask {
2222
2372
  directory: "./public",
2223
2373
  binding: "ASSETS"
2224
2374
  };
2375
+ wrangler.observability ??= {
2376
+ enabled: true,
2377
+ head_sampling_rate: 1
2378
+ };
2225
2379
  this.enhanceDomain(wrangler);
2226
2380
  this.enhanceCron(ctx, wrangler);
2227
2381
  this.enhanceDatabase(wrangler);
@@ -2456,7 +2610,7 @@ var BuildCompressTask = class extends BuildTask {
2456
2610
  *
2457
2611
  * Creates:
2458
2612
  * - Dockerfile with configurable base image
2459
- * - Copies drizzle migrations if they exist
2613
+ * - Copies migrations directory if it exists
2460
2614
  * - Builds Docker image when `--image` flag is provided
2461
2615
  */
2462
2616
  var BuildDockerTask = class extends BuildTask {
@@ -2471,15 +2625,15 @@ var BuildDockerTask = class extends BuildTask {
2471
2625
  await ctx.run({
2472
2626
  name: "generate deploy config (docker)",
2473
2627
  handler: async () => {
2474
- await this.copyDrizzleMigrations(ctx.root, distDir);
2628
+ await this.copyMigrations(ctx.root, distDir);
2475
2629
  await this.writeDockerfile(ctx.root, distDir, dockerFrom, dockerCommand);
2476
2630
  }
2477
2631
  });
2478
2632
  if (ctx.flags?.image) await this.buildDockerImage(ctx, distDir);
2479
2633
  }
2480
- async copyDrizzleMigrations(root, distDir) {
2481
- const drizzleDir = this.fs.join(root, "drizzle");
2482
- if (await this.fs.exists(drizzleDir)) await this.fs.cp(drizzleDir, this.fs.join(root, distDir, "drizzle"));
2634
+ async copyMigrations(root, distDir) {
2635
+ const migrationsDir = this.fs.join(root, "migrations");
2636
+ if (await this.fs.exists(migrationsDir)) await this.fs.cp(migrationsDir, this.fs.join(root, distDir, "migrations"));
2483
2637
  }
2484
2638
  async writeDockerfile(root, distDir, image, command) {
2485
2639
  const dockerfile = `# This file was automatically generated. DO NOT MODIFY.
@@ -2740,6 +2894,10 @@ var BuildServerTask = class extends BuildTask {
2740
2894
  chunkFileNames: "[hash].js",
2741
2895
  assetFileNames: "[hash][extname]",
2742
2896
  format: "esm",
2897
+ codeSplitting: { groups: [{
2898
+ name: "react",
2899
+ test: /node_modules\/react(\/|-dom\/)/
2900
+ }] },
2743
2901
  minify: {
2744
2902
  mangle: { keepNames: true },
2745
2903
  compress: { keepNames: {
@@ -4280,7 +4438,12 @@ const DEFAULT_IGNORE = [
4280
4438
  */
4281
4439
  const changelogOptions = $atom({
4282
4440
  name: "alepha.cli.changelog.options",
4283
- schema: t.object({ ignore: t.optional(t.array(t.string())) }),
4441
+ schema: t.object({
4442
+ /**
4443
+ * Scopes to ignore (e.g., "project", "release", "chore").
4444
+ * Commits like `feat(chore): ...` will be excluded from changelog.
4445
+ */
4446
+ ignore: t.optional(t.array(t.string())) }),
4284
4447
  default: { ignore: DEFAULT_IGNORE }
4285
4448
  });
4286
4449
  //#endregion
@@ -4422,10 +4585,20 @@ var ChangelogCommand = class {
4422
4585
  name: "changelog",
4423
4586
  description: "Generate changelog from conventional commits (outputs to stdout)",
4424
4587
  flags: t.object({
4588
+ /**
4589
+ * Show changes from this ref (tag, commit, branch).
4590
+ * Defaults to the latest version tag.
4591
+ * Example: --from=1.0.0
4592
+ */
4425
4593
  from: t.optional(t.string({
4426
4594
  aliases: ["f"],
4427
4595
  description: "Starting ref (default: latest tag)"
4428
4596
  })),
4597
+ /**
4598
+ * Show changes up to this ref (tag, commit, branch).
4599
+ * Defaults to HEAD.
4600
+ * Example: --to=main
4601
+ */
4429
4602
  to: t.optional(t.string({
4430
4603
  aliases: ["t"],
4431
4604
  description: "Ending ref (default: HEAD)"
@@ -4588,6 +4761,8 @@ var InitCommand = class {
4588
4761
  description: "Include React dependencies and web module (src/web/)"
4589
4762
  })),
4590
4763
  tailwind: t.optional(t.boolean({ description: "Include Tailwind CSS with Vite plugin. Implies --react" })),
4764
+ shadcn: t.optional(t.union([t.boolean(), t.text()], { description: "Set up shadcn/ui (components.json, cn helper, theme tokens, alepha registry). Pass an optional preset id (default: b0). Implies --react and --tailwind" })),
4765
+ saas: t.optional(t.union([t.boolean(), t.text()], { description: "Scaffold a SaaS starter: auth (login/register/reset/verify) + admin panel (/admin AppShell with users/sessions/api-keys/parameters/audits). Pass an optional preset id (default: b0). Implies --shadcn and --api" })),
4591
4766
  test: t.optional(t.boolean({ description: "Include Vitest and create test directory" })),
4592
4767
  force: t.optional(t.boolean({
4593
4768
  aliases: ["f"],