create-backlist 1.3.3 β 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/LICENSE +21 -0
- package/README.md +97 -0
- package/package.json +1 -1
- package/src/analyzer.js +69 -4
- package/src/generators/node.js +81 -37
- package/src/templates/node-ts-express/partials/Controller.ts.ejs +57 -0
- package/src/templates/node-ts-express/partials/Model.ts.ejs +22 -0
- package/src/templates/node-ts-express/partials/routes.ts.ejs +50 -13
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) [2025] [W.A.H.ISHAN]
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# π Create Backlist
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/create-backlist)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
|
|
6
|
+
Tired of manually creating backend boilerplate every time you build a frontend? **`create-backlist`** is an intelligent CLI tool that analyzes your frontend project and automatically generates a backend with all the necessary routes and controllers, saving you hours of repetitive work.
|
|
7
|
+
|
|
8
|
+
It's not just another scaffolder; it's a **context-aware, dynamic code generator** that builds a backend tailor-made for your frontend's specific API needs.
|
|
9
|
+
|
|
10
|
+

|
|
11
|
+
|
|
12
|
+
## β¨ Key Features
|
|
13
|
+
|
|
14
|
+
- **π€ Intelligent Code Analysis:** Scans your frontend codebase (React, Vue, etc.) using Abstract Syntax Trees (ASTs) to detect API calls (`fetch` requests).
|
|
15
|
+
- **π Multi-Language Support:** Generate a backend in your preferred stack.
|
|
16
|
+
- β
**Currently Supports:**
|
|
17
|
+
- Node.js (with TypeScript & Express)
|
|
18
|
+
- C# (with ASP.NET Core Web API)
|
|
19
|
+
- β³ **Coming Soon:**
|
|
20
|
+
- Python (with FastAPI)
|
|
21
|
+
- Java (with Spring Boot)
|
|
22
|
+
- **β‘οΈ Fully Automated:** A single command handles everything from project scaffolding to dependency installation.
|
|
23
|
+
- **π§ Zero-Configuration:** No complex config files needed. Just run the command and answer a few simple questions.
|
|
24
|
+
- **π§Ό Clean Code Generation:** Creates a well-structured backend, ready for you to implement your business logic.
|
|
25
|
+
|
|
26
|
+
## π¦ Installation & Usage
|
|
27
|
+
|
|
28
|
+
No global installation needed! Just run this command inside your existing frontend project's root directory:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npm create backlist@latest
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
The tool will then guide you through an interactive setup process:
|
|
35
|
+
|
|
36
|
+
1. **Enter a name for your backend directory:** (default: `backend`)
|
|
37
|
+
2. **Select the backend stack:** (e.g., `Node.js (TypeScript, Express)`)
|
|
38
|
+
3. **Enter the path to your frontend `src` directory:** (default: `src`)
|
|
39
|
+
|
|
40
|
+
That's it! The tool will analyze your code, generate the backend in a new directory, and install all the necessary dependencies.
|
|
41
|
+
|
|
42
|
+
### Example
|
|
43
|
+
|
|
44
|
+
Let's say your frontend has this API call:
|
|
45
|
+
|
|
46
|
+
```javascript
|
|
47
|
+
// in your React component
|
|
48
|
+
fetch('/api/products/123', { method: 'PUT' });
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
`create-backlist` will automatically generate a backend with a `products` controller and a `PUT` route for `products/:id`.
|
|
52
|
+
|
|
53
|
+
## π‘ How It's Different from Other Tools
|
|
54
|
+
|
|
55
|
+
| Tool | Approach | Use Case |
|
|
56
|
+
| --------------------- | -------------------------------------- | ----------------------------------------------------------- |
|
|
57
|
+
| **Express Generator** | Static Scaffolding | Quickly start a *new, empty* Express project. |
|
|
58
|
+
| **NestJS CLI** | Static Scaffolding & Code Generation | Start a *new, structured* NestJS project and add parts manually. |
|
|
59
|
+
| **`create-backlist`** | **Dynamic & Context-Aware Scaffolding** | Generate a backend that is **tailor-made** for an *existing* frontend. |
|
|
60
|
+
|
|
61
|
+
While traditional generators give you a blank canvas, `create-backlist` looks at your finished painting (the frontend) and builds the perfect frame (the backend) for it.
|
|
62
|
+
|
|
63
|
+
## πΊοΈ Roadmap
|
|
64
|
+
|
|
65
|
+
`create-backlist` is actively being developed. Here are some of the features planned for future releases:
|
|
66
|
+
|
|
67
|
+
- [ ] **Python Support:** Generate a backend using FastAPI.
|
|
68
|
+
- [ ] **Java Support:** Generate a backend using Spring Boot.
|
|
69
|
+
- [ ] **Database Model Generation:** Automatically create basic database models (e.g., Mongoose, Prisma) based on `POST`/`PUT` request bodies.
|
|
70
|
+
- [ ] **Authentication Boilerplate:** Add an option to generate basic JWT-based authentication routes (`/login`, `/register`).
|
|
71
|
+
|
|
72
|
+
Have an idea for a new feature? Feel free to [open an issue](https://github.com/WAH-ISHAN/create-backlist/issues) on GitHub!
|
|
73
|
+
|
|
74
|
+
## π οΈ Contributing
|
|
75
|
+
|
|
76
|
+
Contributions are what make the open-source community such an amazing place to learn, inspire, and create. Any contributions you make are **greatly appreciated**.
|
|
77
|
+
|
|
78
|
+
1. Fork the Project
|
|
79
|
+
2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`)
|
|
80
|
+
3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`)
|
|
81
|
+
4. Push to the Branch (`git push origin feature/AmazingFeature`)
|
|
82
|
+
5. Open a Pull Request
|
|
83
|
+
|
|
84
|
+
## π License
|
|
85
|
+
|
|
86
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
87
|
+
|
|
88
|
+
## π Acknowledgements
|
|
89
|
+
|
|
90
|
+
- **Babel** for the amazing AST parser.
|
|
91
|
+
- **Inquirer.js** for the interactive CLI prompts.
|
|
92
|
+
- **fs-extra** for making file system operations a breeze.
|
|
93
|
+
- **Google's Gemini** for assistance with brainstorming and debugging.
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
_Built with β€οΈ by [W.A.H. ISHAN](https://github.com/WAH-ISHAN)._
|
package/package.json
CHANGED
package/src/analyzer.js
CHANGED
|
@@ -3,12 +3,24 @@ const { glob } = require('glob');
|
|
|
3
3
|
const parser = require('@babel/parser');
|
|
4
4
|
const traverse = require('@babel/traverse').default;
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* Converts a string to TitleCase, which is suitable for model and controller names.
|
|
8
|
+
* e.g., 'user-orders' -> 'UserOrders'
|
|
9
|
+
* @param {string} str The input string.
|
|
10
|
+
* @returns {string} The TitleCased string.
|
|
11
|
+
*/
|
|
6
12
|
function toTitleCase(str) {
|
|
7
13
|
if (!str) return 'Default';
|
|
8
|
-
return str.replace(
|
|
14
|
+
return str.replace(/-_(\w)/g, g => g[1].toUpperCase()) // handle snake_case and kebab-case
|
|
15
|
+
.replace(/\w\S*/g, txt => txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase())
|
|
9
16
|
.replace(/[^a-zA-Z0-9]/g, '');
|
|
10
17
|
}
|
|
11
18
|
|
|
19
|
+
/**
|
|
20
|
+
* Analyzes frontend source files to find API endpoints and their details.
|
|
21
|
+
* @param {string} srcPath The path to the frontend source directory.
|
|
22
|
+
* @returns {Promise<Array<object>>} A promise that resolves to an array of endpoint objects.
|
|
23
|
+
*/
|
|
12
24
|
async function analyzeFrontend(srcPath) {
|
|
13
25
|
if (!fs.existsSync(srcPath)) {
|
|
14
26
|
throw new Error(`The source directory '${srcPath}' does not exist.`);
|
|
@@ -21,39 +33,92 @@ async function analyzeFrontend(srcPath) {
|
|
|
21
33
|
const code = await fs.readFile(file, 'utf-8');
|
|
22
34
|
try {
|
|
23
35
|
const ast = parser.parse(code, { sourceType: 'module', plugins: ['jsx', 'typescript'] });
|
|
36
|
+
|
|
24
37
|
traverse(ast, {
|
|
25
38
|
CallExpression(path) {
|
|
39
|
+
// We are only interested in 'fetch' calls
|
|
26
40
|
if (path.node.callee.name !== 'fetch') return;
|
|
41
|
+
|
|
27
42
|
const urlNode = path.node.arguments[0];
|
|
28
43
|
|
|
29
44
|
let urlValue;
|
|
30
45
|
if (urlNode.type === 'StringLiteral') {
|
|
31
46
|
urlValue = urlNode.value;
|
|
32
47
|
} else if (urlNode.type === 'TemplateLiteral' && urlNode.quasis.length > 0) {
|
|
33
|
-
|
|
48
|
+
// Reconstruct path for dynamic URLs like `/api/users/${id}` -> `/api/users/{id}`
|
|
49
|
+
urlValue = urlNode.quasis.map((q, i) => {
|
|
50
|
+
return q.value.raw + (urlNode.expressions[i] ? `{${urlNode.expressions[i].name || 'id'}}` : '');
|
|
51
|
+
}).join('');
|
|
34
52
|
}
|
|
35
53
|
|
|
54
|
+
// Only process API calls that start with '/api/'
|
|
36
55
|
if (!urlValue || !urlValue.startsWith('/api/')) return;
|
|
37
56
|
|
|
38
57
|
let method = 'GET';
|
|
58
|
+
let schemaFields = null;
|
|
39
59
|
|
|
40
60
|
const optionsNode = path.node.arguments[1];
|
|
41
61
|
if (optionsNode && optionsNode.type === 'ObjectExpression') {
|
|
62
|
+
// Find the HTTP method
|
|
42
63
|
const methodProp = optionsNode.properties.find(p => p.key.name === 'method');
|
|
43
64
|
if (methodProp && methodProp.value.type === 'StringLiteral') {
|
|
44
65
|
method = methodProp.value.value.toUpperCase();
|
|
45
66
|
}
|
|
67
|
+
|
|
68
|
+
// --- NEW LOGIC: Analyze the 'body' for POST/PUT requests ---
|
|
69
|
+
if (method === 'POST' || method === 'PUT') {
|
|
70
|
+
const bodyProp = optionsNode.properties.find(p => p.key.name === 'body');
|
|
71
|
+
|
|
72
|
+
// Check if body is wrapped in JSON.stringify
|
|
73
|
+
if (bodyProp && bodyProp.value.callee && bodyProp.value.callee.name === 'JSON.stringify') {
|
|
74
|
+
const dataObjectNode = bodyProp.value.arguments[0];
|
|
75
|
+
|
|
76
|
+
// This is a simplified analysis assuming the object is defined inline.
|
|
77
|
+
// A more robust solution would trace variables back to their definition.
|
|
78
|
+
if (dataObjectNode.type === 'ObjectExpression') {
|
|
79
|
+
schemaFields = {};
|
|
80
|
+
dataObjectNode.properties.forEach(prop => {
|
|
81
|
+
const key = prop.key.name;
|
|
82
|
+
const valueNode = prop.value;
|
|
83
|
+
|
|
84
|
+
// Infer Mongoose schema type based on the value's literal type
|
|
85
|
+
if (valueNode.type === 'StringLiteral') {
|
|
86
|
+
schemaFields[key] = 'String';
|
|
87
|
+
} else if (valueNode.type === 'NumericLiteral') {
|
|
88
|
+
schemaFields[key] = 'Number';
|
|
89
|
+
} else if (valueNode.type === 'BooleanLiteral') {
|
|
90
|
+
schemaFields[key] = 'Boolean';
|
|
91
|
+
} else {
|
|
92
|
+
// Default to String if the type is complex or a variable
|
|
93
|
+
schemaFields[key] = 'String';
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
46
99
|
}
|
|
47
100
|
|
|
101
|
+
// Generate a clean controller name (e.g., /api/user-orders -> UserOrders)
|
|
48
102
|
const controllerName = toTitleCase(urlValue.split('/')[2]);
|
|
49
103
|
const key = `${method}:${urlValue}`;
|
|
104
|
+
|
|
105
|
+
// Avoid adding duplicate endpoints
|
|
50
106
|
if (!endpoints.has(key)) {
|
|
51
|
-
endpoints.set(key, {
|
|
107
|
+
endpoints.set(key, {
|
|
108
|
+
path: urlValue,
|
|
109
|
+
method,
|
|
110
|
+
controllerName,
|
|
111
|
+
schemaFields // This will be null for GET/DELETE, and an object for POST/PUT
|
|
112
|
+
});
|
|
52
113
|
}
|
|
53
114
|
},
|
|
54
115
|
});
|
|
55
|
-
} catch (e) {
|
|
116
|
+
} catch (e) {
|
|
117
|
+
// Ignore files that babel can't parse (e.g., CSS-in-JS files)
|
|
118
|
+
}
|
|
56
119
|
}
|
|
120
|
+
|
|
121
|
+
// Return all found endpoints as an array
|
|
57
122
|
return Array.from(endpoints.values());
|
|
58
123
|
}
|
|
59
124
|
|
package/src/generators/node.js
CHANGED
|
@@ -9,7 +9,7 @@ async function generateNodeProject(options) {
|
|
|
9
9
|
const { projectDir, projectName, frontendSrcDir } = options;
|
|
10
10
|
|
|
11
11
|
try {
|
|
12
|
-
// --- Step 1: Analyze Frontend ---
|
|
12
|
+
// --- Step 1: Analyze Frontend to get Endpoints and Schema Info ---
|
|
13
13
|
console.log(chalk.blue(' -> Analyzing frontend for API endpoints...'));
|
|
14
14
|
const endpoints = await analyzeFrontend(frontendSrcDir);
|
|
15
15
|
if (endpoints.length > 0) {
|
|
@@ -18,58 +18,101 @@ async function generateNodeProject(options) {
|
|
|
18
18
|
console.log(chalk.yellow(' -> No API endpoints found. A basic project will be created.'));
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
// --- Step 2:
|
|
21
|
+
// --- Step 2: Identify which Database Models to Generate ---
|
|
22
|
+
const modelsToGenerate = new Map();
|
|
23
|
+
endpoints.forEach(ep => {
|
|
24
|
+
// If an endpoint has schemaFields and a valid controllerName, add it to our map.
|
|
25
|
+
if (ep.schemaFields && ep.controllerName !== 'Default' && !modelsToGenerate.has(ep.controllerName)) {
|
|
26
|
+
modelsToGenerate.set(ep.controllerName, ep.schemaFields);
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// --- Step 3: Scaffold Base Project Structure & Files ---
|
|
22
31
|
console.log(chalk.blue(' -> Scaffolding Node.js (Express + TS) project...'));
|
|
23
32
|
|
|
24
|
-
//
|
|
25
|
-
const baseDir = getTemplatePath('node-ts-express/base');
|
|
26
|
-
const serverTemplatePath = path.join(baseDir, 'server.ts');
|
|
27
|
-
const tsconfigTemplatePath = path.join(baseDir, 'tsconfig.json');
|
|
28
|
-
|
|
33
|
+
// Create the main source directory
|
|
29
34
|
const destSrcDir = path.join(projectDir, 'src');
|
|
30
|
-
const serverDestPath = path.join(destSrcDir, 'server.ts');
|
|
31
|
-
const tsconfigDestPath = path.join(projectDir, 'tsconfig.json');
|
|
32
|
-
|
|
33
|
-
// Ensure destination directory exists
|
|
34
35
|
await fs.ensureDir(destSrcDir);
|
|
35
36
|
|
|
36
|
-
// Copy base files
|
|
37
|
-
await fs.copy(
|
|
38
|
-
await fs.copy(
|
|
39
|
-
|
|
40
|
-
console.log(chalk.gray(' -> Base server.ts copied.'));
|
|
37
|
+
// Copy static base files
|
|
38
|
+
await fs.copy(getTemplatePath('node-ts-express/base/server.ts'), path.join(destSrcDir, 'server.ts'));
|
|
39
|
+
await fs.copy(getTemplatePath('node-ts-express/base/tsconfig.json'), path.join(projectDir, 'tsconfig.json'));
|
|
41
40
|
|
|
42
|
-
// --- Step
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
{ projectName }
|
|
41
|
+
// --- Step 4: Generate Dynamic Files (package.json, Models, Controllers) ---
|
|
42
|
+
|
|
43
|
+
// Prepare package.json content (in memory)
|
|
44
|
+
const packageJsonContent = JSON.parse(
|
|
45
|
+
await ejs.renderFile(getTemplatePath('node-ts-express/partials/package.json.ejs'), { projectName })
|
|
47
46
|
);
|
|
47
|
+
|
|
48
|
+
// Conditionally add Mongoose if we are generating models
|
|
49
|
+
if (modelsToGenerate.size > 0) {
|
|
50
|
+
console.log(chalk.gray(' -> Preparing to add Mongoose to dependencies...'));
|
|
51
|
+
packageJsonContent.dependencies['mongoose'] = '^7.5.0'; // Use a recent, stable version
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Write the final package.json to the disk
|
|
55
|
+
await fs.writeJson(path.join(projectDir, 'package.json'), packageJsonContent, { spaces: 2 });
|
|
56
|
+
|
|
57
|
+
// Generate Model and Controller files if any were found
|
|
58
|
+
if (modelsToGenerate.size > 0) {
|
|
59
|
+
console.log(chalk.blue(' -> Generating database models and controllers...'));
|
|
60
|
+
await fs.ensureDir(path.join(destSrcDir, 'models'));
|
|
61
|
+
await fs.ensureDir(path.join(destSrcDir, 'controllers'));
|
|
62
|
+
|
|
63
|
+
for (const [modelName, schema] of modelsToGenerate.entries()) {
|
|
64
|
+
// Generate Model File (e.g., models/User.model.ts)
|
|
65
|
+
await renderAndWrite(
|
|
66
|
+
getTemplatePath('node-ts-express/partials/Model.ts.ejs'),
|
|
67
|
+
path.join(destSrcDir, 'models', `${modelName}.model.ts`),
|
|
68
|
+
{ modelName, schema }
|
|
69
|
+
);
|
|
70
|
+
// Generate Controller File (e.g., controllers/User.controller.ts)
|
|
71
|
+
await renderAndWrite(
|
|
72
|
+
getTemplatePath('node-ts-express/partials/Controller.ts.ejs'),
|
|
73
|
+
path.join(destSrcDir, 'controllers', `${modelName}.controller.ts`),
|
|
74
|
+
{ modelName }
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// --- Step 5: Generate the Smart Route File ---
|
|
80
|
+
console.log(chalk.gray(' -> Generating dynamic routes...'));
|
|
48
81
|
await renderAndWrite(
|
|
49
82
|
getTemplatePath('node-ts-express/partials/routes.ts.ejs'),
|
|
50
83
|
path.join(destSrcDir, 'routes.ts'),
|
|
51
|
-
{ endpoints }
|
|
84
|
+
{ endpoints } // Pass all endpoints to the template
|
|
52
85
|
);
|
|
53
86
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
// --- Step 4: Modify the copied server.ts ---
|
|
57
|
-
// Check if the file exists before reading
|
|
58
|
-
if (!await fs.pathExists(serverDestPath)) {
|
|
59
|
-
throw new Error(`Critical error: server.ts was not found at ${serverDestPath} after copy.`);
|
|
60
|
-
}
|
|
61
|
-
|
|
87
|
+
// --- Step 6: Inject Routes into the Main Server File ---
|
|
88
|
+
const serverDestPath = path.join(destSrcDir, 'server.ts');
|
|
62
89
|
let serverFileContent = await fs.readFile(serverDestPath, 'utf-8');
|
|
63
|
-
|
|
64
|
-
|
|
90
|
+
|
|
91
|
+
let dbConnectionCode = '';
|
|
92
|
+
if (modelsToGenerate.size > 0) {
|
|
93
|
+
dbConnectionCode = `
|
|
94
|
+
// --- Database Connection ---
|
|
95
|
+
import mongoose from 'mongoose';
|
|
96
|
+
const MONGO_URI = process.env.MONGO_URI || 'mongodb://localhost:27017/${projectName}';
|
|
97
|
+
mongoose.connect(MONGO_URI)
|
|
98
|
+
.then(() => console.log('MongoDB Connected...'))
|
|
99
|
+
.catch(err => console.error('MongoDB Connection Error:', err));
|
|
100
|
+
// -------------------------
|
|
101
|
+
`;
|
|
102
|
+
}
|
|
65
103
|
|
|
66
|
-
|
|
104
|
+
// Inject DB connection code after dotenv.config() and route loader
|
|
105
|
+
serverFileContent = serverFileContent
|
|
106
|
+
.replace("dotenv.config();", `dotenv.config();\n${dbConnectionCode}`)
|
|
107
|
+
.replace('// INJECT:ROUTES', `import apiRoutes from './routes';\napp.use(apiRoutes);`);
|
|
108
|
+
|
|
109
|
+
await fs.writeFile(serverDestPath, serverFileContent);
|
|
67
110
|
|
|
68
|
-
// --- Step
|
|
69
|
-
console.log(chalk.magenta(' -> Installing dependencies (npm install)...'));
|
|
111
|
+
// --- Step 7: Install All Dependencies at Once ---
|
|
112
|
+
console.log(chalk.magenta(' -> Installing dependencies (npm install)... This might take a moment.'));
|
|
70
113
|
await execa('npm', ['install'], { cwd: projectDir });
|
|
71
114
|
|
|
72
|
-
// --- Step
|
|
115
|
+
// --- Step 8: Generate README ---
|
|
73
116
|
await renderAndWrite(
|
|
74
117
|
getTemplatePath('node-ts-express/partials/README.md.ejs'),
|
|
75
118
|
path.join(projectDir, 'README.md'),
|
|
@@ -77,7 +120,8 @@ async function generateNodeProject(options) {
|
|
|
77
120
|
);
|
|
78
121
|
|
|
79
122
|
} catch (error) {
|
|
80
|
-
// Re-throw the error
|
|
123
|
+
// Re-throw the error so it can be caught by the main CLI handler in index.js
|
|
124
|
+
// This allows for centralized error message display and cleanup.
|
|
81
125
|
throw error;
|
|
82
126
|
}
|
|
83
127
|
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// Auto-generated by create-backlist on <%= new Date().toISOString() %>
|
|
2
|
+
import { Request, Response } from 'express';
|
|
3
|
+
import <%= modelName %>, { I<%= modelName %> } from '../models/<%= modelName %>.model';
|
|
4
|
+
|
|
5
|
+
// @desc Create a new <%= modelName %>
|
|
6
|
+
export const create<%= modelName %> = async (req: Request, res: Response) => {
|
|
7
|
+
try {
|
|
8
|
+
const newDoc = new <%= modelName %>(req.body);
|
|
9
|
+
await newDoc.save();
|
|
10
|
+
res.status(201).json(newDoc);
|
|
11
|
+
} catch (error) {
|
|
12
|
+
res.status(500).json({ message: 'Error creating document', error });
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
// @desc Get all <%= modelName %>s
|
|
17
|
+
export const getAll<%= modelName %>s = async (req: Request, res: Response) => {
|
|
18
|
+
try {
|
|
19
|
+
const docs = await <%= modelName %>.find();
|
|
20
|
+
res.status(200).json(docs);
|
|
21
|
+
} catch (error) {
|
|
22
|
+
res.status(500).json({ message: 'Error fetching documents', error });
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
// @desc Get a single <%= modelName %> by ID
|
|
27
|
+
export const get<%= modelName %>ById = async (req: Request, res: Response) => {
|
|
28
|
+
try {
|
|
29
|
+
const doc = await <%= modelName %>.findById(req.params.id);
|
|
30
|
+
if (!doc) return res.status(404).json({ message: 'Document not found' });
|
|
31
|
+
res.status(200).json(doc);
|
|
32
|
+
} catch (error) {
|
|
33
|
+
res.status(500).json({ message: 'Error fetching document', error });
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
// @desc Update a <%= modelName %> by ID
|
|
38
|
+
export const update<%= modelName %>ById = async (req: Request, res: Response) => {
|
|
39
|
+
try {
|
|
40
|
+
const doc = await <%= modelName %>.findByIdAndUpdate(req.params.id, req.body, { new: true, runValidators: true });
|
|
41
|
+
if (!doc) return res.status(404).json({ message: 'Document not found' });
|
|
42
|
+
res.status(200).json(doc);
|
|
43
|
+
} catch (error) {
|
|
44
|
+
res.status(500).json({ message: 'Error updating document', error });
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
// @desc Delete a <%= modelName %> by ID
|
|
49
|
+
export const delete<%= modelName %>ById = async (req: Request, res: Response) => {
|
|
50
|
+
try {
|
|
51
|
+
const doc = await <%= modelName %>.findByIdAndDelete(req.params.id);
|
|
52
|
+
if (!doc) return res.status(404).json({ message: 'Document not found' });
|
|
53
|
+
res.status(200).json({ message: 'Document deleted successfully' });
|
|
54
|
+
} catch (error) {
|
|
55
|
+
res.status(500).json({ message: 'Error deleting document', error });
|
|
56
|
+
}
|
|
57
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// Auto-generated by create-backlist on <%= new Date().toISOString() %>
|
|
2
|
+
import mongoose, { Schema, Document } from 'mongoose';
|
|
3
|
+
|
|
4
|
+
// Define the interface for the Document
|
|
5
|
+
export interface I<%= modelName %> extends Document {
|
|
6
|
+
<% Object.keys(schema).forEach(key => { %>
|
|
7
|
+
<%= key %>: <%= schema[key].toLowerCase() %>;
|
|
8
|
+
<% }); %>
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// Define the Mongoose Schema
|
|
12
|
+
const <%= modelName %>Schema: Schema = new Schema({
|
|
13
|
+
<% Object.keys(schema).forEach(key => { %>
|
|
14
|
+
<%= key %>: {
|
|
15
|
+
type: <%= schema[key] %>,
|
|
16
|
+
// TODO: Add 'required', 'unique', etc. here
|
|
17
|
+
},
|
|
18
|
+
<% }); %>
|
|
19
|
+
}, { timestamps: true });
|
|
20
|
+
|
|
21
|
+
// Create and export the Model
|
|
22
|
+
export default mongoose.model<I<%= modelName %>>('<%= modelName %>', <%= modelName %>Schema);
|
|
@@ -1,23 +1,60 @@
|
|
|
1
|
-
// Auto-generated by
|
|
1
|
+
// Auto-generated by create-backlist on <%= new Date().toISOString() %>
|
|
2
2
|
import { Router, Request, Response } from 'express';
|
|
3
|
+
<%# Create a unique set of controller names from the endpoints array %>
|
|
4
|
+
<% const controllersToImport = new Set(endpoints.map(ep => ep.controllerName).filter(name => name !== 'Default')); %>
|
|
5
|
+
|
|
6
|
+
// Import all the generated controllers
|
|
7
|
+
<% for (const controller of controllersToImport) { %>
|
|
8
|
+
import * as <%= controller %>Controller from '../controllers/<%= controller %>.controller';
|
|
9
|
+
<% } %>
|
|
3
10
|
|
|
4
11
|
const router = Router();
|
|
5
12
|
|
|
6
|
-
|
|
7
|
-
// No API endpoints were detected in the frontend code. Add your routes here.
|
|
8
|
-
<% } else { %>
|
|
13
|
+
<%# Loop through each endpoint found by the analyzer %>
|
|
9
14
|
<% endpoints.forEach(endpoint => { %>
|
|
10
|
-
|
|
11
|
-
|
|
15
|
+
<%
|
|
16
|
+
// Convert URL path for Express router (e.g., /api/users/{id} -> /users/:id)
|
|
17
|
+
const expressPath = endpoint.path.replace('/api', '').replace(/{(\w+)}/g, ':$1');
|
|
18
|
+
const controllerName = endpoint.controllerName;
|
|
19
|
+
let handlerFunction;
|
|
20
|
+
|
|
21
|
+
// --- LOGIC TO MAP ENDPOINT TO A CRUD CONTROLLER FUNCTION ---
|
|
22
|
+
// This logic assumes a standard RESTful API structure.
|
|
23
|
+
|
|
24
|
+
if (controllerName !== 'Default') {
|
|
25
|
+
if (endpoint.method === 'POST' && !expressPath.includes(':')) {
|
|
26
|
+
// e.g., POST /users -> create a new user
|
|
27
|
+
handlerFunction = `${controllerName}Controller.create${controllerName}`;
|
|
28
|
+
} else if (endpoint.method === 'GET' && !expressPath.includes(':')) {
|
|
29
|
+
// e.g., GET /users -> get all users
|
|
30
|
+
handlerFunction = `${controllerName}Controller.getAll${controllerName}s`;
|
|
31
|
+
} else if (endpoint.method === 'GET' && expressPath.includes(':')) {
|
|
32
|
+
// e.g., GET /users/:id -> get a single user by ID
|
|
33
|
+
handlerFunction = `${controllerName}Controller.get${controllerName}ById`;
|
|
34
|
+
} else if (endpoint.method === 'PUT' && expressPath.includes(':')) {
|
|
35
|
+
// e.g., PUT /users/:id -> update a user by ID
|
|
36
|
+
handlerFunction = `${controllerName}Controller.update${controllerName}ById`;
|
|
37
|
+
} else if (endpoint.method === 'DELETE' && expressPath.includes(':')) {
|
|
38
|
+
// e.g., DELETE /users/:id -> delete a user by ID
|
|
39
|
+
handlerFunction = `${controllerName}Controller.delete${controllerName}ById`;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// If no specific CRUD function matches, or if it's a default/unhandled route,
|
|
44
|
+
// create a simple placeholder function.
|
|
45
|
+
if (!handlerFunction) {
|
|
46
|
+
handlerFunction = `(req: Request, res: Response) => {
|
|
47
|
+
// TODO: Implement logic for this custom endpoint
|
|
48
|
+
res.status(501).json({ message: 'Handler not implemented for <%= endpoint.method %> <%= expressPath %>' });
|
|
49
|
+
}`;
|
|
50
|
+
}
|
|
51
|
+
%>
|
|
12
52
|
/**
|
|
13
|
-
*
|
|
53
|
+
* Route for <%= endpoint.method.toUpperCase() %> <%= endpoint.path %>
|
|
54
|
+
* Mapped to: <%- handlerFunction.includes('=>') ? 'Inline Handler' : handlerFunction %>
|
|
14
55
|
*/
|
|
15
|
-
router.<%= endpoint.method.toLowerCase() %>('<%- expressPath %>',
|
|
16
|
-
|
|
17
|
-
// TODO: Implement your logic here.
|
|
18
|
-
res.status(200).json({ message: 'Auto-generated response for <%= endpoint.path %>' });
|
|
19
|
-
});
|
|
56
|
+
router.<%= endpoint.method.toLowerCase() %>('<%- expressPath %>', <%- handlerFunction %>);
|
|
57
|
+
|
|
20
58
|
<% }); %>
|
|
21
|
-
<% } %>
|
|
22
59
|
|
|
23
60
|
export default router;
|