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
@@ -0,0 +1,332 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { Alepha, t } from "alepha";
3
+ import { $issuer, AlephaSecurity } from "alepha/security";
4
+ import { $action, ServerProvider } from "alepha/server";
5
+ import { describe, it } from "vitest";
6
+ import { LinkProvider, ServerLinksProvider } from "../index.ts";
7
+
8
+ describe("ServerLinksProvider", () => {
9
+ describe("secured field in links", () => {
10
+ it("should set secured=undefined for public actions (no secure option)", async ({
11
+ expect,
12
+ }) => {
13
+ class App {
14
+ publicAction = $action({
15
+ handler: () => "PUBLIC",
16
+ });
17
+ }
18
+
19
+ const alepha = Alepha.create().with(App).with(ServerLinksProvider);
20
+ await alepha.start();
21
+
22
+ const links = alepha.inject(LinkProvider).getServerLinks();
23
+ const link = links.find((l) => l.name === "publicAction");
24
+
25
+ expect(link).toBeDefined();
26
+ expect(link?.secured).toBeUndefined();
27
+ });
28
+
29
+ it("should set secured=false for explicitly public actions", async ({
30
+ expect,
31
+ }) => {
32
+ class App {
33
+ publicAction = $action({
34
+ secure: false,
35
+ handler: () => "PUBLIC",
36
+ });
37
+ }
38
+
39
+ const alepha = Alepha.create().with(App).with(ServerLinksProvider);
40
+ await alepha.start();
41
+
42
+ const links = alepha.inject(LinkProvider).getServerLinks();
43
+ const link = links.find((l) => l.name === "publicAction");
44
+
45
+ expect(link).toBeDefined();
46
+ expect(link?.secured).toBe(false);
47
+ });
48
+
49
+ it("should set secured=true for secured actions", async ({ expect }) => {
50
+ class App {
51
+ securedAction = $action({
52
+ secure: true,
53
+ handler: () => "SECURED",
54
+ });
55
+ }
56
+
57
+ const alepha = Alepha.create().with(App).with(ServerLinksProvider);
58
+ await alepha.start();
59
+
60
+ const links = alepha.inject(LinkProvider).getServerLinks();
61
+ const link = links.find((l) => l.name === "securedAction");
62
+
63
+ expect(link).toBeDefined();
64
+ expect(link?.secured).toBe(true);
65
+ });
66
+
67
+ it("should set secured to object for realm-secured actions", async ({
68
+ expect,
69
+ }) => {
70
+ class App {
71
+ realmAction = $action({
72
+ secure: { realm: "admin" },
73
+ handler: () => "REALM",
74
+ });
75
+ }
76
+
77
+ const alepha = Alepha.create().with(App).with(ServerLinksProvider);
78
+ await alepha.start();
79
+
80
+ const links = alepha.inject(LinkProvider).getServerLinks();
81
+ const link = links.find((l) => l.name === "realmAction");
82
+
83
+ expect(link).toBeDefined();
84
+ expect(link?.secured).toEqual({ realm: "admin" });
85
+ });
86
+ });
87
+
88
+ describe("/_links endpoint with security", () => {
89
+ it("should return public actions to unauthenticated users", async ({
90
+ expect,
91
+ }) => {
92
+ class App {
93
+ publicAction = $action({
94
+ schema: { response: t.text() },
95
+ handler: () => "PUBLIC",
96
+ });
97
+ issuer = $issuer({
98
+ secret: "test",
99
+ roles: [{ name: "user", permissions: [{ name: "*" }] }],
100
+ });
101
+ }
102
+
103
+ const alepha = Alepha.create()
104
+ .with(App)
105
+ .with(ServerLinksProvider)
106
+ .with(AlephaSecurity);
107
+ await alepha.start();
108
+
109
+ const res = await fetch(
110
+ `${alepha.inject(ServerProvider).hostname}/api/_links`,
111
+ );
112
+ const data = await res.json();
113
+
114
+ expect(data.links).toContainEqual(
115
+ expect.objectContaining({
116
+ name: "publicAction",
117
+ path: "/publicAction",
118
+ }),
119
+ );
120
+ });
121
+
122
+ it("should NOT return secured actions to unauthenticated users", async ({
123
+ expect,
124
+ }) => {
125
+ class App {
126
+ securedAction = $action({
127
+ secure: true,
128
+ schema: { response: t.text() },
129
+ handler: () => "SECURED",
130
+ });
131
+ issuer = $issuer({
132
+ secret: "test",
133
+ roles: [{ name: "user", permissions: [{ name: "*" }] }],
134
+ });
135
+ }
136
+
137
+ const alepha = Alepha.create()
138
+ .with(App)
139
+ .with(ServerLinksProvider)
140
+ .with(AlephaSecurity);
141
+ await alepha.start();
142
+
143
+ const res = await fetch(
144
+ `${alepha.inject(ServerProvider).hostname}/api/_links`,
145
+ );
146
+ const data = await res.json();
147
+
148
+ expect(data.links).not.toContainEqual(
149
+ expect.objectContaining({
150
+ name: "securedAction",
151
+ }),
152
+ );
153
+ });
154
+
155
+ it("should return secured actions to authenticated users with permissions", async ({
156
+ expect,
157
+ }) => {
158
+ class App {
159
+ securedAction = $action({
160
+ secure: true,
161
+ schema: { response: t.text() },
162
+ handler: () => "SECURED",
163
+ });
164
+ issuer = $issuer({
165
+ secret: "test",
166
+ roles: [{ name: "user", permissions: [{ name: "*" }] }],
167
+ });
168
+ }
169
+
170
+ const alepha = Alepha.create()
171
+ .with(App)
172
+ .with(ServerLinksProvider)
173
+ .with(AlephaSecurity);
174
+ await alepha.start();
175
+
176
+ // Use HttpClient to get a token automatically in test mode
177
+ const app = alepha.inject(App);
178
+ const { data } = await app.securedAction.fetch(
179
+ {},
180
+ {
181
+ user: { id: randomUUID(), roles: ["user"] },
182
+ },
183
+ );
184
+ expect(data).toBe("SECURED");
185
+ });
186
+
187
+ it("should return both public and secured actions when user is authenticated", async ({
188
+ expect,
189
+ }) => {
190
+ class App {
191
+ publicAction = $action({
192
+ schema: { response: t.text() },
193
+ handler: () => "PUBLIC",
194
+ });
195
+ securedAction = $action({
196
+ secure: true,
197
+ schema: { response: t.text() },
198
+ handler: () => "SECURED",
199
+ });
200
+ issuer = $issuer({
201
+ secret: "test",
202
+ roles: [{ name: "user", permissions: [{ name: "*" }] }],
203
+ });
204
+ }
205
+
206
+ const alepha = Alepha.create()
207
+ .with(App)
208
+ .with(ServerLinksProvider)
209
+ .with(AlephaSecurity);
210
+ await alepha.start();
211
+
212
+ const linksProvider = alepha.inject(ServerLinksProvider);
213
+ const user = { id: randomUUID(), roles: ["user"] };
214
+
215
+ const { links } = await linksProvider.getUserApiLinks({ user });
216
+
217
+ expect(links).toContainEqual(
218
+ expect.objectContaining({ name: "publicAction" }),
219
+ );
220
+ expect(links).toContainEqual(
221
+ expect.objectContaining({ name: "securedAction" }),
222
+ );
223
+ });
224
+
225
+ it("should filter secured actions based on user permissions", async ({
226
+ expect,
227
+ }) => {
228
+ class App {
229
+ adminOnly = $action({
230
+ secure: true,
231
+ group: "admin",
232
+ schema: { response: t.text() },
233
+ handler: () => "ADMIN",
234
+ });
235
+ userAction = $action({
236
+ secure: true,
237
+ group: "user",
238
+ schema: { response: t.text() },
239
+ handler: () => "USER",
240
+ });
241
+ issuer = $issuer({
242
+ secret: "test",
243
+ roles: [
244
+ { name: "admin", permissions: [{ name: "*" }] },
245
+ { name: "user", permissions: [{ name: "user:*" }] },
246
+ ],
247
+ });
248
+ }
249
+
250
+ const alepha = Alepha.create()
251
+ .with(App)
252
+ .with(ServerLinksProvider)
253
+ .with(AlephaSecurity);
254
+ await alepha.start();
255
+
256
+ const linksProvider = alepha.inject(ServerLinksProvider);
257
+
258
+ // User with "user" role should only see userAction
259
+ const userLinks = await linksProvider.getUserApiLinks({
260
+ user: { id: randomUUID(), roles: ["user"] },
261
+ });
262
+
263
+ expect(userLinks.links).toContainEqual(
264
+ expect.objectContaining({ name: "userAction" }),
265
+ );
266
+ expect(userLinks.links).not.toContainEqual(
267
+ expect.objectContaining({ name: "adminOnly" }),
268
+ );
269
+
270
+ // User with "admin" role should see both
271
+ const adminLinks = await linksProvider.getUserApiLinks({
272
+ user: { id: randomUUID(), roles: ["admin"] },
273
+ });
274
+
275
+ expect(adminLinks.links).toContainEqual(
276
+ expect.objectContaining({ name: "userAction" }),
277
+ );
278
+ expect(adminLinks.links).toContainEqual(
279
+ expect.objectContaining({ name: "adminOnly" }),
280
+ );
281
+ });
282
+ });
283
+
284
+ describe("mixed public and secured actions", () => {
285
+ it("should correctly differentiate public from secured in server links", async ({
286
+ expect,
287
+ }) => {
288
+ class App {
289
+ getUsers = $action({
290
+ path: "/users",
291
+ schema: { response: t.array(t.text()) },
292
+ handler: () => ["user1", "user2"],
293
+ });
294
+ createUser = $action({
295
+ path: "/users",
296
+ secure: true,
297
+ schema: {
298
+ body: t.object({ name: t.text() }),
299
+ response: t.text(),
300
+ },
301
+ handler: ({ body }) => body.name,
302
+ });
303
+ deleteUser = $action({
304
+ method: "DELETE",
305
+ path: "/users/:id",
306
+ secure: true,
307
+ schema: {
308
+ params: t.object({ id: t.text() }),
309
+ response: t.void(),
310
+ },
311
+ handler: () => {},
312
+ });
313
+ }
314
+
315
+ const alepha = Alepha.create().with(App).with(ServerLinksProvider);
316
+ await alepha.start();
317
+
318
+ const links = alepha.inject(LinkProvider).getServerLinks();
319
+
320
+ const getUsers = links.find((l) => l.name === "getUsers");
321
+ const createUser = links.find((l) => l.name === "createUser");
322
+ const deleteUser = links.find((l) => l.name === "deleteUser");
323
+
324
+ // getUsers is public (no secure option)
325
+ expect(getUsers?.secured).toBeUndefined();
326
+
327
+ // createUser and deleteUser are secured
328
+ expect(createUser?.secured).toBe(true);
329
+ expect(deleteUser?.secured).toBe(true);
330
+ });
331
+ });
332
+ });
@@ -48,7 +48,7 @@ export class ServerLinksProvider {
48
48
  group: action.group,
49
49
  schema: action.options.schema,
50
50
  requestBodyType: action.getBodyContentType(),
51
- secured: action.options.secure ?? true,
51
+ secured: action.options.secure,
52
52
  method: action.method === "GET" ? undefined : action.method,
53
53
  prefix: action.prefix,
54
54
  path: action.path,
@@ -1,11 +1,36 @@
1
1
  import { $module } from "alepha";
2
+ import { FileSystemProvider } from "./providers/FileSystemProvider.ts";
3
+ import { MemoryFileSystemProvider } from "./providers/MemoryFileSystemProvider.ts";
4
+ import { MemoryShellProvider } from "./providers/MemoryShellProvider.ts";
5
+ import { ShellProvider } from "./providers/ShellProvider.ts";
6
+ import { FileDetector } from "./services/FileDetector.ts";
2
7
 
3
8
  export * from "./errors/FileError.ts";
4
9
  export * from "./providers/FileSystemProvider.ts";
5
10
  export * from "./providers/MemoryFileSystemProvider.ts";
6
11
  export * from "./providers/MemoryShellProvider.ts";
7
12
  export * from "./providers/ShellProvider.ts";
13
+ export * from "./services/FileDetector.ts";
8
14
 
9
15
  export const AlephaSystem = $module({
10
16
  name: "alepha.system",
17
+ services: [
18
+ FileDetector,
19
+ FileSystemProvider,
20
+ MemoryFileSystemProvider,
21
+ ShellProvider,
22
+ MemoryShellProvider,
23
+ ],
24
+ register: (alepha) =>
25
+ alepha
26
+ .with({
27
+ optional: true,
28
+ provide: FileSystemProvider,
29
+ use: MemoryFileSystemProvider,
30
+ })
31
+ .with({
32
+ optional: true,
33
+ provide: ShellProvider,
34
+ use: MemoryShellProvider,
35
+ }),
11
36
  });
@@ -0,0 +1 @@
1
+ export * from "./index.browser.ts";
@@ -196,8 +196,16 @@ export interface CpOptions {
196
196
  export interface MkdirOptions {
197
197
  /**
198
198
  * If true, creates parent directories as needed
199
+ *
200
+ * @default true
199
201
  */
200
202
  recursive?: boolean;
203
+ /**
204
+ * If true, does not throw an error if the directory already exists
205
+ *
206
+ * @default true
207
+ */
208
+ force?: boolean;
201
209
  /**
202
210
  * File mode (permission and sticky bits)
203
211
  */
@@ -291,8 +291,17 @@ export class NodeFileSystemProvider implements FileSystemProvider {
291
291
  * await fs.mkdir("/tmp/mydir", { mode: 0o755 });
292
292
  * ```
293
293
  */
294
- async mkdir(path: string, options?: MkdirOptions): Promise<void> {
295
- await fsMkdir(path, options);
294
+ async mkdir(path: string, options: MkdirOptions = {}): Promise<void> {
295
+ const p = fsMkdir(path, {
296
+ recursive: options.recursive ?? true,
297
+ mode: options.mode,
298
+ });
299
+
300
+ if (options.force === false) {
301
+ await p;
302
+ } else {
303
+ await p.catch(() => {});
304
+ }
296
305
  }
297
306
 
298
307
  /**
@@ -23,7 +23,7 @@ export interface BuildServerOptions {
23
23
 
24
24
  /**
25
25
  * Optional client directory name (relative to distDir).
26
- * If provided, the client template will be embedded in the server output.
26
+ * If provided, the SSR manifest will be embedded in the server output.
27
27
  */
28
28
  clientDir?: string;
29
29
 
@@ -167,16 +167,6 @@ export async function buildServer(
167
167
 
168
168
  const entryFile = extractEntryFromBundle(opts.entry, result);
169
169
 
170
- // Embed client template if client was built
171
- let template = "";
172
- if (opts.clientDir) {
173
- const index = await readFile(
174
- `${opts.distDir}/${opts.clientDir}/index.html`,
175
- "utf-8",
176
- );
177
- template = `__alepha.set("alepha.react.server.template", \`${index.replace(/>\s*</g, "><").trim()}\`);\n`;
178
- }
179
-
180
170
  // Embed SSR manifests if client was built
181
171
  // This bundles all manifest data into index.js for serverless deployments
182
172
  let manifest = "";
@@ -230,7 +220,7 @@ export async function buildServer(
230
220
 
231
221
  await writeFile(
232
222
  `${opts.distDir}/index.js`,
233
- `${warning}\n${template}${manifest}import './server/${entryFile}';\n`.trim(),
223
+ `${warning}\n${manifest}import './server/${entryFile}';\n`.trim(),
234
224
  );
235
225
 
236
226
  return { entryFile, manifest: manifestData };
@@ -1,5 +1,8 @@
1
1
  import { access, writeFile } from "node:fs/promises";
2
2
  import { basename, join } from "node:path";
3
+ import type { Alepha } from "alepha";
4
+ import type { CronProvider } from "alepha/scheduler";
5
+ import type { WorkerdCronProvider } from "../../scheduler/providers/WorkerdCronProvider.ts";
3
6
 
4
7
  export interface GenerateCloudflareOptions {
5
8
  /**
@@ -13,6 +16,8 @@ export interface GenerateCloudflareOptions {
13
16
  * Additional Wrangler configuration options to merge into wrangler.jsonc.
14
17
  */
15
18
  config?: WranglerConfig;
19
+
20
+ alepha: Alepha;
16
21
  }
17
22
 
18
23
  export interface WranglerConfig {
@@ -31,7 +36,7 @@ const WARNING_COMMENT =
31
36
  * - worker.js entry point for Cloudflare Workers
32
37
  */
33
38
  export async function generateCloudflare(
34
- opts: GenerateCloudflareOptions = {},
39
+ opts: GenerateCloudflareOptions,
35
40
  ): Promise<void> {
36
41
  const distDir = opts.distDir ?? "dist";
37
42
  const root = process.cwd();
@@ -40,6 +45,15 @@ export async function generateCloudflare(
40
45
  .then(() => true)
41
46
  .catch(() => false);
42
47
 
48
+ let workerdCronProvider: CronProvider | undefined;
49
+ try {
50
+ workerdCronProvider = opts.alepha.inject(
51
+ "CronProvider",
52
+ ) as WorkerdCronProvider;
53
+ } catch {}
54
+
55
+ const crons = workerdCronProvider?.getCronJobs();
56
+
43
57
  const wrangler: WranglerConfig = {
44
58
  name,
45
59
  main: "./main.cloudflare.js",
@@ -62,12 +76,15 @@ export async function generateCloudflare(
62
76
  };
63
77
  }
64
78
 
79
+ if (crons && crons.length > 0) {
80
+ const cronExpressions = [...new Set(crons.map((c) => c.expression))];
81
+ wrangler.triggers ??= {};
82
+ wrangler.triggers.crons = cronExpressions;
83
+ }
84
+
65
85
  const url = process.env.DATABASE_URL;
66
- if (url?.startsWith("cloudflare-d1:")) {
67
- const [name, id] = url
68
- .replace("cloudflare-d1://", "")
69
- .replace("cloudflare-d1:", "")
70
- .split(":");
86
+ if (url?.startsWith("d1:")) {
87
+ const [name, id] = url.replace("d1://", "").replace("d1:", "").split(":");
71
88
  wrangler.d1_databases = wrangler.d1_databases || [];
72
89
  wrangler.d1_databases.push({
73
90
  binding: name,
@@ -75,7 +92,7 @@ export async function generateCloudflare(
75
92
  database_id: id,
76
93
  });
77
94
  wrangler.vars ??= {};
78
- wrangler.vars.DATABASE_URL = `cloudflare-d1://${name}:${id}`;
95
+ wrangler.vars.DATABASE_URL = `d1://${name}:${id}`;
79
96
  }
80
97
 
81
98
  await writeFile(
@@ -102,11 +119,33 @@ export default {
102
119
 
103
120
  __alepha.set("cloudflare.env", env);
104
121
 
105
- await __alepha.start();
122
+ try {
123
+ await __alepha.start();
124
+ } catch (err) {
125
+ console.error("Failed to start Alepha for fetch event", err);
126
+ return new Response("Internal Server Error", { status: 500 });
127
+ }
128
+
106
129
  await __alepha.events.emit("web:request", ctx);
107
130
 
108
131
  return ctx.res;
109
132
  },
133
+
134
+ scheduled: async (event, env, ctx) => {
135
+ __alepha.set("cloudflare.env", env);
136
+
137
+ try {
138
+ await __alepha.start();
139
+ } catch (err) {
140
+ console.error("Failed to start Alepha for scheduled event", err);
141
+ throw err;
142
+ }
143
+
144
+ await __alepha.events.emit("cloudflare:scheduled", {
145
+ cron: event.cron,
146
+ scheduledTime: event.scheduledTime,
147
+ });
148
+ },
110
149
  };
111
150
  `.trim();
112
151
 
@@ -67,6 +67,10 @@ WORKDIR /app
67
67
 
68
68
  COPY . .
69
69
 
70
+ RUN ${command === "bun" ? "bun" : "npm"} install
71
+
72
+ ENV SERVER_HOST=0.0.0.0
73
+
70
74
  CMD ["${command}", "index.js"]
71
75
  `;
72
76