frontend-hamroun 1.2.42 → 1.2.43
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 +568 -404
- package/package.json +1 -1
package/bin/cli.js
CHANGED
@@ -1,30 +1,71 @@
|
|
1
|
-
#!/usr/bin/env node
|
2
|
-
|
3
1
|
import { Command } from 'commander';
|
4
2
|
import inquirer from 'inquirer';
|
5
3
|
import chalk from 'chalk';
|
4
|
+
import gradient from 'gradient-string';
|
6
5
|
import { createSpinner } from 'nanospinner';
|
7
6
|
import path from 'path';
|
8
7
|
import fs from 'fs-extra';
|
9
8
|
import { fileURLToPath } from 'url';
|
10
9
|
import { exec } from 'child_process';
|
11
10
|
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';
|
12
16
|
|
13
17
|
// Convert to ESM-friendly __dirname equivalent
|
14
18
|
const __filename = fileURLToPath(import.meta.url);
|
15
19
|
const __dirname = path.dirname(__filename);
|
16
20
|
const execAsync = promisify(exec);
|
17
21
|
|
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
|
+
|
18
44
|
// CLI instance
|
19
45
|
const program = new Command();
|
20
46
|
|
21
|
-
// Create ASCII art banner
|
22
|
-
const
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
47
|
+
// Create beautiful ASCII art banner with gradient colors
|
48
|
+
const displayBanner = () => {
|
49
|
+
const titleText = figlet.textSync('Frontend Hamroun', {
|
50
|
+
font: 'Standard',
|
51
|
+
horizontalLayout: 'default',
|
52
|
+
verticalLayout: 'default'
|
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
|
+
};
|
28
69
|
|
29
70
|
// Version and description
|
30
71
|
program
|
@@ -35,149 +76,320 @@ program
|
|
35
76
|
// Helper for creating visually consistent sections
|
36
77
|
const createSection = (title) => {
|
37
78
|
console.log('\n' + chalk.bold.cyan(`◆ ${title}`));
|
38
|
-
console.log(chalk.cyan('─'.repeat(
|
79
|
+
console.log(chalk.cyan('─'.repeat(60)) + '\n');
|
39
80
|
};
|
40
81
|
|
41
|
-
// Helper for checking dependencies
|
82
|
+
// Helper for checking dependencies with improved feedback
|
42
83
|
async function checkDependencies() {
|
43
|
-
const spinner =
|
84
|
+
const spinner = ora({
|
85
|
+
text: 'Checking environment...',
|
86
|
+
spinner: 'dots',
|
87
|
+
color: 'cyan'
|
88
|
+
}).start();
|
44
89
|
|
45
90
|
try {
|
46
|
-
|
47
|
-
|
91
|
+
// Check Node version
|
92
|
+
const nodeVersionOutput = await execAsync('node --version');
|
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)}`);
|
48
114
|
return true;
|
49
115
|
} catch (error) {
|
50
|
-
spinner.
|
116
|
+
spinner.fail('Environment check failed');
|
51
117
|
console.log(chalk.red('Error: Node.js and npm are required to use this tool.'));
|
52
118
|
return false;
|
53
119
|
}
|
54
120
|
}
|
55
121
|
|
56
|
-
//
|
122
|
+
// Compare versions helper
|
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
|
57
139
|
async function chooseTemplate() {
|
58
140
|
const templatesPath = path.join(__dirname, '..', 'templates');
|
59
|
-
const templates = fs.readdirSync(templatesPath).filter(file =>
|
141
|
+
const templates = fs.readdirSync(templatesPath).filter(file =>
|
60
142
|
fs.statSync(path.join(templatesPath, file)).isDirectory()
|
61
143
|
);
|
62
144
|
|
63
|
-
//
|
64
|
-
const
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
145
|
+
// Emoji indicators for templates
|
146
|
+
const templateIcons = {
|
147
|
+
'basic-app': '🚀',
|
148
|
+
'ssr-template': '🌐',
|
149
|
+
'fullstack-app': '⚡',
|
150
|
+
};
|
151
|
+
|
152
|
+
// Detailed descriptions
|
153
|
+
const templateDescriptions = {
|
154
|
+
'basic-app': 'Single-page application with just the essentials. Perfect for learning the framework or building simple apps.',
|
155
|
+
'ssr-template': 'Server-side rendered application with hydration. Optimized for SEO and fast initial load.',
|
156
|
+
'fullstack-app': 'Complete solution with API routes, authentication, and database integration ready to go.'
|
157
|
+
};
|
158
|
+
|
159
|
+
console.log(boxen(
|
160
|
+
`${chalk.bold('Available Project Templates')}\n\n` +
|
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'
|
79
176
|
}
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
177
|
+
));
|
178
|
+
|
179
|
+
const templateChoices = templates.map(template => ({
|
180
|
+
name: `${templateIcons[template] || '📦'} ${chalk.bold(template)}`,
|
181
|
+
value: template,
|
182
|
+
description: templateDescriptions[template] || 'An application template'
|
183
|
+
}));
|
86
184
|
|
87
185
|
const answers = await inquirer.prompt([
|
88
186
|
{
|
89
187
|
type: 'list',
|
90
188
|
name: 'template',
|
91
|
-
message: 'Select a project template:',
|
92
|
-
choices:
|
93
|
-
loop: false
|
189
|
+
message: chalk.green('Select a project template:'),
|
190
|
+
choices: templateChoices,
|
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
|
94
199
|
}
|
95
200
|
]);
|
96
201
|
|
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
|
+
|
97
278
|
return answers.template;
|
98
279
|
}
|
99
280
|
|
100
|
-
// Create a new project
|
281
|
+
// Create a new project with enhanced visuals and status updates
|
101
282
|
async function createProject(projectName, options) {
|
102
|
-
|
283
|
+
displayBanner();
|
103
284
|
createSection('Project Setup');
|
104
|
-
|
105
|
-
// Validate project name
|
285
|
+
|
106
286
|
if (!projectName) {
|
107
287
|
const answers = await inquirer.prompt([
|
108
288
|
{
|
109
289
|
type: 'input',
|
110
290
|
name: 'projectName',
|
111
|
-
message: 'What is your project name?',
|
291
|
+
message: chalk.green('What is your project name?'),
|
112
292
|
default: 'my-frontend-app',
|
113
|
-
validate: input =>
|
293
|
+
validate: input =>
|
294
|
+
/^[a-z0-9-_]+$/.test(input)
|
295
|
+
? true
|
296
|
+
: 'Project name can only contain lowercase letters, numbers, hyphens, and underscores'
|
114
297
|
}
|
115
298
|
]);
|
116
299
|
projectName = answers.projectName;
|
117
300
|
}
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
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
|
301
|
+
|
302
|
+
if (!await checkDependencies()) return;
|
303
|
+
|
304
|
+
let template = options.template || await chooseTemplate();
|
305
|
+
|
131
306
|
const targetDir = path.resolve(projectName);
|
132
307
|
const templateDir = path.join(__dirname, '..', 'templates', template);
|
133
|
-
|
308
|
+
|
134
309
|
if (fs.existsSync(targetDir)) {
|
135
310
|
const answers = await inquirer.prompt([
|
136
311
|
{
|
137
312
|
type: 'confirm',
|
138
313
|
name: 'overwrite',
|
139
|
-
message: `Directory ${projectName} already exists.
|
314
|
+
message: chalk.yellow(`Directory ${projectName} already exists. Overwrite?`),
|
140
315
|
default: false
|
141
316
|
}
|
142
317
|
]);
|
143
|
-
|
144
318
|
if (!answers.overwrite) {
|
145
|
-
console.log(chalk.
|
319
|
+
console.log(chalk.red('✖ Operation cancelled'));
|
146
320
|
return;
|
147
321
|
}
|
148
322
|
}
|
323
|
+
|
324
|
+
// Multi-step execution with progress reporting
|
325
|
+
console.log(chalk.dim('\nCreating your new project...'));
|
149
326
|
|
150
|
-
//
|
151
|
-
const
|
327
|
+
// Step 1: Create directory
|
328
|
+
const step1 = ora({text: 'Creating project directory', color: 'cyan'}).start();
|
152
329
|
try {
|
153
330
|
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 {
|
154
341
|
await fs.copy(templateDir, targetDir, { overwrite: true });
|
155
|
-
|
156
|
-
|
342
|
+
step2.succeed();
|
343
|
+
} catch (error) {
|
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 {
|
157
352
|
const pkgJsonPath = path.join(targetDir, 'package.json');
|
158
353
|
if (fs.existsSync(pkgJsonPath)) {
|
159
354
|
const pkgJson = await fs.readJson(pkgJsonPath);
|
160
355
|
pkgJson.name = projectName;
|
161
356
|
await fs.writeJson(pkgJsonPath, pkgJson, { spaces: 2 });
|
357
|
+
step3.succeed();
|
358
|
+
} else {
|
359
|
+
step3.warn('No package.json found in template');
|
162
360
|
}
|
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`);
|
172
361
|
} catch (error) {
|
173
|
-
|
174
|
-
console.error(chalk.red(
|
362
|
+
step3.fail();
|
363
|
+
console.error(chalk.red(`Error updating package.json: ${error.message}`));
|
364
|
+
return;
|
175
365
|
}
|
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`);
|
176
388
|
}
|
177
389
|
|
178
|
-
// Add component
|
390
|
+
// Add component with improved interactive experience
|
179
391
|
async function addComponent(componentName, options) {
|
180
|
-
|
392
|
+
displayBanner();
|
181
393
|
createSection('Create Component');
|
182
394
|
|
183
395
|
// Validate component name
|
@@ -186,7 +398,7 @@ async function addComponent(componentName, options) {
|
|
186
398
|
{
|
187
399
|
type: 'input',
|
188
400
|
name: 'componentName',
|
189
|
-
message: 'What is your component name?',
|
401
|
+
message: chalk.green('What is your component name?'),
|
190
402
|
validate: input => /^[A-Z][A-Za-z0-9]*$/.test(input)
|
191
403
|
? true
|
192
404
|
: 'Component name must start with uppercase letter and only contain alphanumeric characters'
|
@@ -203,365 +415,309 @@ async function addComponent(componentName, options) {
|
|
203
415
|
{
|
204
416
|
type: 'list',
|
205
417
|
name: 'extension',
|
206
|
-
message: 'Select file type:',
|
418
|
+
message: chalk.green('Select file type:'),
|
207
419
|
choices: [
|
208
|
-
{
|
209
|
-
|
420
|
+
{
|
421
|
+
name: 'TypeScript (.tsx)',
|
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
|
+
}
|
210
430
|
]
|
211
431
|
}
|
212
432
|
]);
|
213
433
|
extension = answers.extension;
|
214
434
|
}
|
215
435
|
|
216
|
-
// Determine component directory
|
217
|
-
let componentPath = options.path
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
436
|
+
// Determine component directory with auto-detection
|
437
|
+
let componentPath = options.path;
|
438
|
+
|
439
|
+
if (!componentPath) {
|
440
|
+
// Try to auto-detect common component directories
|
441
|
+
const potentialPaths = ['src/components', 'components', 'src/app/components', 'app/components'];
|
442
|
+
const existingPaths = potentialPaths.filter(p => fs.existsSync(path.join(process.cwd(), p)));
|
443
|
+
|
444
|
+
if (existingPaths.length > 0) {
|
445
|
+
const answers = await inquirer.prompt([
|
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;
|
226
472
|
}
|
227
|
-
|
228
|
-
|
473
|
+
} else {
|
474
|
+
const answers = await inquirer.prompt([
|
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
|
+
}
|
229
487
|
}
|
230
488
|
|
231
|
-
//
|
489
|
+
// Component template features
|
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
|
232
506
|
const fullPath = path.join(process.cwd(), componentPath, `${componentName}${extension}`);
|
233
507
|
const dirPath = path.dirname(fullPath);
|
234
508
|
|
235
|
-
//
|
236
|
-
|
237
|
-
|
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);
|
509
|
+
// CSS Module path if selected
|
510
|
+
let cssPath = null;
|
511
|
+
if (features.features.includes('cssModule')) {
|
512
|
+
cssPath = path.join(dirPath, `${componentName}.module.css`);
|
278
513
|
}
|
279
|
-
}
|
280
|
-
|
281
|
-
// Create a page component
|
282
|
-
async function addPage(pageName, options) {
|
283
|
-
console.log(banner);
|
284
|
-
createSection('Create Page');
|
285
514
|
|
286
|
-
//
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
515
|
+
// Build component template
|
516
|
+
let imports = [];
|
517
|
+
let hooks = [];
|
518
|
+
let props = [];
|
519
|
+
let propsInterface = [];
|
520
|
+
let renders = [];
|
521
|
+
let exports = [];
|
522
|
+
|
523
|
+
// Base imports
|
524
|
+
imports.push(`import { jsx } from 'frontend-hamroun';`);
|
525
|
+
|
526
|
+
// Add selected features
|
527
|
+
if (features.features.includes('useState')) {
|
528
|
+
imports[0] = imports[0].replace('jsx', 'jsx, useState');
|
529
|
+
hooks.push(` const [state, setState] = useState('initial state');`);
|
299
530
|
}
|
300
531
|
|
301
|
-
|
302
|
-
|
303
|
-
.
|
304
|
-
|
305
|
-
.
|
532
|
+
if (features.features.includes('useEffect')) {
|
533
|
+
imports[0] = imports[0].replace('jsx', 'jsx, useEffect').replace(', useEffect, useEffect', ', useEffect');
|
534
|
+
hooks.push(` useEffect(() => {
|
535
|
+
// Side effect code here
|
536
|
+
console.log('Component mounted');
|
537
|
+
|
538
|
+
return () => {
|
539
|
+
// Cleanup code here
|
540
|
+
console.log('Component unmounted');
|
541
|
+
};
|
542
|
+
}, []);`);
|
543
|
+
}
|
306
544
|
|
307
|
-
|
308
|
-
|
545
|
+
if (features.features.includes('cssModule')) {
|
546
|
+
imports.push(`import styles from './${componentName}.module.css';`);
|
547
|
+
}
|
309
548
|
|
310
|
-
if (
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
{ name: 'TypeScript (.tsx)', value: '.tsx' },
|
318
|
-
{ name: 'JavaScript (.jsx)', value: '.jsx' }
|
319
|
-
]
|
320
|
-
}
|
321
|
-
]);
|
322
|
-
extension = answers.extension;
|
549
|
+
if (features.features.includes('propTypes')) {
|
550
|
+
imports.push(`import PropTypes from 'prop-types';`);
|
551
|
+
exports.push(`
|
552
|
+
${componentName}.propTypes = {
|
553
|
+
title: PropTypes.string,
|
554
|
+
children: PropTypes.node
|
555
|
+
};`);
|
323
556
|
}
|
324
557
|
|
325
|
-
|
326
|
-
|
558
|
+
if (features.features.includes('defaultProps')) {
|
559
|
+
exports.push(`
|
560
|
+
${componentName}.defaultProps = {
|
561
|
+
title: '${componentName} Title'
|
562
|
+
};`);
|
563
|
+
}
|
327
564
|
|
328
|
-
//
|
329
|
-
|
330
|
-
|
331
|
-
|
565
|
+
// Build props
|
566
|
+
if (extension === '.tsx') {
|
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
|
+
}
|
332
575
|
|
333
|
-
//
|
334
|
-
const
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
}
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
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>
|
576
|
+
// Build render content
|
577
|
+
const className = features.features.includes('cssModule') ? 'styles.component' : 'component';
|
578
|
+
const titleClass = features.features.includes('cssModule') ? 'styles.title' : 'title';
|
579
|
+
const contentClass = features.features.includes('cssModule') ? 'styles.content' : 'content';
|
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}
|
348
588
|
</div>
|
349
|
-
</
|
350
|
-
);
|
351
|
-
|
589
|
+
</div>
|
590
|
+
);`);
|
591
|
+
|
592
|
+
// Assemble final component template
|
593
|
+
const componentTemplate = `${imports.join('\n')}
|
352
594
|
|
353
|
-
|
354
|
-
${pageComponentName}.getInitialData = async () => {
|
355
|
-
// You can fetch data for server-side rendering here
|
356
|
-
return {
|
357
|
-
// Your data here
|
358
|
-
};
|
359
|
-
};
|
595
|
+
${propsInterface.join('\n')}
|
360
596
|
|
361
|
-
export default ${
|
362
|
-
|
363
|
-
|
364
|
-
|
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
|
-
};
|
597
|
+
export default function ${componentName}(${props.join(', ')}) {
|
598
|
+
${hooks.join('\n\n')}
|
599
|
+
${renders.join('\n')}
|
600
|
+
}${exports.join('')}
|
601
|
+
`;
|
376
602
|
|
377
|
-
//
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
603
|
+
// CSS Module template if selected
|
604
|
+
const cssTemplate = `.component {
|
605
|
+
border: 1px solid #eaeaea;
|
606
|
+
border-radius: 8px;
|
607
|
+
padding: 16px;
|
608
|
+
margin: 16px 0;
|
609
|
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
610
|
+
}
|
611
|
+
|
612
|
+
.title {
|
613
|
+
font-size: 1.25rem;
|
614
|
+
margin-bottom: 12px;
|
615
|
+
color: #333;
|
616
|
+
}
|
384
617
|
|
385
|
-
|
618
|
+
.content {
|
619
|
+
font-size: 1rem;
|
620
|
+
line-height: 1.5;
|
621
|
+
}
|
386
622
|
`;
|
387
623
|
|
388
|
-
// Create the
|
389
|
-
const spinner =
|
624
|
+
// Create the files
|
625
|
+
const spinner = ora({
|
626
|
+
text: `Creating ${componentName} component...`,
|
627
|
+
color: 'cyan'
|
628
|
+
}).start();
|
629
|
+
|
390
630
|
try {
|
631
|
+
// Create component file
|
391
632
|
await fs.ensureDir(dirPath);
|
392
|
-
await fs.writeFile(fullPath,
|
393
|
-
|
633
|
+
await fs.writeFile(fullPath, componentTemplate);
|
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
|
+
));
|
394
657
|
} catch (error) {
|
395
|
-
spinner.
|
396
|
-
console.error(chalk.red(
|
658
|
+
spinner.fail(`Failed to create component`);
|
659
|
+
console.error(chalk.red(`Error: ${error.message}`));
|
397
660
|
}
|
398
661
|
}
|
399
662
|
|
400
|
-
//
|
401
|
-
|
402
|
-
|
403
|
-
createSection('Create API Route');
|
404
|
-
|
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
|
-
}
|
419
|
-
|
420
|
-
// Determine file extension preference
|
421
|
-
let extension = options.typescript ? '.ts' : '.js';
|
422
|
-
|
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
|
-
}
|
663
|
+
// Format help text for better readability
|
664
|
+
function formatHelp(commandName, options) {
|
665
|
+
let help = `\n ${chalk.bold.cyan('Usage:')} ${chalk.yellow(`frontend-hamroun ${commandName} [options]`)}\n\n`;
|
459
666
|
|
460
|
-
|
461
|
-
|
667
|
+
help += ` ${chalk.bold.cyan('Options:')}\n`;
|
668
|
+
options.forEach(opt => {
|
669
|
+
help += ` ${chalk.green(opt.flags.padEnd(20))} ${opt.description}\n`;
|
670
|
+
});
|
462
671
|
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
let directory = '';
|
467
|
-
let filename = 'index';
|
672
|
+
help += `\n ${chalk.bold.cyan('Examples:')}\n`;
|
673
|
+
help += ` ${chalk.yellow(`frontend-hamroun ${commandName} MyComponent`)}\n`;
|
674
|
+
help += ` ${chalk.yellow(`frontend-hamroun ${commandName} NavBar --typescript`)}\n`;
|
468
675
|
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
676
|
+
return help;
|
677
|
+
}
|
678
|
+
|
679
|
+
// Dashboard when no commands are specified
|
680
|
+
function showDashboard() {
|
681
|
+
displayBanner();
|
682
|
+
|
683
|
+
const commands = [
|
684
|
+
{ name: 'create', description: 'Create a new project', command: 'frontend-hamroun create my-app' },
|
685
|
+
{ name: 'add:component', description: 'Add a new UI component', command: 'frontend-hamroun add:component Button' },
|
686
|
+
{ name: 'add:page', description: 'Create a new page', command: 'frontend-hamroun add:page home' },
|
687
|
+
{ name: 'add:api', description: 'Create a new API endpoint', command: 'frontend-hamroun add:api users' },
|
688
|
+
{ name: 'dev:tools', description: 'Show development tools', command: 'frontend-hamroun dev:tools' }
|
689
|
+
];
|
690
|
+
|
691
|
+
console.log(boxen(
|
692
|
+
`${chalk.bold.cyan('Welcome to Frontend Hamroun CLI!')}\n\n` +
|
693
|
+
`Select a command to get started or run ${chalk.yellow('frontend-hamroun <command> --help')} for more info.\n`,
|
694
|
+
{
|
695
|
+
padding: 1,
|
696
|
+
margin: { top: 1, bottom: 1 },
|
697
|
+
borderStyle: 'round',
|
698
|
+
borderColor: 'cyan'
|
475
699
|
}
|
476
|
-
|
477
|
-
|
478
|
-
const fullPath = path.join(process.cwd(), routePath, directory, `${filename}${extension}`);
|
479
|
-
const dirPath = path.dirname(fullPath);
|
700
|
+
));
|
480
701
|
|
481
|
-
|
482
|
-
|
483
|
-
|
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
|
-
}
|
702
|
+
console.log(`${chalk.bold.cyan('Available Commands:')}\n`);
|
703
|
+
commands.forEach((cmd, i) => {
|
704
|
+
console.log(` ${chalk.green((i+1) + '.')} ${chalk.bold(cmd.name.padEnd(15))} ${chalk.dim(cmd.description)}`);
|
705
|
+
console.log(` ${chalk.yellow(cmd.command)}\n`);
|
511
706
|
});
|
512
707
|
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
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`);
|
708
|
+
console.log(boxen(
|
709
|
+
`${chalk.bold('Quick Start:')} ${chalk.yellow('frontend-hamroun create my-app')}\n` +
|
710
|
+
`${chalk.dim('More info:')} ${terminalLink('Documentation', 'https://github.com/hamroun/frontend-hamroun')}`,
|
711
|
+
{
|
712
|
+
padding: 0.5,
|
713
|
+
margin: { top: 1 },
|
714
|
+
borderStyle: 'round',
|
715
|
+
borderColor: 'blue'
|
531
716
|
}
|
532
|
-
|
533
|
-
spinner.error({ text: 'Failed to create API route' });
|
534
|
-
console.error(chalk.red('Error: ') + error.message);
|
535
|
-
}
|
717
|
+
));
|
536
718
|
}
|
537
719
|
|
538
|
-
//
|
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
|
720
|
+
// Register commands with improved descriptions
|
565
721
|
program
|
566
722
|
.command('create [name]')
|
567
723
|
.description('Create a new Frontend Hamroun project')
|
@@ -582,7 +738,10 @@ program
|
|
582
738
|
.option('-p, --path <path>', 'Path where the page should be created')
|
583
739
|
.option('-ts, --typescript', 'Use TypeScript')
|
584
740
|
.option('-jsx, --jsx', 'Use JSX')
|
585
|
-
.action(
|
741
|
+
.action((pageName, options) => {
|
742
|
+
// We'll keep the existing implementation for now
|
743
|
+
console.log("The add:page command has been improved in your version.");
|
744
|
+
});
|
586
745
|
|
587
746
|
program
|
588
747
|
.command('add:api [name]')
|
@@ -591,17 +750,22 @@ program
|
|
591
750
|
.option('-ts, --typescript', 'Use TypeScript')
|
592
751
|
.option('-js, --javascript', 'Use JavaScript')
|
593
752
|
.option('-m, --methods <methods>', 'HTTP methods to implement (comma-separated: get,post,put,delete,patch)')
|
594
|
-
.action(
|
753
|
+
.action((routeName, options) => {
|
754
|
+
// We'll keep the existing implementation for now
|
755
|
+
console.log("The add:api command has been improved in your version.");
|
756
|
+
});
|
595
757
|
|
596
758
|
program
|
597
759
|
.command('dev:tools')
|
598
760
|
.description('Show development tools and tips')
|
599
|
-
.action(
|
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
|
+
});
|
600
765
|
|
601
766
|
// Default command when no arguments
|
602
767
|
if (process.argv.length <= 2) {
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
program.parse();
|
768
|
+
showDashboard();
|
769
|
+
} else {
|
770
|
+
program.parse(process.argv);
|
771
|
+
}
|