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
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
package/src/generators/node.js
CHANGED
|
@@ -6,7 +6,8 @@ const { analyzeFrontend } = require('../analyzer');
|
|
|
6
6
|
const { renderAndWrite, getTemplatePath } = require('./template');
|
|
7
7
|
|
|
8
8
|
async function generateNodeProject(options) {
|
|
9
|
-
|
|
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
|
|
66
|
+
// --- Step 5 & 6: Generate Models, Controllers, and Auth boilerplate ---
|
|
59
67
|
if (modelsToGenerate.size > 0) {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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()
|
|
141
|
-
.replace('// INJECT:ROUTES', `${authRoutesInjector}import apiRoutes from './routes';\napp.use('/api', apiRoutes);`);
|
|
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
|
|
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
|
|
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
|
-
|
|
158
|
-
|
|
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();
|