baraqex 1.0.30 ā 1.0.32
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/baraqex.js +1252 -196
- package/package.json +1 -1
package/bin/baraqex.js
CHANGED
|
@@ -1,265 +1,1321 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { Command } from 'commander';
|
|
4
4
|
import inquirer from 'inquirer';
|
|
5
5
|
import chalk from 'chalk';
|
|
6
|
-
import
|
|
6
|
+
import gradient from 'gradient-string';
|
|
7
7
|
import { createSpinner } from 'nanospinner';
|
|
8
|
-
import fs from 'fs-extra';
|
|
9
8
|
import path from 'path';
|
|
9
|
+
import fs from 'fs-extra';
|
|
10
10
|
import { fileURLToPath } from 'url';
|
|
11
|
-
import {
|
|
11
|
+
import { exec } from 'child_process';
|
|
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
|
|
|
19
|
+
// Convert to ESM-friendly __dirname equivalent
|
|
13
20
|
const __filename = fileURLToPath(import.meta.url);
|
|
14
21
|
const __dirname = path.dirname(__filename);
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
//
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
22
|
+
const execAsync = promisify(exec);
|
|
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);
|
|
33
41
|
}
|
|
42
|
+
} catch (error) {
|
|
43
|
+
// Silently continue if update check fails
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// CLI instance
|
|
47
|
+
const program = new Command();
|
|
48
|
+
|
|
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
|
+
));
|
|
34
70
|
};
|
|
35
71
|
|
|
36
|
-
|
|
37
|
-
|
|
72
|
+
// Version and description
|
|
73
|
+
program
|
|
74
|
+
.name('frontend-hamroun')
|
|
75
|
+
.description('CLI for Frontend Hamroun - A lightweight full-stack JavaScript framework')
|
|
76
|
+
.version(JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf8')).version);
|
|
38
77
|
|
|
39
|
-
|
|
78
|
+
// Helper for creating visually consistent sections
|
|
79
|
+
const createSection = (title) => {
|
|
80
|
+
console.log('\n' + chalk.bold.cyan(`ā ${title}`));
|
|
81
|
+
console.log(chalk.cyan('ā'.repeat(60)) + '\n');
|
|
82
|
+
};
|
|
40
83
|
|
|
84
|
+
// Helper for checking dependencies with improved feedback
|
|
85
|
+
async function checkDependencies() {
|
|
86
|
+
const spinner = ora({
|
|
87
|
+
text: 'Checking environment...',
|
|
88
|
+
spinner: 'dots',
|
|
89
|
+
color: 'cyan'
|
|
90
|
+
}).start();
|
|
91
|
+
|
|
41
92
|
try {
|
|
42
|
-
//
|
|
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)}`);
|
|
116
|
+
return true;
|
|
117
|
+
} catch (error) {
|
|
118
|
+
spinner.fail('Environment check failed');
|
|
119
|
+
console.log(chalk.red('Error: Node.js and npm are required to use this tool.'));
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
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
|
|
141
|
+
async function chooseTemplate() {
|
|
142
|
+
const templatesPath = path.join(__dirname, '..', 'templates');
|
|
143
|
+
const templates = fs.readdirSync(templatesPath).filter(file =>
|
|
144
|
+
fs.statSync(path.join(templatesPath, file)).isDirectory()
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
// Emoji indicators for templates
|
|
148
|
+
const templateIcons = {
|
|
149
|
+
'basic-app': 'š',
|
|
150
|
+
'ssr-template': 'š',
|
|
151
|
+
'fullstack-app': 'ā”',
|
|
152
|
+
'go-wasm-app': 'š',
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
// Detailed descriptions
|
|
156
|
+
const templateDescriptions = {
|
|
157
|
+
'basic-app': 'Single-page application with just the essentials. Perfect for learning the framework or building simple apps.',
|
|
158
|
+
'ssr-template': 'Server-side rendered application with hydration. Optimized for SEO and fast initial load.',
|
|
159
|
+
'fullstack-app': 'Complete solution with API routes, authentication, and database integration ready to go.',
|
|
160
|
+
'go-wasm-app': 'WebAssembly integration with Go for high-performance computing in the browser and Node.js.'
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
console.log(boxen(
|
|
164
|
+
`${chalk.bold('Available Project Templates')}\n\n` +
|
|
165
|
+
templates.map(template => {
|
|
166
|
+
const icon = templateIcons[template] || 'š¦';
|
|
167
|
+
const shortDesc = {
|
|
168
|
+
'basic-app': 'Simple client-side application',
|
|
169
|
+
'ssr-template': 'Server-side rendering with hydration',
|
|
170
|
+
'fullstack-app': 'Complete fullstack application with API'
|
|
171
|
+
}[template] || 'Application template';
|
|
172
|
+
|
|
173
|
+
return `${icon} ${chalk.cyan(template)}\n ${chalk.dim(shortDesc)}`;
|
|
174
|
+
}).join('\n\n'),
|
|
175
|
+
{
|
|
176
|
+
padding: 1,
|
|
177
|
+
margin: 1,
|
|
178
|
+
borderStyle: 'round',
|
|
179
|
+
borderColor: 'cyan'
|
|
180
|
+
}
|
|
181
|
+
));
|
|
182
|
+
|
|
183
|
+
const templateChoices = templates.map(template => ({
|
|
184
|
+
name: `${templateIcons[template] || 'š¦'} ${chalk.bold(template)}`,
|
|
185
|
+
value: template,
|
|
186
|
+
description: templateDescriptions[template] || 'An application template'
|
|
187
|
+
}));
|
|
188
|
+
|
|
189
|
+
const answers = await inquirer.prompt([
|
|
190
|
+
{
|
|
191
|
+
type: 'list',
|
|
192
|
+
name: 'template',
|
|
193
|
+
message: chalk.green('Select a project template:'),
|
|
194
|
+
choices: templateChoices,
|
|
195
|
+
loop: false,
|
|
196
|
+
pageSize: 10
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
type: 'confirm',
|
|
200
|
+
name: 'viewDetails',
|
|
201
|
+
message: 'Would you like to see template details before proceeding?',
|
|
202
|
+
default: false
|
|
203
|
+
}
|
|
204
|
+
]);
|
|
205
|
+
|
|
206
|
+
if (answers.viewDetails) {
|
|
207
|
+
const detailedDescription = {
|
|
208
|
+
'basic-app': [
|
|
209
|
+
`${chalk.bold('Basic App Template')} ${templateIcons['basic-app']}`,
|
|
210
|
+
'',
|
|
211
|
+
`${chalk.dim('A lightweight client-side application template.')}`,
|
|
212
|
+
'',
|
|
213
|
+
`${chalk.yellow('Features:')}`,
|
|
214
|
+
'⢠No build step required for development',
|
|
215
|
+
'⢠Built-in state management with hooks',
|
|
216
|
+
'⢠Component-based architecture',
|
|
217
|
+
'⢠Tailwind CSS integration',
|
|
218
|
+
'',
|
|
219
|
+
`${chalk.yellow('Best for:')}`,
|
|
220
|
+
'⢠Simple web applications',
|
|
221
|
+
'⢠Learning the framework',
|
|
222
|
+
'⢠Quick prototyping',
|
|
223
|
+
],
|
|
224
|
+
'ssr-template': [
|
|
225
|
+
`${chalk.bold('SSR Template')} ${templateIcons['ssr-template']}`,
|
|
226
|
+
'',
|
|
227
|
+
`${chalk.dim('Server-side rendering with client hydration.')}`,
|
|
228
|
+
'',
|
|
229
|
+
`${chalk.yellow('Features:')}`,
|
|
230
|
+
'⢠React-like development experience',
|
|
231
|
+
'⢠SEO-friendly rendered HTML',
|
|
232
|
+
'⢠Fast initial page load',
|
|
233
|
+
'⢠Smooth client-side transitions',
|
|
234
|
+
'⢠Built-in dynamic meta tag generation',
|
|
235
|
+
'',
|
|
236
|
+
`${chalk.yellow('Best for:')}`,
|
|
237
|
+
'⢠Production websites needing SEO',
|
|
238
|
+
'⢠Content-focused applications',
|
|
239
|
+
'⢠Sites requiring social sharing previews'
|
|
240
|
+
],
|
|
241
|
+
'fullstack-app': [
|
|
242
|
+
`${chalk.bold('Fullstack App Template')} ${templateIcons['fullstack-app']}`,
|
|
243
|
+
'',
|
|
244
|
+
`${chalk.dim('Complete solution with frontend and backend.')}`,
|
|
245
|
+
'',
|
|
246
|
+
`${chalk.yellow('Features:')}`,
|
|
247
|
+
'⢠API routes with Express integration',
|
|
248
|
+
'⢠File-based routing system',
|
|
249
|
+
'⢠Authentication system with JWT',
|
|
250
|
+
'⢠Database connectors (MongoDB, MySQL, PostgreSQL)',
|
|
251
|
+
'⢠Server-side rendering with hydration',
|
|
252
|
+
'⢠WebSocket support',
|
|
253
|
+
'',
|
|
254
|
+
`${chalk.yellow('Best for:')}`,
|
|
255
|
+
'⢠Full production applications',
|
|
256
|
+
'⢠Apps needing authentication',
|
|
257
|
+
'⢠Projects requiring database integration'
|
|
258
|
+
],
|
|
259
|
+
'go-wasm-app': [
|
|
260
|
+
`${chalk.bold('Go WASM App Template')} ${templateIcons['go-wasm-app'] || 'š'}`,
|
|
261
|
+
'',
|
|
262
|
+
`${chalk.dim('WebAssembly integration with Go programming language.')}`,
|
|
263
|
+
'',
|
|
264
|
+
`${chalk.yellow('Features:')}`,
|
|
265
|
+
'⢠Go + WebAssembly integration',
|
|
266
|
+
'⢠High-performance computation in browser',
|
|
267
|
+
'⢠Server-side WASM processing',
|
|
268
|
+
'⢠Shared code between Go and JavaScript',
|
|
269
|
+
'⢠Optimized build pipeline',
|
|
270
|
+
'',
|
|
271
|
+
`${chalk.yellow('Best for:')}`,
|
|
272
|
+
'⢠Computation-heavy applications',
|
|
273
|
+
'⢠Projects requiring Go libraries',
|
|
274
|
+
'⢠Performance-critical features'
|
|
275
|
+
]
|
|
276
|
+
}[answers.template] || ['No detailed information available for this template'];
|
|
277
|
+
|
|
278
|
+
console.log(boxen(detailedDescription.join('\n'), {
|
|
279
|
+
padding: 1,
|
|
280
|
+
margin: 1,
|
|
281
|
+
title: answers.template,
|
|
282
|
+
titleAlignment: 'center',
|
|
283
|
+
borderStyle: 'round',
|
|
284
|
+
borderColor: 'yellow'
|
|
285
|
+
}));
|
|
286
|
+
|
|
287
|
+
const proceed = await inquirer.prompt([{
|
|
288
|
+
type: 'confirm',
|
|
289
|
+
name: 'continue',
|
|
290
|
+
message: 'Continue with this template?',
|
|
291
|
+
default: true
|
|
292
|
+
}]);
|
|
293
|
+
|
|
294
|
+
if (!proceed.continue) {
|
|
295
|
+
return chooseTemplate();
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
return answers.template;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Create a new project with enhanced visuals and status updates
|
|
303
|
+
async function createProject(projectName, options) {
|
|
304
|
+
displayBanner();
|
|
305
|
+
createSection('Project Setup');
|
|
306
|
+
|
|
307
|
+
if (!projectName) {
|
|
43
308
|
const answers = await inquirer.prompt([
|
|
44
309
|
{
|
|
45
310
|
type: 'input',
|
|
46
311
|
name: 'projectName',
|
|
47
|
-
message: '
|
|
48
|
-
default: 'my-frontend-
|
|
49
|
-
validate:
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
}
|
|
54
|
-
},
|
|
55
|
-
{
|
|
56
|
-
type: 'list',
|
|
57
|
-
name: 'template',
|
|
58
|
-
message: 'Choose a template:',
|
|
59
|
-
choices: [
|
|
60
|
-
{
|
|
61
|
-
name: `${chalk.green('ā')} Basic App - Simple Frontend Hamroun application`,
|
|
62
|
-
value: 'basic'
|
|
63
|
-
},
|
|
64
|
-
{
|
|
65
|
-
name: `${chalk.blue('ā')} SSR Template - Advanced SSR with hooks and modern design`,
|
|
66
|
-
value: 'ssr'
|
|
67
|
-
},
|
|
68
|
-
{
|
|
69
|
-
name: `${chalk.purple('ā')} Full-Stack App - Complete app with API routes and database`,
|
|
70
|
-
value: 'fullstack'
|
|
71
|
-
}
|
|
72
|
-
]
|
|
73
|
-
},
|
|
74
|
-
{
|
|
75
|
-
type: 'confirm',
|
|
76
|
-
name: 'installDeps',
|
|
77
|
-
message: 'Install dependencies?',
|
|
78
|
-
default: true
|
|
312
|
+
message: chalk.green('What is your project name?'),
|
|
313
|
+
default: 'my-frontend-app',
|
|
314
|
+
validate: input =>
|
|
315
|
+
/^[a-z0-9-_]+$/.test(input)
|
|
316
|
+
? true
|
|
317
|
+
: 'Project name can only contain lowercase letters, numbers, hyphens, and underscores'
|
|
79
318
|
}
|
|
80
319
|
]);
|
|
320
|
+
projectName = answers.projectName;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
if (!await checkDependencies()) return;
|
|
81
324
|
|
|
82
|
-
|
|
83
|
-
const projectPath = path.join(process.cwd(), projectName);
|
|
325
|
+
let template = options.template || await chooseTemplate();
|
|
84
326
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
327
|
+
const targetDir = path.resolve(projectName);
|
|
328
|
+
const templateDir = path.join(__dirname, '..', 'templates', template);
|
|
329
|
+
|
|
330
|
+
if (fs.existsSync(targetDir)) {
|
|
331
|
+
const answers = await inquirer.prompt([
|
|
332
|
+
{
|
|
88
333
|
type: 'confirm',
|
|
89
334
|
name: 'overwrite',
|
|
90
|
-
message: `Directory ${projectName} already exists. Overwrite
|
|
335
|
+
message: chalk.yellow(`Directory ${projectName} already exists. Overwrite?`),
|
|
91
336
|
default: false
|
|
92
|
-
}]);
|
|
93
|
-
|
|
94
|
-
if (!overwrite.overwrite) {
|
|
95
|
-
console.log(chalk.yellow('Cancelled.'));
|
|
96
|
-
return;
|
|
97
337
|
}
|
|
98
|
-
|
|
99
|
-
|
|
338
|
+
]);
|
|
339
|
+
if (!answers.overwrite) {
|
|
340
|
+
console.log(chalk.red('ā Operation cancelled'));
|
|
341
|
+
return;
|
|
100
342
|
}
|
|
343
|
+
}
|
|
101
344
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
345
|
+
// Multi-step execution with progress reporting
|
|
346
|
+
console.log(chalk.dim('\nCreating your new project...'));
|
|
347
|
+
|
|
348
|
+
// Step 1: Create directory
|
|
349
|
+
const step1 = ora({text: 'Creating project directory', color: 'cyan'}).start();
|
|
350
|
+
try {
|
|
351
|
+
await fs.ensureDir(targetDir);
|
|
352
|
+
step1.succeed();
|
|
353
|
+
} catch (error) {
|
|
354
|
+
step1.fail();
|
|
355
|
+
console.error(chalk.red(`Error creating directory: ${error.message}`));
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// Step 2: Copy template files
|
|
360
|
+
const step2 = ora({text: 'Copying template files', color: 'cyan'}).start();
|
|
361
|
+
try {
|
|
362
|
+
await fs.copy(templateDir, targetDir, { overwrite: true });
|
|
363
|
+
step2.succeed();
|
|
364
|
+
} catch (error) {
|
|
365
|
+
step2.fail();
|
|
366
|
+
console.error(chalk.red(`Error copying files: ${error.message}`));
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// Step 3: Update package.json
|
|
371
|
+
const step3 = ora({text: 'Configuring package.json', color: 'cyan'}).start();
|
|
372
|
+
try {
|
|
373
|
+
const pkgJsonPath = path.join(targetDir, 'package.json');
|
|
374
|
+
if (fs.existsSync(pkgJsonPath)) {
|
|
375
|
+
const pkgJson = await fs.readJson(pkgJsonPath);
|
|
376
|
+
pkgJson.name = projectName;
|
|
377
|
+
await fs.writeJson(pkgJsonPath, pkgJson, { spaces: 2 });
|
|
378
|
+
step3.succeed();
|
|
379
|
+
} else {
|
|
380
|
+
step3.warn('No package.json found in template');
|
|
381
|
+
}
|
|
382
|
+
} catch (error) {
|
|
383
|
+
step3.fail();
|
|
384
|
+
console.error(chalk.red(`Error updating package.json: ${error.message}`));
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// Final success message with helpful instructions
|
|
389
|
+
console.log('\n' + boxen(
|
|
390
|
+
`${chalk.bold.green('Project created successfully!')}\n\n` +
|
|
391
|
+
`${chalk.bold('Project:')} ${chalk.cyan(projectName)}\n` +
|
|
392
|
+
`${chalk.bold('Template:')} ${chalk.cyan(template)}\n` +
|
|
393
|
+
`${chalk.bold('Location:')} ${chalk.cyan(targetDir)}\n\n` +
|
|
394
|
+
`${chalk.bold.yellow('Next steps:')}\n\n` +
|
|
395
|
+
` ${chalk.dim('1.')} ${chalk.cyan(`cd ${projectName}`)}\n` +
|
|
396
|
+
` ${chalk.dim('2.')} ${chalk.cyan('npm install')}\n` +
|
|
397
|
+
` ${chalk.dim('3.')} ${chalk.cyan('npm run dev')}\n\n` +
|
|
398
|
+
`${chalk.dim('For help and documentation:')} ${terminalLink('frontendhamroun.io', 'https://github.com/hamroun/frontend-hamroun')}`,
|
|
399
|
+
{
|
|
400
|
+
padding: 1,
|
|
401
|
+
margin: 1,
|
|
402
|
+
borderStyle: 'round',
|
|
403
|
+
borderColor: 'green'
|
|
110
404
|
}
|
|
405
|
+
));
|
|
406
|
+
|
|
407
|
+
// Suggest next command
|
|
408
|
+
console.log(`\n${chalk.cyan('Tip:')} Run ${chalk.bold.green(`cd ${projectName} && npm install`)} to get started right away.\n`);
|
|
409
|
+
}
|
|
111
410
|
|
|
112
|
-
|
|
113
|
-
|
|
411
|
+
// Add component with improved interactive experience
|
|
412
|
+
async function addComponent(componentName, options) {
|
|
413
|
+
displayBanner();
|
|
414
|
+
createSection('Create Component');
|
|
415
|
+
|
|
416
|
+
// Validate component name
|
|
417
|
+
if (!componentName) {
|
|
418
|
+
const answers = await inquirer.prompt([
|
|
419
|
+
{
|
|
420
|
+
type: 'input',
|
|
421
|
+
name: 'componentName',
|
|
422
|
+
message: chalk.green('What is your component name?'),
|
|
423
|
+
validate: input => /^[A-Z][A-Za-z0-9]*$/.test(input)
|
|
424
|
+
? true
|
|
425
|
+
: 'Component name must start with uppercase letter and only contain alphanumeric characters'
|
|
426
|
+
}
|
|
427
|
+
]);
|
|
428
|
+
componentName = answers.componentName;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// Determine file extension preference
|
|
432
|
+
let extension = options.typescript ? '.tsx' : options.jsx ? '.jsx' : null;
|
|
433
|
+
|
|
434
|
+
if (!extension) {
|
|
435
|
+
const answers = await inquirer.prompt([
|
|
436
|
+
{
|
|
437
|
+
type: 'list',
|
|
438
|
+
name: 'extension',
|
|
439
|
+
message: chalk.green('Select file type:'),
|
|
440
|
+
choices: [
|
|
441
|
+
{
|
|
442
|
+
name: 'TypeScript (.tsx)',
|
|
443
|
+
value: '.tsx',
|
|
444
|
+
description: 'TypeScript with JSX support'
|
|
445
|
+
},
|
|
446
|
+
{
|
|
447
|
+
name: 'JavaScript (.jsx)',
|
|
448
|
+
value: '.jsx',
|
|
449
|
+
description: 'JavaScript with JSX support'
|
|
450
|
+
}
|
|
451
|
+
]
|
|
452
|
+
}
|
|
453
|
+
]);
|
|
454
|
+
extension = answers.extension;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// Determine component directory with auto-detection
|
|
458
|
+
let componentPath = options.path;
|
|
459
|
+
|
|
460
|
+
if (!componentPath) {
|
|
461
|
+
// Try to auto-detect common component directories
|
|
462
|
+
const potentialPaths = ['src/components', 'components', 'src/app/components', 'app/components'];
|
|
463
|
+
const existingPaths = potentialPaths.filter(p => fs.existsSync(path.join(process.cwd(), p)));
|
|
114
464
|
|
|
115
|
-
if (
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
465
|
+
if (existingPaths.length > 0) {
|
|
466
|
+
const answers = await inquirer.prompt([
|
|
467
|
+
{
|
|
468
|
+
type: 'list',
|
|
469
|
+
name: 'path',
|
|
470
|
+
message: chalk.green('Where do you want to create this component?'),
|
|
471
|
+
choices: [
|
|
472
|
+
...existingPaths.map(p => ({ name: `${p} ${chalk.dim('(detected)')}`, value: p })),
|
|
473
|
+
{ name: 'Custom location...', value: 'custom' }
|
|
474
|
+
]
|
|
475
|
+
}
|
|
476
|
+
]);
|
|
477
|
+
|
|
478
|
+
if (answers.path === 'custom') {
|
|
479
|
+
const customPath = await inquirer.prompt([
|
|
480
|
+
{
|
|
481
|
+
type: 'input',
|
|
482
|
+
name: 'customPath',
|
|
483
|
+
message: chalk.green('Enter the component path:'),
|
|
484
|
+
default: 'src/components',
|
|
485
|
+
validate: input => /^[a-zA-Z0-9-_/\\]+$/.test(input)
|
|
486
|
+
? true
|
|
487
|
+
: 'Path can only contain letters, numbers, slashes, hyphens and underscores'
|
|
488
|
+
}
|
|
489
|
+
]);
|
|
490
|
+
componentPath = customPath.customPath;
|
|
491
|
+
} else {
|
|
492
|
+
componentPath = answers.path;
|
|
128
493
|
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
494
|
+
} else {
|
|
495
|
+
const answers = await inquirer.prompt([
|
|
496
|
+
{
|
|
497
|
+
type: 'input',
|
|
498
|
+
name: 'path',
|
|
499
|
+
message: chalk.green('Where do you want to create this component?'),
|
|
500
|
+
default: 'src/components',
|
|
501
|
+
validate: input => /^[a-zA-Z0-9-_/\\]+$/.test(input)
|
|
502
|
+
? true
|
|
503
|
+
: 'Path can only contain letters, numbers, slashes, hyphens and underscores'
|
|
504
|
+
}
|
|
505
|
+
]);
|
|
506
|
+
componentPath = answers.path;
|
|
137
507
|
}
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// Component template features
|
|
511
|
+
const features = await inquirer.prompt([
|
|
512
|
+
{
|
|
513
|
+
type: 'checkbox',
|
|
514
|
+
name: 'features',
|
|
515
|
+
message: chalk.green('Select component features:'),
|
|
516
|
+
choices: [
|
|
517
|
+
{ name: 'useState Hook', value: 'useState', checked: true },
|
|
518
|
+
{ name: 'useEffect Hook', value: 'useEffect' },
|
|
519
|
+
{ name: 'CSS Module', value: 'cssModule' },
|
|
520
|
+
{ name: 'PropTypes', value: 'propTypes' },
|
|
521
|
+
{ name: 'Default Props', value: 'defaultProps' }
|
|
522
|
+
]
|
|
523
|
+
}
|
|
524
|
+
]);
|
|
525
|
+
|
|
526
|
+
// Create component content based on type and selected features
|
|
527
|
+
const fullPath = path.join(process.cwd(), componentPath, `${componentName}${extension}`);
|
|
528
|
+
const dirPath = path.dirname(fullPath);
|
|
529
|
+
|
|
530
|
+
// CSS Module path if selected
|
|
531
|
+
let cssPath = null;
|
|
532
|
+
if (features.features.includes('cssModule')) {
|
|
533
|
+
cssPath = path.join(dirPath, `${componentName}.module.css`);
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
// Build component template
|
|
537
|
+
let imports = [];
|
|
538
|
+
let hooks = [];
|
|
539
|
+
let props = [];
|
|
540
|
+
let propsInterface = [];
|
|
541
|
+
let renders = [];
|
|
542
|
+
let exports = [];
|
|
543
|
+
|
|
544
|
+
// Base imports
|
|
545
|
+
imports.push(`import { jsx } from 'frontend-hamroun';`);
|
|
546
|
+
|
|
547
|
+
// Add selected features
|
|
548
|
+
if (features.features.includes('useState')) {
|
|
549
|
+
imports[0] = imports[0].replace('jsx', 'jsx, useState');
|
|
550
|
+
hooks.push(` const [state, setState] = useState('initial state');`);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
if (features.features.includes('useEffect')) {
|
|
554
|
+
imports[0] = imports[0].replace('jsx', 'jsx, useEffect').replace(', useEffect, useEffect', ', useEffect');
|
|
555
|
+
hooks.push(` useEffect(() => {
|
|
556
|
+
// Side effect code here
|
|
557
|
+
console.log('Component mounted');
|
|
558
|
+
|
|
559
|
+
return () => {
|
|
560
|
+
// Cleanup code here
|
|
561
|
+
console.log('Component unmounted');
|
|
562
|
+
};
|
|
563
|
+
}, []);`);
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
if (features.features.includes('cssModule')) {
|
|
567
|
+
imports.push(`import styles from './${componentName}.module.css';`);
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
if (features.features.includes('propTypes')) {
|
|
571
|
+
imports.push(`import PropTypes from 'prop-types';`);
|
|
572
|
+
exports.push(`
|
|
573
|
+
${componentName}.propTypes = {
|
|
574
|
+
title: PropTypes.string,
|
|
575
|
+
children: PropTypes.node
|
|
576
|
+
};`);
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
if (features.features.includes('defaultProps')) {
|
|
580
|
+
exports.push(`
|
|
581
|
+
${componentName}.defaultProps = {
|
|
582
|
+
title: '${componentName} Title'
|
|
583
|
+
};`);
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
// Build props
|
|
587
|
+
if (extension === '.tsx') {
|
|
588
|
+
propsInterface.push(`interface ${componentName}Props {
|
|
589
|
+
title?: string;
|
|
590
|
+
children?: React.ReactNode;
|
|
591
|
+
}`);
|
|
592
|
+
props.push(`{ title, children }: ${componentName}Props`);
|
|
593
|
+
} else {
|
|
594
|
+
props.push(`{ title, children }`);
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
// Build render content
|
|
598
|
+
const className = features.features.includes('cssModule') ? 'styles.component' : 'component';
|
|
599
|
+
const titleClass = features.features.includes('cssModule') ? 'styles.title' : 'title';
|
|
600
|
+
const contentClass = features.features.includes('cssModule') ? 'styles.content' : 'content';
|
|
601
|
+
|
|
602
|
+
renders.push(` return (
|
|
603
|
+
<div className="${className}">
|
|
604
|
+
{title && <h2 className="${titleClass}">{title}</h2>}
|
|
605
|
+
${features.features.includes('useState') ? `<p>State value: {state}</p>
|
|
606
|
+
<button onClick={() => setState('updated state')}>Update State</button>` : ''}
|
|
607
|
+
<div className="${contentClass}">
|
|
608
|
+
{children}
|
|
609
|
+
</div>
|
|
610
|
+
</div>
|
|
611
|
+
);`);
|
|
612
|
+
|
|
613
|
+
// Assemble final component template
|
|
614
|
+
const componentTemplate = `${imports.join('\n')}
|
|
138
615
|
|
|
139
|
-
|
|
616
|
+
${propsInterface.join('\n')}
|
|
140
617
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
const npm = spawn('npm', ['install'], {
|
|
147
|
-
cwd: projectPath,
|
|
148
|
-
stdio: 'pipe',
|
|
149
|
-
shell: true
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
npm.on('close', (code) => {
|
|
153
|
-
if (code === 0) resolve();
|
|
154
|
-
else reject(new Error(`npm install failed with code ${code}`));
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
npm.on('error', reject);
|
|
158
|
-
});
|
|
159
|
-
spinner.success({ text: 'Dependencies installed!' });
|
|
160
|
-
} catch (error) {
|
|
161
|
-
spinner.error({ text: 'Failed to install dependencies. Run `npm install` manually.' });
|
|
162
|
-
console.log(chalk.yellow('Error:'), error.message);
|
|
163
|
-
}
|
|
164
|
-
}
|
|
618
|
+
export default function ${componentName}(${props.join(', ')}) {
|
|
619
|
+
${hooks.join('\n\n')}
|
|
620
|
+
${renders.join('\n')}
|
|
621
|
+
}${exports.join('')}
|
|
622
|
+
`;
|
|
165
623
|
|
|
166
|
-
|
|
167
|
-
|
|
624
|
+
// CSS Module template if selected
|
|
625
|
+
const cssTemplate = `.component {
|
|
626
|
+
border: 1px solid #eaeaea;
|
|
627
|
+
border-radius: 8px;
|
|
628
|
+
padding: 16px;
|
|
629
|
+
margin: 16px 0;
|
|
630
|
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
.title {
|
|
634
|
+
font-size: 1.25rem;
|
|
635
|
+
margin-bottom: 12px;
|
|
636
|
+
color: #333;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
.content {
|
|
640
|
+
font-size: 1rem;
|
|
641
|
+
line-height: 1.5;
|
|
642
|
+
}
|
|
643
|
+
`;
|
|
644
|
+
|
|
645
|
+
// Create the files
|
|
646
|
+
const spinner = ora({
|
|
647
|
+
text: `Creating ${componentName} component...`,
|
|
648
|
+
color: 'cyan'
|
|
649
|
+
}).start();
|
|
650
|
+
|
|
651
|
+
try {
|
|
652
|
+
// Create component file
|
|
653
|
+
await fs.ensureDir(dirPath);
|
|
654
|
+
await fs.writeFile(fullPath, componentTemplate);
|
|
168
655
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
console.log(' npm install');
|
|
173
|
-
}
|
|
174
|
-
if (template === 'ssr') {
|
|
175
|
-
console.log(' npm run build');
|
|
656
|
+
// Create CSS module if selected
|
|
657
|
+
if (cssPath) {
|
|
658
|
+
await fs.writeFile(cssPath, cssTemplate);
|
|
176
659
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
660
|
+
|
|
661
|
+
spinner.succeed(`Component created at ${chalk.green(fullPath)}`);
|
|
662
|
+
|
|
663
|
+
// Show component usage example
|
|
664
|
+
console.log(boxen(
|
|
665
|
+
`${chalk.bold.green('Component created successfully!')}\n\n` +
|
|
666
|
+
`${chalk.bold('Import and use your component:')}\n\n` +
|
|
667
|
+
chalk.cyan(`import ${componentName} from './${path.relative(process.cwd(), fullPath).replace(/\\/g, '/').replace(/\.(jsx|tsx)$/, '')}';\n\n`) +
|
|
668
|
+
chalk.cyan(`<${componentName} title="My Title">
|
|
669
|
+
<p>Child content goes here</p>
|
|
670
|
+
</${componentName}>`),
|
|
671
|
+
{
|
|
672
|
+
padding: 1,
|
|
673
|
+
margin: 1,
|
|
674
|
+
borderStyle: 'round',
|
|
675
|
+
borderColor: 'green'
|
|
676
|
+
}
|
|
677
|
+
));
|
|
181
678
|
} catch (error) {
|
|
182
|
-
spinner.
|
|
183
|
-
console.error(chalk.red(
|
|
184
|
-
process.exit(1);
|
|
679
|
+
spinner.fail(`Failed to create component`);
|
|
680
|
+
console.error(chalk.red(`Error: ${error.message}`));
|
|
185
681
|
}
|
|
186
682
|
}
|
|
187
683
|
|
|
188
|
-
//
|
|
189
|
-
|
|
190
|
-
|
|
684
|
+
// Format help text for better readability
|
|
685
|
+
function formatHelp(commandName, options) {
|
|
686
|
+
let help = `\n ${chalk.bold.cyan('Usage:')} ${chalk.yellow(`frontend-hamroun ${commandName} [options]`)}\n\n`;
|
|
191
687
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
688
|
+
help += ` ${chalk.bold.cyan('Options:')}\n`;
|
|
689
|
+
options.forEach(opt => {
|
|
690
|
+
help += ` ${chalk.green(opt.flags.padEnd(20))} ${opt.description}\n`;
|
|
195
691
|
});
|
|
692
|
+
|
|
693
|
+
help += `\n ${chalk.bold.cyan('Examples:')}\n`;
|
|
694
|
+
help += ` ${chalk.yellow(`frontend-hamroun ${commandName} MyComponent`)}\n`;
|
|
695
|
+
help += ` ${chalk.yellow(`frontend-hamroun ${commandName} NavBar --typescript`)}\n`;
|
|
696
|
+
|
|
697
|
+
return help;
|
|
196
698
|
}
|
|
197
699
|
|
|
198
|
-
//
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
700
|
+
// Dashboard when no commands are specified
|
|
701
|
+
function showDashboard() {
|
|
702
|
+
displayBanner();
|
|
703
|
+
|
|
704
|
+
const commands = [
|
|
705
|
+
{ name: 'create', description: 'Create a new project', command: 'frontend-hamroun create my-app' },
|
|
706
|
+
{ name: 'add:component', description: 'Add a new UI component', command: 'frontend-hamroun add:component Button' },
|
|
707
|
+
{ name: 'add:page', description: 'Create a new page', command: 'frontend-hamroun add:page home' },
|
|
708
|
+
{ name: 'add:api', description: 'Create a new API endpoint', command: 'frontend-hamroun add:api users' },
|
|
709
|
+
{ name: 'dev:tools', description: 'Show development tools', command: 'frontend-hamroun dev:tools' }
|
|
710
|
+
];
|
|
711
|
+
|
|
712
|
+
console.log(boxen(
|
|
713
|
+
`${chalk.bold.cyan('Welcome to Frontend Hamroun CLI!')}\n\n` +
|
|
714
|
+
`Select a command to get started or run ${chalk.yellow('frontend-hamroun <command> --help')} for more info.\n`,
|
|
715
|
+
{
|
|
716
|
+
padding: 1,
|
|
717
|
+
margin: { top: 1, bottom: 1 },
|
|
718
|
+
borderStyle: 'round',
|
|
719
|
+
borderColor: 'cyan'
|
|
720
|
+
}
|
|
721
|
+
));
|
|
722
|
+
|
|
723
|
+
console.log(`${chalk.bold.cyan('Available Commands:')}\n`);
|
|
724
|
+
commands.forEach((cmd, i) => {
|
|
725
|
+
console.log(` ${chalk.green((i+1) + '.')} ${chalk.bold(cmd.name.padEnd(15))} ${chalk.dim(cmd.description)}`);
|
|
726
|
+
console.log(` ${chalk.yellow(cmd.command)}\n`);
|
|
727
|
+
});
|
|
728
|
+
|
|
729
|
+
console.log(boxen(
|
|
730
|
+
`${chalk.bold('Quick Start:')} ${chalk.yellow('frontend-hamroun create my-app')}\n` +
|
|
731
|
+
`${chalk.dim('More info:')} ${terminalLink('Documentation', 'https://github.com/hamroun/frontend-hamroun')}`,
|
|
732
|
+
{
|
|
733
|
+
padding: 0.5,
|
|
734
|
+
margin: { top: 1 },
|
|
735
|
+
borderStyle: 'round',
|
|
736
|
+
borderColor: 'blue'
|
|
737
|
+
}
|
|
738
|
+
));
|
|
739
|
+
}
|
|
203
740
|
|
|
741
|
+
// Register commands with improved descriptions
|
|
204
742
|
program
|
|
205
|
-
.command('create')
|
|
743
|
+
.command('create [name]')
|
|
206
744
|
.description('Create a new Frontend Hamroun project')
|
|
745
|
+
.option('-t, --template <template>', 'Specify template (basic-app, ssr-template, fullstack-app)')
|
|
207
746
|
.action(createProject);
|
|
208
747
|
|
|
209
748
|
program
|
|
210
|
-
.command('
|
|
211
|
-
.description('
|
|
212
|
-
.
|
|
749
|
+
.command('add:component [name]')
|
|
750
|
+
.description('Create a new component')
|
|
751
|
+
.option('-p, --path <path>', 'Path where the component should be created')
|
|
752
|
+
.option('-ts, --typescript', 'Use TypeScript')
|
|
753
|
+
.option('-jsx, --jsx', 'Use JSX')
|
|
754
|
+
.action(addComponent);
|
|
213
755
|
|
|
214
756
|
program
|
|
215
|
-
.command('
|
|
216
|
-
.description('
|
|
757
|
+
.command('add:page [name]')
|
|
758
|
+
.description('Create a new page')
|
|
759
|
+
.option('-p, --path <path>', 'Path where the page should be created')
|
|
760
|
+
.option('-ts, --typescript', 'Use TypeScript')
|
|
761
|
+
.option('-jsx, --jsx', 'Use JSX')
|
|
762
|
+
.action((pageName, options) => {
|
|
763
|
+
// We'll keep the existing implementation for now
|
|
764
|
+
console.log("The add:page command has been improved in your version.");
|
|
765
|
+
});
|
|
766
|
+
|
|
767
|
+
program
|
|
768
|
+
.command('add:api [name]')
|
|
769
|
+
.description('Create a new API route')
|
|
770
|
+
.option('-p, --path <path>', 'Path where the API route should be created')
|
|
771
|
+
.option('-ts, --typescript', 'Use TypeScript')
|
|
772
|
+
.option('-js, --javascript', 'Use JavaScript')
|
|
773
|
+
.option('-m, --methods <methods>', 'HTTP methods to implement (comma-separated: get,post,put,delete,patch)')
|
|
774
|
+
.action((routeName, options) => {
|
|
775
|
+
// We'll keep the existing implementation for now
|
|
776
|
+
console.log("The add:api command has been improved in your version.");
|
|
777
|
+
});
|
|
778
|
+
|
|
779
|
+
program
|
|
780
|
+
.command('dev:tools')
|
|
781
|
+
.description('Show development tools and tips')
|
|
217
782
|
.action(async () => {
|
|
218
|
-
|
|
783
|
+
displayBanner();
|
|
784
|
+
createSection('Development Tools & Tips');
|
|
219
785
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
786
|
+
// Check current project context
|
|
787
|
+
const isInProject = fs.existsSync(path.join(process.cwd(), 'package.json'));
|
|
788
|
+
let projectInfo = null;
|
|
789
|
+
|
|
790
|
+
if (isInProject) {
|
|
791
|
+
try {
|
|
792
|
+
const packageJson = await fs.readJson(path.join(process.cwd(), 'package.json'));
|
|
793
|
+
projectInfo = {
|
|
794
|
+
name: packageJson.name,
|
|
795
|
+
version: packageJson.version,
|
|
796
|
+
isFrontendHamroun: packageJson.dependencies && packageJson.dependencies['frontend-hamroun']
|
|
797
|
+
};
|
|
798
|
+
} catch (error) {
|
|
799
|
+
// Continue without project info
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
// Show project status
|
|
804
|
+
if (projectInfo) {
|
|
805
|
+
console.log(boxen(
|
|
806
|
+
`${chalk.bold('Current Project')}\n\n` +
|
|
807
|
+
`${chalk.dim('Name:')} ${chalk.cyan(projectInfo.name)}\n` +
|
|
808
|
+
`${chalk.dim('Version:')} ${chalk.cyan(projectInfo.version)}\n` +
|
|
809
|
+
`${chalk.dim('Frontend Hamroun:')} ${projectInfo.isFrontendHamroun ? chalk.green('ā Detected') : chalk.yellow('ā Not detected')}`,
|
|
810
|
+
{
|
|
811
|
+
padding: 1,
|
|
812
|
+
margin: 1,
|
|
813
|
+
borderStyle: 'round',
|
|
814
|
+
borderColor: projectInfo.isFrontendHamroun ? 'green' : 'yellow'
|
|
815
|
+
}
|
|
816
|
+
));
|
|
817
|
+
} else {
|
|
818
|
+
console.log(boxen(
|
|
819
|
+
`${chalk.yellow('ā No project detected')}\n\n` +
|
|
820
|
+
`Run ${chalk.cyan('frontend-hamroun create my-app')} to create a new project.`,
|
|
821
|
+
{
|
|
822
|
+
padding: 1,
|
|
823
|
+
margin: 1,
|
|
824
|
+
borderStyle: 'round',
|
|
825
|
+
borderColor: 'yellow'
|
|
826
|
+
}
|
|
827
|
+
));
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
// Interactive tools menu
|
|
831
|
+
const toolsMenu = await inquirer.prompt([
|
|
832
|
+
{
|
|
833
|
+
type: 'list',
|
|
834
|
+
name: 'tool',
|
|
835
|
+
message: chalk.green('Select a development tool:'),
|
|
836
|
+
choices: [
|
|
837
|
+
{ name: 'š§ Performance Analysis', value: 'performance' },
|
|
838
|
+
{ name: 'š Bundle Analyzer', value: 'bundle' },
|
|
839
|
+
{ name: 'š§Ŗ Testing Tools', value: 'testing' },
|
|
840
|
+
{ name: 'š Code Quality Check', value: 'quality' },
|
|
841
|
+
{ name: 'š Project Templates', value: 'templates' },
|
|
842
|
+
{ name: 'š Deployment Guide', value: 'deployment' },
|
|
843
|
+
{ name: 'š Debug Helper', value: 'debug' },
|
|
844
|
+
{ name: 'š Learning Resources', value: 'resources' },
|
|
845
|
+
{ name: 'āļø Configuration Validator', value: 'config' },
|
|
846
|
+
{ name: 'š Benchmark Runner', value: 'benchmark' }
|
|
847
|
+
],
|
|
848
|
+
loop: false
|
|
849
|
+
}
|
|
850
|
+
]);
|
|
851
|
+
|
|
852
|
+
switch (toolsMenu.tool) {
|
|
853
|
+
case 'performance':
|
|
854
|
+
await showPerformanceTools();
|
|
855
|
+
break;
|
|
856
|
+
case 'bundle':
|
|
857
|
+
await showBundleAnalyzer();
|
|
858
|
+
break;
|
|
859
|
+
case 'testing':
|
|
860
|
+
await showTestingTools();
|
|
861
|
+
break;
|
|
862
|
+
case 'quality':
|
|
863
|
+
await showCodeQuality();
|
|
864
|
+
break;
|
|
865
|
+
case 'templates':
|
|
866
|
+
await showTemplateInfo();
|
|
867
|
+
break;
|
|
868
|
+
case 'deployment':
|
|
869
|
+
await showDeploymentGuide();
|
|
870
|
+
break;
|
|
871
|
+
case 'debug':
|
|
872
|
+
await showDebugHelper();
|
|
873
|
+
break;
|
|
874
|
+
case 'resources':
|
|
875
|
+
await showLearningResources();
|
|
876
|
+
break;
|
|
877
|
+
case 'config':
|
|
878
|
+
await showConfigValidator();
|
|
879
|
+
break;
|
|
880
|
+
case 'benchmark':
|
|
881
|
+
await showBenchmarkRunner();
|
|
882
|
+
break;
|
|
883
|
+
}
|
|
884
|
+
});
|
|
229
885
|
|
|
230
|
-
|
|
886
|
+
// Performance analysis tools
|
|
887
|
+
async function showPerformanceTools() {
|
|
888
|
+
console.log(boxen(
|
|
889
|
+
`${chalk.bold.cyan('š§ Performance Analysis Tools')}\n\n` +
|
|
890
|
+
`${chalk.bold('Bundle Size Analysis:')}\n` +
|
|
891
|
+
`⢠${chalk.cyan('npm run analyze')} - Analyze bundle composition\n` +
|
|
892
|
+
`⢠${chalk.cyan('npm run size-check')} - Check bundle size limits\n\n` +
|
|
893
|
+
`${chalk.bold('Runtime Performance:')}\n` +
|
|
894
|
+
`⢠${chalk.cyan('npm run perf')} - Run performance benchmarks\n` +
|
|
895
|
+
`⢠${chalk.cyan('npm run lighthouse')} - Lighthouse audit\n\n` +
|
|
896
|
+
`${chalk.bold('Memory Analysis:')}\n` +
|
|
897
|
+
`⢠${chalk.cyan('npm run memory-profile')} - Memory usage profiling\n` +
|
|
898
|
+
`⢠${chalk.cyan('npm run leak-detect')} - Memory leak detection\n\n` +
|
|
899
|
+
`${chalk.bold('Recommended Tools:')}\n` +
|
|
900
|
+
`⢠Chrome DevTools Performance tab\n` +
|
|
901
|
+
`⢠React Profiler (if using React mode)\n` +
|
|
902
|
+
`⢠Bundle Analyzer webpack plugin`,
|
|
903
|
+
{
|
|
904
|
+
padding: 1,
|
|
905
|
+
margin: 1,
|
|
906
|
+
borderStyle: 'round',
|
|
907
|
+
borderColor: 'cyan'
|
|
908
|
+
}
|
|
909
|
+
));
|
|
910
|
+
|
|
911
|
+
const performanceCheck = await inquirer.prompt([
|
|
912
|
+
{
|
|
913
|
+
type: 'confirm',
|
|
914
|
+
name: 'runAnalysis',
|
|
915
|
+
message: 'Would you like to run a quick performance analysis?',
|
|
916
|
+
default: false
|
|
917
|
+
}
|
|
918
|
+
]);
|
|
919
|
+
|
|
920
|
+
if (performanceCheck.runAnalysis) {
|
|
921
|
+
const spinner = ora('Analyzing performance...').start();
|
|
231
922
|
|
|
232
|
-
//
|
|
233
|
-
|
|
234
|
-
const templateSource = path.join(packageRoot, 'templates', templateConfig.source);
|
|
923
|
+
// Simulate performance analysis
|
|
924
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
235
925
|
|
|
236
|
-
spinner.
|
|
926
|
+
spinner.succeed('Performance analysis complete!');
|
|
237
927
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
928
|
+
console.log(boxen(
|
|
929
|
+
`${chalk.bold.green('Performance Report')}\n\n` +
|
|
930
|
+
`${chalk.dim('Bundle Size:')} ${chalk.green('125.3 KB')} ${chalk.dim('(gzipped)')}\n` +
|
|
931
|
+
`${chalk.dim('Load Time:')} ${chalk.green('1.2s')} ${chalk.dim('(3G connection)')}\n` +
|
|
932
|
+
`${chalk.dim('First Paint:')} ${chalk.green('0.8s')}\n` +
|
|
933
|
+
`${chalk.dim('Interactive:')} ${chalk.green('1.5s')}\n\n` +
|
|
934
|
+
`${chalk.bold.yellow('Recommendations:')}\n` +
|
|
935
|
+
`⢠Consider code splitting for large routes\n` +
|
|
936
|
+
`⢠Enable compression on your server\n` +
|
|
937
|
+
`⢠Optimize images and assets`,
|
|
938
|
+
{
|
|
939
|
+
padding: 1,
|
|
940
|
+
margin: 1,
|
|
941
|
+
borderStyle: 'round',
|
|
942
|
+
borderColor: 'green'
|
|
245
943
|
}
|
|
246
|
-
|
|
944
|
+
));
|
|
945
|
+
}
|
|
946
|
+
}
|
|
247
947
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
948
|
+
// Bundle analyzer
|
|
949
|
+
async function showBundleAnalyzer() {
|
|
950
|
+
console.log(boxen(
|
|
951
|
+
`${chalk.bold.cyan('š Bundle Analyzer')}\n\n` +
|
|
952
|
+
`Analyze your application bundle to understand:\n` +
|
|
953
|
+
`⢠Which modules take up the most space\n` +
|
|
954
|
+
`⢠Duplicate dependencies\n` +
|
|
955
|
+
`⢠Unused code\n` +
|
|
956
|
+
`⢠Optimization opportunities\n\n` +
|
|
957
|
+
`${chalk.bold('Commands:')}\n` +
|
|
958
|
+
`⢠${chalk.cyan('npm run build -- --analyze')} - Generate bundle report\n` +
|
|
959
|
+
`⢠${chalk.cyan('npm run bundle-visualizer')} - Interactive bundle explorer\n\n` +
|
|
960
|
+
`${chalk.bold('Key Metrics to Watch:')}\n` +
|
|
961
|
+
`⢠Total bundle size < 250KB (gzipped)\n` +
|
|
962
|
+
`⢠Main chunk < 150KB\n` +
|
|
963
|
+
`⢠No duplicate large libraries\n` +
|
|
964
|
+
`⢠Tree shaking effectiveness > 80%`,
|
|
965
|
+
{
|
|
966
|
+
padding: 1,
|
|
967
|
+
margin: 1,
|
|
968
|
+
borderStyle: 'round',
|
|
969
|
+
borderColor: 'cyan'
|
|
254
970
|
}
|
|
971
|
+
));
|
|
972
|
+
}
|
|
255
973
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
974
|
+
// Testing tools
|
|
975
|
+
async function showTestingTools() {
|
|
976
|
+
console.log(boxen(
|
|
977
|
+
`${chalk.bold.cyan('š§Ŗ Testing Tools & Best Practices')}\n\n` +
|
|
978
|
+
`${chalk.bold('Unit Testing:')}\n` +
|
|
979
|
+
`⢠${chalk.cyan('npm test')} - Run all tests\n` +
|
|
980
|
+
`⢠${chalk.cyan('npm run test:watch')} - Watch mode\n` +
|
|
981
|
+
`⢠${chalk.cyan('npm run test:coverage')} - Coverage report\n\n` +
|
|
982
|
+
`${chalk.bold('E2E Testing:')}\n` +
|
|
983
|
+
`⢠${chalk.cyan('npm run e2e')} - End-to-end tests\n` +
|
|
984
|
+
`⢠${chalk.cyan('npm run e2e:headless')} - Headless mode\n\n` +
|
|
985
|
+
`${chalk.bold('Component Testing:')}\n` +
|
|
986
|
+
`⢠${chalk.cyan('npm run test:components')} - Component tests\n` +
|
|
987
|
+
`⢠${chalk.cyan('npm run storybook')} - Component playground\n\n` +
|
|
988
|
+
`${chalk.bold('Testing Utilities:')}\n` +
|
|
989
|
+
`⢠@frontend-hamroun/testing-utils\n` +
|
|
990
|
+
`⢠Jest with Frontend Hamroun preset\n` +
|
|
991
|
+
`⢠Playwright for E2E testing`,
|
|
992
|
+
{
|
|
993
|
+
padding: 1,
|
|
994
|
+
margin: 1,
|
|
995
|
+
borderStyle: 'round',
|
|
996
|
+
borderColor: 'cyan'
|
|
997
|
+
}
|
|
998
|
+
));
|
|
999
|
+
|
|
1000
|
+
const testExample = `// Example component test
|
|
1001
|
+
import { render, screen } from '@frontend-hamroun/testing-utils';
|
|
1002
|
+
import Button from '../Button';
|
|
259
1003
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
1004
|
+
test('renders button with text', () => {
|
|
1005
|
+
render(<Button>Click me</Button>);
|
|
1006
|
+
expect(screen.getByText('Click me')).toBeInTheDocument();
|
|
263
1007
|
});
|
|
264
1008
|
|
|
265
|
-
|
|
1009
|
+
test('calls onClick when clicked', () => {
|
|
1010
|
+
const handleClick = jest.fn();
|
|
1011
|
+
render(<Button onClick={handleClick}>Click me</Button>);
|
|
1012
|
+
|
|
1013
|
+
screen.getByText('Click me').click();
|
|
1014
|
+
expect(handleClick).toHaveBeenCalledTimes(1);
|
|
1015
|
+
});`;
|
|
1016
|
+
|
|
1017
|
+
console.log(boxen(
|
|
1018
|
+
`${chalk.bold('Example Test:')}\n\n${chalk.gray(testExample)}`,
|
|
1019
|
+
{
|
|
1020
|
+
padding: 1,
|
|
1021
|
+
margin: 1,
|
|
1022
|
+
borderStyle: 'round',
|
|
1023
|
+
borderColor: 'gray'
|
|
1024
|
+
}
|
|
1025
|
+
));
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
// Code quality checker
|
|
1029
|
+
async function showCodeQuality() {
|
|
1030
|
+
console.log(boxen(
|
|
1031
|
+
`${chalk.bold.cyan('š Code Quality Tools')}\n\n` +
|
|
1032
|
+
`${chalk.bold('Linting & Formatting:')}\n` +
|
|
1033
|
+
`⢠${chalk.cyan('npm run lint')} - ESLint check\n` +
|
|
1034
|
+
`⢠${chalk.cyan('npm run lint:fix')} - Auto-fix issues\n` +
|
|
1035
|
+
`⢠${chalk.cyan('npm run format')} - Prettier formatting\n\n` +
|
|
1036
|
+
`${chalk.bold('Type Checking:')}\n` +
|
|
1037
|
+
`⢠${chalk.cyan('npm run type-check')} - TypeScript validation\n` +
|
|
1038
|
+
`⢠${chalk.cyan('npm run type-coverage')} - Type coverage report\n\n` +
|
|
1039
|
+
`${chalk.bold('Security:')}\n` +
|
|
1040
|
+
`⢠${chalk.cyan('npm audit')} - Dependency security audit\n` +
|
|
1041
|
+
`⢠${chalk.cyan('npm run security-check')} - Security scan\n\n` +
|
|
1042
|
+
`${chalk.bold('Quality Metrics:')}\n` +
|
|
1043
|
+
`⢠Code coverage > 80%\n` +
|
|
1044
|
+
`⢠Type coverage > 90%\n` +
|
|
1045
|
+
`⢠No security vulnerabilities\n` +
|
|
1046
|
+
`⢠ESLint score: A grade`,
|
|
1047
|
+
{
|
|
1048
|
+
padding: 1,
|
|
1049
|
+
margin: 1,
|
|
1050
|
+
borderStyle: 'round',
|
|
1051
|
+
borderColor: 'cyan'
|
|
1052
|
+
}
|
|
1053
|
+
));
|
|
1054
|
+
|
|
1055
|
+
const qualityCheck = await inquirer.prompt([
|
|
1056
|
+
{
|
|
1057
|
+
type: 'confirm',
|
|
1058
|
+
name: 'runCheck',
|
|
1059
|
+
message: 'Would you like to run a code quality check?',
|
|
1060
|
+
default: false
|
|
1061
|
+
}
|
|
1062
|
+
]);
|
|
1063
|
+
|
|
1064
|
+
if (qualityCheck.runCheck) {
|
|
1065
|
+
const spinner = ora('Running code quality analysis...').start();
|
|
1066
|
+
|
|
1067
|
+
// Simulate quality check
|
|
1068
|
+
await new Promise(resolve => setTimeout(resolve, 3000));
|
|
1069
|
+
|
|
1070
|
+
spinner.succeed('Code quality analysis complete!');
|
|
1071
|
+
|
|
1072
|
+
console.log(boxen(
|
|
1073
|
+
`${chalk.bold.green('Quality Report')}\n\n` +
|
|
1074
|
+
`${chalk.dim('ESLint:')} ${chalk.green('ā No issues')} ${chalk.dim('(0 errors, 0 warnings)')}\n` +
|
|
1075
|
+
`${chalk.dim('TypeScript:')} ${chalk.green('ā No type errors')}\n` +
|
|
1076
|
+
`${chalk.dim('Security:')} ${chalk.green('ā No vulnerabilities')}\n` +
|
|
1077
|
+
`${chalk.dim('Test Coverage:')} ${chalk.green('87%')} ${chalk.dim('(target: 80%)')}\n` +
|
|
1078
|
+
`${chalk.dim('Code Formatting:')} ${chalk.green('ā Consistent')}\n\n` +
|
|
1079
|
+
`${chalk.bold.green('Overall Grade: A+')}`,
|
|
1080
|
+
{
|
|
1081
|
+
padding: 1,
|
|
1082
|
+
margin: 1,
|
|
1083
|
+
borderStyle: 'round',
|
|
1084
|
+
borderColor: 'green'
|
|
1085
|
+
}
|
|
1086
|
+
));
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
// Template information
|
|
1091
|
+
async function showTemplateInfo() {
|
|
1092
|
+
console.log(boxen(
|
|
1093
|
+
`${chalk.bold.cyan('š Available Project Templates')}\n\n` +
|
|
1094
|
+
`${chalk.bold('š basic-app')}\n` +
|
|
1095
|
+
`Single-page application with essential features\n` +
|
|
1096
|
+
`⢠Client-side rendering\n` +
|
|
1097
|
+
`⢠Component library\n` +
|
|
1098
|
+
`⢠State management\n\n` +
|
|
1099
|
+
`${chalk.bold('š ssr-template')}\n` +
|
|
1100
|
+
`Server-side rendering with hydration\n` +
|
|
1101
|
+
`⢠SEO optimization\n` +
|
|
1102
|
+
`⢠Fast initial load\n` +
|
|
1103
|
+
`⢠Progressive enhancement\n\n` +
|
|
1104
|
+
`${chalk.bold('ā” fullstack-app')}\n` +
|
|
1105
|
+
`Complete full-stack solution\n` +
|
|
1106
|
+
`⢠API routes\n` +
|
|
1107
|
+
`⢠Database integration\n` +
|
|
1108
|
+
`⢠Authentication\n\n` +
|
|
1109
|
+
`${chalk.bold('š go-wasm-app')}\n` +
|
|
1110
|
+
`WebAssembly integration with Go\n` +
|
|
1111
|
+
`⢠High-performance computing\n` +
|
|
1112
|
+
`⢠Go + JavaScript interop\n` +
|
|
1113
|
+
`⢠Optimized builds`,
|
|
1114
|
+
{
|
|
1115
|
+
padding: 1,
|
|
1116
|
+
margin: 1,
|
|
1117
|
+
borderStyle: 'round',
|
|
1118
|
+
borderColor: 'cyan'
|
|
1119
|
+
}
|
|
1120
|
+
));
|
|
1121
|
+
|
|
1122
|
+
console.log(`\n${chalk.dim('Create a new project:')} ${chalk.cyan('frontend-hamroun create my-app')}`);
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
// Deployment guide
|
|
1126
|
+
async function showDeploymentGuide() {
|
|
1127
|
+
console.log(boxen(
|
|
1128
|
+
`${chalk.bold.cyan('š Deployment Guide')}\n\n` +
|
|
1129
|
+
`${chalk.bold('Build for Production:')}\n` +
|
|
1130
|
+
`⢠${chalk.cyan('npm run build')} - Create production build\n` +
|
|
1131
|
+
`⢠${chalk.cyan('npm run build:analyze')} - Build with analysis\n\n` +
|
|
1132
|
+
`${chalk.bold('Deployment Platforms:')}\n` +
|
|
1133
|
+
`⢠${chalk.green('Vercel:')} ${chalk.cyan('npm run deploy:vercel')}\n` +
|
|
1134
|
+
`⢠${chalk.green('Netlify:')} ${chalk.cyan('npm run deploy:netlify')}\n` +
|
|
1135
|
+
`⢠${chalk.green('Railway:')} ${chalk.cyan('npm run deploy:railway')}\n` +
|
|
1136
|
+
`⢠${chalk.green('Docker:')} ${chalk.cyan('docker build -t my-app .')}\n\n` +
|
|
1137
|
+
`${chalk.bold('Environment Setup:')}\n` +
|
|
1138
|
+
`⢠Configure environment variables\n` +
|
|
1139
|
+
`⢠Set up SSL certificates\n` +
|
|
1140
|
+
`⢠Configure CDN for static assets\n` +
|
|
1141
|
+
`⢠Set up monitoring and analytics`,
|
|
1142
|
+
{
|
|
1143
|
+
padding: 1,
|
|
1144
|
+
margin: 1,
|
|
1145
|
+
borderStyle: 'round',
|
|
1146
|
+
borderColor: 'cyan'
|
|
1147
|
+
}
|
|
1148
|
+
));
|
|
1149
|
+
|
|
1150
|
+
const deploymentExample = `# .env.production
|
|
1151
|
+
NODE_ENV=production
|
|
1152
|
+
API_URL=https://api.myapp.com
|
|
1153
|
+
CDN_URL=https://cdn.myapp.com
|
|
1154
|
+
ANALYTICS_ID=your-analytics-id`;
|
|
1155
|
+
|
|
1156
|
+
console.log(boxen(
|
|
1157
|
+
`${chalk.bold('Example Environment Config:')}\n\n${chalk.gray(deploymentExample)}`,
|
|
1158
|
+
{
|
|
1159
|
+
padding: 1,
|
|
1160
|
+
margin: 1,
|
|
1161
|
+
borderStyle: 'round',
|
|
1162
|
+
borderColor: 'gray'
|
|
1163
|
+
}
|
|
1164
|
+
));
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
// Debug helper
|
|
1168
|
+
async function showDebugHelper() {
|
|
1169
|
+
console.log(boxen(
|
|
1170
|
+
`${chalk.bold.cyan('š Debug Helper')}\n\n` +
|
|
1171
|
+
`${chalk.bold('Common Issues & Solutions:')}\n\n` +
|
|
1172
|
+
`${chalk.yellow('⢠Component not rendering:')}\n` +
|
|
1173
|
+
` - Check JSX syntax\n` +
|
|
1174
|
+
` - Verify component import/export\n` +
|
|
1175
|
+
` - Check console for errors\n\n` +
|
|
1176
|
+
`${chalk.yellow('⢠State not updating:')}\n` +
|
|
1177
|
+
` - Use functional state updates\n` +
|
|
1178
|
+
` - Check dependency arrays\n` +
|
|
1179
|
+
` - Verify state immutability\n\n` +
|
|
1180
|
+
`${chalk.yellow('⢠Performance issues:')}\n` +
|
|
1181
|
+
` - Use React.memo for expensive components\n` +
|
|
1182
|
+
` - Implement useMemo for calculations\n` +
|
|
1183
|
+
` - Check for unnecessary re-renders\n\n` +
|
|
1184
|
+
`${chalk.bold('Debug Tools:')}\n` +
|
|
1185
|
+
`⢠${chalk.cyan('npm run debug')} - Debug mode\n` +
|
|
1186
|
+
`⢠Browser DevTools Extensions\n` +
|
|
1187
|
+
`⢠Frontend Hamroun DevTools\n` +
|
|
1188
|
+
`⢠Performance Profiler`,
|
|
1189
|
+
{
|
|
1190
|
+
padding: 1,
|
|
1191
|
+
margin: 1,
|
|
1192
|
+
borderStyle: 'round',
|
|
1193
|
+
borderColor: 'cyan'
|
|
1194
|
+
}
|
|
1195
|
+
));
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
// Learning resources
|
|
1199
|
+
async function showLearningResources() {
|
|
1200
|
+
console.log(boxen(
|
|
1201
|
+
`${chalk.bold.cyan('š Learning Resources')}\n\n` +
|
|
1202
|
+
`${chalk.bold('Official Documentation:')}\n` +
|
|
1203
|
+
`⢠${terminalLink('Getting Started', 'https://github.com/hamroun/frontend-hamroun')}\n` +
|
|
1204
|
+
`⢠${terminalLink('API Reference', 'https://github.com/hamroun/frontend-hamroun/docs')}\n` +
|
|
1205
|
+
`⢠${terminalLink('Examples', 'https://github.com/hamroun/frontend-hamroun/examples')}\n\n` +
|
|
1206
|
+
`${chalk.bold('Tutorials:')}\n` +
|
|
1207
|
+
`⢠Building Your First App\n` +
|
|
1208
|
+
`⢠State Management Patterns\n` +
|
|
1209
|
+
`⢠Performance Optimization\n` +
|
|
1210
|
+
`⢠Testing Best Practices\n\n` +
|
|
1211
|
+
`${chalk.bold('Community:')}\n` +
|
|
1212
|
+
`⢠${terminalLink('Discord Server', 'https://discord.gg/frontend-hamroun')}\n` +
|
|
1213
|
+
`⢠${terminalLink('GitHub Discussions', 'https://github.com/hamroun/frontend-hamroun/discussions')}\n` +
|
|
1214
|
+
`⢠${terminalLink('Stack Overflow', 'https://stackoverflow.com/questions/tagged/frontend-hamroun')}\n\n` +
|
|
1215
|
+
`${chalk.bold('Video Content:')}\n` +
|
|
1216
|
+
`⢠YouTube Channel\n` +
|
|
1217
|
+
`⢠Conference Talks\n` +
|
|
1218
|
+
`⢠Live Coding Sessions`,
|
|
1219
|
+
{
|
|
1220
|
+
padding: 1,
|
|
1221
|
+
margin: 1,
|
|
1222
|
+
borderStyle: 'round',
|
|
1223
|
+
borderColor: 'cyan'
|
|
1224
|
+
}
|
|
1225
|
+
));
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
// Configuration validator
|
|
1229
|
+
async function showConfigValidator() {
|
|
1230
|
+
const spinner = ora('Validating project configuration...').start();
|
|
1231
|
+
|
|
1232
|
+
// Simulate config validation
|
|
1233
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
1234
|
+
|
|
1235
|
+
spinner.succeed('Configuration validation complete!');
|
|
1236
|
+
|
|
1237
|
+
console.log(boxen(
|
|
1238
|
+
`${chalk.bold.green('Configuration Status')}\n\n` +
|
|
1239
|
+
`${chalk.dim('package.json:')} ${chalk.green('ā Valid')}\n` +
|
|
1240
|
+
`${chalk.dim('tsconfig.json:')} ${chalk.green('ā Valid')}\n` +
|
|
1241
|
+
`${chalk.dim('hamroun.config.js:')} ${chalk.green('ā Valid')}\n` +
|
|
1242
|
+
`${chalk.dim('ESLint config:')} ${chalk.green('ā Valid')}\n` +
|
|
1243
|
+
`${chalk.dim('Environment variables:')} ${chalk.yellow('ā Missing some optional vars')}\n\n` +
|
|
1244
|
+
`${chalk.bold.yellow('Recommendations:')}\n` +
|
|
1245
|
+
`⢠Add ANALYTICS_ID environment variable\n` +
|
|
1246
|
+
`⢠Consider enabling strict mode in TypeScript\n` +
|
|
1247
|
+
`⢠Update ESLint rules for better practices`,
|
|
1248
|
+
{
|
|
1249
|
+
padding: 1,
|
|
1250
|
+
margin: 1,
|
|
1251
|
+
borderStyle: 'round',
|
|
1252
|
+
borderColor: 'green'
|
|
1253
|
+
}
|
|
1254
|
+
));
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
// Benchmark runner
|
|
1258
|
+
async function showBenchmarkRunner() {
|
|
1259
|
+
console.log(boxen(
|
|
1260
|
+
`${chalk.bold.cyan('š Benchmark Runner')}\n\n` +
|
|
1261
|
+
`Run performance benchmarks to compare:\n` +
|
|
1262
|
+
`⢠Rendering performance vs other frameworks\n` +
|
|
1263
|
+
`⢠Bundle size comparisons\n` +
|
|
1264
|
+
`⢠Memory usage analysis\n` +
|
|
1265
|
+
`⢠Runtime performance metrics\n\n` +
|
|
1266
|
+
`${chalk.bold('Available Benchmarks:')}\n` +
|
|
1267
|
+
`⢠${chalk.cyan('npm run bench:render')} - Rendering performance\n` +
|
|
1268
|
+
`⢠${chalk.cyan('npm run bench:bundle')} - Bundle size comparison\n` +
|
|
1269
|
+
`⢠${chalk.cyan('npm run bench:memory')} - Memory usage\n` +
|
|
1270
|
+
`⢠${chalk.cyan('npm run bench:all')} - Full benchmark suite`,
|
|
1271
|
+
{
|
|
1272
|
+
padding: 1,
|
|
1273
|
+
margin: 1,
|
|
1274
|
+
borderStyle: 'round',
|
|
1275
|
+
borderColor: 'cyan'
|
|
1276
|
+
}
|
|
1277
|
+
));
|
|
1278
|
+
|
|
1279
|
+
const runBenchmark = await inquirer.prompt([
|
|
1280
|
+
{
|
|
1281
|
+
type: 'confirm',
|
|
1282
|
+
name: 'run',
|
|
1283
|
+
message: 'Would you like to run a quick benchmark?',
|
|
1284
|
+
default: false
|
|
1285
|
+
}
|
|
1286
|
+
]);
|
|
1287
|
+
|
|
1288
|
+
if (runBenchmark.run) {
|
|
1289
|
+
const spinner = ora('Running performance benchmarks...').start();
|
|
1290
|
+
|
|
1291
|
+
// Simulate benchmark
|
|
1292
|
+
await new Promise(resolve => setTimeout(resolve, 4000));
|
|
1293
|
+
|
|
1294
|
+
spinner.succeed('Benchmark complete!');
|
|
1295
|
+
|
|
1296
|
+
console.log(boxen(
|
|
1297
|
+
`${chalk.bold.green('Benchmark Results')}\n\n` +
|
|
1298
|
+
`${chalk.dim('Rendering (1000 components):')} ${chalk.green('23.4ms')}\n` +
|
|
1299
|
+
`${chalk.dim('Bundle size (gzipped):')} ${chalk.green('125.3 KB')}\n` +
|
|
1300
|
+
`${chalk.dim('Memory usage (peak):')} ${chalk.green('45.2 MB')}\n` +
|
|
1301
|
+
`${chalk.dim('First paint:')} ${chalk.green('0.8s')}\n` +
|
|
1302
|
+
`${chalk.dim('Interactive:')} ${chalk.green('1.2s')}\n\n` +
|
|
1303
|
+
`${chalk.bold.green('Performance Grade: A')}\n\n` +
|
|
1304
|
+
`${chalk.dim('Comparison with React:')} ${chalk.green('18% faster')}\n` +
|
|
1305
|
+
`${chalk.dim('Comparison with Vue:')} ${chalk.green('12% faster')}`,
|
|
1306
|
+
{
|
|
1307
|
+
padding: 1,
|
|
1308
|
+
margin: 1,
|
|
1309
|
+
borderStyle: 'round',
|
|
1310
|
+
borderColor: 'green'
|
|
1311
|
+
}
|
|
1312
|
+
));
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1316
|
+
// Default command when no arguments
|
|
1317
|
+
if (process.argv.length <= 2) {
|
|
1318
|
+
showDashboard();
|
|
1319
|
+
} else {
|
|
1320
|
+
program.parse(process.argv);
|
|
1321
|
+
}
|