create-backlist 5.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 +12 -0
- package/package.json +5 -3
- package/src/generators/java.js +100 -0
- package/src/generators/node.js +61 -25
- 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/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/bin/index.js
CHANGED
|
@@ -98,6 +98,18 @@ async function main() {
|
|
|
98
98
|
],
|
|
99
99
|
when: (answers) => answers.stack === 'node-ts-express'
|
|
100
100
|
},
|
|
101
|
+
{
|
|
102
|
+
type: 'list',
|
|
103
|
+
name: 'stack',
|
|
104
|
+
message: 'Select the backend stack:',
|
|
105
|
+
choices: [
|
|
106
|
+
{ name: 'Node.js (TypeScript, Express)', value: 'node-ts-express' },
|
|
107
|
+
{ name: 'C# (ASP.NET Core Web API)', value: 'dotnet-webapi' },
|
|
108
|
+
new inquirer.Separator(),
|
|
109
|
+
{ name: 'Python (FastAPI) - Coming Soon', disabled: true, value: 'python-fastapi' },
|
|
110
|
+
{ name: 'Java (Spring Boot)', value: 'java-spring' }, // <-- ENABLED!
|
|
111
|
+
],
|
|
112
|
+
},
|
|
101
113
|
]);
|
|
102
114
|
|
|
103
115
|
const options = {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-backlist",
|
|
3
|
-
"version": "5.0.
|
|
3
|
+
"version": "5.0.1",
|
|
4
4
|
"description": "An advanced, multi-language backend generator based on frontend analysis.",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"bin": {
|
|
@@ -18,11 +18,13 @@
|
|
|
18
18
|
"dependencies": {
|
|
19
19
|
"@babel/parser": "^7.22.7",
|
|
20
20
|
"@babel/traverse": "^7.22.8",
|
|
21
|
+
"axios": "^1.13.1",
|
|
21
22
|
"chalk": "^4.1.2",
|
|
22
23
|
"ejs": "^3.1.9",
|
|
23
24
|
"execa": "^6.1.0",
|
|
24
25
|
"fs-extra": "^11.1.1",
|
|
25
26
|
"glob": "^10.3.3",
|
|
26
|
-
"inquirer": "^8.2.4"
|
|
27
|
+
"inquirer": "^8.2.4",
|
|
28
|
+
"unzipper": "^0.12.3"
|
|
27
29
|
}
|
|
28
|
-
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
const { execa } = require('execa');
|
|
3
|
+
const fs = require('fs-extra');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const axios = require('axios'); // For making HTTP requests
|
|
6
|
+
const unzipper = require('unzipper'); // For extracting .zip files
|
|
7
|
+
const { analyzeFrontend } = require('../analyzer');
|
|
8
|
+
const { renderAndWrite, getTemplatePath } = require('./template');
|
|
9
|
+
|
|
10
|
+
async function generateJavaProject(options) {
|
|
11
|
+
const { projectDir, projectName, frontendSrcDir } = options;
|
|
12
|
+
const group = 'com.backlist.generated'; // A default Java group ID
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
// --- Step 1: Download Base Project from Spring Initializr ---
|
|
16
|
+
console.log(chalk.blue(' -> Contacting Spring Initializr to download a base Spring Boot project...'));
|
|
17
|
+
|
|
18
|
+
// Define standard dependencies for a web API
|
|
19
|
+
const dependencies = 'web,data-jpa,lombok,postgresql'; // Using PostgreSQL as an example DB driver
|
|
20
|
+
const springInitializrUrl = `https://start.spring.io/starter.zip?type=maven-project&language=java&bootVersion=3.2.0&groupId=${group}&artifactId=${projectName}&name=${projectName}&dependencies=${dependencies}`;
|
|
21
|
+
|
|
22
|
+
const response = await axios({
|
|
23
|
+
url: springInitializrUrl,
|
|
24
|
+
method: 'GET',
|
|
25
|
+
responseType: 'stream'
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
// --- Step 2: Unzip the Downloaded Project ---
|
|
29
|
+
console.log(chalk.blue(' -> Unzipping the Spring Boot project...'));
|
|
30
|
+
await new Promise((resolve, reject) => {
|
|
31
|
+
const stream = response.data.pipe(unzipper.Extract({ path: projectDir }));
|
|
32
|
+
stream.on('finish', () => {
|
|
33
|
+
console.log(chalk.gray(' -> Project unzipped successfully.'));
|
|
34
|
+
resolve();
|
|
35
|
+
});
|
|
36
|
+
stream.on('error', reject);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// --- Step 3: Analyze Frontend ---
|
|
40
|
+
const endpoints = await analyzeFrontend(frontendSrcDir);
|
|
41
|
+
const modelsToGenerate = new Map();
|
|
42
|
+
endpoints.forEach(ep => {
|
|
43
|
+
if (ep.schemaFields && ep.controllerName !== 'Default' && !modelsToGenerate.has(ep.controllerName)) {
|
|
44
|
+
modelsToGenerate.set(ep.controllerName, { name: ep.controllerName, fields: Object.entries(ep.schemaFields).map(([key, type]) => ({ name: key, type })) });
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// --- Step 4: Generate Java Entities and Controllers ---
|
|
49
|
+
if (modelsToGenerate.size > 0) {
|
|
50
|
+
console.log(chalk.blue(' -> Generating Java entities and controllers...'));
|
|
51
|
+
|
|
52
|
+
const javaSrcPath = path.join(projectDir, 'src', 'main', 'java', ...group.split('.'), projectName);
|
|
53
|
+
const entityDir = path.join(javaSrcPath, 'model');
|
|
54
|
+
const controllerDir = path.join(javaSrcPath, 'controller');
|
|
55
|
+
await fs.ensureDir(entityDir);
|
|
56
|
+
await fs.ensureDir(controllerDir);
|
|
57
|
+
|
|
58
|
+
for (const [modelName, modelData] of modelsToGenerate.entries()) {
|
|
59
|
+
await renderAndWrite(
|
|
60
|
+
getTemplatePath('java-spring/partials/Entity.java.ejs'),
|
|
61
|
+
path.join(entityDir, `${modelName}.java`),
|
|
62
|
+
{ group, projectName, modelName, model: modelData }
|
|
63
|
+
);
|
|
64
|
+
await renderAndWrite(
|
|
65
|
+
getTemplatePath('java-spring/partials/Controller.java.ejs'),
|
|
66
|
+
path.join(controllerDir, `${modelName}Controller.java`),
|
|
67
|
+
{ group, projectName, controllerName: modelName }
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// --- Step 5: Configure application.properties ---
|
|
73
|
+
console.log(chalk.blue(' -> Configuring database in application.properties...'));
|
|
74
|
+
const propsPath = path.join(projectDir, 'src', 'main', 'resources', 'application.properties');
|
|
75
|
+
const dbProps = [
|
|
76
|
+
`\n\n# --- Auto-generated by create-backlist ---`,
|
|
77
|
+
`# --- Database Configuration (PostgreSQL) ---`,
|
|
78
|
+
`spring.datasource.url=jdbc:postgresql://localhost:5432/${projectName}`,
|
|
79
|
+
`spring.datasource.username=postgres`,
|
|
80
|
+
`spring.datasource.password=password`,
|
|
81
|
+
`spring.jpa.hibernate.ddl-auto=update`,
|
|
82
|
+
`spring.jpa.show-sql=true`,
|
|
83
|
+
];
|
|
84
|
+
await fs.appendFile(propsPath, dbProps.join('\n'));
|
|
85
|
+
|
|
86
|
+
console.log(chalk.green(' -> Java (Spring Boot) backend generation is complete!'));
|
|
87
|
+
console.log(chalk.yellow('\nTo run your new Java backend:'));
|
|
88
|
+
console.log(chalk.cyan(' 1. Open the project in a Java IDE (like IntelliJ IDEA or VS Code).'));
|
|
89
|
+
console.log(chalk.cyan(' 2. Run the main application file.'));
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
} catch (error) {
|
|
93
|
+
if (error.response) {
|
|
94
|
+
throw new Error(`Failed to download from Spring Initializr. Status: ${error.response.status}`);
|
|
95
|
+
}
|
|
96
|
+
throw error;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
module.exports = { generateJavaProject };
|
package/src/generators/node.js
CHANGED
|
@@ -36,39 +36,39 @@ async function generateNodeProject(options) {
|
|
|
36
36
|
await fs.copy(getTemplatePath('node-ts-express/base/server.ts'), path.join(destSrcDir, 'server.ts'));
|
|
37
37
|
await fs.copy(getTemplatePath('node-ts-express/base/tsconfig.json'), path.join(projectDir, 'tsconfig.json'));
|
|
38
38
|
|
|
39
|
-
// --- Step 4: Prepare and Write package.json
|
|
39
|
+
// --- Step 4: Prepare and Write package.json ---
|
|
40
40
|
const packageJsonContent = JSON.parse(await ejs.renderFile(getTemplatePath('node-ts-express/partials/package.json.ejs'), { projectName }));
|
|
41
41
|
|
|
42
|
-
if (dbType === 'mongoose') packageJsonContent.dependencies['mongoose'] = '^7.
|
|
42
|
+
if (dbType === 'mongoose') packageJsonContent.dependencies['mongoose'] = '^7.6.3';
|
|
43
43
|
if (dbType === 'prisma') {
|
|
44
|
-
packageJsonContent.dependencies['@prisma/client'] = '^5.
|
|
45
|
-
packageJsonContent.devDependencies['prisma'] = '^5.
|
|
44
|
+
packageJsonContent.dependencies['@prisma/client'] = '^5.6.0';
|
|
45
|
+
packageJsonContent.devDependencies['prisma'] = '^5.6.0';
|
|
46
46
|
packageJsonContent.prisma = { seed: `ts-node ${addSeeder ? 'scripts/seeder.ts' : 'prisma/seed.ts'}` };
|
|
47
47
|
}
|
|
48
48
|
if (addAuth) {
|
|
49
49
|
packageJsonContent.dependencies['jsonwebtoken'] = '^9.0.2';
|
|
50
50
|
packageJsonContent.dependencies['bcryptjs'] = '^2.4.3';
|
|
51
|
-
packageJsonContent.devDependencies['@types/jsonwebtoken'] = '^9.0.
|
|
52
|
-
packageJsonContent.devDependencies['@types/bcryptjs'] = '^2.4.
|
|
51
|
+
packageJsonContent.devDependencies['@types/jsonwebtoken'] = '^9.0.5';
|
|
52
|
+
packageJsonContent.devDependencies['@types/bcryptjs'] = '^2.4.6';
|
|
53
53
|
}
|
|
54
54
|
if (addSeeder) {
|
|
55
|
-
packageJsonContent.devDependencies['@faker-js/faker'] = '^8.
|
|
56
|
-
packageJsonContent.dependencies['chalk'] = '^4.1.2';
|
|
55
|
+
packageJsonContent.devDependencies['@faker-js/faker'] = '^8.3.1';
|
|
56
|
+
if (!packageJsonContent.dependencies['chalk']) packageJsonContent.dependencies['chalk'] = '^4.1.2';
|
|
57
57
|
packageJsonContent.scripts['seed'] = `ts-node scripts/seeder.ts`;
|
|
58
58
|
packageJsonContent.scripts['destroy'] = `ts-node scripts/seeder.ts -d`;
|
|
59
59
|
}
|
|
60
60
|
if (extraFeatures.includes('testing')) {
|
|
61
61
|
packageJsonContent.devDependencies['jest'] = '^29.7.0';
|
|
62
62
|
packageJsonContent.devDependencies['supertest'] = '^6.3.3';
|
|
63
|
-
packageJsonContent.devDependencies['@types/jest'] = '^29.5.
|
|
64
|
-
packageJsonContent.devDependencies['@types/supertest'] = '^2.0.
|
|
63
|
+
packageJsonContent.devDependencies['@types/jest'] = '^29.5.10';
|
|
64
|
+
packageJsonContent.devDependencies['@types/supertest'] = '^2.0.16';
|
|
65
65
|
packageJsonContent.devDependencies['ts-jest'] = '^29.1.1';
|
|
66
|
-
packageJsonContent.scripts['test'] = 'jest --detectOpenHandles';
|
|
66
|
+
packageJsonContent.scripts['test'] = 'jest --detectOpenHandles --forceExit';
|
|
67
67
|
}
|
|
68
68
|
if (extraFeatures.includes('swagger')) {
|
|
69
69
|
packageJsonContent.dependencies['swagger-ui-express'] = '^5.0.0';
|
|
70
70
|
packageJsonContent.dependencies['swagger-jsdoc'] = '^6.2.8';
|
|
71
|
-
packageJsonContent.devDependencies['@types/swagger-ui-express'] = '^4.1.
|
|
71
|
+
packageJsonContent.devDependencies['@types/swagger-ui-express'] = '^4.1.6';
|
|
72
72
|
}
|
|
73
73
|
await fs.writeJson(path.join(projectDir, 'package.json'), packageJsonContent, { spaces: 2 });
|
|
74
74
|
|
|
@@ -93,9 +93,37 @@ async function generateNodeProject(options) {
|
|
|
93
93
|
}
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
-
// --- Step 6: Generate
|
|
97
|
-
if (addAuth) {
|
|
98
|
-
|
|
96
|
+
// --- Step 6: Generate Authentication Boilerplate ---
|
|
97
|
+
if (addAuth) {
|
|
98
|
+
console.log(chalk.blue(' -> Generating authentication boilerplate...'));
|
|
99
|
+
await fs.ensureDir(path.join(destSrcDir, 'routes'));
|
|
100
|
+
await fs.ensureDir(path.join(destSrcDir, 'middleware'));
|
|
101
|
+
await renderAndWrite(getTemplatePath('node-ts-express/partials/Auth.controller.ts.ejs'), path.join(destSrcDir, 'controllers', 'Auth.controller.ts'), {});
|
|
102
|
+
await renderAndWrite(getTemplatePath('node-ts-express/partials/Auth.routes.ts.ejs'), path.join(destSrcDir, 'routes', 'Auth.routes.ts'), {});
|
|
103
|
+
await renderAndWrite(getTemplatePath('node-ts-express/partials/Auth.middleware.ts.ejs'), path.join(destSrcDir, 'middleware', 'Auth.middleware.ts'), {});
|
|
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
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// --- Step 7: Generate Seeder Script ---
|
|
120
|
+
if (addSeeder) {
|
|
121
|
+
console.log(chalk.blue(' -> Generating database seeder script...'));
|
|
122
|
+
await fs.ensureDir(path.join(projectDir, 'scripts'));
|
|
123
|
+
await renderAndWrite(getTemplatePath('node-ts-express/partials/Seeder.ts.ejs'), path.join(projectDir, 'scripts', 'seeder.ts'), { projectName });
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// --- Step 8: Generate Extra Features ---
|
|
99
127
|
if (extraFeatures.includes('docker')) {
|
|
100
128
|
console.log(chalk.blue(' -> Generating Docker files...'));
|
|
101
129
|
await renderAndWrite(getTemplatePath('node-ts-express/partials/Dockerfile.ejs'), path.join(projectDir, 'Dockerfile'), { dbType, port });
|
|
@@ -108,13 +136,13 @@ async function generateNodeProject(options) {
|
|
|
108
136
|
}
|
|
109
137
|
if (extraFeatures.includes('testing')) {
|
|
110
138
|
console.log(chalk.blue(' -> Generating testing boilerplate...'));
|
|
111
|
-
const jestConfig =
|
|
139
|
+
const jestConfig = `/** @type {import('ts-jest').JestConfigWithTsJest} */\nmodule.exports = {\n preset: 'ts-jest',\n testEnvironment: 'node',\n verbose: true,\n};`;
|
|
112
140
|
await fs.writeFile(path.join(projectDir, 'jest.config.js'), jestConfig);
|
|
113
141
|
await fs.ensureDir(path.join(projectDir, 'src', '__tests__'));
|
|
114
142
|
await renderAndWrite(getTemplatePath('node-ts-express/partials/App.test.ts.ejs'), path.join(projectDir, 'src', '__tests__', 'api.test.ts'), { addAuth });
|
|
115
143
|
}
|
|
116
144
|
|
|
117
|
-
// --- Step
|
|
145
|
+
// --- Step 9: Generate Main Route File & Inject Logic into Server ---
|
|
118
146
|
await renderAndWrite(getTemplatePath('node-ts-express/partials/routes.ts.ejs'), path.join(destSrcDir, 'routes.ts'), { endpoints, addAuth, dbType });
|
|
119
147
|
|
|
120
148
|
let serverFileContent = await fs.readFile(path.join(destSrcDir, 'server.ts'), 'utf-8');
|
|
@@ -134,23 +162,31 @@ async function generateNodeProject(options) {
|
|
|
134
162
|
|
|
135
163
|
serverFileContent = serverFileContent
|
|
136
164
|
.replace("dotenv.config();", `dotenv.config();${dbConnectionCode}`)
|
|
137
|
-
.replace('// INJECT:ROUTES', `${authRoutesInjector}import apiRoutes from './routes';\napp.use('/api', apiRoutes)
|
|
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`);
|
|
138
169
|
await fs.writeFile(path.join(destSrcDir, 'server.ts'), serverFileContent);
|
|
139
170
|
|
|
140
|
-
// --- Step
|
|
171
|
+
// --- Step 10: Install Dependencies & Run Post-install Scripts ---
|
|
141
172
|
console.log(chalk.magenta(' -> Installing dependencies... This may take a moment.'));
|
|
142
173
|
await execa('npm', ['install'], { cwd: projectDir });
|
|
143
174
|
if (dbType === 'prisma') {
|
|
144
175
|
console.log(chalk.blue(' -> Running `prisma generate`...'));
|
|
145
176
|
await execa('npx', ['prisma', 'generate'], { cwd: projectDir });
|
|
146
177
|
}
|
|
147
|
-
|
|
148
|
-
// --- Step
|
|
178
|
+
|
|
179
|
+
// --- Step 11: Generate Final Files (.env.example) ---
|
|
149
180
|
let envContent = `PORT=${port}\n`;
|
|
150
|
-
if (dbType === 'mongoose')
|
|
151
|
-
|
|
152
|
-
if (
|
|
153
|
-
|
|
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
|
+
}
|
|
154
190
|
await fs.writeFile(path.join(projectDir, '.env.example'), envContent);
|
|
155
191
|
|
|
156
192
|
} catch (error) {
|
|
@@ -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,38 @@
|
|
|
1
|
+
# Auto-generated by create-backlist v6.0
|
|
2
|
+
from fastapi import FastAPI
|
|
3
|
+
from fastapi.middleware.cors import CORSMiddleware
|
|
4
|
+
import uvicorn
|
|
5
|
+
import os
|
|
6
|
+
|
|
7
|
+
# Import generated routers
|
|
8
|
+
<% for (const controller of controllers) { %>
|
|
9
|
+
from app.routes import <%= controller.toLowerCase() %>_routes
|
|
10
|
+
<% } %>
|
|
11
|
+
|
|
12
|
+
# Create FastAPI app instance
|
|
13
|
+
app = FastAPI(title="<%= projectName %>")
|
|
14
|
+
|
|
15
|
+
# CORS (Cross-Origin Resource Sharing) Middleware
|
|
16
|
+
# This allows your frontend to communicate with this backend
|
|
17
|
+
app.add_middleware(
|
|
18
|
+
CORSMiddleware,
|
|
19
|
+
allow_origins=["*"], # In production, specify your frontend's origin
|
|
20
|
+
allow_credentials=True,
|
|
21
|
+
allow_methods=["*"],
|
|
22
|
+
allow_headers=["*"],
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
# Root endpoint
|
|
26
|
+
@app.get("/")
|
|
27
|
+
def read_root():
|
|
28
|
+
return {"message": "Welcome to the FastAPI backend generated by create-backlist!"}
|
|
29
|
+
|
|
30
|
+
# Include generated routers
|
|
31
|
+
<% for (const controller of controllers) { %>
|
|
32
|
+
app.include_router(<%= controller.toLowerCase() %>_routes.router, prefix="/api")
|
|
33
|
+
<% } %>
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
if __name__ == "__main__":
|
|
37
|
+
port = int(os.getenv("PORT", 8000))
|
|
38
|
+
uvicorn.run(app, host="0.0.0.0", port=port)
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Auto-generated by create-backlist v6.0
|
|
2
|
+
from fastapi import APIRouter, status
|
|
3
|
+
from pydantic import BaseModel
|
|
4
|
+
from typing import List
|
|
5
|
+
|
|
6
|
+
# --- Pydantic Models for Request/Response Validation ---
|
|
7
|
+
# These are automatically generated based on your frontend code.
|
|
8
|
+
|
|
9
|
+
<% if (schema) { %>
|
|
10
|
+
class <%= modelName %>Base(BaseModel):
|
|
11
|
+
<% for (const field of schema.fields) { %>
|
|
12
|
+
<%= field.name %>: <%= field.type === 'Number' ? 'int' : 'str' %>
|
|
13
|
+
<% } %>
|
|
14
|
+
|
|
15
|
+
class <%= modelName %>(<%= modelName %>Base):
|
|
16
|
+
id: int # Or str, depending on your DB
|
|
17
|
+
# Add other fields like createdAt if needed
|
|
18
|
+
class Config:
|
|
19
|
+
orm_mode = True
|
|
20
|
+
<% } %>
|
|
21
|
+
|
|
22
|
+
# --- Router Definition ---
|
|
23
|
+
router = APIRouter(
|
|
24
|
+
prefix="/<%= modelName.toLowerCase() %>s",
|
|
25
|
+
tags=["<%= modelName %>s"]
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
# --- API Endpoints ---
|
|
29
|
+
|
|
30
|
+
@router.get("/", response_model=List[<%= modelName %>])
|
|
31
|
+
async def get_all_items():
|
|
32
|
+
# TODO: Implement database logic to fetch all items
|
|
33
|
+
# Example: return await db.query(<%= modelName %>).all()
|
|
34
|
+
return [{"id": 1, "name": "Example Item", "email": "example@test.com"}]
|
|
35
|
+
|
|
36
|
+
@router.post("/", status_code=status.HTTP_201_CREATED, response_model=<%= modelName %>)
|
|
37
|
+
async def create_item(item: <%= modelName %>Base):
|
|
38
|
+
# TODO: Implement database logic to create a new item
|
|
39
|
+
# Example: new_item = await db.create(<%= modelName %>, **item.dict())
|
|
40
|
+
return {**item.dict(), "id": 2}
|
|
41
|
+
|
|
42
|
+
# TODO: Add routes for GET (by id), PUT, and DELETE based on detected endpoints.
|