alepha 0.15.4 → 0.15.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.
- package/README.md +26 -11
- package/dist/api/audits/index.d.ts +3 -3
- package/dist/api/audits/index.js +3 -3
- package/dist/api/audits/index.js.map +1 -1
- package/dist/api/files/index.d.ts +3 -3
- package/dist/api/files/index.js +3 -3
- package/dist/api/files/index.js.map +1 -1
- package/dist/api/jobs/index.d.ts +47 -4
- package/dist/api/jobs/index.d.ts.map +1 -1
- package/dist/api/jobs/index.js +100 -5
- package/dist/api/jobs/index.js.map +1 -1
- package/dist/api/keys/index.d.ts +3 -3
- package/dist/api/keys/index.js +3 -3
- package/dist/api/keys/index.js.map +1 -1
- package/dist/api/notifications/index.d.ts +3 -3
- package/dist/api/notifications/index.js +3 -3
- package/dist/api/notifications/index.js.map +1 -1
- package/dist/api/parameters/index.d.ts +263 -263
- package/dist/api/parameters/index.d.ts.map +1 -1
- package/dist/api/parameters/index.js +31 -30
- package/dist/api/parameters/index.js.map +1 -1
- package/dist/api/users/index.d.ts +373 -67
- package/dist/api/users/index.d.ts.map +1 -1
- package/dist/api/users/index.js +273 -72
- package/dist/api/users/index.js.map +1 -1
- package/dist/api/verifications/index.d.ts +3 -3
- package/dist/api/verifications/index.js +3 -3
- package/dist/api/verifications/index.js.map +1 -1
- package/dist/batch/index.d.ts +7 -7
- package/dist/batch/index.js +3 -3
- package/dist/batch/index.js.map +1 -1
- package/dist/bucket/index.d.ts +3 -3
- package/dist/bucket/index.js +6 -6
- package/dist/bucket/index.js.map +1 -1
- package/dist/cache/core/index.d.ts +3 -3
- package/dist/cache/core/index.js +3 -3
- package/dist/cache/core/index.js.map +1 -1
- package/dist/cli/index.d.ts +5607 -20
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +103 -89
- package/dist/cli/index.js.map +1 -1
- package/dist/command/index.d.ts +11 -4
- package/dist/command/index.d.ts.map +1 -1
- package/dist/command/index.js +8 -6
- package/dist/command/index.js.map +1 -1
- package/dist/core/index.browser.js.map +1 -1
- package/dist/core/index.d.ts +4 -8
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +3 -3
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.native.js.map +1 -1
- package/dist/datetime/index.d.ts +3 -3
- package/dist/datetime/index.js +3 -3
- package/dist/datetime/index.js.map +1 -1
- package/dist/email/index.d.ts +3 -3
- package/dist/email/index.js +8 -8
- package/dist/email/index.js.map +1 -1
- package/dist/fake/index.d.ts +3 -3
- package/dist/fake/index.js +3 -3
- package/dist/fake/index.js.map +1 -1
- package/dist/lock/core/index.d.ts +3 -3
- package/dist/lock/core/index.js +3 -3
- package/dist/lock/core/index.js.map +1 -1
- package/dist/logger/index.d.ts +3 -3
- package/dist/logger/index.js +6 -3
- package/dist/logger/index.js.map +1 -1
- package/dist/mcp/index.d.ts +3 -3
- package/dist/mcp/index.js +3 -3
- package/dist/mcp/index.js.map +1 -1
- package/dist/orm/index.d.ts +12 -12
- package/dist/orm/index.js +4 -4
- package/dist/orm/index.js.map +1 -1
- package/dist/queue/core/index.d.ts +3 -3
- package/dist/queue/core/index.js +3 -3
- package/dist/queue/core/index.js.map +1 -1
- package/dist/react/auth/index.d.ts +3 -3
- package/dist/react/auth/index.js +3 -3
- package/dist/react/auth/index.js.map +1 -1
- package/dist/react/core/index.d.ts +3 -3
- package/dist/react/core/index.js +3 -3
- package/dist/react/core/index.js.map +1 -1
- package/dist/react/form/index.d.ts +3 -3
- package/dist/react/form/index.js +3 -3
- package/dist/react/form/index.js.map +1 -1
- package/dist/react/head/index.d.ts +3 -3
- package/dist/react/head/index.js +3 -3
- package/dist/react/head/index.js.map +1 -1
- package/dist/react/i18n/index.d.ts +3 -3
- package/dist/react/i18n/index.js +3 -3
- package/dist/react/i18n/index.js.map +1 -1
- package/dist/react/intro/index.css +337 -0
- package/dist/react/intro/index.css.map +1 -0
- package/dist/react/intro/index.d.ts +10 -0
- package/dist/react/intro/index.d.ts.map +1 -0
- package/dist/react/intro/index.js +222 -0
- package/dist/react/intro/index.js.map +1 -0
- package/dist/react/router/index.browser.js +2 -2
- package/dist/react/router/index.browser.js.map +1 -1
- package/dist/react/router/index.d.ts +1 -1
- package/dist/react/router/index.d.ts.map +1 -1
- package/dist/react/router/index.js +5 -5
- package/dist/react/router/index.js.map +1 -1
- package/dist/redis/index.d.ts +17 -17
- package/dist/redis/index.js +3 -3
- package/dist/redis/index.js.map +1 -1
- package/dist/retry/index.d.ts +3 -3
- package/dist/retry/index.js +3 -3
- package/dist/retry/index.js.map +1 -1
- package/dist/scheduler/index.d.ts +3 -3
- package/dist/scheduler/index.js +3 -3
- package/dist/scheduler/index.js.map +1 -1
- package/dist/security/index.d.ts +3 -3
- package/dist/security/index.js +5 -5
- package/dist/security/index.js.map +1 -1
- package/dist/server/auth/index.d.ts +3 -3
- package/dist/server/auth/index.js +3 -3
- package/dist/server/auth/index.js.map +1 -1
- package/dist/server/cache/index.d.ts +3 -3
- package/dist/server/cache/index.js +3 -3
- package/dist/server/cache/index.js.map +1 -1
- package/dist/server/compress/index.d.ts +3 -3
- package/dist/server/compress/index.js +3 -3
- package/dist/server/compress/index.js.map +1 -1
- package/dist/server/cookies/index.d.ts +3 -3
- package/dist/server/cookies/index.js +3 -3
- package/dist/server/cookies/index.js.map +1 -1
- package/dist/server/core/index.d.ts +5 -16
- package/dist/server/core/index.d.ts.map +1 -1
- package/dist/server/core/index.js +13 -29
- package/dist/server/core/index.js.map +1 -1
- package/dist/server/cors/index.d.ts +3 -3
- package/dist/server/cors/index.js +3 -3
- package/dist/server/cors/index.js.map +1 -1
- package/dist/server/health/index.d.ts +20 -20
- package/dist/server/health/index.js +3 -3
- package/dist/server/health/index.js.map +1 -1
- package/dist/server/helmet/index.d.ts +3 -3
- package/dist/server/helmet/index.js +3 -3
- package/dist/server/helmet/index.js.map +1 -1
- package/dist/server/links/index.d.ts +42 -42
- package/dist/server/links/index.d.ts.map +1 -1
- package/dist/server/links/index.js +3 -3
- package/dist/server/links/index.js.map +1 -1
- package/dist/server/metrics/index.d.ts +3 -3
- package/dist/server/metrics/index.js +3 -3
- package/dist/server/metrics/index.js.map +1 -1
- package/dist/server/multipart/index.d.ts +3 -3
- package/dist/server/multipart/index.js +3 -3
- package/dist/server/multipart/index.js.map +1 -1
- package/dist/server/proxy/index.d.ts +3 -3
- package/dist/server/proxy/index.js +3 -3
- package/dist/server/proxy/index.js.map +1 -1
- package/dist/server/rate-limit/index.d.ts +3 -3
- package/dist/server/rate-limit/index.js +3 -3
- package/dist/server/rate-limit/index.js.map +1 -1
- package/dist/server/static/index.d.ts +3 -3
- package/dist/server/static/index.js +6 -6
- package/dist/server/static/index.js.map +1 -1
- package/dist/server/swagger/index.d.ts +3 -3
- package/dist/server/swagger/index.js +6 -6
- package/dist/server/swagger/index.js.map +1 -1
- package/dist/sms/index.d.ts +3 -3
- package/dist/sms/index.js +6 -6
- package/dist/sms/index.js.map +1 -1
- package/dist/system/index.d.ts +3 -3
- package/dist/system/index.js +3 -3
- package/dist/system/index.js.map +1 -1
- package/dist/thread/index.d.ts +3 -3
- package/dist/thread/index.js +3 -3
- package/dist/thread/index.js.map +1 -1
- package/dist/topic/core/index.d.ts +3 -3
- package/dist/topic/core/index.js +3 -3
- package/dist/topic/core/index.js.map +1 -1
- package/dist/vite/index.d.ts +6284 -3
- package/dist/vite/index.d.ts.map +1 -1
- package/dist/websocket/index.d.ts +3 -3
- package/dist/websocket/index.js +3 -3
- package/dist/websocket/index.js.map +1 -1
- package/package.json +7 -2
- package/src/api/audits/index.ts +3 -3
- package/src/api/files/index.ts +3 -3
- package/src/api/jobs/controllers/AdminJobController.ts +15 -2
- package/src/api/jobs/index.ts +4 -3
- package/src/api/jobs/services/JobAudits.spec.ts +89 -0
- package/src/api/jobs/services/JobAudits.ts +101 -0
- package/src/api/keys/index.ts +3 -3
- package/src/api/notifications/index.ts +3 -3
- package/src/api/parameters/index.ts +5 -3
- package/src/api/users/__tests__/ApiKeys-integration.spec.ts +1 -1
- package/src/api/users/__tests__/ApiKeys.spec.ts +1 -1
- package/src/api/users/__tests__/EmailVerification.spec.ts +16 -1
- package/src/api/users/__tests__/PasswordReset.spec.ts +11 -0
- package/src/api/users/atoms/realmAuthSettingsAtom.ts +10 -0
- package/src/api/users/index.ts +8 -9
- package/src/api/users/primitives/$realm.ts +117 -19
- package/src/api/users/providers/RealmProvider.ts +15 -7
- package/src/api/users/services/CredentialService.spec.ts +11 -0
- package/src/api/users/services/CredentialService.ts +47 -24
- package/src/api/users/services/IdentityService.ts +12 -4
- package/src/api/users/services/RegistrationService.spec.ts +11 -0
- package/src/api/users/services/RegistrationService.ts +33 -12
- package/src/api/users/services/SessionService.ts +83 -12
- package/src/api/users/services/UserAudits.ts +47 -0
- package/src/api/users/services/UserFiles.ts +19 -0
- package/src/api/users/services/UserJobs.spec.ts +107 -0
- package/src/api/users/services/UserJobs.ts +62 -0
- package/src/api/users/services/UserParameters.ts +23 -0
- package/src/api/users/services/UserService.ts +34 -17
- package/src/api/verifications/index.ts +3 -3
- package/src/batch/index.ts +3 -3
- package/src/bucket/index.ts +3 -3
- package/src/cache/core/index.ts +3 -3
- package/src/cli/commands/db.ts +9 -0
- package/src/cli/commands/init.spec.ts +2 -17
- package/src/cli/commands/init.ts +37 -1
- package/src/cli/providers/ViteDevServerProvider.ts +5 -2
- package/src/cli/services/AlephaCliUtils.ts +17 -0
- package/src/cli/services/PackageManagerUtils.ts +15 -1
- package/src/cli/services/ProjectScaffolder.ts +8 -13
- package/src/cli/templates/agentMd.ts +2 -25
- package/src/cli/templates/apiAppSecurityTs.ts +37 -2
- package/src/cli/templates/mainCss.ts +2 -32
- package/src/cli/templates/webAppRouterTs.ts +5 -5
- package/src/cli/templates/webHomeComponentTsx.ts +10 -0
- package/src/command/helpers/Runner.ts +14 -1
- package/src/command/index.ts +3 -3
- package/src/core/helpers/primitive.ts +0 -5
- package/src/core/index.ts +3 -3
- package/src/datetime/index.ts +3 -3
- package/src/email/index.ts +3 -3
- package/src/email/providers/LocalEmailProvider.ts +2 -2
- package/src/fake/index.ts +3 -3
- package/src/lock/core/index.ts +3 -3
- package/src/logger/index.ts +3 -3
- package/src/logger/providers/PrettyFormatterProvider.ts +7 -0
- package/src/mcp/index.ts +3 -3
- package/src/orm/index.ts +3 -3
- package/src/orm/providers/drivers/NodeSqliteProvider.ts +1 -1
- package/src/queue/core/index.ts +3 -3
- package/src/react/auth/index.ts +3 -3
- package/src/react/core/index.ts +3 -3
- package/src/react/form/index.ts +3 -3
- package/src/react/head/index.ts +3 -3
- package/src/react/i18n/index.ts +3 -3
- package/src/react/intro/components/GettingStarted.css +334 -0
- package/src/react/intro/components/GettingStarted.tsx +276 -0
- package/src/react/intro/index.ts +1 -0
- package/src/react/router/index.browser.ts +2 -0
- package/src/react/router/index.ts +2 -0
- package/src/redis/index.ts +3 -3
- package/src/retry/index.ts +3 -3
- package/src/router/index.ts +3 -3
- package/src/scheduler/index.ts +3 -3
- package/src/security/index.ts +3 -3
- package/src/security/providers/JwtProvider.ts +2 -2
- package/src/server/auth/index.ts +3 -3
- package/src/server/cache/index.ts +3 -3
- package/src/server/compress/index.ts +3 -3
- package/src/server/cookies/index.ts +3 -3
- package/src/server/core/index.ts +3 -3
- package/src/server/core/primitives/$action.spec.ts +3 -2
- package/src/server/core/primitives/$action.ts +6 -2
- package/src/server/core/providers/NodeHttpServerProvider.ts +2 -15
- package/src/server/core/providers/ServerProvider.ts +4 -2
- package/src/server/core/providers/ServerRouterProvider.ts +5 -27
- package/src/server/cors/index.ts +3 -3
- package/src/server/health/index.ts +3 -3
- package/src/server/helmet/index.ts +3 -3
- package/src/server/links/index.ts +3 -3
- package/src/server/metrics/index.ts +3 -3
- package/src/server/multipart/index.ts +3 -3
- package/src/server/proxy/index.ts +3 -3
- package/src/server/rate-limit/index.ts +3 -3
- package/src/server/static/index.ts +3 -3
- package/src/server/swagger/index.ts +3 -3
- package/src/sms/index.ts +3 -3
- package/src/system/index.ts +3 -3
- package/src/thread/index.ts +3 -3
- package/src/topic/core/index.ts +3 -3
- package/src/websocket/index.ts +3 -3
- package/src/cli/templates/webHelloComponentTsx.ts +0 -30
- /package/src/api/users/{notifications → services}/UserNotifications.ts +0 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { Alepha } from "alepha";
|
|
2
|
+
import { AlephaApiJobs } from "alepha/api/jobs";
|
|
3
|
+
import { $repository, AlephaOrm } from "alepha/orm";
|
|
4
|
+
import { describe, test } from "vitest";
|
|
5
|
+
import { sessions } from "../entities/sessions.ts";
|
|
6
|
+
import { users } from "../entities/users.ts";
|
|
7
|
+
import { UserJobs } from "./UserJobs.ts";
|
|
8
|
+
|
|
9
|
+
describe("UserJobs", () => {
|
|
10
|
+
describe("purgeExpiredSessions", () => {
|
|
11
|
+
test("should delete expired sessions", async ({ expect }) => {
|
|
12
|
+
const alepha = Alepha.create().with(AlephaOrm).with(AlephaApiJobs);
|
|
13
|
+
|
|
14
|
+
class TestRepositories {
|
|
15
|
+
userRepository = $repository(users);
|
|
16
|
+
sessionRepository = $repository(sessions);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const userJobs = alepha.inject(UserJobs);
|
|
20
|
+
const repos = alepha.inject(TestRepositories);
|
|
21
|
+
await alepha.start();
|
|
22
|
+
|
|
23
|
+
// Create a test user
|
|
24
|
+
const user = await repos.userRepository.create({
|
|
25
|
+
email: "test@example.com",
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
// Create expired sessions (expiresAt in the past)
|
|
29
|
+
const pastDate = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString(); // 1 day ago
|
|
30
|
+
await repos.sessionRepository.create({
|
|
31
|
+
userId: user.id,
|
|
32
|
+
refreshToken: crypto.randomUUID(),
|
|
33
|
+
expiresAt: pastDate,
|
|
34
|
+
});
|
|
35
|
+
await repos.sessionRepository.create({
|
|
36
|
+
userId: user.id,
|
|
37
|
+
refreshToken: crypto.randomUUID(),
|
|
38
|
+
expiresAt: pastDate,
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// Create a valid session (expiresAt in the future)
|
|
42
|
+
const futureDate = new Date(
|
|
43
|
+
Date.now() + 24 * 60 * 60 * 1000,
|
|
44
|
+
).toISOString(); // 1 day from now
|
|
45
|
+
await repos.sessionRepository.create({
|
|
46
|
+
userId: user.id,
|
|
47
|
+
refreshToken: crypto.randomUUID(),
|
|
48
|
+
expiresAt: futureDate,
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// Verify we have 3 sessions
|
|
52
|
+
const sessionsBefore = await repos.sessionRepository.findMany();
|
|
53
|
+
expect(sessionsBefore).toHaveLength(3);
|
|
54
|
+
|
|
55
|
+
// Trigger the job
|
|
56
|
+
await userJobs.purgeExpiredSessions.trigger();
|
|
57
|
+
|
|
58
|
+
// Verify only the valid session remains
|
|
59
|
+
const sessionsAfter = await repos.sessionRepository.findMany();
|
|
60
|
+
expect(sessionsAfter).toHaveLength(1);
|
|
61
|
+
expect(new Date(sessionsAfter[0].expiresAt).getTime()).toBeGreaterThan(
|
|
62
|
+
Date.now(),
|
|
63
|
+
);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test("should handle case when no expired sessions exist", async ({
|
|
67
|
+
expect,
|
|
68
|
+
}) => {
|
|
69
|
+
const alepha = Alepha.create().with(AlephaOrm).with(AlephaApiJobs);
|
|
70
|
+
|
|
71
|
+
class TestRepositories {
|
|
72
|
+
userRepository = $repository(users);
|
|
73
|
+
sessionRepository = $repository(sessions);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const userJobs = alepha.inject(UserJobs);
|
|
77
|
+
const repos = alepha.inject(TestRepositories);
|
|
78
|
+
await alepha.start();
|
|
79
|
+
|
|
80
|
+
// Create a test user
|
|
81
|
+
const user = await repos.userRepository.create({
|
|
82
|
+
email: "test2@example.com",
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// Create only valid sessions
|
|
86
|
+
const futureDate = new Date(
|
|
87
|
+
Date.now() + 24 * 60 * 60 * 1000,
|
|
88
|
+
).toISOString();
|
|
89
|
+
await repos.sessionRepository.create({
|
|
90
|
+
userId: user.id,
|
|
91
|
+
refreshToken: crypto.randomUUID(),
|
|
92
|
+
expiresAt: futureDate,
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// Verify we have 1 session
|
|
96
|
+
const sessionsBefore = await repos.sessionRepository.findMany();
|
|
97
|
+
expect(sessionsBefore).toHaveLength(1);
|
|
98
|
+
|
|
99
|
+
// Trigger the job - should not throw
|
|
100
|
+
await userJobs.purgeExpiredSessions.trigger();
|
|
101
|
+
|
|
102
|
+
// Session should still exist
|
|
103
|
+
const sessionsAfter = await repos.sessionRepository.findMany();
|
|
104
|
+
expect(sessionsAfter).toHaveLength(1);
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
});
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { $inject } from "alepha";
|
|
2
|
+
import { $job } from "alepha/api/jobs";
|
|
3
|
+
import { DateTimeProvider } from "alepha/datetime";
|
|
4
|
+
import { $logger } from "alepha/logger";
|
|
5
|
+
import { $repository } from "alepha/orm";
|
|
6
|
+
import { sessions } from "../entities/sessions.ts";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* User-specific jobs wrapper service.
|
|
10
|
+
*
|
|
11
|
+
* This service handles user-related scheduled jobs such as:
|
|
12
|
+
* - Session purge (cleaning up expired sessions)
|
|
13
|
+
* - Verification code cleanup
|
|
14
|
+
* - Inactive user notifications
|
|
15
|
+
*
|
|
16
|
+
* It is lazy-loaded when the `jobs` feature is enabled in the realm.
|
|
17
|
+
*/
|
|
18
|
+
export class UserJobs {
|
|
19
|
+
protected readonly log = $logger();
|
|
20
|
+
protected readonly dateTimeProvider = $inject(DateTimeProvider);
|
|
21
|
+
protected readonly sessionRepository = $repository(sessions);
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Purge expired sessions from the database.
|
|
25
|
+
*
|
|
26
|
+
* This job runs daily at 3:00 AM and removes all sessions
|
|
27
|
+
* where the `expiresAt` timestamp has passed.
|
|
28
|
+
*/
|
|
29
|
+
public readonly purgeExpiredSessions = $job({
|
|
30
|
+
name: "users.purgeExpiredSessions",
|
|
31
|
+
description: "Remove expired user sessions from the database",
|
|
32
|
+
cron: "0 3 * * *", // Daily at 3:00 AM
|
|
33
|
+
handler: async () => {
|
|
34
|
+
const now = this.dateTimeProvider.nowISOString();
|
|
35
|
+
|
|
36
|
+
this.log.info("Starting expired sessions purge", { cutoffTime: now });
|
|
37
|
+
|
|
38
|
+
const expiredSessions = await this.sessionRepository.findMany({
|
|
39
|
+
where: {
|
|
40
|
+
expiresAt: { lt: now },
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
if (expiredSessions.length === 0) {
|
|
45
|
+
this.log.info("No expired sessions found");
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
this.log.info("Found expired sessions", {
|
|
50
|
+
count: expiredSessions.length,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const deletedIds = await this.sessionRepository.deleteMany({
|
|
54
|
+
expiresAt: { lt: now },
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
this.log.info("Expired sessions purged successfully", {
|
|
58
|
+
deletedCount: deletedIds.length,
|
|
59
|
+
});
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { $config } from "alepha/api/parameters";
|
|
2
|
+
import { realmAuthSettingsAtom } from "../atoms/realmAuthSettingsAtom.ts";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* User-specific configuration service.
|
|
6
|
+
*
|
|
7
|
+
* This service wraps the core ConfigStore to provide realm settings management.
|
|
8
|
+
* It is lazy-loaded when the `parameters` feature is enabled in the realm.
|
|
9
|
+
*/
|
|
10
|
+
export class UserParameters {
|
|
11
|
+
/**
|
|
12
|
+
* Realm authentication settings configuration.
|
|
13
|
+
*
|
|
14
|
+
* Controls user registration, login methods, verification requirements,
|
|
15
|
+
* and password policies for the realm.
|
|
16
|
+
*/
|
|
17
|
+
public readonly realmSettings = $config({
|
|
18
|
+
name: "alepha.api.users.realmSettings",
|
|
19
|
+
description: "Realm authentication and registration settings",
|
|
20
|
+
schema: realmAuthSettingsAtom.schema,
|
|
21
|
+
default: realmAuthSettingsAtom.options.default,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
@@ -1,23 +1,38 @@
|
|
|
1
|
-
import { $inject } from "alepha";
|
|
2
|
-
import { AuditService } from "alepha/api/audits";
|
|
1
|
+
import { $inject, Alepha } from "alepha";
|
|
3
2
|
import type { VerificationController } from "alepha/api/verifications";
|
|
4
3
|
import { $logger } from "alepha/logger";
|
|
5
4
|
import { type Page, parseQueryString } from "alepha/orm";
|
|
6
5
|
import { BadRequestError } from "alepha/server";
|
|
7
6
|
import { $client } from "alepha/server/links";
|
|
8
7
|
import type { UserEntity } from "../entities/users.ts";
|
|
9
|
-
import { UserNotifications } from "../notifications/UserNotifications.ts";
|
|
10
8
|
import { RealmProvider } from "../providers/RealmProvider.ts";
|
|
11
9
|
import type { CreateUser } from "../schemas/createUserSchema.ts";
|
|
12
10
|
import type { UpdateUser } from "../schemas/updateUserSchema.ts";
|
|
13
11
|
import type { UserQuery } from "../schemas/userQuerySchema.ts";
|
|
12
|
+
import { UserAudits } from "./UserAudits.ts";
|
|
13
|
+
import { UserNotifications } from "./UserNotifications.ts";
|
|
14
14
|
|
|
15
15
|
export class UserService {
|
|
16
|
+
protected readonly alepha = $inject(Alepha);
|
|
16
17
|
protected readonly log = $logger();
|
|
17
18
|
protected readonly verificationController = $client<VerificationController>();
|
|
18
|
-
protected readonly userNotifications = $inject(UserNotifications);
|
|
19
19
|
protected readonly realmProvider = $inject(RealmProvider);
|
|
20
|
-
|
|
20
|
+
|
|
21
|
+
protected userAudits(realmName?: string) {
|
|
22
|
+
const realm = this.realmProvider.getRealm(realmName);
|
|
23
|
+
if (realm.features.audits) {
|
|
24
|
+
return this.alepha.inject(UserAudits);
|
|
25
|
+
}
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
protected userNotifications(realmName?: string) {
|
|
30
|
+
const realm = this.realmProvider.getRealm(realmName);
|
|
31
|
+
if (realm.features.notifications) {
|
|
32
|
+
return this.alepha.inject(UserNotifications);
|
|
33
|
+
}
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
21
36
|
|
|
22
37
|
public users(userRealmName?: string) {
|
|
23
38
|
return this.realmProvider.userRepository(userRealmName);
|
|
@@ -79,21 +94,23 @@ export class UserService {
|
|
|
79
94
|
? `${verifyUrl}${url.search}`
|
|
80
95
|
: url.pathname + url.search;
|
|
81
96
|
|
|
82
|
-
await this.userNotifications
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
97
|
+
await this.userNotifications(userRealmName)?.emailVerificationLink.push(
|
|
98
|
+
{
|
|
99
|
+
contact: email,
|
|
100
|
+
variables: {
|
|
101
|
+
email,
|
|
102
|
+
verifyUrl: fullVerifyUrl,
|
|
103
|
+
expiresInMinutes: Math.floor(verification.codeExpiration / 60),
|
|
104
|
+
},
|
|
88
105
|
},
|
|
89
|
-
|
|
106
|
+
);
|
|
90
107
|
|
|
91
108
|
this.log.debug("Email verification link sent", {
|
|
92
109
|
email,
|
|
93
110
|
userId: user.id,
|
|
94
111
|
});
|
|
95
112
|
} else {
|
|
96
|
-
await this.userNotifications
|
|
113
|
+
await this.userNotifications(userRealmName)?.emailVerification.push({
|
|
97
114
|
contact: email,
|
|
98
115
|
variables: {
|
|
99
116
|
email,
|
|
@@ -158,7 +175,7 @@ export class UserService {
|
|
|
158
175
|
|
|
159
176
|
const realm = this.realmProvider.getRealm(userRealmName);
|
|
160
177
|
|
|
161
|
-
await this.
|
|
178
|
+
await this.userAudits(userRealmName)?.recordUser("update", {
|
|
162
179
|
userId: user.id,
|
|
163
180
|
userEmail: email,
|
|
164
181
|
userRealm: realm.name,
|
|
@@ -314,7 +331,7 @@ export class UserService {
|
|
|
314
331
|
email: user.email,
|
|
315
332
|
});
|
|
316
333
|
|
|
317
|
-
await this.
|
|
334
|
+
await this.userAudits(userRealmName)?.recordUser("create", {
|
|
318
335
|
userRealm: realm.name,
|
|
319
336
|
resourceId: user.id,
|
|
320
337
|
description: "User created",
|
|
@@ -357,7 +374,7 @@ export class UserService {
|
|
|
357
374
|
data.roles !== undefined &&
|
|
358
375
|
JSON.stringify(before.roles) !== JSON.stringify(data.roles);
|
|
359
376
|
|
|
360
|
-
await this.
|
|
377
|
+
await this.userAudits(userRealmName)?.recordUser(
|
|
361
378
|
isRoleChange ? "role_change" : "update",
|
|
362
379
|
{
|
|
363
380
|
userRealm: realm.name,
|
|
@@ -384,7 +401,7 @@ export class UserService {
|
|
|
384
401
|
|
|
385
402
|
const realm = this.realmProvider.getRealm(userRealmName);
|
|
386
403
|
|
|
387
|
-
await this.
|
|
404
|
+
await this.userAudits(userRealmName)?.recordUser("delete", {
|
|
388
405
|
userRealm: realm.name,
|
|
389
406
|
resourceId: id,
|
|
390
407
|
severity: "warning",
|
|
@@ -18,9 +18,9 @@ export * from "./services/VerificationService.ts";
|
|
|
18
18
|
// ---------------------------------------------------------------------------------------------------------------------
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
|
-
* |
|
|
22
|
-
*
|
|
23
|
-
* |
|
|
21
|
+
* | Stability | Since | Runtime |
|
|
22
|
+
* |-----------|-------|---------|
|
|
23
|
+
* | 3 - stable | 0.13.0 | node, bun, workerd|
|
|
24
24
|
*
|
|
25
25
|
* Email and phone verification workflows.
|
|
26
26
|
*
|
package/src/batch/index.ts
CHANGED
|
@@ -10,9 +10,9 @@ export * from "./providers/BatchProvider.ts";
|
|
|
10
10
|
// ---------------------------------------------------------------------------------------------------------------------
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
|
-
* |
|
|
14
|
-
*
|
|
15
|
-
* |
|
|
13
|
+
* | Stability | Since | Runtime |
|
|
14
|
+
* |-----------|-------|---------|
|
|
15
|
+
* | 3 - stable | 0.8.0 | node, bun|
|
|
16
16
|
*
|
|
17
17
|
* Batch accumulation and processing.
|
|
18
18
|
*
|
package/src/bucket/index.ts
CHANGED
|
@@ -43,9 +43,9 @@ declare module "alepha" {
|
|
|
43
43
|
// ---------------------------------------------------------------------------------------------------------------------
|
|
44
44
|
|
|
45
45
|
/**
|
|
46
|
-
* |
|
|
47
|
-
*
|
|
48
|
-
* |
|
|
46
|
+
* | Stability | Since | Runtime |
|
|
47
|
+
* |-----------|-------|---------|
|
|
48
|
+
* | 3 - stable | 0.9.0 | node, bun, workerd|
|
|
49
49
|
*
|
|
50
50
|
* Unified file storage abstraction across multiple backends.
|
|
51
51
|
*
|
package/src/cache/core/index.ts
CHANGED
|
@@ -12,9 +12,9 @@ export * from "./providers/MemoryCacheProvider.ts";
|
|
|
12
12
|
// ---------------------------------------------------------------------------------------------------------------------
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
|
-
* |
|
|
16
|
-
*
|
|
17
|
-
* |
|
|
15
|
+
* | Stability | Since | Runtime |
|
|
16
|
+
* |-----------|-------|---------|
|
|
17
|
+
* | 3 - stable | 0.9.0 | node, bun, workerd|
|
|
18
18
|
*
|
|
19
19
|
* Type-safe caching with TTL support.
|
|
20
20
|
*
|
package/src/cli/commands/db.ts
CHANGED
|
@@ -9,6 +9,7 @@ import type {
|
|
|
9
9
|
import { FileSystemProvider } from "alepha/system";
|
|
10
10
|
import { AppEntryProvider } from "../providers/AppEntryProvider.ts";
|
|
11
11
|
import { AlephaCliUtils } from "../services/AlephaCliUtils.ts";
|
|
12
|
+
import { PackageManagerUtils } from "../services/PackageManagerUtils.ts";
|
|
12
13
|
|
|
13
14
|
const drizzleCommandFlags = t.object({
|
|
14
15
|
provider: t.optional(
|
|
@@ -29,6 +30,7 @@ export class DbCommand {
|
|
|
29
30
|
protected readonly log = $logger();
|
|
30
31
|
protected readonly fs = $inject(FileSystemProvider);
|
|
31
32
|
protected readonly utils = $inject(AlephaCliUtils);
|
|
33
|
+
protected readonly pm = $inject(PackageManagerUtils);
|
|
32
34
|
protected readonly entryProvider = $inject(AppEntryProvider);
|
|
33
35
|
|
|
34
36
|
/**
|
|
@@ -316,6 +318,13 @@ export class DbCommand {
|
|
|
316
318
|
this.log.info("");
|
|
317
319
|
this.log.info(options.logMessage(providerName, dialect));
|
|
318
320
|
|
|
321
|
+
if (dialect === "sqlite") {
|
|
322
|
+
await this.pm.ensureDependency(options.root, "better-sqlite3", {
|
|
323
|
+
dev: true,
|
|
324
|
+
exec: (cmd, opts) => this.utils.exec(cmd, opts),
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
|
|
319
328
|
const drizzleConfigJsPath = await this.prepareDrizzleConfig({
|
|
320
329
|
kit: drizzleKitProvider,
|
|
321
330
|
provider,
|
|
@@ -316,7 +316,7 @@ describe("alepha init", () => {
|
|
|
316
316
|
|
|
317
317
|
expect(fs.wasWritten("/project/src/web/index.ts")).toBe(true);
|
|
318
318
|
expect(fs.wasWritten("/project/src/web/AppRouter.ts")).toBe(true);
|
|
319
|
-
expect(fs.wasWritten("/project/src/web/components/
|
|
319
|
+
expect(fs.wasWritten("/project/src/web/components/Home.tsx")).toBe(true);
|
|
320
320
|
});
|
|
321
321
|
|
|
322
322
|
it("should create main.browser.ts for client-side entry", async () => {
|
|
@@ -331,28 +331,13 @@ describe("alepha init", () => {
|
|
|
331
331
|
).toBe(true);
|
|
332
332
|
});
|
|
333
333
|
|
|
334
|
-
it("should create main.css
|
|
334
|
+
it("should create main.css", async () => {
|
|
335
335
|
const { fs, cli, cmd, json } = createTestEnv();
|
|
336
336
|
await setupProject(fs, json);
|
|
337
337
|
|
|
338
338
|
await cli.run(cmd.init, { argv: "--react", root: "/project" });
|
|
339
339
|
|
|
340
340
|
expect(fs.wasWritten("/project/src/main.css")).toBe(true);
|
|
341
|
-
expect(fs.wasWrittenMatching("/project/src/main.css", /box-sizing/)).toBe(
|
|
342
|
-
true,
|
|
343
|
-
);
|
|
344
|
-
});
|
|
345
|
-
|
|
346
|
-
it("should create main.css with @alepha/ui import when --ui", async () => {
|
|
347
|
-
const { fs, cli, cmd, json } = createTestEnv();
|
|
348
|
-
await setupProject(fs, json);
|
|
349
|
-
|
|
350
|
-
await cli.run(cmd.init, { argv: "--react --ui", root: "/project" });
|
|
351
|
-
|
|
352
|
-
expect(fs.wasWritten("/project/src/main.css")).toBe(true);
|
|
353
|
-
expect(
|
|
354
|
-
fs.wasWrittenMatching("/project/src/main.css", /@alepha\/ui\/styles/),
|
|
355
|
-
).toBe(true);
|
|
356
341
|
});
|
|
357
342
|
|
|
358
343
|
it("should not create api structure without --api flag", async () => {
|
package/src/cli/commands/init.ts
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { $inject, t } from "alepha";
|
|
2
2
|
import { $command } from "alepha/command";
|
|
3
|
+
import { $logger, ConsoleColorProvider } from "alepha/logger";
|
|
3
4
|
import { FileSystemProvider } from "alepha/system";
|
|
4
5
|
import { AlephaCliUtils } from "../services/AlephaCliUtils.ts";
|
|
5
6
|
import { PackageManagerUtils } from "../services/PackageManagerUtils.ts";
|
|
6
7
|
import { ProjectScaffolder } from "../services/ProjectScaffolder.ts";
|
|
7
8
|
|
|
8
9
|
export class InitCommand {
|
|
10
|
+
protected readonly log = $logger();
|
|
11
|
+
protected readonly colors = $inject(ConsoleColorProvider);
|
|
9
12
|
protected readonly utils = $inject(AlephaCliUtils);
|
|
10
13
|
protected readonly pm = $inject(PackageManagerUtils);
|
|
11
14
|
protected readonly scaffolder = $inject(ProjectScaffolder);
|
|
@@ -106,6 +109,11 @@ export class InitCommand {
|
|
|
106
109
|
|
|
107
110
|
const isExpo = await this.pm.hasExpo(root);
|
|
108
111
|
|
|
112
|
+
// Get git email for admin auto-promotion (if auth enabled)
|
|
113
|
+
const adminEmail = flags.auth
|
|
114
|
+
? await this.utils.getGitEmail()
|
|
115
|
+
: undefined;
|
|
116
|
+
|
|
109
117
|
const force = !!flags.force;
|
|
110
118
|
|
|
111
119
|
await run({
|
|
@@ -113,9 +121,9 @@ export class InitCommand {
|
|
|
113
121
|
handler: async () => {
|
|
114
122
|
await this.scaffolder.ensureConfig(root, {
|
|
115
123
|
force,
|
|
116
|
-
tsconfigJson: !workspace.config.tsconfigJson,
|
|
117
124
|
packageJson: { ...flags, isPackage: workspace.isPackage },
|
|
118
125
|
// Skip workspace-level configs if they exist at workspace root
|
|
126
|
+
tsconfigJson: !workspace.config.tsconfigJson,
|
|
119
127
|
biomeJson: !workspace.config.biomeJson,
|
|
120
128
|
editorconfig: !workspace.config.editorconfig,
|
|
121
129
|
agentMd: agentType
|
|
@@ -132,6 +140,7 @@ export class InitCommand {
|
|
|
132
140
|
if (flags.api) {
|
|
133
141
|
await this.scaffolder.ensureApiProject(root, {
|
|
134
142
|
auth: !!flags.auth,
|
|
143
|
+
adminEmail,
|
|
135
144
|
force,
|
|
136
145
|
});
|
|
137
146
|
}
|
|
@@ -196,6 +205,33 @@ export class InitCommand {
|
|
|
196
205
|
});
|
|
197
206
|
}
|
|
198
207
|
}
|
|
208
|
+
|
|
209
|
+
run.end();
|
|
210
|
+
|
|
211
|
+
// Success message
|
|
212
|
+
const projectName = args || ".";
|
|
213
|
+
const pmRun = pmName === "npm" ? "npm run" : pmName;
|
|
214
|
+
const c = this.colors;
|
|
215
|
+
|
|
216
|
+
this.log.info("");
|
|
217
|
+
this.log.info(` ${c.set("GREEN", "✓")} Project ready!`);
|
|
218
|
+
this.log.info("");
|
|
219
|
+
this.log.info(
|
|
220
|
+
` ${c.set("GREY_DARK", "$")} cd ${c.set("CYAN", projectName)}`,
|
|
221
|
+
);
|
|
222
|
+
this.log.info(
|
|
223
|
+
` ${c.set("GREY_DARK", "$")} ${c.set("CYAN", `${pmRun} dev`)}`,
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
if (adminEmail) {
|
|
227
|
+
this.log.info("");
|
|
228
|
+
this.log.info(` Admin email: ${c.set("GREEN", adminEmail)}`);
|
|
229
|
+
this.log.info(
|
|
230
|
+
` ${c.set("GREY_DARK", "(from git config, change in src/api/AppSecurity.ts)")}`,
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
this.log.info("");
|
|
199
235
|
},
|
|
200
236
|
});
|
|
201
237
|
}
|
|
@@ -219,6 +219,9 @@ export class ViteDevServerProvider {
|
|
|
219
219
|
);
|
|
220
220
|
}
|
|
221
221
|
|
|
222
|
+
// expose Vite server to Alepha for Logger SSR Fix stack traces
|
|
223
|
+
alepha.store.set("alepha.vite.server" as any, this.server);
|
|
224
|
+
|
|
222
225
|
this.alepha = alepha;
|
|
223
226
|
await this.setupAlepha();
|
|
224
227
|
|
|
@@ -343,8 +346,8 @@ export class ViteDevServerProvider {
|
|
|
343
346
|
const originalEnd = res.end.bind(res);
|
|
344
347
|
|
|
345
348
|
const guardedCall = <T>(fn: (...args: any[]) => T, ...args: any[]): T => {
|
|
346
|
-
if (resolved &&
|
|
347
|
-
// Vite
|
|
349
|
+
if (resolved && ctx.metadata.vite) {
|
|
350
|
+
// Vite already handled this request, ignore late writes from framework
|
|
348
351
|
return undefined as T;
|
|
349
352
|
}
|
|
350
353
|
return fn(...args);
|
|
@@ -161,4 +161,21 @@ ${models.map((it: string) => `export const ${it} = models["${it}"];`).join("\n")
|
|
|
161
161
|
return "unknown";
|
|
162
162
|
}
|
|
163
163
|
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Get the user's email from git config.
|
|
167
|
+
*
|
|
168
|
+
* @returns The git user email or undefined if not configured
|
|
169
|
+
*/
|
|
170
|
+
public async getGitEmail(): Promise<string | undefined> {
|
|
171
|
+
try {
|
|
172
|
+
const result = await this.shell.run("git config user.email", {
|
|
173
|
+
capture: true,
|
|
174
|
+
});
|
|
175
|
+
const email = result.trim();
|
|
176
|
+
return email || undefined;
|
|
177
|
+
} catch {
|
|
178
|
+
return undefined;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
164
181
|
}
|
|
@@ -48,6 +48,7 @@ export class PackageManagerUtils {
|
|
|
48
48
|
|
|
49
49
|
/**
|
|
50
50
|
* Detect the package manager used in the project.
|
|
51
|
+
* Checks current directory first, then workspace root if in a monorepo.
|
|
51
52
|
*/
|
|
52
53
|
public async getPackageManager(
|
|
53
54
|
root: string,
|
|
@@ -55,10 +56,21 @@ export class PackageManagerUtils {
|
|
|
55
56
|
): Promise<"yarn" | "pnpm" | "npm" | "bun"> {
|
|
56
57
|
if (pm) return pm;
|
|
57
58
|
if (this.alepha.isBun()) return "bun";
|
|
59
|
+
|
|
60
|
+
// Check current directory first
|
|
58
61
|
if (await this.fs.exists(this.fs.join(root, "bun.lock"))) return "bun";
|
|
59
62
|
if (await this.fs.exists(this.fs.join(root, "yarn.lock"))) return "yarn";
|
|
60
63
|
if (await this.fs.exists(this.fs.join(root, "pnpm-lock.yaml")))
|
|
61
64
|
return "pnpm";
|
|
65
|
+
if (await this.fs.exists(this.fs.join(root, "package-lock.json")))
|
|
66
|
+
return "npm";
|
|
67
|
+
|
|
68
|
+
// Check workspace root (for monorepo packages like apps/blog)
|
|
69
|
+
const workspace = await this.getWorkspaceContext(root);
|
|
70
|
+
if (workspace.packageManager) {
|
|
71
|
+
return workspace.packageManager;
|
|
72
|
+
}
|
|
73
|
+
|
|
62
74
|
return "npm";
|
|
63
75
|
}
|
|
64
76
|
|
|
@@ -358,7 +370,9 @@ export class PackageManagerUtils {
|
|
|
358
370
|
alepha: `^${version}`,
|
|
359
371
|
};
|
|
360
372
|
|
|
361
|
-
const devDependencies: Record<string, string> = {
|
|
373
|
+
const devDependencies: Record<string, string> = {
|
|
374
|
+
vite: alephaDeps.vite,
|
|
375
|
+
};
|
|
362
376
|
|
|
363
377
|
// Add biome/vitest only if not a workspace package (workspace root has them)
|
|
364
378
|
if (!modes.isPackage) {
|
|
@@ -19,7 +19,7 @@ import { mainCss } from "../templates/mainCss.ts";
|
|
|
19
19
|
import { mainServerTs } from "../templates/mainServerTs.ts";
|
|
20
20
|
import { tsconfigJson } from "../templates/tsconfigJson.ts";
|
|
21
21
|
import { webAppRouterTs } from "../templates/webAppRouterTs.ts";
|
|
22
|
-
import {
|
|
22
|
+
import { webHomeComponentTsx } from "../templates/webHomeComponentTsx.ts";
|
|
23
23
|
import { webIndexTs } from "../templates/webIndexTs.ts";
|
|
24
24
|
import { AlephaCliUtils } from "./AlephaCliUtils.ts";
|
|
25
25
|
import {
|
|
@@ -34,7 +34,7 @@ import {
|
|
|
34
34
|
* - Project structure (src/api, src/web)
|
|
35
35
|
* - Configuration files (tsconfig, biome, editorconfig)
|
|
36
36
|
* - Entry points (main.server.ts, main.browser.ts)
|
|
37
|
-
* - Example code (HelloController,
|
|
37
|
+
* - Example code (HelloController, Home component)
|
|
38
38
|
*/
|
|
39
39
|
export class ProjectScaffolder {
|
|
40
40
|
protected readonly log = $logger();
|
|
@@ -228,7 +228,7 @@ export class ProjectScaffolder {
|
|
|
228
228
|
*/
|
|
229
229
|
public async ensureApiProject(
|
|
230
230
|
root: string,
|
|
231
|
-
opts: { auth?: boolean; force?: boolean } = {},
|
|
231
|
+
opts: { auth?: boolean; adminEmail?: string; force?: boolean } = {},
|
|
232
232
|
): Promise<void> {
|
|
233
233
|
const appName = this.getAppName(root);
|
|
234
234
|
|
|
@@ -256,7 +256,7 @@ export class ProjectScaffolder {
|
|
|
256
256
|
await this.ensureFile(
|
|
257
257
|
root,
|
|
258
258
|
"src/api/AppSecurity.ts",
|
|
259
|
-
apiAppSecurityTs(),
|
|
259
|
+
apiAppSecurityTs({ adminEmail: opts.adminEmail }),
|
|
260
260
|
opts.force,
|
|
261
261
|
);
|
|
262
262
|
}
|
|
@@ -272,7 +272,7 @@ export class ProjectScaffolder {
|
|
|
272
272
|
* Creates:
|
|
273
273
|
* - src/main.browser.ts
|
|
274
274
|
* - src/main.css
|
|
275
|
-
* - src/web/index.ts, src/web/AppRouter.ts, src/web/components/
|
|
275
|
+
* - src/web/index.ts, src/web/AppRouter.ts, src/web/components/Home.tsx
|
|
276
276
|
*/
|
|
277
277
|
public async ensureWebProject(
|
|
278
278
|
root: string,
|
|
@@ -292,12 +292,7 @@ export class ProjectScaffolder {
|
|
|
292
292
|
});
|
|
293
293
|
|
|
294
294
|
// src/main.css
|
|
295
|
-
await this.ensureFile(
|
|
296
|
-
root,
|
|
297
|
-
"src/main.css",
|
|
298
|
-
mainCss({ ui: opts.ui }),
|
|
299
|
-
opts.force,
|
|
300
|
-
);
|
|
295
|
+
await this.ensureFile(root, "src/main.css", mainCss(), opts.force);
|
|
301
296
|
|
|
302
297
|
// Web structure
|
|
303
298
|
await this.ensureFile(
|
|
@@ -319,8 +314,8 @@ export class ProjectScaffolder {
|
|
|
319
314
|
);
|
|
320
315
|
await this.ensureFile(
|
|
321
316
|
root,
|
|
322
|
-
"src/web/components/
|
|
323
|
-
|
|
317
|
+
"src/web/components/Home.tsx",
|
|
318
|
+
webHomeComponentTsx(),
|
|
324
319
|
opts.force,
|
|
325
320
|
);
|
|
326
321
|
await this.ensureFile(
|