create-charcole 2.2.0 → 2.2.2

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 (77) hide show
  1. package/.github/workflows/release.yml +26 -26
  2. package/CHANGELOG.md +301 -301
  3. package/LICENSE +21 -21
  4. package/README.md +357 -354
  5. package/bin/index.js +494 -444
  6. package/bin/lib/pkgManager.js +49 -49
  7. package/bin/lib/templateHandler.js +33 -33
  8. package/package.json +42 -27
  9. package/packages/swagger/BACKWARD_COMPATIBILITY.md +1 -1
  10. package/packages/swagger/CHANGELOG.md +1 -1
  11. package/packages/swagger/README.md +3 -3
  12. package/packages/swagger/package.json +57 -44
  13. package/packages/swagger/src/index.d.ts +126 -126
  14. package/packages/swagger/src/index.js +12 -12
  15. package/packages/swagger/src/setup.js +100 -100
  16. package/template/js/.env.example +16 -15
  17. package/template/js/README.md +982 -978
  18. package/template/js/basePackage.json +26 -26
  19. package/template/js/src/app.js +81 -81
  20. package/template/js/src/config/constants.js +20 -20
  21. package/template/js/src/config/env.js +26 -26
  22. package/template/js/src/config/swagger.config.js +15 -15
  23. package/template/js/src/lib/swagger/SWAGGER_GUIDE.md +3 -3
  24. package/template/js/src/middlewares/errorHandler.js +180 -180
  25. package/template/js/src/middlewares/requestLogger.js +33 -33
  26. package/template/js/src/middlewares/validateRequest.js +42 -42
  27. package/template/js/src/modules/auth/auth.constants.js +3 -3
  28. package/template/js/src/modules/auth/auth.controller.js +29 -29
  29. package/template/js/src/modules/auth/auth.middlewares.js +19 -19
  30. package/template/js/src/modules/auth/auth.routes.js +131 -131
  31. package/template/js/src/modules/auth/auth.schemas.js +60 -60
  32. package/template/js/src/modules/auth/auth.service.js +67 -67
  33. package/template/js/src/modules/auth/package.json +6 -6
  34. package/template/js/src/modules/health/controller.js +151 -151
  35. package/template/js/src/modules/swagger/package.json +5 -5
  36. package/template/js/src/repositories/user.repo.js +19 -19
  37. package/template/js/src/routes/index.js +25 -25
  38. package/template/js/src/routes/protected.js +57 -57
  39. package/template/js/src/server.js +38 -38
  40. package/template/js/src/utils/AppError.js +182 -182
  41. package/template/js/src/utils/logger.js +73 -73
  42. package/template/js/src/utils/response.js +51 -51
  43. package/template/ts/.env.example +16 -15
  44. package/template/ts/README.md +982 -978
  45. package/template/ts/basePackage.json +36 -36
  46. package/template/ts/build.js +46 -46
  47. package/template/ts/src/app.ts +71 -71
  48. package/template/ts/src/config/constants.ts +27 -27
  49. package/template/ts/src/config/env.ts +40 -40
  50. package/template/ts/src/config/swagger.config.ts +30 -30
  51. package/template/ts/src/lib/swagger/SWAGGER_GUIDE.md +2 -2
  52. package/template/ts/src/middlewares/errorHandler.ts +201 -201
  53. package/template/ts/src/middlewares/requestLogger.ts +38 -38
  54. package/template/ts/src/middlewares/validateRequest.ts +46 -46
  55. package/template/ts/src/modules/auth/auth.constants.ts +6 -6
  56. package/template/ts/src/modules/auth/auth.controller.ts +32 -32
  57. package/template/ts/src/modules/auth/auth.middlewares.ts +46 -46
  58. package/template/ts/src/modules/auth/auth.routes.ts +52 -52
  59. package/template/ts/src/modules/auth/auth.schemas.ts +73 -73
  60. package/template/ts/src/modules/auth/auth.service.ts +106 -106
  61. package/template/ts/src/modules/auth/package.json +10 -10
  62. package/template/ts/src/modules/health/controller.ts +80 -80
  63. package/template/ts/src/modules/swagger/package.json +5 -5
  64. package/template/ts/src/repositories/user.repo.ts +33 -33
  65. package/template/ts/src/routes/index.ts +24 -24
  66. package/template/ts/src/routes/protected.ts +46 -46
  67. package/template/ts/src/server.ts +41 -41
  68. package/template/ts/src/types/express.d.ts +9 -9
  69. package/template/ts/src/utils/AppError.ts +220 -220
  70. package/template/ts/src/utils/logger.ts +55 -55
  71. package/template/ts/src/utils/response.ts +100 -100
  72. package/template/ts/tsconfig.json +26 -26
  73. package/packages/swagger/package-lock.json +0 -1715
  74. package/tmpclaude-1049-cwd +0 -1
  75. package/tmpclaude-3e37-cwd +0 -1
  76. package/tmpclaude-4d73-cwd +0 -1
  77. package/tmpclaude-8a8e-cwd +0 -1
@@ -1,46 +1,46 @@
1
- import jwt from "jsonwebtoken";
2
- import { Request, Response, NextFunction } from "express";
3
-
4
- type JwtPayload = {
5
- sub: string;
6
- email: string;
7
- role: string;
8
- };
9
-
10
- // Extend Express Request type to include user property
11
- declare global {
12
- namespace Express {
13
- interface Request {
14
- user?: JwtPayload;
15
- }
16
- }
17
- }
18
-
19
- export const requireAuth = (
20
- req: Request,
21
- res: Response,
22
- next: NextFunction,
23
- ): void => {
24
- const authHeader = req.headers.authorization;
25
-
26
- if (!authHeader?.startsWith("Bearer ")) {
27
- res.status(401).json({ message: "Unauthorized" });
28
- return;
29
- }
30
-
31
- const token = authHeader.split(" ")[1];
32
-
33
- try {
34
- const secret = process.env.JWT_SECRET;
35
-
36
- if (!secret) {
37
- throw new Error("JWT_SECRET environment variable is not defined");
38
- }
39
-
40
- const payload = jwt.verify(token, secret) as JwtPayload;
41
- req.user = payload;
42
- next();
43
- } catch {
44
- res.status(401).json({ message: "Invalid token" });
45
- }
46
- };
1
+ import jwt from "jsonwebtoken";
2
+ import { Request, Response, NextFunction } from "express";
3
+
4
+ type JwtPayload = {
5
+ sub: string;
6
+ email: string;
7
+ role: string;
8
+ };
9
+
10
+ // Extend Express Request type to include user property
11
+ declare global {
12
+ namespace Express {
13
+ interface Request {
14
+ user?: JwtPayload;
15
+ }
16
+ }
17
+ }
18
+
19
+ export const requireAuth = (
20
+ req: Request,
21
+ res: Response,
22
+ next: NextFunction,
23
+ ): void => {
24
+ const authHeader = req.headers.authorization;
25
+
26
+ if (!authHeader?.startsWith("Bearer ")) {
27
+ res.status(401).json({ message: "Unauthorized" });
28
+ return;
29
+ }
30
+
31
+ const token = authHeader.split(" ")[1];
32
+
33
+ try {
34
+ const secret = process.env.JWT_SECRET;
35
+
36
+ if (!secret) {
37
+ throw new Error("JWT_SECRET environment variable is not defined");
38
+ }
39
+
40
+ const payload = jwt.verify(token, secret) as JwtPayload;
41
+ req.user = payload;
42
+ next();
43
+ } catch {
44
+ res.status(401).json({ message: "Invalid token" });
45
+ }
46
+ };
@@ -1,52 +1,52 @@
1
- import { Router } from "express";
2
- import { AuthController } from "./auth.controller.ts";
3
-
4
- const router = Router();
5
-
6
- /**
7
- * @swagger
8
- * /api/auth/register:
9
- * post:
10
- * summary: Register a new user
11
- * description: Create a new user account with email and password
12
- * tags:
13
- * - Authentication
14
- * requestBody:
15
- * required: true
16
- * content:
17
- * application/json:
18
- * schema:
19
- * $ref: '#/components/schemas/registerSchema'
20
- * responses:
21
- * 201:
22
- * $ref: '#/components/responses/Success'
23
- * 400:
24
- * $ref: '#/components/responses/ValidationError'
25
- */
26
- router.post("/register", AuthController.register);
27
-
28
- /**
29
- * @swagger
30
- * /api/auth/login:
31
- * post:
32
- * summary: Login user
33
- * description: Authenticate user with email and password
34
- * tags:
35
- * - Authentication
36
- * requestBody:
37
- * required: true
38
- * content:
39
- * application/json:
40
- * schema:
41
- * $ref: '#/components/schemas/loginSchema'
42
- * responses:
43
- * 200:
44
- * $ref: '#/components/responses/Success'
45
- * 401:
46
- * $ref: '#/components/responses/Unauthorized'
47
- * 400:
48
- * $ref: '#/components/responses/ValidationError'
49
- */
50
- router.post("/login", AuthController.login);
51
-
52
- export default router;
1
+ import { Router } from "express";
2
+ import { AuthController } from "./auth.controller.ts";
3
+
4
+ const router = Router();
5
+
6
+ /**
7
+ * @swagger
8
+ * /api/auth/register:
9
+ * post:
10
+ * summary: Register a new user
11
+ * description: Create a new user account with email and password
12
+ * tags:
13
+ * - Authentication
14
+ * requestBody:
15
+ * required: true
16
+ * content:
17
+ * application/json:
18
+ * schema:
19
+ * $ref: '#/components/schemas/registerSchema'
20
+ * responses:
21
+ * 201:
22
+ * $ref: '#/components/responses/Success'
23
+ * 400:
24
+ * $ref: '#/components/responses/ValidationError'
25
+ */
26
+ router.post("/register", AuthController.register);
27
+
28
+ /**
29
+ * @swagger
30
+ * /api/auth/login:
31
+ * post:
32
+ * summary: Login user
33
+ * description: Authenticate user with email and password
34
+ * tags:
35
+ * - Authentication
36
+ * requestBody:
37
+ * required: true
38
+ * content:
39
+ * application/json:
40
+ * schema:
41
+ * $ref: '#/components/schemas/loginSchema'
42
+ * responses:
43
+ * 200:
44
+ * $ref: '#/components/responses/Success'
45
+ * 401:
46
+ * $ref: '#/components/responses/Unauthorized'
47
+ * 400:
48
+ * $ref: '#/components/responses/ValidationError'
49
+ */
50
+ router.post("/login", AuthController.login);
51
+
52
+ export default router;
@@ -1,73 +1,73 @@
1
- import { z } from "zod";
2
- import { USER_ROLES, AUTH_PROVIDERS } from "./auth.constants.ts";
3
-
4
- export const emailSchema = z
5
- .string()
6
- .email("Invalid email address")
7
- .toLowerCase();
8
-
9
- export const passwordSchema = z
10
- .string()
11
- .min(8, "Password must be at least 8 characters")
12
- .max(72, "Password too long");
13
-
14
- export const userSchema = z.object({
15
- id: z.string().uuid(),
16
- email: emailSchema,
17
- name: z.string().min(1).max(100),
18
- role: z.enum(USER_ROLES).default("user"),
19
- provider: z.enum(AUTH_PROVIDERS).default("credentials"),
20
-
21
- passwordHash: z.string().optional(), // credentials only
22
- isEmailVerified: z.boolean().default(false),
23
-
24
- createdAt: z.date(),
25
- updatedAt: z.date(),
26
- });
27
-
28
- export const registerSchema = z.object({
29
- name: z.string().min(1, "Name is required"),
30
- email: emailSchema,
31
- password: passwordSchema,
32
- });
33
-
34
- export const loginSchema = z.object({
35
- email: emailSchema,
36
- password: z.string().min(1, "Password is required"),
37
- });
38
-
39
- export const jwtPayloadSchema = z.object({
40
- sub: z.string().uuid(), // user id
41
- email: emailSchema,
42
- role: z.enum(USER_ROLES),
43
- });
44
-
45
- export const forgotPasswordSchema = z.object({
46
- email: emailSchema,
47
- });
48
-
49
- export const resetPasswordSchema = z.object({
50
- token: z.string().min(1),
51
- newPassword: passwordSchema,
52
- });
53
-
54
- export const publicUserSchema = userSchema.omit({
55
- passwordHash: true,
56
- });
57
-
58
- // Generic validate function with proper typing
59
- export const validate = <T extends z.ZodTypeAny>(
60
- schema: T,
61
- data: unknown,
62
- ): z.infer<T> => {
63
- return schema.parse(data);
64
- };
65
-
66
- // Export inferred types for use in other files
67
- export type User = z.infer<typeof userSchema>;
68
- export type RegisterInput = z.infer<typeof registerSchema>;
69
- export type LoginInput = z.infer<typeof loginSchema>;
70
- export type JwtPayload = z.infer<typeof jwtPayloadSchema>;
71
- export type ForgotPasswordInput = z.infer<typeof forgotPasswordSchema>;
72
- export type ResetPasswordInput = z.infer<typeof resetPasswordSchema>;
73
- export type PublicUser = z.infer<typeof publicUserSchema>;
1
+ import { z } from "zod";
2
+ import { USER_ROLES, AUTH_PROVIDERS } from "./auth.constants.ts";
3
+
4
+ export const emailSchema = z
5
+ .string()
6
+ .email("Invalid email address")
7
+ .toLowerCase();
8
+
9
+ export const passwordSchema = z
10
+ .string()
11
+ .min(8, "Password must be at least 8 characters")
12
+ .max(72, "Password too long");
13
+
14
+ export const userSchema = z.object({
15
+ id: z.string().uuid(),
16
+ email: emailSchema,
17
+ name: z.string().min(1).max(100),
18
+ role: z.enum(USER_ROLES).default("user"),
19
+ provider: z.enum(AUTH_PROVIDERS).default("credentials"),
20
+
21
+ passwordHash: z.string().optional(), // credentials only
22
+ isEmailVerified: z.boolean().default(false),
23
+
24
+ createdAt: z.date(),
25
+ updatedAt: z.date(),
26
+ });
27
+
28
+ export const registerSchema = z.object({
29
+ name: z.string().min(1, "Name is required"),
30
+ email: emailSchema,
31
+ password: passwordSchema,
32
+ });
33
+
34
+ export const loginSchema = z.object({
35
+ email: emailSchema,
36
+ password: z.string().min(1, "Password is required"),
37
+ });
38
+
39
+ export const jwtPayloadSchema = z.object({
40
+ sub: z.string().uuid(), // user id
41
+ email: emailSchema,
42
+ role: z.enum(USER_ROLES),
43
+ });
44
+
45
+ export const forgotPasswordSchema = z.object({
46
+ email: emailSchema,
47
+ });
48
+
49
+ export const resetPasswordSchema = z.object({
50
+ token: z.string().min(1),
51
+ newPassword: passwordSchema,
52
+ });
53
+
54
+ export const publicUserSchema = userSchema.omit({
55
+ passwordHash: true,
56
+ });
57
+
58
+ // Generic validate function with proper typing
59
+ export const validate = <T extends z.ZodTypeAny>(
60
+ schema: T,
61
+ data: unknown,
62
+ ): z.infer<T> => {
63
+ return schema.parse(data);
64
+ };
65
+
66
+ // Export inferred types for use in other files
67
+ export type User = z.infer<typeof userSchema>;
68
+ export type RegisterInput = z.infer<typeof registerSchema>;
69
+ export type LoginInput = z.infer<typeof loginSchema>;
70
+ export type JwtPayload = z.infer<typeof jwtPayloadSchema>;
71
+ export type ForgotPasswordInput = z.infer<typeof forgotPasswordSchema>;
72
+ export type ResetPasswordInput = z.infer<typeof resetPasswordSchema>;
73
+ export type PublicUser = z.infer<typeof publicUserSchema>;
@@ -1,106 +1,106 @@
1
- import bcrypt from "bcryptjs";
2
- import jwt from "jsonwebtoken";
3
- import { registerSchema, loginSchema } from "./auth.schemas.ts";
4
- import type { z } from "zod";
5
-
6
- const SALT_ROUNDS = 10;
7
- const JWT_EXPIRES_IN = "7d";
8
-
9
- // Type definitions
10
- type RegisterInput = z.infer<typeof registerSchema>;
11
- type LoginInput = z.infer<typeof loginSchema>;
12
-
13
- type User = {
14
- id: string;
15
- email: string;
16
- name: string;
17
- passwordHash: string;
18
- role: string;
19
- };
20
-
21
- type UserRepo = {
22
- findByEmail(email: string): Promise<User | null>;
23
- create(data: {
24
- email: string;
25
- name: string;
26
- passwordHash: string;
27
- }): Promise<User>;
28
- };
29
-
30
- type JwtPayload = {
31
- sub: string;
32
- email: string;
33
- role: string;
34
- };
35
-
36
- type LoginResult = {
37
- user: User;
38
- token: string;
39
- };
40
-
41
- export const AuthService = {
42
- async hashPassword(password: string): Promise<string> {
43
- return bcrypt.hash(password, SALT_ROUNDS);
44
- },
45
-
46
- async comparePassword(password: string, hash: string): Promise<boolean> {
47
- return bcrypt.compare(password, hash);
48
- },
49
-
50
- signToken(payload: JwtPayload): string {
51
- const secret = process.env.JWT_SECRET;
52
-
53
- if (!secret) {
54
- throw new Error("JWT_SECRET environment variable is not defined");
55
- }
56
-
57
- return jwt.sign(payload, secret, {
58
- expiresIn: JWT_EXPIRES_IN,
59
- });
60
- },
61
-
62
- async register(data: unknown, userRepo: UserRepo): Promise<User> {
63
- const input: RegisterInput = registerSchema.parse(data);
64
-
65
- const existingUser = await userRepo.findByEmail(input.email);
66
- if (existingUser) {
67
- throw new Error("Email already in use");
68
- }
69
-
70
- const passwordHash = await this.hashPassword(input.password);
71
-
72
- const user = await userRepo.create({
73
- email: input.email,
74
- name: input.name,
75
- passwordHash,
76
- });
77
-
78
- return user;
79
- },
80
-
81
- async login(data: unknown, userRepo: UserRepo): Promise<LoginResult> {
82
- const input: LoginInput = loginSchema.parse(data);
83
-
84
- const user = await userRepo.findByEmail(input.email);
85
- if (!user || !user.passwordHash) {
86
- throw new Error("Invalid credentials");
87
- }
88
-
89
- const isValid = await this.comparePassword(
90
- input.password,
91
- user.passwordHash,
92
- );
93
-
94
- if (!isValid) {
95
- throw new Error("Invalid credentials");
96
- }
97
-
98
- const token = this.signToken({
99
- sub: user.id,
100
- email: user.email,
101
- role: user.role,
102
- });
103
-
104
- return { user, token };
105
- },
106
- };
1
+ import bcrypt from "bcryptjs";
2
+ import jwt from "jsonwebtoken";
3
+ import { registerSchema, loginSchema } from "./auth.schemas.ts";
4
+ import type { z } from "zod";
5
+
6
+ const SALT_ROUNDS = 10;
7
+ const JWT_EXPIRES_IN = "7d";
8
+
9
+ // Type definitions
10
+ type RegisterInput = z.infer<typeof registerSchema>;
11
+ type LoginInput = z.infer<typeof loginSchema>;
12
+
13
+ type User = {
14
+ id: string;
15
+ email: string;
16
+ name: string;
17
+ passwordHash: string;
18
+ role: string;
19
+ };
20
+
21
+ type UserRepo = {
22
+ findByEmail(email: string): Promise<User | null>;
23
+ create(data: {
24
+ email: string;
25
+ name: string;
26
+ passwordHash: string;
27
+ }): Promise<User>;
28
+ };
29
+
30
+ type JwtPayload = {
31
+ sub: string;
32
+ email: string;
33
+ role: string;
34
+ };
35
+
36
+ type LoginResult = {
37
+ user: User;
38
+ token: string;
39
+ };
40
+
41
+ export const AuthService = {
42
+ async hashPassword(password: string): Promise<string> {
43
+ return bcrypt.hash(password, SALT_ROUNDS);
44
+ },
45
+
46
+ async comparePassword(password: string, hash: string): Promise<boolean> {
47
+ return bcrypt.compare(password, hash);
48
+ },
49
+
50
+ signToken(payload: JwtPayload): string {
51
+ const secret = process.env.JWT_SECRET;
52
+
53
+ if (!secret) {
54
+ throw new Error("JWT_SECRET environment variable is not defined");
55
+ }
56
+
57
+ return jwt.sign(payload, secret, {
58
+ expiresIn: JWT_EXPIRES_IN,
59
+ });
60
+ },
61
+
62
+ async register(data: unknown, userRepo: UserRepo): Promise<User> {
63
+ const input: RegisterInput = registerSchema.parse(data);
64
+
65
+ const existingUser = await userRepo.findByEmail(input.email);
66
+ if (existingUser) {
67
+ throw new Error("Email already in use");
68
+ }
69
+
70
+ const passwordHash = await this.hashPassword(input.password);
71
+
72
+ const user = await userRepo.create({
73
+ email: input.email,
74
+ name: input.name,
75
+ passwordHash,
76
+ });
77
+
78
+ return user;
79
+ },
80
+
81
+ async login(data: unknown, userRepo: UserRepo): Promise<LoginResult> {
82
+ const input: LoginInput = loginSchema.parse(data);
83
+
84
+ const user = await userRepo.findByEmail(input.email);
85
+ if (!user || !user.passwordHash) {
86
+ throw new Error("Invalid credentials");
87
+ }
88
+
89
+ const isValid = await this.comparePassword(
90
+ input.password,
91
+ user.passwordHash,
92
+ );
93
+
94
+ if (!isValid) {
95
+ throw new Error("Invalid credentials");
96
+ }
97
+
98
+ const token = this.signToken({
99
+ sub: user.id,
100
+ email: user.email,
101
+ role: user.role,
102
+ });
103
+
104
+ return { user, token };
105
+ },
106
+ };
@@ -1,10 +1,10 @@
1
- {
2
- "dependencies": {
3
- "bcryptjs": "^3.0.3",
4
- "jsonwebtoken": "^9.0.3"
5
- },
6
- "devDependencies": {
7
- "@types/bcryptjs": "^2.4.6",
8
- "@types/jsonwebtoken": "^9.0.10"
9
- }
10
- }
1
+ {
2
+ "dependencies": {
3
+ "bcryptjs": "^3.0.3",
4
+ "jsonwebtoken": "^9.0.3"
5
+ },
6
+ "devDependencies": {
7
+ "@types/bcryptjs": "^2.4.6",
8
+ "@types/jsonwebtoken": "^9.0.10"
9
+ }
10
+ }