create-backlist 2.0.1 → 3.0.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.
package/bin/index.js CHANGED
@@ -38,11 +38,21 @@ async function main() {
38
38
  name: 'srcPath',
39
39
  message: 'Enter the path to your frontend `src` directory:',
40
40
  default: 'src',
41
+ },
42
+ // --- NEW QUESTION FOR V3.0 ---
43
+ {
44
+ type: 'confirm',
45
+ name: 'addAuth',
46
+ message: 'Do you want to add basic JWT authentication? (generates a User model, login/register routes)',
47
+ default: true,
48
+ // This question will only be asked if the user selects the Node.js stack
49
+ when: (answers) => answers.stack === 'node-ts-express'
41
50
  }
42
51
  ]);
43
52
 
53
+ // Pass all answers to the options object
44
54
  const options = {
45
- ...answers,
55
+ ...answers,
46
56
  projectDir: path.resolve(process.cwd(), answers.projectName),
47
57
  frontendSrcDir: path.resolve(process.cwd(), answers.srcPath),
48
58
  };
@@ -53,7 +63,7 @@ async function main() {
53
63
  // --- Dispatcher Logic ---
54
64
  switch (options.stack) {
55
65
  case 'node-ts-express':
56
- await generateNodeProject(options);
66
+ await generateNodeProject(options); // Pass the entire options object
57
67
  break;
58
68
 
59
69
  case 'dotnet-webapi':
@@ -61,6 +71,7 @@ async function main() {
61
71
  throw new Error('.NET SDK is not installed. Please install it from https://dotnet.microsoft.com/download');
62
72
  }
63
73
  await generateDotnetProject(options);
74
+
64
75
  break;
65
76
 
66
77
  default:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-backlist",
3
- "version": "2.0.1",
3
+ "version": "3.0.0",
4
4
  "description": "An advanced, multi-language backend generator based on frontend analysis.",
5
5
  "type": "commonjs",
6
6
  "bin": {
@@ -6,7 +6,7 @@ const { analyzeFrontend } = require('../analyzer');
6
6
  const { renderAndWrite, getTemplatePath } = require('./template');
7
7
 
8
8
  async function generateNodeProject(options) {
9
- const { projectDir, projectName, frontendSrcDir } = options;
9
+ const { projectDir, projectName, frontendSrcDir, addAuth } = options;
10
10
 
11
11
  try {
12
12
  // --- Step 1: Analyze Frontend to get Endpoints and Schema Info ---
@@ -21,53 +21,55 @@ async function generateNodeProject(options) {
21
21
  // --- Step 2: Identify which Database Models to Generate ---
22
22
  const modelsToGenerate = new Map();
23
23
  endpoints.forEach(ep => {
24
- // If an endpoint has schemaFields and a valid controllerName, add it to our map.
25
24
  if (ep.schemaFields && ep.controllerName !== 'Default' && !modelsToGenerate.has(ep.controllerName)) {
26
25
  modelsToGenerate.set(ep.controllerName, ep.schemaFields);
27
26
  }
28
27
  });
29
28
 
29
+ // If auth is enabled, we MUST have a 'User' model.
30
+ if (addAuth && !modelsToGenerate.has('User')) {
31
+ console.log(chalk.yellow(' -> Authentication requires a "User" model. Creating a default one.'));
32
+ modelsToGenerate.set('User', { name: 'String', email: 'String', password: 'String' });
33
+ }
34
+
30
35
  // --- Step 3: Scaffold Base Project Structure & Files ---
31
36
  console.log(chalk.blue(' -> Scaffolding Node.js (Express + TS) project...'));
32
-
33
- // Create the main source directory
34
37
  const destSrcDir = path.join(projectDir, 'src');
35
38
  await fs.ensureDir(destSrcDir);
36
-
37
- // Copy static base files
38
39
  await fs.copy(getTemplatePath('node-ts-express/base/server.ts'), path.join(destSrcDir, 'server.ts'));
39
40
  await fs.copy(getTemplatePath('node-ts-express/base/tsconfig.json'), path.join(projectDir, 'tsconfig.json'));
40
41
 
41
- // --- Step 4: Generate Dynamic Files (package.json, Models, Controllers) ---
42
-
43
- // Prepare package.json content (in memory)
42
+ // --- Step 4: Prepare and Write package.json ---
44
43
  const packageJsonContent = JSON.parse(
45
44
  await ejs.renderFile(getTemplatePath('node-ts-express/partials/package.json.ejs'), { projectName })
46
45
  );
47
46
 
48
- // Conditionally add Mongoose if we are generating models
49
- if (modelsToGenerate.size > 0) {
50
- console.log(chalk.gray(' -> Preparing to add Mongoose to dependencies...'));
51
- packageJsonContent.dependencies['mongoose'] = '^7.5.0'; // Use a recent, stable version
47
+ if (modelsToGenerate.size > 0 || addAuth) {
48
+ packageJsonContent.dependencies['mongoose'] = '^7.5.0';
49
+ }
50
+ if (addAuth) {
51
+ packageJsonContent.dependencies['jsonwebtoken'] = '^9.0.2';
52
+ packageJsonContent.dependencies['bcryptjs'] = '^2.4.3';
53
+ packageJsonContent.devDependencies['@types/jsonwebtoken'] = '^9.0.2';
54
+ packageJsonContent.devDependencies['@types/bcryptjs'] = '^2.4.2';
52
55
  }
53
-
54
- // Write the final package.json to the disk
55
56
  await fs.writeJson(path.join(projectDir, 'package.json'), packageJsonContent, { spaces: 2 });
56
57
 
57
- // Generate Model and Controller files if any were found
58
+ // --- Step 5: Generate Models and Controllers ---
58
59
  if (modelsToGenerate.size > 0) {
59
60
  console.log(chalk.blue(' -> Generating database models and controllers...'));
60
61
  await fs.ensureDir(path.join(destSrcDir, 'models'));
61
62
  await fs.ensureDir(path.join(destSrcDir, 'controllers'));
62
63
 
63
- for (const [modelName, schema] of modelsToGenerate.entries()) {
64
- // Generate Model File (e.g., models/User.model.ts)
64
+ for (let [modelName, schema] of modelsToGenerate.entries()) {
65
+ if (addAuth && modelName === 'User') {
66
+ schema = { name: 'String', email: 'String', password: 'String', ...schema };
67
+ }
65
68
  await renderAndWrite(
66
69
  getTemplatePath('node-ts-express/partials/Model.ts.ejs'),
67
70
  path.join(destSrcDir, 'models', `${modelName}.model.ts`),
68
71
  { modelName, schema }
69
72
  );
70
- // Generate Controller File (e.g., controllers/User.controller.ts)
71
73
  await renderAndWrite(
72
74
  getTemplatePath('node-ts-express/partials/Controller.ts.ejs'),
73
75
  path.join(destSrcDir, 'controllers', `${modelName}.controller.ts`),
@@ -75,25 +77,53 @@ async function generateNodeProject(options) {
75
77
  );
76
78
  }
77
79
  }
80
+
81
+ // --- Step 6 (v3.0): Generate Authentication Boilerplate ---
82
+ if (addAuth) {
83
+ console.log(chalk.blue(' -> Generating authentication boilerplate...'));
84
+ await fs.ensureDir(path.join(destSrcDir, 'routes'));
85
+ await fs.ensureDir(path.join(destSrcDir, 'middleware'));
78
86
 
79
- // --- Step 5: Generate the Smart Route File ---
80
- console.log(chalk.gray(' -> Generating dynamic routes...'));
81
- await renderAndWrite(
82
- getTemplatePath('node-ts-express/partials/routes.ts.ejs'),
83
- path.join(destSrcDir, 'routes.ts'),
84
- { endpoints } // Pass all endpoints to the template
85
- );
87
+ await renderAndWrite(getTemplatePath('node-ts-express/partials/Auth.controller.ts.ejs'), path.join(destSrcDir, 'controllers', 'Auth.controller.ts'), {});
88
+ await renderAndWrite(getTemplatePath('node-ts-express/partials/Auth.routes.ts.ejs'), path.join(destSrcDir, 'routes', 'Auth.routes.ts'), {});
89
+ await renderAndWrite(getTemplatePath('node-ts-express/partials/Auth.middleware.ts.ejs'), path.join(destSrcDir, 'middleware', 'Auth.middleware.ts'), {});
90
+
91
+ // Modify the User model to add password hashing
92
+ const userModelPath = path.join(destSrcDir, 'models', 'User.model.ts');
93
+ if (await fs.pathExists(userModelPath)) {
94
+ let userModelContent = await fs.readFile(userModelPath, 'utf-8');
95
+ if (!userModelContent.includes('bcryptjs')) {
96
+ userModelContent = userModelContent.replace(`import mongoose, { Schema, Document } from 'mongoose';`, `import mongoose, { Schema, Document } from 'mongoose';\nimport bcrypt from 'bcryptjs';`);
97
+ const preSaveHook = `
98
+ // Hash password before saving
99
+ UserSchema.pre('save', async function(next) {
100
+ if (!this.isModified('password')) {
101
+ return next();
102
+ }
103
+ const salt = await bcrypt.genSalt(10);
104
+ this.password = await bcrypt.hash(this.password, salt);
105
+ next();
106
+ });
107
+ `;
108
+ userModelContent = userModelContent.replace(`// Create and export the Model`, `${preSaveHook}\n// Create and export the Model`);
109
+ await fs.writeFile(userModelPath, userModelContent);
110
+ }
111
+ }
112
+ }
113
+
114
+ // --- Step 7: Generate the Main Route File ---
115
+ console.log(chalk.gray(' -> Generating dynamic API routes...'));
116
+ await renderAndWrite(getTemplatePath('node-ts-express/partials/routes.ts.ejs'), path.join(destSrcDir, 'routes.ts'), { endpoints, addAuth });
86
117
 
87
- // --- Step 6: Inject Routes into the Main Server File ---
88
- const serverDestPath = path.join(destSrcDir, 'server.ts');
89
- let serverFileContent = await fs.readFile(serverDestPath, 'utf-8');
118
+ // --- Step 8: Inject Logic into Main Server File ---
119
+ let serverFileContent = await fs.readFile(path.join(destSrcDir, 'server.ts'), 'utf-8');
90
120
 
91
121
  let dbConnectionCode = '';
92
- if (modelsToGenerate.size > 0) {
122
+ if (modelsToGenerate.size > 0 || addAuth) {
93
123
  dbConnectionCode = `
94
124
  // --- Database Connection ---
95
125
  import mongoose from 'mongoose';
96
- const MONGO_URI = process.env.MONGO_URI || 'mongodb://localhost:27017/${projectName}';
126
+ const MONGO_URI = process.env.MONGO_URI || 'mongodb://127.0.0.1:27017/${projectName}';
97
127
  mongoose.connect(MONGO_URI)
98
128
  .then(() => console.log('MongoDB Connected...'))
99
129
  .catch(err => console.error('MongoDB Connection Error:', err));
@@ -101,27 +131,35 @@ mongoose.connect(MONGO_URI)
101
131
  `;
102
132
  }
103
133
 
104
- // Inject DB connection code after dotenv.config() and route loader
134
+ let authRoutesInjector = '';
135
+ if (addAuth) {
136
+ authRoutesInjector = `import authRoutes from './routes/Auth.routes';\napp.use('/api/auth', authRoutes);\n\n`;
137
+ }
138
+
105
139
  serverFileContent = serverFileContent
106
140
  .replace("dotenv.config();", `dotenv.config();\n${dbConnectionCode}`)
107
- .replace('// INJECT:ROUTES', `import apiRoutes from './routes';\napp.use(apiRoutes);`);
141
+ .replace('// INJECT:ROUTES', `${authRoutesInjector}import apiRoutes from './routes';\napp.use('/api', apiRoutes);`); // Changed to /api
108
142
 
109
- await fs.writeFile(serverDestPath, serverFileContent);
143
+ await fs.writeFile(path.join(destSrcDir, 'server.ts'), serverFileContent);
110
144
 
111
- // --- Step 7: Install All Dependencies at Once ---
112
- console.log(chalk.magenta(' -> Installing dependencies (npm install)... This might take a moment.'));
145
+ // --- Step 9: Install All Dependencies ---
146
+ console.log(chalk.magenta(' -> Installing all dependencies... This might take a moment.'));
113
147
  await execa('npm', ['install'], { cwd: projectDir });
114
148
 
115
- // --- Step 8: Generate README ---
149
+ // --- Step 10: Generate Final Files (README, .env.example) ---
116
150
  await renderAndWrite(
117
151
  getTemplatePath('node-ts-express/partials/README.md.ejs'),
118
152
  path.join(projectDir, 'README.md'),
119
153
  { projectName }
120
154
  );
155
+
156
+ if (addAuth) {
157
+ const envExampleContent = `PORT=8000\nMONGO_URI=mongodb://127.0.0.1:27017/${projectName}\nJWT_SECRET=your_super_secret_jwt_key_123`;
158
+ await fs.writeFile(path.join(projectDir, '.env.example'), envExampleContent);
159
+ }
160
+
121
161
 
122
162
  } catch (error) {
123
- // Re-throw the error so it can be caught by the main CLI handler in index.js
124
- // This allows for centralized error message display and cleanup.
125
163
  throw error;
126
164
  }
127
165
  }
@@ -0,0 +1,89 @@
1
+ // Auto-generated by create-backlist v3.0 on <%= new Date().toISOString() %>
2
+ import { Request, Response } from 'express';
3
+ import bcrypt from 'bcryptjs';
4
+ import jwt from 'jsonwebtoken';
5
+ import User, { IUser } from '../models/User.model'; // We assume the model is named 'User'
6
+
7
+ // @desc Register a new user
8
+ export const registerUser = async (req: Request, res: Response) => {
9
+ const { name, email, password } = req.body;
10
+
11
+ try {
12
+ // Check if user already exists
13
+ let user = await User.findOne({ email });
14
+ if (user) {
15
+ return res.status(400).json({ message: 'User already exists' });
16
+ }
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
26
+ await user.save();
27
+
28
+ // Create JWT Payload
29
+ const payload = {
30
+ user: {
31
+ id: user.id,
32
+ },
33
+ };
34
+
35
+ // Sign the token
36
+ jwt.sign(
37
+ payload,
38
+ process.env.JWT_SECRET as string,
39
+ { expiresIn: '5h' }, // Token expires in 5 hours
40
+ (err, token) => {
41
+ if (err) throw err;
42
+ res.status(201).json({ token });
43
+ }
44
+ );
45
+ } catch (error) {
46
+ console.error(error);
47
+ res.status(500).send('Server Error');
48
+ }
49
+ };
50
+
51
+ // @desc Authenticate user & get token (Login)
52
+ export const loginUser = async (req: Request, res: Response) => {
53
+ const { email, password } = req.body;
54
+
55
+ try {
56
+ // Check if user exists
57
+ const user = await User.findOne({ email });
58
+ if (!user) {
59
+ return res.status(400).json({ message: 'Invalid Credentials' });
60
+ }
61
+
62
+ // Compare entered password with stored hashed password
63
+ const isMatch = await bcrypt.compare(password, user.password);
64
+ if (!isMatch) {
65
+ return res.status(400).json({ message: 'Invalid Credentials' });
66
+ }
67
+
68
+ // Create JWT Payload
69
+ const payload = {
70
+ user: {
71
+ id: user.id,
72
+ },
73
+ };
74
+
75
+ // Sign the token
76
+ jwt.sign(
77
+ payload,
78
+ process.env.JWT_SECRET as string,
79
+ { expiresIn: '5h' },
80
+ (err, token) => {
81
+ if (err) throw err;
82
+ res.json({ token });
83
+ }
84
+ );
85
+ } catch (error) {
86
+ console.error(error);
87
+ res.status(500).send('Server Error');
88
+ }
89
+ };
@@ -0,0 +1,27 @@
1
+ // Auto-generated by create-backlist v3.0 on <%= new Date().toISOString() %>
2
+ import { Request, Response, NextFunction } from 'express';
3
+ import jwt from 'jsonwebtoken';
4
+
5
+ // Extend the default Request interface to include our 'user' property
6
+ interface AuthRequest extends Request {
7
+ user?: any;
8
+ }
9
+
10
+ export const protect = (req: AuthRequest, res: Response, next: NextFunction) => {
11
+ // Get token from header
12
+ const token = req.header('x-auth-token');
13
+
14
+ // Check if not token
15
+ if (!token) {
16
+ return res.status(401).json({ message: 'No token, authorization denied' });
17
+ }
18
+
19
+ // Verify token
20
+ try {
21
+ const decoded = jwt.verify(token, process.env.JWT_SECRET as string);
22
+ req.user = decoded.user;
23
+ next();
24
+ } catch (err) {
25
+ res.status(401).json({ message: 'Token is not valid' });
26
+ }
27
+ };
@@ -0,0 +1,15 @@
1
+ // Auto-generated by create-backlist v3.0 on <%= new Date().toISOString() %>
2
+ import { Router } from 'express';
3
+ import { registerUser, loginUser } from '../controllers/Auth.controller';
4
+
5
+ const router = Router();
6
+
7
+ // @route POST /api/auth/register
8
+ // @desc Register a new user
9
+ router.post('/register', registerUser);
10
+
11
+ // @route POST /api/auth/login
12
+ // @desc Authenticate user and get token
13
+ router.post('/login', loginUser);
14
+
15
+ export default router;
@@ -1,4 +1,4 @@
1
- // Auto-generated by create-backlist on <%= new Date().toISOString() %>
1
+ // Auto-generated by create-backlist v3.0 on <%= new Date().toISOString() %>
2
2
  import { Router, Request, Response } from 'express';
3
3
  <%# Create a unique set of controller names from the endpoints array %>
4
4
  <% const controllersToImport = new Set(endpoints.map(ep => ep.controllerName).filter(name => name !== 'Default')); %>
@@ -8,6 +8,11 @@ import { Router, Request, Response } from 'express';
8
8
  import * as <%= controller %>Controller from '../controllers/<%= controller %>.controller';
9
9
  <% } %>
10
10
 
11
+ <%# Import the protect middleware only if authentication is enabled %>
12
+ <% if (addAuth) { %>
13
+ import { protect } from '../middleware/Auth.middleware';
14
+ <% } %>
15
+
11
16
  const router = Router();
12
17
 
13
18
  <%# Loop through each endpoint found by the analyzer %>
@@ -19,41 +24,40 @@ const router = Router();
19
24
  let handlerFunction;
20
25
 
21
26
  // --- LOGIC TO MAP ENDPOINT TO A CRUD CONTROLLER FUNCTION ---
22
- // This logic assumes a standard RESTful API structure.
23
-
24
27
  if (controllerName !== 'Default') {
25
28
  if (endpoint.method === 'POST' && !expressPath.includes(':')) {
26
- // e.g., POST /users -> create a new user
27
29
  handlerFunction = `${controllerName}Controller.create${controllerName}`;
28
30
  } else if (endpoint.method === 'GET' && !expressPath.includes(':')) {
29
- // e.g., GET /users -> get all users
30
31
  handlerFunction = `${controllerName}Controller.getAll${controllerName}s`;
31
32
  } else if (endpoint.method === 'GET' && expressPath.includes(':')) {
32
- // e.g., GET /users/:id -> get a single user by ID
33
33
  handlerFunction = `${controllerName}Controller.get${controllerName}ById`;
34
34
  } else if (endpoint.method === 'PUT' && expressPath.includes(':')) {
35
- // e.g., PUT /users/:id -> update a user by ID
36
35
  handlerFunction = `${controllerName}Controller.update${controllerName}ById`;
37
36
  } else if (endpoint.method === 'DELETE' && expressPath.includes(':')) {
38
- // e.g., DELETE /users/:id -> delete a user by ID
39
37
  handlerFunction = `${controllerName}Controller.delete${controllerName}ById`;
40
38
  }
41
39
  }
42
40
 
43
- // If no specific CRUD function matches, or if it's a default/unhandled route,
44
- // create a simple placeholder function.
41
+ // If no specific CRUD function matches, create a placeholder handler.
45
42
  if (!handlerFunction) {
46
43
  handlerFunction = `(req: Request, res: Response) => {
47
- // TODO: Implement logic for this custom endpoint
48
44
  res.status(501).json({ message: 'Handler not implemented for <%= endpoint.method %> <%= expressPath %>' });
49
45
  }`;
50
46
  }
47
+
48
+ // --- V3.0 AUTH LOGIC: Decide if the route should be protected ---
49
+ // We protect all routes that modify data (POST, PUT, DELETE) if auth is enabled.
50
+ // We leave GET routes public by default. This is a common pattern.
51
+ const middleware = (addAuth && (endpoint.method === 'POST' || endpoint.method === 'PUT' || endpoint.method === 'DELETE'))
52
+ ? 'protect, '
53
+ : '';
51
54
  %>
52
55
  /**
53
56
  * Route for <%= endpoint.method.toUpperCase() %> <%= endpoint.path %>
54
57
  * Mapped to: <%- handlerFunction.includes('=>') ? 'Inline Handler' : handlerFunction %>
58
+ * Protected: <%= middleware ? 'Yes' : 'No' %>
55
59
  */
56
- router.<%= endpoint.method.toLowerCase() %>('<%- expressPath %>', <%- handlerFunction %>);
60
+ router.<%= endpoint.method.toLowerCase() %>('<%- expressPath %>', <%- middleware %><%- handlerFunction %>);
57
61
 
58
62
  <% }); %>
59
63