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.
- package/README.md +68 -80
- package/dist/api/audits/index.d.ts.map +1 -1
- package/dist/api/audits/index.js +8 -0
- package/dist/api/audits/index.js.map +1 -1
- package/dist/api/files/index.d.ts +170 -170
- package/dist/api/files/index.d.ts.map +1 -1
- package/dist/api/files/index.js +1 -0
- package/dist/api/files/index.js.map +1 -1
- package/dist/api/jobs/index.d.ts.map +1 -1
- package/dist/api/jobs/index.js +3 -0
- package/dist/api/jobs/index.js.map +1 -1
- package/dist/api/notifications/index.browser.js +1 -0
- package/dist/api/notifications/index.browser.js.map +1 -1
- package/dist/api/notifications/index.js +1 -0
- package/dist/api/notifications/index.js.map +1 -1
- package/dist/api/parameters/index.d.ts +260 -260
- package/dist/api/parameters/index.d.ts.map +1 -1
- package/dist/api/parameters/index.js +10 -0
- package/dist/api/parameters/index.js.map +1 -1
- package/dist/api/users/index.d.ts +12 -1
- package/dist/api/users/index.d.ts.map +1 -1
- package/dist/api/users/index.js +18 -2
- package/dist/api/users/index.js.map +1 -1
- package/dist/batch/index.d.ts +4 -4
- package/dist/bucket/index.d.ts +8 -0
- package/dist/bucket/index.d.ts.map +1 -1
- package/dist/bucket/index.js +7 -2
- package/dist/bucket/index.js.map +1 -1
- package/dist/cli/index.d.ts +196 -74
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +234 -50
- package/dist/cli/index.js.map +1 -1
- package/dist/command/index.d.ts +10 -0
- package/dist/command/index.d.ts.map +1 -1
- package/dist/command/index.js +67 -13
- package/dist/command/index.js.map +1 -1
- package/dist/core/index.browser.js +28 -21
- package/dist/core/index.browser.js.map +1 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +28 -21
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.native.js +28 -21
- package/dist/core/index.native.js.map +1 -1
- package/dist/email/index.d.ts +21 -13
- package/dist/email/index.d.ts.map +1 -1
- package/dist/email/index.js +10561 -4
- package/dist/email/index.js.map +1 -1
- package/dist/lock/core/index.d.ts +6 -1
- package/dist/lock/core/index.d.ts.map +1 -1
- package/dist/lock/core/index.js +9 -1
- package/dist/lock/core/index.js.map +1 -1
- package/dist/mcp/index.d.ts +5 -5
- package/dist/orm/index.bun.js +32 -16
- package/dist/orm/index.bun.js.map +1 -1
- package/dist/orm/index.d.ts +4 -1
- package/dist/orm/index.d.ts.map +1 -1
- package/dist/orm/index.js +34 -22
- package/dist/orm/index.js.map +1 -1
- package/dist/react/auth/index.browser.js +2 -1
- package/dist/react/auth/index.browser.js.map +1 -1
- package/dist/react/auth/index.js +2 -1
- package/dist/react/auth/index.js.map +1 -1
- package/dist/react/core/index.d.ts +3 -3
- package/dist/react/router/index.browser.js +9 -15
- package/dist/react/router/index.browser.js.map +1 -1
- package/dist/react/router/index.d.ts +305 -407
- package/dist/react/router/index.d.ts.map +1 -1
- package/dist/react/router/index.js +581 -781
- package/dist/react/router/index.js.map +1 -1
- package/dist/scheduler/index.d.ts +13 -1
- package/dist/scheduler/index.d.ts.map +1 -1
- package/dist/scheduler/index.js +42 -4
- package/dist/scheduler/index.js.map +1 -1
- package/dist/security/index.d.ts +42 -42
- package/dist/security/index.d.ts.map +1 -1
- package/dist/security/index.js +8 -7
- package/dist/security/index.js.map +1 -1
- package/dist/server/auth/index.d.ts +167 -167
- package/dist/server/compress/index.d.ts.map +1 -1
- package/dist/server/compress/index.js +1 -0
- package/dist/server/compress/index.js.map +1 -1
- package/dist/server/health/index.d.ts +17 -17
- package/dist/server/links/index.d.ts +39 -39
- package/dist/server/links/index.js +1 -1
- package/dist/server/links/index.js.map +1 -1
- package/dist/server/static/index.js +7 -2
- package/dist/server/static/index.js.map +1 -1
- package/dist/server/swagger/index.d.ts +8 -0
- package/dist/server/swagger/index.d.ts.map +1 -1
- package/dist/server/swagger/index.js +7 -2
- package/dist/server/swagger/index.js.map +1 -1
- package/dist/sms/index.d.ts +8 -0
- package/dist/sms/index.d.ts.map +1 -1
- package/dist/sms/index.js +7 -2
- package/dist/sms/index.js.map +1 -1
- package/dist/system/index.browser.js +734 -12
- package/dist/system/index.browser.js.map +1 -1
- package/dist/system/index.d.ts +8 -0
- package/dist/system/index.d.ts.map +1 -1
- package/dist/system/index.js +7 -2
- package/dist/system/index.js.map +1 -1
- package/dist/vite/index.d.ts +3 -2
- package/dist/vite/index.d.ts.map +1 -1
- package/dist/vite/index.js +42 -8
- package/dist/vite/index.js.map +1 -1
- package/dist/websocket/index.d.ts +34 -34
- package/dist/websocket/index.d.ts.map +1 -1
- package/package.json +9 -4
- package/src/api/audits/controllers/AdminAuditController.ts +8 -0
- package/src/api/files/controllers/AdminFileStatsController.ts +1 -0
- package/src/api/jobs/controllers/AdminJobController.ts +3 -0
- package/src/api/logs/TODO.md +13 -10
- package/src/api/notifications/controllers/AdminNotificationController.ts +1 -0
- package/src/api/parameters/controllers/AdminConfigController.ts +10 -0
- package/src/api/users/controllers/AdminIdentityController.ts +3 -0
- package/src/api/users/controllers/AdminSessionController.ts +3 -0
- package/src/api/users/controllers/AdminUserController.ts +5 -0
- package/src/cli/apps/AlephaPackageBuilderCli.ts +9 -0
- package/src/cli/atoms/buildOptions.ts +99 -9
- package/src/cli/commands/build.ts +150 -32
- package/src/cli/commands/db.ts +5 -7
- package/src/cli/commands/init.spec.ts +50 -6
- package/src/cli/commands/init.ts +28 -5
- package/src/cli/providers/ViteDevServerProvider.ts +31 -9
- package/src/cli/services/AlephaCliUtils.ts +16 -0
- package/src/cli/services/PackageManagerUtils.ts +2 -0
- package/src/cli/services/ProjectScaffolder.spec.ts +97 -0
- package/src/cli/services/ProjectScaffolder.ts +28 -6
- package/src/cli/templates/agentMd.ts +6 -1
- package/src/cli/templates/apiAppSecurityTs.ts +11 -0
- package/src/cli/templates/apiIndexTs.ts +18 -4
- package/src/cli/templates/webAppRouterTs.ts +25 -1
- package/src/cli/templates/webHelloComponentTsx.ts +15 -5
- package/src/command/helpers/Runner.spec.ts +135 -0
- package/src/command/helpers/Runner.ts +4 -1
- package/src/command/providers/CliProvider.spec.ts +325 -0
- package/src/command/providers/CliProvider.ts +117 -7
- package/src/core/Alepha.ts +32 -25
- package/src/email/index.workerd.ts +36 -0
- package/src/email/providers/WorkermailerEmailProvider.ts +221 -0
- package/src/lock/core/primitives/$lock.ts +13 -1
- package/src/orm/index.bun.ts +1 -1
- package/src/orm/index.ts +2 -6
- package/src/orm/providers/drivers/BunSqliteProvider.ts +4 -1
- package/src/orm/providers/drivers/CloudflareD1Provider.ts +57 -30
- package/src/orm/providers/drivers/DatabaseProvider.ts +9 -1
- package/src/orm/providers/drivers/NodeSqliteProvider.ts +4 -1
- package/src/react/auth/services/ReactAuth.ts +3 -1
- package/src/react/router/atoms/ssrManifestAtom.ts +7 -0
- package/src/react/router/hooks/useActive.ts +1 -1
- package/src/react/router/hooks/useRouter.ts +1 -1
- package/src/react/router/index.ts +4 -0
- package/src/react/router/primitives/$page.browser.spec.tsx +24 -24
- package/src/react/router/primitives/$page.spec.tsx +0 -32
- package/src/react/router/primitives/$page.ts +6 -14
- package/src/react/router/providers/ReactBrowserProvider.ts +6 -3
- package/src/react/router/providers/ReactPageProvider.ts +1 -1
- package/src/react/router/providers/ReactPreloadProvider.spec.ts +142 -0
- package/src/react/router/providers/ReactPreloadProvider.ts +85 -0
- package/src/react/router/providers/ReactServerProvider.ts +21 -82
- package/src/react/router/providers/ReactServerTemplateProvider.spec.ts +210 -0
- package/src/react/router/providers/ReactServerTemplateProvider.ts +228 -665
- package/src/react/router/providers/SSRManifestProvider.ts +7 -0
- package/src/react/router/services/ReactRouter.ts +13 -13
- package/src/scheduler/index.workerd.ts +43 -0
- package/src/scheduler/providers/CronProvider.ts +53 -6
- package/src/scheduler/providers/WorkerdCronProvider.ts +102 -0
- package/src/security/__tests__/ServerSecurityProvider.spec.ts +77 -0
- package/src/security/providers/ServerSecurityProvider.ts +30 -22
- package/src/server/compress/providers/ServerCompressProvider.ts +6 -0
- package/src/server/core/providers/NodeHttpServerProvider.spec.ts +9 -3
- package/src/server/links/providers/ServerLinksProvider.spec.ts +332 -0
- package/src/server/links/providers/ServerLinksProvider.ts +1 -1
- package/src/system/index.browser.ts +25 -0
- package/src/system/index.workerd.ts +1 -0
- package/src/system/providers/FileSystemProvider.ts +8 -0
- package/src/system/providers/NodeFileSystemProvider.ts +11 -2
- package/src/vite/tasks/buildServer.ts +2 -12
- package/src/vite/tasks/generateCloudflare.ts +47 -8
- 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: {
|
|
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({
|
|
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
|
-
|
|
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
|
};
|
|
@@ -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
|
-
|
|
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: [
|
|
27
|
+
services: [${services.join(", ")}],
|
|
14
28
|
});
|
|
15
29
|
`.trim();
|
|
16
30
|
};
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
export const webAppRouterTs = (options: {
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
196
|
+
const taskId = `task-${++this.taskCounter}`; // Use unique counter-based ID
|
|
194
197
|
|
|
195
198
|
// Setup dynamic logger
|
|
196
199
|
if (this.useDynamicLogger) {
|