alepha 0.15.0 → 0.15.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (222) hide show
  1. package/README.md +43 -98
  2. package/dist/api/audits/index.d.ts +240 -240
  3. package/dist/api/audits/index.d.ts.map +1 -1
  4. package/dist/api/audits/index.js +2 -2
  5. package/dist/api/audits/index.js.map +1 -1
  6. package/dist/api/files/index.d.ts +185 -185
  7. package/dist/api/files/index.d.ts.map +1 -1
  8. package/dist/api/files/index.js +2 -2
  9. package/dist/api/files/index.js.map +1 -1
  10. package/dist/api/jobs/index.d.ts +245 -245
  11. package/dist/api/jobs/index.d.ts.map +1 -1
  12. package/dist/api/notifications/index.browser.js +4 -4
  13. package/dist/api/notifications/index.browser.js.map +1 -1
  14. package/dist/api/notifications/index.d.ts +74 -74
  15. package/dist/api/notifications/index.d.ts.map +1 -1
  16. package/dist/api/notifications/index.js +4 -4
  17. package/dist/api/notifications/index.js.map +1 -1
  18. package/dist/api/parameters/index.d.ts +221 -221
  19. package/dist/api/parameters/index.d.ts.map +1 -1
  20. package/dist/api/users/index.d.ts +1632 -1631
  21. package/dist/api/users/index.d.ts.map +1 -1
  22. package/dist/api/users/index.js +26 -34
  23. package/dist/api/users/index.js.map +1 -1
  24. package/dist/api/verifications/index.d.ts +132 -132
  25. package/dist/api/verifications/index.d.ts.map +1 -1
  26. package/dist/batch/index.d.ts +122 -122
  27. package/dist/batch/index.d.ts.map +1 -1
  28. package/dist/bucket/index.d.ts +163 -163
  29. package/dist/bucket/index.d.ts.map +1 -1
  30. package/dist/cache/core/index.d.ts +46 -46
  31. package/dist/cache/core/index.d.ts.map +1 -1
  32. package/dist/cache/redis/index.d.ts.map +1 -1
  33. package/dist/cache/redis/index.js +2 -2
  34. package/dist/cache/redis/index.js.map +1 -1
  35. package/dist/cli/index.d.ts +5933 -201
  36. package/dist/cli/index.d.ts.map +1 -1
  37. package/dist/cli/index.js +609 -169
  38. package/dist/cli/index.js.map +1 -1
  39. package/dist/command/index.d.ts +296 -296
  40. package/dist/command/index.d.ts.map +1 -1
  41. package/dist/command/index.js +19 -19
  42. package/dist/command/index.js.map +1 -1
  43. package/dist/core/index.browser.js +268 -79
  44. package/dist/core/index.browser.js.map +1 -1
  45. package/dist/core/index.d.ts +768 -694
  46. package/dist/core/index.d.ts.map +1 -1
  47. package/dist/core/index.js +268 -79
  48. package/dist/core/index.js.map +1 -1
  49. package/dist/core/index.native.js +268 -79
  50. package/dist/core/index.native.js.map +1 -1
  51. package/dist/datetime/index.d.ts +44 -44
  52. package/dist/datetime/index.d.ts.map +1 -1
  53. package/dist/email/index.d.ts +25 -25
  54. package/dist/email/index.d.ts.map +1 -1
  55. package/dist/fake/index.d.ts +5409 -5409
  56. package/dist/fake/index.d.ts.map +1 -1
  57. package/dist/fake/index.js +22 -22
  58. package/dist/fake/index.js.map +1 -1
  59. package/dist/file/index.d.ts +435 -435
  60. package/dist/file/index.d.ts.map +1 -1
  61. package/dist/lock/core/index.d.ts +208 -208
  62. package/dist/lock/core/index.d.ts.map +1 -1
  63. package/dist/lock/redis/index.d.ts.map +1 -1
  64. package/dist/logger/index.d.ts +24 -24
  65. package/dist/logger/index.d.ts.map +1 -1
  66. package/dist/logger/index.js +1 -5
  67. package/dist/logger/index.js.map +1 -1
  68. package/dist/mcp/index.d.ts +216 -198
  69. package/dist/mcp/index.d.ts.map +1 -1
  70. package/dist/mcp/index.js +28 -4
  71. package/dist/mcp/index.js.map +1 -1
  72. package/dist/orm/index.browser.js +9 -9
  73. package/dist/orm/index.browser.js.map +1 -1
  74. package/dist/orm/index.bun.js +83 -76
  75. package/dist/orm/index.bun.js.map +1 -1
  76. package/dist/orm/index.d.ts +961 -960
  77. package/dist/orm/index.d.ts.map +1 -1
  78. package/dist/orm/index.js +88 -81
  79. package/dist/orm/index.js.map +1 -1
  80. package/dist/queue/core/index.d.ts +244 -244
  81. package/dist/queue/core/index.d.ts.map +1 -1
  82. package/dist/queue/redis/index.d.ts.map +1 -1
  83. package/dist/redis/index.d.ts +105 -105
  84. package/dist/redis/index.d.ts.map +1 -1
  85. package/dist/retry/index.d.ts +69 -69
  86. package/dist/retry/index.d.ts.map +1 -1
  87. package/dist/router/index.d.ts +6 -6
  88. package/dist/router/index.d.ts.map +1 -1
  89. package/dist/scheduler/index.d.ts +108 -26
  90. package/dist/scheduler/index.d.ts.map +1 -1
  91. package/dist/scheduler/index.js +393 -1
  92. package/dist/scheduler/index.js.map +1 -1
  93. package/dist/security/index.d.ts +532 -209
  94. package/dist/security/index.d.ts.map +1 -1
  95. package/dist/security/index.js +1422 -11
  96. package/dist/security/index.js.map +1 -1
  97. package/dist/server/auth/index.d.ts +1296 -271
  98. package/dist/server/auth/index.d.ts.map +1 -1
  99. package/dist/server/auth/index.js +1249 -18
  100. package/dist/server/auth/index.js.map +1 -1
  101. package/dist/server/cache/index.d.ts +56 -56
  102. package/dist/server/cache/index.d.ts.map +1 -1
  103. package/dist/server/compress/index.d.ts +3 -3
  104. package/dist/server/compress/index.d.ts.map +1 -1
  105. package/dist/server/cookies/index.d.ts +6 -6
  106. package/dist/server/cookies/index.d.ts.map +1 -1
  107. package/dist/server/core/index.d.ts +196 -186
  108. package/dist/server/core/index.d.ts.map +1 -1
  109. package/dist/server/core/index.js +43 -27
  110. package/dist/server/core/index.js.map +1 -1
  111. package/dist/server/cors/index.d.ts +11 -11
  112. package/dist/server/cors/index.d.ts.map +1 -1
  113. package/dist/server/health/index.d.ts.map +1 -1
  114. package/dist/server/helmet/index.d.ts +2 -2
  115. package/dist/server/helmet/index.d.ts.map +1 -1
  116. package/dist/server/links/index.browser.js +9 -1
  117. package/dist/server/links/index.browser.js.map +1 -1
  118. package/dist/server/links/index.d.ts +83 -83
  119. package/dist/server/links/index.d.ts.map +1 -1
  120. package/dist/server/links/index.js +13 -5
  121. package/dist/server/links/index.js.map +1 -1
  122. package/dist/server/metrics/index.d.ts +514 -1
  123. package/dist/server/metrics/index.d.ts.map +1 -1
  124. package/dist/server/metrics/index.js +4462 -4
  125. package/dist/server/metrics/index.js.map +1 -1
  126. package/dist/server/multipart/index.d.ts +6 -6
  127. package/dist/server/multipart/index.d.ts.map +1 -1
  128. package/dist/server/proxy/index.d.ts +102 -102
  129. package/dist/server/proxy/index.d.ts.map +1 -1
  130. package/dist/server/rate-limit/index.d.ts +16 -16
  131. package/dist/server/rate-limit/index.d.ts.map +1 -1
  132. package/dist/server/static/index.d.ts +44 -44
  133. package/dist/server/static/index.d.ts.map +1 -1
  134. package/dist/server/swagger/index.d.ts +47 -47
  135. package/dist/server/swagger/index.d.ts.map +1 -1
  136. package/dist/sms/index.d.ts +11 -11
  137. package/dist/sms/index.d.ts.map +1 -1
  138. package/dist/sms/index.js +3 -3
  139. package/dist/sms/index.js.map +1 -1
  140. package/dist/thread/index.d.ts +71 -71
  141. package/dist/thread/index.d.ts.map +1 -1
  142. package/dist/thread/index.js +2 -2
  143. package/dist/thread/index.js.map +1 -1
  144. package/dist/topic/core/index.d.ts +318 -318
  145. package/dist/topic/core/index.d.ts.map +1 -1
  146. package/dist/topic/redis/index.d.ts +6 -6
  147. package/dist/topic/redis/index.d.ts.map +1 -1
  148. package/dist/vite/index.d.ts +2324 -1719
  149. package/dist/vite/index.d.ts.map +1 -1
  150. package/dist/vite/index.js +123 -475
  151. package/dist/vite/index.js.map +1 -1
  152. package/dist/websocket/index.browser.js +3 -3
  153. package/dist/websocket/index.browser.js.map +1 -1
  154. package/dist/websocket/index.d.ts +275 -275
  155. package/dist/websocket/index.d.ts.map +1 -1
  156. package/dist/websocket/index.js +3 -3
  157. package/dist/websocket/index.js.map +1 -1
  158. package/package.json +9 -9
  159. package/src/api/users/services/SessionService.ts +0 -10
  160. package/src/cli/apps/AlephaCli.ts +2 -2
  161. package/src/cli/apps/AlephaPackageBuilderCli.ts +9 -1
  162. package/src/cli/assets/apiHelloControllerTs.ts +2 -1
  163. package/src/cli/assets/biomeJson.ts +2 -1
  164. package/src/cli/assets/claudeMd.ts +9 -4
  165. package/src/cli/assets/dummySpecTs.ts +2 -1
  166. package/src/cli/assets/editorconfig.ts +2 -1
  167. package/src/cli/assets/mainBrowserTs.ts +2 -1
  168. package/src/cli/assets/mainCss.ts +24 -0
  169. package/src/cli/assets/tsconfigJson.ts +2 -1
  170. package/src/cli/assets/webAppRouterTs.ts +2 -1
  171. package/src/cli/assets/webHelloComponentTsx.ts +6 -2
  172. package/src/cli/atoms/appEntryOptions.ts +13 -0
  173. package/src/cli/atoms/buildOptions.ts +1 -1
  174. package/src/cli/atoms/changelogOptions.ts +1 -1
  175. package/src/cli/commands/build.ts +63 -47
  176. package/src/cli/commands/dev.ts +16 -33
  177. package/src/cli/commands/gen/env.ts +1 -1
  178. package/src/cli/commands/init.ts +17 -8
  179. package/src/cli/commands/lint.ts +1 -1
  180. package/src/cli/defineConfig.ts +9 -0
  181. package/src/cli/index.ts +2 -1
  182. package/src/cli/providers/AppEntryProvider.ts +131 -0
  183. package/src/cli/providers/ViteBuildProvider.ts +82 -0
  184. package/src/cli/providers/ViteDevServerProvider.ts +350 -0
  185. package/src/cli/providers/ViteTemplateProvider.ts +27 -0
  186. package/src/cli/services/AlephaCliUtils.ts +33 -2
  187. package/src/cli/services/PackageManagerUtils.ts +13 -6
  188. package/src/cli/services/ProjectScaffolder.ts +72 -49
  189. package/src/core/Alepha.ts +2 -8
  190. package/src/core/primitives/$module.ts +12 -0
  191. package/src/core/providers/KeylessJsonSchemaCodec.spec.ts +257 -0
  192. package/src/core/providers/KeylessJsonSchemaCodec.ts +396 -14
  193. package/src/core/providers/SchemaValidator.spec.ts +236 -0
  194. package/src/logger/providers/PrettyFormatterProvider.ts +0 -9
  195. package/src/mcp/errors/McpError.ts +30 -0
  196. package/src/mcp/index.ts +3 -0
  197. package/src/mcp/transports/SseMcpTransport.ts +16 -6
  198. package/src/orm/providers/DrizzleKitProvider.ts +3 -5
  199. package/src/orm/services/Repository.ts +11 -0
  200. package/src/server/core/index.ts +1 -1
  201. package/src/server/core/providers/BunHttpServerProvider.ts +1 -1
  202. package/src/server/core/providers/NodeHttpServerProvider.spec.ts +125 -0
  203. package/src/server/core/providers/NodeHttpServerProvider.ts +71 -22
  204. package/src/server/core/providers/ServerLoggerProvider.ts +2 -2
  205. package/src/server/core/providers/ServerProvider.ts +9 -12
  206. package/src/server/links/atoms/apiLinksAtom.ts +7 -0
  207. package/src/server/links/index.browser.ts +2 -0
  208. package/src/server/links/index.ts +2 -0
  209. package/src/vite/index.ts +3 -2
  210. package/src/vite/tasks/buildClient.ts +0 -1
  211. package/src/vite/tasks/buildServer.ts +68 -21
  212. package/src/vite/tasks/copyAssets.ts +5 -4
  213. package/src/vite/tasks/generateSitemap.ts +64 -23
  214. package/src/vite/tasks/index.ts +0 -2
  215. package/src/vite/tasks/prerenderPages.ts +49 -24
  216. package/src/cli/assets/indexHtml.ts +0 -15
  217. package/src/cli/commands/format.ts +0 -23
  218. package/src/vite/helpers/boot.ts +0 -117
  219. package/src/vite/plugins/viteAlephaDev.ts +0 -177
  220. package/src/vite/tasks/devServer.ts +0 -71
  221. package/src/vite/tasks/runAlepha.ts +0 -270
  222. /package/dist/orm/{chunk-DtkW-qnP.js → chunk-DH6iiROE.js} +0 -0
package/dist/cli/index.js CHANGED
@@ -2,7 +2,7 @@ import { $atom, $hook, $inject, $module, $use, Alepha, AlephaError, t } from "al
2
2
  import { FileSystemProvider } from "alepha/file";
3
3
  import { $command, CliProvider, EnvUtils } from "alepha/command";
4
4
  import { $logger, ConsoleColorProvider } from "alepha/logger";
5
- import { boot, buildClient, buildServer, copyAssets, devServer, generateCloudflare, generateDocker, generateSitemap, generateVercel, prerenderPages } from "alepha/vite";
5
+ import { buildClient, buildServer, copyAssets, generateCloudflare, generateDocker, generateSitemap, generateVercel, importVite, importViteReact, prerenderPages, viteAlephaSsrPreload } from "alepha/vite";
6
6
  import { exec, spawn } from "node:child_process";
7
7
  import { readFileSync } from "node:fs";
8
8
  import { basename, dirname, join } from "node:path";
@@ -79,7 +79,7 @@ $atom$1[KIND] = "atom";
79
79
  * Options can be overridden via vite.config.ts or CLI flags.
80
80
  */
81
81
  const buildOptions = $atom$1({
82
- name: "alepha.build.options",
82
+ name: "alepha.cli.build.options",
83
83
  description: "Build configuration options",
84
84
  schema: t.object({
85
85
  stats: t.optional(t.boolean({ default: false })),
@@ -102,6 +102,180 @@ const buildOptions = $atom$1({
102
102
  default: {}
103
103
  });
104
104
 
105
+ //#endregion
106
+ //#region ../../src/cli/atoms/appEntryOptions.ts
107
+ const appEntryOptions = $atom({
108
+ name: "alepha.cli.appEntry.options",
109
+ schema: t.object({
110
+ server: t.optional(t.text()),
111
+ browser: t.optional(t.text()),
112
+ style: t.optional(t.text())
113
+ }),
114
+ default: {}
115
+ });
116
+
117
+ //#endregion
118
+ //#region ../../src/cli/providers/AppEntryProvider.ts
119
+ /**
120
+ * Service for locating entry files in Alepha projects.
121
+ *
122
+ * Originally in alepha/vite, moved to CLI to avoid cli -> vite dependency.
123
+ */
124
+ var AppEntryProvider = class {
125
+ fs = $inject(FileSystemProvider);
126
+ options = $use(appEntryOptions);
127
+ serverEntries = [
128
+ "main.server.ts",
129
+ "main.server.tsx",
130
+ "main.ts",
131
+ "main.tsx"
132
+ ];
133
+ browserEntries = [
134
+ "main.browser.ts",
135
+ "main.browser.tsx",
136
+ "main.ts",
137
+ "main.tsx"
138
+ ];
139
+ styleEntries = [
140
+ "main.css",
141
+ "styles.css",
142
+ "style.css"
143
+ ];
144
+ /**
145
+ * Get application entry points.
146
+ *
147
+ * Server entry is required, an error is thrown if not found.
148
+ * Browser entry is optional.
149
+ *
150
+ * It will first check for custom entries in options, see appEntryOptions.
151
+ */
152
+ async getAppEntry(root) {
153
+ const appEntry = {
154
+ root,
155
+ server: ""
156
+ };
157
+ if (this.options.server) {
158
+ if (!await this.fs.exists(this.fs.join(root, this.options.server))) throw new AlephaError(`Custom server entry "${this.options.server}" not found.`);
159
+ appEntry.server = this.options.server;
160
+ }
161
+ if (this.options.browser) {
162
+ if (!await this.fs.exists(this.fs.join(root, this.options.browser))) throw new AlephaError(`Custom browser entry "${this.options.browser}" not found.`);
163
+ appEntry.browser = this.options.browser;
164
+ }
165
+ if (this.options.style) {
166
+ if (!await this.fs.exists(this.fs.join(root, this.options.style))) throw new AlephaError(`Custom style entry "${this.options.style}" not found.`);
167
+ appEntry.style = this.options.style;
168
+ }
169
+ const srcFiles = await this.fs.ls(this.fs.join(root, "src"));
170
+ if (!appEntry.server) {
171
+ for (const entry of this.serverEntries) if (srcFiles.includes(entry)) {
172
+ appEntry.server = this.fs.join("src", entry);
173
+ break;
174
+ }
175
+ }
176
+ if (!appEntry.server) throw new AlephaError("No server entry found. Please, add a main.server.ts file in the src/ directory or configure a custom entry in alepha.config.ts.");
177
+ if (!appEntry.browser) {
178
+ for (const entry of this.browserEntries) if (srcFiles.includes(entry)) {
179
+ appEntry.browser = this.fs.join("src", entry);
180
+ break;
181
+ }
182
+ }
183
+ if (!appEntry.style) {
184
+ for (const entry of this.styleEntries) if (srcFiles.includes(entry)) {
185
+ appEntry.style = this.fs.join("src", entry);
186
+ break;
187
+ }
188
+ }
189
+ return appEntry;
190
+ }
191
+ };
192
+
193
+ //#endregion
194
+ //#region ../../src/cli/providers/ViteTemplateProvider.ts
195
+ var ViteTemplateProvider = class {
196
+ fs = $inject(FileSystemProvider);
197
+ generateIndexHtml(entry) {
198
+ const style = entry.style;
199
+ const browser = entry.browser ?? entry.server;
200
+ return `
201
+ <!DOCTYPE html>
202
+ <html lang="en">
203
+ <head>
204
+ <meta charset="UTF-8" />
205
+ <title>App</title>
206
+ <meta name="viewport" content="width=device-width, initial-scale=1"/>
207
+ ${style ? `<link rel="stylesheet" href="/${style}" />` : ""}
208
+ </head>
209
+ <body>
210
+ <div id="root"></div>
211
+ <script type="module" src="/${browser}"><\/script>
212
+ </body>
213
+ </html>
214
+ `.trim();
215
+ }
216
+ };
217
+
218
+ //#endregion
219
+ //#region ../../src/cli/providers/ViteBuildProvider.ts
220
+ var ViteBuildProvider = class {
221
+ alepha;
222
+ appEntry;
223
+ viteDevServer;
224
+ templateProvider = $inject(ViteTemplateProvider);
225
+ /**
226
+ * We need to close the Vite dev server after build is done.
227
+ */
228
+ onReady = $hook({
229
+ on: "ready",
230
+ priority: "last",
231
+ handler: async () => {
232
+ await this.viteDevServer?.close();
233
+ }
234
+ });
235
+ onStop = $hook({
236
+ on: "stop",
237
+ handler: async () => {
238
+ await this.viteDevServer?.close();
239
+ }
240
+ });
241
+ async init(opts) {
242
+ const { createServer } = await importVite();
243
+ process.env.ALEPHA_CLI_IMPORT = "true";
244
+ process.env.NODE_ENV = "production";
245
+ process.env.LOG_LEVEL ??= "warn";
246
+ /**
247
+ * 01/26 Vite 7
248
+ * "runnerImport" doesn't work as expected here. (e.g. build docs fail)
249
+ * -> We still use devServer and ssrLoadModule for now.
250
+ * -> This is clearly a bad stuff, we need to find better way.
251
+ */
252
+ this.viteDevServer = await createServer({
253
+ server: { middlewareMode: true },
254
+ appType: "custom",
255
+ logLevel: "silent"
256
+ });
257
+ await this.viteDevServer.ssrLoadModule(opts.entry.server);
258
+ const alepha = globalThis.__alepha;
259
+ if (!alepha) throw new AlephaError("Alepha instance not found after loading entry module");
260
+ this.alepha = alepha;
261
+ this.appEntry = opts.entry;
262
+ return alepha;
263
+ }
264
+ hasClient() {
265
+ if (!this.alepha) throw new AlephaError("ViteBuildProvider not initialized");
266
+ try {
267
+ this.alepha.inject("ReactServerProvider");
268
+ return true;
269
+ } catch {
270
+ return false;
271
+ }
272
+ }
273
+ generateIndexHtml() {
274
+ if (!this.appEntry) throw new AlephaError("ViteBuildProvider not initialized");
275
+ return this.templateProvider.generateIndexHtml(this.appEntry);
276
+ }
277
+ };
278
+
105
279
  //#endregion
106
280
  //#region ../../src/cli/services/AlephaCliUtils.ts
107
281
  /**
@@ -117,14 +291,15 @@ var AlephaCliUtils = class {
117
291
  log = $logger();
118
292
  fs = $inject(FileSystemProvider);
119
293
  envUtils = $inject(EnvUtils);
294
+ boot = $inject(AppEntryProvider);
120
295
  /**
121
296
  * Execute a command with inherited stdio.
122
297
  */
123
298
  async exec(command, options = {}) {
124
299
  const root = options.root ?? process.cwd();
125
300
  this.log.debug(`Executing command: ${command}`, { cwd: root });
126
- const runExec = async (app$1, args$1) => {
127
- const prog = spawn(app$1, args$1, {
301
+ const runExec = async (app, args) => {
302
+ const prog = spawn(app, args, {
128
303
  stdio: "inherit",
129
304
  cwd: root,
130
305
  env: {
@@ -137,14 +312,22 @@ var AlephaCliUtils = class {
137
312
  }));
138
313
  };
139
314
  if (options.global) {
140
- const [app$1, ...args$1] = command.split(" ");
141
- await runExec(app$1, args$1);
315
+ const [app, ...args] = command.split(" ");
316
+ await runExec(app, args);
142
317
  return;
143
318
  }
144
319
  const suffix = process.platform === "win32" ? ".cmd" : "";
145
320
  const [app, ...args] = command.split(" ");
146
321
  let execPath = await this.checkFileExists(root, `node_modules/.bin/${app}${suffix}`);
147
322
  if (!execPath) execPath = await this.checkFileExists(root, `node_modules/alepha/node_modules/.bin/${app}${suffix}`);
323
+ if (!execPath) {
324
+ let parentDir = this.fs.join(root, "..");
325
+ for (let i = 0; i < 3; i++) {
326
+ execPath = await this.checkFileExists(parentDir, `node_modules/.bin/${app}${suffix}`);
327
+ if (execPath) break;
328
+ parentDir = this.fs.join(parentDir, "..");
329
+ }
330
+ }
148
331
  if (!execPath) throw new AlephaError(`Could not find executable for command '${app}'. Make sure the package is installed.`);
149
332
  await runExec(execPath, args);
150
333
  }
@@ -164,7 +347,15 @@ var AlephaCliUtils = class {
164
347
  */
165
348
  async loadAlephaFromServerEntryFile(rootDir, explicitEntry) {
166
349
  process.env.ALEPHA_CLI_IMPORT = "true";
167
- const entry = await boot.getServerEntry(rootDir, explicitEntry);
350
+ const root = rootDir ?? process.cwd();
351
+ let entry;
352
+ if (explicitEntry) {
353
+ entry = this.fs.join(root, explicitEntry);
354
+ if (!await this.fs.exists(entry)) throw new AlephaError(`Explicit server entry file "${explicitEntry}" not found.`);
355
+ } else {
356
+ const appEntry = await this.boot.getAppEntry(root);
357
+ entry = this.fs.join(root, appEntry.server);
358
+ }
168
359
  delete global.__alepha;
169
360
  const mod = await import(entry);
170
361
  this.log.debug(`Load entry: ${entry}`);
@@ -283,6 +474,12 @@ var PackageManagerUtils = class {
283
474
  return this.hasDependency(root, "expo");
284
475
  }
285
476
  /**
477
+ * Check if React is present in the project.
478
+ */
479
+ async hasReact(root) {
480
+ return this.hasDependency(root, "react");
481
+ }
482
+ /**
286
483
  * Install a dependency if it's missing from the project.
287
484
  */
288
485
  async ensureDependency(root, packageName, options = {}) {
@@ -293,7 +490,7 @@ var PackageManagerUtils = class {
293
490
  }
294
491
  const cmd = await this.getInstallCommand(root, packageName, dev);
295
492
  if (options.run) await options.run(cmd, {
296
- alias: `installing ${packageName}`,
493
+ alias: `add ${packageName}`,
297
494
  root
298
495
  });
299
496
  else if (options.exec) {
@@ -368,17 +565,17 @@ var PackageManagerUtils = class {
368
565
  await this.writePackageJson(root, content);
369
566
  return content;
370
567
  }
371
- const packageJson$1 = await this.readPackageJson(root);
568
+ const packageJson = await this.readPackageJson(root);
372
569
  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;
570
+ packageJson.type = "module";
571
+ packageJson.dependencies ??= {};
572
+ packageJson.devDependencies ??= {};
573
+ packageJson.scripts ??= {};
574
+ Object.assign(packageJson.dependencies, newContent.dependencies);
575
+ Object.assign(packageJson.devDependencies, newContent.devDependencies);
576
+ Object.assign(packageJson.scripts, newContent.scripts);
577
+ await this.writePackageJson(root, packageJson);
578
+ return packageJson;
382
579
  }
383
580
  generatePackageJsonContent(modes) {
384
581
  const dependencies = { alepha: `^${version}` };
@@ -390,11 +587,11 @@ var PackageManagerUtils = class {
390
587
  typecheck: "alepha typecheck",
391
588
  verify: "alepha verify"
392
589
  };
393
- if (modes.admin) {
590
+ if (modes.ui) {
394
591
  dependencies["@alepha/ui"] = `^${version}`;
395
- modes.web = true;
592
+ modes.react = true;
396
593
  }
397
- if (modes.web) {
594
+ if (modes.react) {
398
595
  dependencies["@alepha/react"] = `^${version}`;
399
596
  dependencies.react = "^19.2.0";
400
597
  dependencies["react-dom"] = "^19.2.0";
@@ -453,7 +650,7 @@ export const ApiModule = $module({
453
650
 
454
651
  //#endregion
455
652
  //#region ../../src/cli/assets/biomeJson.ts
456
- const biomeJson = `
653
+ const biomeJson = () => `
457
654
  {
458
655
  "$schema": "https://biomejs.dev/schemas/latest/schema.json",
459
656
  "vcs": {
@@ -549,8 +746,8 @@ ${projectName}/
549
746
  │ │ ├── AppRouter.ts # Routes with $page
550
747
  │ │ └── index.ts # Web module definition with $module
551
748
  │ ├── main.server.ts # Server entry
552
- └── main.browser.ts # Browser entry (React only)
553
- ├── index.html # (React only)
749
+ ├── main.browser.ts # Browser entry (React only)
750
+ │ └── main.css # CSS entry (React only)
554
751
  ├── package.json
555
752
  └── tsconfig.json
556
753
  \`\`\`
@@ -595,6 +792,7 @@ This is an **Alepha** project - a convention-driven TypeScript framework for typ
595
792
  - Use \`protected\` instead of \`private\` for class members
596
793
  - Import with file extensions: \`import { User } from "./User.ts"\`
597
794
  - Use \`t\` from Alepha for schemas (not Zod)
795
+ - Prefer \`t.text()\` over \`t.string()\` for user input (has default max length, auto-trim, supports lowercase option)
598
796
 
599
797
  ## Project Structure
600
798
  ${projectStructure}
@@ -792,7 +990,7 @@ test("dummy test", () => {
792
990
 
793
991
  //#endregion
794
992
  //#region ../../src/cli/assets/editorconfig.ts
795
- const editorconfig = `
993
+ const editorconfig = () => `
796
994
  # https://editorconfig.org
797
995
 
798
996
  root = true
@@ -806,22 +1004,6 @@ indent_style = space
806
1004
  indent_size = 2
807
1005
  `.trim();
808
1006
 
809
- //#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>
823
- `.trim();
824
-
825
1007
  //#endregion
826
1008
  //#region ../../src/cli/assets/mainBrowserTs.ts
827
1009
  const mainBrowserTs = () => `
@@ -835,6 +1017,32 @@ alepha.with(WebModule);
835
1017
  run(alepha);
836
1018
  `.trim();
837
1019
 
1020
+ //#endregion
1021
+ //#region ../../src/cli/assets/mainCss.ts
1022
+ const mainCss = () => `
1023
+ * {
1024
+ box-sizing: border-box;
1025
+ margin: 0;
1026
+ padding: 0;
1027
+ }
1028
+
1029
+ html,
1030
+ body {
1031
+ height: 100%;
1032
+ }
1033
+
1034
+ body {
1035
+ font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
1036
+ "Helvetica Neue", Arial, sans-serif;
1037
+ line-height: 1.5;
1038
+ -webkit-font-smoothing: antialiased;
1039
+ }
1040
+
1041
+ #root {
1042
+ height: 100%;
1043
+ }
1044
+ `.trim();
1045
+
838
1046
  //#endregion
839
1047
  //#region ../../src/cli/assets/mainServerTs.ts
840
1048
  const mainServerTs = (options = {}) => {
@@ -853,7 +1061,7 @@ run(alepha);
853
1061
 
854
1062
  //#endregion
855
1063
  //#region ../../src/cli/assets/tsconfigJson.ts
856
- const tsconfigJson = `
1064
+ const tsconfigJson = () => `
857
1065
  {
858
1066
  "extends": "alepha/tsconfig.base"
859
1067
  }
@@ -879,15 +1087,18 @@ export class AppRouter {
879
1087
 
880
1088
  //#endregion
881
1089
  //#region ../../src/cli/assets/webHelloComponentTsx.ts
882
- const webHelloComponentTsx = () => `
1090
+ const webHelloComponentTsx = () => `import { useState } from "react";
1091
+
883
1092
  interface Props {
884
1093
  message: string;
885
1094
  }
886
1095
 
887
1096
  const Hello = (props: Props) => {
1097
+ const [message, setMessage] = useState(props.message);
888
1098
  return (
889
1099
  <div>
890
- <h1>{props.message}</h1>
1100
+ <h1>{message}</h1>
1101
+ <input value={message} onChange={e => setMessage(e.target.value)} />
891
1102
  <p>Edit this component in src/web/components/Hello.tsx</p>
892
1103
  </div>
893
1104
  );
@@ -942,27 +1153,30 @@ var ProjectScaffolder = class {
942
1153
  */
943
1154
  async ensureConfig(root, opts) {
944
1155
  const tasks = [];
1156
+ const force = opts.force ?? false;
945
1157
  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));
1158
+ if (opts.tsconfigJson) tasks.push(this.ensureTsConfig(root, { force }));
1159
+ if (opts.indexHtml) tasks.push(this.ensureReactProject(root, { force }));
1160
+ if (opts.biomeJson) tasks.push(this.ensureBiomeConfig(root, { force }));
1161
+ if (opts.editorconfig) tasks.push(this.ensureEditorConfig(root, { force }));
1162
+ if (opts.claudeMd) tasks.push(this.ensureClaudeMd(root, typeof opts.claudeMd === "boolean" ? { force } : {
1163
+ ...opts.claudeMd,
1164
+ force
1165
+ }));
951
1166
  await Promise.all(tasks);
952
1167
  }
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);
1168
+ async ensureTsConfig(root, opts = {}) {
1169
+ if (!opts.force && await this.existsInParents(root, "tsconfig.json")) return;
1170
+ await this.fs.writeFile(this.fs.join(root, "tsconfig.json"), tsconfigJson());
956
1171
  }
957
- async ensureBiomeConfig(root) {
958
- await this.ensureFileIfNotExists(root, "biome.json", biomeJson);
1172
+ async ensureBiomeConfig(root, opts = {}) {
1173
+ await this.ensureFile(root, "biome.json", biomeJson(), opts.force);
959
1174
  }
960
- async ensureEditorConfig(root) {
961
- await this.ensureFileIfNotExists(root, ".editorconfig", editorconfig);
1175
+ async ensureEditorConfig(root, opts = {}) {
1176
+ await this.ensureFile(root, ".editorconfig", editorconfig(), opts.force);
962
1177
  }
963
1178
  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));
1179
+ await this.ensureFile(root, "CLAUDE.md", claudeMd(options), options.force);
966
1180
  }
967
1181
  /**
968
1182
  * Ensure src/main.server.ts exists with full API structure.
@@ -972,39 +1186,37 @@ var ProjectScaffolder = class {
972
1186
  * - src/api/index.ts (API module)
973
1187
  * - src/api/controllers/HelloController.ts (example controller)
974
1188
  */
975
- async ensureApiProject(root) {
1189
+ async ensureApiProject(root, opts = {}) {
976
1190
  const srcDir = this.fs.join(root, "src");
977
- if (await this.fs.exists(srcDir)) {
1191
+ if (!opts.force && await this.fs.exists(srcDir)) {
978
1192
  if ((await this.fs.ls(srcDir)).length > 0) return;
979
1193
  }
980
1194
  const appName = this.getAppName(root);
981
1195
  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());
1196
+ await this.ensureFile(srcDir, "main.server.ts", mainServerTs(), opts.force);
1197
+ await this.ensureFile(srcDir, "api/index.ts", apiIndexTs({ appName }), opts.force);
1198
+ await this.ensureFile(srcDir, "api/controllers/HelloController.ts", apiHelloControllerTs(), opts.force);
985
1199
  }
986
1200
  /**
987
1201
  * Ensure full React project structure exists.
988
1202
  *
989
1203
  * Creates:
990
- * - index.html
991
1204
  * - src/main.server.ts, src/main.browser.ts
992
1205
  * - src/api/index.ts, src/api/controllers/HelloController.ts
993
1206
  * - src/web/index.ts, src/web/AppRouter.ts, src/web/components/Hello.tsx
994
1207
  */
995
- async ensureReactProject(root) {
996
- if (await this.fs.exists(this.fs.join(root, "index.html"))) return;
1208
+ async ensureReactProject(root, opts = {}) {
997
1209
  const appName = this.getAppName(root);
998
1210
  await this.fs.mkdir(this.fs.join(root, "src/api/controllers"), { recursive: true });
999
1211
  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());
1212
+ await this.ensureFile(root, "src/main.css", mainCss(), opts.force);
1213
+ await this.ensureFile(root, "src/api/index.ts", apiIndexTs({ appName }), opts.force);
1214
+ await this.ensureFile(root, "src/api/controllers/HelloController.ts", apiHelloControllerTs(), opts.force);
1215
+ await this.ensureFile(root, "src/main.server.ts", mainServerTs({ react: true }), opts.force);
1216
+ await this.ensureFile(root, "src/web/index.ts", webIndexTs({ appName }), opts.force);
1217
+ await this.ensureFile(root, "src/web/AppRouter.ts", webAppRouterTs(), opts.force);
1218
+ await this.ensureFile(root, "src/web/components/Hello.tsx", webHelloComponentTsx(), opts.force);
1219
+ await this.ensureFile(root, "src/main.browser.ts", mainBrowserTs(), opts.force);
1008
1220
  }
1009
1221
  /**
1010
1222
  * Ensure test directory exists with a dummy test file.
@@ -1019,9 +1231,12 @@ var ProjectScaffolder = class {
1019
1231
  }
1020
1232
  if ((await this.fs.ls(testDir)).length === 0) await this.fs.writeFile(dummyPath, dummySpecTs());
1021
1233
  }
1022
- async ensureFileIfNotExists(root, relativePath, content) {
1234
+ /**
1235
+ * Write a file, optionally overriding if it exists.
1236
+ */
1237
+ async ensureFile(root, relativePath, content, force) {
1023
1238
  const fullPath = this.fs.join(root, relativePath);
1024
- if (!await this.fs.exists(fullPath)) await this.fs.writeFile(fullPath, content);
1239
+ if (force || !await this.fs.exists(fullPath)) await this.fs.writeFile(fullPath, content);
1025
1240
  }
1026
1241
  /**
1027
1242
  * Check if a file exists in the given directory or any parent directory.
@@ -1045,15 +1260,13 @@ var BuildCommand = class {
1045
1260
  utils = $inject(AlephaCliUtils);
1046
1261
  pm = $inject(PackageManagerUtils);
1047
1262
  scaffolder = $inject(ProjectScaffolder);
1263
+ boot = $inject(AppEntryProvider);
1264
+ viteBuildProvider = $inject(ViteBuildProvider);
1048
1265
  options = $use(buildOptions);
1049
1266
  build = $command({
1050
1267
  name: "build",
1051
1268
  mode: "production",
1052
1269
  description: "Build the project for production",
1053
- args: t.optional(t.text({
1054
- title: "path",
1055
- description: "Filepath to build"
1056
- })),
1057
1270
  flags: t.object({
1058
1271
  stats: t.optional(t.boolean({ description: "Generate build stats report" })),
1059
1272
  vercel: t.optional(t.boolean({ description: "Generate Vercel deployment configuration" })),
@@ -1062,15 +1275,14 @@ var BuildCommand = class {
1062
1275
  sitemap: t.optional(t.text({ description: "Generate sitemap.xml with base URL" })),
1063
1276
  bun: t.optional(t.boolean({ description: "Prioritize .bun.ts entry files for Bun runtime" }))
1064
1277
  }),
1065
- handler: async ({ flags, args, run, root }) => {
1066
- process.env.ALEPHA_BUILD_MODE = "cli";
1278
+ handler: async ({ flags, run, root }) => {
1067
1279
  process.env.NODE_ENV = "production";
1068
1280
  if (await this.pm.hasExpo(root)) return;
1069
1281
  await this.scaffolder.ensureConfig(root, { tsconfigJson: true });
1070
- const entry = await boot.getServerEntry(root, args);
1282
+ const entry = await this.boot.getAppEntry(root);
1071
1283
  this.log.trace("Entry file found", { entry });
1072
1284
  const distDir = "dist";
1073
- const clientDir = "public";
1285
+ const publicDir = "public";
1074
1286
  await this.pm.ensureDependency(root, "vite", {
1075
1287
  run,
1076
1288
  exec: (cmd, opts) => this.utils.exec(cmd, opts)
@@ -1079,36 +1291,58 @@ var BuildCommand = class {
1079
1291
  const options = this.options;
1080
1292
  await this.utils.loadEnv(root, [".env", ".env.production"]);
1081
1293
  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
- })
1294
+ let template = "";
1295
+ let hasClient = false;
1296
+ let alepha;
1297
+ await run({
1298
+ name: "analyze app",
1299
+ handler: async () => {
1300
+ alepha = await this.viteBuildProvider.init({ entry });
1301
+ hasClient = this.viteBuildProvider.hasClient();
1302
+ if (hasClient) template = this.viteBuildProvider.generateIndexHtml();
1303
+ }
1091
1304
  });
1305
+ if (!alepha) throw new AlephaError("Alepha instance not found");
1306
+ if (hasClient) {
1307
+ const indexHtmlPath = this.fs.join(root, "index.html");
1308
+ await this.fs.writeFile(indexHtmlPath, template);
1309
+ try {
1310
+ await run({
1311
+ name: "vite build client",
1312
+ handler: () => buildClient({
1313
+ silent: true,
1314
+ dist: `${distDir}/${publicDir}`,
1315
+ stats,
1316
+ precompress: true
1317
+ })
1318
+ });
1319
+ } finally {
1320
+ await this.fs.rm(indexHtmlPath);
1321
+ }
1322
+ }
1092
1323
  await run({
1093
1324
  name: "vite build server",
1094
1325
  handler: async () => {
1095
- const clientIndexPath = `${distDir}/${clientDir}/index.html`;
1326
+ if (!alepha) throw new AlephaError("Alepha instance not found");
1327
+ const clientIndexPath = `${distDir}/${publicDir}/index.html`;
1096
1328
  const clientBuilt = await this.fs.exists(clientIndexPath);
1097
1329
  const conditions = [];
1098
1330
  if (flags.bun) conditions.push("bun");
1099
1331
  if (options.cloudflare) conditions.push("workerd");
1100
1332
  await buildServer({
1101
1333
  silent: true,
1102
- entry,
1334
+ entry: entry.server,
1103
1335
  distDir,
1104
- clientDir: clientBuilt ? clientDir : void 0,
1336
+ clientDir: clientBuilt ? publicDir : void 0,
1105
1337
  stats,
1106
- conditions
1338
+ conditions,
1339
+ alepha
1107
1340
  });
1108
1341
  if (clientBuilt) await this.fs.rm(clientIndexPath);
1109
1342
  }
1110
1343
  });
1111
1344
  await copyAssets({
1345
+ alepha,
1112
1346
  root,
1113
1347
  entry: `${distDir}/index.js`,
1114
1348
  distDir,
@@ -1116,31 +1350,24 @@ var BuildCommand = class {
1116
1350
  });
1117
1351
  if (hasClient) {
1118
1352
  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
- }
1353
+ if (sitemapHostname) await generateSitemap({
1354
+ alepha,
1355
+ baseUrl: sitemapHostname,
1356
+ output: `${distDir}/${publicDir}/sitemap.xml`,
1357
+ run
1127
1358
  });
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
- }
1359
+ await prerenderPages({
1360
+ alepha,
1361
+ dist: `${distDir}/${publicDir}`,
1362
+ compress: true,
1363
+ run
1137
1364
  });
1138
1365
  }
1139
1366
  if (flags.vercel || options.vercel) await run({
1140
1367
  name: "add Vercel config",
1141
1368
  handler: () => generateVercel({
1142
1369
  distDir,
1143
- clientDir,
1370
+ clientDir: publicDir,
1144
1371
  config: options.vercel
1145
1372
  })
1146
1373
  });
@@ -1528,6 +1755,237 @@ var DeployCommand = class {
1528
1755
  });
1529
1756
  };
1530
1757
 
1758
+ //#endregion
1759
+ //#region ../../src/cli/providers/ViteDevServerProvider.ts
1760
+ /**
1761
+ * Vite development server with Alepha integration.
1762
+ *
1763
+ * Architecture:
1764
+ * - Vite runs in middleware mode (no HTTP server)
1765
+ * - Alepha is the HTTP server via server:onRequest event
1766
+ * - Request flow: Page requests → Alepha SSR, Assets → Vite middleware
1767
+ *
1768
+ * HMR Strategy:
1769
+ * - Browser-only changes (CSS, client components) → Vite HMR (React Fast Refresh)
1770
+ * - Server-only changes → Restart Alepha → Full browser reload
1771
+ * - Shared changes → Restart Alepha → Let Vite HMR propagate
1772
+ *
1773
+ * Features:
1774
+ * - Automatic .env reload detection
1775
+ * - Error recovery on next file change
1776
+ * - Optimized module invalidation (only changed files + importers)
1777
+ */
1778
+ var ViteDevServerProvider = class {
1779
+ log = $logger();
1780
+ fs = $inject(FileSystemProvider);
1781
+ templateProvider = $inject(ViteTemplateProvider);
1782
+ server;
1783
+ options;
1784
+ alepha = null;
1785
+ hasError = false;
1786
+ changedFiles = /* @__PURE__ */ new Set();
1787
+ async init(options) {
1788
+ this.options = options;
1789
+ await this.createViteServer();
1790
+ return await this.loadAlepha(true);
1791
+ }
1792
+ async start() {
1793
+ await this.alepha?.start();
1794
+ }
1795
+ /**
1796
+ * Create the Vite server in middleware mode.
1797
+ */
1798
+ async createViteServer() {
1799
+ const { createServer } = await importVite();
1800
+ const viteReact = await importViteReact();
1801
+ const plugins = [];
1802
+ if (viteReact) plugins.push(viteReact());
1803
+ plugins.push(viteAlephaSsrPreload());
1804
+ plugins.push(this.createHmrPlugin());
1805
+ this.server = await createServer({
1806
+ root: this.options.root,
1807
+ plugins,
1808
+ server: { middlewareMode: true },
1809
+ appType: "custom",
1810
+ customLogger: {
1811
+ info: () => {},
1812
+ warn: this.log.warn.bind(this.log),
1813
+ error: this.log.error.bind(this.log),
1814
+ warnOnce: this.log.warn.bind(this.log),
1815
+ clearScreen: () => {},
1816
+ hasWarned: false,
1817
+ hasErrorLogged: () => false
1818
+ }
1819
+ });
1820
+ this.server.restart = async () => {
1821
+ const startTime = Date.now();
1822
+ try {
1823
+ this.hasError = true;
1824
+ await this.loadAlepha(false);
1825
+ await this.alepha?.start();
1826
+ this.log.debug(`Env reloaded in ${Date.now() - startTime}ms`);
1827
+ this.sendBrowserReload();
1828
+ } catch (err) {
1829
+ this.hasError = true;
1830
+ this.log.error("Reload failed", err);
1831
+ this.log.warn("Waiting for file changes to retry...");
1832
+ this.alepha = null;
1833
+ }
1834
+ };
1835
+ }
1836
+ /**
1837
+ * Vite plugin to handle HMR for Alepha.
1838
+ */
1839
+ createHmrPlugin() {
1840
+ return {
1841
+ name: "alepha-hmr",
1842
+ handleHotUpdate: async (ctx) => {
1843
+ if (ctx.file.includes("/.idea/")) return [];
1844
+ const firstModule = ctx.modules[0];
1845
+ const isBrowserOnly = firstModule && !firstModule._ssrModule;
1846
+ const isServerOnly = firstModule && !firstModule._clientModule;
1847
+ if (isBrowserOnly) return;
1848
+ const startTime = Date.now();
1849
+ try {
1850
+ this.changedFiles.add(ctx.file);
1851
+ await this.loadAlepha(false);
1852
+ await this.alepha?.start();
1853
+ this.log.debug(`Reloaded in ${Date.now() - startTime}ms`);
1854
+ if (isServerOnly) {
1855
+ this.sendBrowserReload();
1856
+ return [];
1857
+ }
1858
+ return;
1859
+ } catch (err) {
1860
+ this.hasError = true;
1861
+ this.log.error("Reload failed", err);
1862
+ this.log.warn("Waiting for file changes to retry...");
1863
+ this.alepha = null;
1864
+ return [];
1865
+ }
1866
+ }
1867
+ };
1868
+ }
1869
+ /**
1870
+ * Send browser reload signal via custom event.
1871
+ * Browser listens for 'alepha:reload' and does window.location.reload()
1872
+ */
1873
+ sendBrowserReload() {
1874
+ this.server.ws.send({
1875
+ type: "custom",
1876
+ event: "alepha:reload",
1877
+ data: {}
1878
+ });
1879
+ }
1880
+ /**
1881
+ * Setup environment variables for dev mode.
1882
+ */
1883
+ async setupEnvironment() {
1884
+ const { loadEnv } = await importVite();
1885
+ const env = loadEnv(process.env.NODE_ENV || "development", this.options.root, "");
1886
+ process.env.NODE_ENV ??= "development";
1887
+ process.env.VITE_ALEPHA_DEV = "true";
1888
+ process.env.SERVER_HOST ??= this.options.host?.toString() ?? "localhost";
1889
+ process.env.SERVER_PORT ??= String(this.options.port ?? (process.env.SERVER_PORT ? Number(process.env.SERVER_PORT) : 3e3));
1890
+ for (const [key, value] of Object.entries(env)) process.env[key] ??= value;
1891
+ }
1892
+ /**
1893
+ * Load or reload the Alepha instance.
1894
+ */
1895
+ async loadAlepha(isInitialLoad = false) {
1896
+ if (this.alepha) {
1897
+ await this.alepha.stop().catch((err) => this.log.warn("Error stopping Alepha", err));
1898
+ this.alepha = null;
1899
+ }
1900
+ if (isInitialLoad || this.hasError) this.server.moduleGraph.invalidateAll();
1901
+ else this.invalidateModulesWithImporters();
1902
+ this.changedFiles.clear();
1903
+ const envSnapshot = { ...process.env };
1904
+ await this.setupEnvironment();
1905
+ await this.server.ssrLoadModule(this.options.entry.server);
1906
+ const alepha = globalThis.__alepha;
1907
+ if (!alepha) throw new AlephaError("Alepha instance not found after loading entry module");
1908
+ this.alepha = alepha;
1909
+ await this.setupAlepha();
1910
+ this.hasError = false;
1911
+ process.env = envSnapshot;
1912
+ return alepha;
1913
+ }
1914
+ hasReact() {
1915
+ try {
1916
+ this.alepha?.inject("ReactServerProvider");
1917
+ return true;
1918
+ } catch {
1919
+ return false;
1920
+ }
1921
+ }
1922
+ /**
1923
+ * Setup Alepha instance with Vite middleware and template.
1924
+ */
1925
+ async setupAlepha() {
1926
+ if (!this.alepha || !this.hasReact()) return;
1927
+ const template = await this.server.transformIndexHtml("/", this.templateProvider.generateIndexHtml(this.options.entry));
1928
+ this.alepha.store.set("alepha.react.server.template", template);
1929
+ this.alepha.events.on("server:onRequest", {
1930
+ priority: "first",
1931
+ callback: async ({ request }) => {
1932
+ const node = request.raw.node;
1933
+ if (!node || this.isPageRequest(node.req)) return;
1934
+ if (await this.runViteMiddleware(node.req, node.res, request)) {
1935
+ request.reply.status = node.res.statusCode || 200;
1936
+ request.reply.body = null;
1937
+ }
1938
+ }
1939
+ });
1940
+ }
1941
+ /**
1942
+ * Check if request is for an HTML page (not an asset).
1943
+ */
1944
+ isPageRequest(req) {
1945
+ const url = req.url || "/";
1946
+ if (url === "/" || url === "/index.html") return true;
1947
+ if (url.startsWith("/@") || url.startsWith("/__vite")) return false;
1948
+ if (/\.\w+$/.test(url.split("?")[0])) return false;
1949
+ return true;
1950
+ }
1951
+ /**
1952
+ * Run Vite middleware and detect if it handled the request.
1953
+ */
1954
+ async runViteMiddleware(req, res, ctx) {
1955
+ return new Promise((resolve) => {
1956
+ let resolved = false;
1957
+ const done = (handled) => {
1958
+ if (resolved) return;
1959
+ resolved = true;
1960
+ if (handled) ctx.metadata.vite = true;
1961
+ resolve(handled);
1962
+ };
1963
+ res.on("finish", () => done(true));
1964
+ res.on("close", () => res.headersSent && done(true));
1965
+ this.server.middlewares(req, res, () => done(false));
1966
+ setImmediate(() => {
1967
+ if (res.headersSent || res.writableEnded) done(true);
1968
+ });
1969
+ });
1970
+ }
1971
+ /**
1972
+ * Invalidate modules and all their importers.
1973
+ */
1974
+ invalidateModulesWithImporters() {
1975
+ const invalidated = /* @__PURE__ */ new Set();
1976
+ const queue = [...this.changedFiles];
1977
+ while (queue.length > 0) {
1978
+ const file = queue.pop();
1979
+ if (invalidated.has(file)) continue;
1980
+ const mod = this.server.moduleGraph.getModuleById(file);
1981
+ if (!mod) continue;
1982
+ this.server.moduleGraph.invalidateModule(mod);
1983
+ invalidated.add(file);
1984
+ for (const importer of mod.importers) if (importer.id && !invalidated.has(importer.id)) queue.push(importer.id);
1985
+ }
1986
+ }
1987
+ };
1988
+
1531
1989
  //#endregion
1532
1990
  //#region ../../src/cli/commands/dev.ts
1533
1991
  var DevCommand = class {
@@ -1537,6 +1995,8 @@ var DevCommand = class {
1537
1995
  pm = $inject(PackageManagerUtils);
1538
1996
  scaffolder = $inject(ProjectScaffolder);
1539
1997
  alepha = $inject(Alepha);
1998
+ viteDevServer = $inject(ViteDevServerProvider);
1999
+ boot = $inject(AppEntryProvider);
1540
2000
  /**
1541
2001
  * Will run the project in watch mode.
1542
2002
  *
@@ -1546,53 +2006,21 @@ var DevCommand = class {
1546
2006
  dev = $command({
1547
2007
  name: "dev",
1548
2008
  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);
2009
+ handler: async ({ root }) => {
2010
+ const [expo, react] = await Promise.all([this.pm.hasExpo(root), this.pm.hasReact(root)]);
1555
2011
  await this.scaffolder.ensureConfig(root, { tsconfigJson: true });
1556
2012
  if (expo) {
1557
2013
  await this.utils.exec("expo start");
1558
2014
  return;
1559
2015
  }
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;
1569
- }
2016
+ const entry = await this.boot.getAppEntry(root);
2017
+ this.log.debug("Entry file found", { entry });
1570
2018
  await this.pm.ensureDependency(root, "vite", { exec: (cmd, opts) => this.utils.exec(cmd, opts) });
1571
- await devServer();
1572
- }
1573
- });
1574
- async isBunProject(root) {
1575
- if (this.alepha.isBun()) return true;
1576
- return this.fs.exists(this.fs.join(root, "bun.lock"));
1577
- }
1578
- async isFullstackProject(root) {
1579
- return this.fs.exists(this.fs.join(root, "index.html"));
1580
- }
1581
- };
1582
-
1583
- //#endregion
1584
- //#region ../../src/cli/commands/format.ts
1585
- var FormatCommand = class {
1586
- utils = $inject(AlephaCliUtils);
1587
- pm = $inject(PackageManagerUtils);
1588
- scaffolder = $inject(ProjectScaffolder);
1589
- format = $command({
1590
- name: "format",
1591
- description: "Format the codebase using Biome",
1592
- 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");
2019
+ await this.viteDevServer.init({
2020
+ root,
2021
+ entry
2022
+ });
2023
+ await this.viteDevServer.start();
1596
2024
  }
1597
2025
  });
1598
2026
  };
@@ -1627,7 +2055,7 @@ const DEFAULT_IGNORE = [
1627
2055
  * ```
1628
2056
  */
1629
2057
  const changelogOptions = $atom({
1630
- name: "alepha.changelog",
2058
+ name: "alepha.cli.changelog.options",
1631
2059
  schema: t.object({ ignore: t.optional(t.array(t.string())) }),
1632
2060
  default: { ignore: DEFAULT_IGNORE }
1633
2061
  });
@@ -1835,7 +2263,7 @@ var GenEnvCommand = class {
1835
2263
  if (value.description) dotEnvFile += `# ${value.description.split("\n").join("\n# ")}\n`;
1836
2264
  if (value.required && !value.default) dotEnvFile += `# (required)\n`;
1837
2265
  if (value.enum) dotEnvFile += `# Possible values: ${value.enum.join(", ")}\n`;
1838
- dotEnvFile += `${key}=${value.default || ""}\n\n`;
2266
+ dotEnvFile += `#${key}=${value.default || ""}\n\n`;
1839
2267
  }
1840
2268
  if (flags.out) await this.fs.writeFile(this.fs.join(root, flags.out), dotEnvFile);
1841
2269
  else this.log.info(dotEnvFile);
@@ -1935,35 +2363,41 @@ var InitCommand = class {
1935
2363
  pnpm: t.optional(t.boolean({ description: "Use pnpm package manager" })),
1936
2364
  npm: t.optional(t.boolean({ description: "Use npm package manager" })),
1937
2365
  bun: t.optional(t.boolean({ description: "Use Bun package manager" })),
1938
- web: t.optional(t.boolean({
2366
+ react: t.optional(t.boolean({
1939
2367
  aliases: ["r"],
1940
2368
  description: "Include Alepha React dependencies"
1941
2369
  })),
1942
- admin: t.optional(t.boolean({ description: "Include Alepha UI dependencies" })),
1943
- test: t.optional(t.boolean({ description: "Include Vitest and create test directory" }))
2370
+ ui: t.optional(t.boolean({ description: "Include Alepha UI dependencies" })),
2371
+ test: t.optional(t.boolean({ description: "Include Vitest and create test directory" })),
2372
+ force: t.optional(t.boolean({
2373
+ aliases: ["f"],
2374
+ description: "Override existing files"
2375
+ }))
1944
2376
  }),
1945
2377
  handler: async ({ run, flags, root, args }) => {
1946
- if (flags.admin) flags.web = true;
2378
+ if (flags.react) flags.ui = true;
1947
2379
  if (args) {
1948
2380
  root = this.fs.join(root, args);
1949
2381
  await this.fs.mkdir(root);
1950
2382
  }
1951
2383
  const isExpo = await this.pm.hasExpo(root);
2384
+ const force = !!flags.force;
1952
2385
  await run({
1953
2386
  name: "ensuring configuration files",
1954
2387
  handler: async () => {
1955
2388
  await this.scaffolder.ensureConfig(root, {
2389
+ force,
1956
2390
  tsconfigJson: true,
1957
2391
  packageJson: flags,
1958
2392
  biomeJson: true,
1959
2393
  editorconfig: true,
1960
- indexHtml: !!flags.web && !isExpo,
2394
+ indexHtml: !!flags.react && !isExpo,
1961
2395
  claudeMd: flags.agent ? {
1962
- react: !!flags.web,
1963
- ui: !!flags.admin
2396
+ react: !!flags.react,
2397
+ ui: !!flags.ui
1964
2398
  } : false
1965
2399
  });
1966
- if (!flags.web) await this.scaffolder.ensureApiProject(root);
2400
+ if (!flags.react) await this.scaffolder.ensureApiProject(root, { force });
1967
2401
  }
1968
2402
  });
1969
2403
  const pmName = await this.pm.getPackageManager(root, flags);
@@ -2009,7 +2443,7 @@ var LintCommand = class {
2009
2443
  handler: async ({ root }) => {
2010
2444
  await this.scaffolder.ensureConfig(root, { biomeJson: true });
2011
2445
  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");
2446
+ await this.utils.exec("biome check --fix");
2013
2447
  }
2014
2448
  });
2015
2449
  };
@@ -2162,7 +2596,6 @@ const AlephaCli = $module({
2162
2596
  DbCommand,
2163
2597
  DeployCommand,
2164
2598
  DevCommand,
2165
- FormatCommand,
2166
2599
  InitCommand,
2167
2600
  LintCommand,
2168
2601
  RootCommand,
@@ -2170,6 +2603,7 @@ const AlephaCli = $module({
2170
2603
  TypecheckCommand,
2171
2604
  VerifyCommand,
2172
2605
  GenCommand,
2606
+ AppEntryProvider,
2173
2607
  GitProvider
2174
2608
  ]
2175
2609
  });
@@ -2232,6 +2666,7 @@ var AlephaPackageBuilderCli = class {
2232
2666
  sourcemap: true,
2233
2667
  fixedExtension: false,
2234
2668
  platform: "node",
2669
+ inlineOnly: false,
2235
2670
  external,
2236
2671
  dts: { sourcemap: true }
2237
2672
  });
@@ -2241,6 +2676,7 @@ var AlephaPackageBuilderCli = class {
2241
2676
  platform: "neutral",
2242
2677
  sourcemap: true,
2243
2678
  dts: false,
2679
+ inlineOnly: false,
2244
2680
  external
2245
2681
  });
2246
2682
  if (item.browser) entries.push({
@@ -2249,6 +2685,7 @@ var AlephaPackageBuilderCli = class {
2249
2685
  platform: "browser",
2250
2686
  sourcemap: true,
2251
2687
  dts: false,
2688
+ inlineOnly: false,
2252
2689
  external
2253
2690
  });
2254
2691
  if (item.bun) entries.push({
@@ -2258,6 +2695,7 @@ var AlephaPackageBuilderCli = class {
2258
2695
  sourcemap: true,
2259
2696
  fixedExtension: false,
2260
2697
  dts: false,
2698
+ inlineOnly: false,
2261
2699
  external
2262
2700
  });
2263
2701
  const config = this.fs.join(tmpDir, `tsdown-${item.name.replace("/", "-")}.config.js`);
@@ -2281,6 +2719,7 @@ var AlephaPackageBuilderCli = class {
2281
2719
  }
2282
2720
  });
2283
2721
  };
2722
+ var AlephaPackageBuilderCli_default = AlephaPackageBuilderCli;
2284
2723
  async function getAllFiles(dir) {
2285
2724
  const files = [];
2286
2725
  async function scan(currentDir) {
@@ -2384,6 +2823,7 @@ const defineConfig = (runConfig) => {
2384
2823
  if (config.services) for (const it of config.services) alepha.with(it);
2385
2824
  if (config.env) for (const [key, value] of Object.entries(config.env)) process.env[key] = String(value);
2386
2825
  if (config.build) alepha.set(buildOptions, config.build);
2826
+ if (config.entry) alepha.set(appEntryOptions, config.entry);
2387
2827
  return { ...config.commands };
2388
2828
  };
2389
2829
  };
@@ -2393,5 +2833,5 @@ const defineConfig = (runConfig) => {
2393
2833
  const defineAlephaConfig = defineConfig;
2394
2834
 
2395
2835
  //#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 };
2836
+ export { AlephaCli, AlephaCliUtils, AlephaPackageBuilderCli_default as AlephaPackageBuilderCli, AppEntryProvider, BuildCommand, ChangelogCommand, CleanCommand, DEFAULT_IGNORE, DbCommand, DeployCommand, DevCommand, GitMessageParser, GitProvider, InitCommand, LintCommand, OpenApiCommand, RootCommand, TestCommand, TypecheckCommand, VerifyCommand, analyzeModules, changelogOptions, defineAlephaConfig, defineConfig, version };
2397
2837
  //# sourceMappingURL=index.js.map