create-charcole 2.0.4 → 2.2.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 (100) hide show
  1. package/CHANGELOG.md +290 -14
  2. package/README.md +258 -312
  3. package/bin/index.js +392 -55
  4. package/bin/lib/pkgManager.js +8 -25
  5. package/bin/lib/templateHandler.js +5 -42
  6. package/create-charcole-2.1.0.tgz +0 -0
  7. package/package.json +2 -2
  8. package/packages/swagger/BACKWARD_COMPATIBILITY.md +145 -0
  9. package/packages/swagger/CHANGELOG.md +404 -0
  10. package/packages/swagger/README.md +578 -0
  11. package/packages/swagger/charcole-swagger-1.0.0.tgz +0 -0
  12. package/packages/swagger/package-lock.json +1715 -0
  13. package/packages/swagger/package.json +44 -0
  14. package/packages/swagger/src/helpers.js +427 -0
  15. package/packages/swagger/src/index.d.ts +126 -0
  16. package/packages/swagger/src/index.js +12 -0
  17. package/packages/swagger/src/setup.js +100 -0
  18. package/template/js/.env.example +8 -0
  19. package/template/js/README.md +128 -5
  20. package/template/js/basePackage.json +11 -13
  21. package/template/js/src/app.js +8 -2
  22. package/template/js/src/config/swagger.config.js +15 -0
  23. package/template/js/src/lib/swagger/SWAGGER_GUIDE.md +561 -0
  24. package/template/js/src/modules/auth/auth.constants.js +3 -0
  25. package/template/js/src/modules/auth/auth.controller.js +29 -0
  26. package/template/js/src/modules/auth/auth.middlewares.js +19 -0
  27. package/template/js/src/modules/auth/auth.routes.js +131 -0
  28. package/template/js/src/modules/auth/auth.schemas.js +60 -0
  29. package/template/js/src/modules/auth/auth.service.js +67 -0
  30. package/template/js/src/modules/auth/package.json +6 -0
  31. package/template/js/src/modules/health/controller.js +104 -3
  32. package/template/js/src/modules/swagger/charcole-swagger-1.0.0.tgz +0 -0
  33. package/template/js/src/modules/swagger/package.json +5 -0
  34. package/template/js/src/repositories/user.repo.js +19 -0
  35. package/template/js/src/routes/index.js +25 -0
  36. package/template/js/src/routes/protected.js +57 -0
  37. package/template/ts/.env.example +8 -0
  38. package/template/ts/README.md +128 -5
  39. package/template/ts/basePackage.json +19 -15
  40. package/template/ts/build.js +46 -0
  41. package/template/ts/src/app.ts +12 -7
  42. package/template/ts/src/config/swagger.config.ts +30 -0
  43. package/template/ts/src/lib/swagger/SWAGGER_GUIDE.md +561 -0
  44. package/template/ts/src/middlewares/errorHandler.ts +15 -23
  45. package/template/ts/src/middlewares/requestLogger.ts +1 -1
  46. package/template/ts/src/middlewares/validateRequest.ts +1 -1
  47. package/template/ts/src/modules/auth/auth.constants.ts +6 -0
  48. package/template/ts/src/modules/auth/auth.controller.ts +32 -0
  49. package/template/ts/src/modules/auth/auth.middlewares.ts +46 -0
  50. package/template/ts/src/modules/auth/auth.routes.ts +52 -0
  51. package/template/ts/src/modules/auth/auth.schemas.ts +73 -0
  52. package/template/ts/src/modules/auth/auth.service.ts +106 -0
  53. package/template/ts/src/modules/auth/package.json +10 -0
  54. package/template/ts/src/modules/health/controller.ts +61 -45
  55. package/template/ts/src/modules/swagger/charcole-swagger-1.0.0.tgz +0 -0
  56. package/template/ts/src/modules/swagger/package.json +5 -0
  57. package/template/ts/src/repositories/user.repo.ts +33 -0
  58. package/template/ts/src/routes/index.ts +24 -0
  59. package/template/ts/src/routes/protected.ts +46 -0
  60. package/template/ts/src/server.ts +3 -4
  61. package/template/ts/src/utils/logger.ts +1 -1
  62. package/template/ts/tsconfig.json +14 -7
  63. package/tmpclaude-1049-cwd +1 -0
  64. package/tmpclaude-3e37-cwd +1 -0
  65. package/tmpclaude-4d73-cwd +1 -0
  66. package/tmpclaude-8a8e-cwd +1 -0
  67. package/template/js/ARCHITECTURE_DIAGRAMS.md +0 -283
  68. package/template/js/CHECKLIST.md +0 -279
  69. package/template/js/COMPLETE.md +0 -405
  70. package/template/js/ERROR_HANDLING.md +0 -393
  71. package/template/js/IMPLEMENTATION.md +0 -368
  72. package/template/js/IMPLEMENTATION_COMPLETE.md +0 -363
  73. package/template/js/INDEX.md +0 -290
  74. package/template/js/QUICK_REFERENCE.md +0 -270
  75. package/template/js/package.json +0 -28
  76. package/template/js/src/routes.js +0 -17
  77. package/template/js/test-api.js +0 -100
  78. package/template/ts/ARCHITECTURE_DIAGRAMS.md +0 -283
  79. package/template/ts/CHECKLIST.md +0 -279
  80. package/template/ts/COMPLETE.md +0 -405
  81. package/template/ts/ERROR_HANDLING.md +0 -393
  82. package/template/ts/IMPLEMENTATION.md +0 -368
  83. package/template/ts/IMPLEMENTATION_COMPLETE.md +0 -363
  84. package/template/ts/INDEX.md +0 -290
  85. package/template/ts/QUICK_REFERENCE.md +0 -270
  86. package/template/ts/package.json +0 -32
  87. package/template/ts/src/app.js +0 -75
  88. package/template/ts/src/config/constants.js +0 -20
  89. package/template/ts/src/config/env.js +0 -26
  90. package/template/ts/src/middlewares/errorHandler.js +0 -180
  91. package/template/ts/src/middlewares/requestLogger.js +0 -33
  92. package/template/ts/src/middlewares/validateRequest.js +0 -42
  93. package/template/ts/src/modules/health/controller.js +0 -50
  94. package/template/ts/src/routes.js +0 -17
  95. package/template/ts/src/routes.ts +0 -16
  96. package/template/ts/src/server.js +0 -38
  97. package/template/ts/src/utils/AppError.js +0 -182
  98. package/template/ts/src/utils/logger.js +0 -73
  99. package/template/ts/src/utils/response.js +0 -51
  100. package/template/ts/test-api.js +0 -100
package/bin/index.js CHANGED
@@ -1,26 +1,116 @@
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
- const { copyTemplateModules } = require("./lib/templateHandler");
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 = [], excludeDirs = []) {
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
+ // Skip excluded files
59
+ if (excludeFiles.includes(entry.name)) {
60
+ console.log(`Skipping excluded file: ${entry.name}`);
61
+ continue;
62
+ }
63
+
64
+ // Skip .tgz files (tarball packages)
65
+ if (entry.name.endsWith(".tgz")) {
66
+ console.log(`Skipping tarball: ${entry.name}`);
67
+ continue;
68
+ }
69
+
70
+ if (entry.isDirectory()) {
71
+ // Skip excluded directories
72
+ if (excludeDirs.includes(entry.name)) {
73
+ console.log(`Skipping excluded directory: ${entry.name}`);
74
+ continue;
75
+ }
76
+ copyDirRecursive(srcPath, destPath, excludeFiles, excludeDirs);
77
+ } else {
78
+ fs.copyFileSync(srcPath, destPath);
79
+ }
80
+ }
81
+ }
82
+
13
83
  (async function main() {
14
84
  try {
15
- console.log("šŸ”„ Welcome to Charcole v2 CLI");
85
+ console.log("šŸ”„ Welcome to Charcole v2.2 CLI");
16
86
 
17
- const responses = await prompts([
18
- {
87
+ // Check if project name is provided as command line argument
88
+ const args = process.argv.slice(2);
89
+ let projectNameFromArgs = null;
90
+
91
+ if (args.length > 0) {
92
+ // The first argument that doesn't start with '-' is likely the project name
93
+ for (const arg of args) {
94
+ if (!arg.startsWith("-")) {
95
+ projectNameFromArgs = arg;
96
+ break;
97
+ }
98
+ }
99
+ }
100
+
101
+ const questions = [];
102
+
103
+ // Only ask for project name if not provided in command line
104
+ if (!projectNameFromArgs) {
105
+ questions.push({
19
106
  type: "text",
20
107
  name: "projectName",
21
108
  message: "Project name:",
22
109
  validate: (name) => (name ? true : "Project name is required"),
23
- },
110
+ });
111
+ }
112
+
113
+ questions.push(
24
114
  {
25
115
  type: "select",
26
116
  name: "language",
@@ -29,25 +119,32 @@ const {
29
119
  { title: "TypeScript", value: "ts" },
30
120
  { title: "JavaScript", value: "js" },
31
121
  ],
32
- initial: 0,
33
122
  },
34
- // TODO: Uncomment when features are implemented
35
- // {
36
- // type: "multiselect",
37
- // name: "features",
38
- // message: "Select features to include:",
39
- // choices: [
40
- // { title: "JWT Authentication", value: "auth" },
41
- // { title: "Swagger Docs", value: "swagger", selected: true },
42
- // { title: "Docker Support", value: "docker" },
43
- // { title: "ESLint + Prettier", value: "lint", selected: true },
44
- // ],
45
- // min: 0,
46
- // },
47
- ]);
48
-
49
- const { projectName, language } = responses;
50
- const features = []; // Empty for now, will be responses.features later
123
+ {
124
+ type: "confirm",
125
+ name: "auth",
126
+ message: "Include JWT authentication module?",
127
+ initial: true,
128
+ },
129
+ {
130
+ type: "confirm",
131
+ name: "swagger",
132
+ message: "Include auto-generated Swagger documentation?",
133
+ initial: true,
134
+ },
135
+ );
136
+
137
+ const responses = await prompts(questions);
138
+
139
+ // Use command line project name if provided, otherwise use prompt response
140
+ const projectName = projectNameFromArgs || responses.projectName;
141
+ const { language, auth, swagger } = responses;
142
+
143
+ if (!projectName || projectName.trim() === "") {
144
+ console.error("āŒ Project name is required");
145
+ process.exit(1);
146
+ }
147
+
51
148
  const targetDir = path.join(process.cwd(), projectName);
52
149
 
53
150
  if (fs.existsSync(targetDir)) {
@@ -56,52 +153,292 @@ const {
56
153
  }
57
154
 
58
155
  const pkgManager = detectPackageManager();
156
+ const templateDir = path.join(__dirname, "..", "template", language);
59
157
 
60
- console.log(`\nšŸ“ Creating project in ${language.toUpperCase()}...`);
158
+ console.log(
159
+ `\nšŸ“ Creating project "${projectName}" in ${language.toUpperCase()}...`,
160
+ );
61
161
 
62
- // Template directory is template/js or template/ts
63
- const templateDir = path.join(__dirname, "..", "template", language);
162
+ fs.mkdirSync(targetDir, { recursive: true });
163
+
164
+ const basePkgPath = path.join(templateDir, "basePackage.json");
165
+ if (!fs.existsSync(basePkgPath)) {
166
+ throw new Error(`basePackage.json not found at ${basePkgPath}`);
167
+ }
64
168
 
65
- copyTemplateModules(templateDir, targetDir, features);
169
+ let mergedPkg = JSON.parse(fs.readFileSync(basePkgPath, "utf-8"));
170
+ console.log("āœ“ Loaded base package configuration");
66
171
 
67
- // basePackage.json is directly in template/js or template/ts
68
- const basePkg = JSON.parse(
69
- fs.readFileSync(path.join(templateDir, "basePackage.json")),
70
- );
71
- let mergedPkg = { ...basePkg };
72
-
73
- // TODO: Uncomment when features are implemented
74
- // features.forEach((f) => {
75
- // const fragPath = path.join(templateDir, "modules", f, "package.json");
76
- // if (fs.existsSync(fragPath)) {
77
- // const frag = JSON.parse(fs.readFileSync(fragPath));
78
- // mergedPkg.dependencies = {
79
- // ...mergedPkg.dependencies,
80
- // ...frag.dependencies,
81
- // };
82
- // mergedPkg.devDependencies = {
83
- // ...mergedPkg.devDependencies,
84
- // ...frag.devDependencies,
85
- // };
86
- // mergedPkg.scripts = { ...mergedPkg.scripts, ...frag.scripts };
87
- // }
88
- // });
172
+ console.log("\nšŸ“ Copying base template structure...");
173
+
174
+ // Exclude basePackage.json and swagger module directory from initial copy
175
+ // We'll handle modules separately based on user selection
176
+ const srcModulesDir = path.join(templateDir, "src", "modules");
177
+
178
+ // Copy everything except basePackage.json and the modules directory
179
+ copyDirRecursive(templateDir, targetDir, ["basePackage.json"], ["modules"]);
180
+
181
+ // Now handle modules directory manually
182
+ const templateModulesDir = srcModulesDir;
183
+ const targetModulesDir = path.join(targetDir, "src", "modules");
184
+
185
+ if (fs.existsSync(templateModulesDir)) {
186
+ if (!fs.existsSync(targetModulesDir)) {
187
+ fs.mkdirSync(targetModulesDir, { recursive: true });
188
+ }
189
+
190
+ const moduleEntries = fs.readdirSync(templateModulesDir, {
191
+ withFileTypes: true,
192
+ });
193
+
194
+ for (const entry of moduleEntries) {
195
+ if (entry.isDirectory()) {
196
+ const moduleName = entry.name;
197
+ const moduleSrcPath = path.join(templateModulesDir, moduleName);
198
+
199
+ if (moduleName === "auth") {
200
+ if (!auth) {
201
+ console.log(`ā­ļø Skipping auth module (not selected)`);
202
+ continue;
203
+ }
204
+ } else if (moduleName === "swagger") {
205
+ if (!swagger) {
206
+ console.log(`ā­ļø Skipping swagger module (not selected)`);
207
+ continue;
208
+ } else {
209
+ // Do not copy swagger module folder, just merge package.json below
210
+ console.log(
211
+ `ā­ļø Not copying swagger module folder (merging dependencies only)`,
212
+ );
213
+ continue;
214
+ }
215
+ } else {
216
+ const moduleDestPath = path.join(targetModulesDir, moduleName);
217
+ console.log(`šŸ“¦ Copying ${moduleName} module...`);
218
+ // Exclude package.json files from module folders
219
+ copyDirRecursive(moduleSrcPath, moduleDestPath, ["package.json"]);
220
+ }
221
+ }
222
+ }
223
+ }
224
+ // Handle Swagger module if selected
225
+ if (swagger) {
226
+ console.log("\nšŸ“¦ Adding Swagger module dependencies...");
227
+ const swaggerModuleDir = path.join(
228
+ templateDir,
229
+ "src",
230
+ "modules",
231
+ "swagger",
232
+ );
233
+ const swaggerPkgPath = path.join(swaggerModuleDir, "package.json");
234
+ const swaggerTgzPath = path.join(
235
+ swaggerModuleDir,
236
+ "charcole-swagger-1.0.0.tgz",
237
+ );
238
+
239
+ // Copy tarball temporarily for npm install (will be cleaned up after)
240
+ if (fs.existsSync(swaggerTgzPath)) {
241
+ fs.copyFileSync(
242
+ swaggerTgzPath,
243
+ path.join(targetDir, "charcole-swagger-1.0.0.tgz"),
244
+ );
245
+ console.log("āœ“ Copied Swagger tarball temporarily for installation");
246
+ } else {
247
+ console.error("āŒ Swagger tarball not found at:", swaggerTgzPath);
248
+ }
249
+
250
+ if (fs.existsSync(swaggerPkgPath)) {
251
+ try {
252
+ const swaggerPkg = JSON.parse(
253
+ fs.readFileSync(swaggerPkgPath, "utf-8"),
254
+ );
255
+ mergedPkg = mergePackageJson(mergedPkg, swaggerPkg);
256
+ console.log("āœ“ Merged Swagger module dependencies");
257
+ console.log(
258
+ " Added dependencies:",
259
+ Object.keys(swaggerPkg.dependencies || {}).join(", "),
260
+ );
261
+ if (swaggerPkg.devDependencies) {
262
+ console.log(
263
+ " Added devDependencies:",
264
+ Object.keys(swaggerPkg.devDependencies).join(", "),
265
+ );
266
+ }
267
+ } catch (error) {
268
+ console.error(
269
+ `āŒ Failed to parse Swagger module package.json:`,
270
+ error.message,
271
+ );
272
+ }
273
+ } else {
274
+ console.error(
275
+ "āŒ Swagger module package.json not found at:",
276
+ swaggerPkgPath,
277
+ );
278
+ }
279
+ }
280
+
281
+ // Handle JWT authentication module if selected
282
+ if (auth) {
283
+ console.log("\nšŸ“¦ Adding JWT authentication module...");
284
+
285
+ // The auth module is in src/modules/auth in the template
286
+ const authModulePath = path.join(templateDir, "src", "modules", "auth");
287
+
288
+ if (!fs.existsSync(authModulePath)) {
289
+ console.error(`āŒ Auth module not found at ${authModulePath}`);
290
+ } else {
291
+ // 1. Merge auth module's package.json
292
+ const authPkgPath = path.join(authModulePath, "package.json");
293
+
294
+ if (fs.existsSync(authPkgPath)) {
295
+ try {
296
+ const authPkg = JSON.parse(fs.readFileSync(authPkgPath, "utf-8"));
297
+ console.log("āœ“ Found auth module package.json");
298
+
299
+ mergedPkg = mergePackageJson(mergedPkg, authPkg);
300
+ console.log("āœ“ Merged auth module dependencies");
301
+ console.log(
302
+ " Added dependencies:",
303
+ Object.keys(authPkg.dependencies || {}).join(", "),
304
+ );
305
+ if (authPkg.devDependencies) {
306
+ console.log(
307
+ " Added devDependencies:",
308
+ Object.keys(authPkg.devDependencies).join(", "),
309
+ );
310
+ }
311
+ } catch (error) {
312
+ console.error(
313
+ `āŒ Failed to parse auth module package.json:`,
314
+ error.message,
315
+ );
316
+ }
317
+ } else {
318
+ console.error(
319
+ "āŒ Auth module package.json not found at:",
320
+ authPkgPath,
321
+ );
322
+ }
323
+
324
+ const targetAuthPath = path.join(targetModulesDir, "auth");
325
+ console.log(
326
+ `Copying auth module to: ${targetAuthPath} (excluding package.json)`,
327
+ );
328
+
329
+ copyDirRecursive(authModulePath, targetAuthPath, ["package.json"], []);
330
+ console.log("āœ“ Copied auth module files (package.json was excluded)");
331
+
332
+ const copiedPkgPath = path.join(targetAuthPath, "package.json");
333
+ if (fs.existsSync(copiedPkgPath)) {
334
+ console.warn(
335
+ "āš ļø package.json was accidentally copied, removing it...",
336
+ );
337
+ fs.unlinkSync(copiedPkgPath);
338
+ }
339
+ }
340
+ } else {
341
+ console.log("\nā­ļø Skipping JWT authentication module");
342
+
343
+ const targetAuthPath = path.join(targetDir, "src", "modules", "auth");
344
+ if (fs.existsSync(targetAuthPath)) {
345
+ console.log("Cleaning up auth directory (not selected)...");
346
+ fs.rmSync(targetAuthPath, { recursive: true, force: true });
347
+ }
348
+ }
349
+
350
+ // Remove Swagger imports and setup from app file if not selected
351
+ if (!swagger) {
352
+ console.log("\n🧹 Removing Swagger references from app file...");
353
+ const appFileName = language === "ts" ? "app.ts" : "app.js";
354
+ const appFilePath = path.join(targetDir, "src", appFileName);
355
+
356
+ if (fs.existsSync(appFilePath)) {
357
+ let appContent = fs.readFileSync(appFilePath, "utf-8");
358
+
359
+ // Remove swagger-related imports
360
+ const swaggerConfigImport =
361
+ language === "ts"
362
+ ? 'import swaggerOptions from "./config/swagger.config";'
363
+ : 'import swaggerOptions from "./config/swagger.config.js";';
364
+ const setupSwaggerImport =
365
+ 'import { setupSwagger } from "@charcoles/swagger";';
366
+
367
+ appContent = appContent
368
+ .split("\n")
369
+ .filter((line) => {
370
+ const trimmedLine = line.trim();
371
+ return (
372
+ !trimmedLine.includes(swaggerConfigImport.trim()) &&
373
+ !trimmedLine.includes(setupSwaggerImport.trim()) &&
374
+ !trimmedLine.includes("setupSwagger(app, swaggerOptions);")
375
+ );
376
+ })
377
+ .join("\n");
378
+
379
+ fs.writeFileSync(appFilePath, appContent, "utf-8");
380
+ console.log(`āœ“ Removed Swagger references from ${appFileName}`);
381
+ }
382
+
383
+ // Remove swagger config file
384
+ const swaggerConfigFile =
385
+ language === "ts" ? "swagger.config.ts" : "swagger.config.js";
386
+ const swaggerConfigPath = path.join(
387
+ targetDir,
388
+ "src",
389
+ "config",
390
+ swaggerConfigFile,
391
+ );
392
+ if (fs.existsSync(swaggerConfigPath)) {
393
+ fs.unlinkSync(swaggerConfigPath);
394
+ console.log(`āœ“ Removed ${swaggerConfigFile}`);
395
+ }
396
+
397
+ // Remove lib/swagger directory
398
+ const swaggerLibPath = path.join(targetDir, "src", "lib", "swagger");
399
+ if (fs.existsSync(swaggerLibPath)) {
400
+ fs.rmSync(swaggerLibPath, { recursive: true, force: true });
401
+ console.log("āœ“ Removed lib/swagger directory");
402
+ }
403
+ }
89
404
 
90
405
  mergedPkg.name = projectName;
91
- fs.writeFileSync(
92
- path.join(targetDir, "package.json"),
93
- JSON.stringify(mergedPkg, null, 2),
406
+
407
+ const finalPkgPath = path.join(targetDir, "package.json");
408
+ fs.writeFileSync(finalPkgPath, JSON.stringify(mergedPkg, null, 2));
409
+ console.log(`\nšŸ“ Created package.json at ${finalPkgPath}`);
410
+
411
+ console.log("\nšŸ“¦ Final package.json dependencies:");
412
+ console.log(
413
+ " dependencies:",
414
+ Object.keys(mergedPkg.dependencies || {}).join(", "),
415
+ );
416
+ console.log(
417
+ " devDependencies:",
418
+ Object.keys(mergedPkg.devDependencies || {}).join(", "),
94
419
  );
95
420
 
96
421
  console.log(`\nšŸ“¦ Installing dependencies using ${pkgManager}...`);
97
422
  installDependencies(targetDir, pkgManager);
98
423
 
424
+ // Clean up the swagger tarball after installation
425
+ if (swagger) {
426
+ const tgzPath = path.join(targetDir, "charcole-swagger-1.0.0.tgz");
427
+ if (fs.existsSync(tgzPath)) {
428
+ fs.unlinkSync(tgzPath);
429
+ console.log("āœ“ Cleaned up temporary Swagger tarball");
430
+ }
431
+ }
432
+
99
433
  console.log("\nāœ… Charcole project created successfully!");
100
434
  console.log(
101
- `\nšŸš€ Next steps:\n cd ${projectName}\n ${pkgManager === "npm" ? "npm run dev" : `${pkgManager} dev`}`,
435
+ `\nšŸš€ Next steps:\n cd ${projectName}\n ${
436
+ pkgManager === "npm" ? "npm run dev" : `${pkgManager} run dev`
437
+ }`,
102
438
  );
103
439
  } catch (err) {
104
440
  console.error("āŒ Failed to create Charcole project:", err.message);
441
+ console.error(err.stack);
105
442
  process.exit(1);
106
443
  }
107
444
  })();
@@ -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
- const installedManagers = ["pnpm", "yarn", "npm"].filter((manager) => {
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 using the detected package manager
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
- try {
50
- const installCmd =
51
- pkgManager === "yarn" ? "yarn install" : `${pkgManager} install`;
37
+ const installCmd =
38
+ pkgManager === "yarn" ? "yarn install" : `${pkgManager} install`;
52
39
 
53
- execSync(installCmd, {
54
- cwd: targetDir,
55
- stdio: "inherit",
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
- if (entry.isDirectory()) {
21
- copyDir(srcPath, destPath);
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
  };
Binary file
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "create-charcole",
3
- "version": "2.0.4",
4
- "description": "Production-ready Express backend starter with engineering guardrails.",
3
+ "version": "2.2.0",
4
+ "description": "CLI to create production-ready Node.js Express APIs with TypeScript/JavaScript, JWT auth, auto-generated Swagger docs, and repository pattern",
5
5
  "license": "MIT",
6
6
  "author": {
7
7
  "name": "Sheraz Manzoor",