frontend-hamroun 1.2.35 → 1.2.41
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/cli.js +402 -568
- package/package.json +2 -8
package/bin/cli.js
CHANGED
@@ -3,71 +3,28 @@
|
|
3
3
|
import { Command } from 'commander';
|
4
4
|
import inquirer from 'inquirer';
|
5
5
|
import chalk from 'chalk';
|
6
|
-
import gradient from 'gradient-string';
|
7
6
|
import { createSpinner } from 'nanospinner';
|
8
7
|
import path from 'path';
|
9
8
|
import fs from 'fs-extra';
|
10
9
|
import { fileURLToPath } from 'url';
|
11
10
|
import { exec } from 'child_process';
|
12
11
|
import { promisify } from 'util';
|
13
|
-
import boxen from 'boxen';
|
14
|
-
import terminalLink from 'terminal-link';
|
15
|
-
import updateNotifier from 'update-notifier';
|
16
|
-
import ora from 'ora';
|
17
|
-
import figlet from 'figlet';
|
18
12
|
|
19
13
|
// Convert to ESM-friendly __dirname equivalent
|
20
14
|
const __filename = fileURLToPath(import.meta.url);
|
21
15
|
const __dirname = path.dirname(__filename);
|
22
16
|
const execAsync = promisify(exec);
|
23
17
|
|
24
|
-
// Check for package updates
|
25
|
-
try {
|
26
|
-
const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf8'));
|
27
|
-
const notifier = updateNotifier({ pkg, updateCheckInterval: 1000 * 60 * 60 * 24 });
|
28
|
-
|
29
|
-
if (notifier.update) {
|
30
|
-
const updateMessage = boxen(
|
31
|
-
`Update available: ${chalk.dim(notifier.update.current)} → ${chalk.green(notifier.update.latest)}\n` +
|
32
|
-
`Run ${chalk.cyan('npm i -g frontend-hamroun')} to update`,
|
33
|
-
{
|
34
|
-
padding: 1,
|
35
|
-
margin: 1,
|
36
|
-
borderStyle: 'round',
|
37
|
-
borderColor: 'cyan'
|
38
|
-
}
|
39
|
-
);
|
40
|
-
console.log(updateMessage);
|
41
|
-
}
|
42
|
-
} catch (error) {
|
43
|
-
// Silently continue if update check fails
|
44
|
-
}
|
45
|
-
|
46
18
|
// CLI instance
|
47
19
|
const program = new Command();
|
48
20
|
|
49
|
-
// Create
|
50
|
-
const
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
console.log('\n' + gradient.pastel.multiline(titleText));
|
58
|
-
|
59
|
-
console.log(boxen(
|
60
|
-
`${chalk.bold('A lightweight full-stack JavaScript framework')}\n\n` +
|
61
|
-
`${chalk.dim('Version:')} ${chalk.cyan(JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf8')).version)}\n` +
|
62
|
-
`${chalk.dim('Documentation:')} ${terminalLink('frontendhamroun.io', 'https://github.com/hamroun/frontend-hamroun')}`,
|
63
|
-
{
|
64
|
-
padding: 1,
|
65
|
-
margin: { top: 1, bottom: 1 },
|
66
|
-
borderStyle: 'round',
|
67
|
-
borderColor: 'cyan'
|
68
|
-
}
|
69
|
-
));
|
70
|
-
};
|
21
|
+
// Create ASCII art banner
|
22
|
+
const banner = `
|
23
|
+
${chalk.blue('╔══════════════════════════════════════════════╗')}
|
24
|
+
${chalk.blue('║')} ${chalk.bold.cyan('Frontend Hamroun')} ${chalk.blue('║')}
|
25
|
+
${chalk.blue('║')} ${chalk.yellow('A lightweight full-stack JavaScript framework')} ${chalk.blue('║')}
|
26
|
+
${chalk.blue('╚══════════════════════════════════════════════╝')}
|
27
|
+
`;
|
71
28
|
|
72
29
|
// Version and description
|
73
30
|
program
|
@@ -78,320 +35,149 @@ program
|
|
78
35
|
// Helper for creating visually consistent sections
|
79
36
|
const createSection = (title) => {
|
80
37
|
console.log('\n' + chalk.bold.cyan(`◆ ${title}`));
|
81
|
-
console.log(chalk.cyan('─'.repeat(
|
38
|
+
console.log(chalk.cyan('─'.repeat(50)) + '\n');
|
82
39
|
};
|
83
40
|
|
84
|
-
// Helper for checking dependencies
|
41
|
+
// Helper for checking dependencies
|
85
42
|
async function checkDependencies() {
|
86
|
-
const spinner =
|
87
|
-
text: 'Checking environment...',
|
88
|
-
spinner: 'dots',
|
89
|
-
color: 'cyan'
|
90
|
-
}).start();
|
43
|
+
const spinner = createSpinner('Checking dependencies...').start();
|
91
44
|
|
92
45
|
try {
|
93
|
-
|
94
|
-
|
95
|
-
const nodeVersion = nodeVersionOutput.stdout.trim().replace('v', '');
|
96
|
-
const requiredNodeVersion = '14.0.0';
|
97
|
-
|
98
|
-
if (compareVersions(nodeVersion, requiredNodeVersion) < 0) {
|
99
|
-
spinner.fail(`Node.js ${requiredNodeVersion}+ required, but found ${nodeVersion}`);
|
100
|
-
console.log(chalk.yellow(`Please upgrade Node.js: ${terminalLink('https://nodejs.org', 'https://nodejs.org')}`));
|
101
|
-
return false;
|
102
|
-
}
|
103
|
-
|
104
|
-
// Check npm version
|
105
|
-
const npmVersionOutput = await execAsync('npm --version');
|
106
|
-
const npmVersion = npmVersionOutput.stdout.trim();
|
107
|
-
const requiredNpmVersion = '6.0.0';
|
108
|
-
|
109
|
-
if (compareVersions(npmVersion, requiredNpmVersion) < 0) {
|
110
|
-
spinner.fail(`npm ${requiredNpmVersion}+ required, but found ${npmVersion}`);
|
111
|
-
console.log(chalk.yellow(`Please upgrade npm: ${chalk.cyan('npm install -g npm')}`));
|
112
|
-
return false;
|
113
|
-
}
|
114
|
-
|
115
|
-
spinner.succeed(`Environment ready: Node ${chalk.green(nodeVersion)}, npm ${chalk.green(npmVersion)}`);
|
46
|
+
await execAsync('npm --version');
|
47
|
+
spinner.success({ text: 'Dependencies verified' });
|
116
48
|
return true;
|
117
49
|
} catch (error) {
|
118
|
-
spinner.
|
50
|
+
spinner.error({ text: 'Missing required dependencies' });
|
119
51
|
console.log(chalk.red('Error: Node.js and npm are required to use this tool.'));
|
120
52
|
return false;
|
121
53
|
}
|
122
54
|
}
|
123
55
|
|
124
|
-
//
|
125
|
-
function compareVersions(a, b) {
|
126
|
-
const aParts = a.split('.').map(Number);
|
127
|
-
const bParts = b.split('.').map(Number);
|
128
|
-
|
129
|
-
for (let i = 0; i < Math.max(aParts.length, bParts.length); i++) {
|
130
|
-
const aVal = aParts[i] || 0;
|
131
|
-
const bVal = bParts[i] || 0;
|
132
|
-
|
133
|
-
if (aVal > bVal) return 1;
|
134
|
-
if (aVal < bVal) return -1;
|
135
|
-
}
|
136
|
-
|
137
|
-
return 0;
|
138
|
-
}
|
139
|
-
|
140
|
-
// Choose template interactively with improved visualization
|
56
|
+
// Choose template interactively
|
141
57
|
async function chooseTemplate() {
|
142
58
|
const templatesPath = path.join(__dirname, '..', 'templates');
|
143
|
-
const templates = fs.readdirSync(templatesPath).filter(file =>
|
59
|
+
const templates = fs.readdirSync(templatesPath).filter(file =>
|
144
60
|
fs.statSync(path.join(templatesPath, file)).isDirectory()
|
145
61
|
);
|
146
62
|
|
147
|
-
//
|
148
|
-
const
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
templates.map(template => {
|
164
|
-
const icon = templateIcons[template] || '📦';
|
165
|
-
const shortDesc = {
|
166
|
-
'basic-app': 'Simple client-side application',
|
167
|
-
'ssr-template': 'Server-side rendering with hydration',
|
168
|
-
'fullstack-app': 'Complete fullstack application with API'
|
169
|
-
}[template] || 'Application template';
|
170
|
-
|
171
|
-
return `${icon} ${chalk.cyan(template)}\n ${chalk.dim(shortDesc)}`;
|
172
|
-
}).join('\n\n'),
|
173
|
-
{
|
174
|
-
padding: 1,
|
175
|
-
margin: 1,
|
176
|
-
borderStyle: 'round',
|
177
|
-
borderColor: 'cyan'
|
63
|
+
// Create template descriptions
|
64
|
+
const templateOptions = templates.map(template => {
|
65
|
+
let description = '';
|
66
|
+
|
67
|
+
switch (template) {
|
68
|
+
case 'basic-app':
|
69
|
+
description = 'Simple client-side application with minimal setup';
|
70
|
+
break;
|
71
|
+
case 'ssr-template':
|
72
|
+
description = 'Server-side rendering with hydration support';
|
73
|
+
break;
|
74
|
+
case 'fullstack-app':
|
75
|
+
description = 'Complete fullstack application with API routes';
|
76
|
+
break;
|
77
|
+
default:
|
78
|
+
description = 'Application template';
|
178
79
|
}
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
}));
|
80
|
+
|
81
|
+
return {
|
82
|
+
name: `${template} - ${chalk.dim(description)}`,
|
83
|
+
value: template
|
84
|
+
};
|
85
|
+
});
|
186
86
|
|
187
87
|
const answers = await inquirer.prompt([
|
188
88
|
{
|
189
89
|
type: 'list',
|
190
90
|
name: 'template',
|
191
|
-
message:
|
192
|
-
choices:
|
193
|
-
loop: false
|
194
|
-
pageSize: 10
|
195
|
-
},
|
196
|
-
{
|
197
|
-
type: 'confirm',
|
198
|
-
name: 'viewDetails',
|
199
|
-
message: 'Would you like to see template details before proceeding?',
|
200
|
-
default: false
|
91
|
+
message: 'Select a project template:',
|
92
|
+
choices: templateOptions,
|
93
|
+
loop: false
|
201
94
|
}
|
202
95
|
]);
|
203
96
|
|
204
|
-
if (answers.viewDetails) {
|
205
|
-
const detailedDescription = {
|
206
|
-
'basic-app': [
|
207
|
-
`${chalk.bold('Basic App Template')} ${templateIcons['basic-app']}`,
|
208
|
-
'',
|
209
|
-
`${chalk.dim('A lightweight client-side application template.')}`,
|
210
|
-
'',
|
211
|
-
`${chalk.yellow('Features:')}`,
|
212
|
-
'• No build step required for development',
|
213
|
-
'• Built-in state management with hooks',
|
214
|
-
'• Component-based architecture',
|
215
|
-
'• Tailwind CSS integration',
|
216
|
-
'',
|
217
|
-
`${chalk.yellow('Best for:')}`,
|
218
|
-
'• Simple web applications',
|
219
|
-
'• Learning the framework',
|
220
|
-
'• Quick prototyping',
|
221
|
-
],
|
222
|
-
'ssr-template': [
|
223
|
-
`${chalk.bold('SSR Template')} ${templateIcons['ssr-template']}`,
|
224
|
-
'',
|
225
|
-
`${chalk.dim('Server-side rendering with client hydration.')}`,
|
226
|
-
'',
|
227
|
-
`${chalk.yellow('Features:')}`,
|
228
|
-
'• React-like development experience',
|
229
|
-
'• SEO-friendly rendered HTML',
|
230
|
-
'• Fast initial page load',
|
231
|
-
'• Smooth client-side transitions',
|
232
|
-
'• Built-in dynamic meta tag generation',
|
233
|
-
'',
|
234
|
-
`${chalk.yellow('Best for:')}`,
|
235
|
-
'• Production websites needing SEO',
|
236
|
-
'• Content-focused applications',
|
237
|
-
'• Sites requiring social sharing previews'
|
238
|
-
],
|
239
|
-
'fullstack-app': [
|
240
|
-
`${chalk.bold('Fullstack App Template')} ${templateIcons['fullstack-app']}`,
|
241
|
-
'',
|
242
|
-
`${chalk.dim('Complete solution with frontend and backend.')}`,
|
243
|
-
'',
|
244
|
-
`${chalk.yellow('Features:')}`,
|
245
|
-
'• API routes with Express integration',
|
246
|
-
'• File-based routing system',
|
247
|
-
'• Authentication system with JWT',
|
248
|
-
'• Database connectors (MongoDB, MySQL, PostgreSQL)',
|
249
|
-
'• Server-side rendering with hydration',
|
250
|
-
'• WebSocket support',
|
251
|
-
'',
|
252
|
-
`${chalk.yellow('Best for:')}`,
|
253
|
-
'• Full production applications',
|
254
|
-
'• Apps needing authentication',
|
255
|
-
'• Projects requiring database integration'
|
256
|
-
]
|
257
|
-
}[answers.template] || ['No detailed information available for this template'];
|
258
|
-
|
259
|
-
console.log(boxen(detailedDescription.join('\n'), {
|
260
|
-
padding: 1,
|
261
|
-
margin: 1,
|
262
|
-
title: answers.template,
|
263
|
-
titleAlignment: 'center',
|
264
|
-
borderStyle: 'round',
|
265
|
-
borderColor: 'yellow'
|
266
|
-
}));
|
267
|
-
|
268
|
-
const proceed = await inquirer.prompt([{
|
269
|
-
type: 'confirm',
|
270
|
-
name: 'continue',
|
271
|
-
message: 'Continue with this template?',
|
272
|
-
default: true
|
273
|
-
}]);
|
274
|
-
|
275
|
-
if (!proceed.continue) {
|
276
|
-
return chooseTemplate();
|
277
|
-
}
|
278
|
-
}
|
279
|
-
|
280
97
|
return answers.template;
|
281
98
|
}
|
282
99
|
|
283
|
-
// Create a new project
|
100
|
+
// Create a new project
|
284
101
|
async function createProject(projectName, options) {
|
285
|
-
|
102
|
+
console.log(banner);
|
286
103
|
createSection('Project Setup');
|
287
|
-
|
104
|
+
|
105
|
+
// Validate project name
|
288
106
|
if (!projectName) {
|
289
107
|
const answers = await inquirer.prompt([
|
290
108
|
{
|
291
109
|
type: 'input',
|
292
110
|
name: 'projectName',
|
293
|
-
message:
|
111
|
+
message: 'What is your project name?',
|
294
112
|
default: 'my-frontend-app',
|
295
|
-
validate: input =>
|
296
|
-
/^[a-z0-9-_]+$/.test(input)
|
297
|
-
? true
|
298
|
-
: 'Project name can only contain lowercase letters, numbers, hyphens, and underscores'
|
113
|
+
validate: input => /^[a-z0-9-_]+$/.test(input) ? true : 'Project name can only contain lowercase letters, numbers, hyphens and underscores'
|
299
114
|
}
|
300
115
|
]);
|
301
116
|
projectName = answers.projectName;
|
302
117
|
}
|
303
|
-
|
304
|
-
if
|
305
|
-
|
306
|
-
|
307
|
-
|
118
|
+
|
119
|
+
// Check if dependencies are installed
|
120
|
+
if (!await checkDependencies()) {
|
121
|
+
return;
|
122
|
+
}
|
123
|
+
|
124
|
+
// Choose template if not specified
|
125
|
+
let template = options.template;
|
126
|
+
if (!template) {
|
127
|
+
template = await chooseTemplate();
|
128
|
+
}
|
129
|
+
|
130
|
+
// Create project directory
|
308
131
|
const targetDir = path.resolve(projectName);
|
309
132
|
const templateDir = path.join(__dirname, '..', 'templates', template);
|
310
|
-
|
133
|
+
|
311
134
|
if (fs.existsSync(targetDir)) {
|
312
135
|
const answers = await inquirer.prompt([
|
313
136
|
{
|
314
137
|
type: 'confirm',
|
315
138
|
name: 'overwrite',
|
316
|
-
message:
|
139
|
+
message: `Directory ${projectName} already exists. Continue and overwrite existing files?`,
|
317
140
|
default: false
|
318
141
|
}
|
319
142
|
]);
|
143
|
+
|
320
144
|
if (!answers.overwrite) {
|
321
|
-
console.log(chalk.
|
145
|
+
console.log(chalk.yellow('✖ Operation cancelled'));
|
322
146
|
return;
|
323
147
|
}
|
324
148
|
}
|
325
|
-
|
326
|
-
// Multi-step execution with progress reporting
|
327
|
-
console.log(chalk.dim('\nCreating your new project...'));
|
328
149
|
|
329
|
-
//
|
330
|
-
const
|
150
|
+
// Copy template
|
151
|
+
const spinner = createSpinner('Creating project...').start();
|
331
152
|
try {
|
332
153
|
await fs.ensureDir(targetDir);
|
333
|
-
step1.succeed();
|
334
|
-
} catch (error) {
|
335
|
-
step1.fail();
|
336
|
-
console.error(chalk.red(`Error creating directory: ${error.message}`));
|
337
|
-
return;
|
338
|
-
}
|
339
|
-
|
340
|
-
// Step 2: Copy template files
|
341
|
-
const step2 = ora({text: 'Copying template files', color: 'cyan'}).start();
|
342
|
-
try {
|
343
154
|
await fs.copy(templateDir, targetDir, { overwrite: true });
|
344
|
-
|
345
|
-
|
346
|
-
step2.fail();
|
347
|
-
console.error(chalk.red(`Error copying files: ${error.message}`));
|
348
|
-
return;
|
349
|
-
}
|
350
|
-
|
351
|
-
// Step 3: Update package.json
|
352
|
-
const step3 = ora({text: 'Configuring package.json', color: 'cyan'}).start();
|
353
|
-
try {
|
155
|
+
|
156
|
+
// Create package.json with project name
|
354
157
|
const pkgJsonPath = path.join(targetDir, 'package.json');
|
355
158
|
if (fs.existsSync(pkgJsonPath)) {
|
356
159
|
const pkgJson = await fs.readJson(pkgJsonPath);
|
357
160
|
pkgJson.name = projectName;
|
358
161
|
await fs.writeJson(pkgJsonPath, pkgJson, { spaces: 2 });
|
359
|
-
step3.succeed();
|
360
|
-
} else {
|
361
|
-
step3.warn('No package.json found in template');
|
362
162
|
}
|
163
|
+
|
164
|
+
spinner.success({ text: `Project created successfully at ${chalk.green(targetDir)}` });
|
165
|
+
|
166
|
+
// Show next steps
|
167
|
+
createSection('Next Steps');
|
168
|
+
console.log(`${chalk.bold.green('✓')} Run the following commands to get started:\n`);
|
169
|
+
console.log(` ${chalk.cyan('cd')} ${projectName}`);
|
170
|
+
console.log(` ${chalk.cyan('npm install')}`);
|
171
|
+
console.log(` ${chalk.cyan('npm run dev')}\n`);
|
363
172
|
} catch (error) {
|
364
|
-
|
365
|
-
console.error(chalk.red(
|
366
|
-
return;
|
173
|
+
spinner.error({ text: 'Failed to create project' });
|
174
|
+
console.error(chalk.red('Error: ') + error.message);
|
367
175
|
}
|
368
|
-
|
369
|
-
// Final success message with helpful instructions
|
370
|
-
console.log('\n' + boxen(
|
371
|
-
`${chalk.bold.green('Project created successfully!')}\n\n` +
|
372
|
-
`${chalk.bold('Project:')} ${chalk.cyan(projectName)}\n` +
|
373
|
-
`${chalk.bold('Template:')} ${chalk.cyan(template)}\n` +
|
374
|
-
`${chalk.bold('Location:')} ${chalk.cyan(targetDir)}\n\n` +
|
375
|
-
`${chalk.bold.yellow('Next steps:')}\n\n` +
|
376
|
-
` ${chalk.dim('1.')} ${chalk.cyan(`cd ${projectName}`)}\n` +
|
377
|
-
` ${chalk.dim('2.')} ${chalk.cyan('npm install')}\n` +
|
378
|
-
` ${chalk.dim('3.')} ${chalk.cyan('npm run dev')}\n\n` +
|
379
|
-
`${chalk.dim('For help and documentation:')} ${terminalLink('frontendhamroun.io', 'https://github.com/hamroun/frontend-hamroun')}`,
|
380
|
-
{
|
381
|
-
padding: 1,
|
382
|
-
margin: 1,
|
383
|
-
borderStyle: 'round',
|
384
|
-
borderColor: 'green'
|
385
|
-
}
|
386
|
-
));
|
387
|
-
|
388
|
-
// Suggest next command
|
389
|
-
console.log(`\n${chalk.cyan('Tip:')} Run ${chalk.bold.green(`cd ${projectName} && npm install`)} to get started right away.\n`);
|
390
176
|
}
|
391
177
|
|
392
|
-
// Add component
|
178
|
+
// Add component to existing project
|
393
179
|
async function addComponent(componentName, options) {
|
394
|
-
|
180
|
+
console.log(banner);
|
395
181
|
createSection('Create Component');
|
396
182
|
|
397
183
|
// Validate component name
|
@@ -400,7 +186,7 @@ async function addComponent(componentName, options) {
|
|
400
186
|
{
|
401
187
|
type: 'input',
|
402
188
|
name: 'componentName',
|
403
|
-
message:
|
189
|
+
message: 'What is your component name?',
|
404
190
|
validate: input => /^[A-Z][A-Za-z0-9]*$/.test(input)
|
405
191
|
? true
|
406
192
|
: 'Component name must start with uppercase letter and only contain alphanumeric characters'
|
@@ -417,309 +203,365 @@ async function addComponent(componentName, options) {
|
|
417
203
|
{
|
418
204
|
type: 'list',
|
419
205
|
name: 'extension',
|
420
|
-
message:
|
206
|
+
message: 'Select file type:',
|
421
207
|
choices: [
|
422
|
-
{
|
423
|
-
|
424
|
-
value: '.tsx',
|
425
|
-
description: 'TypeScript with JSX support'
|
426
|
-
},
|
427
|
-
{
|
428
|
-
name: 'JavaScript (.jsx)',
|
429
|
-
value: '.jsx',
|
430
|
-
description: 'JavaScript with JSX support'
|
431
|
-
}
|
208
|
+
{ name: 'TypeScript (.tsx)', value: '.tsx' },
|
209
|
+
{ name: 'JavaScript (.jsx)', value: '.jsx' }
|
432
210
|
]
|
433
211
|
}
|
434
212
|
]);
|
435
213
|
extension = answers.extension;
|
436
214
|
}
|
437
215
|
|
438
|
-
// Determine component directory
|
439
|
-
let componentPath = options.path;
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
{
|
449
|
-
type: 'list',
|
450
|
-
name: 'path',
|
451
|
-
message: chalk.green('Where do you want to create this component?'),
|
452
|
-
choices: [
|
453
|
-
...existingPaths.map(p => ({ name: `${p} ${chalk.dim('(detected)')}`, value: p })),
|
454
|
-
{ name: 'Custom location...', value: 'custom' }
|
455
|
-
]
|
456
|
-
}
|
457
|
-
]);
|
458
|
-
|
459
|
-
if (answers.path === 'custom') {
|
460
|
-
const customPath = await inquirer.prompt([
|
461
|
-
{
|
462
|
-
type: 'input',
|
463
|
-
name: 'customPath',
|
464
|
-
message: chalk.green('Enter the component path:'),
|
465
|
-
default: 'src/components',
|
466
|
-
validate: input => /^[a-zA-Z0-9-_/\\]+$/.test(input)
|
467
|
-
? true
|
468
|
-
: 'Path can only contain letters, numbers, slashes, hyphens and underscores'
|
469
|
-
}
|
470
|
-
]);
|
471
|
-
componentPath = customPath.customPath;
|
472
|
-
} else {
|
473
|
-
componentPath = answers.path;
|
216
|
+
// Determine component directory
|
217
|
+
let componentPath = options.path || 'src/components';
|
218
|
+
if (!options.path) {
|
219
|
+
const answers = await inquirer.prompt([
|
220
|
+
{
|
221
|
+
type: 'input',
|
222
|
+
name: 'path',
|
223
|
+
message: 'Where do you want to create this component?',
|
224
|
+
default: 'src/components',
|
225
|
+
validate: input => /^[a-zA-Z0-9-_/\\]+$/.test(input) ? true : 'Path can only contain letters, numbers, slashes, hyphens and underscores'
|
474
226
|
}
|
475
|
-
|
476
|
-
|
477
|
-
{
|
478
|
-
type: 'input',
|
479
|
-
name: 'path',
|
480
|
-
message: chalk.green('Where do you want to create this component?'),
|
481
|
-
default: 'src/components',
|
482
|
-
validate: input => /^[a-zA-Z0-9-_/\\]+$/.test(input)
|
483
|
-
? true
|
484
|
-
: 'Path can only contain letters, numbers, slashes, hyphens and underscores'
|
485
|
-
}
|
486
|
-
]);
|
487
|
-
componentPath = answers.path;
|
488
|
-
}
|
227
|
+
]);
|
228
|
+
componentPath = answers.path;
|
489
229
|
}
|
490
230
|
|
491
|
-
//
|
492
|
-
const features = await inquirer.prompt([
|
493
|
-
{
|
494
|
-
type: 'checkbox',
|
495
|
-
name: 'features',
|
496
|
-
message: chalk.green('Select component features:'),
|
497
|
-
choices: [
|
498
|
-
{ name: 'useState Hook', value: 'useState', checked: true },
|
499
|
-
{ name: 'useEffect Hook', value: 'useEffect' },
|
500
|
-
{ name: 'CSS Module', value: 'cssModule' },
|
501
|
-
{ name: 'PropTypes', value: 'propTypes' },
|
502
|
-
{ name: 'Default Props', value: 'defaultProps' }
|
503
|
-
]
|
504
|
-
}
|
505
|
-
]);
|
506
|
-
|
507
|
-
// Create component content based on type and selected features
|
231
|
+
// Create component content based on type
|
508
232
|
const fullPath = path.join(process.cwd(), componentPath, `${componentName}${extension}`);
|
509
233
|
const dirPath = path.dirname(fullPath);
|
510
234
|
|
511
|
-
//
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
235
|
+
// Sample template
|
236
|
+
const componentTemplate = extension === '.tsx'
|
237
|
+
? `import { jsx } from 'frontend-hamroun';
|
238
|
+
|
239
|
+
interface ${componentName}Props {
|
240
|
+
title?: string;
|
241
|
+
children?: any;
|
242
|
+
}
|
243
|
+
|
244
|
+
export default function ${componentName}({ title, children }: ${componentName}Props) {
|
245
|
+
return (
|
246
|
+
<div className="component">
|
247
|
+
{title && <h2>{title}</h2>}
|
248
|
+
<div className="content">
|
249
|
+
{children}
|
250
|
+
</div>
|
251
|
+
</div>
|
252
|
+
);
|
253
|
+
}
|
254
|
+
`
|
255
|
+
: `import { jsx } from 'frontend-hamroun';
|
256
|
+
|
257
|
+
export default function ${componentName}({ title, children }) {
|
258
|
+
return (
|
259
|
+
<div className="component">
|
260
|
+
{title && <h2>{title}</h2>}
|
261
|
+
<div className="content">
|
262
|
+
{children}
|
263
|
+
</div>
|
264
|
+
</div>
|
265
|
+
);
|
266
|
+
}
|
267
|
+
`;
|
268
|
+
|
269
|
+
// Create the file
|
270
|
+
const spinner = createSpinner(`Creating ${componentName} component...`).start();
|
271
|
+
try {
|
272
|
+
await fs.ensureDir(dirPath);
|
273
|
+
await fs.writeFile(fullPath, componentTemplate);
|
274
|
+
spinner.success({ text: `Component created at ${chalk.green(fullPath)}` });
|
275
|
+
} catch (error) {
|
276
|
+
spinner.error({ text: 'Failed to create component' });
|
277
|
+
console.error(chalk.red('Error: ') + error.message);
|
532
278
|
}
|
279
|
+
}
|
280
|
+
|
281
|
+
// Create a page component
|
282
|
+
async function addPage(pageName, options) {
|
283
|
+
console.log(banner);
|
284
|
+
createSection('Create Page');
|
533
285
|
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
286
|
+
// Validate page name
|
287
|
+
if (!pageName) {
|
288
|
+
const answers = await inquirer.prompt([
|
289
|
+
{
|
290
|
+
type: 'input',
|
291
|
+
name: 'pageName',
|
292
|
+
message: 'What is your page name?',
|
293
|
+
validate: input => /^[a-zA-Z0-9-_]+$/.test(input)
|
294
|
+
? true
|
295
|
+
: 'Page name can only contain alphanumeric characters, hyphens and underscores'
|
296
|
+
}
|
297
|
+
]);
|
298
|
+
pageName = answers.pageName;
|
545
299
|
}
|
546
300
|
|
547
|
-
|
548
|
-
|
549
|
-
|
301
|
+
// Format page name to be PascalCase for the component name
|
302
|
+
const pageComponentName = pageName
|
303
|
+
.split('-')
|
304
|
+
.map(part => part.charAt(0).toUpperCase() + part.slice(1))
|
305
|
+
.join('') + 'Page';
|
550
306
|
|
551
|
-
|
552
|
-
|
553
|
-
exports.push(`
|
554
|
-
${componentName}.propTypes = {
|
555
|
-
title: PropTypes.string,
|
556
|
-
children: PropTypes.node
|
557
|
-
};`);
|
558
|
-
}
|
307
|
+
// Determine file extension preference
|
308
|
+
let extension = options.typescript ? '.tsx' : options.jsx ? '.jsx' : null;
|
559
309
|
|
560
|
-
if (
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
|
310
|
+
if (!extension) {
|
311
|
+
const answers = await inquirer.prompt([
|
312
|
+
{
|
313
|
+
type: 'list',
|
314
|
+
name: 'extension',
|
315
|
+
message: 'Select file type:',
|
316
|
+
choices: [
|
317
|
+
{ name: 'TypeScript (.tsx)', value: '.tsx' },
|
318
|
+
{ name: 'JavaScript (.jsx)', value: '.jsx' }
|
319
|
+
]
|
320
|
+
}
|
321
|
+
]);
|
322
|
+
extension = answers.extension;
|
565
323
|
}
|
566
324
|
|
567
|
-
//
|
568
|
-
|
569
|
-
propsInterface.push(`interface ${componentName}Props {
|
570
|
-
title?: string;
|
571
|
-
children?: React.ReactNode;
|
572
|
-
}`);
|
573
|
-
props.push(`{ title, children }: ${componentName}Props`);
|
574
|
-
} else {
|
575
|
-
props.push(`{ title, children }`);
|
576
|
-
}
|
325
|
+
// Create page file path
|
326
|
+
const pagePath = options.path || 'src/pages';
|
577
327
|
|
578
|
-
//
|
579
|
-
const
|
580
|
-
const
|
581
|
-
const
|
582
|
-
|
583
|
-
renders.push(` return (
|
584
|
-
<div className="${className}">
|
585
|
-
{title && <h2 className="${titleClass}">{title}</h2>}
|
586
|
-
${features.features.includes('useState') ? `<p>State value: {state}</p>
|
587
|
-
<button onClick={() => setState('updated state')}>Update State</button>` : ''}
|
588
|
-
<div className="${contentClass}">
|
589
|
-
{children}
|
590
|
-
</div>
|
591
|
-
</div>
|
592
|
-
);`);
|
328
|
+
// For index page, use the directory itself
|
329
|
+
const fileName = pageName === 'index' ? 'index' : pageName;
|
330
|
+
const fullPath = path.join(process.cwd(), pagePath, `${fileName}${extension}`);
|
331
|
+
const dirPath = path.dirname(fullPath);
|
593
332
|
|
594
|
-
//
|
595
|
-
const
|
333
|
+
// Page template
|
334
|
+
const pageTemplate = extension === '.tsx'
|
335
|
+
? `import { jsx } from 'frontend-hamroun';
|
336
|
+
import Layout from '../components/Layout';
|
596
337
|
|
597
|
-
${
|
338
|
+
interface ${pageComponentName}Props {
|
339
|
+
initialState?: any;
|
340
|
+
}
|
598
341
|
|
599
|
-
|
600
|
-
|
601
|
-
${
|
602
|
-
|
603
|
-
|
342
|
+
const ${pageComponentName}: React.FC<${pageComponentName}Props> = ({ initialState }) => {
|
343
|
+
return (
|
344
|
+
<Layout title="${pageName.split('-').map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(' ')}">
|
345
|
+
<div className="page">
|
346
|
+
<h1>${pageName.split('-').map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(' ')}</h1>
|
347
|
+
<p>Welcome to this page!</p>
|
348
|
+
</div>
|
349
|
+
</Layout>
|
350
|
+
);
|
351
|
+
};
|
604
352
|
|
605
|
-
|
606
|
-
|
607
|
-
|
608
|
-
|
609
|
-
|
610
|
-
|
611
|
-
|
612
|
-
}
|
353
|
+
// Optional: Add data fetching for SSR support
|
354
|
+
${pageComponentName}.getInitialData = async () => {
|
355
|
+
// You can fetch data for server-side rendering here
|
356
|
+
return {
|
357
|
+
// Your data here
|
358
|
+
};
|
359
|
+
};
|
613
360
|
|
614
|
-
|
615
|
-
|
616
|
-
|
617
|
-
|
618
|
-
|
361
|
+
export default ${pageComponentName};
|
362
|
+
`
|
363
|
+
: `import { jsx } from 'frontend-hamroun';
|
364
|
+
import Layout from '../components/Layout';
|
365
|
+
|
366
|
+
const ${pageComponentName} = ({ initialState }) => {
|
367
|
+
return (
|
368
|
+
<Layout title="${pageName.split('-').map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(' ')}">
|
369
|
+
<div className="page">
|
370
|
+
<h1>${pageName.split('-').map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(' ')}</h1>
|
371
|
+
<p>Welcome to this page!</p>
|
372
|
+
</div>
|
373
|
+
</Layout>
|
374
|
+
);
|
375
|
+
};
|
619
376
|
|
620
|
-
|
621
|
-
|
622
|
-
|
623
|
-
|
377
|
+
// Optional: Add data fetching for SSR support
|
378
|
+
${pageComponentName}.getInitialData = async () => {
|
379
|
+
// You can fetch data for server-side rendering here
|
380
|
+
return {
|
381
|
+
// Your data here
|
382
|
+
};
|
383
|
+
};
|
384
|
+
|
385
|
+
export default ${pageComponentName};
|
624
386
|
`;
|
625
387
|
|
626
|
-
// Create the
|
627
|
-
const spinner =
|
628
|
-
text: `Creating ${componentName} component...`,
|
629
|
-
color: 'cyan'
|
630
|
-
}).start();
|
631
|
-
|
388
|
+
// Create the file
|
389
|
+
const spinner = createSpinner(`Creating ${pageName} page...`).start();
|
632
390
|
try {
|
633
|
-
// Create component file
|
634
391
|
await fs.ensureDir(dirPath);
|
635
|
-
await fs.writeFile(fullPath,
|
636
|
-
|
637
|
-
// Create CSS module if selected
|
638
|
-
if (cssPath) {
|
639
|
-
await fs.writeFile(cssPath, cssTemplate);
|
640
|
-
}
|
641
|
-
|
642
|
-
spinner.succeed(`Component created at ${chalk.green(fullPath)}`);
|
643
|
-
|
644
|
-
// Show component usage example
|
645
|
-
console.log(boxen(
|
646
|
-
`${chalk.bold.green('Component created successfully!')}\n\n` +
|
647
|
-
`${chalk.bold('Import and use your component:')}\n\n` +
|
648
|
-
chalk.cyan(`import ${componentName} from './${path.relative(process.cwd(), fullPath).replace(/\\/g, '/').replace(/\.(jsx|tsx)$/, '')}';\n\n`) +
|
649
|
-
chalk.cyan(`<${componentName} title="My Title">
|
650
|
-
<p>Child content goes here</p>
|
651
|
-
</${componentName}>`),
|
652
|
-
{
|
653
|
-
padding: 1,
|
654
|
-
margin: 1,
|
655
|
-
borderStyle: 'round',
|
656
|
-
borderColor: 'green'
|
657
|
-
}
|
658
|
-
));
|
392
|
+
await fs.writeFile(fullPath, pageTemplate);
|
393
|
+
spinner.success({ text: `Page created at ${chalk.green(fullPath)}` });
|
659
394
|
} catch (error) {
|
660
|
-
spinner.
|
661
|
-
console.error(chalk.red(
|
395
|
+
spinner.error({ text: 'Failed to create page' });
|
396
|
+
console.error(chalk.red('Error: ') + error.message);
|
662
397
|
}
|
663
398
|
}
|
664
399
|
|
665
|
-
//
|
666
|
-
function
|
667
|
-
|
400
|
+
// Add API route
|
401
|
+
async function addApiRoute(routeName, options) {
|
402
|
+
console.log(banner);
|
403
|
+
createSection('Create API Route');
|
668
404
|
|
669
|
-
|
670
|
-
|
671
|
-
|
672
|
-
|
405
|
+
// Validate route name
|
406
|
+
if (!routeName) {
|
407
|
+
const answers = await inquirer.prompt([
|
408
|
+
{
|
409
|
+
type: 'input',
|
410
|
+
name: 'routeName',
|
411
|
+
message: 'What is your API route name?',
|
412
|
+
validate: input => /^[a-zA-Z0-9-_/[\]]+$/.test(input)
|
413
|
+
? true
|
414
|
+
: 'Route name can only contain alphanumeric characters, hyphens, underscores, slashes, and square brackets for dynamic parameters ([id])'
|
415
|
+
}
|
416
|
+
]);
|
417
|
+
routeName = answers.routeName;
|
418
|
+
}
|
673
419
|
|
674
|
-
|
675
|
-
|
676
|
-
help += ` ${chalk.yellow(`frontend-hamroun ${commandName} NavBar --typescript`)}\n`;
|
420
|
+
// Determine file extension preference
|
421
|
+
let extension = options.typescript ? '.ts' : '.js';
|
677
422
|
|
678
|
-
|
679
|
-
|
680
|
-
|
681
|
-
|
682
|
-
|
683
|
-
|
684
|
-
|
685
|
-
|
686
|
-
|
687
|
-
|
688
|
-
|
689
|
-
|
690
|
-
|
691
|
-
|
692
|
-
|
693
|
-
|
694
|
-
|
695
|
-
|
696
|
-
|
697
|
-
|
698
|
-
|
699
|
-
|
700
|
-
|
423
|
+
if (!options.typescript && !options.javascript) {
|
424
|
+
const answers = await inquirer.prompt([
|
425
|
+
{
|
426
|
+
type: 'list',
|
427
|
+
name: 'extension',
|
428
|
+
message: 'Select file type:',
|
429
|
+
choices: [
|
430
|
+
{ name: 'TypeScript (.ts)', value: '.ts' },
|
431
|
+
{ name: 'JavaScript (.js)', value: '.js' }
|
432
|
+
]
|
433
|
+
}
|
434
|
+
]);
|
435
|
+
extension = answers.extension;
|
436
|
+
}
|
437
|
+
|
438
|
+
// Choose HTTP methods to implement
|
439
|
+
let methods = options.methods ? options.methods.split(',') : null;
|
440
|
+
|
441
|
+
if (!methods) {
|
442
|
+
const answers = await inquirer.prompt([
|
443
|
+
{
|
444
|
+
type: 'checkbox',
|
445
|
+
name: 'methods',
|
446
|
+
message: 'Select HTTP methods to implement:',
|
447
|
+
choices: [
|
448
|
+
{ name: 'GET', value: 'get', checked: true },
|
449
|
+
{ name: 'POST', value: 'post' },
|
450
|
+
{ name: 'PUT', value: 'put' },
|
451
|
+
{ name: 'DELETE', value: 'delete' },
|
452
|
+
{ name: 'PATCH', value: 'patch' }
|
453
|
+
],
|
454
|
+
validate: input => input.length > 0 ? true : 'Please select at least one method'
|
455
|
+
}
|
456
|
+
]);
|
457
|
+
methods = answers.methods;
|
458
|
+
}
|
459
|
+
|
460
|
+
// Create API route path
|
461
|
+
const routePath = options.path || 'api';
|
462
|
+
|
463
|
+
// Determine filename from route name
|
464
|
+
// If last segment includes parameters, handle specially
|
465
|
+
const segments = routeName.split('/').filter(Boolean);
|
466
|
+
let directory = '';
|
467
|
+
let filename = 'index';
|
468
|
+
|
469
|
+
if (segments.length > 0) {
|
470
|
+
if (segments.length === 1) {
|
471
|
+
filename = segments[0];
|
472
|
+
} else {
|
473
|
+
directory = segments.slice(0, -1).join('/');
|
474
|
+
filename = segments[segments.length - 1];
|
701
475
|
}
|
702
|
-
|
476
|
+
}
|
477
|
+
|
478
|
+
const fullPath = path.join(process.cwd(), routePath, directory, `${filename}${extension}`);
|
479
|
+
const dirPath = path.dirname(fullPath);
|
703
480
|
|
704
|
-
|
705
|
-
|
706
|
-
|
707
|
-
|
481
|
+
// API route template
|
482
|
+
let apiTemplate = extension === '.ts'
|
483
|
+
? `import { Request, Response } from 'express';\n\n`
|
484
|
+
: '';
|
485
|
+
|
486
|
+
// Add method handlers
|
487
|
+
methods.forEach(method => {
|
488
|
+
if (method === 'delete' && extension === '.js') {
|
489
|
+
// In JS, "delete" is a reserved keyword, so we name it "delete_"
|
490
|
+
apiTemplate += `export const delete_ = (req, res) => {
|
491
|
+
res.json({
|
492
|
+
message: 'DELETE ${routeName} endpoint',
|
493
|
+
timestamp: new Date().toISOString()
|
494
|
+
});
|
495
|
+
};\n\n`;
|
496
|
+
} else {
|
497
|
+
apiTemplate += extension === '.ts'
|
498
|
+
? `export const ${method} = (req: Request, res: Response) => {
|
499
|
+
res.json({
|
500
|
+
message: '${method.toUpperCase()} ${routeName} endpoint',
|
501
|
+
timestamp: new Date().toISOString()
|
502
|
+
});
|
503
|
+
};\n\n`
|
504
|
+
: `export const ${method} = (req, res) => {
|
505
|
+
res.json({
|
506
|
+
message: '${method.toUpperCase()} ${routeName} endpoint',
|
507
|
+
timestamp: new Date().toISOString()
|
508
|
+
});
|
509
|
+
};\n\n`;
|
510
|
+
}
|
708
511
|
});
|
709
512
|
|
710
|
-
|
711
|
-
|
712
|
-
|
713
|
-
|
714
|
-
|
715
|
-
|
716
|
-
|
717
|
-
|
513
|
+
// Create the file
|
514
|
+
const spinner = createSpinner(`Creating ${routeName} API route...`).start();
|
515
|
+
try {
|
516
|
+
await fs.ensureDir(dirPath);
|
517
|
+
await fs.writeFile(fullPath, apiTemplate);
|
518
|
+
spinner.success({ text: `API route created at ${chalk.green(fullPath)}` });
|
519
|
+
|
520
|
+
createSection('API Route Information');
|
521
|
+
console.log(`${chalk.bold.green('✓')} Your API route is accessible at:`);
|
522
|
+
console.log(` ${chalk.cyan(`/api/${routeName}`)}\n`);
|
523
|
+
|
524
|
+
// For dynamic routes, provide example
|
525
|
+
if (routeName.includes('[') && routeName.includes(']')) {
|
526
|
+
const exampleRoute = routeName.replace(/\[([^\]]+)\]/g, 'value');
|
527
|
+
console.log(`${chalk.bold.yellow('ℹ')} For dynamic parameters, access using:`);
|
528
|
+
console.log(` ${chalk.cyan(`/api/${exampleRoute}`)}\n`);
|
529
|
+
console.log(`${chalk.bold.yellow('ℹ')} Access parameters in your handler with:`);
|
530
|
+
console.log(` ${chalk.cyan('req.params.paramName')}\n`);
|
718
531
|
}
|
719
|
-
)
|
532
|
+
} catch (error) {
|
533
|
+
spinner.error({ text: 'Failed to create API route' });
|
534
|
+
console.error(chalk.red('Error: ') + error.message);
|
535
|
+
}
|
720
536
|
}
|
721
537
|
|
722
|
-
//
|
538
|
+
// Display dev tools and tips
|
539
|
+
function showDevTools() {
|
540
|
+
console.log(banner);
|
541
|
+
createSection('Development Tools');
|
542
|
+
|
543
|
+
console.log(chalk.bold('Available Commands:') + '\n');
|
544
|
+
console.log(`${chalk.green('•')} ${chalk.bold('npm run dev')} - Start development server`);
|
545
|
+
console.log(`${chalk.green('•')} ${chalk.bold('npm run build')} - Create production build`);
|
546
|
+
console.log(`${chalk.green('•')} ${chalk.bold('npm start')} - Run production server\n`);
|
547
|
+
|
548
|
+
console.log(chalk.bold('Useful Tips:') + '\n');
|
549
|
+
console.log(`${chalk.blue('1.')} Use ${chalk.cyan('frontend-hamroun create')} to scaffold a new project`);
|
550
|
+
console.log(`${chalk.blue('2.')} Add components with ${chalk.cyan('frontend-hamroun add:component')}`);
|
551
|
+
console.log(`${chalk.blue('3.')} Create pages with ${chalk.cyan('frontend-hamroun add:page')}`);
|
552
|
+
console.log(`${chalk.blue('4.')} Add API routes with ${chalk.cyan('frontend-hamroun add:api')}\n`);
|
553
|
+
|
554
|
+
console.log(chalk.bold('Project Structure:') + '\n');
|
555
|
+
console.log(`${chalk.yellow('src/')}`);
|
556
|
+
console.log(` ${chalk.yellow('├── components/')} - Reusable UI components`);
|
557
|
+
console.log(` ${chalk.yellow('├── pages/')} - File-based route components`);
|
558
|
+
console.log(` ${chalk.yellow('├── styles/')} - CSS and styling files`);
|
559
|
+
console.log(` ${chalk.yellow('└── utils/')} - Helper functions and utilities`);
|
560
|
+
console.log(`${chalk.yellow('api/')} - API route handlers`);
|
561
|
+
console.log(`${chalk.yellow('public/')} - Static assets\n`);
|
562
|
+
}
|
563
|
+
|
564
|
+
// Register commands
|
723
565
|
program
|
724
566
|
.command('create [name]')
|
725
567
|
.description('Create a new Frontend Hamroun project')
|
@@ -740,10 +582,7 @@ program
|
|
740
582
|
.option('-p, --path <path>', 'Path where the page should be created')
|
741
583
|
.option('-ts, --typescript', 'Use TypeScript')
|
742
584
|
.option('-jsx, --jsx', 'Use JSX')
|
743
|
-
.action(
|
744
|
-
// We'll keep the existing implementation for now
|
745
|
-
console.log("The add:page command has been improved in your version.");
|
746
|
-
});
|
585
|
+
.action(addPage);
|
747
586
|
|
748
587
|
program
|
749
588
|
.command('add:api [name]')
|
@@ -752,22 +591,17 @@ program
|
|
752
591
|
.option('-ts, --typescript', 'Use TypeScript')
|
753
592
|
.option('-js, --javascript', 'Use JavaScript')
|
754
593
|
.option('-m, --methods <methods>', 'HTTP methods to implement (comma-separated: get,post,put,delete,patch)')
|
755
|
-
.action(
|
756
|
-
// We'll keep the existing implementation for now
|
757
|
-
console.log("The add:api command has been improved in your version.");
|
758
|
-
});
|
594
|
+
.action(addApiRoute);
|
759
595
|
|
760
596
|
program
|
761
597
|
.command('dev:tools')
|
762
598
|
.description('Show development tools and tips')
|
763
|
-
.action(
|
764
|
-
// We'll keep the existing implementation for now
|
765
|
-
console.log("The dev:tools command has been improved in your version.");
|
766
|
-
});
|
599
|
+
.action(showDevTools);
|
767
600
|
|
768
601
|
// Default command when no arguments
|
769
602
|
if (process.argv.length <= 2) {
|
770
|
-
|
771
|
-
|
772
|
-
|
773
|
-
|
603
|
+
console.log(banner);
|
604
|
+
program.help();
|
605
|
+
}
|
606
|
+
|
607
|
+
program.parse();
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "frontend-hamroun",
|
3
|
-
"version": "1.2.
|
3
|
+
"version": "1.2.41",
|
4
4
|
"description": "A lightweight full-stack JavaScript framework",
|
5
5
|
"type": "module",
|
6
6
|
"main": "./dist/index.js",
|
@@ -125,24 +125,18 @@
|
|
125
125
|
},
|
126
126
|
"dependencies": {
|
127
127
|
"bcryptjs": "^2.4.3",
|
128
|
-
"boxen": "^7.1.1",
|
129
128
|
"chalk": "^5.3.0",
|
130
129
|
"commander": "^11.0.0",
|
131
130
|
"cors": "^2.8.5",
|
132
131
|
"express": "^4.18.2",
|
133
|
-
"figlet": "^1.7.0",
|
134
132
|
"fs-extra": "^11.1.1",
|
135
|
-
"gradient-string": "^3.0.0",
|
136
133
|
"inquirer": "^9.2.10",
|
137
134
|
"jsonwebtoken": "^9.0.2",
|
138
135
|
"mongodb": "^5.7.0",
|
139
136
|
"mysql2": "^3.6.1",
|
140
137
|
"nanospinner": "^1.1.0",
|
141
|
-
"ora": "^7.0.1",
|
142
138
|
"pg": "^8.11.3",
|
143
|
-
"
|
144
|
-
"ts-node": "^10.9.2",
|
145
|
-
"update-notifier": "^6.0.2"
|
139
|
+
"ts-node": "^10.9.2"
|
146
140
|
},
|
147
141
|
"peerDependencies": {
|
148
142
|
"react": ">=16.8.0",
|