create-charcole 1.1.0 → 2.0.1

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 (73) hide show
  1. package/.github/workflows/release.yml +26 -0
  2. package/CHANGELOG.md +25 -0
  3. package/README.md +11 -1
  4. package/bin/index.js +90 -71
  5. package/bin/lib/pkgManager.js +66 -0
  6. package/bin/lib/templateHandler.js +70 -0
  7. package/package.json +4 -1
  8. package/template/js/basePackage.json +28 -0
  9. package/template/{package-lock.json → js/package-lock.json} +1253 -1253
  10. package/template/{package.json → js/package.json} +28 -28
  11. package/template/ts/.env.example +8 -0
  12. package/template/ts/ARCHITECTURE_DIAGRAMS.md +283 -0
  13. package/template/ts/CHECKLIST.md +279 -0
  14. package/template/ts/COMPLETE.md +405 -0
  15. package/template/ts/ERROR_HANDLING.md +393 -0
  16. package/template/ts/IMPLEMENTATION.md +368 -0
  17. package/template/ts/IMPLEMENTATION_COMPLETE.md +363 -0
  18. package/template/ts/INDEX.md +290 -0
  19. package/template/ts/QUICK_REFERENCE.md +270 -0
  20. package/template/ts/README.md +855 -0
  21. package/template/ts/basePackage.json +36 -0
  22. package/template/ts/package-lock.json +2428 -0
  23. package/template/ts/package.json +32 -0
  24. package/template/ts/src/app.js +75 -0
  25. package/template/ts/src/app.ts +66 -0
  26. package/template/ts/src/config/constants.js +20 -0
  27. package/template/ts/src/config/constants.ts +27 -0
  28. package/template/ts/src/config/env.js +26 -0
  29. package/template/ts/src/config/env.ts +40 -0
  30. package/template/ts/src/middlewares/errorHandler.js +180 -0
  31. package/template/ts/src/middlewares/errorHandler.ts +209 -0
  32. package/template/ts/src/middlewares/requestLogger.js +33 -0
  33. package/template/ts/src/middlewares/requestLogger.ts +38 -0
  34. package/template/ts/src/middlewares/validateRequest.js +42 -0
  35. package/template/ts/src/middlewares/validateRequest.ts +46 -0
  36. package/template/ts/src/modules/health/controller.js +50 -0
  37. package/template/ts/src/modules/health/controller.ts +64 -0
  38. package/template/ts/src/routes.js +17 -0
  39. package/template/ts/src/routes.ts +16 -0
  40. package/template/ts/src/server.js +38 -0
  41. package/template/ts/src/server.ts +42 -0
  42. package/template/ts/src/types/express.d.ts +9 -0
  43. package/template/ts/src/utils/AppError.js +182 -0
  44. package/template/ts/src/utils/AppError.ts +220 -0
  45. package/template/ts/src/utils/logger.js +73 -0
  46. package/template/ts/src/utils/logger.ts +55 -0
  47. package/template/ts/src/utils/response.js +51 -0
  48. package/template/ts/src/utils/response.ts +100 -0
  49. package/template/ts/test-api.js +100 -0
  50. package/template/ts/tsconfig.json +19 -0
  51. /package/template/{.env.example → js/.env.example} +0 -0
  52. /package/template/{ARCHITECTURE_DIAGRAMS.md → js/ARCHITECTURE_DIAGRAMS.md} +0 -0
  53. /package/template/{CHECKLIST.md → js/CHECKLIST.md} +0 -0
  54. /package/template/{COMPLETE.md → js/COMPLETE.md} +0 -0
  55. /package/template/{ERROR_HANDLING.md → js/ERROR_HANDLING.md} +0 -0
  56. /package/template/{IMPLEMENTATION.md → js/IMPLEMENTATION.md} +0 -0
  57. /package/template/{IMPLEMENTATION_COMPLETE.md → js/IMPLEMENTATION_COMPLETE.md} +0 -0
  58. /package/template/{INDEX.md → js/INDEX.md} +0 -0
  59. /package/template/{QUICK_REFERENCE.md → js/QUICK_REFERENCE.md} +0 -0
  60. /package/template/{README.md → js/README.md} +0 -0
  61. /package/template/{src → js/src}/app.js +0 -0
  62. /package/template/{src → js/src}/config/constants.js +0 -0
  63. /package/template/{src → js/src}/config/env.js +0 -0
  64. /package/template/{src → js/src}/middlewares/errorHandler.js +0 -0
  65. /package/template/{src → js/src}/middlewares/requestLogger.js +0 -0
  66. /package/template/{src → js/src}/middlewares/validateRequest.js +0 -0
  67. /package/template/{src → js/src}/modules/health/controller.js +0 -0
  68. /package/template/{src → js/src}/routes.js +0 -0
  69. /package/template/{src → js/src}/server.js +0 -0
  70. /package/template/{src → js/src}/utils/AppError.js +0 -0
  71. /package/template/{src → js/src}/utils/logger.js +0 -0
  72. /package/template/{src → js/src}/utils/response.js +0 -0
  73. /package/template/{test-api.js → js/test-api.js} +0 -0
@@ -0,0 +1,26 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*"
7
+
8
+ permissions:
9
+ contents: read
10
+ id-token: write
11
+
12
+ jobs:
13
+ publish:
14
+ runs-on: ubuntu-latest
15
+
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+
19
+ - uses: actions/setup-node@v4
20
+ with:
21
+ node-version: 20
22
+ registry-url: https://registry.npmjs.org
23
+
24
+ - run: npm ci
25
+
26
+ - run: npm publish --access public
package/CHANGELOG.md ADDED
@@ -0,0 +1,25 @@
1
+ # Changelog
2
+
3
+ ## [2.0.0] – 2026-01-24
4
+
5
+ ### Added
6
+
7
+ - Full TypeScript project template support
8
+ - Language selection (JavaScript / TypeScript) at CLI runtime
9
+ - Improved project scaffolding structure
10
+ - Better developer experience for production setups
11
+
12
+ ### Changed
13
+
14
+ - Internal template resolution logic
15
+ - CLI output and onboarding messages
16
+
17
+ ### Fixed
18
+
19
+ - Incorrect template path resolution in previous versions
20
+ - Local CLI linking issues during development
21
+
22
+ ### Notes
23
+
24
+ - This release focuses on stability and language parity.
25
+ - Authentication and database modules are intentionally not included yet.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Charcole API
2
2
 
3
- > **Production-grade Node.js Express API with enterprise-level error handling, Zod validation, and structured logging.**
3
+ > **Charcole is a production-grade Node.js backend starter CLI that scaffolds enterprise-ready Express APIs with first-class TypeScript or JavaScript support, centralized error handling, Zod validation, and structured logging out of the box.**
4
4
 
5
5
  [![Node.js](https://img.shields.io/badge/Node.js-18+-green.svg)](https://nodejs.org/)
6
6
  [![Express.js](https://img.shields.io/badge/Express-4.18+-blue.svg)](https://expressjs.com/)
@@ -11,6 +11,7 @@
11
11
 
12
12
  A **production-ready Node.js Express backend** with:
13
13
 
14
+ - ✅ **TypeScript or JavaScript** templates (v2 feature)
14
15
  - ✅ **Centralized Error Handling** - Every error flows through one place
15
16
  - ✅ **Error Classification** - Operational vs Programmer errors distinguished
16
17
  - ✅ **Zod Validation** - Type-safe schema validation with automatic error formatting
@@ -396,3 +397,12 @@ ISC
396
397
  **Made for teams that care about code quality and production reliability.** 🚀
397
398
 
398
399
  Need help? See the [Getting Started Guide](template/README.md) or [Full Documentation](ERROR_HANDLING.md).
400
+
401
+ ## 🆕 What’s New in v2.0
402
+
403
+ - 🚀 **First-class TypeScript & JavaScript support**
404
+ - 🧠 CLI-driven language selection
405
+ - 🧩 Modular template architecture (future feature expansion)
406
+ - 🧼 Cleaner project generation flow
407
+ - 📦 Improved dependency handling
408
+ - 🏗️ Foundation for built-in auth, Swagger, Docker modules
package/bin/index.js CHANGED
@@ -1,88 +1,107 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- const fs = require("fs");
4
3
  const path = require("path");
4
+ const fs = require("fs");
5
5
  const { execSync } = require("child_process");
6
+ const prompts = require("prompts");
7
+ const { copyTemplateModules } = require("./lib/templateHandler");
8
+ const {
9
+ detectPackageManager,
10
+ installDependencies,
11
+ } = require("./lib/pkgManager");
6
12
 
7
- const projectName = process.argv[2];
8
-
9
- if (!projectName) {
10
- console.error("❌ Please provide a project name.");
11
- console.error("Usage: npx create-charcole my-backend");
12
- process.exit(1);
13
- }
13
+ (async function main() {
14
+ try {
15
+ console.log("🔥 Welcome to Charcole v2 CLI");
14
16
 
15
- const currentDir = process.cwd();
16
- const targetDir = path.join(currentDir, projectName);
17
- const templateDir = path.join(__dirname, "..", "template");
17
+ const responses = await prompts([
18
+ {
19
+ type: "text",
20
+ name: "projectName",
21
+ message: "Project name:",
22
+ validate: (name) => (name ? true : "Project name is required"),
23
+ },
24
+ {
25
+ type: "select",
26
+ name: "language",
27
+ message: "Language:",
28
+ choices: [
29
+ { title: "TypeScript", value: "ts" },
30
+ { title: "JavaScript", value: "js" },
31
+ ],
32
+ initial: 0,
33
+ },
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
+ ]);
18
48
 
19
- if (fs.existsSync(targetDir)) {
20
- console.error(`❌ Folder "${projectName}" already exists.`);
21
- process.exit(1);
22
- }
49
+ const { projectName, language } = responses;
50
+ const features = []; // Empty for now, will be responses.features later
51
+ const targetDir = path.join(process.cwd(), projectName);
23
52
 
24
- function copyDir(src, dest) {
25
- fs.mkdirSync(dest, { recursive: true });
26
- for (const file of fs.readdirSync(src)) {
27
- const srcPath = path.join(src, file);
28
- const destPath = path.join(dest, file);
29
- if (fs.statSync(srcPath).isDirectory()) {
30
- copyDir(srcPath, destPath);
31
- } else {
32
- fs.copyFileSync(srcPath, destPath);
53
+ if (fs.existsSync(targetDir)) {
54
+ console.error(`❌ Folder "${projectName}" already exists.`);
55
+ process.exit(1);
33
56
  }
34
- }
35
- }
36
57
 
37
- function detectPackageManager() {
38
- try {
39
- execSync("bun -v", { stdio: "ignore" });
40
- return "bun";
41
- } catch {}
42
- try {
43
- execSync("pnpm -v", { stdio: "ignore" });
44
- return "pnpm";
45
- } catch {}
46
- try {
47
- execSync("yarn -v", { stdio: "ignore" });
48
- return "yarn";
49
- } catch {}
50
- return "npm";
51
- }
58
+ const pkgManager = detectPackageManager();
52
59
 
53
- try {
54
- console.log("🔥 Creating Charcole app...");
55
- copyDir(templateDir, targetDir);
60
+ console.log(`\n📁 Creating project in ${language.toUpperCase()}...`);
56
61
 
57
- const pkgPath = path.join(targetDir, "package.json");
58
- if (fs.existsSync(pkgPath)) {
59
- const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
60
- pkg.name = projectName;
61
- fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2));
62
- }
62
+ // Template directory is template/js or template/ts
63
+ const templateDir = path.join(__dirname, "..", "template", language);
63
64
 
64
- console.log("✅ Project created successfully!");
65
+ copyTemplateModules(templateDir, targetDir, features);
65
66
 
66
- const pkgManager = detectPackageManager();
67
- console.log(`📦 Installing dependencies using ${pkgManager}...`);
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 };
68
72
 
69
- const installCmd =
70
- pkgManager === "bun"
71
- ? "bun install"
72
- : pkgManager === "yarn"
73
- ? "yarn"
74
- : pkgManager === "pnpm"
75
- ? "pnpm install"
76
- : "npm install";
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
+ // });
77
89
 
78
- execSync(installCmd, { cwd: targetDir, stdio: "inherit" });
90
+ mergedPkg.name = projectName;
91
+ fs.writeFileSync(
92
+ path.join(targetDir, "package.json"),
93
+ JSON.stringify(mergedPkg, null, 2),
94
+ );
79
95
 
80
- console.log("\n🚀 All set!");
81
- console.log(` cd ${projectName}`);
82
- console.log(" npm run dev");
83
- console.log("\n🧱 Built with Charcole Express, but engineered.");
84
- } catch (err) {
85
- console.error(" Failed to create Charcole app.");
86
- console.error(err.message);
87
- process.exit(1);
88
- }
96
+ console.log(`\n📦 Installing dependencies using ${pkgManager}...`);
97
+ installDependencies(targetDir, pkgManager);
98
+
99
+ console.log("\n Charcole project created successfully!");
100
+ console.log(
101
+ `\n🚀 Next steps:\n cd ${projectName}\n ${pkgManager === "npm" ? "npm run dev" : `${pkgManager} dev`}`,
102
+ );
103
+ } catch (err) {
104
+ console.error("❌ Failed to create Charcole project:", err.message);
105
+ process.exit(1);
106
+ }
107
+ })();
@@ -0,0 +1,66 @@
1
+ const { execSync } = require("child_process");
2
+ const fs = require("fs");
3
+ const path = require("path");
4
+
5
+ /**
6
+ * Detect which package manager the user is using
7
+ * Priority: pnpm > yarn > npm
8
+ * @returns {string} - Package manager name ('pnpm', 'yarn', or 'npm')
9
+ */
10
+ function detectPackageManager() {
11
+ const userAgent = process.env.npm_config_user_agent;
12
+
13
+ if (userAgent) {
14
+ if (userAgent.includes("pnpm")) return "pnpm";
15
+ if (userAgent.includes("yarn")) return "yarn";
16
+ if (userAgent.includes("npm")) return "npm";
17
+ }
18
+
19
+ const lockFiles = {
20
+ "pnpm-lock.yaml": "pnpm",
21
+ "yarn.lock": "yarn",
22
+ "package-lock.json": "npm",
23
+ };
24
+
25
+ for (const [lockFile, manager] of Object.entries(lockFiles)) {
26
+ if (fs.existsSync(path.join(process.cwd(), lockFile))) {
27
+ return manager;
28
+ }
29
+ }
30
+
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";
41
+ }
42
+
43
+ /**
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')
47
+ */
48
+ function installDependencies(targetDir, pkgManager) {
49
+ try {
50
+ const installCmd =
51
+ pkgManager === "yarn" ? "yarn install" : `${pkgManager} install`;
52
+
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
+ }
61
+ }
62
+
63
+ module.exports = {
64
+ detectPackageManager,
65
+ installDependencies,
66
+ };
@@ -0,0 +1,70 @@
1
+ const fs = require("fs");
2
+ const path = require("path");
3
+
4
+ /**
5
+ * Recursively copy directory contents
6
+ * @param {string} src - Source directory
7
+ * @param {string} dest - Destination directory
8
+ */
9
+ function copyDir(src, dest) {
10
+ if (!fs.existsSync(dest)) {
11
+ fs.mkdirSync(dest, { recursive: true });
12
+ }
13
+
14
+ const entries = fs.readdirSync(src, { withFileTypes: true });
15
+
16
+ for (const entry of entries) {
17
+ const srcPath = path.join(src, entry.name);
18
+ const destPath = path.join(dest, entry.name);
19
+
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")) {
46
+ continue;
47
+ }
48
+
49
+ if (entry.isDirectory()) {
50
+ copyDir(srcPath, destPath);
51
+ } else {
52
+ fs.copyFileSync(srcPath, destPath);
53
+ }
54
+ }
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
+ }
66
+
67
+ module.exports = {
68
+ copyTemplateModules,
69
+ copyDir,
70
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-charcole",
3
- "version": "1.1.0",
3
+ "version": "2.0.1",
4
4
  "description": "Production-ready Express backend starter with engineering guardrails.",
5
5
  "license": "MIT",
6
6
  "author": {
@@ -20,5 +20,8 @@
20
20
  ],
21
21
  "engines": {
22
22
  "node": ">=16"
23
+ },
24
+ "dependencies": {
25
+ "prompts": "^2.4.2"
23
26
  }
24
27
  }
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "charcole",
3
+ "version": "2.0.0",
4
+ "description": "Production-grade Node.js Express API",
5
+ "main": "src/server.js",
6
+ "scripts": {
7
+ "start": "node src/server.js",
8
+ "dev": "nodemon src/server.js",
9
+ "lint": "echo 'Add linting here'",
10
+ "test": "echo 'Add tests here'"
11
+ },
12
+ "engines": {
13
+ "node": ">=18.0.0"
14
+ },
15
+ "keywords": [],
16
+ "author": "",
17
+ "license": "ISC",
18
+ "type": "module",
19
+ "dependencies": {
20
+ "cors": "^2.8.5",
21
+ "dotenv": "^16.3.1",
22
+ "express": "^4.18.2",
23
+ "zod": "^3.22.4"
24
+ },
25
+ "devDependencies": {
26
+ "nodemon": "^3.0.2"
27
+ }
28
+ }