create-backlist 6.1.6 → 6.1.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/package.json +1 -1
- package/src/analyzer.js +410 -99
- package/src/generators/dotnet.js +1 -1
- package/src/generators/java.js +154 -97
- package/src/generators/node.js +293 -232
- package/src/templates/java-spring/partials/ApplicationSeeder.java.ejs +29 -7
- package/src/templates/java-spring/partials/AuthController.java.ejs +45 -14
- package/src/templates/java-spring/partials/Controller.java.ejs +25 -11
- package/src/templates/java-spring/partials/Dockerfile.ejs +25 -3
- package/src/templates/java-spring/partials/Entity.java.ejs +28 -3
- package/src/templates/java-spring/partials/JwtAuthFilter.java.ejs +41 -7
- package/src/templates/java-spring/partials/JwtService.java.ejs +47 -12
- package/src/templates/java-spring/partials/Repository.java.ejs +8 -1
- package/src/templates/java-spring/partials/Service.java.ejs +30 -6
- package/src/templates/java-spring/partials/User.java.ejs +26 -3
- package/src/templates/java-spring/partials/UserDetailsServiceImpl.java.ejs +10 -4
- package/src/templates/java-spring/partials/UserRepository.java.ejs +6 -0
- package/src/templates/java-spring/partials/docker-compose.yml.ejs +27 -5
- package/src/templates/node-ts-express/base/server.ts +63 -9
- package/src/templates/node-ts-express/base/tsconfig.json +19 -4
- package/src/templates/node-ts-express/partials/ApiDocs.ts.ejs +24 -9
- package/src/templates/node-ts-express/partials/App.test.ts.ejs +47 -27
- package/src/templates/node-ts-express/partials/Auth.controller.ts.ejs +68 -45
- package/src/templates/node-ts-express/partials/Auth.middleware.ts.ejs +45 -14
- package/src/templates/node-ts-express/partials/Auth.routes.ts.ejs +44 -5
- package/src/templates/node-ts-express/partials/Controller.ts.ejs +30 -16
- package/src/templates/node-ts-express/partials/Dockerfile.ejs +33 -11
- package/src/templates/node-ts-express/partials/Model.cs.ejs +38 -5
- package/src/templates/node-ts-express/partials/Model.ts.ejs +42 -12
- package/src/templates/node-ts-express/partials/PrismaController.ts.ejs +57 -23
- package/src/templates/node-ts-express/partials/PrismaSchema.prisma.ejs +33 -10
- package/src/templates/node-ts-express/partials/README.md.ejs +8 -10
- package/src/templates/node-ts-express/partials/Seeder.ts.ejs +99 -56
- package/src/templates/node-ts-express/partials/docker-compose.yml.ejs +30 -3
- package/src/templates/node-ts-express/partials/package.json.ejs +12 -7
- package/src/templates/node-ts-express/partials/routes.ts.ejs +31 -18
package/src/generators/java.js
CHANGED
|
@@ -1,35 +1,38 @@
|
|
|
1
|
-
const chalk = require(
|
|
2
|
-
const
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
const { analyzeFrontend } = require(
|
|
8
|
-
const { renderAndWrite, getTemplatePath } = require(
|
|
1
|
+
const chalk = require("chalk");
|
|
2
|
+
const fs = require("fs-extra");
|
|
3
|
+
const path = require("path");
|
|
4
|
+
const axios = require("axios");
|
|
5
|
+
const unzipper = require("unzipper");
|
|
6
|
+
|
|
7
|
+
const { analyzeFrontend } = require("../analyzer");
|
|
8
|
+
const { renderAndWrite, getTemplatePath } = require("./template");
|
|
9
9
|
|
|
10
10
|
function sanitizeArtifactId(name) {
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
return String(name || "backend")
|
|
12
|
+
.toLowerCase()
|
|
13
|
+
.replace(/[^a-z0-9\-]/g, "-")
|
|
14
|
+
.replace(/-+/g, "-")
|
|
15
|
+
.replace(/^-+|-+$/g, "");
|
|
13
16
|
}
|
|
14
17
|
|
|
15
18
|
async function downloadInitializrZip({ groupId, artifactId, name, bootVersion, dependencies }) {
|
|
16
19
|
const params = new URLSearchParams({
|
|
17
|
-
type:
|
|
18
|
-
language:
|
|
20
|
+
type: "maven-project",
|
|
21
|
+
language: "java",
|
|
19
22
|
groupId,
|
|
20
23
|
artifactId,
|
|
21
24
|
name,
|
|
22
|
-
packageName: `${groupId}.${artifactId.replace(/-/g,
|
|
23
|
-
dependencies: dependencies.join(
|
|
25
|
+
packageName: `${groupId}.${artifactId.replace(/-/g, "")}`,
|
|
26
|
+
dependencies: dependencies.join(","),
|
|
24
27
|
});
|
|
25
28
|
|
|
26
|
-
if (bootVersion) params.set(
|
|
29
|
+
if (bootVersion) params.set("bootVersion", bootVersion);
|
|
27
30
|
|
|
28
31
|
const url = `https://start.spring.io/starter.zip?${params.toString()}`;
|
|
29
32
|
|
|
30
33
|
const res = await axios.get(url, {
|
|
31
|
-
responseType:
|
|
32
|
-
headers: { Accept:
|
|
34
|
+
responseType: "stream",
|
|
35
|
+
headers: { Accept: "application/zip" },
|
|
33
36
|
});
|
|
34
37
|
|
|
35
38
|
return res;
|
|
@@ -38,143 +41,197 @@ async function downloadInitializrZip({ groupId, artifactId, name, bootVersion, d
|
|
|
38
41
|
async function extractZipStream(stream, dest) {
|
|
39
42
|
await new Promise((resolve, reject) => {
|
|
40
43
|
const out = stream.pipe(unzipper.Extract({ path: dest }));
|
|
41
|
-
out.on(
|
|
42
|
-
out.on(
|
|
43
|
-
out.on(
|
|
44
|
+
out.on("close", resolve);
|
|
45
|
+
out.on("finish", resolve);
|
|
46
|
+
out.on("error", reject);
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function appendApplicationProperties(projectDir, artifactId) {
|
|
51
|
+
try {
|
|
52
|
+
const propsPath = path.join(projectDir, "src", "main", "resources", "application.properties");
|
|
53
|
+
const dbProps = [
|
|
54
|
+
"",
|
|
55
|
+
"",
|
|
56
|
+
"# --- Auto-generated by create-backlist ---",
|
|
57
|
+
`spring.datasource.url=jdbc:postgresql://localhost:5432/${artifactId}`,
|
|
58
|
+
"spring.datasource.username=postgres",
|
|
59
|
+
"spring.datasource.password=password",
|
|
60
|
+
"spring.jpa.hibernate.ddl-auto=update",
|
|
61
|
+
"spring.jpa.show-sql=true",
|
|
62
|
+
].join("\n");
|
|
63
|
+
|
|
64
|
+
await fs.appendFile(propsPath, dbProps);
|
|
65
|
+
} catch {
|
|
66
|
+
console.log(chalk.yellow(" -> Could not update application.properties (continuing)."));
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function buildModelsFromEndpoints(endpoints) {
|
|
71
|
+
// Create models even when schemaFields is null (GET-only APIs).
|
|
72
|
+
const modelsToGenerate = new Map();
|
|
73
|
+
|
|
74
|
+
(Array.isArray(endpoints) ? endpoints : []).forEach((ep) => {
|
|
75
|
+
if (!ep || !ep.controllerName || ep.controllerName === "Default") return;
|
|
76
|
+
|
|
77
|
+
if (!modelsToGenerate.has(ep.controllerName)) {
|
|
78
|
+
modelsToGenerate.set(ep.controllerName, {
|
|
79
|
+
name: ep.controllerName,
|
|
80
|
+
fields: [],
|
|
81
|
+
endpoints: [],
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const m = modelsToGenerate.get(ep.controllerName);
|
|
86
|
+
|
|
87
|
+
m.endpoints.push({
|
|
88
|
+
method: ep.method,
|
|
89
|
+
route: ep.route || ep.path,
|
|
90
|
+
actionName: ep.actionName,
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
if (ep.schemaFields) {
|
|
94
|
+
for (const [key, type] of Object.entries(ep.schemaFields)) {
|
|
95
|
+
if (!m.fields.find((f) => f.name === key)) {
|
|
96
|
+
m.fields.push({ name: key, type });
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
44
100
|
});
|
|
101
|
+
|
|
102
|
+
return modelsToGenerate;
|
|
45
103
|
}
|
|
46
104
|
|
|
47
105
|
async function generateJavaProject(options) {
|
|
48
106
|
const { projectDir, projectName, frontendSrcDir } = options;
|
|
49
|
-
const groupId = 'com.backlist.generated';
|
|
50
|
-
const artifactId = sanitizeArtifactId(projectName || 'backend');
|
|
51
|
-
const name = projectName || 'backend';
|
|
52
107
|
|
|
53
|
-
|
|
54
|
-
|
|
108
|
+
const groupId = "com.backlist.generated";
|
|
109
|
+
const artifactId = sanitizeArtifactId(projectName || "backend");
|
|
110
|
+
const name = projectName || "backend";
|
|
55
111
|
|
|
56
|
-
|
|
57
|
-
|
|
112
|
+
try {
|
|
113
|
+
console.log(chalk.blue(" -> Contacting Spring Initializr to download a base Spring Boot project..."));
|
|
58
114
|
|
|
115
|
+
const deps = ["web", "data-jpa", "lombok", "postgresql"];
|
|
59
116
|
let response;
|
|
117
|
+
|
|
60
118
|
try {
|
|
61
119
|
response = await downloadInitializrZip({
|
|
62
120
|
groupId,
|
|
63
121
|
artifactId,
|
|
64
122
|
name,
|
|
65
|
-
bootVersion:
|
|
66
|
-
dependencies: deps
|
|
123
|
+
bootVersion: "3.3.4",
|
|
124
|
+
dependencies: deps,
|
|
67
125
|
});
|
|
68
|
-
} catch
|
|
69
|
-
|
|
70
|
-
const fallbackDeps = ['web', 'data-jpa', 'lombok'];
|
|
126
|
+
} catch {
|
|
127
|
+
console.log(chalk.yellow(" -> Initial attempt failed. Retrying with default Boot version..."));
|
|
71
128
|
try {
|
|
72
|
-
console.log(chalk.yellow(' -> Initial attempt failed. Retrying with default Boot version...'));
|
|
73
129
|
response = await downloadInitializrZip({
|
|
74
130
|
groupId,
|
|
75
131
|
artifactId,
|
|
76
132
|
name,
|
|
77
|
-
bootVersion:
|
|
78
|
-
dependencies: deps
|
|
133
|
+
bootVersion: "",
|
|
134
|
+
dependencies: deps,
|
|
79
135
|
});
|
|
80
136
|
} catch {
|
|
81
|
-
console.log(chalk.yellow(
|
|
137
|
+
console.log(chalk.yellow(" -> Second attempt failed. Retrying with minimal dependencies..."));
|
|
138
|
+
const fallbackDeps = ["web", "data-jpa", "lombok"];
|
|
82
139
|
response = await downloadInitializrZip({
|
|
83
140
|
groupId,
|
|
84
141
|
artifactId,
|
|
85
142
|
name,
|
|
86
|
-
bootVersion:
|
|
87
|
-
dependencies: fallbackDeps
|
|
143
|
+
bootVersion: "",
|
|
144
|
+
dependencies: fallbackDeps,
|
|
88
145
|
});
|
|
89
146
|
}
|
|
90
147
|
}
|
|
91
148
|
|
|
92
|
-
console.log(chalk.blue(
|
|
149
|
+
console.log(chalk.blue(" -> Unzipping the Spring Boot project..."));
|
|
150
|
+
await fs.ensureDir(projectDir);
|
|
93
151
|
await extractZipStream(response.data, projectDir);
|
|
94
152
|
|
|
95
|
-
|
|
153
|
+
console.log(chalk.blue(" -> Analyzing frontend for API endpoints..."));
|
|
96
154
|
const endpoints = await analyzeFrontend(frontendSrcDir);
|
|
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
155
|
|
|
107
|
-
|
|
156
|
+
if (!Array.isArray(endpoints) || endpoints.length === 0) {
|
|
157
|
+
console.log(chalk.yellow(" -> No endpoints found. Only base Spring project created."));
|
|
158
|
+
} else {
|
|
159
|
+
console.log(chalk.green(` -> Found ${endpoints.length} endpoints.`));
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const modelsToGenerate = buildModelsFromEndpoints(endpoints);
|
|
163
|
+
|
|
164
|
+
// Spring Initializr extracts into projectDir directly. Java package folder:
|
|
165
|
+
const javaSrcRoot = path.join(
|
|
166
|
+
projectDir,
|
|
167
|
+
"src",
|
|
168
|
+
"main",
|
|
169
|
+
"java",
|
|
170
|
+
...groupId.split("."),
|
|
171
|
+
artifactId.replace(/-/g, "")
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
// Always ensure base package dirs exist
|
|
175
|
+
await fs.ensureDir(javaSrcRoot);
|
|
176
|
+
|
|
108
177
|
if (modelsToGenerate.size > 0) {
|
|
109
|
-
console.log(chalk.blue(
|
|
178
|
+
console.log(chalk.blue(" -> Generating Java entities, repositories, and controllers..."));
|
|
110
179
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
const
|
|
114
|
-
const entityDir = path.join(javaSrcRoot, 'model');
|
|
115
|
-
const repoDir = path.join(javaSrcRoot, 'repository');
|
|
116
|
-
const controllerDir = path.join(javaSrcRoot, 'controller');
|
|
180
|
+
const entityDir = path.join(javaSrcRoot, "model");
|
|
181
|
+
const repoDir = path.join(javaSrcRoot, "repository");
|
|
182
|
+
const controllerDir = path.join(javaSrcRoot, "controller");
|
|
117
183
|
|
|
118
184
|
await fs.ensureDir(entityDir);
|
|
119
185
|
await fs.ensureDir(repoDir);
|
|
120
186
|
await fs.ensureDir(controllerDir);
|
|
121
187
|
|
|
122
|
-
for (const
|
|
188
|
+
for (const model of modelsToGenerate.values()) {
|
|
123
189
|
await renderAndWrite(
|
|
124
|
-
getTemplatePath(
|
|
125
|
-
path.join(entityDir, `${
|
|
126
|
-
{
|
|
190
|
+
getTemplatePath("java-spring/partials/Entity.java.ejs"),
|
|
191
|
+
path.join(entityDir, `${model.name}.java`),
|
|
192
|
+
{ projectName, groupId, artifactId, model }
|
|
127
193
|
);
|
|
194
|
+
|
|
128
195
|
await renderAndWrite(
|
|
129
|
-
getTemplatePath(
|
|
130
|
-
path.join(repoDir, `${
|
|
131
|
-
{
|
|
196
|
+
getTemplatePath("java-spring/partials/Repository.java.ejs"),
|
|
197
|
+
path.join(repoDir, `${model.name}Repository.java`),
|
|
198
|
+
{ projectName, groupId, artifactId, model }
|
|
132
199
|
);
|
|
200
|
+
|
|
133
201
|
await renderAndWrite(
|
|
134
|
-
getTemplatePath(
|
|
135
|
-
path.join(controllerDir, `${
|
|
136
|
-
{
|
|
202
|
+
getTemplatePath("java-spring/partials/Controller.java.ejs"),
|
|
203
|
+
path.join(controllerDir, `${model.name}Controller.java`),
|
|
204
|
+
{ projectName, groupId, artifactId, model }
|
|
137
205
|
);
|
|
138
206
|
}
|
|
207
|
+
} else {
|
|
208
|
+
console.log(chalk.yellow(" -> No models inferred (controllerName missing). Skipping entity/controller generation."));
|
|
139
209
|
}
|
|
140
210
|
|
|
141
|
-
|
|
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).'));
|
|
155
|
-
}
|
|
211
|
+
await appendApplicationProperties(projectDir, artifactId);
|
|
156
212
|
|
|
157
|
-
console.log(chalk.green(
|
|
158
|
-
console.log(chalk.yellow(
|
|
213
|
+
console.log(chalk.green(" -> Java (Spring Boot) backend generation is complete!"));
|
|
214
|
+
console.log(chalk.yellow("\nNext steps:"));
|
|
159
215
|
console.log(chalk.cyan(` cd ${path.basename(projectDir)}`));
|
|
160
|
-
console.log(chalk.cyan(
|
|
161
|
-
|
|
216
|
+
console.log(chalk.cyan(" ./mvnw spring-boot:run # or use your IDE to run the Application class"));
|
|
162
217
|
} catch (error) {
|
|
163
|
-
|
|
218
|
+
// Better Initializr error print
|
|
219
|
+
if (error && error.response && error.response.status) {
|
|
164
220
|
console.error(chalk.red(` -> Initializr error status: ${error.response.status}`));
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
} catch {}
|
|
221
|
+
|
|
222
|
+
try {
|
|
223
|
+
if (error.response.data) {
|
|
224
|
+
let text = "";
|
|
225
|
+
for await (const chunk of error.response.data) text += chunk.toString();
|
|
226
|
+
console.error(chalk.yellow(" -> Initializr response body:"), text);
|
|
227
|
+
}
|
|
228
|
+
} catch {
|
|
229
|
+
// ignore
|
|
175
230
|
}
|
|
231
|
+
|
|
176
232
|
throw new Error(`Failed to download from Spring Initializr. Status: ${error.response.status}`);
|
|
177
233
|
}
|
|
234
|
+
|
|
178
235
|
throw error;
|
|
179
236
|
}
|
|
180
237
|
}
|