create-backlist 6.0.7 → 6.0.8
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 +141 -0
- package/package.json +4 -10
- package/src/analyzer.js +104 -315
- package/src/generators/dotnet.js +94 -120
- package/src/generators/java.js +109 -157
- package/src/generators/node.js +85 -262
- package/src/generators/template.js +2 -38
- package/src/templates/dotnet/partials/Controller.cs.ejs +14 -7
- package/src/templates/java-spring/partials/ApplicationSeeder.java.ejs +2 -7
- package/src/templates/java-spring/partials/AuthController.java.ejs +10 -23
- package/src/templates/java-spring/partials/Controller.java.ejs +6 -17
- package/src/templates/java-spring/partials/Dockerfile.ejs +1 -6
- package/src/templates/java-spring/partials/Entity.java.ejs +5 -15
- package/src/templates/java-spring/partials/JwtAuthFilter.java.ejs +7 -30
- package/src/templates/java-spring/partials/JwtService.java.ejs +10 -38
- package/src/templates/java-spring/partials/Repository.java.ejs +1 -10
- package/src/templates/java-spring/partials/Service.java.ejs +7 -45
- package/src/templates/java-spring/partials/User.java.ejs +4 -17
- package/src/templates/java-spring/partials/UserDetailsServiceImpl.java.ejs +4 -10
- package/src/templates/java-spring/partials/UserRepository.java.ejs +0 -8
- package/src/templates/java-spring/partials/docker-compose.yml.ejs +8 -16
- package/src/templates/node-ts-express/base/server.ts +6 -13
- package/src/templates/node-ts-express/base/tsconfig.json +3 -13
- package/src/templates/node-ts-express/partials/ApiDocs.ts.ejs +7 -17
- package/src/templates/node-ts-express/partials/App.test.ts.ejs +26 -49
- package/src/templates/node-ts-express/partials/Auth.controller.ts.ejs +62 -56
- package/src/templates/node-ts-express/partials/Auth.middleware.ts.ejs +10 -21
- package/src/templates/node-ts-express/partials/Controller.ts.ejs +40 -40
- package/src/templates/node-ts-express/partials/DbContext.cs.ejs +3 -3
- package/src/templates/node-ts-express/partials/Dockerfile.ejs +11 -9
- package/src/templates/node-ts-express/partials/Model.cs.ejs +7 -25
- package/src/templates/node-ts-express/partials/Model.ts.ejs +12 -20
- package/src/templates/node-ts-express/partials/PrismaController.ts.ejs +55 -72
- package/src/templates/node-ts-express/partials/PrismaSchema.prisma.ejs +12 -27
- package/src/templates/node-ts-express/partials/README.md.ejs +12 -9
- package/src/templates/node-ts-express/partials/Seeder.ts.ejs +64 -44
- package/src/templates/node-ts-express/partials/docker-compose.yml.ejs +16 -31
- package/src/templates/node-ts-express/partials/package.json.ejs +1 -3
- package/src/templates/node-ts-express/partials/routes.ts.ejs +24 -35
- package/src/utils.js +5 -17
- package/bin/backlist.js +0 -228
- package/src/db/prisma.ts +0 -4
- package/src/scanner/analyzeFrontend.js +0 -146
- package/src/scanner/index.js +0 -99
- package/src/templates/dotnet/partials/Dto.cs.ejs +0 -8
- package/src/templates/node-ts-express/partials/prismaClient.ts.ejs +0 -4
package/src/generators/dotnet.js
CHANGED
|
@@ -5,153 +5,127 @@ const path = require('path');
|
|
|
5
5
|
const { analyzeFrontend } = require('../analyzer');
|
|
6
6
|
const { renderAndWrite, getTemplatePath } = require('./template');
|
|
7
7
|
|
|
8
|
-
function groupByController(endpoints) {
|
|
9
|
-
const map = new Map();
|
|
10
|
-
for (const ep of endpoints) {
|
|
11
|
-
const c = ep.controllerName || 'Default';
|
|
12
|
-
if (!map.has(c)) map.set(c, []);
|
|
13
|
-
map.get(c).push(ep);
|
|
14
|
-
}
|
|
15
|
-
return map;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function collectDtoModels(endpoints) {
|
|
19
|
-
const models = new Map();
|
|
20
|
-
|
|
21
|
-
for (const ep of endpoints) {
|
|
22
|
-
if (ep.requestBody?.fields && ep.requestBody.modelName) {
|
|
23
|
-
models.set(ep.requestBody.modelName, { name: ep.requestBody.modelName, fields: ep.requestBody.fields });
|
|
24
|
-
}
|
|
25
|
-
if (ep.responseBody?.fields && ep.responseBody.modelName) {
|
|
26
|
-
models.set(ep.responseBody.modelName, { name: ep.responseBody.modelName, fields: ep.responseBody.fields });
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
return models;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
async function ensureProgramMarkers(programCsPath) {
|
|
34
|
-
let content = await fs.readFile(programCsPath, 'utf-8');
|
|
35
|
-
|
|
36
|
-
if (!content.includes('<backlist:usings>')) {
|
|
37
|
-
// add marker near top
|
|
38
|
-
content = content.replace(
|
|
39
|
-
/^/m,
|
|
40
|
-
`// <backlist:usings>\n// </backlist:usings>\n\n`
|
|
41
|
-
);
|
|
42
|
-
}
|
|
43
|
-
if (!content.includes('<backlist:services>')) {
|
|
44
|
-
content = content.replace(
|
|
45
|
-
'builder.Services.AddControllers();',
|
|
46
|
-
`builder.Services.AddControllers();\n\n// <backlist:services>\n// </backlist:services>`
|
|
47
|
-
);
|
|
48
|
-
}
|
|
49
|
-
if (!content.includes('<backlist:middleware>')) {
|
|
50
|
-
content = content.replace(
|
|
51
|
-
'var app = builder.Build();',
|
|
52
|
-
`var app = builder.Build();\n\n// <backlist:middleware>\n// </backlist:middleware>\n`
|
|
53
|
-
);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
await fs.writeFile(programCsPath, content);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function insertBetweenMarkers(content, markerName, insertText) {
|
|
60
|
-
const start = `// <backlist:${markerName}>`;
|
|
61
|
-
const end = `// </backlist:${markerName}>`;
|
|
62
|
-
|
|
63
|
-
const s = content.indexOf(start);
|
|
64
|
-
const e = content.indexOf(end);
|
|
65
|
-
if (s === -1 || e === -1 || e < s) return content;
|
|
66
|
-
|
|
67
|
-
const before = content.slice(0, s + start.length);
|
|
68
|
-
const after = content.slice(e);
|
|
69
|
-
|
|
70
|
-
return `${before}\n${insertText}\n${after}`;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
8
|
async function generateDotnetProject(options) {
|
|
74
9
|
const { projectDir, projectName, frontendSrcDir } = options;
|
|
75
10
|
|
|
76
11
|
try {
|
|
77
|
-
|
|
12
|
+
// --- Step 1: Analysis & Model Identification ---
|
|
13
|
+
console.log(chalk.blue(' -> Analyzing frontend for C# backend...'));
|
|
78
14
|
const endpoints = await analyzeFrontend(frontendSrcDir);
|
|
15
|
+
const modelsToGenerate = new Map();
|
|
16
|
+
endpoints.forEach(ep => {
|
|
17
|
+
// For C#, we create a model if schemaFields exist for any endpoint related to a controller
|
|
18
|
+
if (ep.schemaFields && ep.controllerName !== 'Default' && !modelsToGenerate.has(ep.controllerName)) {
|
|
19
|
+
modelsToGenerate.set(ep.controllerName, {
|
|
20
|
+
name: ep.controllerName,
|
|
21
|
+
fields: Object.entries(ep.schemaFields).map(([key, type]) => ({ name: key, type }))
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
});
|
|
79
25
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
26
|
+
if (modelsToGenerate.size > 0) {
|
|
27
|
+
console.log(chalk.green(` -> Identified ${modelsToGenerate.size} models/controllers to generate.`));
|
|
28
|
+
} else {
|
|
29
|
+
console.log(chalk.yellow(' -> No API calls with body data found. A basic API project will be created without models.'));
|
|
30
|
+
}
|
|
84
31
|
|
|
85
|
-
//
|
|
32
|
+
// --- Step 2: Create Base .NET Project using `dotnet new` ---
|
|
86
33
|
console.log(chalk.blue(' -> Scaffolding .NET Core Web API project...'));
|
|
87
34
|
await execa('dotnet', ['new', 'webapi', '-n', projectName, '-o', projectDir, '--no-https']);
|
|
35
|
+
|
|
36
|
+
// --- Step 3: Add Required NuGet Packages ---
|
|
37
|
+
if (modelsToGenerate.size > 0) {
|
|
38
|
+
console.log(chalk.blue(' -> Adding NuGet packages (Entity Framework Core)...'));
|
|
39
|
+
const packages = [
|
|
40
|
+
'Microsoft.EntityFrameworkCore.Design',
|
|
41
|
+
'Microsoft.EntityFrameworkCore.InMemory' // Using InMemory for a simple, runnable setup
|
|
42
|
+
// For a real DB, a user would add: 'Npgsql.EntityFrameworkCore.PostgreSQL' or 'Microsoft.EntityFrameworkCore.SqlServer'
|
|
43
|
+
];
|
|
44
|
+
for (const pkg of packages) {
|
|
45
|
+
await execa('dotnet', ['add', 'package', pkg], { cwd: projectDir });
|
|
46
|
+
}
|
|
47
|
+
}
|
|
88
48
|
|
|
89
|
-
//
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
49
|
+
// --- Step 4: Generate Models and DbContext from Templates ---
|
|
50
|
+
if (modelsToGenerate.size > 0) {
|
|
51
|
+
console.log(chalk.blue(' -> Generating EF Core models and DbContext...'));
|
|
52
|
+
const modelsDir = path.join(projectDir, 'Models');
|
|
53
|
+
const dataDir = path.join(projectDir, 'Data');
|
|
54
|
+
await fs.ensureDir(modelsDir);
|
|
55
|
+
await fs.ensureDir(dataDir);
|
|
56
|
+
|
|
57
|
+
for (const [modelName, modelData] of modelsToGenerate.entries()) {
|
|
58
|
+
await renderAndWrite(
|
|
59
|
+
getTemplatePath('dotnet/partials/Model.cs.ejs'),
|
|
60
|
+
path.join(modelsDir, `${modelName}.cs`),
|
|
61
|
+
{ projectName, modelName, model: modelData }
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
100
65
|
await renderAndWrite(
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
66
|
+
getTemplatePath('dotnet/partials/DbContext.cs.ejs'),
|
|
67
|
+
path.join(dataDir, 'ApplicationDbContext.cs'),
|
|
68
|
+
{ projectName, modelsToGenerate: Array.from(modelsToGenerate.values()) }
|
|
104
69
|
);
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// Generate Controllers from endpoints (not CRUD stub)
|
|
109
|
-
console.log(chalk.blue(' -> Generating controllers from detected endpoints...'));
|
|
110
|
-
const controllersDir = path.join(projectDir, 'Controllers');
|
|
111
|
-
await fs.ensureDir(controllersDir);
|
|
112
|
-
|
|
113
|
-
for (const [controllerName, controllerEndpoints] of byController.entries()) {
|
|
114
|
-
if (controllerName === 'Default') continue;
|
|
115
|
-
|
|
116
|
-
await renderAndWrite(
|
|
117
|
-
getTemplatePath('dotnet/partials/Controller.FromEndpoints.cs.ejs'),
|
|
118
|
-
path.join(controllersDir, `${controllerName}Controller.cs`),
|
|
119
|
-
{ projectName, controllerName, endpoints: controllerEndpoints }
|
|
120
|
-
);
|
|
121
70
|
}
|
|
122
71
|
|
|
123
|
-
//
|
|
124
|
-
console.log(chalk.blue(' -> Configuring Program.cs
|
|
72
|
+
// --- Step 5: Configure Services in Program.cs ---
|
|
73
|
+
console.log(chalk.blue(' -> Configuring services in Program.cs...'));
|
|
125
74
|
const programCsPath = path.join(projectDir, 'Program.cs');
|
|
126
|
-
await ensureProgramMarkers(programCsPath);
|
|
127
|
-
|
|
128
75
|
let programCsContent = await fs.readFile(programCsPath, 'utf-8');
|
|
129
|
-
|
|
130
|
-
|
|
76
|
+
|
|
77
|
+
let usingStatements = 'using Microsoft.EntityFrameworkCore;\nusing '+projectName+'.Data;\n';
|
|
78
|
+
programCsContent = usingStatements + programCsContent;
|
|
79
|
+
|
|
80
|
+
let dbContextService = `// Configure the database context\nbuilder.Services.AddDbContext<ApplicationDbContext>(opt => opt.UseInMemoryDatabase("MyDb"));`;
|
|
81
|
+
programCsContent = programCsContent.replace('builder.Services.AddControllers();', `builder.Services.AddControllers();\n\n${dbContextService}`);
|
|
82
|
+
|
|
83
|
+
// Enable CORS to allow frontend communication
|
|
84
|
+
const corsPolicy = `
|
|
131
85
|
builder.Services.AddCors(options =>
|
|
132
86
|
{
|
|
133
|
-
options.AddDefaultPolicy(
|
|
134
|
-
policy
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
programCsContent =
|
|
87
|
+
options.AddDefaultPolicy(
|
|
88
|
+
policy =>
|
|
89
|
+
{
|
|
90
|
+
policy.WithOrigins("http://localhost:3000", "http://localhost:5173") // Common frontend dev ports
|
|
91
|
+
.AllowAnyHeader()
|
|
92
|
+
.AllowAnyMethod();
|
|
93
|
+
});
|
|
94
|
+
});`;
|
|
95
|
+
programCsContent = programCsContent.replace('var app = builder.Build();', `${corsPolicy}\n\nvar app = builder.Build();\n\napp.UseCors();`);
|
|
142
96
|
|
|
143
97
|
await fs.writeFile(programCsPath, programCsContent);
|
|
144
98
|
|
|
145
|
-
//
|
|
99
|
+
// --- Step 6: Generate Controllers with full CRUD ---
|
|
100
|
+
console.log(chalk.blue(' -> Generating controllers with CRUD logic...'));
|
|
101
|
+
await fs.remove(path.join(projectDir, 'Controllers', 'WeatherForecastController.cs'));
|
|
102
|
+
await fs.remove(path.join(projectDir, 'WeatherForecast.cs'));
|
|
103
|
+
|
|
104
|
+
const controllersToGenerate = new Set(Array.from(modelsToGenerate.keys()));
|
|
105
|
+
// Also add controllers for endpoints that didn't have a body but were detected
|
|
106
|
+
endpoints.forEach(ep => {
|
|
107
|
+
if (ep.controllerName !== 'Default') controllersToGenerate.add(ep.controllerName);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
for (const controllerName of controllersToGenerate) {
|
|
111
|
+
await renderAndWrite(
|
|
112
|
+
getTemplatePath('dotnet/partials/Controller.cs.ejs'),
|
|
113
|
+
path.join(projectDir, 'Controllers', `${controllerName}Controller.cs`),
|
|
114
|
+
{ projectName, controllerName }
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// --- Step 7: Generate README ---
|
|
146
119
|
await renderAndWrite(
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
120
|
+
getTemplatePath('dotnet/partials/README.md.ejs'),
|
|
121
|
+
path.join(projectDir, 'README.md'),
|
|
122
|
+
{ projectName }
|
|
150
123
|
);
|
|
151
124
|
|
|
152
125
|
console.log(chalk.green(' -> C# backend generation is complete!'));
|
|
153
126
|
|
|
154
127
|
} catch (error) {
|
|
128
|
+
// Re-throw the error to be caught by the main CLI handler
|
|
155
129
|
throw error;
|
|
156
130
|
}
|
|
157
131
|
}
|
package/src/generators/java.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const chalk = require('chalk');
|
|
2
|
+
const { execa } = require('execa');
|
|
2
3
|
const fs = require('fs-extra');
|
|
3
4
|
const path = require('path');
|
|
4
5
|
const axios = require('axios');
|
|
@@ -7,10 +8,8 @@ const { analyzeFrontend } = require('../analyzer');
|
|
|
7
8
|
const { renderAndWrite, getTemplatePath } = require('./template');
|
|
8
9
|
|
|
9
10
|
function sanitizeArtifactId(name) {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
.replace(/[^a-z0-9\-]/g, '-')
|
|
13
|
-
.replace(/-+/g, '-');
|
|
11
|
+
// Lowercase, keep letters, numbers and dashes; replace others with dashes
|
|
12
|
+
return String(name || 'backend').toLowerCase().replace(/[^a-z0-9\-]/g, '-').replace(/-+/g, '-');
|
|
14
13
|
}
|
|
15
14
|
|
|
16
15
|
async function downloadInitializrZip({ groupId, artifactId, name, bootVersion, dependencies }) {
|
|
@@ -20,16 +19,20 @@ async function downloadInitializrZip({ groupId, artifactId, name, bootVersion, d
|
|
|
20
19
|
groupId,
|
|
21
20
|
artifactId,
|
|
22
21
|
name,
|
|
23
|
-
packageName: `${groupId}.${artifactId.replace(/-/g, '')}`,
|
|
22
|
+
packageName: `${groupId}.${artifactId.replace(/-/g, '')}`, // com.example.myapp
|
|
24
23
|
dependencies: dependencies.join(','),
|
|
25
24
|
});
|
|
25
|
+
|
|
26
26
|
if (bootVersion) params.set('bootVersion', bootVersion);
|
|
27
27
|
|
|
28
28
|
const url = `https://start.spring.io/starter.zip?${params.toString()}`;
|
|
29
|
-
|
|
29
|
+
|
|
30
|
+
const res = await axios.get(url, {
|
|
30
31
|
responseType: 'stream',
|
|
31
32
|
headers: { Accept: 'application/zip' }
|
|
32
33
|
});
|
|
34
|
+
|
|
35
|
+
return res;
|
|
33
36
|
}
|
|
34
37
|
|
|
35
38
|
async function extractZipStream(stream, dest) {
|
|
@@ -41,186 +44,135 @@ async function extractZipStream(stream, dest) {
|
|
|
41
44
|
});
|
|
42
45
|
}
|
|
43
46
|
|
|
44
|
-
function groupByController(endpoints) {
|
|
45
|
-
const map = new Map();
|
|
46
|
-
for (const ep of endpoints || []) {
|
|
47
|
-
const c = ep.controllerName || 'Default';
|
|
48
|
-
if (!map.has(c)) map.set(c, []);
|
|
49
|
-
map.get(c).push(ep);
|
|
50
|
-
}
|
|
51
|
-
return map;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
function collectModelsForJava(endpointsByController) {
|
|
55
|
-
// Produces one Entity per controller + DTOs per endpoint body/response if provided by analyzer.
|
|
56
|
-
const entities = new Map(); // controllerName -> {name, fields}
|
|
57
|
-
const dtos = new Map(); // dtoName -> {name, fields}
|
|
58
|
-
|
|
59
|
-
for (const [controllerName, eps] of endpointsByController.entries()) {
|
|
60
|
-
if (controllerName === 'Default') continue;
|
|
61
|
-
|
|
62
|
-
// Entity fields heuristic: merge requestBody fields across endpoints
|
|
63
|
-
const mergedFields = {};
|
|
64
|
-
for (const ep of eps) {
|
|
65
|
-
if (ep.requestBody?.fields) {
|
|
66
|
-
for (const [k, t] of Object.entries(ep.requestBody.fields)) mergedFields[k] = t;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
if (Object.keys(mergedFields).length > 0) {
|
|
71
|
-
entities.set(controllerName, { name: controllerName, fields: mergedFields });
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// DTOs from analyzer if exists
|
|
75
|
-
for (const ep of eps) {
|
|
76
|
-
if (ep.requestBody?.modelName && ep.requestBody?.fields) {
|
|
77
|
-
dtos.set(ep.requestBody.modelName, { name: ep.requestBody.modelName, fields: ep.requestBody.fields });
|
|
78
|
-
}
|
|
79
|
-
if (ep.responseBody?.modelName && ep.responseBody?.fields) {
|
|
80
|
-
dtos.set(ep.responseBody.modelName, { name: ep.responseBody.modelName, fields: ep.responseBody.fields });
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
return { entities, dtos };
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
async function upsertApplicationProperties(projectDir, artifactId) {
|
|
89
|
-
const propsPath = path.join(projectDir, 'src', 'main', 'resources', 'application.properties');
|
|
90
|
-
if (!await fs.pathExists(propsPath)) return;
|
|
91
|
-
|
|
92
|
-
const start = '# <backlist:db>';
|
|
93
|
-
const end = '# </backlist:db>';
|
|
94
|
-
|
|
95
|
-
const dbProps = [
|
|
96
|
-
start,
|
|
97
|
-
`spring.datasource.url=jdbc:postgresql://localhost:5432/${artifactId}`,
|
|
98
|
-
`spring.datasource.username=postgres`,
|
|
99
|
-
`spring.datasource.password=password`,
|
|
100
|
-
`spring.jpa.hibernate.ddl-auto=update`,
|
|
101
|
-
`spring.jpa.show-sql=true`,
|
|
102
|
-
end,
|
|
103
|
-
''
|
|
104
|
-
].join('\n');
|
|
105
|
-
|
|
106
|
-
const current = await fs.readFile(propsPath, 'utf-8');
|
|
107
|
-
|
|
108
|
-
if (current.includes(start) && current.includes(end)) {
|
|
109
|
-
// replace existing block
|
|
110
|
-
const replaced = current.replace(new RegExp(`${start}[\\s\\S]*?${end}`), dbProps.trim());
|
|
111
|
-
await fs.writeFile(propsPath, replaced);
|
|
112
|
-
} else {
|
|
113
|
-
await fs.appendFile(propsPath, `\n\n${dbProps}`);
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
47
|
async function generateJavaProject(options) {
|
|
118
48
|
const { projectDir, projectName, frontendSrcDir } = options;
|
|
119
49
|
const groupId = 'com.backlist.generated';
|
|
120
50
|
const artifactId = sanitizeArtifactId(projectName || 'backend');
|
|
121
|
-
const
|
|
51
|
+
const name = projectName || 'backend';
|
|
122
52
|
|
|
123
53
|
try {
|
|
124
|
-
console.log(chalk.blue(' ->
|
|
54
|
+
console.log(chalk.blue(' -> Contacting Spring Initializr to download a base Spring Boot project...'));
|
|
125
55
|
|
|
126
|
-
|
|
56
|
+
// Primary attempt: current stable Boot version. If this fails, we’ll retry without bootVersion.
|
|
57
|
+
const deps = ['web', 'data-jpa', 'lombok', 'postgresql']; // valid Initializr ids
|
|
127
58
|
|
|
128
59
|
let response;
|
|
129
60
|
try {
|
|
130
61
|
response = await downloadInitializrZip({
|
|
131
|
-
groupId,
|
|
132
|
-
|
|
62
|
+
groupId,
|
|
63
|
+
artifactId,
|
|
64
|
+
name,
|
|
65
|
+
bootVersion: '3.3.4', // current stable; adjust as needed
|
|
133
66
|
dependencies: deps
|
|
134
67
|
});
|
|
135
68
|
} catch (err) {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
69
|
+
// Fallback – remove bootVersion and also try smaller dependency set if needed
|
|
70
|
+
const fallbackDeps = ['web', 'data-jpa', 'lombok'];
|
|
71
|
+
try {
|
|
72
|
+
console.log(chalk.yellow(' -> Initial attempt failed. Retrying with default Boot version...'));
|
|
73
|
+
response = await downloadInitializrZip({
|
|
74
|
+
groupId,
|
|
75
|
+
artifactId,
|
|
76
|
+
name,
|
|
77
|
+
bootVersion: '', // let Initializr pick latest
|
|
78
|
+
dependencies: deps
|
|
79
|
+
});
|
|
80
|
+
} catch {
|
|
81
|
+
console.log(chalk.yellow(' -> Second attempt failed. Retrying with minimal dependencies...'));
|
|
82
|
+
response = await downloadInitializrZip({
|
|
83
|
+
groupId,
|
|
84
|
+
artifactId,
|
|
85
|
+
name,
|
|
86
|
+
bootVersion: '',
|
|
87
|
+
dependencies: fallbackDeps
|
|
88
|
+
});
|
|
89
|
+
}
|
|
142
90
|
}
|
|
143
91
|
|
|
144
|
-
console.log(chalk.blue(' -> Unzipping...'));
|
|
92
|
+
console.log(chalk.blue(' -> Unzipping the Spring Boot project...'));
|
|
145
93
|
await extractZipStream(response.data, projectDir);
|
|
146
94
|
|
|
147
|
-
|
|
95
|
+
// Analyze frontend and plan entities/controllers
|
|
148
96
|
const endpoints = await analyzeFrontend(frontendSrcDir);
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
await renderAndWrite(
|
|
192
|
-
getTemplatePath('java-spring/partials/Dto.java.ejs'),
|
|
193
|
-
path.join(dtoDir, `${dto.name}.java`),
|
|
194
|
-
{ basePackage, dto }
|
|
195
|
-
);
|
|
97
|
+
const modelsToGenerate = new Map();
|
|
98
|
+
(Array.isArray(endpoints) ? endpoints : []).forEach(ep => {
|
|
99
|
+
if (ep && ep.schemaFields && ep.controllerName && ep.controllerName !== 'Default' && !modelsToGenerate.has(ep.controllerName)) {
|
|
100
|
+
modelsToGenerate.set(ep.controllerName, {
|
|
101
|
+
name: ep.controllerName,
|
|
102
|
+
fields: Object.entries(ep.schemaFields).map(([key, type]) => ({ name: key, type }))
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
// Generate Entities/Repositories/Controllers (basic)
|
|
108
|
+
if (modelsToGenerate.size > 0) {
|
|
109
|
+
console.log(chalk.blue(' -> Generating Java entities, repositories, and controllers...'));
|
|
110
|
+
|
|
111
|
+
// Spring Initializr zips project as <artifactId> root folder; if you extracted to projectDir,
|
|
112
|
+
// files are already in the right place. Compute Java src path:
|
|
113
|
+
const javaSrcRoot = path.join(projectDir, 'src', 'main', 'java', ...groupId.split('.'), artifactId.replace(/-/g, ''));
|
|
114
|
+
const entityDir = path.join(javaSrcRoot, 'model');
|
|
115
|
+
const repoDir = path.join(javaSrcRoot, 'repository');
|
|
116
|
+
const controllerDir = path.join(javaSrcRoot, 'controller');
|
|
117
|
+
|
|
118
|
+
await fs.ensureDir(entityDir);
|
|
119
|
+
await fs.ensureDir(repoDir);
|
|
120
|
+
await fs.ensureDir(controllerDir);
|
|
121
|
+
|
|
122
|
+
for (const [modelName, modelData] of modelsToGenerate.entries()) {
|
|
123
|
+
await renderAndWrite(
|
|
124
|
+
getTemplatePath('java-spring/partials/Entity.java.ejs'),
|
|
125
|
+
path.join(entityDir, `${modelName}.java`),
|
|
126
|
+
{ group: groupId, projectName: artifactId.replace(/-/g, ''), modelName, model: modelData }
|
|
127
|
+
);
|
|
128
|
+
await renderAndWrite(
|
|
129
|
+
getTemplatePath('java-spring/partials/Repository.java.ejs'),
|
|
130
|
+
path.join(repoDir, `${modelName}Repository.java`),
|
|
131
|
+
{ group: groupId, projectName: artifactId.replace(/-/g, ''), modelName }
|
|
132
|
+
);
|
|
133
|
+
await renderAndWrite(
|
|
134
|
+
getTemplatePath('java-spring/partials/Controller.java.ejs'),
|
|
135
|
+
path.join(controllerDir, `${modelName}Controller.java`),
|
|
136
|
+
{ group: groupId, projectName: artifactId.replace(/-/g, ''), controllerName: modelName, model: modelData }
|
|
137
|
+
);
|
|
138
|
+
}
|
|
196
139
|
}
|
|
197
140
|
|
|
198
|
-
//
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
);
|
|
141
|
+
// Append DB config (PostgreSQL) to application.properties (non-fatal if fails)
|
|
142
|
+
try {
|
|
143
|
+
const propsPath = path.join(projectDir, 'src', 'main', 'resources', 'application.properties');
|
|
144
|
+
const dbProps = [
|
|
145
|
+
`\n\n# --- Auto-generated by create-backlist ---`,
|
|
146
|
+
`spring.datasource.url=jdbc:postgresql://localhost:5432/${artifactId}`,
|
|
147
|
+
`spring.datasource.username=postgres`,
|
|
148
|
+
`spring.datasource.password=password`,
|
|
149
|
+
`spring.jpa.hibernate.ddl-auto=update`,
|
|
150
|
+
`spring.jpa.show-sql=true`,
|
|
151
|
+
].join('\n');
|
|
152
|
+
await fs.appendFile(propsPath, dbProps);
|
|
153
|
+
} catch (e) {
|
|
154
|
+
console.log(chalk.yellow(' -> Could not update application.properties (continuing).'));
|
|
212
155
|
}
|
|
213
156
|
|
|
214
|
-
// application.properties idempotent update
|
|
215
|
-
await upsertApplicationProperties(projectDir, artifactId);
|
|
216
|
-
|
|
217
157
|
console.log(chalk.green(' -> Java (Spring Boot) backend generation is complete!'));
|
|
218
158
|
console.log(chalk.yellow('\nNext steps:'));
|
|
219
159
|
console.log(chalk.cyan(` cd ${path.basename(projectDir)}`));
|
|
220
|
-
console.log(chalk.cyan(' ./mvnw spring-boot:run'));
|
|
160
|
+
console.log(chalk.cyan(' ./mvnw spring-boot:run # or use your IDE to run Application class'));
|
|
221
161
|
|
|
222
162
|
} catch (error) {
|
|
223
163
|
if (error.response && error.response.status) {
|
|
164
|
+
console.error(chalk.red(` -> Initializr error status: ${error.response.status}`));
|
|
165
|
+
if (error.response.data) {
|
|
166
|
+
try {
|
|
167
|
+
// Try read error text body for hints
|
|
168
|
+
const text = (await (async () => {
|
|
169
|
+
let buf = '';
|
|
170
|
+
for await (const chunk of error.response.data) buf += chunk.toString();
|
|
171
|
+
return buf;
|
|
172
|
+
})());
|
|
173
|
+
console.error(chalk.yellow(' -> Initializr response body:'), text);
|
|
174
|
+
} catch {}
|
|
175
|
+
}
|
|
224
176
|
throw new Error(`Failed to download from Spring Initializr. Status: ${error.response.status}`);
|
|
225
177
|
}
|
|
226
178
|
throw error;
|