@flexireact/core 1.0.0

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/cli/index.ts ADDED
@@ -0,0 +1,1129 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * FlexiReact CLI v2.1
5
+ * Professional CLI with TypeScript, colors, prompts, and progress indicators
6
+ */
7
+
8
+ import fs from 'fs';
9
+ import path from 'path';
10
+ import { fileURLToPath, pathToFileURL } from 'url';
11
+ import { execSync, spawn } from 'child_process';
12
+ import pc from 'picocolors';
13
+ import prompts from 'prompts';
14
+ import ora from 'ora';
15
+
16
+ const __filename = fileURLToPath(import.meta.url);
17
+ const __dirname = path.dirname(__filename);
18
+ const VERSION = '2.1.0';
19
+
20
+ // ============================================================================
21
+ // ASCII Logo & Branding
22
+ // ============================================================================
23
+
24
+ const LOGO = `
25
+ ${pc.cyan('╔═══════════════════════════════════════════════════════════╗')}
26
+ ${pc.cyan('║')} ${pc.cyan('║')}
27
+ ${pc.cyan('║')} ${pc.bold(pc.magenta('⚡'))} ${pc.bold(pc.white('F L E X I R E A C T'))} ${pc.cyan('║')}
28
+ ${pc.cyan('║')} ${pc.cyan('║')}
29
+ ${pc.cyan('║')} ${pc.dim('The Modern React Framework')} ${pc.cyan('║')}
30
+ ${pc.cyan('║')} ${pc.dim('TypeScript • Tailwind • SSR • Islands')} ${pc.cyan('║')}
31
+ ${pc.cyan('║')} ${pc.cyan('║')}
32
+ ${pc.cyan('╚═══════════════════════════════════════════════════════════╝')}
33
+ `;
34
+
35
+ const MINI_LOGO = `${pc.magenta('⚡')} ${pc.bold('FlexiReact')}`;
36
+
37
+ // ============================================================================
38
+ // Logger Utilities
39
+ // ============================================================================
40
+
41
+ const log = {
42
+ info: (msg: string) => console.log(`${pc.cyan('ℹ')} ${msg}`),
43
+ success: (msg: string) => console.log(`${pc.green('✓')} ${msg}`),
44
+ warn: (msg: string) => console.log(`${pc.yellow('⚠')} ${pc.yellow(msg)}`),
45
+ error: (msg: string) => console.log(`${pc.red('✗')} ${pc.red(msg)}`),
46
+ step: (num: number, total: number, msg: string) =>
47
+ console.log(`${pc.dim(`[${num}/${total}]`)} ${msg}`),
48
+ blank: () => console.log(''),
49
+ divider: () => console.log(pc.dim('─'.repeat(60))),
50
+ };
51
+
52
+ // ============================================================================
53
+ // Helper Functions
54
+ // ============================================================================
55
+
56
+ function copyDirectory(src: string, dest: string): void {
57
+ if (!fs.existsSync(dest)) {
58
+ fs.mkdirSync(dest, { recursive: true });
59
+ }
60
+
61
+ const entries = fs.readdirSync(src, { withFileTypes: true });
62
+
63
+ for (const entry of entries) {
64
+ const srcPath = path.join(src, entry.name);
65
+ const destPath = path.join(dest, entry.name);
66
+
67
+ if (entry.isDirectory()) {
68
+ copyDirectory(srcPath, destPath);
69
+ } else {
70
+ fs.copyFileSync(srcPath, destPath);
71
+ }
72
+ }
73
+ }
74
+
75
+ async function runCommand(cmd: string, cwd: string): Promise<void> {
76
+ return new Promise((resolve, reject) => {
77
+ const child = spawn(cmd, {
78
+ shell: true,
79
+ cwd,
80
+ stdio: 'pipe'
81
+ });
82
+
83
+ child.on('close', (code) => {
84
+ if (code === 0) resolve();
85
+ else reject(new Error(`Command failed with code ${code}`));
86
+ });
87
+
88
+ child.on('error', reject);
89
+ });
90
+ }
91
+
92
+ // ============================================================================
93
+ // Create Command
94
+ // ============================================================================
95
+
96
+ interface CreateOptions {
97
+ template: 'default' | 'minimal' | 'flexi-ui';
98
+ typescript: boolean;
99
+ tailwind: boolean;
100
+ shadcn: boolean;
101
+ }
102
+
103
+ async function createProject(projectName?: string): Promise<void> {
104
+ console.log(LOGO);
105
+ log.blank();
106
+
107
+ // Get project name
108
+ let name = projectName;
109
+ if (!name) {
110
+ const response = await prompts({
111
+ type: 'text',
112
+ name: 'projectName',
113
+ message: 'Project name:',
114
+ initial: 'my-flexi-app',
115
+ validate: (value) => value.length > 0 || 'Project name is required'
116
+ });
117
+ name = response.projectName;
118
+ if (!name) process.exit(1);
119
+ }
120
+
121
+ const projectPath = path.resolve(process.cwd(), name);
122
+
123
+ // Check if directory exists
124
+ if (fs.existsSync(projectPath)) {
125
+ log.error(`Directory "${name}" already exists.`);
126
+ process.exit(1);
127
+ }
128
+
129
+ // Get options
130
+ const options = await prompts([
131
+ {
132
+ type: 'select',
133
+ name: 'template',
134
+ message: 'Select a template:',
135
+ choices: [
136
+ { title: '🚀 Default (Tailwind + shadcn/ui)', value: 'default' },
137
+ { title: '💚 FlexiUI (Landing page + @flexireact/flexi-ui)', value: 'flexi-ui' },
138
+ { title: '📦 Minimal (Clean slate)', value: 'minimal' }
139
+ ],
140
+ initial: 0
141
+ },
142
+ {
143
+ type: 'toggle',
144
+ name: 'typescript',
145
+ message: 'Use TypeScript?',
146
+ initial: true,
147
+ active: 'Yes',
148
+ inactive: 'No'
149
+ }
150
+ ]);
151
+
152
+ if (options.template === undefined) process.exit(1);
153
+
154
+ log.blank();
155
+ log.divider();
156
+ log.blank();
157
+
158
+ const totalSteps = options.template === 'default' ? 6 : (options.template === 'flexi-ui' ? 5 : 4);
159
+ let currentStep = 0;
160
+
161
+ // Step 1: Create directory
162
+ currentStep++;
163
+ log.step(currentStep, totalSteps, 'Creating project directory...');
164
+ fs.mkdirSync(projectPath, { recursive: true });
165
+ log.success(`Created ${pc.cyan(name)}/`);
166
+
167
+ // Step 2: Copy template
168
+ currentStep++;
169
+ log.step(currentStep, totalSteps, 'Setting up project structure...');
170
+ const templateName = options.template;
171
+ const templatePath = path.resolve(__dirname, '..', 'templates', templateName);
172
+
173
+ if (fs.existsSync(templatePath)) {
174
+ copyDirectory(templatePath, projectPath);
175
+ log.success('Project structure created');
176
+ } else {
177
+ // Create basic structure if template doesn't exist
178
+ await createDefaultTemplate(projectPath, name, options.typescript);
179
+ log.success('Project structure created');
180
+ }
181
+
182
+ // Step 3: Update package.json
183
+ currentStep++;
184
+ log.step(currentStep, totalSteps, 'Configuring project...');
185
+ const packageJsonPath = path.join(projectPath, 'package.json');
186
+ if (fs.existsSync(packageJsonPath)) {
187
+ const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
188
+ pkg.name = name;
189
+ fs.writeFileSync(packageJsonPath, JSON.stringify(pkg, null, 2));
190
+ }
191
+ log.success('Project configured');
192
+
193
+ // Step 4: Install dependencies
194
+ currentStep++;
195
+ log.step(currentStep, totalSteps, 'Installing dependencies...');
196
+ const spinner = ora({ text: 'Installing packages...', color: 'cyan' }).start();
197
+
198
+ try {
199
+ await runCommand('npm install', projectPath);
200
+ spinner.succeed('Dependencies installed');
201
+ } catch {
202
+ spinner.fail('Failed to install dependencies');
203
+ log.warn('Run "npm install" manually in the project directory');
204
+ }
205
+
206
+ // Step 5: Link FlexiReact (for development)
207
+ currentStep++;
208
+ log.step(currentStep, totalSteps, 'Linking FlexiReact...');
209
+ const linkSpinner = ora({ text: 'Linking framework...', color: 'cyan' }).start();
210
+
211
+ try {
212
+ const frameworkRoot = path.resolve(__dirname, '..');
213
+ await runCommand(`npm link "${frameworkRoot}"`, projectPath);
214
+ linkSpinner.succeed('FlexiReact linked');
215
+ } catch {
216
+ linkSpinner.fail('Failed to link FlexiReact');
217
+ log.warn('Run "npm link flexireact" manually');
218
+ }
219
+
220
+ // Step 6: Initialize shadcn/ui (if default template)
221
+ if (options.template === 'default') {
222
+ currentStep++;
223
+ log.step(currentStep, totalSteps, 'Setting up shadcn/ui components...');
224
+ log.success('shadcn/ui configured');
225
+ }
226
+
227
+ // Success message
228
+ log.blank();
229
+ log.divider();
230
+ log.blank();
231
+
232
+ console.log(` ${pc.green('✨')} ${pc.bold('Success!')} Your FlexiReact app is ready.`);
233
+ log.blank();
234
+
235
+ console.log(` ${pc.dim('$')} ${pc.cyan(`cd ${name}`)}`);
236
+ console.log(` ${pc.dim('$')} ${pc.cyan('npm run dev')}`);
237
+ log.blank();
238
+
239
+ console.log(` ${pc.dim('Then open')} ${pc.cyan('http://localhost:3000')} ${pc.dim('in your browser.')}`);
240
+ log.blank();
241
+
242
+ console.log(` ${pc.dim('Documentation:')} ${pc.cyan('https://github.com/flexireact/flexireact')}`);
243
+ log.blank();
244
+ }
245
+
246
+ async function createDefaultTemplate(projectPath: string, name: string, useTypeScript: boolean): Promise<void> {
247
+ const ext = useTypeScript ? 'tsx' : 'jsx';
248
+ const configExt = useTypeScript ? 'ts' : 'js';
249
+
250
+ // Create directories
251
+ const dirs = [
252
+ 'app/components',
253
+ 'app/styles',
254
+ 'pages/api',
255
+ 'public',
256
+ ];
257
+
258
+ for (const dir of dirs) {
259
+ fs.mkdirSync(path.join(projectPath, dir), { recursive: true });
260
+ }
261
+
262
+ // package.json
263
+ const packageJson = {
264
+ name,
265
+ version: '0.1.0',
266
+ private: true,
267
+ type: 'module',
268
+ scripts: {
269
+ dev: 'flexi dev',
270
+ build: 'flexi build',
271
+ start: 'flexi start',
272
+ doctor: 'flexi doctor'
273
+ },
274
+ dependencies: {
275
+ react: '^18.3.1',
276
+ 'react-dom': '^18.3.1',
277
+ 'class-variance-authority': '^0.7.0',
278
+ clsx: '^2.1.1',
279
+ 'tailwind-merge': '^2.5.5',
280
+ 'lucide-react': '^0.468.0'
281
+ },
282
+ devDependencies: {
283
+ tailwindcss: '^3.4.16',
284
+ postcss: '^8.4.49',
285
+ autoprefixer: '^10.4.20',
286
+ ...(useTypeScript ? {
287
+ typescript: '^5.7.2',
288
+ '@types/react': '^18.3.14',
289
+ '@types/react-dom': '^18.3.2',
290
+ '@types/node': '^22.10.1'
291
+ } : {})
292
+ }
293
+ };
294
+ fs.writeFileSync(
295
+ path.join(projectPath, 'package.json'),
296
+ JSON.stringify(packageJson, null, 2)
297
+ );
298
+
299
+ // TypeScript config
300
+ if (useTypeScript) {
301
+ const tsconfig = {
302
+ compilerOptions: {
303
+ target: 'ES2022',
304
+ lib: ['dom', 'dom.iterable', 'ES2022'],
305
+ allowJs: true,
306
+ skipLibCheck: true,
307
+ strict: true,
308
+ noEmit: true,
309
+ esModuleInterop: true,
310
+ module: 'ESNext',
311
+ moduleResolution: 'bundler',
312
+ resolveJsonModule: true,
313
+ isolatedModules: true,
314
+ jsx: 'react-jsx',
315
+ baseUrl: '.',
316
+ paths: {
317
+ '@/*': ['./*'],
318
+ '@/components/*': ['./app/components/*']
319
+ }
320
+ },
321
+ include: ['**/*.ts', '**/*.tsx'],
322
+ exclude: ['node_modules']
323
+ };
324
+ fs.writeFileSync(
325
+ path.join(projectPath, 'tsconfig.json'),
326
+ JSON.stringify(tsconfig, null, 2)
327
+ );
328
+ }
329
+
330
+ // Tailwind config
331
+ const tailwindConfig = `/** @type {import('tailwindcss').Config} */
332
+ export default {
333
+ content: [
334
+ './app/**/*.{js,ts,jsx,tsx}',
335
+ './pages/**/*.{js,ts,jsx,tsx}',
336
+ './components/**/*.{js,ts,jsx,tsx}',
337
+ ],
338
+ darkMode: 'class',
339
+ theme: {
340
+ extend: {
341
+ colors: {
342
+ border: 'hsl(var(--border))',
343
+ background: 'hsl(var(--background))',
344
+ foreground: 'hsl(var(--foreground))',
345
+ primary: {
346
+ DEFAULT: 'hsl(var(--primary))',
347
+ foreground: 'hsl(var(--primary-foreground))',
348
+ },
349
+ secondary: {
350
+ DEFAULT: 'hsl(var(--secondary))',
351
+ foreground: 'hsl(var(--secondary-foreground))',
352
+ },
353
+ muted: {
354
+ DEFAULT: 'hsl(var(--muted))',
355
+ foreground: 'hsl(var(--muted-foreground))',
356
+ },
357
+ accent: {
358
+ DEFAULT: 'hsl(var(--accent))',
359
+ foreground: 'hsl(var(--accent-foreground))',
360
+ },
361
+ },
362
+ borderRadius: {
363
+ lg: 'var(--radius)',
364
+ md: 'calc(var(--radius) - 2px)',
365
+ sm: 'calc(var(--radius) - 4px)',
366
+ },
367
+ },
368
+ },
369
+ plugins: [],
370
+ };
371
+ `;
372
+ fs.writeFileSync(path.join(projectPath, 'tailwind.config.js'), tailwindConfig);
373
+
374
+ // PostCSS config
375
+ const postcssConfig = `export default {
376
+ plugins: {
377
+ tailwindcss: {},
378
+ autoprefixer: {},
379
+ },
380
+ };
381
+ `;
382
+ fs.writeFileSync(path.join(projectPath, 'postcss.config.js'), postcssConfig);
383
+
384
+ // Global CSS
385
+ const globalsCss = `@tailwind base;
386
+ @tailwind components;
387
+ @tailwind utilities;
388
+
389
+ @layer base {
390
+ :root {
391
+ --background: 222 47% 11%;
392
+ --foreground: 210 40% 98%;
393
+ --primary: 263 70% 50%;
394
+ --primary-foreground: 210 40% 98%;
395
+ --secondary: 217 33% 17%;
396
+ --secondary-foreground: 210 40% 98%;
397
+ --muted: 217 33% 17%;
398
+ --muted-foreground: 215 20% 65%;
399
+ --accent: 263 70% 50%;
400
+ --accent-foreground: 210 40% 98%;
401
+ --border: 217 33% 17%;
402
+ --radius: 0.5rem;
403
+ }
404
+ }
405
+
406
+ @layer base {
407
+ * {
408
+ @apply border-border;
409
+ }
410
+ body {
411
+ @apply bg-background text-foreground antialiased;
412
+ font-feature-settings: "rlig" 1, "calt" 1;
413
+ }
414
+ }
415
+ `;
416
+ fs.writeFileSync(path.join(projectPath, 'app/styles/globals.css'), globalsCss);
417
+
418
+ // FlexiReact config
419
+ const flexiConfig = `export default {
420
+ server: {
421
+ port: 3000,
422
+ host: 'localhost'
423
+ },
424
+ islands: {
425
+ enabled: true
426
+ },
427
+ rsc: {
428
+ enabled: true
429
+ }
430
+ };
431
+ `;
432
+ fs.writeFileSync(path.join(projectPath, `flexireact.config.${configExt}`), flexiConfig);
433
+
434
+ // Create components
435
+ await createComponents(projectPath, ext);
436
+
437
+ // Create pages
438
+ await createPages(projectPath, ext);
439
+ }
440
+
441
+ async function createComponents(projectPath: string, ext: string): Promise<void> {
442
+ // Button component
443
+ const buttonComponent = `import React from 'react';
444
+ import { cva, type VariantProps } from 'class-variance-authority';
445
+ import { clsx } from 'clsx';
446
+ import { twMerge } from 'tailwind-merge';
447
+
448
+ function cn(...inputs${ext === 'tsx' ? ': (string | undefined)[]' : ''}) {
449
+ return twMerge(clsx(inputs));
450
+ }
451
+
452
+ const buttonVariants = cva(
453
+ 'inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
454
+ {
455
+ variants: {
456
+ variant: {
457
+ default: 'bg-primary text-primary-foreground hover:bg-primary/90',
458
+ secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
459
+ outline: 'border border-border bg-transparent hover:bg-accent hover:text-accent-foreground',
460
+ ghost: 'hover:bg-accent hover:text-accent-foreground',
461
+ },
462
+ size: {
463
+ default: 'h-10 px-4 py-2',
464
+ sm: 'h-9 rounded-md px-3',
465
+ lg: 'h-11 rounded-md px-8',
466
+ icon: 'h-10 w-10',
467
+ },
468
+ },
469
+ defaultVariants: {
470
+ variant: 'default',
471
+ size: 'default',
472
+ },
473
+ }
474
+ );
475
+
476
+ ${ext === 'tsx' ? `interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> {}` : ''}
477
+
478
+ export function Button({ className, variant, size, ...props }${ext === 'tsx' ? ': ButtonProps' : ''}) {
479
+ return (
480
+ <button
481
+ className={cn(buttonVariants({ variant, size, className }))}
482
+ {...props}
483
+ />
484
+ );
485
+ }
486
+ `;
487
+ fs.writeFileSync(path.join(projectPath, `app/components/Button.${ext}`), buttonComponent);
488
+
489
+ // Card component
490
+ const cardComponent = `import React from 'react';
491
+
492
+ ${ext === 'tsx' ? `interface CardProps {
493
+ children: React.ReactNode;
494
+ className?: string;
495
+ }` : ''}
496
+
497
+ export function Card({ children, className = '' }${ext === 'tsx' ? ': CardProps' : ''}) {
498
+ return (
499
+ <div className={\`rounded-lg border border-border bg-secondary/50 p-6 backdrop-blur-sm \${className}\`}>
500
+ {children}
501
+ </div>
502
+ );
503
+ }
504
+
505
+ export function CardHeader({ children, className = '' }${ext === 'tsx' ? ': CardProps' : ''}) {
506
+ return <div className={\`mb-4 \${className}\`}>{children}</div>;
507
+ }
508
+
509
+ export function CardTitle({ children, className = '' }${ext === 'tsx' ? ': CardProps' : ''}) {
510
+ return <h3 className={\`text-xl font-semibold \${className}\`}>{children}</h3>;
511
+ }
512
+
513
+ export function CardDescription({ children, className = '' }${ext === 'tsx' ? ': CardProps' : ''}) {
514
+ return <p className={\`text-muted-foreground \${className}\`}>{children}</p>;
515
+ }
516
+
517
+ export function CardContent({ children, className = '' }${ext === 'tsx' ? ': CardProps' : ''}) {
518
+ return <div className={className}>{children}</div>;
519
+ }
520
+ `;
521
+ fs.writeFileSync(path.join(projectPath, `app/components/Card.${ext}`), cardComponent);
522
+
523
+ // Navbar component
524
+ const navbarComponent = `import React from 'react';
525
+
526
+ export function Navbar() {
527
+ return (
528
+ <nav className="fixed top-0 left-0 right-0 z-50 border-b border-border bg-background/80 backdrop-blur-md">
529
+ <div className="mx-auto flex h-16 max-w-7xl items-center justify-between px-4">
530
+ <a href="/" className="flex items-center gap-2 text-xl font-bold">
531
+ <span className="text-2xl">⚡</span>
532
+ <span className="bg-gradient-to-r from-purple-400 to-pink-400 bg-clip-text text-transparent">
533
+ FlexiReact
534
+ </span>
535
+ </a>
536
+
537
+ <div className="flex items-center gap-6">
538
+ <a href="/" className="text-muted-foreground hover:text-foreground transition-colors">
539
+ Home
540
+ </a>
541
+ <a href="/docs" className="text-muted-foreground hover:text-foreground transition-colors">
542
+ Docs
543
+ </a>
544
+ <a href="/api/hello" className="text-muted-foreground hover:text-foreground transition-colors">
545
+ API
546
+ </a>
547
+ <a
548
+ href="https://github.com/flexireact/flexireact"
549
+ target="_blank"
550
+ rel="noopener noreferrer"
551
+ className="text-muted-foreground hover:text-foreground transition-colors"
552
+ >
553
+ GitHub
554
+ </a>
555
+ </div>
556
+ </div>
557
+ </nav>
558
+ );
559
+ }
560
+ `;
561
+ fs.writeFileSync(path.join(projectPath, `app/components/Navbar.${ext}`), navbarComponent);
562
+ }
563
+
564
+ async function createPages(projectPath: string, ext: string): Promise<void> {
565
+ // Home page with INLINE STYLES (works without Tailwind build)
566
+ const homePage = `import React from 'react';
567
+
568
+ export const title = 'FlexiReact - The Modern React Framework';
569
+
570
+ const features = [
571
+ { icon: '⚡', title: 'Lightning Fast', desc: 'Powered by esbuild for instant builds.' },
572
+ { icon: '📘', title: 'TypeScript', desc: 'First-class TypeScript support.' },
573
+ { icon: '🏝️', title: 'Islands', desc: 'Partial hydration for minimal JS.' },
574
+ { icon: '📁', title: 'File Routing', desc: 'Create a file, get a route.' },
575
+ { icon: '🔌', title: 'API Routes', desc: 'Build your API alongside frontend.' },
576
+ { icon: '🚀', title: 'SSR/SSG', desc: 'Server rendering and static generation.' },
577
+ ];
578
+
579
+ export default function HomePage() {
580
+ return (
581
+ <div style={styles.container}>
582
+ {/* Navbar */}
583
+ <nav style={styles.nav}>
584
+ <a href="/" style={styles.logo}>
585
+ <svg style={{ width: 32, height: 32 }} viewBox="0 0 200 200" fill="none">
586
+ <defs>
587
+ <linearGradient id="g" x1="0%" y1="0%" x2="100%" y2="100%">
588
+ <stop offset="0%" stopColor="#61DAFB"/>
589
+ <stop offset="100%" stopColor="#21A1F1"/>
590
+ </linearGradient>
591
+ </defs>
592
+ <circle cx="100" cy="100" r="12" fill="url(#g)"/>
593
+ <ellipse cx="100" cy="100" rx="80" ry="30" fill="none" stroke="url(#g)" strokeWidth="6" transform="rotate(-30 100 100)"/>
594
+ <ellipse cx="100" cy="100" rx="80" ry="30" fill="none" stroke="url(#g)" strokeWidth="6" transform="rotate(30 100 100)"/>
595
+ <ellipse cx="100" cy="100" rx="80" ry="30" fill="none" stroke="url(#g)" strokeWidth="6" transform="rotate(90 100 100)"/>
596
+ </svg>
597
+ <span style={styles.logoText}>FlexiReact</span>
598
+ </a>
599
+ <div style={styles.navLinks}>
600
+ <a href="/" style={styles.navLink}>Home</a>
601
+ <a href="/api/hello" style={styles.navLink}>API</a>
602
+ </div>
603
+ </nav>
604
+
605
+ {/* Hero */}
606
+ <section style={styles.hero}>
607
+ <svg style={{ width: 120, height: 120, marginBottom: 24 }} viewBox="0 0 200 200" fill="none">
608
+ <defs>
609
+ <linearGradient id="hero" x1="0%" y1="0%" x2="100%" y2="100%">
610
+ <stop offset="0%" stopColor="#61DAFB"/>
611
+ <stop offset="100%" stopColor="#21A1F1"/>
612
+ </linearGradient>
613
+ </defs>
614
+ <circle cx="100" cy="100" r="12" fill="url(#hero)"/>
615
+ <ellipse cx="100" cy="100" rx="80" ry="30" fill="none" stroke="url(#hero)" strokeWidth="6" transform="rotate(-30 100 100)"/>
616
+ <ellipse cx="100" cy="100" rx="80" ry="30" fill="none" stroke="url(#hero)" strokeWidth="6" transform="rotate(30 100 100)"/>
617
+ <ellipse cx="100" cy="100" rx="80" ry="30" fill="none" stroke="url(#hero)" strokeWidth="6" transform="rotate(90 100 100)"/>
618
+ <circle cx="28" cy="70" r="8" fill="url(#hero)"/>
619
+ <circle cx="172" cy="130" r="8" fill="url(#hero)"/>
620
+ <circle cx="100" cy="20" r="8" fill="url(#hero)"/>
621
+ </svg>
622
+
623
+ <div style={styles.badge}>🚀 v2.1 — TypeScript & Islands</div>
624
+
625
+ <h1 style={styles.title}>
626
+ Build faster with<br/>
627
+ <span style={styles.titleGradient}>FlexiReact</span>
628
+ </h1>
629
+
630
+ <p style={styles.subtitle}>
631
+ The modern React framework with SSR, SSG, Islands architecture,<br/>
632
+ and file-based routing. Simple and powerful.
633
+ </p>
634
+
635
+ <div style={styles.buttons}>
636
+ <a href="/docs" style={styles.primaryBtn}>Get Started →</a>
637
+ <a href="/api/hello" style={styles.secondaryBtn}>View API</a>
638
+ </div>
639
+ </section>
640
+
641
+ {/* Features */}
642
+ <section style={styles.features}>
643
+ <h2 style={styles.featuresTitle}>Everything you need</h2>
644
+ <div style={styles.grid}>
645
+ {features.map((f, i) => (
646
+ <div key={i} style={styles.card}>
647
+ <div style={styles.cardIcon}>{f.icon}</div>
648
+ <h3 style={styles.cardTitle}>{f.title}</h3>
649
+ <p style={styles.cardDesc}>{f.desc}</p>
650
+ </div>
651
+ ))}
652
+ </div>
653
+ </section>
654
+
655
+ {/* Footer */}
656
+ <footer style={styles.footer}>
657
+ Built with ❤️ using FlexiReact
658
+ </footer>
659
+ </div>
660
+ );
661
+ }
662
+
663
+ const styles = {
664
+ container: {
665
+ minHeight: '100vh',
666
+ background: 'linear-gradient(180deg, #0f172a 0%, #1e293b 100%)',
667
+ color: '#f8fafc',
668
+ fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
669
+ },
670
+ nav: {
671
+ position: 'fixed' as const,
672
+ top: 0,
673
+ left: 0,
674
+ right: 0,
675
+ height: 64,
676
+ display: 'flex',
677
+ alignItems: 'center',
678
+ justifyContent: 'space-between',
679
+ padding: '0 24px',
680
+ background: 'rgba(15, 23, 42, 0.8)',
681
+ backdropFilter: 'blur(12px)',
682
+ borderBottom: '1px solid rgba(255,255,255,0.1)',
683
+ zIndex: 100,
684
+ },
685
+ logo: {
686
+ display: 'flex',
687
+ alignItems: 'center',
688
+ gap: 8,
689
+ textDecoration: 'none',
690
+ color: '#f8fafc',
691
+ },
692
+ logoText: {
693
+ fontSize: 20,
694
+ fontWeight: 700,
695
+ background: 'linear-gradient(90deg, #61DAFB, #21A1F1)',
696
+ WebkitBackgroundClip: 'text',
697
+ WebkitTextFillColor: 'transparent',
698
+ },
699
+ navLinks: { display: 'flex', gap: 24 },
700
+ navLink: { color: '#94a3b8', textDecoration: 'none', fontSize: 14 },
701
+ hero: {
702
+ display: 'flex',
703
+ flexDirection: 'column' as const,
704
+ alignItems: 'center',
705
+ justifyContent: 'center',
706
+ textAlign: 'center' as const,
707
+ padding: '140px 24px 80px',
708
+ },
709
+ badge: {
710
+ background: 'rgba(99, 102, 241, 0.2)',
711
+ border: '1px solid rgba(99, 102, 241, 0.3)',
712
+ borderRadius: 9999,
713
+ padding: '8px 16px',
714
+ fontSize: 14,
715
+ marginBottom: 24,
716
+ },
717
+ title: {
718
+ fontSize: 'clamp(2.5rem, 8vw, 4.5rem)',
719
+ fontWeight: 800,
720
+ lineHeight: 1.1,
721
+ marginBottom: 24,
722
+ },
723
+ titleGradient: {
724
+ background: 'linear-gradient(90deg, #61DAFB, #a78bfa, #61DAFB)',
725
+ WebkitBackgroundClip: 'text',
726
+ WebkitTextFillColor: 'transparent',
727
+ },
728
+ subtitle: {
729
+ fontSize: 18,
730
+ color: '#94a3b8',
731
+ maxWidth: 600,
732
+ lineHeight: 1.6,
733
+ marginBottom: 32,
734
+ },
735
+ buttons: { display: 'flex', gap: 16, flexWrap: 'wrap' as const, justifyContent: 'center' },
736
+ primaryBtn: {
737
+ background: 'linear-gradient(135deg, #6366f1, #8b5cf6)',
738
+ color: '#fff',
739
+ padding: '14px 28px',
740
+ borderRadius: 12,
741
+ textDecoration: 'none',
742
+ fontWeight: 600,
743
+ boxShadow: '0 4px 20px rgba(99, 102, 241, 0.4)',
744
+ },
745
+ secondaryBtn: {
746
+ background: 'transparent',
747
+ color: '#f8fafc',
748
+ padding: '14px 28px',
749
+ borderRadius: 12,
750
+ textDecoration: 'none',
751
+ fontWeight: 600,
752
+ border: '1px solid rgba(255,255,255,0.2)',
753
+ },
754
+ features: {
755
+ padding: '80px 24px',
756
+ maxWidth: 1200,
757
+ margin: '0 auto',
758
+ },
759
+ featuresTitle: {
760
+ fontSize: 32,
761
+ fontWeight: 700,
762
+ textAlign: 'center' as const,
763
+ marginBottom: 48,
764
+ },
765
+ grid: {
766
+ display: 'grid',
767
+ gridTemplateColumns: 'repeat(auto-fit, minmax(280px, 1fr))',
768
+ gap: 24,
769
+ },
770
+ card: {
771
+ background: 'rgba(255,255,255,0.05)',
772
+ border: '1px solid rgba(255,255,255,0.1)',
773
+ borderRadius: 16,
774
+ padding: 24,
775
+ },
776
+ cardIcon: { fontSize: 32, marginBottom: 12 },
777
+ cardTitle: { fontSize: 18, fontWeight: 600, marginBottom: 8 },
778
+ cardDesc: { fontSize: 14, color: '#94a3b8', lineHeight: 1.5 },
779
+ footer: {
780
+ textAlign: 'center' as const,
781
+ padding: 32,
782
+ color: '#64748b',
783
+ borderTop: '1px solid rgba(255,255,255,0.1)',
784
+ },
785
+ };
786
+ `;
787
+ // Always use .jsx for pages (simpler, works without TS config)
788
+ fs.writeFileSync(path.join(projectPath, 'pages/index.jsx'), homePage);
789
+
790
+ // API route
791
+ const apiRoute = `/**
792
+ * API Route: /api/hello
793
+ */
794
+
795
+ export function get(req${ext === 'tsx' ? ': any' : ''}, res${ext === 'tsx' ? ': any' : ''}) {
796
+ res.json({
797
+ message: 'Hello from FlexiReact API! 🚀',
798
+ timestamp: new Date().toISOString(),
799
+ framework: 'FlexiReact v2.1'
800
+ });
801
+ }
802
+
803
+ export function post(req${ext === 'tsx' ? ': any' : ''}, res${ext === 'tsx' ? ': any' : ''}) {
804
+ const { name } = req.body || {};
805
+ res.json({
806
+ message: \`Hello, \${name || 'World'}!\`,
807
+ timestamp: new Date().toISOString()
808
+ });
809
+ }
810
+ `;
811
+ fs.writeFileSync(path.join(projectPath, `pages/api/hello.${ext === 'tsx' ? 'ts' : 'js'}`), apiRoute);
812
+
813
+ // .gitkeep for public
814
+ fs.writeFileSync(path.join(projectPath, 'public/.gitkeep'), '');
815
+ }
816
+
817
+ // ============================================================================
818
+ // Dev Command
819
+ // ============================================================================
820
+
821
+ async function runDev(): Promise<void> {
822
+ // Show styled logo
823
+ console.log(`
824
+ ${pc.cyan(' ╭─────────────────────────────────────────╮')}
825
+ ${pc.cyan(' │')} ${pc.cyan('│')}
826
+ ${pc.cyan(' │')} ${pc.bold(pc.cyan('⚛'))} ${pc.bold(pc.white('F L E X I R E A C T'))} ${pc.cyan('│')}
827
+ ${pc.cyan(' │')} ${pc.cyan('│')}
828
+ ${pc.cyan(' │')} ${pc.dim('The Modern React Framework')} ${pc.cyan('│')}
829
+ ${pc.cyan(' │')} ${pc.cyan('│')}
830
+ ${pc.cyan(' ╰─────────────────────────────────────────╯')}
831
+ `);
832
+
833
+ const loaderPath = path.join(__dirname, '..', 'core', 'loader.js');
834
+ const serverPath = path.join(__dirname, '..', 'core', 'server', 'index.js');
835
+ const loaderUrl = pathToFileURL(loaderPath).href;
836
+
837
+ const child = spawn(
838
+ process.execPath,
839
+ [
840
+ '--import',
841
+ `data:text/javascript,import { register } from 'node:module'; register('${loaderUrl}', import.meta.url);`,
842
+ '-e',
843
+ `import('${pathToFileURL(serverPath).href}').then(m => m.createServer({ mode: 'development' }))`
844
+ ],
845
+ {
846
+ stdio: 'inherit',
847
+ cwd: process.cwd(),
848
+ env: { ...process.env, NODE_ENV: 'development', FORCE_COLOR: '1' }
849
+ }
850
+ );
851
+
852
+ child.on('error', (error) => {
853
+ log.error(`Failed to start dev server: ${error.message}`);
854
+ process.exit(1);
855
+ });
856
+
857
+ process.on('SIGINT', () => child.kill('SIGINT'));
858
+ process.on('SIGTERM', () => child.kill('SIGTERM'));
859
+ }
860
+
861
+ // ============================================================================
862
+ // Build Command
863
+ // ============================================================================
864
+
865
+ async function runBuild(): Promise<void> {
866
+ console.log(MINI_LOGO);
867
+ log.blank();
868
+ log.info('Building for production...');
869
+ log.blank();
870
+
871
+ const spinner = ora({ text: 'Compiling...', color: 'cyan' }).start();
872
+
873
+ try {
874
+ // Dynamic import of build module
875
+ const buildPath = path.join(__dirname, '..', 'core', 'build', 'index.js');
876
+ const configPath = path.join(__dirname, '..', 'core', 'config.js');
877
+
878
+ const buildModule = await import(pathToFileURL(buildPath).href);
879
+ const configModule = await import(pathToFileURL(configPath).href);
880
+
881
+ const projectRoot = process.cwd();
882
+ const rawConfig = await configModule.loadConfig(projectRoot);
883
+ const config = configModule.resolvePaths(rawConfig, projectRoot);
884
+
885
+ await buildModule.build({
886
+ projectRoot,
887
+ config,
888
+ mode: 'production'
889
+ });
890
+
891
+ spinner.succeed('Build complete!');
892
+ log.blank();
893
+ log.success(`Output: ${pc.cyan('.flexi/')}`);
894
+
895
+ } catch (error: any) {
896
+ spinner.fail('Build failed');
897
+ log.error(error.message);
898
+ process.exit(1);
899
+ }
900
+ }
901
+
902
+ // ============================================================================
903
+ // Start Command
904
+ // ============================================================================
905
+
906
+ async function runStart(): Promise<void> {
907
+ console.log(MINI_LOGO);
908
+ log.blank();
909
+ log.info('Starting production server...');
910
+ log.blank();
911
+
912
+ const loaderPath = path.join(__dirname, '..', 'core', 'loader.js');
913
+ const serverPath = path.join(__dirname, '..', 'core', 'server', 'index.js');
914
+ const loaderUrl = pathToFileURL(loaderPath).href;
915
+
916
+ const child = spawn(
917
+ process.execPath,
918
+ [
919
+ '--import',
920
+ `data:text/javascript,import { register } from 'node:module'; register('${loaderUrl}', import.meta.url);`,
921
+ '-e',
922
+ `import('${pathToFileURL(serverPath).href}').then(m => m.createServer({ mode: 'production' }))`
923
+ ],
924
+ {
925
+ stdio: 'inherit',
926
+ cwd: process.cwd(),
927
+ env: { ...process.env, NODE_ENV: 'production' }
928
+ }
929
+ );
930
+
931
+ child.on('error', (error) => {
932
+ log.error(`Failed to start server: ${error.message}`);
933
+ process.exit(1);
934
+ });
935
+
936
+ process.on('SIGINT', () => child.kill('SIGINT'));
937
+ process.on('SIGTERM', () => child.kill('SIGTERM'));
938
+ }
939
+
940
+ // ============================================================================
941
+ // Doctor Command
942
+ // ============================================================================
943
+
944
+ async function runDoctor(): Promise<void> {
945
+ console.log(MINI_LOGO);
946
+ log.blank();
947
+ log.info('Checking your project...');
948
+ log.blank();
949
+
950
+ interface Check {
951
+ name: string;
952
+ status: 'pass' | 'fail' | 'warn' | 'info';
953
+ message: string;
954
+ }
955
+
956
+ const checks: Check[] = [];
957
+ const projectRoot = process.cwd();
958
+
959
+ // Node.js version
960
+ const nodeVersion = process.version;
961
+ const nodeMajor = parseInt(nodeVersion.slice(1).split('.')[0]);
962
+ checks.push({
963
+ name: 'Node.js version',
964
+ status: nodeMajor >= 18 ? 'pass' : 'fail',
965
+ message: nodeMajor >= 18 ? `${nodeVersion} ✓` : `${nodeVersion} (requires 18+)`
966
+ });
967
+
968
+ // package.json
969
+ const packageJsonPath = path.join(projectRoot, 'package.json');
970
+ const hasPackageJson = fs.existsSync(packageJsonPath);
971
+ checks.push({
972
+ name: 'package.json',
973
+ status: hasPackageJson ? 'pass' : 'fail',
974
+ message: hasPackageJson ? 'Found' : 'Not found'
975
+ });
976
+
977
+ // pages directory
978
+ const pagesDir = path.join(projectRoot, 'pages');
979
+ const hasPages = fs.existsSync(pagesDir);
980
+ checks.push({
981
+ name: 'pages/ directory',
982
+ status: hasPages ? 'pass' : 'warn',
983
+ message: hasPages ? 'Found' : 'Not found'
984
+ });
985
+
986
+ // TypeScript
987
+ const tsconfigPath = path.join(projectRoot, 'tsconfig.json');
988
+ const hasTypeScript = fs.existsSync(tsconfigPath);
989
+ checks.push({
990
+ name: 'TypeScript',
991
+ status: 'info',
992
+ message: hasTypeScript ? 'Enabled' : 'Not configured'
993
+ });
994
+
995
+ // Tailwind
996
+ const tailwindPath = path.join(projectRoot, 'tailwind.config.js');
997
+ const hasTailwind = fs.existsSync(tailwindPath);
998
+ checks.push({
999
+ name: 'Tailwind CSS',
1000
+ status: 'info',
1001
+ message: hasTailwind ? 'Configured' : 'Not configured'
1002
+ });
1003
+
1004
+ // Print results
1005
+ let hasErrors = false;
1006
+ let hasWarnings = false;
1007
+
1008
+ for (const check of checks) {
1009
+ let icon: string;
1010
+ let color: (s: string) => string;
1011
+
1012
+ switch (check.status) {
1013
+ case 'pass':
1014
+ icon = '✓';
1015
+ color = pc.green;
1016
+ break;
1017
+ case 'fail':
1018
+ icon = '✗';
1019
+ color = pc.red;
1020
+ hasErrors = true;
1021
+ break;
1022
+ case 'warn':
1023
+ icon = '⚠';
1024
+ color = pc.yellow;
1025
+ hasWarnings = true;
1026
+ break;
1027
+ default:
1028
+ icon = '○';
1029
+ color = pc.cyan;
1030
+ }
1031
+
1032
+ console.log(` ${color(icon)} ${check.name}: ${pc.dim(check.message)}`);
1033
+ }
1034
+
1035
+ log.blank();
1036
+
1037
+ if (hasErrors) {
1038
+ log.error('Some checks failed. Please fix the issues above.');
1039
+ } else if (hasWarnings) {
1040
+ log.warn('All critical checks passed with some warnings.');
1041
+ } else {
1042
+ log.success('All checks passed! Your project is ready.');
1043
+ }
1044
+
1045
+ log.blank();
1046
+ }
1047
+
1048
+ // ============================================================================
1049
+ // Help Command
1050
+ // ============================================================================
1051
+
1052
+ function showHelp(): void {
1053
+ console.log(LOGO);
1054
+
1055
+ console.log(` ${pc.bold('Usage:')}`);
1056
+ console.log(` ${pc.cyan('flexi')} ${pc.dim('<command>')} ${pc.dim('[options]')}`);
1057
+ log.blank();
1058
+
1059
+ console.log(` ${pc.bold('Commands:')}`);
1060
+ console.log(` ${pc.cyan('create')} ${pc.dim('<name>')} Create a new FlexiReact project`);
1061
+ console.log(` ${pc.cyan('dev')} Start development server`);
1062
+ console.log(` ${pc.cyan('build')} Build for production`);
1063
+ console.log(` ${pc.cyan('start')} Start production server`);
1064
+ console.log(` ${pc.cyan('doctor')} Check project health`);
1065
+ console.log(` ${pc.cyan('help')} Show this help message`);
1066
+ log.blank();
1067
+
1068
+ console.log(` ${pc.bold('Examples:')}`);
1069
+ console.log(` ${pc.dim('$')} flexi create my-app`);
1070
+ console.log(` ${pc.dim('$')} flexi dev`);
1071
+ console.log(` ${pc.dim('$')} flexi build && flexi start`);
1072
+ log.blank();
1073
+ }
1074
+
1075
+ // ============================================================================
1076
+ // Main Entry Point
1077
+ // ============================================================================
1078
+
1079
+ async function main(): Promise<void> {
1080
+ const args = process.argv.slice(2);
1081
+ const command = args[0];
1082
+
1083
+ switch (command) {
1084
+ case 'create':
1085
+ await createProject(args[1]);
1086
+ break;
1087
+
1088
+ case 'dev':
1089
+ await runDev();
1090
+ break;
1091
+
1092
+ case 'build':
1093
+ await runBuild();
1094
+ break;
1095
+
1096
+ case 'start':
1097
+ await runStart();
1098
+ break;
1099
+
1100
+ case 'doctor':
1101
+ await runDoctor();
1102
+ break;
1103
+
1104
+ case 'version':
1105
+ case '-v':
1106
+ case '--version':
1107
+ console.log(`${MINI_LOGO} ${pc.dim(`v${VERSION}`)}`);
1108
+ break;
1109
+
1110
+ case 'help':
1111
+ case '--help':
1112
+ case '-h':
1113
+ showHelp();
1114
+ break;
1115
+
1116
+ default:
1117
+ if (command) {
1118
+ log.error(`Unknown command: ${command}`);
1119
+ log.blank();
1120
+ }
1121
+ showHelp();
1122
+ process.exit(command ? 1 : 0);
1123
+ }
1124
+ }
1125
+
1126
+ main().catch((error) => {
1127
+ log.error(error.message);
1128
+ process.exit(1);
1129
+ });