nexpgen 1.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.
@@ -0,0 +1,205 @@
1
+ const path = require('path');
2
+ const fs = require('fs');
3
+ const fileGenerator = require('../utils/fileGenerator');
4
+
5
+
6
+ async function promptInquirer(questions) {
7
+ try {
8
+ const inquirer = require('inquirer');
9
+ if (typeof inquirer.createPromptModule === 'function') {
10
+ const prompt = inquirer.createPromptModule();
11
+ return await prompt(questions);
12
+ }
13
+
14
+
15
+ if (typeof inquirer.prompt === 'function') {
16
+ return await inquirer.prompt(questions);
17
+ }
18
+
19
+ throw new Error('Inquirer prompt method not available');
20
+ } catch (error) {
21
+
22
+ if (error.code === 'MODULE_NOT_FOUND') {
23
+ throw new Error('Inquirer package not found. Please install it: npm install inquirer');
24
+ }
25
+ throw error;
26
+ }
27
+ }
28
+
29
+ async function generate(type, name, options) {
30
+ try {
31
+ if (!type || typeof type !== 'string') {
32
+ console.error('❌ Error: Type must be a non-empty string');
33
+ process.exit(1);
34
+ }
35
+
36
+ if (!name || typeof name !== 'string' || name.trim().length === 0) {
37
+ console.error('❌ Error: Name must be a non-empty string');
38
+ process.exit(1);
39
+ }
40
+
41
+ if (!options || typeof options !== 'object') {
42
+ console.error('❌ Error: Options must be an object');
43
+ process.exit(1);
44
+ }
45
+
46
+ const { directory = 'src' } = options;
47
+
48
+ if (typeof directory !== 'string' || directory.trim().length === 0) {
49
+ console.error('❌ Error: Directory must be a non-empty string');
50
+ process.exit(1);
51
+ }
52
+
53
+ const typeMap = {
54
+ controller: 'controller',
55
+ service: 'service',
56
+ util: 'util',
57
+ error: 'error',
58
+ route: 'route',
59
+ middleware: 'middleware',
60
+ crud: 'crud'
61
+ };
62
+
63
+ const templateType = typeMap[type.toLowerCase()];
64
+
65
+ if (!templateType) {
66
+ const availableTypes = Object.keys(typeMap).join(', ');
67
+ console.error(`❌ Error: Unknown type "${type}". Available types: ${availableTypes}`);
68
+ process.exit(1);
69
+ }
70
+
71
+ const outputDir = path.resolve(process.cwd(), directory);
72
+
73
+
74
+ try {
75
+ if (fs.existsSync(outputDir)) {
76
+ const stats = fs.statSync(outputDir);
77
+ if (!stats.isDirectory()) {
78
+ console.error(`❌ Error: Output path exists but is not a directory: ${outputDir}`);
79
+ process.exit(1);
80
+ }
81
+ } else {
82
+
83
+ const parentDir = path.dirname(outputDir);
84
+ if (!fs.existsSync(parentDir)) {
85
+ console.error(`❌ Error: Parent directory does not exist: ${parentDir}`);
86
+ process.exit(1);
87
+ }
88
+ }
89
+ } catch (error) {
90
+ if (error.code === 'EACCES') {
91
+ console.error(`❌ Error: Permission denied accessing directory: ${outputDir}`);
92
+ } else {
93
+ console.error(`❌ Error: Failed to validate output directory: ${error.message}`);
94
+ }
95
+ process.exit(1);
96
+ }
97
+
98
+
99
+ const names = name.split(',')
100
+ .map(n => n.trim())
101
+ .filter(n => n.length > 0);
102
+
103
+ if (names.length === 0) {
104
+ console.error('❌ Error: No valid names provided after parsing');
105
+ process.exit(1);
106
+ }
107
+
108
+
109
+ const invalidCharRegex = /[<>:"|?*\x00-\x1f]/;
110
+ const invalidNames = names.filter(n => {
111
+ return invalidCharRegex.test(n) || n.includes('..');
112
+ });
113
+
114
+ if (invalidNames.length > 0) {
115
+ console.error(`❌ Error: Invalid characters in names: ${invalidNames.join(', ')}`);
116
+ process.exit(1);
117
+ }
118
+
119
+
120
+ if (templateType === 'crud') {
121
+ const crudQuestions = [
122
+ {
123
+ type: 'confirm',
124
+ name: 'generateCRUD',
125
+ message: `Do you want to generate complete CRUD (controller + service + routes) for: ${names.join(', ')}?`,
126
+ default: true
127
+ }
128
+ ];
129
+
130
+ try {
131
+ const crudAnswers = await promptInquirer(crudQuestions);
132
+
133
+ if (!crudAnswers.generateCRUD) {
134
+ console.log('\n❌ CRUD generation cancelled.');
135
+ console.log('💡 Tip: You can generate individual files using:');
136
+ console.log(' nexpgen generate controller <name>');
137
+ console.log(' nexpgen generate service <name>');
138
+ console.log(' nexpgen generate route <name>\n');
139
+ process.exit(0);
140
+ }
141
+ } catch (error) {
142
+ if (error.isTtyError) {
143
+ console.error('❌ Prompt couldn\'t be rendered in the current environment');
144
+ console.log('💡 Proceeding with CRUD generation...\n');
145
+ } else if (error.message && error.message.includes('Inquirer')) {
146
+ console.error(`❌ ${error.message}`);
147
+ console.log('💡 Proceeding with CRUD generation...\n');
148
+ } else {
149
+ console.error(`❌ Error: ${error.message}`);
150
+ process.exit(1);
151
+ }
152
+ }
153
+ }
154
+
155
+ let successCount = 0;
156
+ let failureCount = 0;
157
+
158
+
159
+ if (templateType === 'crud') {
160
+ for (const nameItem of names) {
161
+ try {
162
+ fileGenerator.generateCRUD(nameItem, outputDir);
163
+ successCount++;
164
+ } catch (error) {
165
+ failureCount++;
166
+ console.error(`❌ Failed to generate CRUD for ${nameItem}: ${error.message}`);
167
+ }
168
+ }
169
+ } else {
170
+
171
+ for (const nameItem of names) {
172
+ try {
173
+
174
+ if (templateType === 'middleware') {
175
+ fileGenerator.generateMiddleware(nameItem, outputDir);
176
+ } else {
177
+ fileGenerator.generateFromTemplate(templateType, nameItem, outputDir);
178
+ }
179
+ successCount++;
180
+ } catch (error) {
181
+ failureCount++;
182
+ console.error(`❌ Failed to generate ${nameItem}: ${error.message}`);
183
+
184
+ }
185
+ }
186
+ }
187
+
188
+ if (failureCount > 0) {
189
+ console.error(`\n⚠️ Generated ${successCount} file(s), failed ${failureCount} file(s)`);
190
+ if (failureCount === names.length) {
191
+ process.exit(1);
192
+ }
193
+ } else {
194
+ console.log(`\n✅ Successfully generated ${successCount} file(s)`);
195
+ }
196
+ } catch (error) {
197
+ console.error(`❌ Unexpected error: ${error.message}`);
198
+ if (error.stack && process.env.NODE_ENV === 'development') {
199
+ console.error('Stack trace:', error.stack);
200
+ }
201
+ process.exit(1);
202
+ }
203
+ }
204
+
205
+ module.exports = generate;
@@ -0,0 +1,191 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const architectureGenerator = require('../utils/architectureGenerator');
4
+
5
+ async function promptInquirer(questions) {
6
+ try {
7
+
8
+ const inquirer = require('inquirer');
9
+ if (typeof inquirer.createPromptModule === 'function') {
10
+ const prompt = inquirer.createPromptModule();
11
+ return await prompt(questions);
12
+ }
13
+
14
+ if (typeof inquirer.prompt === 'function') {
15
+ return await inquirer.prompt(questions);
16
+ }
17
+
18
+ throw new Error('Inquirer prompt method not available');
19
+ } catch (error) {
20
+
21
+ if (error.code === 'MODULE_NOT_FOUND') {
22
+ throw new Error('Inquirer package not found. Please install it: npm install inquirer');
23
+ }
24
+ throw error;
25
+ }
26
+ }
27
+
28
+ async function init() {
29
+ console.log('🚀 Initializing project...\n');
30
+
31
+ try {
32
+ const configPath = path.resolve(process.cwd(), '.nexpgenrc');
33
+ if (fs.existsSync(configPath)) {
34
+ const readline = require('readline');
35
+ const rl = readline.createInterface({
36
+ input: process.stdin,
37
+ output: process.stdout
38
+ });
39
+
40
+ return new Promise((resolve, reject) => {
41
+ rl.question('⚠️ .nexpgenrc already exists. Do you want to overwrite it? (y/N): ', async (answer) => {
42
+ rl.close();
43
+ if (answer.toLowerCase() !== 'y' && answer.toLowerCase() !== 'yes') {
44
+ console.log('Initialization cancelled.');
45
+ process.exit(0);
46
+ }
47
+ try {
48
+ await proceedWithInit();
49
+ resolve();
50
+ } catch (error) {
51
+ reject(error);
52
+ }
53
+ });
54
+ });
55
+ } else {
56
+ await proceedWithInit();
57
+ }
58
+ } catch (error) {
59
+ console.error(`❌ Error during initialization: ${error.message}`);
60
+ process.exit(1);
61
+ }
62
+ }
63
+
64
+ async function proceedWithInit() {
65
+ const questions = [
66
+ {
67
+ type: 'input',
68
+ name: 'packageName',
69
+ message: 'What is your package name?',
70
+ default: 'my-app',
71
+ validate: (input) => {
72
+ if (!input || typeof input !== 'string' || !input.trim()) {
73
+ return 'Package name cannot be empty';
74
+ }
75
+
76
+ const npmNameRegex = /^(@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/;
77
+ if (!npmNameRegex.test(input.trim())) {
78
+ return 'Invalid package name format. Must be a valid npm package name';
79
+ }
80
+
81
+ return true;
82
+ }
83
+ },
84
+ {
85
+ type: 'list',
86
+ name: 'architecture',
87
+ message: 'Which architecture do you want to use?',
88
+ choices: [
89
+ { name: 'MVC (Model-View-Controller)', value: 'mvc' },
90
+ { name: 'Microservices', value: 'microservices' },
91
+ { name: 'Layered Architecture', value: 'layered' },
92
+ { name: 'Clean Architecture', value: 'clean' },
93
+ { name: 'Simple Structure', value: 'simple' }
94
+ ],
95
+ default: 'mvc'
96
+ },
97
+ {
98
+ type: 'list',
99
+ name: 'functionStyle',
100
+ message: 'What function style do you prefer?',
101
+ choices: [
102
+ { name: 'Class-based', value: 'class' },
103
+ { name: 'Arrow functions with module.exports', value: 'arrow' }
104
+ ],
105
+ default: 'class'
106
+ },
107
+ {
108
+ type: 'checkbox',
109
+ name: 'packages',
110
+ message: 'Select additional packages you want to include:',
111
+ choices: [
112
+ { name: 'dotenv (Environment variables)', value: 'dotenv', checked: true },
113
+ { name: 'helmet (Security headers)', value: 'helmet' },
114
+ { name: 'Image Upload Helper (multer)', value: 'imageUpload' },
115
+ { name: 'Time Converter Helper', value: 'timeConverter' }
116
+ ],
117
+ validate: (input) => {
118
+ if (!Array.isArray(input)) {
119
+ return 'Packages must be an array';
120
+ }
121
+ return true;
122
+ }
123
+ }
124
+ ];
125
+
126
+ try {
127
+ const answers = await promptInquirer(questions);
128
+
129
+
130
+ if (!answers.packageName || !answers.architecture || !answers.functionStyle) {
131
+ throw new Error('Missing required answers');
132
+ }
133
+
134
+
135
+ if (!Array.isArray(answers.packages)) {
136
+ answers.packages = [];
137
+ }
138
+
139
+
140
+ const configPath = path.resolve(process.cwd(), '.nexpgenrc');
141
+
142
+ try {
143
+ const configContent = JSON.stringify(answers, null, 2);
144
+ fs.writeFileSync(configPath, configContent, 'utf-8');
145
+ console.log('\n✅ Configuration saved to .nexpgenrc\n');
146
+ } catch (error) {
147
+ if (error.code === 'EACCES') {
148
+ throw new Error(`Permission denied writing .nexpgenrc`);
149
+ }
150
+ throw new Error(`Failed to save configuration: ${error.message}`);
151
+ }
152
+
153
+
154
+ try {
155
+ architectureGenerator.generate(
156
+ answers.architecture,
157
+ answers.functionStyle,
158
+ answers.packageName,
159
+ answers.packages
160
+ );
161
+
162
+ console.log('\n✨ Project initialized successfully!');
163
+ } catch (error) {
164
+ console.error(`\n❌ Failed to generate project structure: ${error.message}`);
165
+
166
+ try {
167
+ if (fs.existsSync(configPath)) {
168
+ fs.unlinkSync(configPath);
169
+ }
170
+ } catch (cleanupError) {
171
+
172
+ }
173
+ throw error;
174
+ }
175
+ } catch (error) {
176
+ if (error.isTtyError) {
177
+ console.error('❌ Prompt couldn\'t be rendered in the current environment');
178
+ console.error('💡 This might be because:');
179
+ console.error(' 1. The terminal doesn\'t support interactive prompts');
180
+ console.error(' 2. Inquirer version compatibility issue');
181
+ console.error(' 3. Try running: npm install inquirer@latest');
182
+ } else if (error.message && error.message.includes('Inquirer')) {
183
+ console.error(`❌ ${error.message}`);
184
+ } else {
185
+ console.error(`❌ Error initializing project: ${error.message}`);
186
+ }
187
+ process.exit(1);
188
+ }
189
+ }
190
+
191
+ module.exports = init;
@@ -0,0 +1,127 @@
1
+ {{#if isClass}}
2
+ class {{className}}Controller {
3
+ constructor() {
4
+ this.name = '{{name}}';
5
+ }
6
+
7
+ async getAll(req, res, next) {
8
+ try {
9
+ // TODO: Implement get all logic
10
+ res.json({ message: 'Get all {{camelCaseName}}s', data: [] });
11
+ } catch (error) {
12
+ next(error);
13
+ }
14
+ }
15
+
16
+ async getById(req, res, next) {
17
+ try {
18
+ const { id } = req.params;
19
+ // TODO: Implement get by id logic
20
+ res.json({ message: \`Get {{camelCaseName}} by id: \${id}\` });
21
+ } catch (error) {
22
+ next(error);
23
+ }
24
+ }
25
+
26
+ async create(req, res, next) {
27
+ try {
28
+ const {{camelCaseName}}Data = req.body;
29
+ // TODO: Implement create logic
30
+ res.json({ message: 'Create {{camelCaseName}}', data: {{camelCaseName}}Data });
31
+ } catch (error) {
32
+ next(error);
33
+ }
34
+ }
35
+
36
+ async update(req, res, next) {
37
+ try {
38
+ const { id } = req.params;
39
+ const {{camelCaseName}}Data = req.body;
40
+ // TODO: Implement update logic
41
+ res.json({ message: \`Update {{camelCaseName}} \${id}\`, data: {{camelCaseName}}Data });
42
+ } catch (error) {
43
+ next(error);
44
+ }
45
+ }
46
+
47
+ async remove(req, res, next) {
48
+ try {
49
+ const { id } = req.params;
50
+ // TODO: Implement delete logic
51
+ res.json({ message: \`Delete {{camelCaseName}} \${id}\` });
52
+ } catch (error) {
53
+ next(error);
54
+ }
55
+ }
56
+ }
57
+
58
+ module.exports = {{className}}Controller;
59
+ {{else}}
60
+ const handle = async (req, res, next) => {
61
+ try {
62
+ // TODO: Implement controller logic
63
+ res.json({ message: '{{className}}Controller - handle method' });
64
+ } catch (error) {
65
+ next(error);
66
+ }
67
+ };
68
+
69
+ const getAll = async (req, res, next) => {
70
+ try {
71
+ // TODO: Implement get all logic
72
+ res.json({ message: 'Get all {{camelCaseName}}s', data: [] });
73
+ } catch (error) {
74
+ next(error);
75
+ }
76
+ };
77
+
78
+ const getById = async (req, res, next) => {
79
+ try {
80
+ const { id } = req.params;
81
+ // TODO: Implement get by id logic
82
+ res.json({ message: \`Get {{camelCaseName}} by id: \${id}\` });
83
+ } catch (error) {
84
+ next(error);
85
+ }
86
+ };
87
+
88
+ const create = async (req, res, next) => {
89
+ try {
90
+ const {{camelCaseName}}Data = req.body;
91
+ // TODO: Implement create logic
92
+ res.json({ message: 'Create {{camelCaseName}}', data: {{camelCaseName}}Data });
93
+ } catch (error) {
94
+ next(error);
95
+ }
96
+ };
97
+
98
+ const update = async (req, res, next) => {
99
+ try {
100
+ const { id } = req.params;
101
+ const {{camelCaseName}}Data = req.body;
102
+ // TODO: Implement update logic
103
+ res.json({ message: \`Update {{camelCaseName}} \${id}\`, data: {{camelCaseName}}Data });
104
+ } catch (error) {
105
+ next(error);
106
+ }
107
+ };
108
+
109
+ const remove = async (req, res, next) => {
110
+ try {
111
+ const { id } = req.params;
112
+ // TODO: Implement delete logic
113
+ res.json({ message: \`Delete {{camelCaseName}} \${id}\` });
114
+ } catch (error) {
115
+ next(error);
116
+ }
117
+ };
118
+
119
+ module.exports = {
120
+ handle,
121
+ getAll,
122
+ getById,
123
+ create,
124
+ update,
125
+ remove
126
+ };
127
+ {{/if}}
@@ -0,0 +1,24 @@
1
+ {{#if isClass}}
2
+ class {{className}}Error extends Error {
3
+ constructor(message, statusCode = 500) {
4
+ super(message);
5
+ this.name = '{{className}}Error';
6
+ this.statusCode = statusCode;
7
+ Error.captureStackTrace(this, this.constructor);
8
+ }
9
+ }
10
+
11
+ module.exports = {{className}}Error;
12
+ {{else}}
13
+ const create{{className}}Error = (message, statusCode = 500) => {
14
+ const error = new Error(message);
15
+ error.name = '{{className}}Error';
16
+ error.statusCode = statusCode;
17
+ Error.captureStackTrace(error, create{{className}}Error);
18
+ return error;
19
+ };
20
+
21
+ module.exports = {
22
+ create{{className}}Error
23
+ };
24
+ {{/if}}