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