alepha 0.15.2 → 0.15.3

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 (132) hide show
  1. package/README.md +68 -80
  2. package/dist/api/audits/index.d.ts +332 -332
  3. package/dist/api/audits/index.d.ts.map +1 -1
  4. package/dist/api/files/index.d.ts +170 -170
  5. package/dist/api/files/index.d.ts.map +1 -1
  6. package/dist/api/jobs/index.d.ts +151 -151
  7. package/dist/api/keys/index.d.ts +195 -195
  8. package/dist/api/keys/index.d.ts.map +1 -1
  9. package/dist/api/parameters/index.d.ts +260 -260
  10. package/dist/api/users/index.d.ts +22 -11
  11. package/dist/api/users/index.d.ts.map +1 -1
  12. package/dist/api/users/index.js +7 -2
  13. package/dist/api/users/index.js.map +1 -1
  14. package/dist/api/verifications/index.d.ts +128 -128
  15. package/dist/api/verifications/index.d.ts.map +1 -1
  16. package/dist/bucket/index.d.ts +8 -0
  17. package/dist/bucket/index.d.ts.map +1 -1
  18. package/dist/bucket/index.js +7 -2
  19. package/dist/bucket/index.js.map +1 -1
  20. package/dist/cli/index.d.ts +191 -74
  21. package/dist/cli/index.d.ts.map +1 -1
  22. package/dist/cli/index.js +215 -48
  23. package/dist/cli/index.js.map +1 -1
  24. package/dist/command/index.d.ts +10 -0
  25. package/dist/command/index.d.ts.map +1 -1
  26. package/dist/command/index.js +67 -13
  27. package/dist/command/index.js.map +1 -1
  28. package/dist/core/index.browser.js +28 -21
  29. package/dist/core/index.browser.js.map +1 -1
  30. package/dist/core/index.d.ts.map +1 -1
  31. package/dist/core/index.js +28 -21
  32. package/dist/core/index.js.map +1 -1
  33. package/dist/core/index.native.js +28 -21
  34. package/dist/core/index.native.js.map +1 -1
  35. package/dist/email/index.d.ts +8 -0
  36. package/dist/email/index.d.ts.map +1 -1
  37. package/dist/email/index.js +7 -2
  38. package/dist/email/index.js.map +1 -1
  39. package/dist/mcp/index.d.ts +5 -5
  40. package/dist/orm/index.bun.js +32 -16
  41. package/dist/orm/index.bun.js.map +1 -1
  42. package/dist/orm/index.d.ts +4 -1
  43. package/dist/orm/index.d.ts.map +1 -1
  44. package/dist/orm/index.js +34 -22
  45. package/dist/orm/index.js.map +1 -1
  46. package/dist/react/router/index.browser.js +9 -15
  47. package/dist/react/router/index.browser.js.map +1 -1
  48. package/dist/react/router/index.d.ts +295 -407
  49. package/dist/react/router/index.d.ts.map +1 -1
  50. package/dist/react/router/index.js +566 -776
  51. package/dist/react/router/index.js.map +1 -1
  52. package/dist/redis/index.d.ts +19 -19
  53. package/dist/security/index.d.ts +42 -42
  54. package/dist/security/index.d.ts.map +1 -1
  55. package/dist/security/index.js +8 -7
  56. package/dist/security/index.js.map +1 -1
  57. package/dist/server/auth/index.d.ts +167 -167
  58. package/dist/server/core/index.d.ts +9 -9
  59. package/dist/server/health/index.d.ts +17 -17
  60. package/dist/server/links/index.d.ts +39 -39
  61. package/dist/server/static/index.js +7 -2
  62. package/dist/server/static/index.js.map +1 -1
  63. package/dist/server/swagger/index.d.ts +8 -0
  64. package/dist/server/swagger/index.d.ts.map +1 -1
  65. package/dist/server/swagger/index.js +7 -2
  66. package/dist/server/swagger/index.js.map +1 -1
  67. package/dist/sms/index.d.ts +8 -0
  68. package/dist/sms/index.d.ts.map +1 -1
  69. package/dist/sms/index.js +7 -2
  70. package/dist/sms/index.js.map +1 -1
  71. package/dist/system/index.browser.js +734 -12
  72. package/dist/system/index.browser.js.map +1 -1
  73. package/dist/system/index.d.ts +8 -0
  74. package/dist/system/index.d.ts.map +1 -1
  75. package/dist/system/index.js +7 -2
  76. package/dist/system/index.js.map +1 -1
  77. package/dist/vite/index.d.ts +1 -1
  78. package/dist/vite/index.js +15 -7
  79. package/dist/vite/index.js.map +1 -1
  80. package/package.json +4 -2
  81. package/src/api/logs/TODO.md +13 -10
  82. package/src/cli/apps/AlephaPackageBuilderCli.ts +9 -0
  83. package/src/cli/atoms/buildOptions.ts +99 -9
  84. package/src/cli/commands/build.ts +149 -32
  85. package/src/cli/commands/db.ts +5 -7
  86. package/src/cli/commands/init.spec.ts +50 -6
  87. package/src/cli/commands/init.ts +28 -5
  88. package/src/cli/providers/ViteDevServerProvider.ts +1 -10
  89. package/src/cli/services/AlephaCliUtils.ts +16 -0
  90. package/src/cli/services/PackageManagerUtils.ts +2 -0
  91. package/src/cli/services/ProjectScaffolder.spec.ts +97 -0
  92. package/src/cli/services/ProjectScaffolder.ts +28 -6
  93. package/src/cli/templates/agentMd.ts +6 -1
  94. package/src/cli/templates/apiAppSecurityTs.ts +11 -0
  95. package/src/cli/templates/apiIndexTs.ts +18 -4
  96. package/src/cli/templates/webAppRouterTs.ts +25 -1
  97. package/src/cli/templates/webHelloComponentTsx.ts +15 -5
  98. package/src/command/helpers/Runner.spec.ts +135 -0
  99. package/src/command/helpers/Runner.ts +4 -1
  100. package/src/command/providers/CliProvider.spec.ts +325 -0
  101. package/src/command/providers/CliProvider.ts +117 -7
  102. package/src/core/Alepha.ts +32 -25
  103. package/src/orm/index.bun.ts +1 -1
  104. package/src/orm/index.ts +2 -6
  105. package/src/orm/providers/drivers/BunSqliteProvider.ts +4 -1
  106. package/src/orm/providers/drivers/CloudflareD1Provider.ts +57 -30
  107. package/src/orm/providers/drivers/DatabaseProvider.ts +9 -1
  108. package/src/orm/providers/drivers/NodeSqliteProvider.ts +4 -1
  109. package/src/react/router/hooks/useActive.ts +1 -1
  110. package/src/react/router/hooks/useRouter.ts +1 -1
  111. package/src/react/router/index.ts +4 -0
  112. package/src/react/router/primitives/$page.browser.spec.tsx +24 -24
  113. package/src/react/router/primitives/$page.spec.tsx +0 -32
  114. package/src/react/router/primitives/$page.ts +6 -14
  115. package/src/react/router/providers/ReactBrowserProvider.ts +6 -3
  116. package/src/react/router/providers/ReactPageProvider.ts +1 -1
  117. package/src/react/router/providers/ReactPreloadProvider.spec.ts +142 -0
  118. package/src/react/router/providers/ReactPreloadProvider.ts +85 -0
  119. package/src/react/router/providers/ReactServerProvider.ts +7 -78
  120. package/src/react/router/providers/ReactServerTemplateProvider.spec.ts +210 -0
  121. package/src/react/router/providers/ReactServerTemplateProvider.ts +228 -665
  122. package/src/react/router/services/ReactRouter.ts +13 -13
  123. package/src/security/__tests__/ServerSecurityProvider.spec.ts +77 -0
  124. package/src/security/providers/ServerSecurityProvider.ts +30 -22
  125. package/src/server/core/providers/NodeHttpServerProvider.spec.ts +9 -3
  126. package/src/system/index.browser.ts +25 -0
  127. package/src/system/index.workerd.ts +1 -0
  128. package/src/system/providers/FileSystemProvider.ts +8 -0
  129. package/src/system/providers/NodeFileSystemProvider.ts +11 -2
  130. package/src/vite/tasks/buildServer.ts +2 -12
  131. package/src/vite/tasks/generateCloudflare.ts +10 -7
  132. package/src/vite/tasks/generateDocker.ts +4 -0
@@ -7,6 +7,7 @@ import {
7
7
  type AgentMdType,
8
8
  agentMd,
9
9
  } from "../templates/agentMd.ts";
10
+ import { apiAppSecurityTs } from "../templates/apiAppSecurityTs.ts";
10
11
  import { apiHelloControllerTs } from "../templates/apiHelloControllerTs.ts";
11
12
  import { apiIndexTs } from "../templates/apiIndexTs.ts";
12
13
  import { biomeJson } from "../templates/biomeJson.ts";
@@ -51,7 +52,7 @@ export class ProjectScaffolder {
51
52
  */
52
53
  public getAppName(root: string): string {
53
54
  const dirName = basename(root);
54
- const appName = dirName.toLowerCase().replace(/[\s\-_]/g, "");
55
+ const appName = dirName.toLowerCase().replace(/[\s\-_.\d]/g, "");
55
56
  return appName || "app";
56
57
  }
57
58
 
@@ -227,7 +228,7 @@ export class ProjectScaffolder {
227
228
  */
228
229
  public async ensureApiProject(
229
230
  root: string,
230
- opts: { force?: boolean } = {},
231
+ opts: { auth?: boolean; force?: boolean } = {},
231
232
  ): Promise<void> {
232
233
  const appName = this.getAppName(root);
233
234
 
@@ -240,7 +241,7 @@ export class ProjectScaffolder {
240
241
  await this.ensureFile(
241
242
  root,
242
243
  "src/api/index.ts",
243
- apiIndexTs({ appName }),
244
+ apiIndexTs({ appName, auth: opts.auth }),
244
245
  opts.force,
245
246
  );
246
247
  await this.ensureFile(
@@ -249,6 +250,16 @@ export class ProjectScaffolder {
249
250
  apiHelloControllerTs(),
250
251
  opts.force,
251
252
  );
253
+
254
+ // Create AppSecurity if auth is enabled
255
+ if (opts.auth) {
256
+ await this.ensureFile(
257
+ root,
258
+ "src/api/AppSecurity.ts",
259
+ apiAppSecurityTs(),
260
+ opts.force,
261
+ );
262
+ }
252
263
  }
253
264
 
254
265
  // ===========================================
@@ -265,7 +276,13 @@ export class ProjectScaffolder {
265
276
  */
266
277
  public async ensureWebProject(
267
278
  root: string,
268
- opts: { api?: boolean; ui?: boolean; force?: boolean } = {},
279
+ opts: {
280
+ api?: boolean;
281
+ ui?: boolean;
282
+ auth?: boolean;
283
+ admin?: boolean;
284
+ force?: boolean;
285
+ } = {},
269
286
  ): Promise<void> {
270
287
  const appName = this.getAppName(root);
271
288
 
@@ -292,13 +309,18 @@ export class ProjectScaffolder {
292
309
  await this.ensureFile(
293
310
  root,
294
311
  "src/web/AppRouter.ts",
295
- webAppRouterTs({ api: opts.api, ui: opts.ui }),
312
+ webAppRouterTs({
313
+ api: opts.api,
314
+ ui: opts.ui,
315
+ auth: opts.auth,
316
+ admin: opts.admin,
317
+ }),
296
318
  opts.force,
297
319
  );
298
320
  await this.ensureFile(
299
321
  root,
300
322
  "src/web/components/Hello.tsx",
301
- webHelloComponentTsx(),
323
+ webHelloComponentTsx({ auth: opts.auth }),
302
324
  opts.force,
303
325
  );
304
326
  await this.ensureFile(
@@ -311,6 +311,11 @@ alepha build # Build the project
311
311
  ## Source Code Access
312
312
 
313
313
  Full framework source available at \`node_modules/alepha/src/\`.
314
- Read primitives directly when you need implementation details.
314
+
315
+ **IMPORTANT:** When answering questions about Alepha primitives, APIs, or internals:
316
+ 1. ALWAYS read the local source code first at \`node_modules/alepha/src/\` or \`node_modules/@alepha/ui/src/\` for UI-related questions
317
+ 2. Use \`Glob\` to find relevant files: \`node_modules/alepha/src/**/primitives/$<name>.ts\`
318
+ 3. Read the implementation AND the \`.spec.ts\` test files for usage examples
319
+ 4. Use external documentation as a fallback if source code is insufficient
315
320
  `.trim();
316
321
  };
@@ -0,0 +1,11 @@
1
+ export const apiAppSecurityTs = () => {
2
+ return `
3
+ import { $realm } from "alepha/api/users";
4
+
5
+ export class AppSecurity {
6
+ users = $realm({
7
+ // configure your realm here
8
+ });
9
+ }
10
+ `.trim();
11
+ };
@@ -1,16 +1,30 @@
1
1
  export interface ApiIndexTsOptions {
2
2
  appName?: string;
3
+ auth?: boolean;
3
4
  }
4
5
 
5
6
  export const apiIndexTs = (options: ApiIndexTsOptions = {}) => {
6
- const { appName = "app" } = options;
7
+ const { appName = "app", auth = false } = options;
8
+
9
+ const imports: string[] = ['import { $module } from "alepha";'];
10
+ const services: string[] = [];
11
+
12
+ if (auth) {
13
+ imports.push('import { AppSecurity } from "./AppSecurity.ts";');
14
+ services.push("AppSecurity");
15
+ }
16
+
17
+ imports.push(
18
+ 'import { HelloController } from "./controllers/HelloController.ts";',
19
+ );
20
+ services.push("HelloController");
21
+
7
22
  return `
8
- import { $module } from "alepha";
9
- import { HelloController } from "./controllers/HelloController.ts";
23
+ ${imports.join("\n")}
10
24
 
11
25
  export const ApiModule = $module({
12
26
  name: "${appName}.api",
13
- services: [HelloController],
27
+ services: [${services.join(", ")}],
14
28
  });
15
29
  `.trim();
16
30
  };
@@ -1,4 +1,9 @@
1
- export const webAppRouterTs = (options: { api?: boolean; ui?: boolean }) => {
1
+ export const webAppRouterTs = (options: {
2
+ api?: boolean;
3
+ ui?: boolean;
4
+ auth?: boolean;
5
+ admin?: boolean;
6
+ }) => {
2
7
  const imports: string[] = [];
3
8
  const classMembers: string[] = [];
4
9
 
@@ -7,6 +12,16 @@ export const webAppRouterTs = (options: { api?: boolean; ui?: boolean }) => {
7
12
  imports.push('import { $ui } from "@alepha/ui";');
8
13
  }
9
14
 
15
+ // Auth import
16
+ if (options.auth) {
17
+ imports.push('import { $uiAuth } from "@alepha/ui/auth";');
18
+ }
19
+
20
+ // Admin import
21
+ if (options.admin) {
22
+ imports.push('import { $uiAdmin } from "@alepha/ui/admin";');
23
+ }
24
+
10
25
  // Page import
11
26
  imports.push('import { $page } from "alepha/react/router";');
12
27
 
@@ -22,6 +37,15 @@ export const webAppRouterTs = (options: { api?: boolean; ui?: boolean }) => {
22
37
  // UI layout setup
23
38
  if (options.ui) {
24
39
  classMembers.push(" ui = $ui();");
40
+
41
+ if (options.auth) {
42
+ classMembers.push(" uiAuth = $uiAuth();");
43
+ }
44
+
45
+ if (options.admin) {
46
+ classMembers.push(" uiAdmin = $uiAdmin();");
47
+ }
48
+
25
49
  classMembers.push(` layout = $page({
26
50
  parent: this.ui.root,
27
51
  children: () => [this.home],
@@ -1,5 +1,14 @@
1
- export const webHelloComponentTsx = () =>
2
- `import { useState } from "react";
1
+ export const webHelloComponentTsx = (options: { auth?: boolean } = {}) => {
2
+ const imports: string[] = [];
3
+
4
+ if (options.auth) {
5
+ imports.push('import { UserButton } from "@alepha/ui/auth";');
6
+ }
7
+ imports.push('import { useState } from "react";');
8
+
9
+ const userButton = options.auth ? "\n <UserButton />" : "";
10
+
11
+ return `${imports.join("\n")}
3
12
 
4
13
  interface Props {
5
14
  message?: string;
@@ -10,11 +19,12 @@ const Hello = (props: Props) => {
10
19
  return (
11
20
  <div>
12
21
  <h1>{message}</h1>
13
- <input value={message} onChange={e => setMessage(e.target.value)} />
14
- <p>Edit this component in src/web/components/Hello.tsx</p>
22
+ <input value={message} onChange={(e) => setMessage(e.target.value)} />
23
+ <p>Edit this component in src/web/components/Hello.tsx</p>${userButton}
15
24
  </div>
16
25
  );
17
26
  };
18
27
 
19
28
  export default Hello;
20
- `.trim();
29
+ `;
30
+ };
@@ -7,6 +7,12 @@ import { MemoryShellProvider } from "alepha/system";
7
7
  import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
8
8
  import { Runner } from "../index.ts";
9
9
 
10
+ class TestRunner extends Runner {
11
+ protected override get useDynamicLogger() {
12
+ return true;
13
+ }
14
+ }
15
+
10
16
  describe("Runner", () => {
11
17
  let mockLogger: MemoryDestinationProvider;
12
18
  let mockShell: MemoryShellProvider;
@@ -127,4 +133,133 @@ describe("Runner", () => {
127
133
  const logs = mockLogger.logs.map((l) => l.message);
128
134
  expect(logs.length).toBe(0);
129
135
  });
136
+
137
+ test("should reset firstTaskStarted when starting a new command", async () => {
138
+ // Simulate running command A
139
+ runner.startCommand("cli", "commandA");
140
+ await runner.run("task A1", () => {});
141
+ await runner.run("task A2", () => {});
142
+ runner.end();
143
+
144
+ const logsAfterA = mockLogger.logs.length;
145
+ expect(logsAfterA).toBeGreaterThan(0);
146
+
147
+ // Clear logs for next command
148
+ mockLogger.clear();
149
+
150
+ // Simulate running command B (should get fresh start)
151
+ runner.startCommand("cli", "commandB");
152
+ await runner.run("task B1", () => {});
153
+ runner.end();
154
+
155
+ // Verify command B also logged properly (not reusing A's state)
156
+ expect(mockLogger.logs.length).toBeGreaterThan(0);
157
+ expect(mockLogger.logs[0].message).toBe("Starting 'task B1' ...");
158
+ });
159
+
160
+ test("should handle running same task name in different commands", async () => {
161
+ // Run command with task "clean"
162
+ runner.startCommand("cli", "build");
163
+ await runner.run("clean", () => {});
164
+ runner.end();
165
+
166
+ mockLogger.clear();
167
+
168
+ // Run another command with same task name "clean"
169
+ runner.startCommand("cli", "verify");
170
+ await runner.run("clean", () => {});
171
+ runner.end();
172
+
173
+ // Both should have executed and logged independently
174
+ expect(mockLogger.logs[0].message).toBe("Starting 'clean' ...");
175
+ expect(mockLogger.logs[1].message).toMatch(/^Finished 'clean' after/);
176
+ });
177
+
178
+ test("should reset firstTaskStarted with PrettyPrint (LOG_FORMAT=raw)", async () => {
179
+ // Use TestRunner to force useDynamicLogger=true (bypasses isCI check)
180
+ const alepha = Alepha.create({
181
+ env: {
182
+ LOG_LEVEL: "info",
183
+ LOG_FORMAT: "raw",
184
+ },
185
+ })
186
+ .with({ provide: LogDestinationProvider, use: MemoryDestinationProvider })
187
+ .with({ provide: Runner, use: TestRunner });
188
+
189
+ const prettyRunner = alepha.inject(Runner);
190
+
191
+ // Capture stdout to verify PrettyPrint output
192
+ const output: string[] = [];
193
+ const originalWrite = process.stdout.write.bind(process.stdout);
194
+ process.stdout.write = (chunk: any) => {
195
+ output.push(String(chunk));
196
+ return true;
197
+ };
198
+
199
+ try {
200
+ // Run command A
201
+ prettyRunner.startCommand("cli", "commandA");
202
+ await prettyRunner.run("task A", () => {});
203
+ prettyRunner.end();
204
+
205
+ // Verify command A header was printed
206
+ expect(output.some((line) => line.includes("cli commandA"))).toBe(true);
207
+
208
+ // Clear output for command B
209
+ output.length = 0;
210
+
211
+ // Run command B - should get its own header
212
+ prettyRunner.startCommand("cli", "commandB");
213
+ await prettyRunner.run("task B", () => {});
214
+ prettyRunner.end();
215
+
216
+ // Verify command B header was printed (not reusing A's state)
217
+ expect(output.some((line) => line.includes("cli commandB"))).toBe(true);
218
+ } finally {
219
+ process.stdout.write = originalWrite;
220
+ }
221
+ });
222
+
223
+ test("should display duplicate task names in same command with PrettyPrint", async () => {
224
+ // Use TestRunner to force useDynamicLogger=true (bypasses isCI check)
225
+ const alepha = Alepha.create({
226
+ env: {
227
+ LOG_LEVEL: "info",
228
+ LOG_FORMAT: "raw",
229
+ },
230
+ })
231
+ .with({ provide: LogDestinationProvider, use: MemoryDestinationProvider })
232
+ .with({ provide: Runner, use: TestRunner });
233
+
234
+ const prettyRunner = alepha.inject(Runner);
235
+
236
+ // Capture stdout to verify PrettyPrint output
237
+ const output: string[] = [];
238
+ const originalWrite = process.stdout.write.bind(process.stdout);
239
+ process.stdout.write = (chunk: any) => {
240
+ output.push(String(chunk));
241
+ return true;
242
+ };
243
+
244
+ try {
245
+ // Run command with duplicate task names (like "alepha verify" does with "clean")
246
+ prettyRunner.startCommand("cli", "verify");
247
+ await prettyRunner.run("clean", () => {});
248
+ await prettyRunner.run("lint", () => {});
249
+ await prettyRunner.run("typecheck", () => {});
250
+ await prettyRunner.run("clean", () => {}); // Same name as first task
251
+ prettyRunner.end();
252
+
253
+ // Count occurrences of "clean" in output (should appear twice)
254
+ const fullOutput = output.join("");
255
+ const cleanMatches = fullOutput.match(/clean/g) || [];
256
+ expect(cleanMatches.length).toBeGreaterThanOrEqual(2);
257
+
258
+ // Verify all tasks were logged
259
+ expect(fullOutput).toContain("lint");
260
+ expect(fullOutput).toContain("typecheck");
261
+ } finally {
262
+ process.stdout.write = originalWrite;
263
+ }
264
+ });
130
265
  });
@@ -47,6 +47,7 @@ export class Runner {
47
47
  protected cliName = "";
48
48
  protected commandName = "";
49
49
  protected firstTaskStarted = false;
50
+ protected taskCounter = 0;
50
51
 
51
52
  constructor() {
52
53
  this.run = this.createRunMethod();
@@ -66,6 +67,8 @@ export class Runner {
66
67
  public startCommand(cliName: string, commandName: string): void {
67
68
  this.cliName = cliName;
68
69
  this.commandName = commandName;
70
+ this.firstTaskStarted = false;
71
+ this.taskCounter = 0;
69
72
  }
70
73
 
71
74
  protected createRunMethod() {
@@ -190,7 +193,7 @@ export class Runner {
190
193
 
191
194
  protected async executeTask(task: Task): Promise<string> {
192
195
  const now = Date.now();
193
- const taskId = task.name; // Use task name as unique ID
196
+ const taskId = `task-${++this.taskCounter}`; // Use unique counter-based ID
194
197
 
195
198
  // Setup dynamic logger
196
199
  if (this.useDynamicLogger) {