create-backlist 3.0.0 → 4.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
@@ -34,19 +34,26 @@ async function main() {
34
34
  ],
35
35
  },
36
36
  {
37
- type: 'input',
38
- name: 'srcPath',
39
- message: 'Enter the path to your frontend `src` directory:',
40
- default: 'src',
37
+ type: 'input',
38
+ name: 'srcPath',
39
+ message: 'Enter the path to your frontend `src` directory:',
40
+ default: 'src',
41
41
  },
42
- // --- NEW QUESTION FOR V3.0 ---
43
42
  {
44
43
  type: 'confirm',
45
44
  name: 'addAuth',
46
45
  message: 'Do you want to add basic JWT authentication? (generates a User model, login/register routes)',
47
46
  default: true,
48
- // This question will only be asked if the user selects the Node.js stack
49
47
  when: (answers) => answers.stack === 'node-ts-express'
48
+ },
49
+ // --- NEW QUESTION FOR V4.0 ---
50
+ {
51
+ type: 'confirm',
52
+ name: 'addSeeder',
53
+ message: 'Do you want to add a database seeder with sample user data?',
54
+ default: true,
55
+ // Only ask this if Node.js is selected AND authentication is being added
56
+ when: (answers) => answers.stack === 'node-ts-express' && answers.addAuth
50
57
  }
51
58
  ]);
52
59
 
@@ -71,7 +78,6 @@ async function main() {
71
78
  throw new Error('.NET SDK is not installed. Please install it from https://dotnet.microsoft.com/download');
72
79
  }
73
80
  await generateDotnetProject(options);
74
-
75
81
  break;
76
82
 
77
83
  default:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-backlist",
3
- "version": "3.0.0",
3
+ "version": "4.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,8 @@ const { analyzeFrontend } = require('../analyzer');
6
6
  const { renderAndWrite, getTemplatePath } = require('./template');
7
7
 
8
8
  async function generateNodeProject(options) {
9
- const { projectDir, projectName, frontendSrcDir, addAuth } = options;
9
+ // v4.0: Destructure the new 'addSeeder' option
10
+ const { projectDir, projectName, frontendSrcDir, addAuth, addSeeder } = options;
10
11
 
11
12
  try {
12
13
  // --- Step 1: Analyze Frontend to get Endpoints and Schema Info ---
@@ -26,7 +27,6 @@ async function generateNodeProject(options) {
26
27
  }
27
28
  });
28
29
 
29
- // If auth is enabled, we MUST have a 'User' model.
30
30
  if (addAuth && !modelsToGenerate.has('User')) {
31
31
  console.log(chalk.yellow(' -> Authentication requires a "User" model. Creating a default one.'));
32
32
  modelsToGenerate.set('User', { name: 'String', email: 'String', password: 'String' });
@@ -39,7 +39,7 @@ async function generateNodeProject(options) {
39
39
  await fs.copy(getTemplatePath('node-ts-express/base/server.ts'), path.join(destSrcDir, 'server.ts'));
40
40
  await fs.copy(getTemplatePath('node-ts-express/base/tsconfig.json'), path.join(projectDir, 'tsconfig.json'));
41
41
 
42
- // --- Step 4: Prepare and Write package.json ---
42
+ // --- Step 4: Prepare and Write package.json with all conditional dependencies ---
43
43
  const packageJsonContent = JSON.parse(
44
44
  await ejs.renderFile(getTemplatePath('node-ts-express/partials/package.json.ejs'), { projectName })
45
45
  );
@@ -53,112 +53,90 @@ async function generateNodeProject(options) {
53
53
  packageJsonContent.devDependencies['@types/jsonwebtoken'] = '^9.0.2';
54
54
  packageJsonContent.devDependencies['@types/bcryptjs'] = '^2.4.2';
55
55
  }
56
+ // v4.0: Add seeder dependencies and scripts
57
+ if (addSeeder) {
58
+ packageJsonContent.devDependencies['@faker-js/faker'] = '^8.2.0';
59
+ // We also need chalk for the seeder script's console logs
60
+ packageJsonContent.dependencies['chalk'] = '^4.1.2';
61
+ packageJsonContent.scripts['seed'] = 'ts-node scripts/seeder.ts';
62
+ packageJsonContent.scripts['destroy'] = 'ts-node scripts/seeder.ts -d';
63
+ }
56
64
  await fs.writeJson(path.join(projectDir, 'package.json'), packageJsonContent, { spaces: 2 });
57
65
 
58
- // --- Step 5: Generate Models and Controllers ---
66
+ // --- Step 5 & 6: Generate Models, Controllers, and Auth boilerplate ---
59
67
  if (modelsToGenerate.size > 0) {
60
- console.log(chalk.blue(' -> Generating database models and controllers...'));
61
- await fs.ensureDir(path.join(destSrcDir, 'models'));
62
- await fs.ensureDir(path.join(destSrcDir, 'controllers'));
63
-
64
- for (let [modelName, schema] of modelsToGenerate.entries()) {
65
- if (addAuth && modelName === 'User') {
66
- schema = { name: 'String', email: 'String', password: 'String', ...schema };
68
+ console.log(chalk.blue(' -> Generating database models and controllers...'));
69
+ await fs.ensureDir(path.join(destSrcDir, 'models'));
70
+ await fs.ensureDir(path.join(destSrcDir, 'controllers'));
71
+ for (let [modelName, schema] of modelsToGenerate.entries()) {
72
+ if (addAuth && modelName === 'User') {
73
+ schema = { name: 'String', email: 'String', password: 'String', ...schema };
74
+ }
75
+ await renderAndWrite(getTemplatePath('node-ts-express/partials/Model.ts.ejs'), path.join(destSrcDir, 'models', `${modelName}.model.ts`), { modelName, schema });
76
+ await renderAndWrite(getTemplatePath('node-ts-express/partials/Controller.ts.ejs'), path.join(destSrcDir, 'controllers', `${modelName}.controller.ts`), { modelName });
67
77
  }
68
- await renderAndWrite(
69
- getTemplatePath('node-ts-express/partials/Model.ts.ejs'),
70
- path.join(destSrcDir, 'models', `${modelName}.model.ts`),
71
- { modelName, schema }
72
- );
73
- await renderAndWrite(
74
- getTemplatePath('node-ts-express/partials/Controller.ts.ejs'),
75
- path.join(destSrcDir, 'controllers', `${modelName}.controller.ts`),
76
- { modelName }
77
- );
78
- }
79
78
  }
80
-
81
- // --- Step 6 (v3.0): Generate Authentication Boilerplate ---
82
79
  if (addAuth) {
83
80
  console.log(chalk.blue(' -> Generating authentication boilerplate...'));
84
81
  await fs.ensureDir(path.join(destSrcDir, 'routes'));
85
82
  await fs.ensureDir(path.join(destSrcDir, 'middleware'));
86
-
87
83
  await renderAndWrite(getTemplatePath('node-ts-express/partials/Auth.controller.ts.ejs'), path.join(destSrcDir, 'controllers', 'Auth.controller.ts'), {});
88
84
  await renderAndWrite(getTemplatePath('node-ts-express/partials/Auth.routes.ts.ejs'), path.join(destSrcDir, 'routes', 'Auth.routes.ts'), {});
89
85
  await renderAndWrite(getTemplatePath('node-ts-express/partials/Auth.middleware.ts.ejs'), path.join(destSrcDir, 'middleware', 'Auth.middleware.ts'), {});
90
86
 
91
- // Modify the User model to add password hashing
92
87
  const userModelPath = path.join(destSrcDir, 'models', 'User.model.ts');
93
88
  if (await fs.pathExists(userModelPath)) {
94
89
  let userModelContent = await fs.readFile(userModelPath, 'utf-8');
95
90
  if (!userModelContent.includes('bcryptjs')) {
96
91
  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
- `;
92
+ const preSaveHook = `\n// Hash password before saving\nUserSchema.pre('save', async function(next) {\n if (!this.isModified('password')) {\n return next();\n }\n const salt = await bcrypt.genSalt(10);\n this.password = await bcrypt.hash(this.password, salt);\n next();\n});\n`;
108
93
  userModelContent = userModelContent.replace(`// Create and export the Model`, `${preSaveHook}\n// Create and export the Model`);
109
94
  await fs.writeFile(userModelPath, userModelContent);
110
95
  }
111
96
  }
112
97
  }
113
98
 
114
- // --- Step 7: Generate the Main Route File ---
99
+ // --- Step 7 (v4.0): Generate Seeder Script ---
100
+ if (addSeeder) {
101
+ console.log(chalk.blue(' -> Generating database seeder script...'));
102
+ await fs.ensureDir(path.join(projectDir, 'scripts'));
103
+ await renderAndWrite(
104
+ getTemplatePath('node-ts-express/partials/Seeder.ts.ejs'),
105
+ path.join(projectDir, 'scripts', 'seeder.ts'),
106
+ { projectName }
107
+ );
108
+ }
109
+
110
+ // --- Step 8: Generate the Main Route File ---
115
111
  console.log(chalk.gray(' -> Generating dynamic API routes...'));
116
112
  await renderAndWrite(getTemplatePath('node-ts-express/partials/routes.ts.ejs'), path.join(destSrcDir, 'routes.ts'), { endpoints, addAuth });
117
113
 
118
- // --- Step 8: Inject Logic into Main Server File ---
114
+ // --- Step 9: Inject Logic into Main Server File ---
119
115
  let serverFileContent = await fs.readFile(path.join(destSrcDir, 'server.ts'), 'utf-8');
120
-
121
116
  let dbConnectionCode = '';
122
117
  if (modelsToGenerate.size > 0 || addAuth) {
123
- dbConnectionCode = `
124
- // --- Database Connection ---
125
- import mongoose from 'mongoose';
126
- const MONGO_URI = process.env.MONGO_URI || 'mongodb://127.0.0.1:27017/${projectName}';
127
- mongoose.connect(MONGO_URI)
128
- .then(() => console.log('MongoDB Connected...'))
129
- .catch(err => console.error('MongoDB Connection Error:', err));
130
- // -------------------------
131
- `;
118
+ 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)\n .then(() => console.log('MongoDB Connected...'))\n .catch(err => console.error('MongoDB Connection Error:', err));\n// -------------------------\n`;
132
119
  }
133
-
134
120
  let authRoutesInjector = '';
135
121
  if (addAuth) {
136
- authRoutesInjector = `import authRoutes from './routes/Auth.routes';\napp.use('/api/auth', authRoutes);\n\n`;
122
+ authRoutesInjector = `import authRoutes from './routes/Auth.routes';\napp.use('/api/auth', authRoutes);\n\n`;
137
123
  }
138
-
139
124
  serverFileContent = serverFileContent
140
- .replace("dotenv.config();", `dotenv.config();\n${dbConnectionCode}`)
141
- .replace('// INJECT:ROUTES', `${authRoutesInjector}import apiRoutes from './routes';\napp.use('/api', apiRoutes);`); // Changed to /api
142
-
125
+ .replace("dotenv.config();", `dotenv.config();${dbConnectionCode}`)
126
+ .replace('// INJECT:ROUTES', `${authRoutesInjector}import apiRoutes from './routes';\napp.use('/api', apiRoutes);`);
143
127
  await fs.writeFile(path.join(destSrcDir, 'server.ts'), serverFileContent);
144
128
 
145
- // --- Step 9: Install All Dependencies ---
129
+ // --- Step 10: Install All Dependencies ---
146
130
  console.log(chalk.magenta(' -> Installing all dependencies... This might take a moment.'));
147
131
  await execa('npm', ['install'], { cwd: projectDir });
148
132
 
149
- // --- Step 10: Generate Final Files (README, .env.example) ---
150
- await renderAndWrite(
151
- getTemplatePath('node-ts-express/partials/README.md.ejs'),
152
- path.join(projectDir, 'README.md'),
153
- { projectName }
154
- );
155
-
133
+ // --- Step 11: Generate Final Files (README, .env.example) ---
134
+ await renderAndWrite(getTemplatePath('node-ts-express/partials/README.md.ejs'), path.join(projectDir, 'README.md'), { projectName });
156
135
  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);
136
+ const envExampleContent = `PORT=8000\nMONGO_URI=mongodb://127.0.0.1:27017/${projectName}\nJWT_SECRET=your_super_secret_jwt_key_123`;
137
+ await fs.writeFile(path.join(projectDir, '.env.example'), envExampleContent);
159
138
  }
160
139
 
161
-
162
140
  } catch (error) {
163
141
  throw error;
164
142
  }
@@ -9,6 +9,9 @@ export const create<%= modelName %> = async (req: Request, res: Response) => {
9
9
  await newDoc.save();
10
10
  res.status(201).json(newDoc);
11
11
  } catch (error) {
12
+ if (error.name === 'ValidationError') {
13
+ return res.status(400).json({ message: 'Validation Error', errors: error.errors });
14
+ }
12
15
  res.status(500).json({ message: 'Error creating document', error });
13
16
  }
14
17
  };
@@ -41,6 +44,9 @@ export const update<%= modelName %>ById = async (req: Request, res: Response) =>
41
44
  if (!doc) return res.status(404).json({ message: 'Document not found' });
42
45
  res.status(200).json(doc);
43
46
  } catch (error) {
47
+ if (error.name === 'ValidationError') {
48
+ return res.status(400).json({ message: 'Validation Error', errors: error.errors });
49
+ }
44
50
  res.status(500).json({ message: 'Error updating document', error });
45
51
  }
46
52
  };
@@ -50,6 +56,7 @@ export const delete<%= modelName %>ById = async (req: Request, res: Response) =>
50
56
  try {
51
57
  const doc = await <%= modelName %>.findByIdAndDelete(req.params.id);
52
58
  if (!doc) return res.status(404).json({ message: 'Document not found' });
59
+ // For DELETE, it's common to return a success message or just a 204 No Content status.
53
60
  res.status(200).json({ message: 'Document deleted successfully' });
54
61
  } catch (error) {
55
62
  res.status(500).json({ message: 'Error deleting document', error });
@@ -0,0 +1,83 @@
1
+ // Auto-generated by create-backlist v4.0 on <%= new Date().toISOString() %>
2
+ import mongoose from 'mongoose';
3
+ import dotenv from 'dotenv';
4
+ import { faker } from '@faker-js/faker';
5
+ import chalk from 'chalk'; // For colorful console logs
6
+
7
+ // Load env vars
8
+ dotenv.config();
9
+
10
+ // We assume a User model exists for seeding.
11
+ // The path is relative to the generated 'backend' project root.
12
+ import User from '../src/models/User.model';
13
+
14
+ // --- Connect to DB ---
15
+ const connectDB = async () => {
16
+ try {
17
+ const MONGO_URI = process.env.MONGO_URI || 'mongodb://127.0.0.1:27017/<%= projectName %>';
18
+ if (!MONGO_URI) {
19
+ throw new Error('MONGO_URI is not defined in your .env file');
20
+ }
21
+ await mongoose.connect(MONGO_URI);
22
+ console.log(chalk.green('MongoDB Connected for Seeder...'));
23
+ } catch (err) {
24
+ console.error(chalk.red(`Seeder DB Connection Error: ${err.message}`));
25
+ process.exit(1);
26
+ }
27
+ };
28
+
29
+ // --- Import Data ---
30
+ const importData = async () => {
31
+ try {
32
+ // Clear existing data
33
+ await User.deleteMany();
34
+
35
+ const sampleUsers = [];
36
+ const userCount = 10; // Number of sample users to create
37
+
38
+ for (let i = 0; i < userCount; i++) {
39
+ sampleUsers.push({
40
+ name: faker.person.fullName(),
41
+ email: faker.internet.email().toLowerCase(),
42
+ password: 'password123', // All sample users will have the same password for easy testing
43
+ });
44
+ }
45
+
46
+ await User.insertMany(sampleUsers);
47
+
48
+ console.log(chalk.green.bold('✅ Data Imported Successfully!'));
49
+ process.exit();
50
+ } catch (error) {
51
+ console.error(chalk.red(`Error with data import: ${error.message}`));
52
+ process.exit(1);
53
+ }
54
+ };
55
+
56
+ // --- Destroy Data ---
57
+ const destroyData = async () => {
58
+ try {
59
+ await User.deleteMany();
60
+ // If you have other models, you can add them here for destruction
61
+ // e.g., await Product.deleteMany();
62
+
63
+ console.log(chalk.red.bold('🔥 Data Destroyed Successfully!'));
64
+ process.exit();
65
+ } catch (error) {
66
+ console.error(chalk.red(`Error with data destruction: ${error.message}`));
67
+ process.exit(1);
68
+ }
69
+ };
70
+
71
+ // --- CLI Logic to run the seeder ---
72
+ const runSeeder = async () => {
73
+ await connectDB();
74
+
75
+ // process.argv[2] will be '-d' if the script is run with `npm run destroy`
76
+ if (process.argv[2] === '-d') {
77
+ await destroyData();
78
+ } else {
79
+ await importData();
80
+ }
81
+ };
82
+
83
+ runSeeder();