create-backlist 7.3.1 → 7.4.0

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 (44) hide show
  1. package/bin/index.js +483 -470
  2. package/bin/qa.js +103 -0
  3. package/package.json +7 -3
  4. package/src/env-resolver.js +70 -70
  5. package/src/generators/dotnet.js +134 -134
  6. package/src/generators/java.js +248 -248
  7. package/src/generators/js.js +345 -345
  8. package/src/generators/nestjs.js +277 -277
  9. package/src/generators/python.js +86 -86
  10. package/src/project-detector.js +131 -131
  11. package/src/qa/qa-engine.js +909 -0
  12. package/src/templates/dotnet/partials/Dockerfile.ejs +27 -27
  13. package/src/templates/dotnet/partials/docker-compose.yml.ejs +33 -33
  14. package/src/templates/js-express/base/server.js +59 -59
  15. package/src/templates/js-express/partials/Dockerfile.ejs +12 -12
  16. package/src/templates/js-express/partials/auth.controller.js.ejs +66 -66
  17. package/src/templates/js-express/partials/auth.middleware.js.ejs +19 -19
  18. package/src/templates/js-express/partials/auth.routes.js.ejs +9 -9
  19. package/src/templates/js-express/partials/controller.js.ejs +53 -53
  20. package/src/templates/js-express/partials/db.js.ejs +19 -19
  21. package/src/templates/js-express/partials/docker-compose.yml.ejs +46 -46
  22. package/src/templates/js-express/partials/model.js.ejs +18 -18
  23. package/src/templates/js-express/partials/package.json.ejs +17 -17
  24. package/src/templates/js-express/partials/prisma.schema.ejs +21 -21
  25. package/src/templates/js-express/partials/routes.js.ejs +19 -19
  26. package/src/templates/js-express/partials/seeder.js.ejs +103 -103
  27. package/src/templates/js-express/partials/service.js.ejs +51 -51
  28. package/src/templates/js-express/partials/swagger.js.ejs +30 -30
  29. package/src/templates/js-express/partials/test.js.ejs +46 -46
  30. package/src/templates/nestjs/base/app.module.ts +9 -9
  31. package/src/templates/nestjs/base/main.ts +23 -23
  32. package/src/templates/nestjs/base/tsconfig.json +21 -21
  33. package/src/templates/nestjs/partials/auth.controller.ts.ejs +17 -17
  34. package/src/templates/nestjs/partials/auth.module.ts.ejs +17 -17
  35. package/src/templates/nestjs/partials/auth.service.ts.ejs +70 -70
  36. package/src/templates/nestjs/partials/controller.ts.ejs +34 -34
  37. package/src/templates/nestjs/partials/create-dto.ts.ejs +22 -22
  38. package/src/templates/nestjs/partials/jwt-guard.ts.ejs +24 -24
  39. package/src/templates/nestjs/partials/module.ts.ejs +10 -10
  40. package/src/templates/nestjs/partials/package.json.ejs +27 -27
  41. package/src/templates/nestjs/partials/prisma.service.ts.ejs +13 -13
  42. package/src/templates/nestjs/partials/schema.ts.ejs +19 -19
  43. package/src/templates/nestjs/partials/service.ts.ejs +67 -67
  44. package/src/templates/nestjs/partials/update-dto.ts.ejs +4 -4
@@ -1,248 +1,248 @@
1
- import chalk from "chalk";
2
- import fs from "fs-extra";
3
- import path from "node:path";
4
- import axios from "axios";
5
- import unzipper from "unzipper";
6
-
7
- import { analyzeFrontend } from "../analyzer.js";
8
- import { renderAndWrite, getTemplatePath } from "./template.js";
9
-
10
- function sanitizeArtifactId(name) {
11
- return String(name || "backend")
12
- .toLowerCase()
13
- .replace(/[^a-z0-9\-]/g, "-")
14
- .replace(/-+/g, "-")
15
- .replace(/^-+|-+$/g, "");
16
- }
17
-
18
- async function downloadInitializrZip({ groupId, artifactId, name, bootVersion, dependencies }) {
19
- const params = new URLSearchParams({
20
- type: "maven-project",
21
- language: "java",
22
- groupId,
23
- artifactId,
24
- name,
25
- packageName: `${groupId}.${artifactId.replace(/-/g, "")}`,
26
- dependencies: dependencies.join(","),
27
- });
28
-
29
- if (bootVersion) params.set("bootVersion", bootVersion);
30
-
31
- const url = `https://start.spring.io/starter.zip?${params.toString()}`;
32
-
33
- const res = await axios.get(url, {
34
- responseType: "stream",
35
- headers: { Accept: "application/zip" },
36
- });
37
-
38
- return res;
39
- }
40
-
41
- async function extractZipStream(stream, dest) {
42
- await new Promise((resolve, reject) => {
43
- const out = stream.pipe(unzipper.Extract({ path: dest }));
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
- const modelsToGenerate = new Map();
72
-
73
- (Array.isArray(endpoints) ? endpoints : []).forEach((ep) => {
74
- if (!ep || !ep.controllerName || ep.controllerName === "Default") return;
75
-
76
- if (!modelsToGenerate.has(ep.controllerName)) {
77
- modelsToGenerate.set(ep.controllerName, {
78
- name: ep.controllerName,
79
- fields: [],
80
- endpoints: [],
81
- });
82
- }
83
-
84
- const m = modelsToGenerate.get(ep.controllerName);
85
-
86
- m.endpoints.push({
87
- method: ep.method,
88
- route: ep.route || ep.path,
89
- actionName: ep.actionName,
90
- });
91
-
92
- if (ep.schemaFields) {
93
- for (const [key, type] of Object.entries(ep.schemaFields)) {
94
- if (!m.fields.find((f) => f.name === key)) {
95
- m.fields.push({ name: key, type });
96
- }
97
- }
98
- }
99
- });
100
-
101
- return modelsToGenerate;
102
- }
103
-
104
- export async function generateJavaProject(options) {
105
- const { projectDir, projectName, frontendSrcDir } = options;
106
-
107
- const groupId = "com.backlist.generated";
108
- const artifactId = sanitizeArtifactId(projectName || "backend");
109
- const name = projectName || "backend";
110
-
111
- try {
112
- console.log(chalk.blue(" -> Contacting Spring Initializr to download a base Spring Boot project..."));
113
-
114
- const deps = ["web", "data-jpa", "lombok", "postgresql"];
115
- let response;
116
-
117
- try {
118
- response = await downloadInitializrZip({
119
- groupId,
120
- artifactId,
121
- name,
122
- bootVersion: "3.3.4",
123
- dependencies: deps,
124
- });
125
- } catch {
126
- console.log(chalk.yellow(" -> Initial attempt failed. Retrying with default Boot version..."));
127
- try {
128
- response = await downloadInitializrZip({
129
- groupId,
130
- artifactId,
131
- name,
132
- bootVersion: "",
133
- dependencies: deps,
134
- });
135
- } catch {
136
- console.log(chalk.yellow(" -> Second attempt failed. Retrying with minimal dependencies..."));
137
- const fallbackDeps = ["web", "data-jpa", "lombok"];
138
- response = await downloadInitializrZip({
139
- groupId,
140
- artifactId,
141
- name,
142
- bootVersion: "",
143
- dependencies: fallbackDeps,
144
- });
145
- }
146
- }
147
-
148
- console.log(chalk.blue(" -> Unzipping the Spring Boot project..."));
149
- await fs.ensureDir(projectDir);
150
- await extractZipStream(response.data, projectDir);
151
-
152
- console.log(chalk.blue(" -> Analyzing frontend for API endpoints..."));
153
- const endpoints = await analyzeFrontend(frontendSrcDir);
154
-
155
- if (!Array.isArray(endpoints) || endpoints.length === 0) {
156
- console.log(chalk.yellow(" -> No endpoints found. Only base Spring project created."));
157
- } else {
158
- console.log(chalk.green(` -> Found ${endpoints.length} endpoints.`));
159
- }
160
-
161
- const modelsToGenerate = buildModelsFromEndpoints(endpoints);
162
-
163
- const javaSrcRoot = path.join(
164
- projectDir,
165
- "src",
166
- "main",
167
- "java",
168
- ...groupId.split("."),
169
- artifactId.replace(/-/g, "")
170
- );
171
-
172
- await fs.ensureDir(javaSrcRoot);
173
-
174
- if (modelsToGenerate.size > 0) {
175
- console.log(chalk.blue(" -> Generating Java entities, repositories, services, and controllers..."));
176
-
177
- const entityDir = path.join(javaSrcRoot, "model");
178
- const repoDir = path.join(javaSrcRoot, "repository");
179
- const serviceDir = path.join(javaSrcRoot, "service");
180
- const controllerDir = path.join(javaSrcRoot, "controller");
181
-
182
- await fs.ensureDir(entityDir);
183
- await fs.ensureDir(repoDir);
184
- await fs.ensureDir(serviceDir);
185
- await fs.ensureDir(controllerDir);
186
-
187
- for (const model of modelsToGenerate.values()) {
188
- const commonData = {
189
- projectName,
190
- groupId,
191
- artifactId,
192
- group: groupId,
193
- model,
194
- modelName: model.name,
195
- controllerName: model.name
196
- };
197
-
198
- await renderAndWrite(
199
- getTemplatePath("java-spring/partials/Entity.java.ejs"),
200
- path.join(entityDir, `${model.name}.java`),
201
- commonData
202
- );
203
-
204
- await renderAndWrite(
205
- getTemplatePath("java-spring/partials/Repository.java.ejs"),
206
- path.join(repoDir, `${model.name}Repository.java`),
207
- commonData
208
- );
209
-
210
- await renderAndWrite(
211
- getTemplatePath("java-spring/partials/Service.java.ejs"),
212
- path.join(serviceDir, `${model.name}Service.java`),
213
- commonData
214
- );
215
-
216
- await renderAndWrite(
217
- getTemplatePath("java-spring/partials/Controller.java.ejs"),
218
- path.join(controllerDir, `${model.name}Controller.java`),
219
- commonData
220
- );
221
- }
222
- } else {
223
- console.log(chalk.yellow(" -> No models inferred. Skipping entity/controller generation."));
224
- }
225
-
226
- await appendApplicationProperties(projectDir, artifactId);
227
-
228
- console.log(chalk.blue(" -> Generating Docker files..."));
229
- await renderAndWrite(
230
- getTemplatePath("java-spring/partials/Dockerfile.ejs"),
231
- path.join(projectDir, "Dockerfile"),
232
- { projectName, artifactId }
233
- );
234
- await renderAndWrite(
235
- getTemplatePath("java-spring/partials/docker-compose.yml.ejs"),
236
- path.join(projectDir, "docker-compose.yml"),
237
- { projectName, artifactId }
238
- );
239
-
240
- console.log(chalk.green(" -> Java (Spring Boot) backend generation is complete!"));
241
- } catch (error) {
242
- if (error && error.response && error.response.status) {
243
- console.error(chalk.red(` -> Initializr error status: ${error.response.status}`));
244
- throw new Error(`Failed to download from Spring Initializr. Status: ${error.response.status}`);
245
- }
246
- throw error;
247
- }
248
- }
1
+ import chalk from "chalk";
2
+ import fs from "fs-extra";
3
+ import path from "node:path";
4
+ import axios from "axios";
5
+ import unzipper from "unzipper";
6
+
7
+ import { analyzeFrontend } from "../analyzer.js";
8
+ import { renderAndWrite, getTemplatePath } from "./template.js";
9
+
10
+ function sanitizeArtifactId(name) {
11
+ return String(name || "backend")
12
+ .toLowerCase()
13
+ .replace(/[^a-z0-9\-]/g, "-")
14
+ .replace(/-+/g, "-")
15
+ .replace(/^-+|-+$/g, "");
16
+ }
17
+
18
+ async function downloadInitializrZip({ groupId, artifactId, name, bootVersion, dependencies }) {
19
+ const params = new URLSearchParams({
20
+ type: "maven-project",
21
+ language: "java",
22
+ groupId,
23
+ artifactId,
24
+ name,
25
+ packageName: `${groupId}.${artifactId.replace(/-/g, "")}`,
26
+ dependencies: dependencies.join(","),
27
+ });
28
+
29
+ if (bootVersion) params.set("bootVersion", bootVersion);
30
+
31
+ const url = `https://start.spring.io/starter.zip?${params.toString()}`;
32
+
33
+ const res = await axios.get(url, {
34
+ responseType: "stream",
35
+ headers: { Accept: "application/zip" },
36
+ });
37
+
38
+ return res;
39
+ }
40
+
41
+ async function extractZipStream(stream, dest) {
42
+ await new Promise((resolve, reject) => {
43
+ const out = stream.pipe(unzipper.Extract({ path: dest }));
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
+ const modelsToGenerate = new Map();
72
+
73
+ (Array.isArray(endpoints) ? endpoints : []).forEach((ep) => {
74
+ if (!ep || !ep.controllerName || ep.controllerName === "Default") return;
75
+
76
+ if (!modelsToGenerate.has(ep.controllerName)) {
77
+ modelsToGenerate.set(ep.controllerName, {
78
+ name: ep.controllerName,
79
+ fields: [],
80
+ endpoints: [],
81
+ });
82
+ }
83
+
84
+ const m = modelsToGenerate.get(ep.controllerName);
85
+
86
+ m.endpoints.push({
87
+ method: ep.method,
88
+ route: ep.route || ep.path,
89
+ actionName: ep.actionName,
90
+ });
91
+
92
+ if (ep.schemaFields) {
93
+ for (const [key, type] of Object.entries(ep.schemaFields)) {
94
+ if (!m.fields.find((f) => f.name === key)) {
95
+ m.fields.push({ name: key, type });
96
+ }
97
+ }
98
+ }
99
+ });
100
+
101
+ return modelsToGenerate;
102
+ }
103
+
104
+ export async function generateJavaProject(options) {
105
+ const { projectDir, projectName, frontendSrcDir } = options;
106
+
107
+ const groupId = "com.backlist.generated";
108
+ const artifactId = sanitizeArtifactId(projectName || "backend");
109
+ const name = projectName || "backend";
110
+
111
+ try {
112
+ console.log(chalk.blue(" -> Contacting Spring Initializr to download a base Spring Boot project..."));
113
+
114
+ const deps = ["web", "data-jpa", "lombok", "postgresql"];
115
+ let response;
116
+
117
+ try {
118
+ response = await downloadInitializrZip({
119
+ groupId,
120
+ artifactId,
121
+ name,
122
+ bootVersion: "3.3.4",
123
+ dependencies: deps,
124
+ });
125
+ } catch {
126
+ console.log(chalk.yellow(" -> Initial attempt failed. Retrying with default Boot version..."));
127
+ try {
128
+ response = await downloadInitializrZip({
129
+ groupId,
130
+ artifactId,
131
+ name,
132
+ bootVersion: "",
133
+ dependencies: deps,
134
+ });
135
+ } catch {
136
+ console.log(chalk.yellow(" -> Second attempt failed. Retrying with minimal dependencies..."));
137
+ const fallbackDeps = ["web", "data-jpa", "lombok"];
138
+ response = await downloadInitializrZip({
139
+ groupId,
140
+ artifactId,
141
+ name,
142
+ bootVersion: "",
143
+ dependencies: fallbackDeps,
144
+ });
145
+ }
146
+ }
147
+
148
+ console.log(chalk.blue(" -> Unzipping the Spring Boot project..."));
149
+ await fs.ensureDir(projectDir);
150
+ await extractZipStream(response.data, projectDir);
151
+
152
+ console.log(chalk.blue(" -> Analyzing frontend for API endpoints..."));
153
+ const endpoints = await analyzeFrontend(frontendSrcDir);
154
+
155
+ if (!Array.isArray(endpoints) || endpoints.length === 0) {
156
+ console.log(chalk.yellow(" -> No endpoints found. Only base Spring project created."));
157
+ } else {
158
+ console.log(chalk.green(` -> Found ${endpoints.length} endpoints.`));
159
+ }
160
+
161
+ const modelsToGenerate = buildModelsFromEndpoints(endpoints);
162
+
163
+ const javaSrcRoot = path.join(
164
+ projectDir,
165
+ "src",
166
+ "main",
167
+ "java",
168
+ ...groupId.split("."),
169
+ artifactId.replace(/-/g, "")
170
+ );
171
+
172
+ await fs.ensureDir(javaSrcRoot);
173
+
174
+ if (modelsToGenerate.size > 0) {
175
+ console.log(chalk.blue(" -> Generating Java entities, repositories, services, and controllers..."));
176
+
177
+ const entityDir = path.join(javaSrcRoot, "model");
178
+ const repoDir = path.join(javaSrcRoot, "repository");
179
+ const serviceDir = path.join(javaSrcRoot, "service");
180
+ const controllerDir = path.join(javaSrcRoot, "controller");
181
+
182
+ await fs.ensureDir(entityDir);
183
+ await fs.ensureDir(repoDir);
184
+ await fs.ensureDir(serviceDir);
185
+ await fs.ensureDir(controllerDir);
186
+
187
+ for (const model of modelsToGenerate.values()) {
188
+ const commonData = {
189
+ projectName,
190
+ groupId,
191
+ artifactId,
192
+ group: groupId,
193
+ model,
194
+ modelName: model.name,
195
+ controllerName: model.name
196
+ };
197
+
198
+ await renderAndWrite(
199
+ getTemplatePath("java-spring/partials/Entity.java.ejs"),
200
+ path.join(entityDir, `${model.name}.java`),
201
+ commonData
202
+ );
203
+
204
+ await renderAndWrite(
205
+ getTemplatePath("java-spring/partials/Repository.java.ejs"),
206
+ path.join(repoDir, `${model.name}Repository.java`),
207
+ commonData
208
+ );
209
+
210
+ await renderAndWrite(
211
+ getTemplatePath("java-spring/partials/Service.java.ejs"),
212
+ path.join(serviceDir, `${model.name}Service.java`),
213
+ commonData
214
+ );
215
+
216
+ await renderAndWrite(
217
+ getTemplatePath("java-spring/partials/Controller.java.ejs"),
218
+ path.join(controllerDir, `${model.name}Controller.java`),
219
+ commonData
220
+ );
221
+ }
222
+ } else {
223
+ console.log(chalk.yellow(" -> No models inferred. Skipping entity/controller generation."));
224
+ }
225
+
226
+ await appendApplicationProperties(projectDir, artifactId);
227
+
228
+ console.log(chalk.blue(" -> Generating Docker files..."));
229
+ await renderAndWrite(
230
+ getTemplatePath("java-spring/partials/Dockerfile.ejs"),
231
+ path.join(projectDir, "Dockerfile"),
232
+ { projectName, artifactId }
233
+ );
234
+ await renderAndWrite(
235
+ getTemplatePath("java-spring/partials/docker-compose.yml.ejs"),
236
+ path.join(projectDir, "docker-compose.yml"),
237
+ { projectName, artifactId }
238
+ );
239
+
240
+ console.log(chalk.green(" -> Java (Spring Boot) backend generation is complete!"));
241
+ } catch (error) {
242
+ if (error && error.response && error.response.status) {
243
+ console.error(chalk.red(` -> Initializr error status: ${error.response.status}`));
244
+ throw new Error(`Failed to download from Spring Initializr. Status: ${error.response.status}`);
245
+ }
246
+ throw error;
247
+ }
248
+ }