create-backlist 6.0.6 → 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.
Files changed (46) hide show
  1. package/bin/index.js +141 -0
  2. package/package.json +4 -10
  3. package/src/analyzer.js +105 -308
  4. package/src/generators/dotnet.js +94 -120
  5. package/src/generators/java.js +109 -157
  6. package/src/generators/node.js +85 -262
  7. package/src/generators/template.js +2 -38
  8. package/src/templates/dotnet/partials/Controller.cs.ejs +14 -7
  9. package/src/templates/java-spring/partials/ApplicationSeeder.java.ejs +2 -7
  10. package/src/templates/java-spring/partials/AuthController.java.ejs +10 -23
  11. package/src/templates/java-spring/partials/Controller.java.ejs +6 -17
  12. package/src/templates/java-spring/partials/Dockerfile.ejs +1 -6
  13. package/src/templates/java-spring/partials/Entity.java.ejs +5 -15
  14. package/src/templates/java-spring/partials/JwtAuthFilter.java.ejs +7 -30
  15. package/src/templates/java-spring/partials/JwtService.java.ejs +10 -38
  16. package/src/templates/java-spring/partials/Repository.java.ejs +1 -10
  17. package/src/templates/java-spring/partials/Service.java.ejs +7 -45
  18. package/src/templates/java-spring/partials/User.java.ejs +4 -17
  19. package/src/templates/java-spring/partials/UserDetailsServiceImpl.java.ejs +4 -10
  20. package/src/templates/java-spring/partials/UserRepository.java.ejs +0 -8
  21. package/src/templates/java-spring/partials/docker-compose.yml.ejs +8 -16
  22. package/src/templates/node-ts-express/base/server.ts +6 -13
  23. package/src/templates/node-ts-express/base/tsconfig.json +3 -13
  24. package/src/templates/node-ts-express/partials/ApiDocs.ts.ejs +7 -17
  25. package/src/templates/node-ts-express/partials/App.test.ts.ejs +26 -49
  26. package/src/templates/node-ts-express/partials/Auth.controller.ts.ejs +62 -56
  27. package/src/templates/node-ts-express/partials/Auth.middleware.ts.ejs +10 -21
  28. package/src/templates/node-ts-express/partials/Controller.ts.ejs +40 -40
  29. package/src/templates/node-ts-express/partials/DbContext.cs.ejs +3 -3
  30. package/src/templates/node-ts-express/partials/Dockerfile.ejs +11 -9
  31. package/src/templates/node-ts-express/partials/Model.cs.ejs +7 -25
  32. package/src/templates/node-ts-express/partials/Model.ts.ejs +12 -20
  33. package/src/templates/node-ts-express/partials/PrismaController.ts.ejs +55 -72
  34. package/src/templates/node-ts-express/partials/PrismaSchema.prisma.ejs +12 -27
  35. package/src/templates/node-ts-express/partials/README.md.ejs +12 -9
  36. package/src/templates/node-ts-express/partials/Seeder.ts.ejs +64 -44
  37. package/src/templates/node-ts-express/partials/docker-compose.yml.ejs +16 -31
  38. package/src/templates/node-ts-express/partials/package.json.ejs +1 -3
  39. package/src/templates/node-ts-express/partials/routes.ts.ejs +24 -35
  40. package/src/utils.js +4 -19
  41. package/bin/backlist.js +0 -227
  42. package/src/db/prisma.ts +0 -4
  43. package/src/scanner/analyzeFrontend.js +0 -146
  44. package/src/scanner/index.js +0 -99
  45. package/src/templates/dotnet/partials/Dto.cs.ejs +0 -8
  46. package/src/templates/node-ts-express/partials/prismaClient.ts.ejs +0 -4
@@ -7,70 +7,27 @@ const { analyzeFrontend } = require('../analyzer');
7
7
  const { renderAndWrite, getTemplatePath } = require('./template');
8
8
 
9
9
  async function generateNodeProject(options) {
10
- const {
11
- projectDir,
12
- projectName,
13
- frontendSrcDir,
14
- dbType,
15
- addAuth,
16
- addSeeder,
17
- extraFeatures = [],
18
- } = options;
19
-
10
+ // v5.0: Destructure all new options
11
+ const { projectDir, projectName, frontendSrcDir, dbType, addAuth, addSeeder, extraFeatures = [] } = options;
20
12
  const port = 8000;
21
13
 
22
14
  try {
23
15
  // --- Step 1: Analyze Frontend ---
24
- console.log(chalk.blue(' -> Analyzing frontend for API endpoints (AST)...'));
16
+ console.log(chalk.blue(' -> Analyzing frontend for API endpoints...'));
25
17
  const endpoints = await analyzeFrontend(frontendSrcDir);
26
18
  if (endpoints.length > 0) console.log(chalk.green(` -> Found ${endpoints.length} endpoints.`));
27
19
  else console.log(chalk.yellow(' -> No API endpoints found. A basic project will be created.'));
28
20
 
29
- // Group endpoints by controller
30
- const endpointsByController = new Map();
31
- for (const ep of endpoints) {
32
- const c = ep && ep.controllerName ? ep.controllerName : 'Default';
33
- if (c === 'Default') continue;
34
- if (!endpointsByController.has(c)) endpointsByController.set(c, []);
35
- endpointsByController.get(c).push(ep);
36
- }
37
-
38
- // --- Step 2: Identify Models to Generate (merge fields per controller) ---
21
+ // --- Step 2: Identify Models to Generate ---
39
22
  const modelsToGenerate = new Map();
40
-
41
- for (const [controllerName, eps] of endpointsByController.entries()) {
42
- const fieldMap = new Map();
43
-
44
- for (const ep of eps) {
45
- const fields = ep?.requestBody?.fields || ep?.schemaFields;
46
- if (!fields) continue;
47
-
48
- for (const [key, type] of Object.entries(fields)) {
49
- fieldMap.set(key, { name: key, type, isUnique: key === 'email' });
50
- }
51
- }
52
-
53
- if (fieldMap.size > 0) {
54
- modelsToGenerate.set(controllerName, {
55
- name: controllerName,
56
- fields: Array.from(fieldMap.values()),
57
- });
23
+ endpoints.forEach(ep => {
24
+ if (ep.schemaFields && ep.controllerName !== 'Default' && !modelsToGenerate.has(ep.controllerName)) {
25
+ modelsToGenerate.set(ep.controllerName, { name: ep.controllerName, fields: Object.entries(ep.schemaFields).map(([key, type]) => ({ name: key, type, isUnique: key === 'email' })) });
58
26
  }
59
- }
60
-
61
- // Ensure User model if auth enabled
27
+ });
62
28
  if (addAuth && !modelsToGenerate.has('User')) {
63
29
  console.log(chalk.yellow(' -> Authentication requires a "User" model. Creating a default one.'));
64
- modelsToGenerate.set('User', {
65
- name: 'User',
66
- fields: [
67
- { name: 'name', type: 'string' },
68
- { name: 'email', type: 'string', isUnique: true },
69
- { name: 'password', type: 'string' },
70
- ],
71
- });
72
- // Also ensure controller exists so routes/controller can generate if frontend didn't call /api/users
73
- if (!endpointsByController.has('User')) endpointsByController.set('User', []);
30
+ modelsToGenerate.set('User', { name: 'User', fields: [{ name: 'name', type: 'String' }, { name: 'email', type: 'String', isUnique: true }, { name: 'password', type: 'String' }] });
74
31
  }
75
32
 
76
33
  // --- Step 3: Base Scaffolding ---
@@ -79,38 +36,28 @@ async function generateNodeProject(options) {
79
36
  await fs.ensureDir(destSrcDir);
80
37
  await fs.copy(getTemplatePath('node-ts-express/base/server.ts'), path.join(destSrcDir, 'server.ts'));
81
38
  await fs.copy(getTemplatePath('node-ts-express/base/tsconfig.json'), path.join(projectDir, 'tsconfig.json'));
82
-
39
+
83
40
  // --- Step 4: Prepare and Write package.json ---
84
- const packageJsonContent = JSON.parse(
85
- await ejs.renderFile(getTemplatePath('node-ts-express/partials/package.json.ejs'), { projectName })
86
- );
87
-
88
- if (dbType === 'mongoose') {
89
- packageJsonContent.dependencies['mongoose'] = '^7.6.3';
90
- }
91
-
41
+ const packageJsonContent = JSON.parse(await ejs.renderFile(getTemplatePath('node-ts-express/partials/package.json.ejs'), { projectName }));
42
+
43
+ if (dbType === 'mongoose') packageJsonContent.dependencies['mongoose'] = '^7.6.3';
92
44
  if (dbType === 'prisma') {
93
45
  packageJsonContent.dependencies['@prisma/client'] = '^5.6.0';
94
46
  packageJsonContent.devDependencies['prisma'] = '^5.6.0';
95
- // prisma seed entry is fine, but if you do mongoose seeder only, don't point prisma seed to scripts/seeder.ts
96
- packageJsonContent.prisma = { seed: `ts-node prisma/seed.ts` };
47
+ packageJsonContent.prisma = { seed: `ts-node ${addSeeder ? 'scripts/seeder.ts' : 'prisma/seed.ts'}` };
97
48
  }
98
-
99
49
  if (addAuth) {
100
50
  packageJsonContent.dependencies['jsonwebtoken'] = '^9.0.2';
101
51
  packageJsonContent.dependencies['bcryptjs'] = '^2.4.3';
102
52
  packageJsonContent.devDependencies['@types/jsonwebtoken'] = '^9.0.5';
103
53
  packageJsonContent.devDependencies['@types/bcryptjs'] = '^2.4.6';
104
54
  }
105
-
106
- // Seeder deps only if mongoose seeder enabled
107
- if (addSeeder && dbType === 'mongoose') {
55
+ if (addSeeder) {
108
56
  packageJsonContent.devDependencies['@faker-js/faker'] = '^8.3.1';
109
57
  if (!packageJsonContent.dependencies['chalk']) packageJsonContent.dependencies['chalk'] = '^4.1.2';
110
58
  packageJsonContent.scripts['seed'] = `ts-node scripts/seeder.ts`;
111
59
  packageJsonContent.scripts['destroy'] = `ts-node scripts/seeder.ts -d`;
112
60
  }
113
-
114
61
  if (extraFeatures.includes('testing')) {
115
62
  packageJsonContent.devDependencies['jest'] = '^29.7.0';
116
63
  packageJsonContent.devDependencies['supertest'] = '^6.3.3';
@@ -119,255 +66,131 @@ async function generateNodeProject(options) {
119
66
  packageJsonContent.devDependencies['ts-jest'] = '^29.1.1';
120
67
  packageJsonContent.scripts['test'] = 'jest --detectOpenHandles --forceExit';
121
68
  }
122
-
123
69
  if (extraFeatures.includes('swagger')) {
124
70
  packageJsonContent.dependencies['swagger-ui-express'] = '^5.0.0';
125
71
  packageJsonContent.dependencies['swagger-jsdoc'] = '^6.2.8';
126
72
  packageJsonContent.devDependencies['@types/swagger-ui-express'] = '^4.1.6';
127
73
  }
128
-
129
74
  await fs.writeJson(path.join(projectDir, 'package.json'), packageJsonContent, { spaces: 2 });
130
-
131
- // --- Step 5: Generate DB-specific files & Models ---
132
- await fs.ensureDir(path.join(destSrcDir, 'controllers'));
133
-
75
+
76
+ // --- Step 5: Generate DB-specific files & Controllers ---
134
77
  if (modelsToGenerate.size > 0) {
135
- if (dbType === 'mongoose') {
136
- console.log(chalk.blue(' -> Generating Mongoose models...'));
137
- await fs.ensureDir(path.join(destSrcDir, 'models'));
138
-
139
- for (const [modelName, modelData] of modelsToGenerate.entries()) {
140
- // normalize schema fields to simple types (string/number/boolean)
141
- const schema = {};
142
- for (const field of modelData.fields || []) {
143
- const t = String(field.type || 'string').toLowerCase();
144
- schema[field.name] = (t === 'number' || t === 'boolean') ? t : 'string';
145
- }
146
-
147
- await renderAndWrite(
148
- getTemplatePath('node-ts-express/partials/Model.ts.ejs'),
149
- path.join(destSrcDir, 'models', `${modelName}.model.ts`),
150
- { modelName, schema, projectName }
151
- );
78
+ await fs.ensureDir(path.join(destSrcDir, 'controllers'));
79
+ if (dbType === 'mongoose') {
80
+ console.log(chalk.blue(' -> Generating Mongoose models and controllers...'));
81
+ await fs.ensureDir(path.join(destSrcDir, 'models'));
82
+ for (const [modelName, modelData] of modelsToGenerate.entries()) {
83
+ const schema = modelData.fields.reduce((acc, field) => { acc[field.name] = field.type; return acc; }, {});
84
+ await renderAndWrite(getTemplatePath('node-ts-express/partials/Model.ts.ejs'), path.join(destSrcDir, 'models', `${modelName}.model.ts`), { modelName, schema, projectName });
85
+ }
86
+ } else if (dbType === 'prisma') {
87
+ console.log(chalk.blue(' -> Generating Prisma schema...'));
88
+ await fs.ensureDir(path.join(projectDir, 'prisma'));
89
+ await renderAndWrite(getTemplatePath('node-ts-express/partials/PrismaSchema.prisma.ejs'), path.join(projectDir, 'prisma', 'schema.prisma'), { modelsToGenerate: Array.from(modelsToGenerate.values()) });
90
+ }
91
+ console.log(chalk.blue(' -> Generating controllers...'));
92
+ for (const [modelName] of modelsToGenerate.entries()) {
93
+ const templateFile = dbType === 'mongoose' ? 'Controller.ts.ejs' : 'PrismaController.ts.ejs';
94
+ await renderAndWrite(getTemplatePath(`node-ts-express/partials/${templateFile}`), path.join(destSrcDir, 'controllers', `${modelName}.controller.ts`), { modelName, projectName });
152
95
  }
153
- }
154
-
155
- if (dbType === 'prisma') {
156
- console.log(chalk.blue(' -> Generating Prisma schema + client...'));
157
- await fs.ensureDir(path.join(projectDir, 'prisma'));
158
-
159
- await renderAndWrite(
160
- getTemplatePath('node-ts-express/partials/PrismaSchema.prisma.ejs'),
161
- path.join(projectDir, 'prisma', 'schema.prisma'),
162
- { modelsToGenerate: Array.from(modelsToGenerate.values()), projectName }
163
- );
164
-
165
- // Prisma client singleton
166
- await fs.ensureDir(path.join(destSrcDir, 'db'));
167
- await renderAndWrite(
168
- getTemplatePath('node-ts-express/partials/prismaClient.ts.ejs'),
169
- path.join(destSrcDir, 'db', 'prisma.ts'),
170
- { projectName }
171
- );
172
- }
173
- }
174
-
175
- // --- Step 5b: Generate Controllers from Endpoints (AST-driven) ---
176
- console.log(chalk.blue(' -> Generating controllers (from endpoints)...'));
177
- for (const [controllerName, controllerEndpoints] of endpointsByController.entries()) {
178
- const tpl =
179
- dbType === 'prisma'
180
- ? 'node-ts-express/partials/PrismaController.FromEndpoints.ts.ejs'
181
- : 'node-ts-express/partials/Controller.FromEndpoints.ts.ejs';
182
-
183
- await renderAndWrite(
184
- getTemplatePath(tpl),
185
- path.join(destSrcDir, 'controllers', `${controllerName}.controller.ts`),
186
- { controllerName, endpoints: controllerEndpoints, projectName, dbType }
187
- );
188
96
  }
189
-
190
- // --- Step 6: Authentication Boilerplate ---
97
+
98
+ // --- Step 6: Generate Authentication Boilerplate ---
191
99
  if (addAuth) {
192
- console.log(chalk.blue(' -> Generating authentication boilerplate...'));
193
- await fs.ensureDir(path.join(destSrcDir, 'routes'));
194
- await fs.ensureDir(path.join(destSrcDir, 'middleware'));
195
-
196
- await renderAndWrite(
197
- getTemplatePath('node-ts-express/partials/Auth.controller.ts.ejs'),
198
- path.join(destSrcDir, 'controllers', 'Auth.controller.ts'),
199
- { dbType, projectName }
200
- );
201
-
202
- await renderAndWrite(
203
- getTemplatePath('node-ts-express/partials/Auth.routes.ts.ejs'),
204
- path.join(destSrcDir, 'routes', 'Auth.routes.ts'),
205
- { projectName }
206
- );
207
-
208
- await renderAndWrite(
209
- getTemplatePath('node-ts-express/partials/Auth.middleware.ts.ejs'),
210
- path.join(destSrcDir, 'middleware', 'Auth.middleware.ts'),
211
- { projectName }
212
- );
213
-
214
- // For mongoose: inject password hashing hook into User.model.ts if exists
215
- if (dbType === 'mongoose') {
216
- const userModelPath = path.join(destSrcDir, 'models', 'User.model.ts');
217
- if (await fs.pathExists(userModelPath)) {
218
- let userModelContent = await fs.readFile(userModelPath, 'utf-8');
219
-
220
- if (!userModelContent.includes(`import bcrypt`)) {
221
- userModelContent = userModelContent.replace(
222
- `import mongoose, { Schema, Document } from 'mongoose';`,
223
- `import mongoose, { Schema, Document } from 'mongoose';\nimport bcrypt from 'bcryptjs';`
224
- );
225
- }
226
-
227
- if (!userModelContent.includes(`pre('save'`)) {
228
- const preSaveHook =
229
- `\n// Hash password before saving\n` +
230
- `UserSchema.pre('save', async function(next) {\n` +
231
- ` if (!this.isModified('password')) { return next(); }\n` +
232
- ` const salt = await bcrypt.genSalt(10);\n` +
233
- ` this.password = await bcrypt.hash(this.password, salt);\n` +
234
- ` next();\n` +
235
- `});\n`;
236
-
237
- userModelContent = userModelContent.replace(
238
- `// Create and export the Model`,
239
- `${preSaveHook}\n// Create and export the Model`
240
- );
241
- }
242
-
243
- await fs.writeFile(userModelPath, userModelContent);
100
+ console.log(chalk.blue(' -> Generating authentication boilerplate...'));
101
+ await fs.ensureDir(path.join(destSrcDir, 'routes'));
102
+ await fs.ensureDir(path.join(destSrcDir, 'middleware'));
103
+ await renderAndWrite(getTemplatePath('node-ts-express/partials/Auth.controller.ts.ejs'), path.join(destSrcDir, 'controllers', 'Auth.controller.ts'), { dbType, projectName });
104
+ await renderAndWrite(getTemplatePath('node-ts-express/partials/Auth.routes.ts.ejs'), path.join(destSrcDir, 'routes', 'Auth.routes.ts'), { projectName });
105
+ await renderAndWrite(getTemplatePath('node-ts-express/partials/Auth.middleware.ts.ejs'), path.join(destSrcDir, 'middleware', 'Auth.middleware.ts'), { projectName });
106
+
107
+ if (dbType === 'mongoose') {
108
+ const userModelPath = path.join(destSrcDir, 'models', 'User.model.ts');
109
+ if (await fs.pathExists(userModelPath)) {
110
+ let userModelContent = await fs.readFile(userModelPath, 'utf-8');
111
+ if (!userModelContent.includes('bcryptjs')) {
112
+ userModelContent = userModelContent.replace(`import mongoose, { Schema, Document } from 'mongoose';`, `import mongoose, { Schema, Document } from 'mongoose';\nimport bcrypt from 'bcryptjs';`);
113
+ const preSaveHook = `\n// Hash password before saving\nUserSchema.pre('save', async function(next) {\n if (!this.isModified('password')) { return next(); }\n const salt = await bcrypt.genSalt(10);\n this.password = await bcrypt.hash(this.password, salt);\n next();\n});\n`;
114
+ userModelContent = userModelContent.replace(`// Create and export the Model`, `${preSaveHook}\n// Create and export the Model`);
115
+ await fs.writeFile(userModelPath, userModelContent);
116
+ }
117
+ }
244
118
  }
245
- }
246
119
  }
247
120
 
248
- // --- Step 7: Seeder Script (mongoose only) ---
249
- if (addSeeder && dbType === 'mongoose') {
250
- console.log(chalk.blue(' -> Generating database seeder script (mongoose)...'));
121
+ // --- Step 7: Generate Seeder Script ---
122
+ if (addSeeder) {
123
+ console.log(chalk.blue(' -> Generating database seeder script...'));
251
124
  await fs.ensureDir(path.join(projectDir, 'scripts'));
252
- await renderAndWrite(
253
- getTemplatePath('node-ts-express/partials/Seeder.ts.ejs'),
254
- path.join(projectDir, 'scripts', 'seeder.ts'),
255
- { projectName }
256
- );
125
+ await renderAndWrite(getTemplatePath('node-ts-express/partials/Seeder.ts.ejs'), path.join(projectDir, 'scripts', 'seeder.ts'), { projectName });
257
126
  }
258
127
 
259
- // --- Step 8: Extra Features ---
128
+ // --- Step 8: Generate Extra Features ---
260
129
  if (extraFeatures.includes('docker')) {
261
130
  console.log(chalk.blue(' -> Generating Docker files...'));
262
- await renderAndWrite(
263
- getTemplatePath('node-ts-express/partials/Dockerfile.ejs'),
264
- path.join(projectDir, 'Dockerfile'),
265
- { dbType, port }
266
- );
267
- await renderAndWrite(
268
- getTemplatePath('node-ts-express/partials/docker-compose.yml.ejs'),
269
- path.join(projectDir, 'docker-compose.yml'),
270
- { projectName, dbType, port }
271
- );
131
+ await renderAndWrite(getTemplatePath('node-ts-express/partials/Dockerfile.ejs'), path.join(projectDir, 'Dockerfile'), { dbType, port });
132
+ await renderAndWrite(getTemplatePath('node-ts-express/partials/docker-compose.yml.ejs'), path.join(projectDir, 'docker-compose.yml'), { projectName, dbType, port });
272
133
  }
273
-
274
134
  if (extraFeatures.includes('swagger')) {
275
135
  console.log(chalk.blue(' -> Generating API documentation setup...'));
276
136
  await fs.ensureDir(path.join(destSrcDir, 'utils'));
277
- await renderAndWrite(
278
- getTemplatePath('node-ts-express/partials/ApiDocs.ts.ejs'),
279
- path.join(destSrcDir, 'utils', 'swagger.ts'),
280
- { projectName, port, addAuth }
281
- );
137
+ await renderAndWrite(getTemplatePath('node-ts-express/partials/ApiDocs.ts.ejs'), path.join(destSrcDir, 'utils', 'swagger.ts'), { projectName, port });
282
138
  }
283
-
284
139
  if (extraFeatures.includes('testing')) {
285
140
  console.log(chalk.blue(' -> Generating testing boilerplate...'));
286
- const jestConfig =
287
- `/** @type {import('ts-jest').JestConfigWithTsJest} */\n` +
288
- `module.exports = {\n` +
289
- ` preset: 'ts-jest',\n` +
290
- ` testEnvironment: 'node',\n` +
291
- ` verbose: true,\n` +
292
- `};\n`;
141
+ const jestConfig = `/** @type {import('ts-jest').JestConfigWithTsJest} */\nmodule.exports = {\n preset: 'ts-jest',\n testEnvironment: 'node',\n verbose: true,\n};`;
293
142
  await fs.writeFile(path.join(projectDir, 'jest.config.js'), jestConfig);
294
-
295
143
  await fs.ensureDir(path.join(projectDir, 'src', '__tests__'));
296
- await renderAndWrite(
297
- getTemplatePath('node-ts-express/partials/App.test.ts.ejs'),
298
- path.join(projectDir, 'src', '__tests__', 'api.test.ts'),
299
- { addAuth, endpoints }
300
- );
144
+ await renderAndWrite(getTemplatePath('node-ts-express/partials/App.test.ts.ejs'), path.join(projectDir, 'src', '__tests__', 'api.test.ts'), { addAuth });
301
145
  }
302
146
 
303
- // --- Step 9: Generate Main Route File & Inject into server.ts ---
304
- await renderAndWrite(
305
- getTemplatePath('node-ts-express/partials/routes.ts.ejs'),
306
- path.join(destSrcDir, 'routes.ts'),
307
- { endpoints, addAuth, dbType }
308
- );
309
-
147
+ // --- Step 9: Generate Main Route File & Inject Logic into Server ---
148
+ await renderAndWrite(getTemplatePath('node-ts-express/partials/routes.ts.ejs'), path.join(destSrcDir, 'routes.ts'), { endpoints, addAuth, dbType });
149
+
310
150
  let serverFileContent = await fs.readFile(path.join(destSrcDir, 'server.ts'), 'utf-8');
151
+ let dbConnectionCode = '', swaggerInjector = '', authRoutesInjector = '';
311
152
 
312
- // Mongoose db connect injection only; prisma uses src/db/prisma.ts
313
- let dbConnectionCode = '';
314
153
  if (dbType === 'mongoose') {
315
- dbConnectionCode =
316
- `\n// --- Database Connection ---\n` +
317
- `import mongoose from 'mongoose';\n` +
318
- `const MONGO_URI = process.env.MONGO_URI || 'mongodb://127.0.0.1:27017/${projectName}';\n` +
319
- `mongoose.connect(MONGO_URI)\n` +
320
- ` .then(() => console.log('MongoDB Connected...'))\n` +
321
- ` .catch(err => console.error(err));\n` +
322
- `// -------------------------\n`;
154
+ dbConnectionCode = `\n// --- Database Connection ---\nimport mongoose from 'mongoose';\nconst MONGO_URI = process.env.MONGO_URI || 'mongodb://127.0.0.1:27017/${projectName}';\nmongoose.connect(MONGO_URI).then(() => console.log('MongoDB Connected...')).catch(err => console.error(err));\n// -------------------------\n`;
155
+ } else if (dbType === 'prisma') {
156
+ dbConnectionCode = `\nimport { PrismaClient } from '@prisma/client';\nexport const prisma = new PrismaClient();\n`;
323
157
  }
324
-
325
- let swaggerInjector = '';
326
158
  if (extraFeatures.includes('swagger')) {
327
- swaggerInjector = `\nimport { setupSwagger } from './utils/swagger';\nsetupSwagger(app);\n`;
159
+ swaggerInjector = `\nimport { setupSwagger } from './utils/swagger';\nsetupSwagger(app);\n`;
328
160
  }
329
-
330
- let authRoutesInjector = '';
331
161
  if (addAuth) {
332
- authRoutesInjector = `import authRoutes from './routes/Auth.routes';\napp.use('/api/auth', authRoutes);\n\n`;
162
+ authRoutesInjector = `import authRoutes from './routes/Auth.routes';\napp.use('/api/auth', authRoutes);\n\n`;
333
163
  }
334
164
 
335
165
  serverFileContent = serverFileContent
336
- .replace('dotenv.config();', `dotenv.config();${dbConnectionCode}`)
337
- .replace('// INJECT:ROUTES', `${authRoutesInjector}import apiRoutes from './routes';\napp.use('/api', apiRoutes);\n`);
338
-
339
- // place swagger setup before listen
166
+ .replace("dotenv.config();", `dotenv.config();${dbConnectionCode}`)
167
+ .replace('// INJECT:ROUTES', `${authRoutesInjector}import apiRoutes from './routes';\napp.use('/api', apiRoutes);`);
168
+
340
169
  const listenRegex = /(app\.listen\()/;
341
170
  serverFileContent = serverFileContent.replace(listenRegex, `${swaggerInjector}\n$1`);
342
-
343
171
  await fs.writeFile(path.join(destSrcDir, 'server.ts'), serverFileContent);
344
172
 
345
- // --- Step 10: Install Dependencies & Post-install ---
173
+ // --- Step 10: Install Dependencies & Run Post-install Scripts ---
346
174
  console.log(chalk.magenta(' -> Installing dependencies... This may take a moment.'));
347
175
  await execa('npm', ['install'], { cwd: projectDir });
348
-
349
176
  if (dbType === 'prisma') {
350
177
  console.log(chalk.blue(' -> Running `prisma generate`...'));
351
178
  await execa('npx', ['prisma', 'generate'], { cwd: projectDir });
352
179
  }
353
-
354
- // --- Step 11: Final Files (.env.example) ---
180
+
181
+ // --- Step 11: Generate Final Files (.env.example) ---
355
182
  let envContent = `PORT=${port}\n`;
356
-
357
183
  if (dbType === 'mongoose') {
358
- envContent += `MONGO_URI=mongodb://127.0.0.1:27017/${projectName}\n`;
184
+ envContent += `MONGO_URI=mongodb://root:example@db:27017/${projectName}?authSource=admin\n`;
359
185
  } else if (dbType === 'prisma') {
360
- envContent += `DATABASE_URL="postgresql://user:password@localhost:5432/${projectName}?schema=public"\n`;
186
+ envContent += `DATABASE_URL="postgresql://user:password@db:5432/${projectName}?schema=public"\n`;
361
187
  }
362
-
363
- if (addAuth) envContent += `JWT_SECRET=change_me_long_secret_change_me_long_secret\n`;
364
-
365
- if (extraFeatures.includes('docker') && dbType === 'prisma') {
366
- envContent += `\n# Docker-compose credentials\nDB_USER=postgres\nDB_PASSWORD=password\nDB_NAME=${projectName}\n`;
188
+ if (addAuth) envContent += `JWT_SECRET=your_super_secret_jwt_key_12345\n`;
189
+ if (extraFeatures.includes('docker')) {
190
+ envContent += `\n# Docker-compose credentials (used in docker-compose.yml)\nDB_USER=user\nDB_PASSWORD=password\nDB_NAME=${projectName}`;
367
191
  }
368
-
369
192
  await fs.writeFile(path.join(projectDir, '.env.example'), envContent);
370
-
193
+
371
194
  } catch (error) {
372
195
  throw error;
373
196
  }
@@ -2,47 +2,11 @@ const fs = require('fs-extra');
2
2
  const ejs = require('ejs');
3
3
  const path = require('path');
4
4
 
5
- function pascalCase(str) {
6
- return String(str || '')
7
- .replace(/[-_]+(.)/g, (_, c) => c.toUpperCase())
8
- .replace(/^\w/, c => c.toUpperCase())
9
- .replace(/[^a-zA-Z0-9]/g, '');
10
- }
11
-
12
- function camelCase(str) {
13
- const p = pascalCase(str);
14
- return p ? p.charAt(0).toLowerCase() + p.slice(1) : '';
15
- }
16
-
17
- function mapTsType(t) {
18
- const x = String(t || '').toLowerCase();
19
- if (x === 'number' || x === 'int' || x === 'integer' || x === 'float' || x === 'double') return 'number';
20
- if (x === 'boolean' || x === 'bool') return 'boolean';
21
- return 'string';
22
- }
23
-
24
- function mapMongooseType(t) {
25
- const x = String(t || '').toLowerCase();
26
- if (x === 'number' || x === 'int' || x === 'integer' || x === 'float' || x === 'double') return 'Number';
27
- if (x === 'boolean' || x === 'bool') return 'Boolean';
28
- return 'String';
29
- }
30
-
31
5
  async function renderAndWrite(templatePath, outPath, data) {
32
- const helpers = { pascalCase, camelCase, mapTsType, mapMongooseType };
33
-
34
6
  try {
35
7
  const tpl = await fs.readFile(templatePath, 'utf-8');
36
- const code = ejs.render(tpl, { ...(data || {}), ...helpers }, { filename: templatePath });
37
-
38
- // avoid rewriting identical content (useful in watch mode)
39
- const exists = await fs.pathExists(outPath);
40
- if (exists) {
41
- const current = await fs.readFile(outPath, 'utf-8');
42
- if (current === code) return;
43
- }
44
-
45
- await fs.outputFile(outPath, code.endsWith('\n') ? code : code + '\n');
8
+ const code = ejs.render(tpl, data || {}, { filename: templatePath }); // filename helps with EJS errors
9
+ await fs.outputFile(outPath, code);
46
10
  } catch (err) {
47
11
  console.error('EJS render failed for:', templatePath);
48
12
  console.error('Data keys:', Object.keys(data || {}));
@@ -3,15 +3,22 @@ using Microsoft.AspNetCore.Mvc;
3
3
  namespace <%= projectName %>.Controllers;
4
4
 
5
5
  [ApiController]
6
- [Route("[controller]")]
6
+ [Route("api/[controller]")]
7
7
  public class <%= controllerName %>Controller : ControllerBase
8
8
  {
9
- <% endpoints.forEach(ep => { -%>
10
- [Http<%= ep.method.charAt(0) + ep.method.slice(1).toLowerCase() %>("<%= ep.route.replace(`/${controllerName.toLowerCase()}`, "") %>")]
11
- public IActionResult <%= ep.actionName || (ep.method.toLowerCase() + controllerName) %>()
9
+ // Endpoints for <%= controllerName %> auto-generated by Backlist
10
+
11
+ <% endpoints.forEach(endpoint => { %>
12
+ <%# Convert /api/users/{id} to just {id} for the route attribute %>
13
+ <% const routePath = endpoint.path.replace(`/api/${controllerName.toLowerCase()}`, '').substring(1); %>
14
+ /**
15
+ * <%= endpoint.method.toUpperCase() %> <%= endpoint.path %>
16
+ */
17
+ [Http<%= endpoint.method.charAt(0) + endpoint.method.slice(1).toLowerCase() %>("<%- routePath %>")]
18
+ public IActionResult AutoGenerated_<%= endpoint.method %>_<%= routePath.replace(/{|}/g, 'By_').replace(/[^a-zA-Z0-9_]/g, '') || 'Index' %>()
12
19
  {
13
- return Ok(new { message = "TODO: Implement <%= ep.method %> <%= ep.route %>" });
20
+ // TODO: Implement logic here. You can access route parameters like: public IActionResult Get(int id)
21
+ return Ok(new { message = "Auto-generated response for <%= endpoint.method.toUpperCase() %> <%= endpoint.path %>" });
14
22
  }
15
-
16
- <% }) -%>
23
+ <% }); %>
17
24
  }
@@ -4,27 +4,22 @@ package <%= group %>.<%= projectName %>;
4
4
  import org.springframework.boot.CommandLineRunner;
5
5
  import org.springframework.context.annotation.Bean;
6
6
  import org.springframework.context.annotation.Configuration;
7
-
8
7
  import <%= group %>.<%= projectName %>.model.User;
9
8
  import <%= group %>.<%= projectName %>.repository.UserRepository;
10
-
11
9
  import org.springframework.security.crypto.password.PasswordEncoder;
12
10
 
13
11
  @Configuration
14
12
  public class ApplicationSeeder {
15
-
16
13
  @Bean
17
14
  CommandLineRunner seed(UserRepository userRepository, PasswordEncoder encoder) {
18
15
  return args -> {
19
- userRepository.findByEmail("admin@example.com").ifPresentOrElse(u -> {
20
- // already exists
21
- }, () -> {
16
+ if (userRepository.findByEmail("admin@example.com").isEmpty()) {
22
17
  User admin = new User();
23
18
  admin.setName("Admin");
24
19
  admin.setEmail("admin@example.com");
25
20
  admin.setPassword(encoder.encode("admin123"));
26
21
  userRepository.save(admin);
27
- });
22
+ }
28
23
  };
29
24
  }
30
25
  }
@@ -4,7 +4,6 @@ package <%= group %>.<%= projectName %>.controller;
4
4
  import <%= group %>.<%= projectName %>.model.User;
5
5
  import <%= group %>.<%= projectName %>.repository.UserRepository;
6
6
  import <%= group %>.<%= projectName %>.security.JwtService;
7
-
8
7
  import org.springframework.http.ResponseEntity;
9
8
  import org.springframework.http.HttpStatus;
10
9
  import org.springframework.security.crypto.password.PasswordEncoder;
@@ -22,41 +21,29 @@ public class AuthController {
22
21
  private final JwtService jwt;
23
22
 
24
23
  public AuthController(UserRepository repo, PasswordEncoder encoder, JwtService jwt) {
25
- this.repo = repo;
26
- this.encoder = encoder;
27
- this.jwt = jwt;
24
+ this.repo = repo; this.encoder = encoder; this.jwt = jwt;
28
25
  }
29
26
 
30
27
  @PostMapping("/register")
31
- public ResponseEntity<?> register(@RequestBody AuthRequest req) {
32
- if (repo.findByEmail(req.email()).isPresent()) {
28
+ public ResponseEntity<?> register(@RequestBody User req) {
29
+ if (repo.findByEmail(req.getEmail()).isPresent()) {
33
30
  return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("User already exists");
34
31
  }
35
-
36
- User user = new User();
37
- user.setName(req.name());
38
- user.setEmail(req.email());
39
- user.setPassword(encoder.encode(req.password()));
40
- repo.save(user);
41
-
42
- String token = jwt.generateToken(user.getEmail());
32
+ req.setPassword(encoder.encode(req.getPassword()));
33
+ repo.save(req);
34
+ String token = jwt.generateToken(req.getEmail());
43
35
  return ResponseEntity.status(HttpStatus.CREATED).body(new TokenResponse(token));
44
36
  }
45
37
 
46
38
  @PostMapping("/login")
47
- public ResponseEntity<?> login(@RequestBody LoginRequest req) {
48
- Optional<User> current = repo.findByEmail(req.email());
39
+ public ResponseEntity<?> login(@RequestBody User req) {
40
+ Optional<User> current = repo.findByEmail(req.getEmail());
49
41
  if (current.isEmpty()) return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("Invalid credentials");
50
-
51
- if (!encoder.matches(req.password(), current.get().getPassword())) {
42
+ if (!encoder.matches(req.getPassword(), current.get().getPassword()))
52
43
  return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("Invalid credentials");
53
- }
54
-
55
- String token = jwt.generateToken(current.get().getEmail());
44
+ String token = jwt.generateToken(req.getEmail());
56
45
  return ResponseEntity.ok(new TokenResponse(token));
57
46
  }
58
47
 
59
- public record AuthRequest(String name, String email, String password) {}
60
- public record LoginRequest(String email, String password) {}
61
48
  public record TokenResponse(String token) {}
62
49
  }