create-warlock 5.37.1 → 5.38.0
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/CHANGELOG.md +15 -0
- package/package.json +2 -2
- package/templates/warlock/AGENTS.md +1 -1
- package/templates/warlock/README.md +1 -0
- package/templates/warlock/docs/new-module.md +21 -18
- package/templates/warlock/skills/code-standards/SKILL.md +23 -4
- package/templates/warlock/skills/module-boundaries/SKILL.md +32 -13
- package/templates/warlock/skills/testing-strategy/SKILL.md +41 -26
- package/templates/warlock/skills/web-code-standards/SKILL.md +12 -1
- package/templates/warlock/src/app/auth/utils/locales.json +64 -0
- package/templates/warlock/src/app/shared/utils/locales/attributes.json +31 -0
- package/templates/warlock/src/app/shared/utils/locales/http.json +31 -0
- package/templates/warlock/src/app/shared/utils/locales/validation.json +699 -0
- package/templates/warlock/src/config/database.postgres.ts +10 -2
- package/templates/warlock/src/config/database.ts +7 -1
- package/templates/warlock/src/config/tests.ts +14 -0
- package/templates/warlock/src/config/web.ts +8 -0
- package/templates/warlock/src/web/app.css +1 -0
- package/templates/warlock/src/web/forbidden.css +109 -0
- package/templates/warlock/src/web/forbidden.page.tsx +18 -0
- package/templates/warlock/src/web/forbidden.setup.ts +8 -0
- package/templates/warlock/src/app/auth/utils/locales.ts +0 -89
- package/templates/warlock/src/app/shared/utils/locales.ts +0 -766
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
import type { ConnectionOptions, PostgresPoolConfig } from "@warlock.js/cascade";
|
|
2
2
|
import { env } from "@warlock.js/core";
|
|
3
3
|
|
|
4
|
+
// Each Vitest worker keeps its own database and truncates it between test files
|
|
5
|
+
// (src/config/tests.ts), so tests run against "<DB_NAME>_test_<worker id>". Create one
|
|
6
|
+
// database per worker first (for example <DB_NAME>_test_1 to _4, one per `maxWorkers`) and
|
|
7
|
+
// run the migrations on each.
|
|
8
|
+
const databaseName = process.env.VITEST
|
|
9
|
+
? `${env("DB_NAME", "app")}_test_${process.env.VITEST_POOL_ID ?? 1}`
|
|
10
|
+
: env("DB_NAME");
|
|
11
|
+
|
|
4
12
|
const databaseConfigurations: ConnectionOptions<never, PostgresPoolConfig> = {
|
|
5
13
|
driver: env("DB_DRIVER", "postgres"),
|
|
6
14
|
name: "default",
|
|
7
|
-
database:
|
|
15
|
+
database: databaseName,
|
|
8
16
|
host: env("DB_HOST", "localhost"),
|
|
9
17
|
port: env("DB_PORT", 5432),
|
|
10
18
|
username: env("DB_USERNAME"),
|
|
@@ -16,7 +24,7 @@ const databaseConfigurations: ConnectionOptions<never, PostgresPoolConfig> = {
|
|
|
16
24
|
clientOptions: {
|
|
17
25
|
// `PostgresPoolConfig` extends the connection config, so `database` is
|
|
18
26
|
// required here too even though it duplicates the top-level value above.
|
|
19
|
-
database:
|
|
27
|
+
database: databaseName,
|
|
20
28
|
|
|
21
29
|
// Native `pg` pool options — tune these for your workload.
|
|
22
30
|
// max: 10,
|
|
@@ -5,10 +5,16 @@ import type {
|
|
|
5
5
|
} from "@warlock.js/cascade";
|
|
6
6
|
import { env } from "@warlock.js/core";
|
|
7
7
|
|
|
8
|
+
// Each Vitest worker keeps its own database and truncates it between test files
|
|
9
|
+
// (src/config/tests.ts), so tests run against "<DB_NAME>_test_<worker id>".
|
|
10
|
+
const databaseName = process.env.VITEST
|
|
11
|
+
? `${env("DB_NAME", "app")}_test_${process.env.VITEST_POOL_ID ?? 1}`
|
|
12
|
+
: env("DB_NAME");
|
|
13
|
+
|
|
8
14
|
const databaseConfigurations: ConnectionOptions<MongoDriverOptions, MongoClientOptions> = {
|
|
9
15
|
driver: env("DB_DRIVER", "mongodb"),
|
|
10
16
|
name: "default",
|
|
11
|
-
database:
|
|
17
|
+
database: databaseName,
|
|
12
18
|
host: env("DB_HOST", "localhost"),
|
|
13
19
|
port: env("DB_PORT", 27017),
|
|
14
20
|
username: env("DB_USERNAME"),
|
|
@@ -1,4 +1,18 @@
|
|
|
1
|
+
// The framework boots once per Vitest worker and every test file starts from a reset state
|
|
2
|
+
// (the default since 5.38): the database is truncated, the fakes, the request context and the
|
|
3
|
+
// route table are restored. Each worker uses its own database, see src/config/database.ts.
|
|
4
|
+
//
|
|
5
|
+
// WARLOCK_TEST_LIFETIME=file boots the framework once per test file instead (slower, with
|
|
6
|
+
// isolated module state). A spec that changes global configuration the reset does not undo
|
|
7
|
+
// opts out for its file with `setupTest({ importModule, lifetime: "file" })`.
|
|
8
|
+
const workerLifetime = process.env.WARLOCK_TEST_LIFETIME !== "file";
|
|
9
|
+
|
|
1
10
|
const testsConfigurations = {
|
|
11
|
+
lifetime: workerLifetime ? "worker" : "file",
|
|
12
|
+
resetDatabase: workerLifetime,
|
|
13
|
+
// WARLOCK_TEST_PROFILE=1 prints the boot phases and the slowest files of a run.
|
|
14
|
+
profileSetup: process.env.WARLOCK_TEST_PROFILE === "1",
|
|
15
|
+
|
|
2
16
|
// In tests, mail, queue, cache, storage, clock and ids are faked by default:
|
|
3
17
|
// nothing is delivered, pushed to Redis or uploaded, and assertions such as
|
|
4
18
|
// mailFake.assertSent(...) work (see @warlock.js/core/tests).
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { WebConfigurations } from "@warlock.js/web";
|
|
2
|
+
|
|
3
|
+
const webConfigurations: WebConfigurations = {
|
|
4
|
+
// Browser code may import only browser-safe module files; anything else fails the client build.
|
|
5
|
+
moduleBoundary: "error",
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export default webConfigurations;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
.forbidden,
|
|
2
|
+
.forbidden *,
|
|
3
|
+
.forbidden *::before,
|
|
4
|
+
.forbidden *::after {
|
|
5
|
+
box-sizing: border-box;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.forbidden {
|
|
9
|
+
display: grid;
|
|
10
|
+
min-block-size: 100vh;
|
|
11
|
+
min-block-size: 100dvb;
|
|
12
|
+
align-items: center;
|
|
13
|
+
padding-block: clamp(2rem, 8vh, 6rem);
|
|
14
|
+
padding-inline: clamp(1.5rem, 7vw, 8rem);
|
|
15
|
+
background: #fafafa;
|
|
16
|
+
color: #18181b;
|
|
17
|
+
/* prettier-ignore */
|
|
18
|
+
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.forbidden__content {
|
|
22
|
+
inline-size: min(100%, 62rem);
|
|
23
|
+
margin-inline-start: clamp(0rem, 7vw, 8rem);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.forbidden__title,
|
|
27
|
+
.forbidden__description {
|
|
28
|
+
margin: 0;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.forbidden h1 {
|
|
32
|
+
margin: 0;
|
|
33
|
+
color: #facc15;
|
|
34
|
+
font-size: clamp(8rem, 25vw, 22rem);
|
|
35
|
+
font-weight: 800;
|
|
36
|
+
letter-spacing: -0.09em;
|
|
37
|
+
line-height: 0.72;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.forbidden__title {
|
|
41
|
+
margin-block-start: clamp(2rem, 6vw, 5rem);
|
|
42
|
+
font-size: clamp(1.5rem, 3vw, 2.5rem);
|
|
43
|
+
font-weight: 680;
|
|
44
|
+
letter-spacing: -0.035em;
|
|
45
|
+
line-height: 1.1;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.forbidden__description {
|
|
49
|
+
max-inline-size: 30rem;
|
|
50
|
+
margin-block-start: 0.75rem;
|
|
51
|
+
color: #52525b;
|
|
52
|
+
font-size: clamp(1rem, 1.4vw, 1.125rem);
|
|
53
|
+
line-height: 1.55;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.forbidden__action {
|
|
57
|
+
display: inline-block;
|
|
58
|
+
margin-block-start: clamp(1.5rem, 3vw, 2.5rem);
|
|
59
|
+
padding-block: 0.65rem;
|
|
60
|
+
padding-inline: 1rem;
|
|
61
|
+
background: #facc15;
|
|
62
|
+
color: #18181b;
|
|
63
|
+
font-weight: 700;
|
|
64
|
+
text-decoration: none;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.forbidden__action:hover {
|
|
68
|
+
text-decoration: underline;
|
|
69
|
+
text-underline-offset: 0.18em;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.forbidden__action:focus-visible {
|
|
73
|
+
outline: 3px solid #18181b;
|
|
74
|
+
outline-offset: 4px;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
:dir(rtl) .forbidden__content {
|
|
78
|
+
margin-inline-start: 0;
|
|
79
|
+
margin-inline-end: clamp(0rem, 7vw, 8rem);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
@media (max-width: 40rem) {
|
|
83
|
+
.forbidden__content {
|
|
84
|
+
margin-inline-start: 0;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
:dir(rtl) .forbidden__content {
|
|
88
|
+
margin-inline-end: 0;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
@media (prefers-color-scheme: dark) {
|
|
93
|
+
.forbidden {
|
|
94
|
+
background: #18181b;
|
|
95
|
+
color: #fafafa;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.forbidden__description {
|
|
99
|
+
color: #d4d4d8;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.forbidden__action {
|
|
103
|
+
color: #18181b;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.forbidden__action:focus-visible {
|
|
107
|
+
outline-color: #fafafa;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { ErrorPageProps } from "@warlock.js/web";
|
|
2
|
+
|
|
3
|
+
export default function ForbiddenPage({ status }: ErrorPageProps) {
|
|
4
|
+
return (
|
|
5
|
+
<main className="forbidden">
|
|
6
|
+
<div className="forbidden__content">
|
|
7
|
+
<h1>{status}</h1>
|
|
8
|
+
<p className="forbidden__title">You do not have access to this page.</p>
|
|
9
|
+
<p className="forbidden__description">
|
|
10
|
+
Sign in with an account that has access, or head back to the home page.
|
|
11
|
+
</p>
|
|
12
|
+
<a className="forbidden__action" href="/">
|
|
13
|
+
Return home
|
|
14
|
+
</a>
|
|
15
|
+
</div>
|
|
16
|
+
</main>
|
|
17
|
+
);
|
|
18
|
+
}
|
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
import { groupedTranslations } from "@warlock.js/core";
|
|
2
|
-
|
|
3
|
-
groupedTranslations("auth", {
|
|
4
|
-
// App-level translations
|
|
5
|
-
invalidCredentials: {
|
|
6
|
-
en: "Invalid email or password",
|
|
7
|
-
ar: "البريد الالكتروني أو كلمة المرور غير صحيحة",
|
|
8
|
-
},
|
|
9
|
-
loggedOut: {
|
|
10
|
-
en: "Logged out successfully",
|
|
11
|
-
ar: "تم تسجيل الخروج بنجاح",
|
|
12
|
-
},
|
|
13
|
-
loggedOutAll: {
|
|
14
|
-
en: "Logged out from all devices",
|
|
15
|
-
ar: "تم تسجيل الخروج من جميع الأجهزة",
|
|
16
|
-
},
|
|
17
|
-
refreshTokenRequired: {
|
|
18
|
-
en: "Refresh token is required",
|
|
19
|
-
ar: "رمز التحديث مطلوب",
|
|
20
|
-
},
|
|
21
|
-
invalidRefreshToken: {
|
|
22
|
-
en: "Invalid or expired refresh token",
|
|
23
|
-
ar: "رمز التحديث غير صالح أو منتهي الصلاحية",
|
|
24
|
-
},
|
|
25
|
-
|
|
26
|
-
userNotFound: {
|
|
27
|
-
en: "User not found",
|
|
28
|
-
ar: "المستخدم غير موجود",
|
|
29
|
-
},
|
|
30
|
-
|
|
31
|
-
// OTP translations
|
|
32
|
-
otpSent: {
|
|
33
|
-
en: "Verification code sent",
|
|
34
|
-
ar: "تم إرسال رمز التحقق",
|
|
35
|
-
},
|
|
36
|
-
missingOtp: {
|
|
37
|
-
en: "Verification code not found",
|
|
38
|
-
ar: "رمز التحقق غير موجود",
|
|
39
|
-
},
|
|
40
|
-
otpInvalid: {
|
|
41
|
-
en: "Invalid verification code",
|
|
42
|
-
ar: "رمز التحقق غير صالح",
|
|
43
|
-
},
|
|
44
|
-
otpExpired: {
|
|
45
|
-
en: "Verification code has expired",
|
|
46
|
-
ar: "رمز التحقق منتهي الصلاحية",
|
|
47
|
-
},
|
|
48
|
-
otpMaxAttempts: {
|
|
49
|
-
en: "Too many failed attempts",
|
|
50
|
-
ar: "عدد المحاولات تجاوز الحد المسموح",
|
|
51
|
-
},
|
|
52
|
-
passwordResetSuccess: {
|
|
53
|
-
en: "Password reset successfully",
|
|
54
|
-
ar: "تم إعادة تعيين كلمة المرور بنجاح",
|
|
55
|
-
},
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* ============================================
|
|
59
|
-
* @warlock.js/auth package translations
|
|
60
|
-
* ============================================
|
|
61
|
-
* The following translations are used by the @warlock.js/auth package
|
|
62
|
-
* in the auth middleware. They must be defined here so they can be
|
|
63
|
-
* translated in your application.
|
|
64
|
-
*/
|
|
65
|
-
errors: {
|
|
66
|
-
/**
|
|
67
|
-
* Called when the access token is missing from Authorization header
|
|
68
|
-
*/
|
|
69
|
-
missingAccessToken: {
|
|
70
|
-
en: "Access token is required",
|
|
71
|
-
ar: "رمز الوصول مطلوب",
|
|
72
|
-
},
|
|
73
|
-
/**
|
|
74
|
-
* Called when an access token is invalid or expired
|
|
75
|
-
*/
|
|
76
|
-
invalidAccessToken: {
|
|
77
|
-
en: "Invalid or expired access token",
|
|
78
|
-
ar: "رمز الوصول غير صالح أو منتهي الصلاحية",
|
|
79
|
-
},
|
|
80
|
-
/**
|
|
81
|
-
* Called when the current user type is not authorized to
|
|
82
|
-
* access a protected resource for certain user types
|
|
83
|
-
*/
|
|
84
|
-
unauthorized: {
|
|
85
|
-
en: "You are not authorized to access this resource",
|
|
86
|
-
ar: "غير مصرح لك بالوصول إلى هذا المورد",
|
|
87
|
-
},
|
|
88
|
-
},
|
|
89
|
-
});
|