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.
Files changed (36) hide show
  1. package/package.json +1 -1
  2. package/src/analyzer.js +410 -99
  3. package/src/generators/dotnet.js +1 -1
  4. package/src/generators/java.js +154 -97
  5. package/src/generators/node.js +293 -232
  6. package/src/templates/java-spring/partials/ApplicationSeeder.java.ejs +29 -7
  7. package/src/templates/java-spring/partials/AuthController.java.ejs +45 -14
  8. package/src/templates/java-spring/partials/Controller.java.ejs +25 -11
  9. package/src/templates/java-spring/partials/Dockerfile.ejs +25 -3
  10. package/src/templates/java-spring/partials/Entity.java.ejs +28 -3
  11. package/src/templates/java-spring/partials/JwtAuthFilter.java.ejs +41 -7
  12. package/src/templates/java-spring/partials/JwtService.java.ejs +47 -12
  13. package/src/templates/java-spring/partials/Repository.java.ejs +8 -1
  14. package/src/templates/java-spring/partials/Service.java.ejs +30 -6
  15. package/src/templates/java-spring/partials/User.java.ejs +26 -3
  16. package/src/templates/java-spring/partials/UserDetailsServiceImpl.java.ejs +10 -4
  17. package/src/templates/java-spring/partials/UserRepository.java.ejs +6 -0
  18. package/src/templates/java-spring/partials/docker-compose.yml.ejs +27 -5
  19. package/src/templates/node-ts-express/base/server.ts +63 -9
  20. package/src/templates/node-ts-express/base/tsconfig.json +19 -4
  21. package/src/templates/node-ts-express/partials/ApiDocs.ts.ejs +24 -9
  22. package/src/templates/node-ts-express/partials/App.test.ts.ejs +47 -27
  23. package/src/templates/node-ts-express/partials/Auth.controller.ts.ejs +68 -45
  24. package/src/templates/node-ts-express/partials/Auth.middleware.ts.ejs +45 -14
  25. package/src/templates/node-ts-express/partials/Auth.routes.ts.ejs +44 -5
  26. package/src/templates/node-ts-express/partials/Controller.ts.ejs +30 -16
  27. package/src/templates/node-ts-express/partials/Dockerfile.ejs +33 -11
  28. package/src/templates/node-ts-express/partials/Model.cs.ejs +38 -5
  29. package/src/templates/node-ts-express/partials/Model.ts.ejs +42 -12
  30. package/src/templates/node-ts-express/partials/PrismaController.ts.ejs +57 -23
  31. package/src/templates/node-ts-express/partials/PrismaSchema.prisma.ejs +33 -10
  32. package/src/templates/node-ts-express/partials/README.md.ejs +8 -10
  33. package/src/templates/node-ts-express/partials/Seeder.ts.ejs +99 -56
  34. package/src/templates/node-ts-express/partials/docker-compose.yml.ejs +30 -3
  35. package/src/templates/node-ts-express/partials/package.json.ejs +12 -7
  36. package/src/templates/node-ts-express/partials/routes.ts.ejs +31 -18
@@ -1,35 +1,38 @@
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');
6
- const unzipper = require('unzipper');
7
- const { analyzeFrontend } = require('../analyzer');
8
- const { renderAndWrite, getTemplatePath } = require('./template');
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
- // Lowercase, keep letters, numbers and dashes; replace others with dashes
12
- return String(name || 'backend').toLowerCase().replace(/[^a-z0-9\-]/g, '-').replace(/-+/g, '-');
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: 'maven-project',
18
- language: 'java',
20
+ type: "maven-project",
21
+ language: "java",
19
22
  groupId,
20
23
  artifactId,
21
24
  name,
22
- packageName: `${groupId}.${artifactId.replace(/-/g, '')}`, // com.example.myapp
23
- dependencies: dependencies.join(','),
25
+ packageName: `${groupId}.${artifactId.replace(/-/g, "")}`,
26
+ dependencies: dependencies.join(","),
24
27
  });
25
28
 
26
- if (bootVersion) params.set('bootVersion', bootVersion);
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: 'stream',
32
- headers: { Accept: 'application/zip' }
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('close', resolve);
42
- out.on('finish', resolve);
43
- out.on('error', reject);
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
- try {
54
- console.log(chalk.blue(' -> Contacting Spring Initializr to download a base Spring Boot project...'));
108
+ const groupId = "com.backlist.generated";
109
+ const artifactId = sanitizeArtifactId(projectName || "backend");
110
+ const name = projectName || "backend";
55
111
 
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
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: '3.3.4', // current stable; adjust as needed
66
- dependencies: deps
123
+ bootVersion: "3.3.4",
124
+ dependencies: deps,
67
125
  });
68
- } catch (err) {
69
- // Fallback remove bootVersion and also try smaller dependency set if needed
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: '', // let Initializr pick latest
78
- dependencies: deps
133
+ bootVersion: "",
134
+ dependencies: deps,
79
135
  });
80
136
  } catch {
81
- console.log(chalk.yellow(' -> Second attempt failed. Retrying with minimal dependencies...'));
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(' -> Unzipping the Spring Boot project...'));
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
- // Analyze frontend and plan entities/controllers
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
- // Generate Entities/Repositories/Controllers (basic)
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(' -> Generating Java entities, repositories, and controllers...'));
178
+ console.log(chalk.blue(" -> Generating Java entities, repositories, and controllers..."));
110
179
 
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');
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 [modelName, modelData] of modelsToGenerate.entries()) {
188
+ for (const model of modelsToGenerate.values()) {
123
189
  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 }
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('java-spring/partials/Repository.java.ejs'),
130
- path.join(repoDir, `${modelName}Repository.java`),
131
- { group: groupId, projectName: artifactId.replace(/-/g, ''), modelName }
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('java-spring/partials/Controller.java.ejs'),
135
- path.join(controllerDir, `${modelName}Controller.java`),
136
- { group: groupId, projectName: artifactId.replace(/-/g, ''), controllerName: modelName, model: modelData }
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
- // 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).'));
155
- }
211
+ await appendApplicationProperties(projectDir, artifactId);
156
212
 
157
- console.log(chalk.green(' -> Java (Spring Boot) backend generation is complete!'));
158
- console.log(chalk.yellow('\nNext steps:'));
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(' ./mvnw spring-boot:run # or use your IDE to run Application class'));
161
-
216
+ console.log(chalk.cyan(" ./mvnw spring-boot:run # or use your IDE to run the Application class"));
162
217
  } catch (error) {
163
- if (error.response && error.response.status) {
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
- 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 {}
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
  }