alepha 0.15.2 → 0.15.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 (180) hide show
  1. package/README.md +68 -80
  2. package/dist/api/audits/index.d.ts.map +1 -1
  3. package/dist/api/audits/index.js +8 -0
  4. package/dist/api/audits/index.js.map +1 -1
  5. package/dist/api/files/index.d.ts +170 -170
  6. package/dist/api/files/index.d.ts.map +1 -1
  7. package/dist/api/files/index.js +1 -0
  8. package/dist/api/files/index.js.map +1 -1
  9. package/dist/api/jobs/index.d.ts.map +1 -1
  10. package/dist/api/jobs/index.js +3 -0
  11. package/dist/api/jobs/index.js.map +1 -1
  12. package/dist/api/notifications/index.browser.js +1 -0
  13. package/dist/api/notifications/index.browser.js.map +1 -1
  14. package/dist/api/notifications/index.js +1 -0
  15. package/dist/api/notifications/index.js.map +1 -1
  16. package/dist/api/parameters/index.d.ts +260 -260
  17. package/dist/api/parameters/index.d.ts.map +1 -1
  18. package/dist/api/parameters/index.js +10 -0
  19. package/dist/api/parameters/index.js.map +1 -1
  20. package/dist/api/users/index.d.ts +12 -1
  21. package/dist/api/users/index.d.ts.map +1 -1
  22. package/dist/api/users/index.js +18 -2
  23. package/dist/api/users/index.js.map +1 -1
  24. package/dist/batch/index.d.ts +4 -4
  25. package/dist/bucket/index.d.ts +8 -0
  26. package/dist/bucket/index.d.ts.map +1 -1
  27. package/dist/bucket/index.js +7 -2
  28. package/dist/bucket/index.js.map +1 -1
  29. package/dist/cli/index.d.ts +196 -74
  30. package/dist/cli/index.d.ts.map +1 -1
  31. package/dist/cli/index.js +234 -50
  32. package/dist/cli/index.js.map +1 -1
  33. package/dist/command/index.d.ts +10 -0
  34. package/dist/command/index.d.ts.map +1 -1
  35. package/dist/command/index.js +67 -13
  36. package/dist/command/index.js.map +1 -1
  37. package/dist/core/index.browser.js +28 -21
  38. package/dist/core/index.browser.js.map +1 -1
  39. package/dist/core/index.d.ts.map +1 -1
  40. package/dist/core/index.js +28 -21
  41. package/dist/core/index.js.map +1 -1
  42. package/dist/core/index.native.js +28 -21
  43. package/dist/core/index.native.js.map +1 -1
  44. package/dist/email/index.d.ts +21 -13
  45. package/dist/email/index.d.ts.map +1 -1
  46. package/dist/email/index.js +10561 -4
  47. package/dist/email/index.js.map +1 -1
  48. package/dist/lock/core/index.d.ts +6 -1
  49. package/dist/lock/core/index.d.ts.map +1 -1
  50. package/dist/lock/core/index.js +9 -1
  51. package/dist/lock/core/index.js.map +1 -1
  52. package/dist/mcp/index.d.ts +5 -5
  53. package/dist/orm/index.bun.js +32 -16
  54. package/dist/orm/index.bun.js.map +1 -1
  55. package/dist/orm/index.d.ts +4 -1
  56. package/dist/orm/index.d.ts.map +1 -1
  57. package/dist/orm/index.js +34 -22
  58. package/dist/orm/index.js.map +1 -1
  59. package/dist/react/auth/index.browser.js +2 -1
  60. package/dist/react/auth/index.browser.js.map +1 -1
  61. package/dist/react/auth/index.js +2 -1
  62. package/dist/react/auth/index.js.map +1 -1
  63. package/dist/react/core/index.d.ts +3 -3
  64. package/dist/react/router/index.browser.js +9 -15
  65. package/dist/react/router/index.browser.js.map +1 -1
  66. package/dist/react/router/index.d.ts +305 -407
  67. package/dist/react/router/index.d.ts.map +1 -1
  68. package/dist/react/router/index.js +581 -781
  69. package/dist/react/router/index.js.map +1 -1
  70. package/dist/scheduler/index.d.ts +13 -1
  71. package/dist/scheduler/index.d.ts.map +1 -1
  72. package/dist/scheduler/index.js +42 -4
  73. package/dist/scheduler/index.js.map +1 -1
  74. package/dist/security/index.d.ts +42 -42
  75. package/dist/security/index.d.ts.map +1 -1
  76. package/dist/security/index.js +8 -7
  77. package/dist/security/index.js.map +1 -1
  78. package/dist/server/auth/index.d.ts +167 -167
  79. package/dist/server/compress/index.d.ts.map +1 -1
  80. package/dist/server/compress/index.js +1 -0
  81. package/dist/server/compress/index.js.map +1 -1
  82. package/dist/server/health/index.d.ts +17 -17
  83. package/dist/server/links/index.d.ts +39 -39
  84. package/dist/server/links/index.js +1 -1
  85. package/dist/server/links/index.js.map +1 -1
  86. package/dist/server/static/index.js +7 -2
  87. package/dist/server/static/index.js.map +1 -1
  88. package/dist/server/swagger/index.d.ts +8 -0
  89. package/dist/server/swagger/index.d.ts.map +1 -1
  90. package/dist/server/swagger/index.js +7 -2
  91. package/dist/server/swagger/index.js.map +1 -1
  92. package/dist/sms/index.d.ts +8 -0
  93. package/dist/sms/index.d.ts.map +1 -1
  94. package/dist/sms/index.js +7 -2
  95. package/dist/sms/index.js.map +1 -1
  96. package/dist/system/index.browser.js +734 -12
  97. package/dist/system/index.browser.js.map +1 -1
  98. package/dist/system/index.d.ts +8 -0
  99. package/dist/system/index.d.ts.map +1 -1
  100. package/dist/system/index.js +7 -2
  101. package/dist/system/index.js.map +1 -1
  102. package/dist/vite/index.d.ts +3 -2
  103. package/dist/vite/index.d.ts.map +1 -1
  104. package/dist/vite/index.js +42 -8
  105. package/dist/vite/index.js.map +1 -1
  106. package/dist/websocket/index.d.ts +34 -34
  107. package/dist/websocket/index.d.ts.map +1 -1
  108. package/package.json +9 -4
  109. package/src/api/audits/controllers/AdminAuditController.ts +8 -0
  110. package/src/api/files/controllers/AdminFileStatsController.ts +1 -0
  111. package/src/api/jobs/controllers/AdminJobController.ts +3 -0
  112. package/src/api/logs/TODO.md +13 -10
  113. package/src/api/notifications/controllers/AdminNotificationController.ts +1 -0
  114. package/src/api/parameters/controllers/AdminConfigController.ts +10 -0
  115. package/src/api/users/controllers/AdminIdentityController.ts +3 -0
  116. package/src/api/users/controllers/AdminSessionController.ts +3 -0
  117. package/src/api/users/controllers/AdminUserController.ts +5 -0
  118. package/src/cli/apps/AlephaPackageBuilderCli.ts +9 -0
  119. package/src/cli/atoms/buildOptions.ts +99 -9
  120. package/src/cli/commands/build.ts +150 -32
  121. package/src/cli/commands/db.ts +5 -7
  122. package/src/cli/commands/init.spec.ts +50 -6
  123. package/src/cli/commands/init.ts +28 -5
  124. package/src/cli/providers/ViteDevServerProvider.ts +31 -9
  125. package/src/cli/services/AlephaCliUtils.ts +16 -0
  126. package/src/cli/services/PackageManagerUtils.ts +2 -0
  127. package/src/cli/services/ProjectScaffolder.spec.ts +97 -0
  128. package/src/cli/services/ProjectScaffolder.ts +28 -6
  129. package/src/cli/templates/agentMd.ts +6 -1
  130. package/src/cli/templates/apiAppSecurityTs.ts +11 -0
  131. package/src/cli/templates/apiIndexTs.ts +18 -4
  132. package/src/cli/templates/webAppRouterTs.ts +25 -1
  133. package/src/cli/templates/webHelloComponentTsx.ts +15 -5
  134. package/src/command/helpers/Runner.spec.ts +135 -0
  135. package/src/command/helpers/Runner.ts +4 -1
  136. package/src/command/providers/CliProvider.spec.ts +325 -0
  137. package/src/command/providers/CliProvider.ts +117 -7
  138. package/src/core/Alepha.ts +32 -25
  139. package/src/email/index.workerd.ts +36 -0
  140. package/src/email/providers/WorkermailerEmailProvider.ts +221 -0
  141. package/src/lock/core/primitives/$lock.ts +13 -1
  142. package/src/orm/index.bun.ts +1 -1
  143. package/src/orm/index.ts +2 -6
  144. package/src/orm/providers/drivers/BunSqliteProvider.ts +4 -1
  145. package/src/orm/providers/drivers/CloudflareD1Provider.ts +57 -30
  146. package/src/orm/providers/drivers/DatabaseProvider.ts +9 -1
  147. package/src/orm/providers/drivers/NodeSqliteProvider.ts +4 -1
  148. package/src/react/auth/services/ReactAuth.ts +3 -1
  149. package/src/react/router/atoms/ssrManifestAtom.ts +7 -0
  150. package/src/react/router/hooks/useActive.ts +1 -1
  151. package/src/react/router/hooks/useRouter.ts +1 -1
  152. package/src/react/router/index.ts +4 -0
  153. package/src/react/router/primitives/$page.browser.spec.tsx +24 -24
  154. package/src/react/router/primitives/$page.spec.tsx +0 -32
  155. package/src/react/router/primitives/$page.ts +6 -14
  156. package/src/react/router/providers/ReactBrowserProvider.ts +6 -3
  157. package/src/react/router/providers/ReactPageProvider.ts +1 -1
  158. package/src/react/router/providers/ReactPreloadProvider.spec.ts +142 -0
  159. package/src/react/router/providers/ReactPreloadProvider.ts +85 -0
  160. package/src/react/router/providers/ReactServerProvider.ts +21 -82
  161. package/src/react/router/providers/ReactServerTemplateProvider.spec.ts +210 -0
  162. package/src/react/router/providers/ReactServerTemplateProvider.ts +228 -665
  163. package/src/react/router/providers/SSRManifestProvider.ts +7 -0
  164. package/src/react/router/services/ReactRouter.ts +13 -13
  165. package/src/scheduler/index.workerd.ts +43 -0
  166. package/src/scheduler/providers/CronProvider.ts +53 -6
  167. package/src/scheduler/providers/WorkerdCronProvider.ts +102 -0
  168. package/src/security/__tests__/ServerSecurityProvider.spec.ts +77 -0
  169. package/src/security/providers/ServerSecurityProvider.ts +30 -22
  170. package/src/server/compress/providers/ServerCompressProvider.ts +6 -0
  171. package/src/server/core/providers/NodeHttpServerProvider.spec.ts +9 -3
  172. package/src/server/links/providers/ServerLinksProvider.spec.ts +332 -0
  173. package/src/server/links/providers/ServerLinksProvider.ts +1 -1
  174. package/src/system/index.browser.ts +25 -0
  175. package/src/system/index.workerd.ts +1 -0
  176. package/src/system/providers/FileSystemProvider.ts +8 -0
  177. package/src/system/providers/NodeFileSystemProvider.ts +11 -2
  178. package/src/vite/tasks/buildServer.ts +2 -12
  179. package/src/vite/tasks/generateCloudflare.ts +47 -8
  180. package/src/vite/tasks/generateDocker.ts +4 -0
package/dist/cli/index.js CHANGED
@@ -1370,8 +1370,13 @@ var NodeFileSystemProvider = class {
1370
1370
  * await fs.mkdir("/tmp/mydir", { mode: 0o755 });
1371
1371
  * ```
1372
1372
  */
1373
- async mkdir(path, options) {
1374
- await mkdir(path, options);
1373
+ async mkdir(path, options = {}) {
1374
+ const p = mkdir(path, {
1375
+ recursive: options.recursive ?? true,
1376
+ mode: options.mode
1377
+ });
1378
+ if (options.force === false) await p;
1379
+ else await p.catch(() => {});
1375
1380
  }
1376
1381
  /**
1377
1382
  * Lists files in a directory.
@@ -1912,13 +1917,24 @@ $atom$1[KIND] = "atom";
1912
1917
  * Build options atom for CLI build command.
1913
1918
  *
1914
1919
  * Defines the available build configuration options with their defaults.
1915
- * Options can be overridden via vite.config.ts or CLI flags.
1920
+ * Options can be overridden via alepha.config.ts or CLI flags.
1916
1921
  */
1917
1922
  const buildOptions = $atom$1({
1918
1923
  name: "alepha.cli.build.options",
1919
1924
  description: "Build configuration options",
1920
1925
  schema: t.object({
1921
1926
  stats: t.optional(t.boolean({ default: false })),
1927
+ target: t.optional(t.enum([
1928
+ "bare",
1929
+ "docker",
1930
+ "vercel",
1931
+ "cloudflare"
1932
+ ])),
1933
+ runtime: t.optional(t.enum([
1934
+ "node",
1935
+ "bun",
1936
+ "workerd"
1937
+ ])),
1922
1938
  vercel: t.optional(t.object({
1923
1939
  projectName: t.optional(t.string()),
1924
1940
  orgId: t.optional(t.string()),
@@ -1930,8 +1946,13 @@ const buildOptions = $atom$1({
1930
1946
  })),
1931
1947
  cloudflare: t.optional(t.object({ config: t.optional(t.json()) })),
1932
1948
  docker: t.optional(t.object({
1933
- image: t.optional(t.string({ default: "node:24-alpine" })),
1934
- command: t.optional(t.string({ default: "node" }))
1949
+ from: t.optional(t.string()),
1950
+ command: t.optional(t.string()),
1951
+ image: t.optional(t.object({
1952
+ tag: t.string(),
1953
+ args: t.optional(t.string()),
1954
+ oci: t.optional(t.boolean())
1955
+ }))
1935
1956
  })),
1936
1957
  sitemap: t.optional(t.object({ hostname: t.string() }))
1937
1958
  }),
@@ -2205,6 +2226,18 @@ ${models.map((it) => `export const ${it} = models["${it}"];`).join("\n")}
2205
2226
  isInstalledAsync(cmd) {
2206
2227
  return this.shell.isInstalled(cmd);
2207
2228
  }
2229
+ /**
2230
+ * Get the current git revision (commit SHA).
2231
+ *
2232
+ * @returns The short commit SHA or "unknown" if not in a git repo
2233
+ */
2234
+ async getGitRevision() {
2235
+ try {
2236
+ return (await this.shell.run("git rev-parse --short HEAD", { capture: true })).trim();
2237
+ } catch {
2238
+ return "unknown";
2239
+ }
2240
+ }
2208
2241
  };
2209
2242
 
2210
2243
  //#endregion
@@ -2220,16 +2253,19 @@ var devDependencies = {
2220
2253
  "@types/react": "^19.2.10",
2221
2254
  "@types/react-dom": "^19.2.3",
2222
2255
  "@types/ws": "^8.18.1",
2256
+ "@vitejs/plugin-react": "^5.1.2",
2223
2257
  "cron-schedule": "^6.0.0",
2224
2258
  "jose": "^6.1.3",
2225
2259
  "jsdom": "^27.4.0",
2260
+ "nodemailer": "^7.0.13",
2226
2261
  "openid-client": "^6.8.1",
2227
2262
  "prom-client": "^15.1.3",
2228
2263
  "react": "^19.2.4",
2229
2264
  "react-dom": "^19.2.4",
2230
2265
  "swagger-ui-dist": "^5.31.0",
2231
2266
  "tsdown": "^0.20.1",
2232
- "vitest": "^4.0.18"
2267
+ "vitest": "^4.0.18",
2268
+ "worker-mailer": "^1.2.1"
2233
2269
  };
2234
2270
 
2235
2271
  //#endregion
@@ -2477,6 +2513,7 @@ var PackageManagerUtils = class {
2477
2513
  if (modes.react) {
2478
2514
  dependencies.react = alephaDeps.react;
2479
2515
  dependencies["react-dom"] = alephaDeps["react-dom"];
2516
+ devDependencies$1["@vitejs/plugin-react"] = alephaDeps["@vitejs/plugin-react"];
2480
2517
  devDependencies$1["@types/react"] = alephaDeps["@types/react"];
2481
2518
  }
2482
2519
  return {
@@ -2783,7 +2820,26 @@ alepha build # Build the project
2783
2820
  ## Source Code Access
2784
2821
 
2785
2822
  Full framework source available at \`node_modules/alepha/src/\`.
2786
- Read primitives directly when you need implementation details.
2823
+
2824
+ **IMPORTANT:** When answering questions about Alepha primitives, APIs, or internals:
2825
+ 1. ALWAYS read the local source code first at \`node_modules/alepha/src/\` or \`node_modules/@alepha/ui/src/\` for UI-related questions
2826
+ 2. Use \`Glob\` to find relevant files: \`node_modules/alepha/src/**/primitives/$<name>.ts\`
2827
+ 3. Read the implementation AND the \`.spec.ts\` test files for usage examples
2828
+ 4. Use external documentation as a fallback if source code is insufficient
2829
+ `.trim();
2830
+ };
2831
+
2832
+ //#endregion
2833
+ //#region ../../src/cli/templates/apiAppSecurityTs.ts
2834
+ const apiAppSecurityTs = () => {
2835
+ return `
2836
+ import { $realm } from "alepha/api/users";
2837
+
2838
+ export class AppSecurity {
2839
+ users = $realm({
2840
+ // configure your realm here
2841
+ });
2842
+ }
2787
2843
  `.trim();
2788
2844
  };
2789
2845
 
@@ -2811,14 +2867,21 @@ export class HelloController {
2811
2867
  //#endregion
2812
2868
  //#region ../../src/cli/templates/apiIndexTs.ts
2813
2869
  const apiIndexTs = (options = {}) => {
2814
- const { appName = "app" } = options;
2870
+ const { appName = "app", auth = false } = options;
2871
+ const imports = ["import { $module } from \"alepha\";"];
2872
+ const services = [];
2873
+ if (auth) {
2874
+ imports.push("import { AppSecurity } from \"./AppSecurity.ts\";");
2875
+ services.push("AppSecurity");
2876
+ }
2877
+ imports.push("import { HelloController } from \"./controllers/HelloController.ts\";");
2878
+ services.push("HelloController");
2815
2879
  return `
2816
- import { $module } from "alepha";
2817
- import { HelloController } from "./controllers/HelloController.ts";
2880
+ ${imports.join("\n")}
2818
2881
 
2819
2882
  export const ApiModule = $module({
2820
2883
  name: "${appName}.api",
2821
- services: [HelloController],
2884
+ services: [${services.join(", ")}],
2822
2885
  });
2823
2886
  `.trim();
2824
2887
  };
@@ -3006,6 +3069,8 @@ const webAppRouterTs = (options) => {
3006
3069
  const imports = [];
3007
3070
  const classMembers = [];
3008
3071
  if (options.ui) imports.push("import { $ui } from \"@alepha/ui\";");
3072
+ if (options.auth) imports.push("import { $uiAuth } from \"@alepha/ui/auth\";");
3073
+ if (options.admin) imports.push("import { $uiAdmin } from \"@alepha/ui/admin\";");
3009
3074
  imports.push("import { $page } from \"alepha/react/router\";");
3010
3075
  if (options.api) {
3011
3076
  imports.push("import { $client } from \"alepha/server/links\";");
@@ -3014,6 +3079,8 @@ const webAppRouterTs = (options) => {
3014
3079
  }
3015
3080
  if (options.ui) {
3016
3081
  classMembers.push(" ui = $ui();");
3082
+ if (options.auth) classMembers.push(" uiAuth = $uiAuth();");
3083
+ if (options.admin) classMembers.push(" uiAdmin = $uiAdmin();");
3017
3084
  classMembers.push(` layout = $page({
3018
3085
  parent: this.ui.root,
3019
3086
  children: () => [this.home],
@@ -3037,7 +3104,12 @@ ${classMembers.join("\n\n")}
3037
3104
 
3038
3105
  //#endregion
3039
3106
  //#region ../../src/cli/templates/webHelloComponentTsx.ts
3040
- const webHelloComponentTsx = () => `import { useState } from "react";
3107
+ const webHelloComponentTsx = (options = {}) => {
3108
+ const imports = [];
3109
+ if (options.auth) imports.push("import { UserButton } from \"@alepha/ui/auth\";");
3110
+ imports.push("import { useState } from \"react\";");
3111
+ const userButton = options.auth ? "\n <UserButton />" : "";
3112
+ return `${imports.join("\n")}
3041
3113
 
3042
3114
  interface Props {
3043
3115
  message?: string;
@@ -3048,14 +3120,15 @@ const Hello = (props: Props) => {
3048
3120
  return (
3049
3121
  <div>
3050
3122
  <h1>{message}</h1>
3051
- <input value={message} onChange={e => setMessage(e.target.value)} />
3052
- <p>Edit this component in src/web/components/Hello.tsx</p>
3123
+ <input value={message} onChange={(e) => setMessage(e.target.value)} />
3124
+ <p>Edit this component in src/web/components/Hello.tsx</p>${userButton}
3053
3125
  </div>
3054
3126
  );
3055
3127
  };
3056
3128
 
3057
3129
  export default Hello;
3058
- `.trim();
3130
+ `;
3131
+ };
3059
3132
 
3060
3133
  //#endregion
3061
3134
  //#region ../../src/cli/templates/webIndexTs.ts
@@ -3097,7 +3170,7 @@ var ProjectScaffolder = class {
3097
3170
  * - Falls back to "app" if empty
3098
3171
  */
3099
3172
  getAppName(root) {
3100
- return basename(root).toLowerCase().replace(/[\s\-_]/g, "") || "app";
3173
+ return basename(root).toLowerCase().replace(/[\s\-_.\d]/g, "") || "app";
3101
3174
  }
3102
3175
  /**
3103
3176
  * Ensure all configuration files exist.
@@ -3175,8 +3248,12 @@ var ProjectScaffolder = class {
3175
3248
  async ensureApiProject(root, opts = {}) {
3176
3249
  const appName = this.getAppName(root);
3177
3250
  await this.fs.mkdir(this.fs.join(root, "src/api/controllers"), { recursive: true });
3178
- await this.ensureFile(root, "src/api/index.ts", apiIndexTs({ appName }), opts.force);
3251
+ await this.ensureFile(root, "src/api/index.ts", apiIndexTs({
3252
+ appName,
3253
+ auth: opts.auth
3254
+ }), opts.force);
3179
3255
  await this.ensureFile(root, "src/api/controllers/HelloController.ts", apiHelloControllerTs(), opts.force);
3256
+ if (opts.auth) await this.ensureFile(root, "src/api/AppSecurity.ts", apiAppSecurityTs(), opts.force);
3180
3257
  }
3181
3258
  /**
3182
3259
  * Ensure web/React project structure exists.
@@ -3193,9 +3270,11 @@ var ProjectScaffolder = class {
3193
3270
  await this.ensureFile(root, "src/web/index.ts", webIndexTs({ appName }), opts.force);
3194
3271
  await this.ensureFile(root, "src/web/AppRouter.ts", webAppRouterTs({
3195
3272
  api: opts.api,
3196
- ui: opts.ui
3273
+ ui: opts.ui,
3274
+ auth: opts.auth,
3275
+ admin: opts.admin
3197
3276
  }), opts.force);
3198
- await this.ensureFile(root, "src/web/components/Hello.tsx", webHelloComponentTsx(), opts.force);
3277
+ await this.ensureFile(root, "src/web/components/Hello.tsx", webHelloComponentTsx({ auth: opts.auth }), opts.force);
3199
3278
  await this.ensureFile(root, "src/main.browser.ts", mainBrowserTs(), opts.force);
3200
3279
  }
3201
3280
  /**
@@ -3243,17 +3322,55 @@ var BuildCommand = class {
3243
3322
  boot = $inject(AppEntryProvider);
3244
3323
  viteBuildProvider = $inject(ViteBuildProvider);
3245
3324
  options = $use(buildOptions);
3325
+ /**
3326
+ * Resolve the effective runtime based on target and explicit runtime flag.
3327
+ *
3328
+ * Some targets force a specific runtime:
3329
+ * - `cloudflare` always uses `workerd`
3330
+ * - `vercel` always uses `node`
3331
+ * - `docker` and bare deployments respect the runtime flag
3332
+ *
3333
+ * @throws {AlephaError} If an incompatible runtime is specified for a target
3334
+ */
3335
+ resolveRuntime(target, runtime) {
3336
+ if (target === "cloudflare") {
3337
+ if (runtime && runtime !== "workerd") throw new AlephaError(`Target 'cloudflare' requires 'workerd' runtime, got '${runtime}'`);
3338
+ return "workerd";
3339
+ }
3340
+ if (target === "vercel") {
3341
+ if (runtime && runtime !== "node") throw new AlephaError(`Target 'vercel' requires 'node' runtime, got '${runtime}'`);
3342
+ return "node";
3343
+ }
3344
+ return runtime ?? "node";
3345
+ }
3246
3346
  build = $command({
3247
3347
  name: "build",
3248
3348
  mode: "production",
3249
3349
  description: "Build the project for production",
3250
3350
  flags: t.object({
3251
3351
  stats: t.optional(t.boolean({ description: "Generate build stats report" })),
3252
- vercel: t.optional(t.boolean({ description: "Generate Vercel deployment configuration" })),
3253
- cloudflare: t.optional(t.boolean({ description: "Generate Cloudflare Workers configuration" })),
3254
- docker: t.optional(t.boolean({ description: "Generate Docker configuration" })),
3255
- sitemap: t.optional(t.text({ description: "Generate sitemap.xml with base URL" })),
3256
- bun: t.optional(t.boolean({ description: "Prioritize .bun.ts entry files for Bun runtime" }))
3352
+ target: t.optional(t.enum([
3353
+ "bare",
3354
+ "docker",
3355
+ "vercel",
3356
+ "cloudflare"
3357
+ ], {
3358
+ aliases: ["t"],
3359
+ description: "Deployment target"
3360
+ })),
3361
+ runtime: t.optional(t.enum([
3362
+ "node",
3363
+ "bun",
3364
+ "workerd"
3365
+ ], {
3366
+ aliases: ["r"],
3367
+ description: "JavaScript runtime"
3368
+ })),
3369
+ image: t.optional(t.union([t.boolean(), t.text()], {
3370
+ aliases: ["i"],
3371
+ description: "Build Docker image. Use -i for latest, -i=<version> for specific version"
3372
+ })),
3373
+ sitemap: t.optional(t.text({ description: "Generate sitemap.xml with base URL" }))
3257
3374
  }),
3258
3375
  handler: async ({ flags, run, root }) => {
3259
3376
  process.env.NODE_ENV = "production";
@@ -3266,6 +3383,13 @@ var BuildCommand = class {
3266
3383
  await run.rm("dist", { alias: "clean dist" });
3267
3384
  const options = this.options;
3268
3385
  await this.utils.loadEnv(root, [".env", ".env.production"]);
3386
+ const target = flags.target ?? options.target;
3387
+ const runtime = this.resolveRuntime(target, flags.runtime ?? options.runtime);
3388
+ if (flags.image && target !== "docker") throw new AlephaError(`Flag '--image' requires '--target=docker', got '${target ?? "bare"}'`);
3389
+ this.log.trace("Build configuration", {
3390
+ target,
3391
+ runtime
3392
+ });
3269
3393
  const stats = flags.stats ?? options.stats ?? false;
3270
3394
  let template = "";
3271
3395
  let hasClient = false;
@@ -3303,8 +3427,8 @@ var BuildCommand = class {
3303
3427
  const clientIndexPath = `${distDir}/${publicDir}/index.html`;
3304
3428
  const clientBuilt = await this.fs.exists(clientIndexPath);
3305
3429
  const conditions = [];
3306
- if (flags.bun) conditions.push("bun");
3307
- if (options.cloudflare) conditions.push("workerd");
3430
+ if (runtime === "bun") conditions.push("bun");
3431
+ else if (runtime === "workerd") conditions.push("workerd");
3308
3432
  await buildServer({
3309
3433
  silent: true,
3310
3434
  entry: entry.server,
@@ -3339,7 +3463,7 @@ var BuildCommand = class {
3339
3463
  run
3340
3464
  });
3341
3465
  }
3342
- if (flags.vercel || options.vercel) await run({
3466
+ if (target === "vercel") await run({
3343
3467
  name: "add Vercel config",
3344
3468
  handler: () => generateVercel({
3345
3469
  distDir,
@@ -3347,20 +3471,57 @@ var BuildCommand = class {
3347
3471
  config: options.vercel
3348
3472
  })
3349
3473
  });
3350
- if (flags.cloudflare || options.cloudflare) await run({
3474
+ if (target === "cloudflare") await run({
3351
3475
  name: "add Cloudflare config",
3352
3476
  handler: () => generateCloudflare({
3353
3477
  distDir,
3354
- config: options.cloudflare?.config
3355
- })
3356
- });
3357
- if (flags.docker || options.docker) await run({
3358
- name: "add Docker config",
3359
- handler: () => generateDocker({
3360
- distDir,
3361
- ...options.docker
3478
+ config: options.cloudflare?.config,
3479
+ alepha
3362
3480
  })
3363
3481
  });
3482
+ if (target === "docker") {
3483
+ const dockerFrom = options.docker?.from ?? (runtime === "bun" ? "oven/bun:alpine" : "node:24-alpine");
3484
+ const dockerCommand = options.docker?.command ?? (runtime === "bun" ? "bun" : "node");
3485
+ await run({
3486
+ name: "add Docker config",
3487
+ handler: () => generateDocker({
3488
+ distDir,
3489
+ image: dockerFrom,
3490
+ command: dockerCommand
3491
+ })
3492
+ });
3493
+ if (flags.image) {
3494
+ const imageConfig = options.docker?.image;
3495
+ const flagValue = typeof flags.image === "string" ? flags.image : null;
3496
+ let imageTag;
3497
+ let version;
3498
+ if (!flagValue) {
3499
+ if (!imageConfig?.tag) throw new AlephaError("Flag '--image' requires 'build.docker.image.tag' in config");
3500
+ version = "latest";
3501
+ imageTag = `${imageConfig.tag}:${version}`;
3502
+ } else if (flagValue.startsWith(":")) {
3503
+ if (!imageConfig?.tag) throw new AlephaError("Flag '--image=:version' requires 'build.docker.image.tag' in config");
3504
+ version = flagValue.slice(1);
3505
+ imageTag = `${imageConfig.tag}:${version}`;
3506
+ } else if (flagValue.includes(":")) {
3507
+ imageTag = flagValue;
3508
+ version = flagValue.split(":")[1];
3509
+ } else {
3510
+ imageTag = `${flagValue}:latest`;
3511
+ version = "latest";
3512
+ }
3513
+ const args = [];
3514
+ if (imageConfig?.args) args.push(imageConfig.args);
3515
+ if (imageConfig?.oci) {
3516
+ const revision = await this.utils.getGitRevision();
3517
+ const created = (/* @__PURE__ */ new Date()).toISOString();
3518
+ args.push(`--label "org.opencontainers.image.revision=${revision}"`);
3519
+ args.push(`--label "org.opencontainers.image.created=${created}"`);
3520
+ args.push(`--label "org.opencontainers.image.version=${version}"`);
3521
+ }
3522
+ await run(`docker build ${args.length > 0 ? `${args.join(" ")} ` : ""}-t ${imageTag} ${distDir}`, { alias: `docker build ${imageTag}` });
3523
+ }
3524
+ }
3364
3525
  }
3365
3526
  });
3366
3527
  };
@@ -3629,9 +3790,9 @@ var DbCommand = class {
3629
3790
  const accountId = process.env.CLOUDFLARE_ACCOUNT_ID;
3630
3791
  if (!accountId) throw new AlephaError("CLOUDFLARE_ACCOUNT_ID environment variable is not set. https://orm.drizzle.team/docs/guides/d1-http-with-drizzle-kit");
3631
3792
  const url = options.providerUrl;
3632
- if (!url.startsWith("cloudflare-d1://")) throw new AlephaError("D1 provider URL must start with 'cloudflare-d1://'.");
3633
- const [, databaseId] = url.replace("cloudflare-d1://", "").replace("cloudflare-d1:", "").split(":");
3634
- if (!databaseId) throw new AlephaError("Database ID is missing in the D1 provider URL. Cloudflare D1 URL format: cloudflare-d1://<database_name>:<database_id>");
3793
+ if (!url.startsWith("d1://")) throw new AlephaError("D1 provider URL must start with 'd1://'.");
3794
+ const [, databaseId] = url.replace("d1://", "").replace("d1:", "").split(":");
3795
+ if (!databaseId) throw new AlephaError("Database ID is missing in the D1 provider URL. Cloudflare D1 URL format: d1://<database_name>:<database_id>");
3635
3796
  config.dbCredentials = {
3636
3797
  accountId,
3637
3798
  databaseId,
@@ -3763,7 +3924,6 @@ var DeployCommand = class {
3763
3924
  var ViteDevServerProvider = class {
3764
3925
  log = $logger();
3765
3926
  fs = $inject(FileSystemProvider);
3766
- templateProvider = $inject(ViteUtils);
3767
3927
  server;
3768
3928
  options;
3769
3929
  alepha = null;
@@ -3905,12 +4065,12 @@ var ViteDevServerProvider = class {
3905
4065
  }
3906
4066
  }
3907
4067
  /**
3908
- * Setup Alepha instance with Vite middleware and template.
4068
+ * Setup Alepha instance with Vite middleware.
3909
4069
  */
3910
4070
  async setupAlepha() {
3911
4071
  if (!this.alepha || !this.hasReact()) return;
3912
- const template = await this.server.transformIndexHtml("/", this.templateProvider.generateIndexHtml(this.options.entry));
3913
- this.alepha.store.set("alepha.react.server.template", template);
4072
+ const devHead = await this.generateDevHead();
4073
+ this.alepha.store.set("alepha.react.ssr.manifest", { devHead });
3914
4074
  this.alepha.events.on("server:onRequest", {
3915
4075
  priority: "first",
3916
4076
  callback: async ({ request }) => {
@@ -3924,6 +4084,18 @@ var ViteDevServerProvider = class {
3924
4084
  });
3925
4085
  }
3926
4086
  /**
4087
+ * Generate dev head content by transforming a minimal HTML through Vite.
4088
+ * This lets Vite and all plugins inject their scripts (HMR client, React Fast Refresh, etc.).
4089
+ */
4090
+ async generateDevHead() {
4091
+ const { browser, style } = this.options.entry;
4092
+ const scripts = [];
4093
+ if (style) scripts.push(`<link rel="stylesheet" href="/${style}">`);
4094
+ if (browser) scripts.push(`<script type="module" src="/${browser}"><\/script>`);
4095
+ const minimalHtml = `<!DOCTYPE html><html><head>${scripts.join("\n")}</head><body></body></html>`;
4096
+ return (await this.server.transformIndexHtml("/", minimalHtml)).match(/<head>([\s\S]*?)<\/head>/i)?.[1]?.trim() ?? "";
4097
+ }
4098
+ /**
3927
4099
  * Check if request is for an HTML page (not an asset).
3928
4100
  */
3929
4101
  isPageRequest(req) {
@@ -4350,10 +4522,7 @@ var InitCommand = class {
4350
4522
  lowercase: true
4351
4523
  })),
4352
4524
  flags: t.object({
4353
- agent: t.optional(t.boolean({
4354
- aliases: ["a"],
4355
- description: "Add AI agent instructions (CLAUDE.md if claude CLI installed, else AGENTS.md)"
4356
- })),
4525
+ ai: t.optional(t.boolean({ description: "Add AI agent instructions (CLAUDE.md if claude CLI installed, else AGENTS.md)" })),
4357
4526
  pm: t.optional(t.enum([
4358
4527
  "yarn",
4359
4528
  "npm",
@@ -4366,6 +4535,8 @@ var InitCommand = class {
4366
4535
  description: "Include React dependencies and web module (src/web/)"
4367
4536
  })),
4368
4537
  ui: t.optional(t.boolean({ description: "Include @alepha/ui (components, auth portal, admin portal)" })),
4538
+ auth: t.optional(t.boolean({ description: "Include authentication (AppSecurity, $uiAuth). Implies --api --ui --react" })),
4539
+ admin: t.optional(t.boolean({ description: "Include admin portal ($uiAdmin). Implies --auth" })),
4369
4540
  test: t.optional(t.boolean({ description: "Include Vitest and create test directory" })),
4370
4541
  force: t.optional(t.boolean({
4371
4542
  aliases: ["f"],
@@ -4375,12 +4546,17 @@ var InitCommand = class {
4375
4546
  handler: async ({ run, flags, root, args }) => {
4376
4547
  if (args) {
4377
4548
  root = this.fs.join(root, args);
4378
- await this.fs.mkdir(root);
4549
+ await this.fs.mkdir(root, { force: true });
4550
+ }
4551
+ if (flags.admin) flags.auth = true;
4552
+ if (flags.auth) {
4553
+ flags.api = true;
4554
+ flags.ui = true;
4379
4555
  }
4380
4556
  if (flags.ui) flags.react = true;
4381
4557
  const workspace = await this.pm.getWorkspaceContext(root);
4382
4558
  let agentType = false;
4383
- if (flags.agent) agentType = await this.utils.isInstalledAsync("claude") ? "claude" : "agents";
4559
+ if (flags.ai) agentType = await this.utils.isInstalledAsync("claude") ? "claude" : "agents";
4384
4560
  const isExpo = await this.pm.hasExpo(root);
4385
4561
  const force = !!flags.force;
4386
4562
  await run({
@@ -4406,10 +4582,15 @@ var InitCommand = class {
4406
4582
  react: !!flags.react && !isExpo,
4407
4583
  force
4408
4584
  });
4409
- if (flags.api) await this.scaffolder.ensureApiProject(root, { force });
4585
+ if (flags.api) await this.scaffolder.ensureApiProject(root, {
4586
+ auth: !!flags.auth,
4587
+ force
4588
+ });
4410
4589
  if (flags.react && !isExpo) await this.scaffolder.ensureWebProject(root, {
4411
4590
  api: !!flags.api,
4412
4591
  ui: !!flags.ui,
4592
+ auth: !!flags.auth,
4593
+ admin: !!flags.admin,
4413
4594
  force
4414
4595
  });
4415
4596
  }
@@ -4648,6 +4829,7 @@ var AlephaPackageBuilderCli = class {
4648
4829
  pkgData.exports[path].types = `./src/${item.name}/index.ts`;
4649
4830
  if (item.native) pkgData.exports[path]["react-native"] = `./src/${item.name}/index.native.ts`;
4650
4831
  else if (item.browser) pkgData.exports[path]["react-native"] = `./src/${item.name}/index.browser.ts`;
4832
+ if (item.workerd) pkgData.exports[path].workerd = `./src/${item.name}/index.workerd.ts`;
4651
4833
  if (item.browser) pkgData.exports[path].browser = `./src/${item.name}/index.browser.ts`;
4652
4834
  if (item.bun) pkgData.exports[path].bun = `./src/${item.name}/index.bun.ts`;
4653
4835
  pkgData.exports[path].import = `./src/${item.name}/index.ts`;
@@ -4804,6 +4986,7 @@ async function analyzeModules(srcDir, packageName) {
4804
4986
  const hasNative = await fileExists(join(modulePath, "index.native.ts"));
4805
4987
  const hasBun = await fileExists(join(modulePath, "index.bun.ts"));
4806
4988
  const hasNode = await fileExists(join(modulePath, "index.node.ts"));
4989
+ const hasEdge = await fileExists(join(modulePath, "index.workerd.ts"));
4807
4990
  const files = await getAllFiles(modulePath);
4808
4991
  for (const file of files) {
4809
4992
  const deps = extractAlephaDependencies(await readFile(file, "utf-8"), packageName, moduleName);
@@ -4818,6 +5001,7 @@ async function analyzeModules(srcDir, packageName) {
4818
5001
  dependencies: Array.from(dependencies)
4819
5002
  };
4820
5003
  if (hasNative) module.native = true;
5004
+ if (hasEdge) module.workerd = true;
4821
5005
  if (hasBrowser) module.browser = true;
4822
5006
  if (hasBun) module.bun = true;
4823
5007
  if (hasNode) module.node = true;