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