neatnode 3.2.0 → 3.3.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/README.md +135 -131
- package/package.json +1 -1
- package/src/actions/addEnv.js +20 -0
- package/src/actions/createProject.js +22 -3
- package/src/actions/removeCRUD.js +32 -9
- package/src/cli.js +147 -128
- package/src/config/templates.js +19 -13
- package/src/utils/downloadRepoTemplateByVersionTags.js +0 -3
- package/src/utils/downloadRepoTemplate.js +0 -51
package/README.md
CHANGED
|
@@ -1,131 +1,135 @@
|
|
|
1
|
-
<div align="center">
|
|
2
|
-
<img src="https://i.postimg.cc/9Fvw7p3q/1762540009103.png" width="120" alt="NeatNode Logo" />
|
|
3
|
-
<h1>NeatNode</h1>
|
|
4
|
-
<p><strong>Instantly scaffold production-ready Node.js backends with one command.</strong></p>
|
|
5
|
-
|
|
6
|
-
<img src="https://i.postimg.cc/59sw6LN6/1762540161048.png" alt="NeatNode Banner" />
|
|
7
|
-
</div>
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
-
|
|
21
|
-
-
|
|
22
|
-
-
|
|
23
|
-
-
|
|
24
|
-
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
```bash
|
|
56
|
-
cd
|
|
57
|
-
npm install
|
|
58
|
-
npm run dev
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
###
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
## License
|
|
130
|
-
|
|
131
|
-
MIT
|
|
1
|
+
<div align="center">
|
|
2
|
+
<img src="https://i.postimg.cc/9Fvw7p3q/1762540009103.png" width="120" alt="NeatNode Logo" />
|
|
3
|
+
<h1>NeatNode</h1>
|
|
4
|
+
<p><strong>Instantly scaffold production-ready Node.js backends with one command.</strong></p>
|
|
5
|
+
|
|
6
|
+
<img src="https://i.postimg.cc/59sw6LN6/1762540161048.png" alt="NeatNode Banner" />
|
|
7
|
+
</div>
|
|
8
|
+
|
|
9
|
+
## Overview
|
|
10
|
+
|
|
11
|
+
**NeatNode** is a plug-and-play **CLI tool** that scaffolds clean, production-ready **Node.js backend projects** (JavaScript & TypeScript) in seconds.
|
|
12
|
+
It comes with pre-built templates, MVC or Modular architecture choices, optional CRUD modules, and a modern developer workflow - so you can **start coding instead of configuring.**
|
|
13
|
+
|
|
14
|
+
## Features
|
|
15
|
+
|
|
16
|
+
- **Instant project setup** - generate a complete Node.js project structure instantly
|
|
17
|
+
- **JavaScript & TypeScript support** - choose your preferred language
|
|
18
|
+
- **Pre-configured templates** - Basic Express, REST API, and Socket.IO
|
|
19
|
+
- **Flexible Architecture** - pick between classic MVC or Modular architectures for REST APIs
|
|
20
|
+
- **Dynamic template logic** - easily include or exclude example CRUD modules (User, Todo, Auth)
|
|
21
|
+
- **Scalable architecture** - prebuilt folder layouts
|
|
22
|
+
- **Security-first setup** - Helmet, Rate Limiter, and CORS integrated
|
|
23
|
+
- **Built-in error handling & logging** - Winston + Morgan setup (depending on template)
|
|
24
|
+
- **Production-ready** - clean, validated, and deployment-ready output
|
|
25
|
+
|
|
26
|
+
## Installation
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
# Install globally
|
|
30
|
+
npm install -g neatnode
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
or run it directly using `npx`:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
npx neatnode
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Usage
|
|
40
|
+
|
|
41
|
+
1. Run the CLI:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
npx neatnode
|
|
45
|
+
```
|
|
46
|
+
2. Enter project folder name (default: `my-app`).
|
|
47
|
+
3. Select Language:
|
|
48
|
+
* **JavaScript**
|
|
49
|
+
* **TypeScript**
|
|
50
|
+
4. Choose your template (options depend on language).
|
|
51
|
+
5. (Optional) For REST API in JavaScript, choose your architecture (MVC or Modular).
|
|
52
|
+
6. (Optional) Incorporate example CRUD modules, like Todo, User, or Auth.
|
|
53
|
+
7. Navigate and start building:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
cd <project_name>
|
|
57
|
+
npm install
|
|
58
|
+
npm run dev
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Available Templates
|
|
62
|
+
|
|
63
|
+
### **JavaScript**
|
|
64
|
+
| Template | Description |
|
|
65
|
+
| ------------ | -------------------------------------------------------------------------------- |
|
|
66
|
+
| **Basic Express** | Minimal structure with .env, nodemon, and clean folder setup (Optional Todo CRUD) |
|
|
67
|
+
| **REST API** | Express.js REST API with controllers, routes, and MongoDB config. Supports **MVC** or **Modular** architecture. (Optional User CRUD) |
|
|
68
|
+
| **Socket.IO**| Socket.io server setup for real-time apps |
|
|
69
|
+
|
|
70
|
+
### **TypeScript**
|
|
71
|
+
| Template | Description |
|
|
72
|
+
| ------------ | -------------------------------------------------------------------------------- |
|
|
73
|
+
| **Basic Express** | Minimal TS structure for Node.js (Optional Todo CRUD) |
|
|
74
|
+
| **REST API** | Express.js REST API boilerplate with TS. (Modular architectural setup with Optional Auth CRUD) |
|
|
75
|
+
|
|
76
|
+
## Example CLI Output
|
|
77
|
+
|
|
78
|
+
```
|
|
79
|
+
$ npx neatnode
|
|
80
|
+
🚀 Welcome to NeatNode CLI!
|
|
81
|
+
|
|
82
|
+
? Enter project folder name: api-server
|
|
83
|
+
? Select language: TypeScript
|
|
84
|
+
? Choose a template: REST API (TS)
|
|
85
|
+
? Include example Auth CRUD? Yes
|
|
86
|
+
|
|
87
|
+
✅ Project "api-server" created successfully using "REST API (TS)".
|
|
88
|
+
|
|
89
|
+
Next steps:
|
|
90
|
+
cd api-server
|
|
91
|
+
npm install
|
|
92
|
+
npm run dev
|
|
93
|
+
|
|
94
|
+
🎉 Happy Coding!
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Previews
|
|
98
|
+
|
|
99
|
+
### **Frontend Website**
|
|
100
|
+
|
|
101
|
+
> Official landing page for NeatNode
|
|
102
|
+
>
|
|
103
|
+
> <img alt="NeatNode Landing Page" src="https://raw.githubusercontent.com/aakash-gupta02/NeatNode/refs/heads/main/assets/images/landingPage.png" />
|
|
104
|
+
|
|
105
|
+
### **Documentation Website**
|
|
106
|
+
|
|
107
|
+
> Comprehensive docs built with Nextra
|
|
108
|
+
>
|
|
109
|
+
> <img alt="NeatNode Docs" src="https://raw.githubusercontent.com/aakash-gupta02/NeatNode/refs/heads/main/assets/images/docs.png" />
|
|
110
|
+
|
|
111
|
+
## Build With
|
|
112
|
+
|
|
113
|
+
* Node.js
|
|
114
|
+
* Inquirer (CLI prompts)
|
|
115
|
+
* Degit (repository fetching)
|
|
116
|
+
* Chalk & Ora
|
|
117
|
+
|
|
118
|
+
## Learn More
|
|
119
|
+
|
|
120
|
+
* 📄 [Documentation](https://docs.neatnode.codes)
|
|
121
|
+
* 💻 [Website](https://neatnode.codes)
|
|
122
|
+
* 🧰 [NPM Package](https://www.npmjs.com/package/neatnode)
|
|
123
|
+
|
|
124
|
+
## Author
|
|
125
|
+
|
|
126
|
+
**Aakash Gupta**
|
|
127
|
+
[GitHub](https://github.com/aakash-gupta02) • [Portfolio](https://aakashgupta.app)
|
|
128
|
+
|
|
129
|
+
## License
|
|
130
|
+
|
|
131
|
+
This project is licensed under the **MIT License** — feel free to use, modify, and distribute.
|
|
132
|
+
|
|
133
|
+
<div align="center">
|
|
134
|
+
<sub>Built with ❤️ by Aakash Gupta | NeatNode © 2026</sub>
|
|
135
|
+
</div>
|
package/package.json
CHANGED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
|
|
4
|
+
export async function addEnv({ targetPath }) {
|
|
5
|
+
try {
|
|
6
|
+
const envExamplePath = path.join(targetPath, ".env.example");
|
|
7
|
+
const envPath = path.join(targetPath, ".env");
|
|
8
|
+
|
|
9
|
+
if (
|
|
10
|
+
fs.existsSync(envExamplePath) &&
|
|
11
|
+
!fs.existsSync(envPath)
|
|
12
|
+
) {
|
|
13
|
+
const envContent = fs.readFileSync(envExamplePath, "utf8");
|
|
14
|
+
|
|
15
|
+
fs.writeFileSync(envPath, envContent, "utf8");
|
|
16
|
+
}
|
|
17
|
+
} catch (error) {
|
|
18
|
+
console.error("Error adding .env file:", error);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -3,8 +3,9 @@ import path from "path";
|
|
|
3
3
|
import os from "os";
|
|
4
4
|
import { fileURLToPath } from "url";
|
|
5
5
|
import { copyTemplate } from "../utils/copyTemplate.js";
|
|
6
|
-
import { removeCrud, removeCrudModule, removeCrudReferences } from "./removeCRUD.js";
|
|
6
|
+
import { cleanupTemplateMarkers, removeCrud, removeCrudModule, removeCrudReferences } from "./removeCRUD.js";
|
|
7
7
|
import { downloadTemplate } from "../utils/downloadRepoTemplateByVersionTags.js";
|
|
8
|
+
import { addEnv } from "./addEnv.js";
|
|
8
9
|
|
|
9
10
|
const __filename = fileURLToPath(import.meta.url);
|
|
10
11
|
const __dirname = path.dirname(__filename);
|
|
@@ -33,17 +34,35 @@ export async function createProject({ projectName, repoPath, includeCrud, crudNa
|
|
|
33
34
|
"author": os.userInfo().username || "author",
|
|
34
35
|
});
|
|
35
36
|
|
|
37
|
+
await addEnv({ targetPath });
|
|
38
|
+
|
|
36
39
|
if (!includeCrud && crudName) {
|
|
37
40
|
console.log("🗑 Removing CRUD files...");
|
|
38
41
|
|
|
39
42
|
if (isModular) {
|
|
40
43
|
removeCrudModule(targetPath, crudName);
|
|
41
|
-
removeCrudReferences(path.join(targetPath, "src", `routes/index.route.${langKey}`));
|
|
42
44
|
|
|
45
|
+
removeCrudReferences(
|
|
46
|
+
path.join(targetPath, "src", `routes/index.route.${langKey}`)
|
|
47
|
+
);
|
|
43
48
|
}
|
|
44
49
|
|
|
45
50
|
removeCrud(targetPath, crudName, langKey);
|
|
46
|
-
|
|
51
|
+
|
|
52
|
+
removeCrudReferences(
|
|
53
|
+
path.join(targetPath, "src", `app.${langKey}`)
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// ALWAYS CLEANUP MARKERS
|
|
58
|
+
cleanupTemplateMarkers(
|
|
59
|
+
path.join(targetPath, "src", `app.${langKey}`)
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
if (isModular) {
|
|
63
|
+
cleanupTemplateMarkers(
|
|
64
|
+
path.join(targetPath, "src", `routes/index.route.${langKey}`)
|
|
65
|
+
);
|
|
47
66
|
}
|
|
48
67
|
|
|
49
68
|
console.log(`\n✅ Project "${projectName}" created successfully!\n`);
|
|
@@ -26,29 +26,52 @@ export function removeCrud(targetPath, name, langKey) {
|
|
|
26
26
|
} catch (err) {
|
|
27
27
|
console.error("❌ Error while removing CRUD files:", err.message);
|
|
28
28
|
}
|
|
29
|
-
}
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export function cleanupTemplateMarkers(filePath) {
|
|
32
|
+
if (!fs.existsSync(filePath)) {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
let content = fs.readFileSync(filePath, "utf8");
|
|
37
|
+
|
|
38
|
+
content = content
|
|
39
|
+
.replace(/^\s*\/\/ ROUTE_IMPORTS_START\s*$/gm, "")
|
|
40
|
+
.replace(/^\s*\/\/ ROUTE_IMPORTS_END\s*$/gm, "")
|
|
41
|
+
.replace(/^\s*\/\/ ROUTE_USES_START\s*$/gm, "")
|
|
42
|
+
.replace(/^\s*\/\/ ROUTE_USES_END\s*$/gm, "");
|
|
43
|
+
|
|
44
|
+
// Remove excessive blank lines
|
|
45
|
+
content = content.replace(/\n{3,}/g, "\n\n");
|
|
46
|
+
|
|
47
|
+
fs.writeFileSync(filePath, content, "utf8");
|
|
48
|
+
|
|
49
|
+
return true;
|
|
50
|
+
};
|
|
30
51
|
|
|
31
52
|
export function removeCrudReferences(appJsPath) {
|
|
32
53
|
if (!fs.existsSync(appJsPath)) {
|
|
33
54
|
return false;
|
|
34
55
|
}
|
|
56
|
+
|
|
35
57
|
let content = fs.readFileSync(appJsPath, "utf8");
|
|
36
58
|
|
|
37
|
-
// Remove imports block
|
|
59
|
+
// Remove imports block + surrounding blank lines
|
|
38
60
|
content = content.replace(
|
|
39
|
-
|
|
40
|
-
"",
|
|
61
|
+
/\s*\/\/ ROUTE_IMPORTS_START[\s\S]*?\/\/ ROUTE_IMPORTS_END\s*/g,
|
|
62
|
+
"\n",
|
|
41
63
|
);
|
|
42
64
|
|
|
43
|
-
// Remove route usage block
|
|
65
|
+
// Remove route usage block + surrounding blank lines
|
|
44
66
|
content = content.replace(
|
|
45
|
-
|
|
46
|
-
"",
|
|
67
|
+
/\s*\/\/ ROUTE_USES_START[\s\S]*?\/\/ ROUTE_USES_END\s*/g,
|
|
68
|
+
"\n",
|
|
47
69
|
);
|
|
48
70
|
|
|
49
71
|
fs.writeFileSync(appJsPath, content, "utf8");
|
|
72
|
+
|
|
50
73
|
return true;
|
|
51
|
-
}
|
|
74
|
+
};
|
|
52
75
|
|
|
53
76
|
export function removeCrudModule(targetPath, name) {
|
|
54
77
|
try {
|
|
@@ -62,4 +85,4 @@ export function removeCrudModule(targetPath, name) {
|
|
|
62
85
|
} catch (err) {
|
|
63
86
|
console.error("❌ Error while removing CRUD module:", err.message);
|
|
64
87
|
}
|
|
65
|
-
}
|
|
88
|
+
};
|
package/src/cli.js
CHANGED
|
@@ -1,128 +1,147 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import inquirer from "inquirer";
|
|
3
|
-
import templates from "./config/templates.js";
|
|
4
|
-
import { createProject } from "./actions/createProject.js";
|
|
5
|
-
|
|
6
|
-
async function main() {
|
|
7
|
-
console.log("\n🚀 Welcome to NeatNode CLI!\n");
|
|
8
|
-
|
|
9
|
-
// STEP 1 — Project Name
|
|
10
|
-
const { projectName } = await inquirer.prompt([
|
|
11
|
-
{
|
|
12
|
-
type: "input",
|
|
13
|
-
name: "projectName",
|
|
14
|
-
message: "Enter project folder name:",
|
|
15
|
-
default: "my-app",
|
|
16
|
-
validate: (v) => v.trim() !== "" || "Project name cannot be empty.",
|
|
17
|
-
},
|
|
18
|
-
]);
|
|
19
|
-
|
|
20
|
-
// STEP 2 — Choose Language
|
|
21
|
-
const { language } = await inquirer.prompt([
|
|
22
|
-
{
|
|
23
|
-
type: "list",
|
|
24
|
-
name: "language",
|
|
25
|
-
message: "Select language:",
|
|
26
|
-
choices: ["JavaScript", "TypeScript"],
|
|
27
|
-
},
|
|
28
|
-
]);
|
|
29
|
-
|
|
30
|
-
const langKey = language === "JavaScript" ? "js" : "ts";
|
|
31
|
-
const templateList = templates[langKey];
|
|
32
|
-
|
|
33
|
-
// STEP 3 — Choose Template
|
|
34
|
-
const { template } = await inquirer.prompt([
|
|
35
|
-
{
|
|
36
|
-
type: "list",
|
|
37
|
-
name: "template",
|
|
38
|
-
message: "Choose a template:",
|
|
39
|
-
choices: templateList.map((t) => t.name),
|
|
40
|
-
},
|
|
41
|
-
]);
|
|
42
|
-
|
|
43
|
-
const chosen = templateList.find((t) => t.name === template);
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
let
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import inquirer from "inquirer";
|
|
3
|
+
import templates from "./config/templates.js";
|
|
4
|
+
import { createProject } from "./actions/createProject.js";
|
|
5
|
+
|
|
6
|
+
async function main() {
|
|
7
|
+
console.log("\n🚀 Welcome to NeatNode CLI!\n");
|
|
8
|
+
|
|
9
|
+
// STEP 1 — Project Name
|
|
10
|
+
const { projectName } = await inquirer.prompt([
|
|
11
|
+
{
|
|
12
|
+
type: "input",
|
|
13
|
+
name: "projectName",
|
|
14
|
+
message: "Enter project folder name:",
|
|
15
|
+
default: "my-app",
|
|
16
|
+
validate: (v) => v.trim() !== "" || "Project name cannot be empty.",
|
|
17
|
+
},
|
|
18
|
+
]);
|
|
19
|
+
|
|
20
|
+
// STEP 2 — Choose Language
|
|
21
|
+
const { language } = await inquirer.prompt([
|
|
22
|
+
{
|
|
23
|
+
type: "list",
|
|
24
|
+
name: "language",
|
|
25
|
+
message: "Select language:",
|
|
26
|
+
choices: ["JavaScript", "TypeScript"],
|
|
27
|
+
},
|
|
28
|
+
]);
|
|
29
|
+
|
|
30
|
+
const langKey = language === "JavaScript" ? "js" : "ts";
|
|
31
|
+
const templateList = templates[langKey];
|
|
32
|
+
|
|
33
|
+
// STEP 3 — Choose Template
|
|
34
|
+
const { template } = await inquirer.prompt([
|
|
35
|
+
{
|
|
36
|
+
type: "list",
|
|
37
|
+
name: "template",
|
|
38
|
+
message: "Choose a template:",
|
|
39
|
+
choices: templateList.map((t) => t.name),
|
|
40
|
+
},
|
|
41
|
+
]);
|
|
42
|
+
|
|
43
|
+
const chosen = templateList.find((t) => t.name === template);
|
|
44
|
+
|
|
45
|
+
let isModular = chosen.isModular || false;
|
|
46
|
+
let architecture = null;
|
|
47
|
+
|
|
48
|
+
if (chosen.architecture) {
|
|
49
|
+
const answer = await inquirer.prompt([
|
|
50
|
+
{
|
|
51
|
+
type: "list",
|
|
52
|
+
name: "architecture",
|
|
53
|
+
message: "Select architecture:",
|
|
54
|
+
choices: ["mvc", "modular"],
|
|
55
|
+
},
|
|
56
|
+
]);
|
|
57
|
+
|
|
58
|
+
architecture = answer.architecture;
|
|
59
|
+
isModular = architecture === "modular";
|
|
60
|
+
|
|
61
|
+
chosen.repoPath = chosen.architecture[architecture];
|
|
62
|
+
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// STEP 4 — CRUD Optional (only for some templates)
|
|
66
|
+
let includeCrud = false;
|
|
67
|
+
let crudName = "";
|
|
68
|
+
|
|
69
|
+
if (chosen.name === "Basic Express") {
|
|
70
|
+
const { includeCrud: answer } = await inquirer.prompt([
|
|
71
|
+
{
|
|
72
|
+
type: "confirm",
|
|
73
|
+
name: "includeCrud",
|
|
74
|
+
message: "Include example Todo CRUD?",
|
|
75
|
+
default: true,
|
|
76
|
+
},
|
|
77
|
+
]);
|
|
78
|
+
includeCrud = answer;
|
|
79
|
+
crudName = "todo";
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (chosen.name === "REST API") {
|
|
83
|
+
const { includeCrud: answer } = await inquirer.prompt([
|
|
84
|
+
{
|
|
85
|
+
type: "confirm",
|
|
86
|
+
name: "includeCrud",
|
|
87
|
+
message: "Include example User CRUD?",
|
|
88
|
+
default: true,
|
|
89
|
+
},
|
|
90
|
+
]);
|
|
91
|
+
includeCrud = answer;
|
|
92
|
+
crudName = "user";
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (chosen.name === "Basic Express (TS)") {
|
|
96
|
+
const { includeCrud: answer } = await inquirer.prompt([
|
|
97
|
+
{
|
|
98
|
+
type: "confirm",
|
|
99
|
+
name: "includeCrud",
|
|
100
|
+
message: "Include example Todo CRUD?",
|
|
101
|
+
default: true,
|
|
102
|
+
},
|
|
103
|
+
]);
|
|
104
|
+
includeCrud = answer;
|
|
105
|
+
crudName = "todo";
|
|
106
|
+
}
|
|
107
|
+
if (chosen.name === "REST API (TS)") {
|
|
108
|
+
const { includeCrud: answer } = await inquirer.prompt([
|
|
109
|
+
{
|
|
110
|
+
type: "confirm",
|
|
111
|
+
name: "includeCrud",
|
|
112
|
+
message: "Include example Auth CRUD?",
|
|
113
|
+
default: true,
|
|
114
|
+
},
|
|
115
|
+
]);
|
|
116
|
+
includeCrud = answer;
|
|
117
|
+
crudName = "auth";
|
|
118
|
+
isModular = chosen.isModular;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// STEP 5 — Create Project (Remote download logic inside)
|
|
122
|
+
await createProject({
|
|
123
|
+
projectName,
|
|
124
|
+
repoPath: chosen.repoPath,
|
|
125
|
+
includeCrud,
|
|
126
|
+
crudName,
|
|
127
|
+
langKey,
|
|
128
|
+
isModular,
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
console.log(`\n✅ Project "${projectName}" created successfully using "${chosen.name}".\n`);
|
|
133
|
+
|
|
134
|
+
console.log("Next steps:");
|
|
135
|
+
console.log(` cd ${projectName}`);
|
|
136
|
+
console.log(" npm install");
|
|
137
|
+
console.log(" npm run dev\n");
|
|
138
|
+
|
|
139
|
+
console.log("🎉 Happy Coding!\n");
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
main().catch((err) => {
|
|
146
|
+
console.error("❌ Error:", err.message || err);
|
|
147
|
+
});
|
package/src/config/templates.js
CHANGED
|
@@ -1,13 +1,19 @@
|
|
|
1
|
-
export default {
|
|
2
|
-
js: [
|
|
3
|
-
{ name: "Basic Express", repoPath: "templates/js/express-basic" },
|
|
4
|
-
{
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
],
|
|
13
|
-
|
|
1
|
+
export default {
|
|
2
|
+
js: [
|
|
3
|
+
{ name: "Basic Express", repoPath: "templates/js/express-basic" },
|
|
4
|
+
{
|
|
5
|
+
name: "REST API",
|
|
6
|
+
architecture: {
|
|
7
|
+
mvc: "templates/js/express-rest-api",
|
|
8
|
+
modular: "templates/js/express-modular-rest-api"
|
|
9
|
+
}
|
|
10
|
+
},
|
|
11
|
+
{ name: "Socket.IO", repoPath: "templates/js/express-socket" },
|
|
12
|
+
],
|
|
13
|
+
|
|
14
|
+
ts: [
|
|
15
|
+
{ name: "Basic Express (TS)", repoPath: "templates/ts/basic-express" },
|
|
16
|
+
{ name: "REST API (TS)", repoPath: "templates/ts/express-rest-api", isModular: true },
|
|
17
|
+
// { name: "Socket.IO (TS)", repoPath: "templates/ts/express-socket" },
|
|
18
|
+
],
|
|
19
|
+
};
|
|
@@ -114,9 +114,6 @@ export async function downloadTemplate(repoPath) {
|
|
|
114
114
|
refType: candidate.refType,
|
|
115
115
|
});
|
|
116
116
|
|
|
117
|
-
console.log(
|
|
118
|
-
`Template downloaded and extracted from ${candidate.refType} "${candidate.ref}"`,
|
|
119
|
-
);
|
|
120
117
|
return templatePath;
|
|
121
118
|
} catch (error) {
|
|
122
119
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import axios from "axios";
|
|
2
|
-
import extract from "extract-zip";
|
|
3
|
-
import fs from "fs";
|
|
4
|
-
import path from "path";
|
|
5
|
-
import os from "os";
|
|
6
|
-
import { fileURLToPath } from "url";
|
|
7
|
-
|
|
8
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
9
|
-
const __dirname = path.dirname(__filename);
|
|
10
|
-
|
|
11
|
-
const owner = "aakash-gupta02";
|
|
12
|
-
const repo = "NeatNode";
|
|
13
|
-
|
|
14
|
-
const zipUrl = `https://codeload.github.com/${owner}/${repo}/zip/refs/heads/main`;
|
|
15
|
-
|
|
16
|
-
export async function downloadTemplate(repoPath) {
|
|
17
|
-
// SAFE TEMP DIRECTORY
|
|
18
|
-
const tmpBase = fs.mkdtempSync(path.join(os.tmpdir(), "neatnode-"));
|
|
19
|
-
|
|
20
|
-
const tempZip = path.join(tmpBase, "repo.zip");
|
|
21
|
-
const tempExtractDir = path.join(tmpBase, "repo-extract");
|
|
22
|
-
const tempFinalDir = path.join(tmpBase, "template-final");
|
|
23
|
-
|
|
24
|
-
// download zip
|
|
25
|
-
const response = await axios({
|
|
26
|
-
url: zipUrl,
|
|
27
|
-
responseType: "arraybuffer",
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
fs.writeFileSync(tempZip, response.data);
|
|
31
|
-
|
|
32
|
-
// unzip
|
|
33
|
-
await extract(tempZip, { dir: tempExtractDir });
|
|
34
|
-
|
|
35
|
-
const extractedRoot = path.join(tempExtractDir, `${repo}-main`);
|
|
36
|
-
const srcTemplatePath = path.join(extractedRoot, repoPath);
|
|
37
|
-
|
|
38
|
-
if (!fs.existsSync(srcTemplatePath)) {
|
|
39
|
-
throw new Error(
|
|
40
|
-
`Template path not found in downloaded archive: ${repoPath}`,
|
|
41
|
-
);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// copy template
|
|
45
|
-
fs.mkdirSync(tempFinalDir, { recursive: true });
|
|
46
|
-
fs.cpSync(srcTemplatePath, tempFinalDir, { recursive: true });
|
|
47
|
-
|
|
48
|
-
console.log("✔ Template downloaded & extracted");
|
|
49
|
-
|
|
50
|
-
return tempFinalDir;
|
|
51
|
-
}
|