servcraft 0.1.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.
Files changed (106) hide show
  1. package/.dockerignore +45 -0
  2. package/.env.example +46 -0
  3. package/.husky/commit-msg +1 -0
  4. package/.husky/pre-commit +1 -0
  5. package/.prettierignore +4 -0
  6. package/.prettierrc +11 -0
  7. package/Dockerfile +76 -0
  8. package/Dockerfile.dev +31 -0
  9. package/README.md +232 -0
  10. package/commitlint.config.js +24 -0
  11. package/dist/cli/index.cjs +3968 -0
  12. package/dist/cli/index.cjs.map +1 -0
  13. package/dist/cli/index.d.cts +1 -0
  14. package/dist/cli/index.d.ts +1 -0
  15. package/dist/cli/index.js +3945 -0
  16. package/dist/cli/index.js.map +1 -0
  17. package/dist/index.cjs +2458 -0
  18. package/dist/index.cjs.map +1 -0
  19. package/dist/index.d.cts +828 -0
  20. package/dist/index.d.ts +828 -0
  21. package/dist/index.js +2332 -0
  22. package/dist/index.js.map +1 -0
  23. package/docker-compose.prod.yml +118 -0
  24. package/docker-compose.yml +147 -0
  25. package/eslint.config.js +27 -0
  26. package/npm-cache/_cacache/content-v2/sha512/1c/d0/03440d500a0487621aad1d6402978340698976602046db8e24fa03c01ee6c022c69b0582f969042d9442ee876ac35c038e960dd427d1e622fa24b8eb7dba +0 -0
  27. package/npm-cache/_cacache/content-v2/sha512/42/55/28b493ca491833e5aab0e9c3108d29ab3f36c248ca88f45d4630674fce9130959e56ae308797ac2b6328fa7f09a610b9550ed09cb971d039876d293fc69d +0 -0
  28. package/npm-cache/_cacache/content-v2/sha512/e0/12/f360dc9315ee5f17844a0c8c233ee6bf7c30837c4a02ea0d56c61c7f7ab21c0e958e50ed2c57c59f983c762b93056778c9009b2398ffc26def0183999b13 +0 -0
  29. package/npm-cache/_cacache/content-v2/sha512/ed/b0/fae1161902898f4c913c67d7f6cdf6be0665aec3b389b9c4f4f0a101ca1da59badf1b59c4e0030f5223023b8d63cfe501c46a32c20c895d4fb3f11ca2232 +0 -0
  30. package/npm-cache/_cacache/index-v5/58/94/c2cba79e0f16b4c10e95a87e32255741149e8222cc314a476aab67c39cc0 +5 -0
  31. package/npm-cache/_update-notifier-last-checked +0 -0
  32. package/package.json +112 -0
  33. package/prisma/schema.prisma +157 -0
  34. package/src/cli/commands/add-module.ts +422 -0
  35. package/src/cli/commands/db.ts +137 -0
  36. package/src/cli/commands/docs.ts +16 -0
  37. package/src/cli/commands/generate.ts +459 -0
  38. package/src/cli/commands/init.ts +640 -0
  39. package/src/cli/index.ts +32 -0
  40. package/src/cli/templates/controller.ts +67 -0
  41. package/src/cli/templates/dynamic-prisma.ts +89 -0
  42. package/src/cli/templates/dynamic-schemas.ts +232 -0
  43. package/src/cli/templates/dynamic-types.ts +60 -0
  44. package/src/cli/templates/module-index.ts +33 -0
  45. package/src/cli/templates/prisma-model.ts +17 -0
  46. package/src/cli/templates/repository.ts +104 -0
  47. package/src/cli/templates/routes.ts +70 -0
  48. package/src/cli/templates/schemas.ts +26 -0
  49. package/src/cli/templates/service.ts +58 -0
  50. package/src/cli/templates/types.ts +27 -0
  51. package/src/cli/utils/docs-generator.ts +47 -0
  52. package/src/cli/utils/field-parser.ts +315 -0
  53. package/src/cli/utils/helpers.ts +89 -0
  54. package/src/config/env.ts +80 -0
  55. package/src/config/index.ts +97 -0
  56. package/src/core/index.ts +5 -0
  57. package/src/core/logger.ts +43 -0
  58. package/src/core/server.ts +132 -0
  59. package/src/database/index.ts +7 -0
  60. package/src/database/prisma.ts +54 -0
  61. package/src/database/seed.ts +59 -0
  62. package/src/index.ts +63 -0
  63. package/src/middleware/error-handler.ts +73 -0
  64. package/src/middleware/index.ts +3 -0
  65. package/src/middleware/security.ts +116 -0
  66. package/src/modules/audit/audit.service.ts +192 -0
  67. package/src/modules/audit/index.ts +2 -0
  68. package/src/modules/audit/types.ts +37 -0
  69. package/src/modules/auth/auth.controller.ts +182 -0
  70. package/src/modules/auth/auth.middleware.ts +87 -0
  71. package/src/modules/auth/auth.routes.ts +123 -0
  72. package/src/modules/auth/auth.service.ts +142 -0
  73. package/src/modules/auth/index.ts +49 -0
  74. package/src/modules/auth/schemas.ts +52 -0
  75. package/src/modules/auth/types.ts +69 -0
  76. package/src/modules/email/email.service.ts +212 -0
  77. package/src/modules/email/index.ts +10 -0
  78. package/src/modules/email/templates.ts +213 -0
  79. package/src/modules/email/types.ts +57 -0
  80. package/src/modules/swagger/index.ts +3 -0
  81. package/src/modules/swagger/schema-builder.ts +263 -0
  82. package/src/modules/swagger/swagger.service.ts +169 -0
  83. package/src/modules/swagger/types.ts +68 -0
  84. package/src/modules/user/index.ts +30 -0
  85. package/src/modules/user/schemas.ts +49 -0
  86. package/src/modules/user/types.ts +78 -0
  87. package/src/modules/user/user.controller.ts +139 -0
  88. package/src/modules/user/user.repository.ts +156 -0
  89. package/src/modules/user/user.routes.ts +199 -0
  90. package/src/modules/user/user.service.ts +145 -0
  91. package/src/modules/validation/index.ts +18 -0
  92. package/src/modules/validation/validator.ts +104 -0
  93. package/src/types/common.ts +61 -0
  94. package/src/types/index.ts +10 -0
  95. package/src/utils/errors.ts +66 -0
  96. package/src/utils/index.ts +33 -0
  97. package/src/utils/pagination.ts +38 -0
  98. package/src/utils/response.ts +63 -0
  99. package/tests/integration/auth.test.ts +59 -0
  100. package/tests/setup.ts +17 -0
  101. package/tests/unit/modules/validation.test.ts +88 -0
  102. package/tests/unit/utils/errors.test.ts +113 -0
  103. package/tests/unit/utils/pagination.test.ts +82 -0
  104. package/tsconfig.json +33 -0
  105. package/tsup.config.ts +14 -0
  106. package/vitest.config.ts +34 -0
@@ -0,0 +1,63 @@
1
+ import type { FastifyReply } from 'fastify';
2
+ import type { ApiResponse } from '../types/index.js';
3
+
4
+ export function success<T>(reply: FastifyReply, data: T, statusCode = 200): FastifyReply {
5
+ const response: ApiResponse<T> = {
6
+ success: true,
7
+ data,
8
+ };
9
+ return reply.status(statusCode).send(response);
10
+ }
11
+
12
+ export function created<T>(reply: FastifyReply, data: T): FastifyReply {
13
+ return success(reply, data, 201);
14
+ }
15
+
16
+ export function noContent(reply: FastifyReply): FastifyReply {
17
+ return reply.status(204).send();
18
+ }
19
+
20
+ export function error(
21
+ reply: FastifyReply,
22
+ message: string,
23
+ statusCode = 400,
24
+ errors?: Record<string, string[]>
25
+ ): FastifyReply {
26
+ const response: ApiResponse = {
27
+ success: false,
28
+ message,
29
+ errors,
30
+ };
31
+ return reply.status(statusCode).send(response);
32
+ }
33
+
34
+ export function notFound(reply: FastifyReply, message = 'Resource not found'): FastifyReply {
35
+ return error(reply, message, 404);
36
+ }
37
+
38
+ export function unauthorized(reply: FastifyReply, message = 'Unauthorized'): FastifyReply {
39
+ return error(reply, message, 401);
40
+ }
41
+
42
+ export function forbidden(reply: FastifyReply, message = 'Forbidden'): FastifyReply {
43
+ return error(reply, message, 403);
44
+ }
45
+
46
+ export function badRequest(
47
+ reply: FastifyReply,
48
+ message = 'Bad request',
49
+ errors?: Record<string, string[]>
50
+ ): FastifyReply {
51
+ return error(reply, message, 400, errors);
52
+ }
53
+
54
+ export function conflict(reply: FastifyReply, message = 'Resource already exists'): FastifyReply {
55
+ return error(reply, message, 409);
56
+ }
57
+
58
+ export function internalError(
59
+ reply: FastifyReply,
60
+ message = 'Internal server error'
61
+ ): FastifyReply {
62
+ return error(reply, message, 500);
63
+ }
@@ -0,0 +1,59 @@
1
+ import { describe, it, expect, beforeAll, afterAll } from 'vitest';
2
+ import Fastify from 'fastify';
3
+ import type { FastifyInstance } from 'fastify';
4
+
5
+ // Note: This is a placeholder for integration tests
6
+ // In a real scenario, you would set up the full app with database connections
7
+
8
+ describe('Auth Integration Tests', () => {
9
+ let app: FastifyInstance;
10
+
11
+ beforeAll(async () => {
12
+ app = Fastify();
13
+
14
+ // Mock health endpoint for basic test
15
+ app.get('/health', async () => ({
16
+ status: 'ok',
17
+ timestamp: new Date().toISOString(),
18
+ }));
19
+
20
+ await app.ready();
21
+ });
22
+
23
+ afterAll(async () => {
24
+ await app.close();
25
+ });
26
+
27
+ describe('Health Check', () => {
28
+ it('should return health status', async () => {
29
+ const response = await app.inject({
30
+ method: 'GET',
31
+ url: '/health',
32
+ });
33
+
34
+ expect(response.statusCode).toBe(200);
35
+ const body = JSON.parse(response.body);
36
+ expect(body.status).toBe('ok');
37
+ expect(body.timestamp).toBeDefined();
38
+ });
39
+ });
40
+
41
+ // Placeholder tests - would require full app setup
42
+ describe('Authentication Endpoints (Placeholder)', () => {
43
+ it.skip('POST /auth/register - should register a new user', async () => {
44
+ // This test would require the full app setup with database
45
+ });
46
+
47
+ it.skip('POST /auth/login - should login a user', async () => {
48
+ // This test would require the full app setup with database
49
+ });
50
+
51
+ it.skip('POST /auth/refresh - should refresh tokens', async () => {
52
+ // This test would require the full app setup with database
53
+ });
54
+
55
+ it.skip('POST /auth/logout - should logout a user', async () => {
56
+ // This test would require the full app setup with database
57
+ });
58
+ });
59
+ });
package/tests/setup.ts ADDED
@@ -0,0 +1,17 @@
1
+ import { beforeAll, afterAll, afterEach } from 'vitest';
2
+
3
+ // Set test environment
4
+ process.env.NODE_ENV = 'test';
5
+ process.env.LOG_LEVEL = 'error'; // Reduce log noise during tests
6
+
7
+ beforeAll(async () => {
8
+ // Setup code that runs before all tests
9
+ });
10
+
11
+ afterAll(async () => {
12
+ // Cleanup code that runs after all tests
13
+ });
14
+
15
+ afterEach(async () => {
16
+ // Cleanup code that runs after each test
17
+ });
@@ -0,0 +1,88 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { z } from 'zod';
3
+ import {
4
+ validate,
5
+ validateBody,
6
+ validateQuery,
7
+ emailSchema,
8
+ passwordSchema,
9
+ } from '../../../src/modules/validation/validator.js';
10
+ import { ValidationError } from '../../../src/utils/errors.js';
11
+
12
+ describe('Validation Module', () => {
13
+ describe('validate', () => {
14
+ const testSchema = z.object({
15
+ name: z.string().min(2),
16
+ age: z.number().positive(),
17
+ });
18
+
19
+ it('should return validated data for valid input', () => {
20
+ const input = { name: 'John', age: 25 };
21
+ const result = validate(testSchema, input);
22
+ expect(result).toEqual(input);
23
+ });
24
+
25
+ it('should throw ValidationError for invalid input', () => {
26
+ const input = { name: 'J', age: -1 };
27
+ expect(() => validate(testSchema, input)).toThrow(ValidationError);
28
+ });
29
+
30
+ it('should include field errors in ValidationError', () => {
31
+ const input = { name: 'J', age: -1 };
32
+ try {
33
+ validate(testSchema, input);
34
+ } catch (error) {
35
+ if (error instanceof ValidationError) {
36
+ expect(error.errors).toHaveProperty('name');
37
+ expect(error.errors).toHaveProperty('age');
38
+ }
39
+ }
40
+ });
41
+ });
42
+
43
+ describe('emailSchema', () => {
44
+ it('should accept valid emails', () => {
45
+ const validEmails = [
46
+ 'test@example.com',
47
+ 'user.name@domain.org',
48
+ 'user+tag@example.co.uk',
49
+ ];
50
+
51
+ validEmails.forEach((email) => {
52
+ expect(() => emailSchema.parse(email)).not.toThrow();
53
+ });
54
+ });
55
+
56
+ it('should reject invalid emails', () => {
57
+ const invalidEmails = ['notanemail', '@example.com', 'test@', 'test@.com'];
58
+
59
+ invalidEmails.forEach((email) => {
60
+ expect(() => emailSchema.parse(email)).toThrow();
61
+ });
62
+ });
63
+ });
64
+
65
+ describe('passwordSchema', () => {
66
+ it('should accept strong passwords', () => {
67
+ const validPasswords = ['Password1!', 'Str0ng@Pass', 'MyP@ssw0rd'];
68
+
69
+ validPasswords.forEach((password) => {
70
+ expect(() => passwordSchema.parse(password)).not.toThrow();
71
+ });
72
+ });
73
+
74
+ it('should reject weak passwords', () => {
75
+ const invalidPasswords = [
76
+ 'short', // Too short
77
+ 'nouppercase1!', // No uppercase
78
+ 'NOLOWERCASE1!', // No lowercase
79
+ 'NoNumbers!', // No numbers
80
+ 'NoSpecial1', // No special chars
81
+ ];
82
+
83
+ invalidPasswords.forEach((password) => {
84
+ expect(() => passwordSchema.parse(password)).toThrow();
85
+ });
86
+ });
87
+ });
88
+ });
@@ -0,0 +1,113 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import {
3
+ AppError,
4
+ NotFoundError,
5
+ UnauthorizedError,
6
+ ForbiddenError,
7
+ BadRequestError,
8
+ ConflictError,
9
+ ValidationError,
10
+ TooManyRequestsError,
11
+ isAppError,
12
+ } from '../../../src/utils/errors.js';
13
+
14
+ describe('Error Classes', () => {
15
+ describe('AppError', () => {
16
+ it('should create error with default values', () => {
17
+ const error = new AppError('Test error');
18
+ expect(error.message).toBe('Test error');
19
+ expect(error.statusCode).toBe(500);
20
+ expect(error.isOperational).toBe(true);
21
+ });
22
+
23
+ it('should create error with custom status code', () => {
24
+ const error = new AppError('Custom error', 418);
25
+ expect(error.statusCode).toBe(418);
26
+ });
27
+
28
+ it('should include errors object', () => {
29
+ const errors = { field: ['error message'] };
30
+ const error = new AppError('Validation error', 400, true, errors);
31
+ expect(error.errors).toEqual(errors);
32
+ });
33
+ });
34
+
35
+ describe('NotFoundError', () => {
36
+ it('should have 404 status code', () => {
37
+ const error = new NotFoundError('User');
38
+ expect(error.statusCode).toBe(404);
39
+ expect(error.message).toBe('User not found');
40
+ });
41
+
42
+ it('should use default message', () => {
43
+ const error = new NotFoundError();
44
+ expect(error.message).toBe('Resource not found');
45
+ });
46
+ });
47
+
48
+ describe('UnauthorizedError', () => {
49
+ it('should have 401 status code', () => {
50
+ const error = new UnauthorizedError();
51
+ expect(error.statusCode).toBe(401);
52
+ expect(error.message).toBe('Unauthorized');
53
+ });
54
+ });
55
+
56
+ describe('ForbiddenError', () => {
57
+ it('should have 403 status code', () => {
58
+ const error = new ForbiddenError();
59
+ expect(error.statusCode).toBe(403);
60
+ expect(error.message).toBe('Forbidden');
61
+ });
62
+ });
63
+
64
+ describe('BadRequestError', () => {
65
+ it('should have 400 status code', () => {
66
+ const error = new BadRequestError();
67
+ expect(error.statusCode).toBe(400);
68
+ });
69
+
70
+ it('should include validation errors', () => {
71
+ const errors = { email: ['Invalid email'] };
72
+ const error = new BadRequestError('Invalid input', errors);
73
+ expect(error.errors).toEqual(errors);
74
+ });
75
+ });
76
+
77
+ describe('ConflictError', () => {
78
+ it('should have 409 status code', () => {
79
+ const error = new ConflictError();
80
+ expect(error.statusCode).toBe(409);
81
+ });
82
+ });
83
+
84
+ describe('ValidationError', () => {
85
+ it('should have 422 status code', () => {
86
+ const errors = { name: ['Required'] };
87
+ const error = new ValidationError(errors);
88
+ expect(error.statusCode).toBe(422);
89
+ expect(error.errors).toEqual(errors);
90
+ });
91
+ });
92
+
93
+ describe('TooManyRequestsError', () => {
94
+ it('should have 429 status code', () => {
95
+ const error = new TooManyRequestsError();
96
+ expect(error.statusCode).toBe(429);
97
+ });
98
+ });
99
+
100
+ describe('isAppError', () => {
101
+ it('should return true for AppError instances', () => {
102
+ expect(isAppError(new AppError('test'))).toBe(true);
103
+ expect(isAppError(new NotFoundError())).toBe(true);
104
+ expect(isAppError(new ValidationError({}))).toBe(true);
105
+ });
106
+
107
+ it('should return false for other errors', () => {
108
+ expect(isAppError(new Error('test'))).toBe(false);
109
+ expect(isAppError(null)).toBe(false);
110
+ expect(isAppError('error')).toBe(false);
111
+ });
112
+ });
113
+ });
@@ -0,0 +1,82 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import {
3
+ parsePaginationParams,
4
+ createPaginatedResult,
5
+ getSkip,
6
+ DEFAULT_PAGE,
7
+ DEFAULT_LIMIT,
8
+ MAX_LIMIT,
9
+ } from '../../../src/utils/pagination.js';
10
+
11
+ describe('Pagination Utils', () => {
12
+ describe('parsePaginationParams', () => {
13
+ it('should return default values when no params provided', () => {
14
+ const result = parsePaginationParams({});
15
+ expect(result.page).toBe(DEFAULT_PAGE);
16
+ expect(result.limit).toBe(DEFAULT_LIMIT);
17
+ expect(result.sortOrder).toBe('asc');
18
+ });
19
+
20
+ it('should parse page and limit from query', () => {
21
+ const result = parsePaginationParams({ page: '3', limit: '50' });
22
+ expect(result.page).toBe(3);
23
+ expect(result.limit).toBe(50);
24
+ });
25
+
26
+ it('should enforce minimum page of 1', () => {
27
+ const result = parsePaginationParams({ page: '-1' });
28
+ expect(result.page).toBe(1);
29
+ });
30
+
31
+ it('should enforce maximum limit', () => {
32
+ const result = parsePaginationParams({ limit: '500' });
33
+ expect(result.limit).toBe(MAX_LIMIT);
34
+ });
35
+
36
+ it('should parse sortBy and sortOrder', () => {
37
+ const result = parsePaginationParams({ sortBy: 'name', sortOrder: 'desc' });
38
+ expect(result.sortBy).toBe('name');
39
+ expect(result.sortOrder).toBe('desc');
40
+ });
41
+ });
42
+
43
+ describe('createPaginatedResult', () => {
44
+ it('should create correct metadata', () => {
45
+ const data = [1, 2, 3];
46
+ const result = createPaginatedResult(data, 10, { page: 1, limit: 3 });
47
+
48
+ expect(result.data).toEqual(data);
49
+ expect(result.meta.total).toBe(10);
50
+ expect(result.meta.page).toBe(1);
51
+ expect(result.meta.limit).toBe(3);
52
+ expect(result.meta.totalPages).toBe(4);
53
+ expect(result.meta.hasNextPage).toBe(true);
54
+ expect(result.meta.hasPrevPage).toBe(false);
55
+ });
56
+
57
+ it('should handle last page correctly', () => {
58
+ const result = createPaginatedResult([1], 10, { page: 4, limit: 3 });
59
+
60
+ expect(result.meta.hasNextPage).toBe(false);
61
+ expect(result.meta.hasPrevPage).toBe(true);
62
+ });
63
+
64
+ it('should handle empty results', () => {
65
+ const result = createPaginatedResult([], 0, { page: 1, limit: 10 });
66
+
67
+ expect(result.data).toEqual([]);
68
+ expect(result.meta.total).toBe(0);
69
+ expect(result.meta.totalPages).toBe(0);
70
+ expect(result.meta.hasNextPage).toBe(false);
71
+ expect(result.meta.hasPrevPage).toBe(false);
72
+ });
73
+ });
74
+
75
+ describe('getSkip', () => {
76
+ it('should calculate correct skip value', () => {
77
+ expect(getSkip({ page: 1, limit: 10 })).toBe(0);
78
+ expect(getSkip({ page: 2, limit: 10 })).toBe(10);
79
+ expect(getSkip({ page: 3, limit: 20 })).toBe(40);
80
+ });
81
+ });
82
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
6
+ "lib": ["ES2022"],
7
+ "outDir": "./dist",
8
+ "rootDir": "./src",
9
+ "strict": true,
10
+ "esModuleInterop": true,
11
+ "skipLibCheck": true,
12
+ "forceConsistentCasingInFileNames": true,
13
+ "resolveJsonModule": true,
14
+ "declaration": true,
15
+ "declarationMap": true,
16
+ "sourceMap": true,
17
+ "noImplicitReturns": true,
18
+ "noFallthroughCasesInSwitch": true,
19
+ "noUncheckedIndexedAccess": true,
20
+ "noImplicitOverride": true,
21
+ "allowSyntheticDefaultImports": true,
22
+ "paths": {
23
+ "@/*": ["./src/*"],
24
+ "@core/*": ["./src/core/*"],
25
+ "@modules/*": ["./src/modules/*"],
26
+ "@config/*": ["./src/config/*"],
27
+ "@utils/*": ["./src/utils/*"]
28
+ },
29
+ "baseUrl": "."
30
+ },
31
+ "include": ["src/**/*"],
32
+ "exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts"]
33
+ }
package/tsup.config.ts ADDED
@@ -0,0 +1,14 @@
1
+ import { defineConfig } from 'tsup';
2
+
3
+ export default defineConfig({
4
+ entry: ['src/index.ts', 'src/cli/index.ts'],
5
+ format: ['esm', 'cjs'],
6
+ dts: true,
7
+ splitting: false,
8
+ sourcemap: true,
9
+ clean: true,
10
+ minify: false,
11
+ target: 'node18',
12
+ outDir: 'dist',
13
+ shims: true,
14
+ });
@@ -0,0 +1,34 @@
1
+ import { defineConfig } from 'vitest/config';
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ globals: true,
6
+ environment: 'node',
7
+ include: ['tests/**/*.test.ts', 'tests/**/*.spec.ts'],
8
+ exclude: ['node_modules', 'dist'],
9
+ coverage: {
10
+ provider: 'v8',
11
+ reporter: ['text', 'json', 'html'],
12
+ exclude: [
13
+ 'node_modules',
14
+ 'dist',
15
+ 'tests',
16
+ '**/*.d.ts',
17
+ '**/*.config.*',
18
+ '**/types.ts',
19
+ ],
20
+ },
21
+ testTimeout: 10000,
22
+ hookTimeout: 10000,
23
+ setupFiles: ['./tests/setup.ts'],
24
+ },
25
+ resolve: {
26
+ alias: {
27
+ '@': '/src',
28
+ '@core': '/src/core',
29
+ '@modules': '/src/modules',
30
+ '@config': '/src/config',
31
+ '@utils': '/src/utils',
32
+ },
33
+ },
34
+ });