alepha 0.20.3 → 0.20.5

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 (218) hide show
  1. package/dist/api/audits/index.d.ts.map +1 -1
  2. package/dist/api/files/index.d.ts.map +1 -1
  3. package/dist/api/jobs/index.d.ts +14 -14
  4. package/dist/api/jobs/index.d.ts.map +1 -1
  5. package/dist/api/organizations/index.d.ts.map +1 -1
  6. package/dist/api/parameters/index.d.ts +6 -1
  7. package/dist/api/parameters/index.d.ts.map +1 -1
  8. package/dist/api/parameters/index.js +20 -4
  9. package/dist/api/parameters/index.js.map +1 -1
  10. package/dist/api/payments/index.d.ts.map +1 -1
  11. package/dist/api/users/index.browser.js +6 -0
  12. package/dist/api/users/index.browser.js.map +1 -1
  13. package/dist/api/users/index.d.ts +5032 -134
  14. package/dist/api/users/index.d.ts.map +1 -1
  15. package/dist/api/users/index.js +58 -10
  16. package/dist/api/users/index.js.map +1 -1
  17. package/dist/bin/index.js +0 -0
  18. package/dist/bucket/index.d.ts +77 -107
  19. package/dist/bucket/index.d.ts.map +1 -1
  20. package/dist/bucket/index.js +148 -4
  21. package/dist/bucket/index.js.map +1 -1
  22. package/dist/bucket/index.workerd.js +7 -1
  23. package/dist/bucket/index.workerd.js.map +1 -1
  24. package/dist/cache/core/index.d.ts +26 -0
  25. package/dist/cache/core/index.d.ts.map +1 -1
  26. package/dist/cache/core/index.js +11 -1
  27. package/dist/cache/core/index.js.map +1 -1
  28. package/dist/cache/core/index.workerd.js +11 -1
  29. package/dist/cache/core/index.workerd.js.map +1 -1
  30. package/dist/cli/config/index.d.ts +7 -5
  31. package/dist/cli/config/index.d.ts.map +1 -1
  32. package/dist/cli/config/index.js +2 -3
  33. package/dist/cli/config/index.js.map +1 -1
  34. package/dist/cli/core/index.d.ts +419 -12
  35. package/dist/cli/core/index.d.ts.map +1 -1
  36. package/dist/cli/core/index.js +22 -511
  37. package/dist/cli/core/index.js.map +1 -1
  38. package/dist/cli/devtools/index.d.ts +4 -8
  39. package/dist/cli/devtools/index.d.ts.map +1 -1
  40. package/dist/cli/devtools/index.js +13 -15
  41. package/dist/cli/devtools/index.js.map +1 -1
  42. package/dist/cli/platform/index.d.ts +10 -13
  43. package/dist/cli/platform/index.d.ts.map +1 -1
  44. package/dist/cli/platform/index.js +18 -15
  45. package/dist/cli/platform/index.js.map +1 -1
  46. package/dist/cli/vendor/index.d.ts +10 -13
  47. package/dist/cli/vendor/index.d.ts.map +1 -1
  48. package/dist/cli/vendor/index.js +16 -13
  49. package/dist/cli/vendor/index.js.map +1 -1
  50. package/dist/command/index.d.ts +1 -1
  51. package/dist/core/index.browser.js +27 -3
  52. package/dist/core/index.browser.js.map +1 -1
  53. package/dist/core/index.d.ts +6 -3
  54. package/dist/core/index.d.ts.map +1 -1
  55. package/dist/core/index.js +27 -3
  56. package/dist/core/index.js.map +1 -1
  57. package/dist/core/index.native.js +27 -3
  58. package/dist/core/index.native.js.map +1 -1
  59. package/dist/core/index.workerd.js +27 -3
  60. package/dist/core/index.workerd.js.map +1 -1
  61. package/dist/datetime/index.d.ts +69 -10
  62. package/dist/datetime/index.d.ts.map +1 -1
  63. package/dist/datetime/index.js +135 -13
  64. package/dist/datetime/index.js.map +1 -1
  65. package/dist/email/smtp/index.js +10636 -2
  66. package/dist/email/smtp/index.js.map +1 -1
  67. package/dist/fake/index.d.ts +8085 -4
  68. package/dist/fake/index.d.ts.map +1 -1
  69. package/dist/fake/index.js +33554 -3
  70. package/dist/fake/index.js.map +1 -1
  71. package/dist/lock/core/index.d.ts +30 -2
  72. package/dist/lock/core/index.d.ts.map +1 -1
  73. package/dist/lock/core/index.js +35 -12
  74. package/dist/lock/core/index.js.map +1 -1
  75. package/dist/mcp/index.d.ts +238 -31
  76. package/dist/mcp/index.d.ts.map +1 -1
  77. package/dist/mcp/index.js +198 -71
  78. package/dist/mcp/index.js.map +1 -1
  79. package/dist/orm/core/index.browser.js +1 -1
  80. package/dist/orm/core/index.browser.js.map +1 -1
  81. package/dist/orm/core/index.bun.js +4 -3
  82. package/dist/orm/core/index.bun.js.map +1 -1
  83. package/dist/orm/core/index.d.ts +4877 -9
  84. package/dist/orm/core/index.d.ts.map +1 -1
  85. package/dist/orm/core/index.js +4 -3
  86. package/dist/orm/core/index.js.map +1 -1
  87. package/dist/orm/postgres/index.d.ts +608 -1
  88. package/dist/orm/postgres/index.d.ts.map +1 -1
  89. package/dist/react/core/index.d.ts +102 -1
  90. package/dist/react/core/index.d.ts.map +1 -1
  91. package/dist/react/core/index.js +65 -1
  92. package/dist/react/core/index.js.map +1 -1
  93. package/dist/react/form/index.d.ts +6 -0
  94. package/dist/react/form/index.d.ts.map +1 -1
  95. package/dist/react/form/index.js +7 -7
  96. package/dist/react/form/index.js.map +1 -1
  97. package/dist/react/i18n/index.d.ts +7 -1
  98. package/dist/react/i18n/index.d.ts.map +1 -1
  99. package/dist/react/i18n/index.js +6 -0
  100. package/dist/react/i18n/index.js.map +1 -1
  101. package/dist/react/router/index.browser.js +20 -2
  102. package/dist/react/router/index.browser.js.map +1 -1
  103. package/dist/react/router/index.d.ts +36 -4
  104. package/dist/react/router/index.d.ts.map +1 -1
  105. package/dist/react/router/index.js +20 -2
  106. package/dist/react/router/index.js.map +1 -1
  107. package/dist/react/testing/chunk-6Ep1yQYe.js +16 -0
  108. package/dist/react/testing/index.d.ts +411 -1
  109. package/dist/react/testing/index.d.ts.map +1 -1
  110. package/dist/react/testing/index.js +12293 -13
  111. package/dist/react/testing/index.js.map +1 -1
  112. package/dist/react/ui/index.d.ts +195 -1
  113. package/dist/react/ui/index.d.ts.map +1 -1
  114. package/dist/react/ui/index.js +61 -1
  115. package/dist/react/ui/index.js.map +1 -1
  116. package/dist/scheduler/index.d.ts +84 -3
  117. package/dist/scheduler/index.d.ts.map +1 -1
  118. package/dist/scheduler/index.js +390 -1
  119. package/dist/scheduler/index.js.map +1 -1
  120. package/dist/scheduler/index.workerd.js +390 -1
  121. package/dist/scheduler/index.workerd.js.map +1 -1
  122. package/dist/security/index.d.ts +325 -2
  123. package/dist/security/index.d.ts.map +1 -1
  124. package/dist/security/index.js +1361 -2
  125. package/dist/security/index.js.map +1 -1
  126. package/dist/server/auth/index.d.ts +1054 -1
  127. package/dist/server/auth/index.d.ts.map +1 -1
  128. package/dist/server/auth/index.js +1223 -1
  129. package/dist/server/auth/index.js.map +1 -1
  130. package/dist/server/core/index.browser.js +10 -3
  131. package/dist/server/core/index.browser.js.map +1 -1
  132. package/dist/server/core/index.d.ts.map +1 -1
  133. package/dist/server/core/index.js +28 -5
  134. package/dist/server/core/index.js.map +1 -1
  135. package/dist/server/metrics/index.d.ts +514 -1
  136. package/dist/server/metrics/index.d.ts.map +1 -1
  137. package/dist/server/metrics/index.js +4374 -4
  138. package/dist/server/metrics/index.js.map +1 -1
  139. package/dist/server/swagger/index.d.ts.map +1 -1
  140. package/dist/server/swagger/index.js +3 -4
  141. package/dist/server/swagger/index.js.map +1 -1
  142. package/dist/websocket/index.browser.js +11 -5
  143. package/dist/websocket/index.browser.js.map +1 -1
  144. package/dist/websocket/index.d.ts +3 -1
  145. package/dist/websocket/index.d.ts.map +1 -1
  146. package/dist/websocket/index.js +21 -6
  147. package/dist/websocket/index.js.map +1 -1
  148. package/package.json +416 -8
  149. package/src/api/parameters/services/ParameterProvider.ts +21 -4
  150. package/src/api/users/__tests__/SessionService.spec.ts +99 -0
  151. package/src/api/users/__tests__/UserJobs.spec.ts +67 -0
  152. package/src/api/users/atoms/realmAuthSettingsAtom.ts +15 -0
  153. package/src/api/users/entities/sessions.ts +6 -0
  154. package/src/api/users/jobs/UserJobs.ts +44 -17
  155. package/src/api/users/providers/RealmProvider.ts +4 -0
  156. package/src/api/users/services/SessionService.ts +27 -0
  157. package/src/bucket/__tests__/NodeS3BucketProvider.spec.ts +74 -0
  158. package/src/bucket/index.ts +19 -2
  159. package/src/bucket/primitives/$bucket.ts +9 -1
  160. package/src/bucket/providers/CloudflareR2Provider.ts +2 -137
  161. package/src/bucket/providers/NodeS3BucketProvider.ts +218 -0
  162. package/src/cache/core/index.ts +29 -0
  163. package/src/cache/core/primitives/$cache.ts +14 -1
  164. package/src/cli/config/defineConfig.ts +13 -15
  165. package/src/cli/core/__tests__/init.spec.ts +6 -7
  166. package/src/cli/core/services/ProjectScaffolder.ts +18 -14
  167. package/src/cli/core/tasks/BuildCloudflareTask.ts +5 -0
  168. package/src/cli/core/templates/agentMd.ts +2 -10
  169. package/src/cli/core/templates/saasAdminLayoutTsx.ts +3 -3
  170. package/src/cli/devtools/index.ts +12 -26
  171. package/src/cli/platform/index.ts +15 -24
  172. package/src/cli/vendor/atoms/vendorOptions.ts +1 -1
  173. package/src/cli/vendor/index.ts +14 -23
  174. package/src/core/Alepha.ts +11 -1
  175. package/src/core/helpers/ref.ts +18 -0
  176. package/src/core/index.shared.ts +1 -0
  177. package/src/core/providers/SchemaValidator.ts +9 -1
  178. package/src/core/providers/TypeProvider.ts +1 -2
  179. package/src/datetime/REFACTORING.md +118 -0
  180. package/src/datetime/providers/DateTimeProvider.ts +203 -24
  181. package/src/lock/core/index.ts +31 -0
  182. package/src/lock/core/primitives/$lock.ts +14 -1
  183. package/src/mcp/__tests__/jsonrpc.spec.ts +1 -1
  184. package/src/mcp/helpers/jsonrpc.ts +26 -1
  185. package/src/mcp/index.ts +10 -5
  186. package/src/mcp/interfaces/McpTypes.ts +83 -6
  187. package/src/mcp/primitives/$prompt.ts +18 -1
  188. package/src/mcp/primitives/$resource.ts +18 -1
  189. package/src/mcp/primitives/$tool.ts +83 -7
  190. package/src/mcp/providers/McpServerProvider.ts +74 -16
  191. package/src/mcp/transports/StreamableHttpMcpTransport.ts +226 -0
  192. package/src/orm/REFACTORING.md +330 -0
  193. package/src/orm/core/primitives/$transactional.ts +11 -0
  194. package/src/orm/core/schemas/updateSchema.ts +1 -1
  195. package/src/orm/core/services/PgRelationManager.ts +4 -2
  196. package/src/react/core/__tests__/useQuery.browser.spec.tsx +86 -0
  197. package/src/react/core/hooks/useQuery.ts +153 -0
  198. package/src/react/core/index.ts +1 -0
  199. package/src/react/form/services/FormModel.ts +15 -6
  200. package/src/react/form/services/parseField.ts +8 -0
  201. package/src/react/i18n/providers/I18nProvider.ts +8 -2
  202. package/src/react/router/__tests__/$page.spec.tsx +0 -16
  203. package/src/react/router/__tests__/ssr.spec.tsx +339 -0
  204. package/src/react/router/primitives/$page.ts +28 -4
  205. package/src/react/router/providers/ReactPageProvider.ts +27 -9
  206. package/src/react/ui/atoms/uiThemeListAtom.ts +36 -0
  207. package/src/react/ui/index.ts +6 -0
  208. package/src/react/ui/services/SchemaControl.ts +209 -0
  209. package/src/security/primitives/$issuer.ts +6 -3
  210. package/src/server/core/__tests__/ServerRouterProvider-serializationError.spec.ts +75 -0
  211. package/src/server/core/__tests__/ServerRouterProvider-validationError.spec.ts +306 -0
  212. package/src/server/core/errors/ValidationError.ts +13 -1
  213. package/src/server/core/primitives/$action.ts +16 -5
  214. package/src/server/core/providers/ServerRouterProvider.ts +26 -4
  215. package/src/server/swagger/providers/ServerSwaggerProvider.ts +5 -7
  216. package/src/websocket/providers/NodeWebSocketServerProvider.ts +10 -4
  217. package/src/websocket/services/WebSocketClient.ts +11 -5
  218. package/src/mcp/transports/SseMcpTransport.ts +0 -182
@@ -0,0 +1,218 @@
1
+ import { Buffer } from "node:buffer";
2
+ import {
3
+ $env,
4
+ $hook,
5
+ $inject,
6
+ Alepha,
7
+ AlephaError,
8
+ type FileLike,
9
+ type Static,
10
+ t,
11
+ } from "alepha";
12
+ import { $logger } from "alepha/logger";
13
+ import { FileDetector, FileSystemProvider } from "alepha/system";
14
+ import { S3mini } from "s3mini";
15
+ import { FileNotFoundError } from "../errors/FileNotFoundError.ts";
16
+ import { $bucket } from "../primitives/$bucket.ts";
17
+ import type { FileStorageProvider } from "./FileStorageProvider.ts";
18
+
19
+ const envSchema = t.object({
20
+ /**
21
+ * S3 endpoint URL. The bucket name is appended (path-style) per request.
22
+ *
23
+ * Examples:
24
+ * - AWS S3: `https://s3.us-east-1.amazonaws.com`
25
+ * - Cloudflare R2: `https://<account-id>.r2.cloudflarestorage.com`
26
+ * - MinIO: `http://localhost:9000`
27
+ * - DigitalOcean Spaces: `https://<region>.digitaloceanspaces.com`
28
+ */
29
+ S3_ENDPOINT: t.string(),
30
+
31
+ /**
32
+ * AWS region or "auto" for R2.
33
+ *
34
+ * @default "auto"
35
+ */
36
+ S3_REGION: t.optional(t.string()),
37
+
38
+ /**
39
+ * Access key ID for S3 authentication.
40
+ */
41
+ S3_ACCESS_KEY_ID: t.string(),
42
+
43
+ /**
44
+ * Secret access key for S3 authentication.
45
+ */
46
+ S3_SECRET_ACCESS_KEY: t.string(),
47
+ });
48
+
49
+ declare module "alepha" {
50
+ interface Env extends Partial<Static<typeof envSchema>> {}
51
+ }
52
+
53
+ /**
54
+ * S3-compatible file storage provider for Node.js.
55
+ *
56
+ * Backed by `s3mini` (zero-dep, ~20 KB). Works with AWS S3, Cloudflare R2,
57
+ * MinIO, DigitalOcean Spaces, Backblaze B2, and any other S3-compatible service.
58
+ *
59
+ * Uses path-style addressing (`<endpoint>/<bucket>`).
60
+ */
61
+ export class NodeS3BucketProvider implements FileStorageProvider {
62
+ protected readonly log = $logger();
63
+ protected readonly env = $env(envSchema);
64
+ protected readonly alepha = $inject(Alepha);
65
+ protected readonly fileSystem = $inject(FileSystemProvider);
66
+ protected readonly fileDetector = $inject(FileDetector);
67
+ protected readonly clients: Map<string, S3mini> = new Map();
68
+
69
+ /**
70
+ * Convert bucket name to S3-compatible format.
71
+ * S3 bucket names must be lowercase, 3-63 characters, no underscores.
72
+ */
73
+ public convertName(name: string): string {
74
+ return name.replaceAll("/", "-").replaceAll("_", "-").toLowerCase();
75
+ }
76
+
77
+ protected getClient(bucketName: string): S3mini {
78
+ const name = this.convertName(bucketName);
79
+ let client = this.clients.get(name);
80
+ if (!client) {
81
+ const endpoint = this.env.S3_ENDPOINT.replace(/\/+$/, "");
82
+ client = new S3mini({
83
+ accessKeyId: this.env.S3_ACCESS_KEY_ID,
84
+ secretAccessKey: this.env.S3_SECRET_ACCESS_KEY,
85
+ region: this.env.S3_REGION || "auto",
86
+ endpoint: `${endpoint}/${name}`,
87
+ });
88
+ this.clients.set(name, client);
89
+ }
90
+ return client;
91
+ }
92
+
93
+ protected readonly onStart = $hook({
94
+ on: "start",
95
+ handler: async () => {
96
+ for (const bucket of this.alepha.primitives($bucket)) {
97
+ if (bucket.provider !== this) {
98
+ continue;
99
+ }
100
+
101
+ const name = this.convertName(bucket.name);
102
+ const client = this.getClient(bucket.name);
103
+
104
+ this.log.debug(`Preparing S3 bucket '${name}'...`);
105
+
106
+ const exists = await client.bucketExists();
107
+ if (!exists) {
108
+ this.log.debug(`Creating S3 bucket '${name}'...`);
109
+ const created = await client.createBucket();
110
+ if (!created) {
111
+ throw new AlephaError(`Failed to create S3 bucket '${name}'`);
112
+ }
113
+ }
114
+
115
+ this.log.info(`S3 bucket '${bucket.name}' OK`);
116
+ }
117
+ },
118
+ });
119
+
120
+ protected createId(mimeType: string): string {
121
+ const ext = this.fileDetector.getExtensionFromMimeType(mimeType);
122
+ return `${crypto.randomUUID()}.${ext}`;
123
+ }
124
+
125
+ public async upload(
126
+ bucketName: string,
127
+ file: FileLike,
128
+ fileId?: string,
129
+ ): Promise<string> {
130
+ fileId ??= this.createId(file.type);
131
+
132
+ this.log.trace(
133
+ `Uploading file '${file.name}' to bucket '${bucketName}' with id '${fileId}'...`,
134
+ );
135
+
136
+ const client = this.getClient(bucketName);
137
+
138
+ try {
139
+ const buffer = new Uint8Array(await file.arrayBuffer());
140
+ await client.putObject(
141
+ fileId,
142
+ buffer,
143
+ file.type || "application/octet-stream",
144
+ undefined,
145
+ { "x-amz-meta-name": encodeURIComponent(file.name) },
146
+ file.size,
147
+ );
148
+
149
+ this.log.trace(`File uploaded successfully: ${fileId}`);
150
+ return fileId;
151
+ } catch (error) {
152
+ this.log.error(`Failed to upload file: ${error}`);
153
+ if (error instanceof Error) {
154
+ throw new AlephaError(`Upload failed: ${error.message}`, {
155
+ cause: error,
156
+ });
157
+ }
158
+ throw error;
159
+ }
160
+ }
161
+
162
+ public async download(bucketName: string, fileId: string): Promise<FileLike> {
163
+ this.log.trace(
164
+ `Downloading file '${fileId}' from bucket '${bucketName}'...`,
165
+ );
166
+
167
+ const client = this.getClient(bucketName);
168
+ const response = await client.getObjectResponse(fileId);
169
+
170
+ if (!response) {
171
+ throw new FileNotFoundError(
172
+ `File '${fileId}' not found in bucket '${bucketName}'`,
173
+ );
174
+ }
175
+
176
+ const buffer = Buffer.from(await response.arrayBuffer());
177
+
178
+ const mimeType =
179
+ response.headers.get("content-type") ||
180
+ this.fileDetector.getContentType(fileId);
181
+
182
+ const metaName = response.headers.get("x-amz-meta-name");
183
+ const name = metaName ? decodeURIComponent(metaName) : fileId;
184
+
185
+ return this.fileSystem.createFile({
186
+ buffer,
187
+ name,
188
+ type: mimeType,
189
+ size: buffer.length,
190
+ });
191
+ }
192
+
193
+ public async exists(bucketName: string, fileId: string): Promise<boolean> {
194
+ this.log.trace(
195
+ `Checking existence of file '${fileId}' in bucket '${bucketName}'...`,
196
+ );
197
+
198
+ const client = this.getClient(bucketName);
199
+ const result = await client.objectExists(fileId);
200
+ return result === true;
201
+ }
202
+
203
+ public async delete(bucketName: string, fileId: string): Promise<void> {
204
+ this.log.trace(`Deleting file '${fileId}' from bucket '${bucketName}'...`);
205
+
206
+ const client = this.getClient(bucketName);
207
+
208
+ try {
209
+ await client.deleteObject(fileId);
210
+ } catch (error) {
211
+ this.log.error(`Failed to delete file: ${error}`);
212
+ if (error instanceof Error) {
213
+ throw new FileNotFoundError("Error deleting file", { cause: error });
214
+ }
215
+ throw error;
216
+ }
217
+ }
218
+ }
@@ -12,6 +12,35 @@ export * from "./providers/MemoryCacheProvider.ts";
12
12
 
13
13
  // ---------------------------------------------------------------------------------------------------------------------
14
14
 
15
+ declare module "alepha" {
16
+ interface Hooks {
17
+ /**
18
+ * Fires when a cache lookup finds a value.
19
+ */
20
+ "cache:hit": {
21
+ container: string;
22
+ key: string;
23
+ };
24
+ /**
25
+ * Fires when a cache lookup does not find a value.
26
+ */
27
+ "cache:miss": {
28
+ container: string;
29
+ key: string;
30
+ };
31
+ /**
32
+ * Fires when a value is written to the cache.
33
+ */
34
+ "cache:set": {
35
+ container: string;
36
+ key: string;
37
+ ttlMs?: number;
38
+ };
39
+ }
40
+ }
41
+
42
+ // ---------------------------------------------------------------------------------------------------------------------
43
+
15
44
  /**
16
45
  * Type-safe caching with TTL support.
17
46
  *
@@ -239,6 +239,12 @@ export class CachePrimitive<
239
239
  ttl: px > 0 ? px : undefined,
240
240
  compress: this.options.compress,
241
241
  });
242
+
243
+ await this.alepha.events.emit("cache:set", {
244
+ container: this.container,
245
+ key,
246
+ ttlMs: px > 0 ? px : undefined,
247
+ });
242
248
  }
243
249
 
244
250
  public async get(key: string): Promise<TReturn | undefined> {
@@ -250,7 +256,14 @@ export class CachePrimitive<
250
256
  return undefined;
251
257
  }
252
258
 
253
- return this.provider.getTyped<TReturn>(this.container, key);
259
+ const value = await this.provider.getTyped<TReturn>(this.container, key);
260
+
261
+ await this.alepha.events.emit(
262
+ value === undefined ? "cache:miss" : "cache:hit",
263
+ { container: this.container, key },
264
+ );
265
+
266
+ return value;
254
267
  }
255
268
 
256
269
  protected $provider(): CacheProvider {
@@ -1,4 +1,4 @@
1
- import type { Alepha } from "alepha";
1
+ import type { Alepha, Service } from "alepha";
2
2
  import {
3
3
  type AppEntryOptions,
4
4
  appEntryOptions,
@@ -10,15 +10,6 @@ import {
10
10
 
11
11
  // ---------------------------------------------------------------------------------------------------------------------
12
12
 
13
- export type AlephaCliConfigPlugin = (
14
- config: AlephaCliConfig,
15
- alepha: Alepha,
16
- ) => void;
17
-
18
- export const cliConfigPlugins: AlephaCliConfigPlugin[] = [];
19
-
20
- // ---------------------------------------------------------------------------------------------------------------------
21
-
22
13
  export interface AlephaCliConfig {
23
14
  /**
24
15
  * Override entry paths.
@@ -28,7 +19,12 @@ export interface AlephaCliConfig {
28
19
  /**
29
20
  * Register more services to the Alepha CLI (enhancements, commands, etc.).
30
21
  */
31
- services?: Array<any>;
22
+ services?: Array<Service>;
23
+
24
+ /**
25
+ * @alias services Register more services to the Alepha CLI (enhancements, commands, etc.).
26
+ */
27
+ plugins?: Array<Service>;
32
28
 
33
29
  /**
34
30
  * Configure Alepha build command.
@@ -58,6 +54,12 @@ export const defineConfig = (config: AlephaCliConfig) => {
58
54
  }
59
55
  }
60
56
 
57
+ if (config.plugins) {
58
+ for (const it of config.plugins) {
59
+ alepha.with(it);
60
+ }
61
+ }
62
+
61
63
  if (config.env) {
62
64
  for (const [key, value] of Object.entries(config.env)) {
63
65
  process.env[key] = String(value);
@@ -76,10 +78,6 @@ export const defineConfig = (config: AlephaCliConfig) => {
76
78
  alepha.set(appEntryOptions, config.entry);
77
79
  }
78
80
 
79
- for (const plugin of cliConfigPlugins) {
80
- plugin(config, alepha);
81
- }
82
-
83
81
  return {};
84
82
  };
85
83
  };
@@ -98,25 +98,24 @@ describe("alepha init", () => {
98
98
  // ─────────────────────────────────────────────────────────────────────────────
99
99
 
100
100
  describe("AI agent files", () => {
101
- it("should create CLAUDE.md when claude CLI is installed", async () => {
102
- const { fs, shell, cli, cmd, json } = createTestEnv();
101
+ it("should create both AGENTS.md and CLAUDE.md", async () => {
102
+ const { fs, cli, cmd, json } = createTestEnv();
103
103
  await setupProject(fs, json);
104
- shell.installedCommands.add("claude");
105
104
 
106
105
  await cli.run(cmd.init, { root: "/project" });
107
106
 
107
+ expect(fs.wasWritten("/project/AGENTS.md")).toBe(true);
108
108
  expect(fs.wasWritten("/project/CLAUDE.md")).toBe(true);
109
- expect(fs.wasWritten("/project/AGENTS.md")).toBe(false);
110
109
  });
111
110
 
112
- it("should create AGENTS.md when claude CLI is not installed", async () => {
111
+ it("should write CLAUDE.md as a stub importing AGENTS.md", async () => {
113
112
  const { fs, cli, cmd, json } = createTestEnv();
114
113
  await setupProject(fs, json);
115
114
 
116
115
  await cli.run(cmd.init, { root: "/project" });
117
116
 
118
- expect(fs.wasWritten("/project/AGENTS.md")).toBe(true);
119
- expect(fs.wasWritten("/project/CLAUDE.md")).toBe(false);
117
+ const claude = await fs.readTextFile("/project/CLAUDE.md");
118
+ expect(claude.trim()).toBe("@AGENTS.md");
120
119
  });
121
120
 
122
121
  it("should include Alepha instructions in agent file", async () => {
@@ -3,7 +3,7 @@ import { $inject, AlephaError } from "alepha";
3
3
  import type { RunnerMethod } from "alepha/command";
4
4
  import { $logger, ConsoleColorProvider } from "alepha/logger";
5
5
  import { FileSystemProvider, ShellProvider } from "alepha/system";
6
- import { type AgentMdOptions, agentMd } from "../templates/agentMd.ts";
6
+ import { agentMd } from "../templates/agentMd.ts";
7
7
  import { alephaConfigTs } from "../templates/alephaConfigTs.ts";
8
8
  import { apiHelloControllerTs } from "../templates/apiHelloControllerTs.ts";
9
9
  import { apiHelloResponseSchemaTs } from "../templates/apiHelloResponseSchemaTs.ts";
@@ -94,7 +94,7 @@ export class ProjectScaffolder {
94
94
  tsconfigJson?: boolean | "local";
95
95
  biomeJson?: boolean;
96
96
  editorconfig?: boolean;
97
- agentMd?: false | AgentMdOptions;
97
+ agentMd?: boolean;
98
98
  },
99
99
  ): Promise<void> {
100
100
  const tasks: Promise<void>[] = [];
@@ -126,7 +126,7 @@ export class ProjectScaffolder {
126
126
  tasks.push(this.ensureEditorConfig(root, { force, checkWorkspace }));
127
127
  }
128
128
  if (opts.agentMd) {
129
- tasks.push(this.ensureAgentMd(root, { ...opts.agentMd, force }));
129
+ tasks.push(this.ensureAgentMd(root, { force }));
130
130
  }
131
131
 
132
132
  await Promise.all(tasks);
@@ -214,12 +214,19 @@ export class ProjectScaffolder {
214
214
  return true;
215
215
  }
216
216
 
217
+ /**
218
+ * Ensure AGENTS.md (cross-tool standard, canonical source) exists, with a
219
+ * CLAUDE.md stub that imports it via Claude Code's `@` syntax. Single
220
+ * source of truth, cross-platform, no symlink needed.
221
+ */
217
222
  public async ensureAgentMd(
218
223
  root: string,
219
- options: AgentMdOptions & { force?: boolean },
224
+ options: { force?: boolean } = {},
220
225
  ): Promise<void> {
221
- const filename = options.type === "claude" ? "CLAUDE.md" : "AGENTS.md";
222
- await this.ensureFile(root, filename, agentMd(options), options.force);
226
+ await Promise.all([
227
+ this.ensureFile(root, "AGENTS.md", agentMd(), options.force),
228
+ this.ensureFile(root, "CLAUDE.md", "@AGENTS.md\n", options.force),
229
+ ]);
223
230
  }
224
231
 
225
232
  /**
@@ -602,12 +609,9 @@ export class ProjectScaffolder {
602
609
  // Detect workspace context (are we inside packages/ or apps/ of a monorepo?)
603
610
  const workspace = await this.pm.getWorkspaceContext(root);
604
611
 
605
- // Detect agent type: claude CLI CLAUDE.md, else AGENTS.md
606
- let agentType: "claude" | "agents" | false = false;
607
- if (!workspace.isPackage) {
608
- const hasClaudeCli = await this.utils.isInstalledAsync("claude");
609
- agentType = hasClaudeCli ? "claude" : "agents";
610
- }
612
+ // Always emit both AGENTS.md and CLAUDE.md at project roots (skip for
613
+ // monorepo sub-packages where agent files live at workspace root).
614
+ const writeAgentMd = !workspace.isPackage;
611
615
 
612
616
  const isExpo = await this.pm.hasExpo(root);
613
617
 
@@ -626,7 +630,7 @@ export class ProjectScaffolder {
626
630
  tsconfigJson: f.shadcn ? "local" : !workspace.config.tsconfigJson,
627
631
  biomeJson: true,
628
632
  editorconfig: !workspace.config.editorconfig,
629
- agentMd: agentType ? { type: agentType } : false,
633
+ agentMd: writeAgentMd,
630
634
  });
631
635
 
632
636
  // Create alepha.config.ts with documented options
@@ -729,7 +733,7 @@ export class ProjectScaffolder {
729
733
  // pulls its peer primitives + dependencies (sonner, etc.).
730
734
  if (flags.saas) {
731
735
  // Pull the public SaaS bundle in one shot — it aggregates control,
732
- // auto-form, alepha-table, use-confirm, app-shell, every auth-*, and
736
+ // auto-form, alepha-table, use-dialog, app-shell, every auth-*, and
733
737
  // every admin-* block. Definition lives at
734
738
  // https://alepha.dev/r/saas.json (see @alepha/ui-registry).
735
739
  // `--yes --overwrite` is the only combo that works non-interactively
@@ -73,6 +73,11 @@ export class BuildCloudflareTask extends BuildTask {
73
73
  };
74
74
  }
75
75
 
76
+ wrangler.observability ??= {
77
+ enabled: true,
78
+ head_sampling_rate: 1,
79
+ };
80
+
76
81
  this.enhanceDomain(wrangler);
77
82
  this.enhanceCron(ctx, wrangler);
78
83
  this.enhanceDatabase(wrangler);
@@ -1,13 +1,5 @@
1
- export type AgentMdType = "claude" | "agents";
2
-
3
- export interface AgentMdOptions {
4
- type: AgentMdType;
5
- }
6
-
7
- export const agentMd = (options: AgentMdOptions): string => {
8
- const header = options.type === "claude" ? `# CLAUDE.md` : `# AGENTS.md`;
9
-
10
- return `${header}
1
+ export const agentMd = (): string => {
2
+ return `# AGENTS.md
11
3
 
12
4
  This is an **Alepha** project.
13
5
 
@@ -11,7 +11,7 @@ export const saasAdminLayoutTsx = () =>
11
11
  `import { AppShell } from "@/components/app-shell";
12
12
  import { Toaster } from "@/components/ui/sonner";
13
13
  import { TooltipProvider } from "@/components/ui/tooltip";
14
- import { ConfirmProvider } from "@/components/use-confirm";
14
+ import { DialogProvider } from "@/components/use-dialog";
15
15
  import { NestedView, useRouterState } from "alepha/react/router";
16
16
  import { ShieldCheck, Users } from "lucide-react";
17
17
 
@@ -39,7 +39,7 @@ const AdminLayout = () => {
39
39
 
40
40
  return (
41
41
  <TooltipProvider>
42
- <ConfirmProvider>
42
+ <DialogProvider>
43
43
  <AppShell
44
44
  brand={
45
45
  <a
@@ -68,7 +68,7 @@ const AdminLayout = () => {
68
68
  <NestedView />
69
69
  </AppShell>
70
70
  <Toaster />
71
- </ConfirmProvider>
71
+ </DialogProvider>
72
72
  </TooltipProvider>
73
73
  );
74
74
  };
@@ -1,9 +1,8 @@
1
1
  import { readFile } from "node:fs/promises";
2
2
  import { createRequire } from "node:module";
3
3
  import { dirname, join } from "node:path";
4
- import { $module, AlephaError } from "alepha";
4
+ import { $context, $module, AlephaError } from "alepha";
5
5
  import { ViteDevServerProvider } from "alepha/cli";
6
- import { cliConfigPlugins } from "alepha/cli/config";
7
6
  import {
8
7
  type DevtoolsOptions,
9
8
  devtoolsOptions,
@@ -11,14 +10,6 @@ import {
11
10
 
12
11
  // ---------------------------------------------------------------------------------------------------------------------
13
12
 
14
- declare module "alepha/cli/config" {
15
- interface AlephaCliConfig {
16
- devtools?: DevtoolsOptions;
17
- }
18
- }
19
-
20
- // ---------------------------------------------------------------------------------------------------------------------
21
-
22
13
  const DEVTOOLS_OVERLAY_SCRIPT = `
23
14
  (function () {
24
15
  if (window.__alepha_devtools_injected) return;
@@ -29,23 +20,19 @@ const DEVTOOLS_OVERLAY_SCRIPT = `
29
20
  // Button
30
21
  const btn = document.createElement("button");
31
22
  btn.id = "alepha-devtools-btn";
32
- btn.innerHTML = \`<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22c5.523 0 10-4.477 10-10S17.523 2 12 2 2 6.477 2 12s4.477 10 10 10z"/><path d="m14.7 6.3-5.4 5.4a2.1 2.1 0 1 0 3 3l5.4-5.4a2.1 2.1 0 1 0-3-3z"/><path d="m8 16 1.5-1.5"/></svg>\`;
23
+ btn.innerHTML = \`<svg width="28" height="28" viewBox="0 0 300 300" xmlns="http://www.w3.org/2000/svg"><defs><linearGradient id="adb" x1="142.3" x2="159.6" y1="123.3" y2="176.7" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#33a72c"/><stop offset="1" stop-color="#2d8d40"/></linearGradient><linearGradient id="adc" x1="61.6" x2="100.7" y1="218.5" y2="174" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#298e35"/><stop offset="1" stop-color="#327952"/></linearGradient><linearGradient id="add" x1="262.7" x2="242.2" y1="178.4" y2="220.1" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#32a62d"/><stop offset="1" stop-color="#2d8d40"/></linearGradient><linearGradient id="ade" x1="81.2" x2="69.1" y1="126.7" y2="92.3" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#299a2c"/><stop offset="1" stop-color="#51be40"/></linearGradient><clipPath id="ada"><path fill="none" d="M0 0h300v241H0z"/></clipPath></defs><g fill="none" stroke-miterlimit="10" clip-path="url(#ada)"><path fill="url(#adb)" d="M54 174a182 182 0 0 1 106-51c19-2 37-6 54-15l16-11 3-4 1 1 1 4 1 6v9c0 6-2 12-5 18l-1 4-7 8c-5 7-12 14-19 19l-4 2-11 7v1l-1-1-3 2a120 120 0 0 1-30 8l-8 1-20-1c-21-3-44-10-64 0-5 2-9 4-12 8l-9 9h1l14-9c7-4 15-6 23-7h7l25 3 3 1-2 5-3 5c-4 7-10 14-15 18l-4 3c-12 8-25 12-39 13h-5l-5 1h-8l-7-1H15c0-3 4-7 5-9l7-11 5-8c6-10 14-19 22-28z"/><path fill="#1d524a" d="M178 141c9-1 17-4 24-8l8-4-6 4c-9 7-20 11-32 13l-39 5h-5l-15 3 16 13 18 15-20-1c-21-3-44-10-64 0-5 2-9 4-12 8l-9 9h1l14-9c7-4 15-6 23-7h7l25 3 3 1-2 5-3 5c-4 7-10 14-15 18l-4 3c-12 8-25 12-39 13h-5l-5 1h-8l-7-1H15c0-3 4-7 5-9l7-11 5-8c6-10 14-19 22-28l4-2 23-14 11-5c21-7 43-9 65-9l21-3z"/><path fill="url(#adc)" d="M57 189c7-4 15-6 23-7h7l25 3 3 1-2 5-3 5c-4 7-10 14-15 18l-4 3c-12 8-25 12-39 13h-5l-4-9-2-5 5-2 9-4 10-5 10-4c5-2 10-3 14-6l3-1h-1a1136 1136 0 0 0-43 14c1-6 6-14 9-19 1 0 0 0 0 0z"/><path fill="#2d6f4d" d="m41 216 5-2 9-4 5 3c9 4 18 6 27 4h4c-12 8-25 12-39 13h-5l-4-9-2-5zm16-27s1 0 0 0c-3 5-8 13-9 19l-15 8c0-3 1-9 3-11l3-3 4-4 14-9z"/><path fill="#49b63b" d="M42 198h1l-4 4 3-4z"/><path fill="#2d6f4d" d="m113 154 15-3 11 2 18 8c11 6 23 8 35 5l8-2-11 7v1l-1-1-3 2a120 120 0 0 1-30 8l-8 1-18-15-16-13z"/><path fill="#49b63b" d="M81 158c2-4 11-7 15-8 18-6 36-7 55-8l12-1h15l-21 3c-22 0-44 2-65 9l-11 5z"/><path fill="#2d913b" d="m110 52 2-5 9-14c7-8 19-14 29-18l10-3 12-6 1-1 1 1 1 5v6l6 12 5 10 12 26 2 5 4 10 7 17 4 8c-9 5-20 9-30 12l-16 3-7-14-6-14-5-10-1-3-4 7-13 27-6 11-6 2-24 9 2-4c11-16 14-36 11-56l-3 1-9 5 1-3 9-21 2-5z"/><path fill="#2d6f4d" d="m175 17 6 12 5 10 12 26 2 5 4 10 7 17 4 8c-9 5-20 9-30 12l-16 3-7-14-6-14-5-10-1-3c0-2 5-8 6-10l1-2c9-13 17-28 18-45v-5z"/><path fill="#1d524a" d="m175 17 6 12 5 10 12 26 2 5-3-1-13-8-7 4 3 7-4 1-2-3-5-12-5 7a136 136 0 0 1-10 15l-3 2-1-3c0-2 5-8 6-10l1-2c9-13 17-28 18-45v-5z"/><path fill="#2d6f4d" d="m186 39 12 26 2 5-3-1-13-8-7 4-6-12 8-7 7-7z"/><path fill="#2d913b" d="m177 65 7-4 13 8 3 1 4 10-3 7-7 14-2 8-1-4-6-19a99 99 0 0 0-8-21zm-3 5 2 3 12 32h-1l-8 5c-4 1-9 0-13-2l-1-1c-1-8 2-15 4-22 1-5 2-10 5-15z"/><path fill="#1d524a" d="m110 52 2-5 9-14c7-8 19-14 29-18l10-3 12-6 1-1 1 1c-3 1-3 2-4 4l-11 14-5 5v1c-11 11-22 24-31 37l-5 6-6 11h1l6 12c3 7 8 13 14 17l-6 11-6 2-24 9 2-4c11-16 14-36 11-56l-3 1-9 5 1-3 9-21 2-5z"/><path fill="#49b63b" d="m110 52 2-5 9-14c7-8 19-14 29-18l10-3 12-6 1-1 1 1c-3 1-3 2-4 4l-11 14-5 5-8 7-13 11-5 6c-5-2-10-5-15-3l-3 2z"/><path fill="#2d913b" d="m110 52 3-2c5-2 10 1 15 3l-12 13-6 9-3 1-9 5 1-3 9-21 2-5z"/><path fill="#2d6f4d" d="M121 92c10-5 19-9 28-17l7-6c-1 2-6 8-6 10l-4 7-13 27c-6-4-11-10-14-17l-6-12 4-5 4 13z"/><path fill="#49b63b" d="m154 30-1 11c0 4-3 9-6 12-6 9-19 10-25 19-1 0-3 2-4 1l5-6c9-13 20-26 31-37z"/><path fill="url(#add)" d="m230 135 3 6 11 17 2 5 4 5 3 6 9 14 19 36 4 5c-2 2-19 2-22 2l-5-1h-2c-12 0-25-4-35-10l-4-3c-10-8-15-16-21-27l-7-18v-1l11-7 4-2c7-5 14-12 19-19l7-8z"/><path fill="#1d524a" d="m230 135 3 6 11 17 2 5 4 5 3 6h-1c-10-8-20-9-32-4l23 23 13 12 1 2-20-14-13-12-4-3-5-6c-2 0-2 3-2 5l-1 10c-1 8-2 11 1 19l2 6 2 5c-10-8-15-16-21-27l-7-18v-1l11-7 4-2c7-5 14-12 19-19l7-8z"/><path fill="#2d6f4d" d="m233 141 11 17 2 5 4 5 3 6h-1c-10-8-20-9-32-4l-7-9 15-15 5-5zm-16 76-2-5-2-6c-3-8-2-11-1-19l1-10c0-2 0-5 2-5l5 6 4 3h-1c0 4 8 22 11 26 6 10 15 18 25 22 2 0 3 0 4 2l-5-1h-2c-12 0-25-4-35-10l-4-3z"/><path fill="#2d913b" d="M110 75c3 20 0 40-11 56l-2 4a145 145 0 0 0-43 31l-5 4-11 15v-7l-1-10a143 143 0 0 1 5-29l9-20c13-18 32-29 52-37l4-3 3-4z"/><path fill="url(#ade)" d="m42 139 9-20c13-18 32-29 52-37l4-3-3 7c-4 5-8 11-13 15l-3 3c-6 6-13 12-18 19l-8 11-20 5z"/><path fill="#1d524a" d="M91 101c0 2-4 6-6 8-5 5-10 11-14 18l-9 13-4 9 5 1c4 0 16-9 19-7-10 7-20 14-28 23l-5 4-11 15v-7l-1-10a143 143 0 0 1 5-29l20-5 8-11c5-7 12-13 18-19l3-3z"/><path fill="#2d6f4d" d="m62 134-9 16-3 7-2 4c-3 3-8 5-10 8l-1-1a143 143 0 0 1 5-29l20-5zm0 6 11-5 7-4c5-1 14-2 19 0l-2 4-15 8c-3-2-15 7-19 7l-5-1 4-9z"/></g></svg>\`;
33
24
  Object.assign(btn.style, {
34
- position: "fixed", bottom: "16px", right: "16px", zIndex: "99998",
25
+ position: "fixed", bottom: "16px", left: "16px", zIndex: "99998",
35
26
  width: "40px", height: "40px", borderRadius: "50%",
36
- background: "#1a1a2e", color: "#a0a0c0", border: "1px solid #2a2a4a",
27
+ background: "#fff", border: "1px solid #2a2a4a",
37
28
  cursor: "pointer", display: "flex", alignItems: "center", justifyContent: "center",
38
29
  boxShadow: "0 2px 8px rgba(0,0,0,0.3)", transition: "all 0.2s",
39
30
  padding: "0", fontSize: "0",
40
31
  });
41
32
  btn.addEventListener("mouseenter", () => {
42
- btn.style.background = "#2a2a4e";
43
- btn.style.color = "#c0c0e0";
44
33
  btn.style.transform = "scale(1.1)";
45
34
  });
46
35
  btn.addEventListener("mouseleave", () => {
47
- btn.style.background = "#1a1a2e";
48
- btn.style.color = "#a0a0c0";
49
36
  btn.style.transform = "scale(1)";
50
37
  });
51
38
 
@@ -110,10 +97,10 @@ const DEVTOOLS_OVERLAY_SCRIPT = `
110
97
  *
111
98
  * Usage in `alepha.config.ts`:
112
99
  * ```ts
113
- * import { AlephaCliDevtoolsPlugin } from "alepha/devtools/plugin";
100
+ * import { devtools } from "alepha/cli/devtools";
114
101
  *
115
102
  * export default defineConfig({
116
- * services: [AlephaCliDevtoolsPlugin],
103
+ * plugins: [devtools()],
117
104
  * });
118
105
  * ```
119
106
  *
@@ -201,13 +188,12 @@ export const AlephaCliDevtoolsPlugin = $module({
201
188
  },
202
189
  });
203
190
 
204
- // ---------------------------------------------------------------------------------------------------------------------
205
-
206
- cliConfigPlugins.push((config, alepha) => {
207
- if (config.devtools) {
208
- alepha.set(devtoolsOptions, config.devtools);
209
- }
210
- });
191
+ export const devtools = (options: DevtoolsOptions = {}) => {
192
+ return () => {
193
+ const { alepha } = $context();
194
+ alepha.with(AlephaCliDevtoolsPlugin).set(devtoolsOptions, options);
195
+ };
196
+ };
211
197
 
212
198
  // ---------------------------------------------------------------------------------------------------------------------
213
199
 
@@ -1,6 +1,5 @@
1
- import { $module } from "alepha";
1
+ import { $context, $module } from "alepha";
2
2
  import { AlephaCli } from "alepha/cli";
3
- import { cliConfigPlugins } from "alepha/cli/config";
4
3
  import { CloudflareAdapter } from "./adapters/CloudflareAdapter.ts";
5
4
  import { VercelAdapter } from "./adapters/VercelAdapter.ts";
6
5
  import {
@@ -23,14 +22,6 @@ import { WranglerApi } from "./services/WranglerApi.ts";
23
22
 
24
23
  // ---------------------------------------------------------------------------
25
24
 
26
- declare module "alepha/cli/config" {
27
- interface AlephaCliConfig {
28
- platform?: PlatformOptions;
29
- }
30
- }
31
-
32
- // ---------------------------------------------------------------------------
33
-
34
25
  /**
35
26
  * CLI plugin for multi-cloud deployment orchestration.
36
27
  *
@@ -51,15 +42,16 @@ declare module "alepha/cli/config" {
51
42
  * Configuration in `alepha.config.ts`:
52
43
  *
53
44
  * ```typescript
54
- * import { AlephaCliPlatformPlugin } from "alepha/cli/platform";
45
+ * import { platform } from "alepha/cli/platform";
55
46
  *
56
47
  * export default defineConfig({
57
- * services: [AlephaCliPlatformPlugin],
58
- * platform: {
59
- * environments: {
60
- * production: { adapter: "cloudflare", domain: "myapp.com" },
61
- * },
62
- * },
48
+ * plugins: [
49
+ * platform({
50
+ * environments: {
51
+ * production: { adapter: "cloudflare", domain: "myapp.com" },
52
+ * },
53
+ * }),
54
+ * ],
63
55
  * });
64
56
  * ```
65
57
  */
@@ -85,13 +77,12 @@ export const AlephaCliPlatformPlugin = $module({
85
77
  ],
86
78
  });
87
79
 
88
- // ---------------------------------------------------------------------------
89
-
90
- cliConfigPlugins.push((config, alepha) => {
91
- if (config.platform) {
92
- alepha.set(platformOptions, config.platform);
93
- }
94
- });
80
+ export const platform = (options: PlatformOptions) => {
81
+ return () => {
82
+ const { alepha } = $context();
83
+ alepha.with(AlephaCliPlatformPlugin).set(platformOptions, options);
84
+ };
85
+ };
95
86
 
96
87
  // ---------------------------------------------------------------------------
97
88