create-charcole 2.0.4 → 2.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.
- package/CHANGELOG.md +200 -14
- package/README.md +137 -332
- package/bin/index.js +236 -55
- package/bin/lib/pkgManager.js +8 -25
- package/bin/lib/templateHandler.js +5 -42
- package/package.json +2 -2
- package/plans/V2_1_PLAN.md +20 -0
- package/template/js/.env.example +8 -0
- package/template/js/basePackage.json +11 -13
- package/template/js/src/app.js +4 -1
- package/template/js/src/modules/auth/auth.constants.js +3 -0
- package/template/js/src/modules/auth/auth.controller.js +29 -0
- package/template/js/src/modules/auth/auth.middlewares.js +19 -0
- package/template/js/src/modules/auth/auth.routes.js +9 -0
- package/template/js/src/modules/auth/auth.schemas.js +60 -0
- package/template/js/src/modules/auth/auth.service.js +67 -0
- package/template/js/src/modules/auth/package.json +6 -0
- package/template/js/src/repositories/user.repo.js +19 -0
- package/template/js/src/routes/index.js +25 -0
- package/template/js/src/routes/protected.js +13 -0
- package/template/ts/.env.example +8 -0
- package/template/ts/basePackage.json +19 -15
- package/template/ts/build.js +46 -0
- package/template/ts/src/app.ts +5 -4
- package/template/ts/src/middlewares/errorHandler.ts +15 -23
- package/template/ts/src/middlewares/requestLogger.ts +1 -1
- package/template/ts/src/middlewares/validateRequest.ts +1 -1
- package/template/ts/src/modules/auth/auth.constants.ts +6 -0
- package/template/ts/src/modules/auth/auth.controller.ts +32 -0
- package/template/ts/src/modules/auth/auth.middlewares.ts +46 -0
- package/template/ts/src/modules/auth/auth.routes.ts +9 -0
- package/template/ts/src/modules/auth/auth.schemas.ts +73 -0
- package/template/ts/src/modules/auth/auth.service.ts +106 -0
- package/template/ts/src/modules/auth/package.json +10 -0
- package/template/ts/src/modules/health/controller.ts +3 -3
- package/template/ts/src/repositories/user.repo.ts +33 -0
- package/template/ts/src/routes/index.ts +24 -0
- package/template/ts/src/routes/protected.ts +13 -0
- package/template/ts/src/server.ts +0 -1
- package/template/ts/src/utils/logger.ts +1 -1
- package/template/ts/tsconfig.json +14 -7
- package/template/js/ARCHITECTURE_DIAGRAMS.md +0 -283
- package/template/js/CHECKLIST.md +0 -279
- package/template/js/COMPLETE.md +0 -405
- package/template/js/ERROR_HANDLING.md +0 -393
- package/template/js/IMPLEMENTATION.md +0 -368
- package/template/js/IMPLEMENTATION_COMPLETE.md +0 -363
- package/template/js/INDEX.md +0 -290
- package/template/js/QUICK_REFERENCE.md +0 -270
- package/template/js/package.json +0 -28
- package/template/js/src/routes.js +0 -17
- package/template/js/test-api.js +0 -100
- package/template/ts/ARCHITECTURE_DIAGRAMS.md +0 -283
- package/template/ts/CHECKLIST.md +0 -279
- package/template/ts/COMPLETE.md +0 -405
- package/template/ts/ERROR_HANDLING.md +0 -393
- package/template/ts/IMPLEMENTATION.md +0 -368
- package/template/ts/IMPLEMENTATION_COMPLETE.md +0 -363
- package/template/ts/INDEX.md +0 -290
- package/template/ts/QUICK_REFERENCE.md +0 -270
- package/template/ts/package.json +0 -32
- package/template/ts/src/app.js +0 -75
- package/template/ts/src/config/constants.js +0 -20
- package/template/ts/src/config/env.js +0 -26
- package/template/ts/src/middlewares/errorHandler.js +0 -180
- package/template/ts/src/middlewares/requestLogger.js +0 -33
- package/template/ts/src/middlewares/validateRequest.js +0 -42
- package/template/ts/src/modules/health/controller.js +0 -50
- package/template/ts/src/routes.js +0 -17
- package/template/ts/src/routes.ts +0 -16
- package/template/ts/src/server.js +0 -38
- package/template/ts/src/utils/AppError.js +0 -182
- package/template/ts/src/utils/logger.js +0 -73
- package/template/ts/src/utils/response.js +0 -51
- package/template/ts/test-api.js +0 -100
package/bin/index.js
CHANGED
|
@@ -1,26 +1,104 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
1
|
+
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
const path = require("path");
|
|
4
4
|
const fs = require("fs");
|
|
5
|
-
const { execSync } = require("child_process");
|
|
6
5
|
const prompts = require("prompts");
|
|
7
|
-
|
|
6
|
+
|
|
8
7
|
const {
|
|
9
8
|
detectPackageManager,
|
|
10
9
|
installDependencies,
|
|
11
10
|
} = require("./lib/pkgManager");
|
|
12
11
|
|
|
12
|
+
/**
|
|
13
|
+
* Merge base package.json with a feature package.json
|
|
14
|
+
*/
|
|
15
|
+
function mergePackageJson(base, fragment) {
|
|
16
|
+
const merged = { ...base };
|
|
17
|
+
|
|
18
|
+
// Merge dependencies
|
|
19
|
+
if (fragment.dependencies) {
|
|
20
|
+
merged.dependencies = {
|
|
21
|
+
...merged.dependencies,
|
|
22
|
+
...fragment.dependencies,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Merge devDependencies
|
|
27
|
+
if (fragment.devDependencies) {
|
|
28
|
+
merged.devDependencies = {
|
|
29
|
+
...merged.devDependencies,
|
|
30
|
+
...fragment.devDependencies,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Merge scripts
|
|
35
|
+
if (fragment.scripts) {
|
|
36
|
+
merged.scripts = {
|
|
37
|
+
...merged.scripts,
|
|
38
|
+
...fragment.scripts,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return merged;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function copyDirRecursive(src, dest, excludeFiles = []) {
|
|
46
|
+
if (!fs.existsSync(src)) return;
|
|
47
|
+
|
|
48
|
+
if (!fs.existsSync(dest)) {
|
|
49
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
53
|
+
|
|
54
|
+
for (const entry of entries) {
|
|
55
|
+
const srcPath = path.join(src, entry.name);
|
|
56
|
+
const destPath = path.join(dest, entry.name);
|
|
57
|
+
|
|
58
|
+
if (excludeFiles.includes(entry.name)) {
|
|
59
|
+
console.log(`Skipping excluded file: ${entry.name}`);
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (entry.isDirectory()) {
|
|
64
|
+
copyDirRecursive(srcPath, destPath, excludeFiles);
|
|
65
|
+
} else {
|
|
66
|
+
fs.copyFileSync(srcPath, destPath);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
13
71
|
(async function main() {
|
|
14
72
|
try {
|
|
15
|
-
console.log("🔥 Welcome to Charcole v2 CLI");
|
|
73
|
+
console.log("🔥 Welcome to Charcole v2.1 CLI");
|
|
16
74
|
|
|
17
|
-
|
|
18
|
-
|
|
75
|
+
// Check if project name is provided as command line argument
|
|
76
|
+
const args = process.argv.slice(2);
|
|
77
|
+
let projectNameFromArgs = null;
|
|
78
|
+
|
|
79
|
+
if (args.length > 0) {
|
|
80
|
+
// The first argument that doesn't start with '-' is likely the project name
|
|
81
|
+
for (const arg of args) {
|
|
82
|
+
if (!arg.startsWith("-")) {
|
|
83
|
+
projectNameFromArgs = arg;
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const questions = [];
|
|
90
|
+
|
|
91
|
+
// Only ask for project name if not provided in command line
|
|
92
|
+
if (!projectNameFromArgs) {
|
|
93
|
+
questions.push({
|
|
19
94
|
type: "text",
|
|
20
95
|
name: "projectName",
|
|
21
96
|
message: "Project name:",
|
|
22
97
|
validate: (name) => (name ? true : "Project name is required"),
|
|
23
|
-
}
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
questions.push(
|
|
24
102
|
{
|
|
25
103
|
type: "select",
|
|
26
104
|
name: "language",
|
|
@@ -29,25 +107,26 @@ const {
|
|
|
29
107
|
{ title: "TypeScript", value: "ts" },
|
|
30
108
|
{ title: "JavaScript", value: "js" },
|
|
31
109
|
],
|
|
32
|
-
initial: 0,
|
|
33
110
|
},
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
111
|
+
{
|
|
112
|
+
type: "confirm",
|
|
113
|
+
name: "auth",
|
|
114
|
+
message: "Include JWT authentication module?",
|
|
115
|
+
initial: true,
|
|
116
|
+
},
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
const responses = await prompts(questions);
|
|
120
|
+
|
|
121
|
+
// Use command line project name if provided, otherwise use prompt response
|
|
122
|
+
const projectName = projectNameFromArgs || responses.projectName;
|
|
123
|
+
const { language, auth } = responses;
|
|
124
|
+
|
|
125
|
+
if (!projectName || projectName.trim() === "") {
|
|
126
|
+
console.error("❌ Project name is required");
|
|
127
|
+
process.exit(1);
|
|
128
|
+
}
|
|
129
|
+
|
|
51
130
|
const targetDir = path.join(process.cwd(), projectName);
|
|
52
131
|
|
|
53
132
|
if (fs.existsSync(targetDir)) {
|
|
@@ -56,41 +135,140 @@ const {
|
|
|
56
135
|
}
|
|
57
136
|
|
|
58
137
|
const pkgManager = detectPackageManager();
|
|
138
|
+
const templateDir = path.join(__dirname, "..", "template", language);
|
|
59
139
|
|
|
60
|
-
console.log(
|
|
140
|
+
console.log(
|
|
141
|
+
`\n📁 Creating project "${projectName}" in ${language.toUpperCase()}...`,
|
|
142
|
+
);
|
|
61
143
|
|
|
62
|
-
|
|
63
|
-
|
|
144
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
145
|
+
|
|
146
|
+
const basePkgPath = path.join(templateDir, "basePackage.json");
|
|
147
|
+
if (!fs.existsSync(basePkgPath)) {
|
|
148
|
+
throw new Error(`basePackage.json not found at ${basePkgPath}`);
|
|
149
|
+
}
|
|
64
150
|
|
|
65
|
-
|
|
151
|
+
let mergedPkg = JSON.parse(fs.readFileSync(basePkgPath, "utf-8"));
|
|
152
|
+
console.log("✓ Loaded base package configuration");
|
|
66
153
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
154
|
+
console.log("\n📁 Copying base template structure...");
|
|
155
|
+
|
|
156
|
+
copyDirRecursive(templateDir, targetDir, ["basePackage.json"]);
|
|
157
|
+
|
|
158
|
+
const templateModulesDir = path.join(templateDir, "src", "modules");
|
|
159
|
+
const targetModulesDir = path.join(targetDir, "src", "modules");
|
|
160
|
+
|
|
161
|
+
if (fs.existsSync(templateModulesDir)) {
|
|
162
|
+
if (!fs.existsSync(targetModulesDir)) {
|
|
163
|
+
fs.mkdirSync(targetModulesDir, { recursive: true });
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const moduleEntries = fs.readdirSync(templateModulesDir, {
|
|
167
|
+
withFileTypes: true,
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
for (const entry of moduleEntries) {
|
|
171
|
+
if (entry.isDirectory()) {
|
|
172
|
+
const moduleName = entry.name;
|
|
173
|
+
const moduleSrcPath = path.join(templateModulesDir, moduleName);
|
|
174
|
+
|
|
175
|
+
if (moduleName === "auth") {
|
|
176
|
+
if (!auth) {
|
|
177
|
+
console.log(`⏭️ Skipping auth module (not selected)`);
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
} else {
|
|
181
|
+
const moduleDestPath = path.join(targetModulesDir, moduleName);
|
|
182
|
+
console.log(`📦 Copying ${moduleName} module...`);
|
|
183
|
+
copyDirRecursive(moduleSrcPath, moduleDestPath);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Handle JWT authentication module if selected
|
|
190
|
+
if (auth) {
|
|
191
|
+
console.log("\n📦 Adding JWT authentication module...");
|
|
192
|
+
|
|
193
|
+
// The auth module is in src/modules/auth in the template
|
|
194
|
+
const authModulePath = path.join(templateDir, "src", "modules", "auth");
|
|
195
|
+
|
|
196
|
+
if (!fs.existsSync(authModulePath)) {
|
|
197
|
+
console.error(`❌ Auth module not found at ${authModulePath}`);
|
|
198
|
+
} else {
|
|
199
|
+
// 1. Merge auth module's package.json
|
|
200
|
+
const authPkgPath = path.join(authModulePath, "package.json");
|
|
201
|
+
|
|
202
|
+
if (fs.existsSync(authPkgPath)) {
|
|
203
|
+
try {
|
|
204
|
+
const authPkg = JSON.parse(fs.readFileSync(authPkgPath, "utf-8"));
|
|
205
|
+
console.log("✓ Found auth module package.json");
|
|
206
|
+
|
|
207
|
+
mergedPkg = mergePackageJson(mergedPkg, authPkg);
|
|
208
|
+
console.log("✓ Merged auth module dependencies");
|
|
209
|
+
console.log(
|
|
210
|
+
" Added dependencies:",
|
|
211
|
+
Object.keys(authPkg.dependencies || {}).join(", "),
|
|
212
|
+
);
|
|
213
|
+
if (authPkg.devDependencies) {
|
|
214
|
+
console.log(
|
|
215
|
+
" Added devDependencies:",
|
|
216
|
+
Object.keys(authPkg.devDependencies).join(", "),
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
} catch (error) {
|
|
220
|
+
console.error(
|
|
221
|
+
`❌ Failed to parse auth module package.json:`,
|
|
222
|
+
error.message,
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
} else {
|
|
226
|
+
console.error(
|
|
227
|
+
"❌ Auth module package.json not found at:",
|
|
228
|
+
authPkgPath,
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const targetAuthPath = path.join(targetModulesDir, "auth");
|
|
233
|
+
console.log(
|
|
234
|
+
`Copying auth module to: ${targetAuthPath} (excluding package.json)`,
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
copyDirRecursive(authModulePath, targetAuthPath, ["package.json"]);
|
|
238
|
+
console.log("✓ Copied auth module files (package.json was excluded)");
|
|
239
|
+
|
|
240
|
+
const copiedPkgPath = path.join(targetAuthPath, "package.json");
|
|
241
|
+
if (fs.existsSync(copiedPkgPath)) {
|
|
242
|
+
console.warn(
|
|
243
|
+
"⚠️ package.json was accidentally copied, removing it...",
|
|
244
|
+
);
|
|
245
|
+
fs.unlinkSync(copiedPkgPath);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
} else {
|
|
249
|
+
console.log("\n⏭️ Skipping JWT authentication module");
|
|
250
|
+
|
|
251
|
+
const targetAuthPath = path.join(targetDir, "src", "modules", "auth");
|
|
252
|
+
if (fs.existsSync(targetAuthPath)) {
|
|
253
|
+
console.log("Cleaning up auth directory (not selected)...");
|
|
254
|
+
fs.rmSync(targetAuthPath, { recursive: true, force: true });
|
|
255
|
+
}
|
|
256
|
+
}
|
|
89
257
|
|
|
90
258
|
mergedPkg.name = projectName;
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
259
|
+
|
|
260
|
+
const finalPkgPath = path.join(targetDir, "package.json");
|
|
261
|
+
fs.writeFileSync(finalPkgPath, JSON.stringify(mergedPkg, null, 2));
|
|
262
|
+
console.log(`\n📝 Created package.json at ${finalPkgPath}`);
|
|
263
|
+
|
|
264
|
+
console.log("\n📦 Final package.json dependencies:");
|
|
265
|
+
console.log(
|
|
266
|
+
" dependencies:",
|
|
267
|
+
Object.keys(mergedPkg.dependencies || {}).join(", "),
|
|
268
|
+
);
|
|
269
|
+
console.log(
|
|
270
|
+
" devDependencies:",
|
|
271
|
+
Object.keys(mergedPkg.devDependencies || {}).join(", "),
|
|
94
272
|
);
|
|
95
273
|
|
|
96
274
|
console.log(`\n📦 Installing dependencies using ${pkgManager}...`);
|
|
@@ -98,10 +276,13 @@ const {
|
|
|
98
276
|
|
|
99
277
|
console.log("\n✅ Charcole project created successfully!");
|
|
100
278
|
console.log(
|
|
101
|
-
`\n🚀 Next steps:\n cd ${projectName}\n ${
|
|
279
|
+
`\n🚀 Next steps:\n cd ${projectName}\n ${
|
|
280
|
+
pkgManager === "npm" ? "npm run dev" : `${pkgManager} run dev`
|
|
281
|
+
}`,
|
|
102
282
|
);
|
|
103
283
|
} catch (err) {
|
|
104
284
|
console.error("❌ Failed to create Charcole project:", err.message);
|
|
285
|
+
console.error(err.stack);
|
|
105
286
|
process.exit(1);
|
|
106
287
|
}
|
|
107
288
|
})();
|
package/bin/lib/pkgManager.js
CHANGED
|
@@ -5,7 +5,6 @@ const path = require("path");
|
|
|
5
5
|
/**
|
|
6
6
|
* Detect which package manager the user is using
|
|
7
7
|
* Priority: pnpm > yarn > npm
|
|
8
|
-
* @returns {string} - Package manager name ('pnpm', 'yarn', or 'npm')
|
|
9
8
|
*/
|
|
10
9
|
function detectPackageManager() {
|
|
11
10
|
const userAgent = process.env.npm_config_user_agent;
|
|
@@ -28,36 +27,20 @@ function detectPackageManager() {
|
|
|
28
27
|
}
|
|
29
28
|
}
|
|
30
29
|
|
|
31
|
-
|
|
32
|
-
try {
|
|
33
|
-
execSync(`${manager} --version`, { stdio: "ignore" });
|
|
34
|
-
return true;
|
|
35
|
-
} catch {
|
|
36
|
-
return false;
|
|
37
|
-
}
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
return installedManagers[0] || "npm";
|
|
30
|
+
return "npm";
|
|
41
31
|
}
|
|
42
32
|
|
|
43
33
|
/**
|
|
44
|
-
* Install dependencies
|
|
45
|
-
* @param {string} targetDir - Project directory where package.json exists
|
|
46
|
-
* @param {string} pkgManager - Package manager to use ('pnpm', 'yarn', or 'npm')
|
|
34
|
+
* Install dependencies
|
|
47
35
|
*/
|
|
48
36
|
function installDependencies(targetDir, pkgManager) {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
pkgManager === "yarn" ? "yarn install" : `${pkgManager} install`;
|
|
37
|
+
const installCmd =
|
|
38
|
+
pkgManager === "yarn" ? "yarn install" : `${pkgManager} install`;
|
|
52
39
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
} catch (error) {
|
|
58
|
-
console.error(`\n❌ Failed to install dependencies with ${pkgManager}`);
|
|
59
|
-
throw error;
|
|
60
|
-
}
|
|
40
|
+
execSync(installCmd, {
|
|
41
|
+
cwd: targetDir,
|
|
42
|
+
stdio: "inherit",
|
|
43
|
+
});
|
|
61
44
|
}
|
|
62
45
|
|
|
63
46
|
module.exports = {
|
|
@@ -2,11 +2,9 @@ const fs = require("fs");
|
|
|
2
2
|
const path = require("path");
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* Recursively copy directory contents
|
|
6
|
-
* @param {string} src - Source directory
|
|
7
|
-
* @param {string} dest - Destination directory
|
|
5
|
+
* Recursively copy directory contents, excluding specific files
|
|
8
6
|
*/
|
|
9
|
-
function copyDir(src, dest) {
|
|
7
|
+
function copyDir(src, dest, excludeFiles = []) {
|
|
10
8
|
if (!fs.existsSync(dest)) {
|
|
11
9
|
fs.mkdirSync(dest, { recursive: true });
|
|
12
10
|
}
|
|
@@ -17,54 +15,19 @@ function copyDir(src, dest) {
|
|
|
17
15
|
const srcPath = path.join(src, entry.name);
|
|
18
16
|
const destPath = path.join(dest, entry.name);
|
|
19
17
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
} else {
|
|
23
|
-
fs.copyFileSync(srcPath, destPath);
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Copy template files based on selected language and features
|
|
30
|
-
* @param {string} templateDir - Path to template directory (e.g., template/js or template/ts)
|
|
31
|
-
* @param {string} targetDir - Destination project directory
|
|
32
|
-
* @param {string[]} features - Array of selected features
|
|
33
|
-
*/
|
|
34
|
-
function copyTemplateModules(templateDir, targetDir, features) {
|
|
35
|
-
if (!fs.existsSync(targetDir)) {
|
|
36
|
-
fs.mkdirSync(targetDir, { recursive: true });
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const entries = fs.readdirSync(templateDir, { withFileTypes: true });
|
|
40
|
-
|
|
41
|
-
for (const entry of entries) {
|
|
42
|
-
const srcPath = path.join(templateDir, entry.name);
|
|
43
|
-
const destPath = path.join(targetDir, entry.name);
|
|
44
|
-
|
|
45
|
-
if (entry.name === "modules" || entry.name.includes("package.json")) {
|
|
18
|
+
// Skip excluded files
|
|
19
|
+
if (excludeFiles.includes(entry.name)) {
|
|
46
20
|
continue;
|
|
47
21
|
}
|
|
48
22
|
|
|
49
23
|
if (entry.isDirectory()) {
|
|
50
|
-
copyDir(srcPath, destPath);
|
|
24
|
+
copyDir(srcPath, destPath, excludeFiles);
|
|
51
25
|
} else {
|
|
52
26
|
fs.copyFileSync(srcPath, destPath);
|
|
53
27
|
}
|
|
54
28
|
}
|
|
55
|
-
|
|
56
|
-
const modulesDir = path.join(templateDir, "modules");
|
|
57
|
-
if (fs.existsSync(modulesDir)) {
|
|
58
|
-
features.forEach((feature) => {
|
|
59
|
-
const featurePath = path.join(modulesDir, feature);
|
|
60
|
-
if (fs.existsSync(featurePath)) {
|
|
61
|
-
copyDir(featurePath, targetDir);
|
|
62
|
-
}
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
29
|
}
|
|
66
30
|
|
|
67
31
|
module.exports = {
|
|
68
|
-
copyTemplateModules,
|
|
69
32
|
copyDir,
|
|
70
33
|
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-charcole",
|
|
3
|
-
"version": "2.0
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "2.1.0",
|
|
4
|
+
"description": "CLI to create production-ready Node.js Express APIs with TypeScript/JavaScript, JWT auth, and repository pattern",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Sheraz Manzoor",
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Charcole v2.1 Plan
|
|
2
|
+
|
|
3
|
+
## Features
|
|
4
|
+
|
|
5
|
+
- [DONE] Auth module (JWT)
|
|
6
|
+
- [DONE] CLI feature flag for auth
|
|
7
|
+
- [DONE] JS template support
|
|
8
|
+
- [DONE] TS template support
|
|
9
|
+
|
|
10
|
+
## Non-Goals
|
|
11
|
+
|
|
12
|
+
- No OAuth
|
|
13
|
+
- No DB choice
|
|
14
|
+
- No breaking changes
|
|
15
|
+
|
|
16
|
+
## Release
|
|
17
|
+
|
|
18
|
+
- [ ] Changelog
|
|
19
|
+
- [ ] Docs update
|
|
20
|
+
- [ ] npm publish
|
package/template/js/.env.example
CHANGED
|
@@ -1,28 +1,26 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "charcole",
|
|
3
|
-
"version": "2.0
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "Production-grade Node.js Express API",
|
|
5
5
|
"main": "src/server.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"engines": {
|
|
8
|
+
"node": ">=18.0.0"
|
|
9
|
+
},
|
|
6
10
|
"scripts": {
|
|
7
11
|
"start": "node src/server.js",
|
|
8
12
|
"dev": "nodemon src/server.js",
|
|
9
|
-
"lint": "echo
|
|
10
|
-
"test": "echo
|
|
13
|
+
"lint": "echo \"Add linting here\"",
|
|
14
|
+
"test": "echo \"Add tests here\""
|
|
11
15
|
},
|
|
12
|
-
"engines": {
|
|
13
|
-
"node": ">=18.0.0"
|
|
14
|
-
},
|
|
15
|
-
"keywords": [],
|
|
16
|
-
"author": "",
|
|
17
|
-
"license": "ISC",
|
|
18
|
-
"type": "module",
|
|
19
16
|
"dependencies": {
|
|
17
|
+
"express": "^4.18.2",
|
|
20
18
|
"cors": "^2.8.5",
|
|
21
19
|
"dotenv": "^16.3.1",
|
|
22
|
-
"
|
|
23
|
-
"zod": "^3.22.4"
|
|
20
|
+
"zod": "^3.25.76"
|
|
24
21
|
},
|
|
25
22
|
"devDependencies": {
|
|
26
23
|
"nodemon": "^3.0.2"
|
|
27
|
-
}
|
|
24
|
+
},
|
|
25
|
+
"license": "ISC"
|
|
28
26
|
}
|
package/template/js/src/app.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import express from "express";
|
|
2
|
+
import { userRepo } from "./repositories/user.repo.js";
|
|
2
3
|
import cors from "cors";
|
|
3
4
|
import { env } from "./config/env.js";
|
|
4
5
|
import { HTTP_STATUS, ERROR_MESSAGES } from "./config/constants.js";
|
|
@@ -10,7 +11,7 @@ import {
|
|
|
10
11
|
} from "./middlewares/errorHandler.js";
|
|
11
12
|
import { sendSuccess } from "./utils/response.js";
|
|
12
13
|
import { logger } from "./utils/logger.js";
|
|
13
|
-
import routes from "./routes.js";
|
|
14
|
+
import routes from "./routes/index.js";
|
|
14
15
|
|
|
15
16
|
export const app = express();
|
|
16
17
|
|
|
@@ -73,3 +74,5 @@ app.use((req, res, next) => {
|
|
|
73
74
|
app.use(errorHandler);
|
|
74
75
|
|
|
75
76
|
logger.info("Express app configured successfully");
|
|
77
|
+
|
|
78
|
+
app.locals.userRepo = userRepo;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { AuthService } from "./auth.service.js";
|
|
2
|
+
|
|
3
|
+
export const AuthController = {
|
|
4
|
+
async register(req, res) {
|
|
5
|
+
try {
|
|
6
|
+
const user = await AuthService.register(
|
|
7
|
+
req.body,
|
|
8
|
+
req.app.locals.userRepo,
|
|
9
|
+
);
|
|
10
|
+
|
|
11
|
+
res.status(201).json({
|
|
12
|
+
message: "User registered successfully",
|
|
13
|
+
user,
|
|
14
|
+
});
|
|
15
|
+
} catch (err) {
|
|
16
|
+
res.status(400).json({ message: err.message });
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
|
|
20
|
+
async login(req, res) {
|
|
21
|
+
try {
|
|
22
|
+
const result = await AuthService.login(req.body, req.app.locals.userRepo);
|
|
23
|
+
|
|
24
|
+
res.json(result);
|
|
25
|
+
} catch (err) {
|
|
26
|
+
res.status(401).json({ message: err.message });
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import jwt from "jsonwebtoken";
|
|
2
|
+
|
|
3
|
+
export const requireAuth = (req, res, next) => {
|
|
4
|
+
const authHeader = req.headers.authorization;
|
|
5
|
+
|
|
6
|
+
if (!authHeader?.startsWith("Bearer ")) {
|
|
7
|
+
return res.status(401).json({ message: "Unauthorized" });
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const token = authHeader.split(" ")[1];
|
|
11
|
+
|
|
12
|
+
try {
|
|
13
|
+
const payload = jwt.verify(token, process.env.JWT_SECRET);
|
|
14
|
+
req.user = payload;
|
|
15
|
+
next();
|
|
16
|
+
} catch {
|
|
17
|
+
res.status(401).json({ message: "Invalid token" });
|
|
18
|
+
}
|
|
19
|
+
};
|