@flexireact/core 3.0.1 → 3.0.2

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