create-backlist 5.0.4 → 5.0.6

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-backlist",
3
- "version": "5.0.4",
3
+ "version": "5.0.6",
4
4
  "description": "An advanced, multi-language backend generator based on frontend analysis.",
5
5
  "type": "commonjs",
6
6
  "bin": {
@@ -7,13 +7,18 @@ const { analyzeFrontend } = require('../analyzer');
7
7
  const { renderAndWrite, getTemplatePath } = require('./template');
8
8
 
9
9
  async function generateNodeProject(options) {
10
- const { projectDir, projectName, dbType, addAuth, addSeeder, extraFeatures = [] } = options;
10
+ // v5.0: Destructure all new options
11
+ const { projectDir, projectName, frontendSrcDir, dbType, addAuth, addSeeder, extraFeatures = [] } = options;
11
12
  const port = 8000;
12
13
 
13
14
  try {
14
- // --- Step 1: Analysis & Model Identification ---
15
+ // --- Step 1: Analyze Frontend ---
15
16
  console.log(chalk.blue(' -> Analyzing frontend for API endpoints...'));
16
- const endpoints = await analyzeFrontend(options.frontendSrcDir);
17
+ const endpoints = await analyzeFrontend(frontendSrcDir);
18
+ if (endpoints.length > 0) console.log(chalk.green(` -> Found ${endpoints.length} endpoints.`));
19
+ else console.log(chalk.yellow(' -> No API endpoints found. A basic project will be created.'));
20
+
21
+ // --- Step 2: Identify Models to Generate ---
17
22
  const modelsToGenerate = new Map();
18
23
  endpoints.forEach(ep => {
19
24
  if (ep.schemaFields && ep.controllerName !== 'Default' && !modelsToGenerate.has(ep.controllerName)) {
@@ -21,17 +26,18 @@ async function generateNodeProject(options) {
21
26
  }
22
27
  });
23
28
  if (addAuth && !modelsToGenerate.has('User')) {
29
+ console.log(chalk.yellow(' -> Authentication requires a "User" model. Creating a default one.'));
24
30
  modelsToGenerate.set('User', { name: 'User', fields: [{ name: 'name', type: 'String' }, { name: 'email', type: 'String', isUnique: true }, { name: 'password', type: 'String' }] });
25
31
  }
26
-
27
- // --- Step 2: Base Scaffolding ---
32
+
33
+ // --- Step 3: Base Scaffolding ---
28
34
  console.log(chalk.blue(' -> Scaffolding Node.js project...'));
29
35
  const destSrcDir = path.join(projectDir, 'src');
30
36
  await fs.ensureDir(destSrcDir);
31
37
  await fs.copy(getTemplatePath('node-ts-express/base/server.ts'), path.join(destSrcDir, 'server.ts'));
32
38
  await fs.copy(getTemplatePath('node-ts-express/base/tsconfig.json'), path.join(projectDir, 'tsconfig.json'));
33
39
 
34
- // --- Step 3: Prepare package.json ---
40
+ // --- Step 4: Prepare and Write package.json ---
35
41
  const packageJsonContent = JSON.parse(await ejs.renderFile(getTemplatePath('node-ts-express/partials/package.json.ejs'), { projectName }));
36
42
 
37
43
  if (dbType === 'mongoose') packageJsonContent.dependencies['mongoose'] = '^7.6.3';
@@ -49,8 +55,8 @@ async function generateNodeProject(options) {
49
55
  if (addSeeder) {
50
56
  packageJsonContent.devDependencies['@faker-js/faker'] = '^8.3.1';
51
57
  if (!packageJsonContent.dependencies['chalk']) packageJsonContent.dependencies['chalk'] = '^4.1.2';
52
- packageJsonContent.scripts['seed'] = 'ts-node scripts/seeder.ts';
53
- packageJsonContent.scripts['destroy'] = 'ts-node scripts/seeder.ts -d';
58
+ packageJsonContent.scripts['seed'] = `ts-node scripts/seeder.ts`;
59
+ packageJsonContent.scripts['destroy'] = `ts-node scripts/seeder.ts -d`;
54
60
  }
55
61
  if (extraFeatures.includes('testing')) {
56
62
  packageJsonContent.devDependencies['jest'] = '^29.7.0';
@@ -67,7 +73,7 @@ async function generateNodeProject(options) {
67
73
  }
68
74
  await fs.writeJson(path.join(projectDir, 'package.json'), packageJsonContent, { spaces: 2 });
69
75
 
70
- // --- Step 4: Generate DB-specific files & Controllers ---
76
+ // --- Step 5: Generate DB-specific files & Controllers ---
71
77
  if (modelsToGenerate.size > 0) {
72
78
  await fs.ensureDir(path.join(destSrcDir, 'controllers'));
73
79
  if (dbType === 'mongoose') {
@@ -82,7 +88,6 @@ async function generateNodeProject(options) {
82
88
  await fs.ensureDir(path.join(projectDir, 'prisma'));
83
89
  await renderAndWrite(getTemplatePath('node-ts-express/partials/PrismaSchema.prisma.ejs'), path.join(projectDir, 'prisma', 'schema.prisma'), { modelsToGenerate: Array.from(modelsToGenerate.values()) });
84
90
  }
85
- // Generate controllers for both DB types
86
91
  console.log(chalk.blue(' -> Generating controllers...'));
87
92
  for (const [modelName] of modelsToGenerate.entries()) {
88
93
  const templateFile = dbType === 'mongoose' ? 'Controller.ts.ejs' : 'PrismaController.ts.ejs';
@@ -90,7 +95,7 @@ async function generateNodeProject(options) {
90
95
  }
91
96
  }
92
97
 
93
- // --- Step 5: Generate Auth, Seeder, and Extra Features ---
98
+ // --- Step 6: Generate Authentication Boilerplate ---
94
99
  if (addAuth) {
95
100
  console.log(chalk.blue(' -> Generating authentication boilerplate...'));
96
101
  await fs.ensureDir(path.join(destSrcDir, 'routes'));
@@ -112,12 +117,34 @@ async function generateNodeProject(options) {
112
117
  }
113
118
  }
114
119
  }
115
- if (addSeeder) { /* ... Seeder logic as before ... */ }
116
- if (extraFeatures.includes('docker')) { /* ... Docker logic as before ... */ }
117
- if (extraFeatures.includes('swagger')) { /* ... Swagger logic as before ... */ }
118
- if (extraFeatures.includes('testing')) { /* ... Testing logic as before ... */ }
119
120
 
120
- // --- Step 6: Generate Main Route File & Inject Logic into Server ---
121
+ // --- Step 7: Generate Seeder Script ---
122
+ if (addSeeder) {
123
+ console.log(chalk.blue(' -> Generating database seeder script...'));
124
+ await fs.ensureDir(path.join(projectDir, 'scripts'));
125
+ await renderAndWrite(getTemplatePath('node-ts-express/partials/Seeder.ts.ejs'), path.join(projectDir, 'scripts', 'seeder.ts'), { projectName });
126
+ }
127
+
128
+ // --- Step 8: Generate Extra Features ---
129
+ if (extraFeatures.includes('docker')) {
130
+ console.log(chalk.blue(' -> Generating Docker files...'));
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 });
133
+ }
134
+ if (extraFeatures.includes('swagger')) {
135
+ console.log(chalk.blue(' -> Generating API documentation setup...'));
136
+ await fs.ensureDir(path.join(destSrcDir, 'utils'));
137
+ await renderAndWrite(getTemplatePath('node-ts-express/partials/ApiDocs.ts.ejs'), path.join(destSrcDir, 'utils', 'swagger.ts'), { projectName, port });
138
+ }
139
+ if (extraFeatures.includes('testing')) {
140
+ console.log(chalk.blue(' -> Generating testing boilerplate...'));
141
+ const jestConfig = `/** @type {import('ts-jest').JestConfigWithTsJest} */\nmodule.exports = {\n preset: 'ts-jest',\n testEnvironment: 'node',\n verbose: true,\n};`;
142
+ await fs.writeFile(path.join(projectDir, 'jest.config.js'), jestConfig);
143
+ await fs.ensureDir(path.join(projectDir, 'src', '__tests__'));
144
+ await renderAndWrite(getTemplatePath('node-ts-express/partials/App.test.ts.ejs'), path.join(projectDir, 'src', '__tests__', 'api.test.ts'), { addAuth });
145
+ }
146
+
147
+ // --- Step 9: Generate Main Route File & Inject Logic into Server ---
121
148
  await renderAndWrite(getTemplatePath('node-ts-express/partials/routes.ts.ejs'), path.join(destSrcDir, 'routes.ts'), { endpoints, addAuth, dbType });
122
149
 
123
150
  let serverFileContent = await fs.readFile(path.join(destSrcDir, 'server.ts'), 'utf-8');
@@ -143,17 +170,27 @@ async function generateNodeProject(options) {
143
170
  serverFileContent = serverFileContent.replace(listenRegex, `${swaggerInjector}\n$1`);
144
171
  await fs.writeFile(path.join(destSrcDir, 'server.ts'), serverFileContent);
145
172
 
146
- // --- Step 7: Install Dependencies & Post-install ---
147
- console.log(chalk.magenta(' -> Installing dependencies...'));
173
+ // --- Step 10: Install Dependencies & Run Post-install Scripts ---
174
+ console.log(chalk.magenta(' -> Installing dependencies... This may take a moment.'));
148
175
  await execa('npm', ['install'], { cwd: projectDir });
149
176
  if (dbType === 'prisma') {
150
177
  console.log(chalk.blue(' -> Running `prisma generate`...'));
151
178
  await execa('npx', ['prisma', 'generate'], { cwd: projectDir });
152
179
  }
153
180
 
154
- // --- Step 8: Generate Final Files (.env.example) ---
155
- // ... logic as before ...
156
-
181
+ // --- Step 11: Generate Final Files (.env.example) ---
182
+ let envContent = `PORT=${port}\n`;
183
+ if (dbType === 'mongoose') {
184
+ envContent += `MONGO_URI=mongodb://root:example@db:27017/${projectName}?authSource=admin\n`;
185
+ } else if (dbType === 'prisma') {
186
+ envContent += `DATABASE_URL="postgresql://user:password@db:5432/${projectName}?schema=public"\n`;
187
+ }
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}`;
191
+ }
192
+ await fs.writeFile(path.join(projectDir, '.env.example'), envContent);
193
+
157
194
  } catch (error) {
158
195
  throw error;
159
196
  }
@@ -1,11 +1,17 @@
1
- const ejs = require('ejs');
2
1
  const fs = require('fs-extra');
2
+ const ejs = require('ejs');
3
3
  const path = require('path');
4
4
 
5
- async function renderAndWrite(templatePath, destinationPath, data) {
6
- const template = await fs.readFile(templatePath, 'utf-8');
7
- const content = ejs.render(template, data);
8
- await fs.outputFile(destinationPath, content);
5
+ async function renderAndWrite(templatePath, outPath, data) {
6
+ try {
7
+ const tpl = await fs.readFile(templatePath, 'utf-8');
8
+ const code = ejs.render(tpl, data || {}, { filename: templatePath }); // filename helps with EJS errors
9
+ await fs.outputFile(outPath, code);
10
+ } catch (err) {
11
+ console.error('EJS render failed for:', templatePath);
12
+ console.error('Data keys:', Object.keys(data || {}));
13
+ throw err;
14
+ }
9
15
  }
10
16
 
11
17
  function getTemplatePath(subpath) {
@@ -1,42 +1,44 @@
1
- // Auto-generated by create-backlist v3.0 on <%= new Date().toISOString() %>
1
+ // Auto-generated by create-backlist on <%= new Date().toISOString() %>
2
2
  import { Request, Response } from 'express';
3
3
  import bcrypt from 'bcryptjs';
4
4
  import jwt from 'jsonwebtoken';
5
- import User, { IUser } from '../models/User.model'; // We assume the model is named 'User'
5
+
6
+ <% if (dbType === 'mongoose') { %>
7
+ import User from '../models/User.model';
8
+ <% } else if (dbType === 'prisma') { %>
9
+ import { prisma } from '../server';
10
+ <% } %>
6
11
 
7
12
  // @desc Register a new user
8
13
  export const registerUser = async (req: Request, res: Response) => {
9
14
  const { name, email, password } = req.body;
10
15
 
11
16
  try {
12
- // Check if user already exists
17
+ <% if (dbType === 'mongoose') { %>
18
+ // Mongoose Logic
13
19
  let user = await User.findOne({ email });
14
20
  if (user) {
15
21
  return res.status(400).json({ message: 'User already exists' });
16
22
  }
17
-
18
- // Create a new user instance
19
- user = new User({
20
- name,
21
- email,
22
- password, // Password will be hashed by the pre-save hook in the model
23
- });
24
-
25
- // Save the user to the database
23
+ user = new User({ name, email, password });
26
24
  await user.save();
25
+ <% } else if (dbType === 'prisma') { %>
26
+ // Prisma Logic
27
+ const existingUser = await prisma.user.findUnique({ where: { email } });
28
+ if (existingUser) {
29
+ return res.status(400).json({ message: 'User already exists' });
30
+ }
31
+ const hashedPassword = await bcrypt.hash(password, 10);
32
+ const user = await prisma.user.create({
33
+ data: { name, email, password: hashedPassword },
34
+ });
35
+ <% } %>
27
36
 
28
- // Create JWT Payload
29
- const payload = {
30
- user: {
31
- id: user.id,
32
- },
33
- };
34
-
35
- // Sign the token
37
+ const payload = { user: { id: user.id } };
36
38
  jwt.sign(
37
39
  payload,
38
40
  process.env.JWT_SECRET as string,
39
- { expiresIn: '5h' }, // Token expires in 5 hours
41
+ { expiresIn: '5h' },
40
42
  (err, token) => {
41
43
  if (err) throw err;
42
44
  res.status(201).json({ token });
@@ -53,26 +55,24 @@ export const loginUser = async (req: Request, res: Response) => {
53
55
  const { email, password } = req.body;
54
56
 
55
57
  try {
56
- // Check if user exists
58
+ <% if (dbType === 'mongoose') { %>
59
+ // Mongoose Logic
57
60
  const user = await User.findOne({ email });
61
+ <% } else if (dbType === 'prisma') { %>
62
+ // Prisma Logic
63
+ const user = await prisma.user.findUnique({ where: { email } });
64
+ <% } %>
65
+
58
66
  if (!user) {
59
67
  return res.status(400).json({ message: 'Invalid Credentials' });
60
68
  }
61
69
 
62
- // Compare entered password with stored hashed password
63
70
  const isMatch = await bcrypt.compare(password, user.password);
64
71
  if (!isMatch) {
65
72
  return res.status(400).json({ message: 'Invalid Credentials' });
66
73
  }
67
74
 
68
- // Create JWT Payload
69
- const payload = {
70
- user: {
71
- id: user.id,
72
- },
73
- };
74
-
75
- // Sign the token
75
+ const payload = { user: { id: user.id } };
76
76
  jwt.sign(
77
77
  payload,
78
78
  process.env.JWT_SECRET as string,
@@ -1,125 +1,50 @@
1
- // Auto-generated by create-backlist v5.0
2
- using Microsoft.AspNetCore.Mvc;
3
- using Microsoft.EntityFrameworkCore;
4
- using <%= projectName %>.Data;
5
- using <%= projectName %>.Models;
6
-
7
- namespace <%= projectName %>.Controllers
8
- {
9
- [ApiController]
10
- [Route("api/[controller]")]
11
- public class <%= controllerName %>Controller : ControllerBase
12
- {
13
- private readonly ApplicationDbContext _context;
14
-
15
- public <%= controllerName %>Controller(ApplicationDbContext context)
16
- {
17
- _context = context;
18
- }
19
-
20
- // GET: api/<%= controllerName.toLowerCase() %>
21
- // Retrieves all items.
22
- [HttpGet]
23
- public async Task<ActionResult<IEnumerable<<%= controllerName %>>>> Get<%= controllerName %>s()
24
- {
25
- if (_context.<%= controllerName %>s == null)
26
- {
27
- return NotFound("Entity set '<%= controllerName %>s' is null.");
28
- }
29
- return await _context.<%= controllerName %>s.ToListAsync();
30
- }
31
-
32
- // GET: api/<%= controllerName.toLowerCase() %>/{id}
33
- // Retrieves a specific item by its ID.
34
- [HttpGet("{id}")]
35
- public async Task<ActionResult<<%= controllerName %>>> Get<%= controllerName %>(Guid id)
36
- {
37
- if (_context.<%= controllerName %>s == null)
38
- {
39
- return NotFound();
40
- }
41
- var item = await _context.<%= controllerName %>s.FindAsync(id);
42
-
43
- if (item == null)
44
- {
45
- return NotFound();
46
- }
47
-
48
- return item;
49
- }
50
-
51
- // PUT: api/<%= controllerName.toLowerCase() %>/{id}
52
- // Updates a specific item.
53
- [HttpPut("{id}")]
54
- public async Task<IActionResult> Put<%= controllerName %>(Guid id, <%= controllerName %> item)
55
- {
56
- if (id != item.Id)
57
- {
58
- return BadRequest();
59
- }
60
-
61
- _context.Entry(item).State = EntityState.Modified;
62
-
63
- try
64
- {
65
- await _context.SaveChangesAsync();
66
- }
67
- catch (DbUpdateConcurrencyException)
68
- {
69
- if (!ItemExists(id))
70
- {
71
- return NotFound();
72
- }
73
- else
74
- {
75
- throw;
76
- }
77
- }
78
-
79
- return NoContent();
80
- }
81
-
82
- // POST: api/<%= controllerName.toLowerCase() %>
83
- // Creates a new item.
84
- [HttpPost]
85
- public async Task<ActionResult<<%= controllerName %>>> Post<%= controllerName %>(<%= controllerName %> item)
86
- {
87
- if (_context.<%= controllerName %>s == null)
88
- {
89
- return Problem("Entity set '<%= controllerName %>s' is null.");
90
- }
91
- // Ensure a new Guid is created for the new item
92
- item.Id = Guid.NewGuid();
93
- _context.<%= controllerName %>s.Add(item);
94
- await _context.SaveChangesAsync();
95
-
96
- return CreatedAtAction(nameof(Get<%= controllerName %>), new { id = item.Id }, item);
97
- }
98
-
99
- // DELETE: api/<%= controllerName.toLowerCase() %>/{id}
100
- // Deletes a specific item.
101
- [HttpDelete("{id}")]
102
- public async Task<IActionResult> Delete<%= controllerName %>(Guid id)
103
- {
104
- if (_context.<%= controllerName %>s == null)
105
- {
106
- return NotFound();
107
- }
108
- var item = await _context.<%= controllerName %>s.FindAsync(id);
109
- if (item == null)
110
- {
111
- return NotFound();
112
- }
113
-
114
- _context.<%= controllerName %>s.Remove(item);
115
- await _context.SaveChangesAsync();
116
-
117
- return NoContent();
118
- }
119
-
120
- private bool ItemExists(Guid id)
121
- {
122
- return (_context.<%= controllerName %>s?.Any(e => e.Id == id)).GetValueOrDefault();
123
- }
124
- }
125
- }
1
+ // Auto-generated by create-backlist on <%= new Date().toISOString() %>
2
+ import { Request, Response } from 'express';
3
+
4
+ export const create<%= modelName %> = async (req: Request, res: Response) => {
5
+ try {
6
+ // TODO: implement create logic
7
+ res.status(201).json({ message: '<%= modelName %> created', data: req.body });
8
+ } catch (error) {
9
+ res.status(500).json({ message: 'Error creating <%= modelName %>', error });
10
+ }
11
+ };
12
+
13
+ export const getAll<%= modelName %>s = async (_req: Request, res: Response) => {
14
+ try {
15
+ // TODO: implement list logic
16
+ res.status(200).json([]);
17
+ } catch (error) {
18
+ res.status(500).json({ message: 'Error fetching <%= modelName %>s', error });
19
+ }
20
+ };
21
+
22
+ export const get<%= modelName %>ById = async (req: Request, res: Response) => {
23
+ try {
24
+ const { id } = req.params;
25
+ // TODO: implement get by id logic
26
+ res.status(200).json({ id });
27
+ } catch (error) {
28
+ res.status(500).json({ message: 'Error fetching <%= modelName %>', error });
29
+ }
30
+ };
31
+
32
+ export const update<%= modelName %>ById = async (req: Request, res: Response) => {
33
+ try {
34
+ const { id } = req.params;
35
+ // TODO: implement update logic
36
+ res.status(200).json({ id, ...req.body });
37
+ } catch (error) {
38
+ res.status(500).json({ message: 'Error updating <%= modelName %>', error });
39
+ }
40
+ };
41
+
42
+ export const delete<%= modelName %>ById = async (req: Request, res: Response) => {
43
+ try {
44
+ const { id } = req.params;
45
+ // TODO: implement delete logic
46
+ res.status(204).send();
47
+ } catch (error) {
48
+ res.status(500).json({ message: 'Error deleting <%= modelName %>', error });
49
+ }
50
+ };