alepha 0.14.4 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (277) hide show
  1. package/README.md +1 -4
  2. package/dist/api/audits/index.d.ts +619 -731
  3. package/dist/api/audits/index.d.ts.map +1 -1
  4. package/dist/api/files/index.d.ts +185 -298
  5. package/dist/api/files/index.d.ts.map +1 -1
  6. package/dist/api/files/index.js +0 -1
  7. package/dist/api/files/index.js.map +1 -1
  8. package/dist/api/jobs/index.d.ts +245 -356
  9. package/dist/api/jobs/index.d.ts.map +1 -1
  10. package/dist/api/notifications/index.d.ts +238 -350
  11. package/dist/api/notifications/index.d.ts.map +1 -1
  12. package/dist/api/parameters/index.d.ts +499 -611
  13. package/dist/api/parameters/index.d.ts.map +1 -1
  14. package/dist/api/users/index.browser.js +1 -2
  15. package/dist/api/users/index.browser.js.map +1 -1
  16. package/dist/api/users/index.d.ts +1697 -1804
  17. package/dist/api/users/index.d.ts.map +1 -1
  18. package/dist/api/users/index.js +178 -151
  19. package/dist/api/users/index.js.map +1 -1
  20. package/dist/api/verifications/index.d.ts +132 -132
  21. package/dist/api/verifications/index.d.ts.map +1 -1
  22. package/dist/batch/index.d.ts +122 -122
  23. package/dist/batch/index.d.ts.map +1 -1
  24. package/dist/batch/index.js +1 -2
  25. package/dist/batch/index.js.map +1 -1
  26. package/dist/bucket/index.d.ts +163 -163
  27. package/dist/bucket/index.d.ts.map +1 -1
  28. package/dist/cache/core/index.d.ts +46 -46
  29. package/dist/cache/core/index.d.ts.map +1 -1
  30. package/dist/cache/redis/index.d.ts.map +1 -1
  31. package/dist/cli/index.d.ts +302 -299
  32. package/dist/cli/index.d.ts.map +1 -1
  33. package/dist/cli/index.js +966 -564
  34. package/dist/cli/index.js.map +1 -1
  35. package/dist/command/index.d.ts +303 -299
  36. package/dist/command/index.d.ts.map +1 -1
  37. package/dist/command/index.js +11 -7
  38. package/dist/command/index.js.map +1 -1
  39. package/dist/core/index.browser.js +419 -99
  40. package/dist/core/index.browser.js.map +1 -1
  41. package/dist/core/index.d.ts +718 -625
  42. package/dist/core/index.d.ts.map +1 -1
  43. package/dist/core/index.js +420 -99
  44. package/dist/core/index.js.map +1 -1
  45. package/dist/core/index.native.js +419 -99
  46. package/dist/core/index.native.js.map +1 -1
  47. package/dist/datetime/index.d.ts +44 -44
  48. package/dist/datetime/index.d.ts.map +1 -1
  49. package/dist/datetime/index.js +4 -4
  50. package/dist/datetime/index.js.map +1 -1
  51. package/dist/email/index.d.ts +97 -50
  52. package/dist/email/index.d.ts.map +1 -1
  53. package/dist/email/index.js +129 -33
  54. package/dist/email/index.js.map +1 -1
  55. package/dist/fake/index.d.ts +7981 -14
  56. package/dist/fake/index.d.ts.map +1 -1
  57. package/dist/file/index.d.ts +523 -390
  58. package/dist/file/index.d.ts.map +1 -1
  59. package/dist/file/index.js +253 -1
  60. package/dist/file/index.js.map +1 -1
  61. package/dist/lock/core/index.d.ts +208 -208
  62. package/dist/lock/core/index.d.ts.map +1 -1
  63. package/dist/lock/redis/index.d.ts.map +1 -1
  64. package/dist/logger/index.d.ts +25 -26
  65. package/dist/logger/index.d.ts.map +1 -1
  66. package/dist/mcp/index.d.ts +197 -197
  67. package/dist/mcp/index.d.ts.map +1 -1
  68. package/dist/orm/chunk-DtkW-qnP.js +38 -0
  69. package/dist/orm/index.browser.js.map +1 -1
  70. package/dist/orm/index.bun.js +2814 -0
  71. package/dist/orm/index.bun.js.map +1 -0
  72. package/dist/orm/index.d.ts +1205 -1057
  73. package/dist/orm/index.d.ts.map +1 -1
  74. package/dist/orm/index.js +2056 -1753
  75. package/dist/orm/index.js.map +1 -1
  76. package/dist/queue/core/index.d.ts +248 -248
  77. package/dist/queue/core/index.d.ts.map +1 -1
  78. package/dist/queue/redis/index.d.ts.map +1 -1
  79. package/dist/redis/index.bun.js +285 -0
  80. package/dist/redis/index.bun.js.map +1 -0
  81. package/dist/redis/index.d.ts +118 -136
  82. package/dist/redis/index.d.ts.map +1 -1
  83. package/dist/redis/index.js +18 -38
  84. package/dist/redis/index.js.map +1 -1
  85. package/dist/retry/index.d.ts +69 -69
  86. package/dist/retry/index.d.ts.map +1 -1
  87. package/dist/router/index.d.ts +6 -6
  88. package/dist/router/index.d.ts.map +1 -1
  89. package/dist/scheduler/index.d.ts +25 -25
  90. package/dist/scheduler/index.d.ts.map +1 -1
  91. package/dist/security/index.browser.js +5 -1
  92. package/dist/security/index.browser.js.map +1 -1
  93. package/dist/security/index.d.ts +417 -254
  94. package/dist/security/index.d.ts.map +1 -1
  95. package/dist/security/index.js +386 -86
  96. package/dist/security/index.js.map +1 -1
  97. package/dist/server/auth/index.d.ts +277 -277
  98. package/dist/server/auth/index.d.ts.map +1 -1
  99. package/dist/server/auth/index.js +20 -20
  100. package/dist/server/auth/index.js.map +1 -1
  101. package/dist/server/cache/index.d.ts +60 -57
  102. package/dist/server/cache/index.d.ts.map +1 -1
  103. package/dist/server/cache/index.js +1 -1
  104. package/dist/server/cache/index.js.map +1 -1
  105. package/dist/server/compress/index.d.ts +3 -3
  106. package/dist/server/compress/index.d.ts.map +1 -1
  107. package/dist/server/cookies/index.d.ts +6 -6
  108. package/dist/server/cookies/index.d.ts.map +1 -1
  109. package/dist/server/cookies/index.js +3 -3
  110. package/dist/server/cookies/index.js.map +1 -1
  111. package/dist/server/core/index.d.ts +242 -150
  112. package/dist/server/core/index.d.ts.map +1 -1
  113. package/dist/server/core/index.js +288 -122
  114. package/dist/server/core/index.js.map +1 -1
  115. package/dist/server/cors/index.d.ts +11 -12
  116. package/dist/server/cors/index.d.ts.map +1 -1
  117. package/dist/server/health/index.d.ts +0 -1
  118. package/dist/server/health/index.d.ts.map +1 -1
  119. package/dist/server/helmet/index.d.ts +2 -2
  120. package/dist/server/helmet/index.d.ts.map +1 -1
  121. package/dist/server/links/index.browser.js.map +1 -1
  122. package/dist/server/links/index.d.ts +84 -85
  123. package/dist/server/links/index.d.ts.map +1 -1
  124. package/dist/server/links/index.js +1 -2
  125. package/dist/server/links/index.js.map +1 -1
  126. package/dist/server/metrics/index.d.ts.map +1 -1
  127. package/dist/server/multipart/index.d.ts +6 -6
  128. package/dist/server/multipart/index.d.ts.map +1 -1
  129. package/dist/server/proxy/index.d.ts +102 -103
  130. package/dist/server/proxy/index.d.ts.map +1 -1
  131. package/dist/server/rate-limit/index.d.ts +16 -16
  132. package/dist/server/rate-limit/index.d.ts.map +1 -1
  133. package/dist/server/static/index.d.ts +44 -44
  134. package/dist/server/static/index.d.ts.map +1 -1
  135. package/dist/server/swagger/index.d.ts +48 -49
  136. package/dist/server/swagger/index.d.ts.map +1 -1
  137. package/dist/server/swagger/index.js +1 -2
  138. package/dist/server/swagger/index.js.map +1 -1
  139. package/dist/sms/index.d.ts +13 -11
  140. package/dist/sms/index.d.ts.map +1 -1
  141. package/dist/sms/index.js +7 -7
  142. package/dist/sms/index.js.map +1 -1
  143. package/dist/thread/index.d.ts +71 -72
  144. package/dist/thread/index.d.ts.map +1 -1
  145. package/dist/topic/core/index.d.ts +318 -318
  146. package/dist/topic/core/index.d.ts.map +1 -1
  147. package/dist/topic/redis/index.d.ts +6 -6
  148. package/dist/topic/redis/index.d.ts.map +1 -1
  149. package/dist/vite/index.d.ts +5720 -159
  150. package/dist/vite/index.d.ts.map +1 -1
  151. package/dist/vite/index.js +41 -18
  152. package/dist/vite/index.js.map +1 -1
  153. package/dist/websocket/index.browser.js +6 -6
  154. package/dist/websocket/index.browser.js.map +1 -1
  155. package/dist/websocket/index.d.ts +247 -247
  156. package/dist/websocket/index.d.ts.map +1 -1
  157. package/dist/websocket/index.js +6 -6
  158. package/dist/websocket/index.js.map +1 -1
  159. package/package.json +9 -14
  160. package/src/api/files/controllers/AdminFileStatsController.ts +0 -1
  161. package/src/api/users/atoms/realmAuthSettingsAtom.ts +5 -0
  162. package/src/api/users/controllers/{UserRealmController.ts → RealmController.ts} +11 -11
  163. package/src/api/users/entities/users.ts +1 -1
  164. package/src/api/users/index.ts +8 -8
  165. package/src/api/users/primitives/{$userRealm.ts → $realm.ts} +17 -19
  166. package/src/api/users/providers/{UserRealmProvider.ts → RealmProvider.ts} +26 -30
  167. package/src/api/users/schemas/{userRealmConfigSchema.ts → realmConfigSchema.ts} +2 -2
  168. package/src/api/users/services/CredentialService.ts +7 -7
  169. package/src/api/users/services/IdentityService.ts +4 -4
  170. package/src/api/users/services/RegistrationService.spec.ts +25 -27
  171. package/src/api/users/services/RegistrationService.ts +38 -27
  172. package/src/api/users/services/SessionCrudService.ts +3 -3
  173. package/src/api/users/services/SessionService.spec.ts +3 -3
  174. package/src/api/users/services/SessionService.ts +28 -9
  175. package/src/api/users/services/UserService.ts +7 -7
  176. package/src/batch/providers/BatchProvider.ts +1 -2
  177. package/src/cli/apps/AlephaPackageBuilderCli.ts +38 -19
  178. package/src/cli/assets/apiHelloControllerTs.ts +18 -0
  179. package/src/cli/assets/apiIndexTs.ts +16 -0
  180. package/src/cli/assets/claudeMd.ts +303 -0
  181. package/src/cli/assets/mainBrowserTs.ts +2 -2
  182. package/src/cli/assets/mainServerTs.ts +24 -0
  183. package/src/cli/assets/webAppRouterTs.ts +15 -0
  184. package/src/cli/assets/webHelloComponentTsx.ts +16 -0
  185. package/src/cli/assets/webIndexTs.ts +16 -0
  186. package/src/cli/commands/build.ts +41 -21
  187. package/src/cli/commands/db.ts +21 -18
  188. package/src/cli/commands/deploy.ts +17 -5
  189. package/src/cli/commands/dev.ts +13 -17
  190. package/src/cli/commands/format.ts +8 -2
  191. package/src/cli/commands/init.ts +74 -29
  192. package/src/cli/commands/lint.ts +8 -2
  193. package/src/cli/commands/test.ts +8 -2
  194. package/src/cli/commands/typecheck.ts +5 -1
  195. package/src/cli/commands/verify.ts +4 -2
  196. package/src/cli/services/AlephaCliUtils.ts +39 -600
  197. package/src/cli/services/PackageManagerUtils.ts +301 -0
  198. package/src/cli/services/ProjectScaffolder.ts +306 -0
  199. package/src/command/helpers/Runner.ts +15 -3
  200. package/src/core/__tests__/Alepha-graph.spec.ts +4 -0
  201. package/src/core/index.shared.ts +1 -0
  202. package/src/core/index.ts +2 -0
  203. package/src/core/primitives/$hook.ts +6 -2
  204. package/src/core/primitives/$module.spec.ts +4 -0
  205. package/src/core/providers/AlsProvider.ts +1 -1
  206. package/src/core/providers/CodecManager.spec.ts +12 -6
  207. package/src/core/providers/CodecManager.ts +26 -6
  208. package/src/core/providers/EventManager.ts +169 -13
  209. package/src/core/providers/KeylessJsonSchemaCodec.spec.ts +621 -0
  210. package/src/core/providers/KeylessJsonSchemaCodec.ts +407 -0
  211. package/src/core/providers/StateManager.spec.ts +27 -16
  212. package/src/email/providers/LocalEmailProvider.spec.ts +111 -87
  213. package/src/email/providers/LocalEmailProvider.ts +52 -15
  214. package/src/email/providers/NodemailerEmailProvider.ts +167 -56
  215. package/src/file/errors/FileError.ts +7 -0
  216. package/src/file/index.ts +9 -1
  217. package/src/file/providers/MemoryFileSystemProvider.ts +393 -0
  218. package/src/orm/index.browser.ts +1 -19
  219. package/src/orm/index.bun.ts +77 -0
  220. package/src/orm/index.shared-server.ts +22 -0
  221. package/src/orm/index.shared.ts +15 -0
  222. package/src/orm/index.ts +19 -39
  223. package/src/orm/providers/drivers/BunPostgresProvider.ts +3 -5
  224. package/src/orm/providers/drivers/BunSqliteProvider.ts +1 -1
  225. package/src/orm/providers/drivers/CloudflareD1Provider.ts +4 -0
  226. package/src/orm/providers/drivers/DatabaseProvider.ts +4 -0
  227. package/src/orm/providers/drivers/PglitePostgresProvider.ts +4 -0
  228. package/src/orm/services/Repository.ts +8 -0
  229. package/src/redis/index.bun.ts +35 -0
  230. package/src/redis/providers/BunRedisProvider.ts +12 -43
  231. package/src/redis/providers/BunRedisSubscriberProvider.ts +2 -3
  232. package/src/redis/providers/NodeRedisProvider.ts +16 -34
  233. package/src/{server/security → security}/__tests__/BasicAuth.spec.ts +11 -11
  234. package/src/{server/security → security}/__tests__/ServerSecurityProvider-realm.spec.ts +21 -16
  235. package/src/{server/security/providers → security/__tests__}/ServerSecurityProvider.spec.ts +5 -5
  236. package/src/security/index.browser.ts +5 -0
  237. package/src/security/index.ts +90 -7
  238. package/src/security/primitives/{$realm.spec.ts → $issuer.spec.ts} +11 -11
  239. package/src/security/primitives/{$realm.ts → $issuer.ts} +20 -17
  240. package/src/security/primitives/$role.ts +5 -5
  241. package/src/security/primitives/$serviceAccount.spec.ts +5 -5
  242. package/src/security/primitives/$serviceAccount.ts +3 -3
  243. package/src/{server/security → security}/providers/ServerSecurityProvider.ts +5 -7
  244. package/src/server/auth/primitives/$auth.ts +10 -10
  245. package/src/server/auth/primitives/$authCredentials.ts +3 -3
  246. package/src/server/auth/primitives/$authGithub.ts +3 -3
  247. package/src/server/auth/primitives/$authGoogle.ts +3 -3
  248. package/src/server/auth/providers/ServerAuthProvider.ts +13 -13
  249. package/src/server/cache/providers/ServerCacheProvider.ts +1 -1
  250. package/src/server/cookies/providers/ServerCookiesProvider.ts +3 -3
  251. package/src/server/core/providers/NodeHttpServerProvider.ts +25 -6
  252. package/src/server/core/providers/ServerBodyParserProvider.ts +19 -23
  253. package/src/server/core/providers/ServerLoggerProvider.ts +23 -19
  254. package/src/server/core/providers/ServerProvider.ts +144 -21
  255. package/src/server/core/providers/ServerRouterProvider.ts +259 -115
  256. package/src/server/core/providers/ServerTimingProvider.ts +2 -2
  257. package/src/server/links/index.ts +1 -1
  258. package/src/server/links/providers/LinkProvider.ts +1 -1
  259. package/src/server/swagger/index.ts +1 -1
  260. package/src/sms/providers/LocalSmsProvider.spec.ts +153 -111
  261. package/src/sms/providers/LocalSmsProvider.ts +8 -7
  262. package/src/vite/helpers/boot.ts +28 -17
  263. package/src/vite/tasks/buildServer.ts +12 -1
  264. package/src/vite/tasks/devServer.ts +3 -1
  265. package/src/vite/tasks/generateCloudflare.ts +7 -0
  266. package/dist/server/security/index.browser.js +0 -13
  267. package/dist/server/security/index.browser.js.map +0 -1
  268. package/dist/server/security/index.d.ts +0 -173
  269. package/dist/server/security/index.d.ts.map +0 -1
  270. package/dist/server/security/index.js +0 -311
  271. package/dist/server/security/index.js.map +0 -1
  272. package/src/cli/assets/appRouterTs.ts +0 -9
  273. package/src/cli/assets/mainTs.ts +0 -13
  274. package/src/server/security/index.browser.ts +0 -10
  275. package/src/server/security/index.ts +0 -94
  276. /package/src/{server/security → security}/primitives/$basicAuth.ts +0 -0
  277. /package/src/{server/security → security}/providers/ServerBasicAuthProvider.ts +0 -0
@@ -0,0 +1,301 @@
1
+ import { $inject, Alepha } from "alepha";
2
+ import type { RunnerMethod } from "alepha/command";
3
+ import { FileSystemProvider } from "alepha/file";
4
+ import { $logger } from "alepha/logger";
5
+ import { version } from "../version.ts";
6
+
7
+ /**
8
+ * Utility service for package manager operations.
9
+ *
10
+ * Handles detection, installation, and cleanup for:
11
+ * - Yarn
12
+ * - npm
13
+ * - pnpm
14
+ * - Bun
15
+ */
16
+ export class PackageManagerUtils {
17
+ protected readonly log = $logger();
18
+ protected readonly fs = $inject(FileSystemProvider);
19
+ protected readonly alepha = $inject(Alepha);
20
+
21
+ /**
22
+ * Detect the package manager used in the project.
23
+ */
24
+ public async getPackageManager(
25
+ root: string,
26
+ flags?: { yarn?: boolean; pnpm?: boolean; npm?: boolean; bun?: boolean },
27
+ ): Promise<"yarn" | "pnpm" | "npm" | "bun"> {
28
+ if (flags?.yarn) return "yarn";
29
+ if (flags?.pnpm) return "pnpm";
30
+ if (flags?.npm) return "npm";
31
+ if (flags?.bun) return "bun";
32
+ if (this.alepha.isBun()) return "bun";
33
+ if (await this.fs.exists(this.fs.join(root, "bun.lock"))) return "bun";
34
+ if (await this.fs.exists(this.fs.join(root, "yarn.lock"))) return "yarn";
35
+ if (await this.fs.exists(this.fs.join(root, "pnpm-lock.yaml")))
36
+ return "pnpm";
37
+ return "npm";
38
+ }
39
+
40
+ /**
41
+ * Get the install command for a package.
42
+ */
43
+ public async getInstallCommand(
44
+ root: string,
45
+ packageName: string,
46
+ dev = true,
47
+ ): Promise<string> {
48
+ const pm = await this.getPackageManager(root);
49
+ let cmd: string;
50
+
51
+ switch (pm) {
52
+ case "yarn":
53
+ cmd = `yarn add ${dev ? "-D" : ""} ${packageName}`;
54
+ break;
55
+ case "pnpm":
56
+ cmd = `pnpm add ${dev ? "-D" : ""} ${packageName}`;
57
+ break;
58
+ case "bun":
59
+ cmd = `bun add ${dev ? "-d" : ""} ${packageName}`;
60
+ break;
61
+ default:
62
+ cmd = `npm install ${dev ? "--save-dev" : ""} ${packageName}`;
63
+ }
64
+
65
+ return cmd.replace(/\s+/g, " ").trim();
66
+ }
67
+
68
+ /**
69
+ * Check if a dependency is installed in the project.
70
+ */
71
+ public async hasDependency(
72
+ root: string,
73
+ packageName: string,
74
+ ): Promise<boolean> {
75
+ try {
76
+ const pkg = await this.readPackageJson(root);
77
+ return !!(
78
+ pkg.dependencies?.[packageName] || pkg.devDependencies?.[packageName]
79
+ );
80
+ } catch {
81
+ return false;
82
+ }
83
+ }
84
+
85
+ /**
86
+ * Check if Expo is present in the project.
87
+ */
88
+ public async hasExpo(root: string): Promise<boolean> {
89
+ return this.hasDependency(root, "expo");
90
+ }
91
+
92
+ /**
93
+ * Install a dependency if it's missing from the project.
94
+ */
95
+ public async ensureDependency(
96
+ root: string,
97
+ packageName: string,
98
+ options: {
99
+ dev?: boolean;
100
+ run?: RunnerMethod;
101
+ exec?: (
102
+ cmd: string,
103
+ opts?: { global?: boolean; root?: string },
104
+ ) => Promise<void>;
105
+ } = {},
106
+ ): Promise<void> {
107
+ const { dev = true } = options;
108
+
109
+ if (await this.hasDependency(root, packageName)) {
110
+ this.log.debug(`Dependency '${packageName}' is already installed`);
111
+ return;
112
+ }
113
+
114
+ const cmd = await this.getInstallCommand(root, packageName, dev);
115
+
116
+ if (options.run) {
117
+ await options.run(cmd, { alias: `installing ${packageName}`, root });
118
+ } else if (options.exec) {
119
+ this.log.debug(`Installing ${packageName}`);
120
+ await options.exec(cmd, { global: true, root });
121
+ }
122
+ }
123
+
124
+ // ===========================================
125
+ // Package Manager Setup & Cleanup
126
+ // ===========================================
127
+
128
+ public async ensureYarn(root: string): Promise<void> {
129
+ const yarnrcPath = this.fs.join(root, ".yarnrc.yml");
130
+ if (!(await this.fs.exists(yarnrcPath))) {
131
+ await this.fs.writeFile(yarnrcPath, "nodeLinker: node-modules");
132
+ }
133
+ await this.removeAllPmFilesExcept(root, "yarn");
134
+ }
135
+
136
+ public async ensureBun(root: string): Promise<void> {
137
+ await this.removeAllPmFilesExcept(root, "bun");
138
+ }
139
+
140
+ public async ensurePnpm(root: string): Promise<void> {
141
+ await this.removeAllPmFilesExcept(root, "pnpm");
142
+ }
143
+
144
+ public async ensureNpm(root: string): Promise<void> {
145
+ await this.removeAllPmFilesExcept(root, "npm");
146
+ }
147
+
148
+ public async removeAllPmFilesExcept(
149
+ root: string,
150
+ except: string,
151
+ ): Promise<void> {
152
+ if (except !== "yarn") await this.removeYarn(root);
153
+ if (except !== "pnpm") await this.removePnpm(root);
154
+ if (except !== "npm") await this.removeNpm(root);
155
+ if (except !== "bun") await this.removeBun(root);
156
+ }
157
+
158
+ public async removeYarn(root: string): Promise<void> {
159
+ await this.removeFiles(root, [".yarn", ".yarnrc.yml", "yarn.lock"]);
160
+ await this.editPackageJson(root, (pkg) => {
161
+ delete pkg.packageManager;
162
+ return pkg;
163
+ });
164
+ }
165
+
166
+ public async removePnpm(root: string): Promise<void> {
167
+ await this.removeFiles(root, ["pnpm-lock.yaml", "pnpm-workspace.yaml"]);
168
+ await this.editPackageJson(root, (pkg) => {
169
+ delete pkg.packageManager;
170
+ return pkg;
171
+ });
172
+ }
173
+
174
+ public async removeNpm(root: string): Promise<void> {
175
+ await this.removeFiles(root, ["package-lock.json"]);
176
+ }
177
+
178
+ public async removeBun(root: string): Promise<void> {
179
+ await this.removeFiles(root, ["bun.lockb", "bun.lock"]);
180
+ }
181
+
182
+ // ===========================================
183
+ // Package.json utilities
184
+ // ===========================================
185
+
186
+ public async readPackageJson(root: string): Promise<Record<string, any>> {
187
+ const content = await this.fs
188
+ .createFile({ path: this.fs.join(root, "package.json") })
189
+ .text();
190
+ return JSON.parse(content);
191
+ }
192
+
193
+ public async writePackageJson(
194
+ root: string,
195
+ content: Record<string, any>,
196
+ ): Promise<void> {
197
+ await this.fs.writeFile(
198
+ this.fs.join(root, "package.json"),
199
+ JSON.stringify(content, null, 2),
200
+ );
201
+ }
202
+
203
+ public async editPackageJson(
204
+ root: string,
205
+ editFn: (pkg: Record<string, any>) => Record<string, any>,
206
+ ): Promise<void> {
207
+ try {
208
+ const pkg = await this.readPackageJson(root);
209
+ const updated = editFn(pkg);
210
+ await this.writePackageJson(root, updated);
211
+ } catch {
212
+ // package.json doesn't exist, skip
213
+ }
214
+ }
215
+
216
+ public async ensurePackageJson(
217
+ root: string,
218
+ modes: DependencyModes,
219
+ ): Promise<Record<string, any>> {
220
+ const packageJsonPath = this.fs.join(root, "package.json");
221
+
222
+ if (!(await this.fs.exists(packageJsonPath))) {
223
+ const content = this.generatePackageJsonContent(modes);
224
+ await this.writePackageJson(root, content);
225
+ return content;
226
+ }
227
+
228
+ const packageJson = await this.readPackageJson(root);
229
+ const newContent = this.generatePackageJsonContent(modes);
230
+
231
+ packageJson.type = "module";
232
+ packageJson.dependencies ??= {};
233
+ packageJson.devDependencies ??= {};
234
+ packageJson.scripts ??= {};
235
+
236
+ Object.assign(packageJson.dependencies, newContent.dependencies);
237
+ Object.assign(packageJson.devDependencies, newContent.devDependencies);
238
+ Object.assign(packageJson.scripts, newContent.scripts);
239
+
240
+ await this.writePackageJson(root, packageJson);
241
+ return packageJson;
242
+ }
243
+
244
+ public generatePackageJsonContent(modes: DependencyModes): {
245
+ dependencies: Record<string, string>;
246
+ devDependencies: Record<string, string>;
247
+ scripts: Record<string, string>;
248
+ type: "module";
249
+ } {
250
+ const dependencies: Record<string, string> = {
251
+ alepha: `^${version}`,
252
+ };
253
+
254
+ const devDependencies: Record<string, string> = {};
255
+
256
+ const scripts: Record<string, string> = {
257
+ dev: "alepha dev",
258
+ build: "alepha build",
259
+ lint: "alepha lint",
260
+ typecheck: "alepha typecheck",
261
+ verify: "alepha verify",
262
+ };
263
+
264
+ if (modes.admin) {
265
+ dependencies["@alepha/ui"] = `^${version}`;
266
+ modes.web = true;
267
+ }
268
+
269
+ if (modes.web) {
270
+ dependencies["@alepha/react"] = `^${version}`;
271
+ dependencies.react = "^19.2.0";
272
+ dependencies["react-dom"] = "^19.2.0";
273
+ devDependencies["@types/react"] = "^19.2.0";
274
+ }
275
+
276
+ return {
277
+ type: "module",
278
+ dependencies,
279
+ devDependencies,
280
+ scripts,
281
+ };
282
+ }
283
+
284
+ // ===========================================
285
+ // Helper methods
286
+ // ===========================================
287
+
288
+ protected async removeFiles(root: string, files: string[]): Promise<void> {
289
+ await Promise.all(
290
+ files.map((file) =>
291
+ this.fs.rm(this.fs.join(root, file), { force: true, recursive: true }),
292
+ ),
293
+ );
294
+ }
295
+ }
296
+
297
+ export interface DependencyModes {
298
+ web?: boolean;
299
+ admin?: boolean;
300
+ expo?: boolean;
301
+ }
@@ -0,0 +1,306 @@
1
+ import { basename, dirname } from "node:path";
2
+ import { $inject } from "alepha";
3
+ import { FileSystemProvider } from "alepha/file";
4
+ import { $logger } from "alepha/logger";
5
+ import { apiHelloControllerTs } from "../assets/apiHelloControllerTs.ts";
6
+ import { apiIndexTs } from "../assets/apiIndexTs.ts";
7
+ import { biomeJson } from "../assets/biomeJson.ts";
8
+ import { type ClaudeMdOptions, claudeMd } from "../assets/claudeMd.ts";
9
+ import { dummySpecTs } from "../assets/dummySpecTs.ts";
10
+ import { editorconfig } from "../assets/editorconfig.ts";
11
+ import { indexHtml } from "../assets/indexHtml.ts";
12
+ import { mainBrowserTs } from "../assets/mainBrowserTs.ts";
13
+ import { mainServerTs } from "../assets/mainServerTs.ts";
14
+ import { tsconfigJson } from "../assets/tsconfigJson.ts";
15
+ import { webAppRouterTs } from "../assets/webAppRouterTs.ts";
16
+ import { webHelloComponentTsx } from "../assets/webHelloComponentTsx.ts";
17
+ import { webIndexTs } from "../assets/webIndexTs.ts";
18
+ import {
19
+ type DependencyModes,
20
+ PackageManagerUtils,
21
+ } from "./PackageManagerUtils.ts";
22
+
23
+ /**
24
+ * Service for scaffolding new Alepha projects.
25
+ *
26
+ * Handles creation of:
27
+ * - Project structure (src/api, src/web)
28
+ * - Configuration files (tsconfig, biome, editorconfig)
29
+ * - Entry points (main.server.ts, main.browser.ts)
30
+ * - Example code (HelloController, Hello component)
31
+ */
32
+ export class ProjectScaffolder {
33
+ protected readonly log = $logger();
34
+ protected readonly fs = $inject(FileSystemProvider);
35
+ protected readonly pm = $inject(PackageManagerUtils);
36
+
37
+ /**
38
+ * Get the app name from the directory name.
39
+ *
40
+ * Converts the directory name to a valid module name:
41
+ * - Converts to lowercase
42
+ * - Replaces spaces, dashes, underscores with nothing
43
+ * - Falls back to "app" if empty
44
+ */
45
+ public getAppName(root: string): string {
46
+ const dirName = basename(root);
47
+ const appName = dirName.toLowerCase().replace(/[\s\-_]/g, "");
48
+ return appName || "app";
49
+ }
50
+
51
+ /**
52
+ * Ensure all configuration files exist.
53
+ */
54
+ public async ensureConfig(
55
+ root: string,
56
+ opts: {
57
+ packageJson?: boolean | DependencyModes;
58
+ tsconfigJson?: boolean;
59
+ indexHtml?: boolean;
60
+ biomeJson?: boolean;
61
+ editorconfig?: boolean;
62
+ claudeMd?: boolean | ClaudeMdOptions;
63
+ },
64
+ ): Promise<void> {
65
+ const tasks: Promise<void>[] = [];
66
+
67
+ if (opts.packageJson) {
68
+ tasks.push(
69
+ this.pm
70
+ .ensurePackageJson(
71
+ root,
72
+ typeof opts.packageJson === "boolean" ? {} : opts.packageJson,
73
+ )
74
+ .then(() => {}),
75
+ );
76
+ }
77
+ if (opts.tsconfigJson) {
78
+ tasks.push(this.ensureTsConfig(root));
79
+ }
80
+ if (opts.indexHtml) {
81
+ tasks.push(this.ensureReactProject(root));
82
+ }
83
+ if (opts.biomeJson) {
84
+ tasks.push(this.ensureBiomeConfig(root));
85
+ }
86
+ if (opts.editorconfig) {
87
+ tasks.push(this.ensureEditorConfig(root));
88
+ }
89
+ if (opts.claudeMd) {
90
+ tasks.push(
91
+ this.ensureClaudeMd(
92
+ root,
93
+ typeof opts.claudeMd === "boolean" ? {} : opts.claudeMd,
94
+ ),
95
+ );
96
+ }
97
+
98
+ await Promise.all(tasks);
99
+ }
100
+
101
+ // ===========================================
102
+ // Config Files
103
+ // ===========================================
104
+
105
+ public async ensureTsConfig(root: string): Promise<void> {
106
+ // Check if tsconfig.json exists in current or parent directories
107
+ if (await this.existsInParents(root, "tsconfig.json")) {
108
+ return;
109
+ }
110
+ await this.fs.writeFile(this.fs.join(root, "tsconfig.json"), tsconfigJson);
111
+ }
112
+
113
+ public async ensureBiomeConfig(root: string): Promise<void> {
114
+ await this.ensureFileIfNotExists(root, "biome.json", biomeJson);
115
+ }
116
+
117
+ public async ensureEditorConfig(root: string): Promise<void> {
118
+ await this.ensureFileIfNotExists(root, ".editorconfig", editorconfig);
119
+ }
120
+
121
+ public async ensureClaudeMd(
122
+ root: string,
123
+ options: ClaudeMdOptions = {},
124
+ ): Promise<void> {
125
+ const path = this.fs.join(root, "CLAUDE.md");
126
+ if (!(await this.fs.exists(path))) {
127
+ await this.fs.writeFile(path, claudeMd(options));
128
+ }
129
+ }
130
+
131
+ // ===========================================
132
+ // API Project Structure
133
+ // ===========================================
134
+
135
+ /**
136
+ * Ensure src/main.server.ts exists with full API structure.
137
+ *
138
+ * Creates:
139
+ * - src/main.server.ts (entry point)
140
+ * - src/api/index.ts (API module)
141
+ * - src/api/controllers/HelloController.ts (example controller)
142
+ */
143
+ public async ensureApiProject(root: string): Promise<void> {
144
+ const srcDir = this.fs.join(root, "src");
145
+
146
+ // Don't overwrite existing content
147
+ if (await this.fs.exists(srcDir)) {
148
+ const files = await this.fs.ls(srcDir);
149
+ if (files.length > 0) return;
150
+ }
151
+
152
+ const appName = this.getAppName(root);
153
+
154
+ // Create directories
155
+ await this.fs.mkdir(this.fs.join(root, "src/api/controllers"), {
156
+ recursive: true,
157
+ });
158
+
159
+ // Create files
160
+ await this.fs.writeFile(
161
+ this.fs.join(srcDir, "main.server.ts"),
162
+ mainServerTs(),
163
+ );
164
+ await this.fs.writeFile(
165
+ this.fs.join(srcDir, "api/index.ts"),
166
+ apiIndexTs({ appName }),
167
+ );
168
+ await this.fs.writeFile(
169
+ this.fs.join(srcDir, "api/controllers/HelloController.ts"),
170
+ apiHelloControllerTs(),
171
+ );
172
+ }
173
+
174
+ // ===========================================
175
+ // React Project Structure
176
+ // ===========================================
177
+
178
+ /**
179
+ * Ensure full React project structure exists.
180
+ *
181
+ * Creates:
182
+ * - index.html
183
+ * - src/main.server.ts, src/main.browser.ts
184
+ * - src/api/index.ts, src/api/controllers/HelloController.ts
185
+ * - src/web/index.ts, src/web/AppRouter.ts, src/web/components/Hello.tsx
186
+ */
187
+ public async ensureReactProject(root: string): Promise<void> {
188
+ if (await this.fs.exists(this.fs.join(root, "index.html"))) {
189
+ return;
190
+ }
191
+
192
+ const appName = this.getAppName(root);
193
+
194
+ // Create directories
195
+ await this.fs.mkdir(this.fs.join(root, "src/api/controllers"), {
196
+ recursive: true,
197
+ });
198
+ await this.fs.mkdir(this.fs.join(root, "src/web/components"), {
199
+ recursive: true,
200
+ });
201
+
202
+ // index.html
203
+ await this.fs.writeFile(
204
+ this.fs.join(root, "index.html"),
205
+ indexHtml("src/main.browser.ts"),
206
+ );
207
+
208
+ // API structure
209
+ await this.ensureFileIfNotExists(
210
+ root,
211
+ "src/api/index.ts",
212
+ apiIndexTs({ appName }),
213
+ );
214
+ await this.ensureFileIfNotExists(
215
+ root,
216
+ "src/api/controllers/HelloController.ts",
217
+ apiHelloControllerTs(),
218
+ );
219
+ await this.ensureFileIfNotExists(
220
+ root,
221
+ "src/main.server.ts",
222
+ mainServerTs({ react: true }),
223
+ );
224
+
225
+ // Web structure
226
+ await this.ensureFileIfNotExists(
227
+ root,
228
+ "src/web/index.ts",
229
+ webIndexTs({ appName }),
230
+ );
231
+ await this.ensureFileIfNotExists(
232
+ root,
233
+ "src/web/AppRouter.ts",
234
+ webAppRouterTs(),
235
+ );
236
+ await this.ensureFileIfNotExists(
237
+ root,
238
+ "src/web/components/Hello.tsx",
239
+ webHelloComponentTsx(),
240
+ );
241
+ await this.ensureFileIfNotExists(
242
+ root,
243
+ "src/main.browser.ts",
244
+ mainBrowserTs(),
245
+ );
246
+ }
247
+
248
+ // ===========================================
249
+ // Test Directory
250
+ // ===========================================
251
+
252
+ /**
253
+ * Ensure test directory exists with a dummy test file.
254
+ */
255
+ public async ensureTestDir(root: string): Promise<void> {
256
+ const testDir = this.fs.join(root, "test");
257
+ const dummyPath = this.fs.join(testDir, "dummy.spec.ts");
258
+
259
+ if (!(await this.fs.exists(testDir))) {
260
+ await this.fs.mkdir(testDir, { recursive: true });
261
+ await this.fs.writeFile(dummyPath, dummySpecTs());
262
+ return;
263
+ }
264
+
265
+ const files = await this.fs.ls(testDir);
266
+ if (files.length === 0) {
267
+ await this.fs.writeFile(dummyPath, dummySpecTs());
268
+ }
269
+ }
270
+
271
+ // ===========================================
272
+ // Helpers
273
+ // ===========================================
274
+
275
+ protected async ensureFileIfNotExists(
276
+ root: string,
277
+ relativePath: string,
278
+ content: string,
279
+ ): Promise<void> {
280
+ const fullPath = this.fs.join(root, relativePath);
281
+ if (!(await this.fs.exists(fullPath))) {
282
+ await this.fs.writeFile(fullPath, content);
283
+ }
284
+ }
285
+
286
+ /**
287
+ * Check if a file exists in the given directory or any parent directory.
288
+ */
289
+ protected async existsInParents(
290
+ root: string,
291
+ filename: string,
292
+ ): Promise<boolean> {
293
+ let current = root;
294
+ while (true) {
295
+ if (await this.fs.exists(this.fs.join(current, filename))) {
296
+ return true;
297
+ }
298
+ const parent = dirname(current);
299
+ if (parent === current) {
300
+ // Reached filesystem root
301
+ return false;
302
+ }
303
+ current = parent;
304
+ }
305
+ }
306
+ }
@@ -20,6 +20,11 @@ export interface RunOptions {
20
20
  * Rename the command for logging purposes.
21
21
  */
22
22
  alias?: string;
23
+
24
+ /**
25
+ * Root directory to execute the command in.
26
+ */
27
+ root?: string;
23
28
  }
24
29
 
25
30
  export interface RunnerMethod {
@@ -73,11 +78,14 @@ export class Runner {
73
78
 
74
79
  this.firstTaskStarted = true;
75
80
 
81
+ const root =
82
+ typeof options === "object" && options.root ? options.root : undefined;
83
+
76
84
  if (Array.isArray(cmd)) {
77
85
  return await this.execute(
78
86
  cmd.map((it) =>
79
87
  typeof it === "string"
80
- ? { name: it, handler: () => this.exec(it) }
88
+ ? { name: it, handler: () => this.exec(it, { root }) }
81
89
  : it,
82
90
  ),
83
91
  );
@@ -89,7 +97,7 @@ export class Runner {
89
97
  typeof options === "function"
90
98
  ? options
91
99
  : typeof cmd === "string"
92
- ? () => this.exec(cmd)
100
+ ? () => this.exec(cmd, { root })
93
101
  : cmd.handler;
94
102
 
95
103
  return await this.execute({
@@ -140,11 +148,15 @@ export class Runner {
140
148
  return runFn;
141
149
  }
142
150
 
143
- protected async exec(cmd: string): Promise<string> {
151
+ protected async exec(
152
+ cmd: string,
153
+ opts: { root?: string } = {},
154
+ ): Promise<string> {
144
155
  return await new Promise<string>((resolve, reject) => {
145
156
  exec(
146
157
  cmd,
147
158
  {
159
+ cwd: opts.root,
148
160
  env: {
149
161
  ...process.env,
150
162
  LOG_FORMAT: "pretty",
@@ -80,6 +80,10 @@ describe("Alepha#graph", () => {
80
80
  from: ["StateManager", "CodecManager"],
81
81
  module: "alepha.core",
82
82
  },
83
+ KeylessJsonSchemaCodec: {
84
+ from: ["CodecManager"],
85
+ module: "alepha.core",
86
+ },
83
87
  CodecManager: {
84
88
  from: ["Alepha"],
85
89
  module: "alepha.core",
@@ -28,6 +28,7 @@ export * from "./providers/AlsProvider.ts";
28
28
  export * from "./providers/CodecManager.ts";
29
29
  export * from "./providers/EventManager.ts";
30
30
  export * from "./providers/JsonSchemaCodec.ts";
31
+ export * from "./providers/KeylessJsonSchemaCodec.ts";
31
32
  export * from "./providers/SchemaCodec.ts";
32
33
  export * from "./providers/StateManager.ts";
33
34
  export * from "./providers/TypeProvider.ts";
package/src/core/index.ts CHANGED
@@ -11,6 +11,7 @@ import { CodecManager } from "./providers/CodecManager.ts";
11
11
  import { EventManager } from "./providers/EventManager.ts";
12
12
  import { Json } from "./providers/Json.ts";
13
13
  import { JsonSchemaCodec } from "./providers/JsonSchemaCodec.ts";
14
+ import { KeylessJsonSchemaCodec } from "./providers/KeylessJsonSchemaCodec.ts";
14
15
  import { SchemaCodec } from "./providers/SchemaCodec.ts";
15
16
  import { SchemaValidator } from "./providers/SchemaValidator.ts";
16
17
  import { StateManager } from "./providers/StateManager.ts";
@@ -30,6 +31,7 @@ export const AlephaCore = $module({
30
31
  AlsProvider,
31
32
  Json,
32
33
  JsonSchemaCodec,
34
+ KeylessJsonSchemaCodec,
33
35
  SchemaCodec,
34
36
  SchemaValidator,
35
37
  ],