create-backlist 4.0.0 → 5.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/bin/index.js +71 -15
- package/package.json +5 -3
- package/src/generators/dotnet.js +117 -36
- package/src/generators/java.js +100 -0
- package/src/generators/node.js +122 -70
- package/src/generators/python.js +75 -0
- package/src/templates/java-spring/partials/Controller.java.ejs +61 -0
- package/src/templates/java-spring/partials/Entity.java.ejs +24 -0
- package/src/templates/java-spring/partials/Repository.java.ejs +12 -0
- package/src/templates/node-ts-express/partials/ApiDocs.ts.ejs +33 -0
- package/src/templates/node-ts-express/partials/App.test.ts.ejs +38 -0
- package/src/templates/node-ts-express/partials/Controller.ts.ejs +124 -63
- package/src/templates/node-ts-express/partials/DbContext.cs.ejs +15 -0
- package/src/templates/node-ts-express/partials/Dockerfile.ejs +33 -0
- package/src/templates/node-ts-express/partials/Model.cs.ejs +18 -0
- package/src/templates/node-ts-express/partials/PrismaController.ts.ejs +66 -0
- package/src/templates/node-ts-express/partials/PrismaSchema.prisma.ejs +28 -0
- package/src/templates/node-ts-express/partials/docker-compose.yml.ejs +47 -0
- package/src/templates/python-fastapi/main.py.ejs +38 -0
- package/src/templates/python-fastapi/requirements.txt.ejs +4 -0
- package/src/templates/python-fastapi/routes.py.ejs +42 -0
package/src/generators/node.js
CHANGED
|
@@ -6,76 +6,94 @@ const { analyzeFrontend } = require('../analyzer');
|
|
|
6
6
|
const { renderAndWrite, getTemplatePath } = require('./template');
|
|
7
7
|
|
|
8
8
|
async function generateNodeProject(options) {
|
|
9
|
-
//
|
|
10
|
-
const { projectDir, projectName, frontendSrcDir, addAuth, addSeeder } = options;
|
|
9
|
+
// v5.0: Destructure all new options
|
|
10
|
+
const { projectDir, projectName, frontendSrcDir, dbType, addAuth, addSeeder, extraFeatures = [] } = options;
|
|
11
|
+
const port = 8000;
|
|
11
12
|
|
|
12
13
|
try {
|
|
13
|
-
// --- Step 1: Analyze Frontend
|
|
14
|
+
// --- Step 1: Analyze Frontend ---
|
|
14
15
|
console.log(chalk.blue(' -> Analyzing frontend for API endpoints...'));
|
|
15
16
|
const endpoints = await analyzeFrontend(frontendSrcDir);
|
|
16
|
-
if (endpoints.length > 0) {
|
|
17
|
-
|
|
18
|
-
} else {
|
|
19
|
-
console.log(chalk.yellow(' -> No API endpoints found. A basic project will be created.'));
|
|
20
|
-
}
|
|
17
|
+
if (endpoints.length > 0) console.log(chalk.green(` -> Found ${endpoints.length} endpoints.`));
|
|
18
|
+
else console.log(chalk.yellow(' -> No API endpoints found. A basic project will be created.'));
|
|
21
19
|
|
|
22
|
-
// --- Step 2: Identify
|
|
20
|
+
// --- Step 2: Identify Models to Generate ---
|
|
23
21
|
const modelsToGenerate = new Map();
|
|
24
22
|
endpoints.forEach(ep => {
|
|
25
23
|
if (ep.schemaFields && ep.controllerName !== 'Default' && !modelsToGenerate.has(ep.controllerName)) {
|
|
26
|
-
modelsToGenerate.set(ep.controllerName, ep.schemaFields);
|
|
24
|
+
modelsToGenerate.set(ep.controllerName, { name: ep.controllerName, fields: Object.entries(ep.schemaFields).map(([key, type]) => ({ name: key, type, isUnique: key === 'email' })) });
|
|
27
25
|
}
|
|
28
26
|
});
|
|
29
|
-
|
|
30
27
|
if (addAuth && !modelsToGenerate.has('User')) {
|
|
31
28
|
console.log(chalk.yellow(' -> Authentication requires a "User" model. Creating a default one.'));
|
|
32
|
-
modelsToGenerate.set('User', { name: 'String', email: 'String', password: 'String' });
|
|
29
|
+
modelsToGenerate.set('User', { name: 'User', fields: [{ name: 'name', type: 'String' }, { name: 'email', type: 'String', isUnique: true }, { name: 'password', type: 'String' }] });
|
|
33
30
|
}
|
|
34
31
|
|
|
35
|
-
// --- Step 3:
|
|
36
|
-
console.log(chalk.blue(' -> Scaffolding Node.js
|
|
32
|
+
// --- Step 3: Base Scaffolding ---
|
|
33
|
+
console.log(chalk.blue(' -> Scaffolding Node.js project...'));
|
|
37
34
|
const destSrcDir = path.join(projectDir, 'src');
|
|
38
35
|
await fs.ensureDir(destSrcDir);
|
|
39
36
|
await fs.copy(getTemplatePath('node-ts-express/base/server.ts'), path.join(destSrcDir, 'server.ts'));
|
|
40
37
|
await fs.copy(getTemplatePath('node-ts-express/base/tsconfig.json'), path.join(projectDir, 'tsconfig.json'));
|
|
41
38
|
|
|
42
|
-
// --- Step 4: Prepare and Write package.json
|
|
43
|
-
const packageJsonContent = JSON.parse(
|
|
44
|
-
|
|
45
|
-
);
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
packageJsonContent.
|
|
39
|
+
// --- Step 4: Prepare and Write package.json ---
|
|
40
|
+
const packageJsonContent = JSON.parse(await ejs.renderFile(getTemplatePath('node-ts-express/partials/package.json.ejs'), { projectName }));
|
|
41
|
+
|
|
42
|
+
if (dbType === 'mongoose') packageJsonContent.dependencies['mongoose'] = '^7.6.3';
|
|
43
|
+
if (dbType === 'prisma') {
|
|
44
|
+
packageJsonContent.dependencies['@prisma/client'] = '^5.6.0';
|
|
45
|
+
packageJsonContent.devDependencies['prisma'] = '^5.6.0';
|
|
46
|
+
packageJsonContent.prisma = { seed: `ts-node ${addSeeder ? 'scripts/seeder.ts' : 'prisma/seed.ts'}` };
|
|
49
47
|
}
|
|
50
48
|
if (addAuth) {
|
|
51
49
|
packageJsonContent.dependencies['jsonwebtoken'] = '^9.0.2';
|
|
52
50
|
packageJsonContent.dependencies['bcryptjs'] = '^2.4.3';
|
|
53
|
-
packageJsonContent.devDependencies['@types/jsonwebtoken'] = '^9.0.
|
|
54
|
-
packageJsonContent.devDependencies['@types/bcryptjs'] = '^2.4.
|
|
51
|
+
packageJsonContent.devDependencies['@types/jsonwebtoken'] = '^9.0.5';
|
|
52
|
+
packageJsonContent.devDependencies['@types/bcryptjs'] = '^2.4.6';
|
|
55
53
|
}
|
|
56
|
-
// v4.0: Add seeder dependencies and scripts
|
|
57
54
|
if (addSeeder) {
|
|
58
|
-
packageJsonContent.devDependencies['@faker-js/faker'] = '^8.
|
|
59
|
-
|
|
60
|
-
packageJsonContent.
|
|
61
|
-
packageJsonContent.scripts['
|
|
62
|
-
|
|
55
|
+
packageJsonContent.devDependencies['@faker-js/faker'] = '^8.3.1';
|
|
56
|
+
if (!packageJsonContent.dependencies['chalk']) packageJsonContent.dependencies['chalk'] = '^4.1.2';
|
|
57
|
+
packageJsonContent.scripts['seed'] = `ts-node scripts/seeder.ts`;
|
|
58
|
+
packageJsonContent.scripts['destroy'] = `ts-node scripts/seeder.ts -d`;
|
|
59
|
+
}
|
|
60
|
+
if (extraFeatures.includes('testing')) {
|
|
61
|
+
packageJsonContent.devDependencies['jest'] = '^29.7.0';
|
|
62
|
+
packageJsonContent.devDependencies['supertest'] = '^6.3.3';
|
|
63
|
+
packageJsonContent.devDependencies['@types/jest'] = '^29.5.10';
|
|
64
|
+
packageJsonContent.devDependencies['@types/supertest'] = '^2.0.16';
|
|
65
|
+
packageJsonContent.devDependencies['ts-jest'] = '^29.1.1';
|
|
66
|
+
packageJsonContent.scripts['test'] = 'jest --detectOpenHandles --forceExit';
|
|
67
|
+
}
|
|
68
|
+
if (extraFeatures.includes('swagger')) {
|
|
69
|
+
packageJsonContent.dependencies['swagger-ui-express'] = '^5.0.0';
|
|
70
|
+
packageJsonContent.dependencies['swagger-jsdoc'] = '^6.2.8';
|
|
71
|
+
packageJsonContent.devDependencies['@types/swagger-ui-express'] = '^4.1.6';
|
|
63
72
|
}
|
|
64
73
|
await fs.writeJson(path.join(projectDir, 'package.json'), packageJsonContent, { spaces: 2 });
|
|
65
74
|
|
|
66
|
-
// --- Step 5
|
|
75
|
+
// --- Step 5: Generate DB-specific files & Controllers ---
|
|
67
76
|
if (modelsToGenerate.size > 0) {
|
|
68
|
-
console.log(chalk.blue(' -> Generating database models and controllers...'));
|
|
69
|
-
await fs.ensureDir(path.join(destSrcDir, 'models'));
|
|
70
77
|
await fs.ensureDir(path.join(destSrcDir, 'controllers'));
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
78
|
+
if (dbType === 'mongoose') {
|
|
79
|
+
console.log(chalk.blue(' -> Generating Mongoose models and controllers...'));
|
|
80
|
+
await fs.ensureDir(path.join(destSrcDir, 'models'));
|
|
81
|
+
for (const [modelName, modelData] of modelsToGenerate.entries()) {
|
|
82
|
+
const schema = modelData.fields.reduce((acc, field) => { acc[field.name] = field.type; return acc; }, {});
|
|
83
|
+
await renderAndWrite(getTemplatePath('node-ts-express/partials/Model.ts.ejs'), path.join(destSrcDir, 'models', `${modelName}.model.ts`), { modelName, schema });
|
|
84
|
+
await renderAndWrite(getTemplatePath('node-ts-express/partials/Controller.ts.ejs'), path.join(destSrcDir, 'controllers', `${modelName}.controller.ts`), { modelName });
|
|
85
|
+
}
|
|
86
|
+
} else if (dbType === 'prisma') {
|
|
87
|
+
console.log(chalk.blue(' -> Generating Prisma schema and controllers...'));
|
|
88
|
+
await fs.ensureDir(path.join(projectDir, 'prisma'));
|
|
89
|
+
await renderAndWrite(getTemplatePath('node-ts-express/partials/PrismaSchema.prisma.ejs'), path.join(projectDir, 'prisma', 'schema.prisma'), { modelsToGenerate: Array.from(modelsToGenerate.values()) });
|
|
90
|
+
for (const [modelName] of modelsToGenerate.entries()) {
|
|
91
|
+
await renderAndWrite(getTemplatePath('node-ts-express/partials/PrismaController.ts.ejs'), path.join(destSrcDir, 'controllers', `${modelName}.controller.ts`), { modelName });
|
|
74
92
|
}
|
|
75
|
-
await renderAndWrite(getTemplatePath('node-ts-express/partials/Model.ts.ejs'), path.join(destSrcDir, 'models', `${modelName}.model.ts`), { modelName, schema });
|
|
76
|
-
await renderAndWrite(getTemplatePath('node-ts-express/partials/Controller.ts.ejs'), path.join(destSrcDir, 'controllers', `${modelName}.controller.ts`), { modelName });
|
|
77
93
|
}
|
|
78
94
|
}
|
|
95
|
+
|
|
96
|
+
// --- Step 6: Generate Authentication Boilerplate ---
|
|
79
97
|
if (addAuth) {
|
|
80
98
|
console.log(chalk.blue(' -> Generating authentication boilerplate...'));
|
|
81
99
|
await fs.ensureDir(path.join(destSrcDir, 'routes'));
|
|
@@ -83,60 +101,94 @@ async function generateNodeProject(options) {
|
|
|
83
101
|
await renderAndWrite(getTemplatePath('node-ts-express/partials/Auth.controller.ts.ejs'), path.join(destSrcDir, 'controllers', 'Auth.controller.ts'), {});
|
|
84
102
|
await renderAndWrite(getTemplatePath('node-ts-express/partials/Auth.routes.ts.ejs'), path.join(destSrcDir, 'routes', 'Auth.routes.ts'), {});
|
|
85
103
|
await renderAndWrite(getTemplatePath('node-ts-express/partials/Auth.middleware.ts.ejs'), path.join(destSrcDir, 'middleware', 'Auth.middleware.ts'), {});
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
104
|
+
|
|
105
|
+
if (dbType === 'mongoose') {
|
|
106
|
+
const userModelPath = path.join(destSrcDir, 'models', 'User.model.ts');
|
|
107
|
+
if (await fs.pathExists(userModelPath)) {
|
|
108
|
+
let userModelContent = await fs.readFile(userModelPath, 'utf-8');
|
|
109
|
+
if (!userModelContent.includes('bcryptjs')) {
|
|
110
|
+
userModelContent = userModelContent.replace(`import mongoose, { Schema, Document } from 'mongoose';`, `import mongoose, { Schema, Document } from 'mongoose';\nimport bcrypt from 'bcryptjs';`);
|
|
111
|
+
const preSaveHook = `\n// Hash password before saving\nUserSchema.pre('save', async function(next) {\n if (!this.isModified('password')) {\n return next();\n }\n const salt = await bcrypt.genSalt(10);\n this.password = await bcrypt.hash(this.password, salt);\n next();\n});\n`;
|
|
112
|
+
userModelContent = userModelContent.replace(`// Create and export the Model`, `${preSaveHook}\n// Create and export the Model`);
|
|
113
|
+
await fs.writeFile(userModelPath, userModelContent);
|
|
114
|
+
}
|
|
95
115
|
}
|
|
96
116
|
}
|
|
97
117
|
}
|
|
98
118
|
|
|
99
|
-
// --- Step 7
|
|
119
|
+
// --- Step 7: Generate Seeder Script ---
|
|
100
120
|
if (addSeeder) {
|
|
101
121
|
console.log(chalk.blue(' -> Generating database seeder script...'));
|
|
102
122
|
await fs.ensureDir(path.join(projectDir, 'scripts'));
|
|
103
|
-
await renderAndWrite(
|
|
104
|
-
getTemplatePath('node-ts-express/partials/Seeder.ts.ejs'),
|
|
105
|
-
path.join(projectDir, 'scripts', 'seeder.ts'),
|
|
106
|
-
{ projectName }
|
|
107
|
-
);
|
|
123
|
+
await renderAndWrite(getTemplatePath('node-ts-express/partials/Seeder.ts.ejs'), path.join(projectDir, 'scripts', 'seeder.ts'), { projectName });
|
|
108
124
|
}
|
|
109
125
|
|
|
110
|
-
// --- Step 8: Generate
|
|
111
|
-
|
|
112
|
-
|
|
126
|
+
// --- Step 8: Generate Extra Features ---
|
|
127
|
+
if (extraFeatures.includes('docker')) {
|
|
128
|
+
console.log(chalk.blue(' -> Generating Docker files...'));
|
|
129
|
+
await renderAndWrite(getTemplatePath('node-ts-express/partials/Dockerfile.ejs'), path.join(projectDir, 'Dockerfile'), { dbType, port });
|
|
130
|
+
await renderAndWrite(getTemplatePath('node-ts-express/partials/docker-compose.yml.ejs'), path.join(projectDir, 'docker-compose.yml'), { projectName, dbType, port });
|
|
131
|
+
}
|
|
132
|
+
if (extraFeatures.includes('swagger')) {
|
|
133
|
+
console.log(chalk.blue(' -> Generating API documentation setup...'));
|
|
134
|
+
await fs.ensureDir(path.join(destSrcDir, 'utils'));
|
|
135
|
+
await renderAndWrite(getTemplatePath('node-ts-express/partials/ApiDocs.ts.ejs'), path.join(destSrcDir, 'utils', 'swagger.ts'), { projectName, port });
|
|
136
|
+
}
|
|
137
|
+
if (extraFeatures.includes('testing')) {
|
|
138
|
+
console.log(chalk.blue(' -> Generating testing boilerplate...'));
|
|
139
|
+
const jestConfig = `/** @type {import('ts-jest').JestConfigWithTsJest} */\nmodule.exports = {\n preset: 'ts-jest',\n testEnvironment: 'node',\n verbose: true,\n};`;
|
|
140
|
+
await fs.writeFile(path.join(projectDir, 'jest.config.js'), jestConfig);
|
|
141
|
+
await fs.ensureDir(path.join(projectDir, 'src', '__tests__'));
|
|
142
|
+
await renderAndWrite(getTemplatePath('node-ts-express/partials/App.test.ts.ejs'), path.join(projectDir, 'src', '__tests__', 'api.test.ts'), { addAuth });
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// --- Step 9: Generate Main Route File & Inject Logic into Server ---
|
|
146
|
+
await renderAndWrite(getTemplatePath('node-ts-express/partials/routes.ts.ejs'), path.join(destSrcDir, 'routes.ts'), { endpoints, addAuth, dbType });
|
|
113
147
|
|
|
114
|
-
// --- Step 9: Inject Logic into Main Server File ---
|
|
115
148
|
let serverFileContent = await fs.readFile(path.join(destSrcDir, 'server.ts'), 'utf-8');
|
|
116
|
-
let dbConnectionCode = '';
|
|
117
|
-
|
|
118
|
-
|
|
149
|
+
let dbConnectionCode = '', swaggerInjector = '', authRoutesInjector = '';
|
|
150
|
+
|
|
151
|
+
if (dbType === 'mongoose') {
|
|
152
|
+
dbConnectionCode = `\n// --- Database Connection ---\nimport mongoose from 'mongoose';\nconst MONGO_URI = process.env.MONGO_URI || 'mongodb://127.0.0.1:27017/${projectName}';\nmongoose.connect(MONGO_URI).then(() => console.log('MongoDB Connected...')).catch(err => console.error(err));\n// -------------------------\n`;
|
|
153
|
+
} else if (dbType === 'prisma') {
|
|
154
|
+
dbConnectionCode = `\nimport { PrismaClient } from '@prisma/client';\nexport const prisma = new PrismaClient();\n`;
|
|
155
|
+
}
|
|
156
|
+
if (extraFeatures.includes('swagger')) {
|
|
157
|
+
swaggerInjector = `\nimport { setupSwagger } from './utils/swagger';\nsetupSwagger(app);\n`;
|
|
119
158
|
}
|
|
120
|
-
let authRoutesInjector = '';
|
|
121
159
|
if (addAuth) {
|
|
122
|
-
|
|
160
|
+
authRoutesInjector = `import authRoutes from './routes/Auth.routes';\napp.use('/api/auth', authRoutes);\n\n`;
|
|
123
161
|
}
|
|
162
|
+
|
|
124
163
|
serverFileContent = serverFileContent
|
|
125
164
|
.replace("dotenv.config();", `dotenv.config();${dbConnectionCode}`)
|
|
126
165
|
.replace('// INJECT:ROUTES', `${authRoutesInjector}import apiRoutes from './routes';\napp.use('/api', apiRoutes);`);
|
|
166
|
+
|
|
167
|
+
const listenRegex = /(app\.listen\()/;
|
|
168
|
+
serverFileContent = serverFileContent.replace(listenRegex, `${swaggerInjector}\n$1`);
|
|
127
169
|
await fs.writeFile(path.join(destSrcDir, 'server.ts'), serverFileContent);
|
|
128
170
|
|
|
129
|
-
// --- Step 10: Install
|
|
130
|
-
console.log(chalk.magenta(' -> Installing
|
|
171
|
+
// --- Step 10: Install Dependencies & Run Post-install Scripts ---
|
|
172
|
+
console.log(chalk.magenta(' -> Installing dependencies... This may take a moment.'));
|
|
131
173
|
await execa('npm', ['install'], { cwd: projectDir });
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
if (addAuth) {
|
|
136
|
-
const envExampleContent = `PORT=8000\nMONGO_URI=mongodb://127.0.0.1:27017/${projectName}\nJWT_SECRET=your_super_secret_jwt_key_123`;
|
|
137
|
-
await fs.writeFile(path.join(projectDir, '.env.example'), envExampleContent);
|
|
174
|
+
if (dbType === 'prisma') {
|
|
175
|
+
console.log(chalk.blue(' -> Running `prisma generate`...'));
|
|
176
|
+
await execa('npx', ['prisma', 'generate'], { cwd: projectDir });
|
|
138
177
|
}
|
|
139
|
-
|
|
178
|
+
|
|
179
|
+
// --- Step 11: Generate Final Files (.env.example) ---
|
|
180
|
+
let envContent = `PORT=${port}\n`;
|
|
181
|
+
if (dbType === 'mongoose') {
|
|
182
|
+
envContent += `MONGO_URI=mongodb://root:example@db:27017/${projectName}?authSource=admin\n`;
|
|
183
|
+
} else if (dbType === 'prisma') {
|
|
184
|
+
envContent += `DATABASE_URL="postgresql://user:password@db:5432/${projectName}?schema=public"\n`;
|
|
185
|
+
}
|
|
186
|
+
if (addAuth) envContent += `JWT_SECRET=your_super_secret_jwt_key_12345\n`;
|
|
187
|
+
if (extraFeatures.includes('docker')) {
|
|
188
|
+
envContent += `\n# Docker-compose credentials (used in docker-compose.yml)\nDB_USER=user\nDB_PASSWORD=password\nDB_NAME=${projectName}`;
|
|
189
|
+
}
|
|
190
|
+
await fs.writeFile(path.join(projectDir, '.env.example'), envContent);
|
|
191
|
+
|
|
140
192
|
} catch (error) {
|
|
141
193
|
throw error;
|
|
142
194
|
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
const { execa } = require('execa');
|
|
3
|
+
const fs = require('fs-extra');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { analyzeFrontend } = require('../analyzer');
|
|
6
|
+
const { renderAndWrite, getTemplatePath } = require('./template');
|
|
7
|
+
|
|
8
|
+
async function generatePythonProject(options) {
|
|
9
|
+
const { projectDir, projectName, frontendSrcDir } = options;
|
|
10
|
+
|
|
11
|
+
try {
|
|
12
|
+
// --- Step 1: Analysis & Model Identification ---
|
|
13
|
+
console.log(chalk.blue(' -> Analyzing frontend for Python (FastAPI) backend...'));
|
|
14
|
+
const endpoints = await analyzeFrontend(frontendSrcDir);
|
|
15
|
+
const modelsToGenerate = new Map();
|
|
16
|
+
endpoints.forEach(ep => {
|
|
17
|
+
if (ep.schemaFields && ep.controllerName !== 'Default' && !modelsToGenerate.has(ep.controllerName)) {
|
|
18
|
+
modelsToGenerate.set(ep.controllerName, { name: ep.controllerName, fields: Object.entries(ep.schemaFields).map(([key, type]) => ({ name: key, type })) });
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
// --- Step 2: Scaffold Base Python Project ---
|
|
23
|
+
console.log(chalk.blue(' -> Scaffolding Python (FastAPI) project...'));
|
|
24
|
+
const appDir = path.join(projectDir, 'app');
|
|
25
|
+
const routesDir = path.join(appDir, 'routes');
|
|
26
|
+
await fs.ensureDir(appDir);
|
|
27
|
+
await fs.ensureDir(routesDir);
|
|
28
|
+
|
|
29
|
+
// --- Step 3: Generate Files from Templates ---
|
|
30
|
+
const controllers = Array.from(modelsToGenerate.keys());
|
|
31
|
+
|
|
32
|
+
// Generate main.py
|
|
33
|
+
await renderAndWrite(getTemplatePath('python-fastapi/main.py.ejs'), path.join(projectDir, 'app', 'main.py'), { projectName, controllers });
|
|
34
|
+
|
|
35
|
+
// Generate requirements.txt
|
|
36
|
+
await renderAndWrite(getTemplatePath('python-fastapi/requirements.txt.ejs'), path.join(projectDir, 'requirements.txt'), {});
|
|
37
|
+
|
|
38
|
+
// Generate route file for each model
|
|
39
|
+
for (const [modelName, modelData] of modelsToGenerate.entries()) {
|
|
40
|
+
await renderAndWrite(
|
|
41
|
+
getTemplatePath('python-fastapi/routes.py.ejs'),
|
|
42
|
+
path.join(routesDir, `${modelName.toLowerCase()}_routes.py`),
|
|
43
|
+
{ modelName, schema: modelData }
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// --- Step 4: Setup Virtual Environment and Install Dependencies ---
|
|
48
|
+
console.log(chalk.magenta(' -> Setting up virtual environment and installing dependencies...'));
|
|
49
|
+
// Create a virtual environment
|
|
50
|
+
await execa('python', ['-m', 'venv', 'venv'], { cwd: projectDir });
|
|
51
|
+
|
|
52
|
+
// Determine the correct pip executable path based on OS
|
|
53
|
+
const pipPath = process.platform === 'win32'
|
|
54
|
+
? path.join('venv', 'Scripts', 'pip')
|
|
55
|
+
: path.join('venv', 'bin', 'pip');
|
|
56
|
+
|
|
57
|
+
// Install dependencies using the virtual environment's pip
|
|
58
|
+
await execa(path.join(projectDir, pipPath), ['install', '-r', 'requirements.txt'], { cwd: projectDir });
|
|
59
|
+
|
|
60
|
+
console.log(chalk.green(' -> Python backend generation is complete!'));
|
|
61
|
+
console.log(chalk.yellow('\nTo run your new Python backend:'));
|
|
62
|
+
console.log(chalk.cyan(' 1. Activate the virtual environment: `source venv/bin/activate` (or `venv\\Scripts\\activate` on Windows)'));
|
|
63
|
+
console.log(chalk.cyan(' 2. Start the server: `uvicorn app.main:app --reload`'));
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
} catch (error) {
|
|
67
|
+
// Improve error message for command not found
|
|
68
|
+
if (error.code === 'ENOENT') {
|
|
69
|
+
throw new Error(`'${error.command}' command not found. Please ensure Python and venv are installed and in your system's PATH.`);
|
|
70
|
+
}
|
|
71
|
+
throw error;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
module.exports = { generatePythonProject };
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// Auto-generated by create-backlist
|
|
2
|
+
package <%= group %>.<%= projectName %>.controller;
|
|
3
|
+
|
|
4
|
+
import <%= group %>.<%= projectName %>.model.<%= controllerName %>;
|
|
5
|
+
import <%= group %>.<%= projectName %>.repository.<%= controllerName %>Repository;
|
|
6
|
+
import org.springframework.beans.factory.annotation.Autowired;
|
|
7
|
+
import org.springframework.http.HttpStatus;
|
|
8
|
+
import org.springframework.http.ResponseEntity;
|
|
9
|
+
import org.springframework.web.bind.annotation.*;
|
|
10
|
+
|
|
11
|
+
import java.util.List;
|
|
12
|
+
import java.util.Optional;
|
|
13
|
+
|
|
14
|
+
@RestController
|
|
15
|
+
@CrossOrigin(origins = "*") // Allow all origins for development
|
|
16
|
+
@RequestMapping("/api/<%= controllerName.toLowerCase() %>s")
|
|
17
|
+
public class <%= controllerName %>Controller {
|
|
18
|
+
|
|
19
|
+
@Autowired
|
|
20
|
+
private <%= controllerName %>Repository repository;
|
|
21
|
+
|
|
22
|
+
@GetMapping
|
|
23
|
+
public List<<%= controllerName %>> getAll<%= controllerName %>s() {
|
|
24
|
+
return repository.findAll();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
@GetMapping("/{id}")
|
|
28
|
+
public ResponseEntity<<%= controllerName %>> get<%= controllerName %>ById(@PathVariable Long id) {
|
|
29
|
+
Optional<<%= controllerName %>> item = repository.findById(id);
|
|
30
|
+
return item.map(ResponseEntity::ok).orElseGet(() -> ResponseEntity.notFound().build());
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
@PostMapping
|
|
34
|
+
public ResponseEntity<<%= controllerName %>> create<%= controllerName %>(@RequestBody <%= controllerName %> newItem) {
|
|
35
|
+
<%= controllerName %> savedItem = repository.save(newItem);
|
|
36
|
+
return new ResponseEntity<>(savedItem, HttpStatus.CREATED);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
@PutMapping("/{id}")
|
|
40
|
+
public ResponseEntity<<%= controllerName %>> update<%= controllerName %>(@PathVariable Long id, @RequestBody <%= controllerName %> updatedItem) {
|
|
41
|
+
return repository.findById(id)
|
|
42
|
+
.map(item -> {
|
|
43
|
+
// Manually map fields to update. For simplicity, we assume all fields are updatable.
|
|
44
|
+
<% model.fields.forEach(field => { %>
|
|
45
|
+
item.set<%= field.name.charAt(0).toUpperCase() + field.name.slice(1) %>(updatedItem.get<%= field.name.charAt(0).toUpperCase() + field.name.slice(1) %>());
|
|
46
|
+
<% }); %>
|
|
47
|
+
<%= controllerName %> savedItem = repository.save(item);
|
|
48
|
+
return ResponseEntity.ok(savedItem);
|
|
49
|
+
})
|
|
50
|
+
.orElseGet(() -> ResponseEntity.notFound().build());
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
@DeleteMapping("/{id}")
|
|
54
|
+
public ResponseEntity<Void> delete<%= controllerName %>(@PathVariable Long id) {
|
|
55
|
+
if (!repository.existsById(id)) {
|
|
56
|
+
return ResponseEntity.notFound().build();
|
|
57
|
+
}
|
|
58
|
+
repository.deleteById(id);
|
|
59
|
+
return ResponseEntity.noContent().build();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// Auto-generated by create-backlist v6.0
|
|
2
|
+
package <%= group %>.<%= projectName %>.model;
|
|
3
|
+
|
|
4
|
+
import jakarta.persistence.Entity;
|
|
5
|
+
import jakarta.persistence.Id;
|
|
6
|
+
import jakarta.persistence.GeneratedValue;
|
|
7
|
+
import jakarta.persistence.GenerationType;
|
|
8
|
+
import lombok.Data;
|
|
9
|
+
|
|
10
|
+
@Data
|
|
11
|
+
@Entity
|
|
12
|
+
public class <%= modelName %> {
|
|
13
|
+
|
|
14
|
+
@Id
|
|
15
|
+
@GeneratedValue(strategy = GenerationType.AUTO)
|
|
16
|
+
private Long id;
|
|
17
|
+
|
|
18
|
+
<% model.fields.forEach(field => { %>
|
|
19
|
+
<% let javaType = 'String'; %>
|
|
20
|
+
<% if (field.type === 'Number') javaType = 'Integer'; %>
|
|
21
|
+
<% if (field.type === 'Boolean') javaType = 'boolean'; %>
|
|
22
|
+
private <%= javaType %> <%= field.name %>;
|
|
23
|
+
<% }); %>
|
|
24
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Auto-generated by create-backlist
|
|
2
|
+
package <%= group %>.<%= projectName %>.repository;
|
|
3
|
+
|
|
4
|
+
import <%= group %>.<%= projectName %>.model.<%= modelName %>;
|
|
5
|
+
import org.springframework.data.jpa.repository.JpaRepository;
|
|
6
|
+
import org.springframework.stereotype.Repository;
|
|
7
|
+
|
|
8
|
+
@Repository
|
|
9
|
+
public interface <%= modelName %>Repository extends JpaRepository<<%= modelName %>, Long> {
|
|
10
|
+
// Spring Data JPA automatically provides CRUD methods like findAll(), findById(), save(), deleteById()
|
|
11
|
+
// You can add custom query methods here if needed.
|
|
12
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// Auto-generated by create-backlist v5.0
|
|
2
|
+
import swaggerUi from 'swagger-ui-express';
|
|
3
|
+
import swaggerJsdoc from 'swagger-jsdoc';
|
|
4
|
+
import { Express } from 'express';
|
|
5
|
+
|
|
6
|
+
const options: swaggerJsdoc.Options = {
|
|
7
|
+
definition: {
|
|
8
|
+
openapi: '3.0.0',
|
|
9
|
+
info: {
|
|
10
|
+
title: '<%= projectName %> API Documentation',
|
|
11
|
+
version: '1.0.0',
|
|
12
|
+
description: 'API documentation for the auto-generated backend.',
|
|
13
|
+
},
|
|
14
|
+
servers: [
|
|
15
|
+
{
|
|
16
|
+
url: 'http://localhost:<%= port %>',
|
|
17
|
+
description: 'Development server',
|
|
18
|
+
},
|
|
19
|
+
],
|
|
20
|
+
// TODO: Add components (e.g., securitySchemes for JWT)
|
|
21
|
+
},
|
|
22
|
+
// Path to the API docs
|
|
23
|
+
apis: ['./src/routes/*.ts', './src/routes.ts'], // Looks for JSDoc comments in routes
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const swaggerSpec = swaggerJsdoc(options);
|
|
27
|
+
|
|
28
|
+
export function setupSwagger(app: Express) {
|
|
29
|
+
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec));
|
|
30
|
+
console.log(
|
|
31
|
+
`📄 API documentation is available at http://localhost:<%= port %>/api-docs`
|
|
32
|
+
);
|
|
33
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// Auto-generated by create-backlist v5.0
|
|
2
|
+
import request from 'supertest';
|
|
3
|
+
import express from 'express';
|
|
4
|
+
// Import your main app configuration. This might need path adjustment.
|
|
5
|
+
// For simplicity, we create a test server here.
|
|
6
|
+
import apiRoutes from '../routes'; // Assuming main routes
|
|
7
|
+
import authRoutes from '../routes/Auth.routes'; // Assuming auth routes
|
|
8
|
+
|
|
9
|
+
const app = express();
|
|
10
|
+
app.use(express.json());
|
|
11
|
+
app.use('/api', apiRoutes);
|
|
12
|
+
app.use('/api/auth', authRoutes);
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
describe('API Endpoints', () => {
|
|
16
|
+
|
|
17
|
+
it('should respond to the root GET endpoint', async () => {
|
|
18
|
+
// This test assumes a GET /api/ endpoint exists or a similar public one.
|
|
19
|
+
// You might need to adjust this to a real endpoint from your app.
|
|
20
|
+
// Example for GET /api/users
|
|
21
|
+
// const res = await request(app).get('/api/users');
|
|
22
|
+
// expect(res.statusCode).toEqual(200);
|
|
23
|
+
|
|
24
|
+
// For now, a placeholder test:
|
|
25
|
+
expect(1 + 1).toBe(2);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
// TODO: Add more specific tests for your generated endpoints
|
|
29
|
+
// describe('POST /api/users', () => {
|
|
30
|
+
// it('should create a new user', async () => {
|
|
31
|
+
// const res = await request(app)
|
|
32
|
+
// .post('/api/users')
|
|
33
|
+
// .send({ name: 'Test User', email: 'test@example.com', password: 'password123' });
|
|
34
|
+
// expect(res.statusCode).toEqual(201);
|
|
35
|
+
// expect(res.body).toHaveProperty('name', 'Test User');
|
|
36
|
+
// });
|
|
37
|
+
// });
|
|
38
|
+
});
|