create-charcole 1.0.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.
- package/.github/workflows/release.yml +26 -0
- package/CHANGELOG.md +25 -0
- package/README.md +11 -1
- package/bin/index.js +94 -49
- package/bin/lib/pkgManager.js +66 -0
- package/bin/lib/templateHandler.js +70 -0
- package/package.json +4 -1
- package/template/js/basePackage.json +28 -0
- package/template/{package-lock.json → js/package-lock.json} +1253 -1253
- package/template/{package.json → js/package.json} +28 -28
- package/template/ts/.env.example +8 -0
- package/template/ts/ARCHITECTURE_DIAGRAMS.md +283 -0
- package/template/ts/CHECKLIST.md +279 -0
- package/template/ts/COMPLETE.md +405 -0
- package/template/ts/ERROR_HANDLING.md +393 -0
- package/template/ts/IMPLEMENTATION.md +368 -0
- package/template/ts/IMPLEMENTATION_COMPLETE.md +363 -0
- package/template/ts/INDEX.md +290 -0
- package/template/ts/QUICK_REFERENCE.md +270 -0
- package/template/ts/README.md +855 -0
- package/template/ts/basePackage.json +36 -0
- package/template/ts/package-lock.json +2428 -0
- package/template/ts/package.json +32 -0
- package/template/ts/src/app.js +75 -0
- package/template/ts/src/app.ts +66 -0
- package/template/ts/src/config/constants.js +20 -0
- package/template/ts/src/config/constants.ts +27 -0
- package/template/ts/src/config/env.js +26 -0
- package/template/ts/src/config/env.ts +40 -0
- package/template/ts/src/middlewares/errorHandler.js +180 -0
- package/template/ts/src/middlewares/errorHandler.ts +209 -0
- package/template/ts/src/middlewares/requestLogger.js +33 -0
- package/template/ts/src/middlewares/requestLogger.ts +38 -0
- package/template/ts/src/middlewares/validateRequest.js +42 -0
- package/template/ts/src/middlewares/validateRequest.ts +46 -0
- package/template/ts/src/modules/health/controller.js +50 -0
- package/template/ts/src/modules/health/controller.ts +64 -0
- package/template/ts/src/routes.js +17 -0
- package/template/ts/src/routes.ts +16 -0
- package/template/ts/src/server.js +38 -0
- package/template/ts/src/server.ts +42 -0
- package/template/ts/src/types/express.d.ts +9 -0
- package/template/ts/src/utils/AppError.js +182 -0
- package/template/ts/src/utils/AppError.ts +220 -0
- package/template/ts/src/utils/logger.js +73 -0
- package/template/ts/src/utils/logger.ts +55 -0
- package/template/ts/src/utils/response.js +51 -0
- package/template/ts/src/utils/response.ts +100 -0
- package/template/ts/test-api.js +100 -0
- package/template/ts/tsconfig.json +19 -0
- /package/template/{.env.example → js/.env.example} +0 -0
- /package/template/{ARCHITECTURE_DIAGRAMS.md → js/ARCHITECTURE_DIAGRAMS.md} +0 -0
- /package/template/{CHECKLIST.md → js/CHECKLIST.md} +0 -0
- /package/template/{COMPLETE.md → js/COMPLETE.md} +0 -0
- /package/template/{ERROR_HANDLING.md → js/ERROR_HANDLING.md} +0 -0
- /package/template/{IMPLEMENTATION.md → js/IMPLEMENTATION.md} +0 -0
- /package/template/{IMPLEMENTATION_COMPLETE.md → js/IMPLEMENTATION_COMPLETE.md} +0 -0
- /package/template/{INDEX.md → js/INDEX.md} +0 -0
- /package/template/{QUICK_REFERENCE.md → js/QUICK_REFERENCE.md} +0 -0
- /package/template/{README.md → js/README.md} +0 -0
- /package/template/{src → js/src}/app.js +0 -0
- /package/template/{src → js/src}/config/constants.js +0 -0
- /package/template/{src → js/src}/config/env.js +0 -0
- /package/template/{src → js/src}/middlewares/errorHandler.js +0 -0
- /package/template/{src → js/src}/middlewares/requestLogger.js +0 -0
- /package/template/{src → js/src}/middlewares/validateRequest.js +0 -0
- /package/template/{src → js/src}/modules/health/controller.js +0 -0
- /package/template/{src → js/src}/routes.js +0 -0
- /package/template/{src → js/src}/server.js +0 -0
- /package/template/{src → js/src}/utils/AppError.js +0 -0
- /package/template/{src → js/src}/utils/logger.js +0 -0
- /package/template/{src → js/src}/utils/response.js +0 -0
- /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
|
-
> **
|
|
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
|
[](https://nodejs.org/)
|
|
6
6
|
[](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,62 +1,107 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
1
|
+
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
const fs = require("fs");
|
|
4
3
|
const path = require("path");
|
|
4
|
+
const fs = require("fs");
|
|
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");
|
|
5
12
|
|
|
6
|
-
|
|
13
|
+
(async function main() {
|
|
14
|
+
try {
|
|
15
|
+
console.log("🔥 Welcome to Charcole v2 CLI");
|
|
7
16
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
+
]);
|
|
13
48
|
|
|
14
|
-
const
|
|
15
|
-
const
|
|
16
|
-
const
|
|
49
|
+
const { projectName, language } = responses;
|
|
50
|
+
const features = []; // Empty for now, will be responses.features later
|
|
51
|
+
const targetDir = path.join(process.cwd(), projectName);
|
|
17
52
|
|
|
18
|
-
if (fs.existsSync(targetDir)) {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}
|
|
53
|
+
if (fs.existsSync(targetDir)) {
|
|
54
|
+
console.error(`❌ Folder "${projectName}" already exists.`);
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
22
57
|
|
|
23
|
-
|
|
24
|
-
fs.mkdirSync(dest, { recursive: true });
|
|
58
|
+
const pkgManager = detectPackageManager();
|
|
25
59
|
|
|
26
|
-
|
|
27
|
-
const srcPath = path.join(src, file);
|
|
28
|
-
const destPath = path.join(dest, file);
|
|
60
|
+
console.log(`\n📁 Creating project in ${language.toUpperCase()}...`);
|
|
29
61
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
} else {
|
|
33
|
-
fs.copyFileSync(srcPath, destPath);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
}
|
|
62
|
+
// Template directory is template/js or template/ts
|
|
63
|
+
const templateDir = path.join(__dirname, "..", "template", language);
|
|
37
64
|
|
|
38
|
-
|
|
39
|
-
console.log("🔥 Creating Charcole app...");
|
|
65
|
+
copyTemplateModules(templateDir, targetDir, features);
|
|
40
66
|
|
|
41
|
-
|
|
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 };
|
|
42
72
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
+
// });
|
|
89
|
+
|
|
90
|
+
mergedPkg.name = projectName;
|
|
91
|
+
fs.writeFileSync(
|
|
92
|
+
path.join(targetDir, "package.json"),
|
|
93
|
+
JSON.stringify(mergedPkg, null, 2),
|
|
94
|
+
);
|
|
49
95
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
}
|
|
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": "
|
|
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
|
+
}
|