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