create-flexireact 1.0.1 → 1.1.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/index.js +561 -238
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -74,11 +74,7 @@ ${c.green} ╰─────────────────────
|
|
|
74
74
|
const TEMPLATES = {
|
|
75
75
|
default: {
|
|
76
76
|
name: 'Default',
|
|
77
|
-
description: '
|
|
78
|
-
},
|
|
79
|
-
'flexi-ui': {
|
|
80
|
-
name: 'FlexiUI',
|
|
81
|
-
description: 'FlexiReact with FlexiUI component library',
|
|
77
|
+
description: 'Premium template with modern UI, animations & dark mode',
|
|
82
78
|
},
|
|
83
79
|
minimal: {
|
|
84
80
|
name: 'Minimal',
|
|
@@ -106,10 +102,6 @@ function info(msg) {
|
|
|
106
102
|
console.log(` ${c.cyan}ℹ${c.reset} ${msg}`);
|
|
107
103
|
}
|
|
108
104
|
|
|
109
|
-
function step(num, total, msg) {
|
|
110
|
-
console.log(` ${c.dim}[${num}/${total}]${c.reset} ${msg}`);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
105
|
// Spinner
|
|
114
106
|
class Spinner {
|
|
115
107
|
constructor(message) {
|
|
@@ -184,26 +176,6 @@ async function select(question, options) {
|
|
|
184
176
|
});
|
|
185
177
|
}
|
|
186
178
|
|
|
187
|
-
// Copy directory recursively
|
|
188
|
-
function copyDir(src, dest) {
|
|
189
|
-
if (!fs.existsSync(dest)) {
|
|
190
|
-
fs.mkdirSync(dest, { recursive: true });
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
194
|
-
|
|
195
|
-
for (const entry of entries) {
|
|
196
|
-
const srcPath = path.join(src, entry.name);
|
|
197
|
-
const destPath = path.join(dest, entry.name);
|
|
198
|
-
|
|
199
|
-
if (entry.isDirectory()) {
|
|
200
|
-
copyDir(srcPath, destPath);
|
|
201
|
-
} else {
|
|
202
|
-
fs.copyFileSync(srcPath, destPath);
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
|
|
207
179
|
// Check if directory is empty
|
|
208
180
|
function isDirEmpty(dir) {
|
|
209
181
|
if (!fs.existsSync(dir)) return true;
|
|
@@ -211,7 +183,7 @@ function isDirEmpty(dir) {
|
|
|
211
183
|
}
|
|
212
184
|
|
|
213
185
|
// ============================================================================
|
|
214
|
-
// Template Files
|
|
186
|
+
// Template Files
|
|
215
187
|
// ============================================================================
|
|
216
188
|
|
|
217
189
|
const TEMPLATE_FILES = {
|
|
@@ -224,21 +196,24 @@ const TEMPLATE_FILES = {
|
|
|
224
196
|
dev: "npm run css && flexireact dev",
|
|
225
197
|
build: "npm run css && flexireact build",
|
|
226
198
|
start: "flexireact start",
|
|
227
|
-
css: "npx tailwindcss -i ./app/styles/
|
|
199
|
+
css: "npx tailwindcss -i ./app/styles/globals.css -o ./public/styles.css --minify"
|
|
228
200
|
},
|
|
229
201
|
dependencies: {
|
|
230
|
-
react: "^18.2.0",
|
|
202
|
+
"react": "^18.2.0",
|
|
231
203
|
"react-dom": "^18.2.0",
|
|
232
204
|
"@flexireact/core": "^1.0.0",
|
|
233
|
-
|
|
205
|
+
"framer-motion": "^11.0.0",
|
|
206
|
+
"lucide-react": "^0.400.0",
|
|
207
|
+
"clsx": "^2.1.0",
|
|
208
|
+
"tailwind-merge": "^2.2.0"
|
|
234
209
|
},
|
|
235
210
|
devDependencies: {
|
|
236
211
|
"@types/react": "^18.2.0",
|
|
237
212
|
"@types/react-dom": "^18.2.0",
|
|
238
|
-
typescript: "^5.3.0",
|
|
239
|
-
tailwindcss: "^3.4.0",
|
|
240
|
-
postcss: "^8.4.32",
|
|
241
|
-
autoprefixer: "^10.4.16"
|
|
213
|
+
"typescript": "^5.3.0",
|
|
214
|
+
"tailwindcss": "^3.4.0",
|
|
215
|
+
"postcss": "^8.4.32",
|
|
216
|
+
"autoprefixer": "^10.4.16"
|
|
242
217
|
}
|
|
243
218
|
}, null, 2),
|
|
244
219
|
|
|
@@ -265,8 +240,7 @@ const TEMPLATE_FILES = {
|
|
|
265
240
|
exclude: ["node_modules", ".flexi"]
|
|
266
241
|
}, null, 2),
|
|
267
242
|
|
|
268
|
-
'tailwind.config.js': (
|
|
269
|
-
${template === 'flexi-ui' ? "const { flexiUIPlugin } = require('@flexireact/flexi-ui/tailwind');\n" : ''}
|
|
243
|
+
'tailwind.config.js': () => `/** @type {import('tailwindcss').Config} */
|
|
270
244
|
module.exports = {
|
|
271
245
|
darkMode: 'class',
|
|
272
246
|
content: [
|
|
@@ -274,16 +248,71 @@ module.exports = {
|
|
|
274
248
|
'./pages/**/*.{js,ts,jsx,tsx}',
|
|
275
249
|
'./components/**/*.{js,ts,jsx,tsx}',
|
|
276
250
|
'./layouts/**/*.{js,ts,jsx,tsx}',
|
|
277
|
-
${template === 'flexi-ui' ? "'./node_modules/@flexireact/flexi-ui/dist/**/*.js'," : ''}
|
|
278
251
|
],
|
|
279
252
|
theme: {
|
|
280
253
|
extend: {
|
|
281
254
|
fontFamily: {
|
|
282
255
|
sans: ['Inter', 'system-ui', 'sans-serif'],
|
|
283
256
|
},
|
|
257
|
+
colors: {
|
|
258
|
+
border: 'hsl(240 3.7% 15.9%)',
|
|
259
|
+
input: 'hsl(240 3.7% 15.9%)',
|
|
260
|
+
ring: 'hsl(142.1 76.2% 36.3%)',
|
|
261
|
+
background: 'hsl(240 10% 3.9%)',
|
|
262
|
+
foreground: 'hsl(0 0% 98%)',
|
|
263
|
+
primary: {
|
|
264
|
+
DEFAULT: 'hsl(142.1 76.2% 36.3%)',
|
|
265
|
+
foreground: 'hsl(144.9 80.4% 10%)',
|
|
266
|
+
},
|
|
267
|
+
secondary: {
|
|
268
|
+
DEFAULT: 'hsl(240 3.7% 15.9%)',
|
|
269
|
+
foreground: 'hsl(0 0% 98%)',
|
|
270
|
+
},
|
|
271
|
+
muted: {
|
|
272
|
+
DEFAULT: 'hsl(240 3.7% 15.9%)',
|
|
273
|
+
foreground: 'hsl(240 5% 64.9%)',
|
|
274
|
+
},
|
|
275
|
+
accent: {
|
|
276
|
+
DEFAULT: 'hsl(240 3.7% 15.9%)',
|
|
277
|
+
foreground: 'hsl(0 0% 98%)',
|
|
278
|
+
},
|
|
279
|
+
card: {
|
|
280
|
+
DEFAULT: 'hsl(240 10% 3.9%)',
|
|
281
|
+
foreground: 'hsl(0 0% 98%)',
|
|
282
|
+
},
|
|
283
|
+
},
|
|
284
|
+
borderRadius: {
|
|
285
|
+
lg: '0.75rem',
|
|
286
|
+
md: '0.5rem',
|
|
287
|
+
sm: '0.25rem',
|
|
288
|
+
},
|
|
289
|
+
animation: {
|
|
290
|
+
'fade-in': 'fadeIn 0.5s ease-out',
|
|
291
|
+
'fade-up': 'fadeUp 0.5s ease-out',
|
|
292
|
+
'scale-in': 'scaleIn 0.3s ease-out',
|
|
293
|
+
'glow': 'glow 2s ease-in-out infinite alternate',
|
|
294
|
+
},
|
|
295
|
+
keyframes: {
|
|
296
|
+
fadeIn: {
|
|
297
|
+
'0%': { opacity: '0' },
|
|
298
|
+
'100%': { opacity: '1' },
|
|
299
|
+
},
|
|
300
|
+
fadeUp: {
|
|
301
|
+
'0%': { opacity: '0', transform: 'translateY(20px)' },
|
|
302
|
+
'100%': { opacity: '1', transform: 'translateY(0)' },
|
|
303
|
+
},
|
|
304
|
+
scaleIn: {
|
|
305
|
+
'0%': { opacity: '0', transform: 'scale(0.95)' },
|
|
306
|
+
'100%': { opacity: '1', transform: 'scale(1)' },
|
|
307
|
+
},
|
|
308
|
+
glow: {
|
|
309
|
+
'0%': { boxShadow: '0 0 20px rgba(16, 185, 129, 0.2)' },
|
|
310
|
+
'100%': { boxShadow: '0 0 40px rgba(16, 185, 129, 0.4)' },
|
|
311
|
+
},
|
|
312
|
+
},
|
|
284
313
|
},
|
|
285
314
|
},
|
|
286
|
-
plugins: [
|
|
315
|
+
plugins: [],
|
|
287
316
|
};
|
|
288
317
|
`,
|
|
289
318
|
|
|
@@ -295,158 +324,398 @@ module.exports = {
|
|
|
295
324
|
};
|
|
296
325
|
`,
|
|
297
326
|
|
|
298
|
-
'flexireact.config.js': (
|
|
327
|
+
'flexireact.config.js': () => `/** @type {import('@flexireact/core').Config} */
|
|
299
328
|
export default {
|
|
300
|
-
// Styles to include
|
|
301
329
|
styles: [
|
|
302
330
|
'/styles.css',
|
|
303
|
-
'https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap'
|
|
331
|
+
'https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap'
|
|
304
332
|
],
|
|
305
|
-
|
|
306
|
-
// Favicon
|
|
307
333
|
favicon: '/favicon.svg',
|
|
308
|
-
|
|
309
|
-
// Server options
|
|
310
334
|
server: {
|
|
311
335
|
port: 3000
|
|
312
336
|
},
|
|
313
|
-
|
|
314
|
-
// Islands (partial hydration)
|
|
315
337
|
islands: {
|
|
316
338
|
enabled: true
|
|
317
339
|
}
|
|
318
340
|
};
|
|
319
341
|
`,
|
|
320
342
|
|
|
321
|
-
|
|
343
|
+
// ============================================================================
|
|
344
|
+
// Components
|
|
345
|
+
// ============================================================================
|
|
322
346
|
|
|
323
|
-
|
|
347
|
+
'components/ui/button.tsx': () => `import React from 'react';
|
|
348
|
+
import { cn } from '../../lib/utils';
|
|
349
|
+
|
|
350
|
+
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
351
|
+
variant?: 'default' | 'outline' | 'ghost';
|
|
352
|
+
size?: 'default' | 'sm' | 'lg';
|
|
353
|
+
children: React.ReactNode;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
export function Button({
|
|
357
|
+
className,
|
|
358
|
+
variant = 'default',
|
|
359
|
+
size = 'default',
|
|
360
|
+
children,
|
|
361
|
+
...props
|
|
362
|
+
}: ButtonProps) {
|
|
324
363
|
return (
|
|
325
|
-
<
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
364
|
+
<button
|
|
365
|
+
className={cn(
|
|
366
|
+
'inline-flex items-center justify-center rounded-lg font-medium transition-all duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50',
|
|
367
|
+
{
|
|
368
|
+
'bg-primary text-primary-foreground hover:bg-primary/90 shadow-lg shadow-primary/25': variant === 'default',
|
|
369
|
+
'border border-border bg-transparent hover:bg-secondary hover:text-foreground': variant === 'outline',
|
|
370
|
+
'hover:bg-secondary hover:text-foreground': variant === 'ghost',
|
|
371
|
+
},
|
|
372
|
+
{
|
|
373
|
+
'h-10 px-4 py-2 text-sm': size === 'default',
|
|
374
|
+
'h-9 px-3 text-sm': size === 'sm',
|
|
375
|
+
'h-12 px-8 text-base': size === 'lg',
|
|
376
|
+
},
|
|
377
|
+
className
|
|
378
|
+
)}
|
|
379
|
+
{...props}
|
|
380
|
+
>
|
|
381
|
+
{children}
|
|
382
|
+
</button>
|
|
383
|
+
);
|
|
384
|
+
}
|
|
385
|
+
`,
|
|
339
386
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
387
|
+
'components/ui/card.tsx': () => `import React from 'react';
|
|
388
|
+
import { cn } from '../../lib/utils';
|
|
389
|
+
|
|
390
|
+
interface CardProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
391
|
+
children: React.ReactNode;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
export function Card({ className, children, ...props }: CardProps) {
|
|
395
|
+
return (
|
|
396
|
+
<div
|
|
397
|
+
className={cn(
|
|
398
|
+
'rounded-xl border border-border bg-card text-card-foreground shadow-sm transition-all duration-300 hover:border-primary/50 hover:shadow-lg hover:shadow-primary/5',
|
|
399
|
+
className
|
|
400
|
+
)}
|
|
401
|
+
{...props}
|
|
402
|
+
>
|
|
403
|
+
{children}
|
|
349
404
|
</div>
|
|
350
405
|
);
|
|
351
406
|
}
|
|
352
|
-
` : `import React from 'react';
|
|
353
407
|
|
|
354
|
-
export
|
|
408
|
+
export function CardHeader({ className, children, ...props }: CardProps) {
|
|
355
409
|
return (
|
|
356
|
-
<div className=
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
410
|
+
<div className={cn('flex flex-col space-y-1.5 p-6', className)} {...props}>
|
|
411
|
+
{children}
|
|
412
|
+
</div>
|
|
413
|
+
);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
export function CardTitle({ className, children, ...props }: CardProps) {
|
|
417
|
+
return (
|
|
418
|
+
<h3 className={cn('text-lg font-semibold leading-none tracking-tight', className)} {...props}>
|
|
419
|
+
{children}
|
|
420
|
+
</h3>
|
|
421
|
+
);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
export function CardDescription({ className, children, ...props }: CardProps) {
|
|
425
|
+
return (
|
|
426
|
+
<p className={cn('text-sm text-muted-foreground', className)} {...props}>
|
|
427
|
+
{children}
|
|
428
|
+
</p>
|
|
429
|
+
);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
export function CardContent({ className, children, ...props }: CardProps) {
|
|
433
|
+
return (
|
|
434
|
+
<div className={cn('p-6 pt-0', className)} {...props}>
|
|
435
|
+
{children}
|
|
436
|
+
</div>
|
|
437
|
+
);
|
|
438
|
+
}
|
|
439
|
+
`,
|
|
440
|
+
|
|
441
|
+
'components/ui/badge.tsx': () => `import React from 'react';
|
|
442
|
+
import { cn } from '../../lib/utils';
|
|
443
|
+
|
|
444
|
+
interface BadgeProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
445
|
+
variant?: 'default' | 'secondary' | 'outline';
|
|
446
|
+
children: React.ReactNode;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
export function Badge({ className, variant = 'default', children, ...props }: BadgeProps) {
|
|
450
|
+
return (
|
|
451
|
+
<div
|
|
452
|
+
className={cn(
|
|
453
|
+
'inline-flex items-center rounded-full px-3 py-1 text-xs font-medium transition-colors',
|
|
454
|
+
{
|
|
455
|
+
'bg-primary/10 text-primary border border-primary/20': variant === 'default',
|
|
456
|
+
'bg-secondary text-secondary-foreground': variant === 'secondary',
|
|
457
|
+
'border border-border text-foreground': variant === 'outline',
|
|
458
|
+
},
|
|
459
|
+
className
|
|
460
|
+
)}
|
|
461
|
+
{...props}
|
|
462
|
+
>
|
|
463
|
+
{children}
|
|
464
|
+
</div>
|
|
465
|
+
);
|
|
466
|
+
}
|
|
467
|
+
`,
|
|
468
|
+
|
|
469
|
+
'components/Navbar.tsx': () => `import React from 'react';
|
|
470
|
+
import { Button } from './ui/button';
|
|
471
|
+
|
|
472
|
+
export function Navbar() {
|
|
473
|
+
return (
|
|
474
|
+
<header className="sticky top-0 z-50 w-full border-b border-border/40 bg-background/80 backdrop-blur-xl">
|
|
475
|
+
<nav className="container mx-auto flex h-16 max-w-6xl items-center justify-between px-4">
|
|
476
|
+
<a href="/" className="flex items-center gap-2 transition-opacity hover:opacity-80">
|
|
477
|
+
<div className="flex h-8 w-8 items-center justify-center rounded-lg bg-gradient-to-br from-primary to-emerald-400">
|
|
478
|
+
<svg className="h-4 w-4 text-primary-foreground" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2.5}>
|
|
479
|
+
<path strokeLinecap="round" strokeLinejoin="round" d="M13 10V3L4 14h7v7l9-11h-7z" />
|
|
480
|
+
</svg>
|
|
481
|
+
</div>
|
|
482
|
+
<span className="text-lg font-bold">FlexiReact</span>
|
|
483
|
+
</a>
|
|
370
484
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
485
|
+
<div className="flex items-center gap-2">
|
|
486
|
+
<Button variant="ghost" size="sm" asChild>
|
|
487
|
+
<a href="https://github.com/flexireact/flexireact">Docs</a>
|
|
488
|
+
</Button>
|
|
489
|
+
<Button variant="ghost" size="sm" asChild>
|
|
490
|
+
<a href="https://github.com/flexireact/flexireact" className="flex items-center gap-2">
|
|
491
|
+
<svg className="h-4 w-4" fill="currentColor" viewBox="0 0 24 24">
|
|
492
|
+
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/>
|
|
493
|
+
</svg>
|
|
494
|
+
GitHub
|
|
495
|
+
</a>
|
|
496
|
+
</Button>
|
|
497
|
+
</div>
|
|
498
|
+
</nav>
|
|
499
|
+
</header>
|
|
500
|
+
);
|
|
501
|
+
}
|
|
502
|
+
`,
|
|
503
|
+
|
|
504
|
+
'components/Hero.tsx': () => `import React from 'react';
|
|
505
|
+
import { Button } from './ui/button';
|
|
506
|
+
import { Badge } from './ui/badge';
|
|
376
507
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
</a>
|
|
385
|
-
<a
|
|
386
|
-
href="https://github.com/flexireact/flexireact"
|
|
387
|
-
className="px-8 py-4 bg-slate-800 text-white font-semibold rounded-xl border border-slate-700 hover:bg-slate-700 transition-all"
|
|
388
|
-
>
|
|
389
|
-
GitHub
|
|
390
|
-
</a>
|
|
508
|
+
export function Hero() {
|
|
509
|
+
return (
|
|
510
|
+
<section className="relative overflow-hidden">
|
|
511
|
+
{/* Gradient Background */}
|
|
512
|
+
<div className="absolute inset-0 -z-10">
|
|
513
|
+
<div className="absolute left-1/2 top-0 -translate-x-1/2 -translate-y-1/2">
|
|
514
|
+
<div className="h-[600px] w-[600px] rounded-full bg-gradient-to-r from-primary/20 via-emerald-500/10 to-cyan-500/20 blur-3xl" />
|
|
391
515
|
</div>
|
|
516
|
+
<div className="absolute right-0 top-1/2 -translate-y-1/2">
|
|
517
|
+
<div className="h-[400px] w-[400px] rounded-full bg-gradient-to-l from-primary/10 to-transparent blur-3xl" />
|
|
518
|
+
</div>
|
|
519
|
+
</div>
|
|
392
520
|
|
|
393
|
-
|
|
394
|
-
<div className="
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
<
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
<
|
|
521
|
+
<div className="container mx-auto max-w-6xl px-4 py-24 sm:py-32 lg:py-40">
|
|
522
|
+
<div className="flex flex-col items-center text-center">
|
|
523
|
+
{/* Badge */}
|
|
524
|
+
<Badge className="mb-6 animate-fade-in">
|
|
525
|
+
<span className="mr-1">⚡</span> The Modern React Framework
|
|
526
|
+
</Badge>
|
|
527
|
+
|
|
528
|
+
{/* Title */}
|
|
529
|
+
<h1 className="mb-6 max-w-4xl animate-fade-up text-4xl font-extrabold tracking-tight sm:text-5xl md:text-6xl lg:text-7xl">
|
|
530
|
+
Build{' '}
|
|
531
|
+
<span className="bg-gradient-to-r from-primary via-emerald-400 to-cyan-400 bg-clip-text text-transparent">
|
|
532
|
+
blazing fast
|
|
533
|
+
</span>{' '}
|
|
534
|
+
web apps
|
|
535
|
+
</h1>
|
|
536
|
+
|
|
537
|
+
{/* Subtitle */}
|
|
538
|
+
<p className="mb-10 max-w-2xl animate-fade-up text-lg text-muted-foreground sm:text-xl" style={{ animationDelay: '0.1s' }}>
|
|
539
|
+
A modern React framework with TypeScript, Tailwind CSS, SSR, SSG,
|
|
540
|
+
Islands architecture, and file-based routing. Ship faster.
|
|
541
|
+
</p>
|
|
542
|
+
|
|
543
|
+
{/* CTA Buttons */}
|
|
544
|
+
<div className="flex flex-wrap items-center justify-center gap-4 animate-fade-up" style={{ animationDelay: '0.2s' }}>
|
|
545
|
+
<Button size="lg" className="gap-2">
|
|
546
|
+
Start Building
|
|
547
|
+
<svg className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
|
548
|
+
<path strokeLinecap="round" strokeLinejoin="round" d="M13 7l5 5m0 0l-5 5m5-5H6" />
|
|
549
|
+
</svg>
|
|
550
|
+
</Button>
|
|
551
|
+
<Button variant="outline" size="lg" asChild>
|
|
552
|
+
<a href="https://github.com/flexireact/flexireact" className="gap-2">
|
|
553
|
+
<svg className="h-4 w-4" fill="currentColor" viewBox="0 0 24 24">
|
|
554
|
+
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/>
|
|
555
|
+
</svg>
|
|
556
|
+
GitHub
|
|
557
|
+
</a>
|
|
558
|
+
</Button>
|
|
404
559
|
</div>
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
<
|
|
560
|
+
|
|
561
|
+
{/* Code Preview */}
|
|
562
|
+
<div className="mt-16 w-full max-w-2xl animate-fade-up rounded-xl border border-border bg-card/50 p-4 backdrop-blur-sm" style={{ animationDelay: '0.3s' }}>
|
|
563
|
+
<div className="flex items-center gap-2 border-b border-border pb-3">
|
|
564
|
+
<div className="h-3 w-3 rounded-full bg-red-500/80" />
|
|
565
|
+
<div className="h-3 w-3 rounded-full bg-yellow-500/80" />
|
|
566
|
+
<div className="h-3 w-3 rounded-full bg-green-500/80" />
|
|
567
|
+
<span className="ml-2 text-xs text-muted-foreground">terminal</span>
|
|
568
|
+
</div>
|
|
569
|
+
<pre className="mt-4 overflow-x-auto text-left text-sm">
|
|
570
|
+
<code className="text-muted-foreground">
|
|
571
|
+
<span className="text-muted-foreground/60">$</span>{' '}
|
|
572
|
+
<span className="text-primary">npx</span> create-flexireact@latest my-app{'\n'}
|
|
573
|
+
<span className="text-muted-foreground/60">$</span>{' '}
|
|
574
|
+
<span className="text-primary">cd</span> my-app{'\n'}
|
|
575
|
+
<span className="text-muted-foreground/60">$</span>{' '}
|
|
576
|
+
<span className="text-primary">npm</span> run dev{'\n'}
|
|
577
|
+
{'\n'}
|
|
578
|
+
<span className="text-emerald-400">✓</span> Ready in <span className="text-primary">38ms</span>
|
|
579
|
+
</code>
|
|
580
|
+
</pre>
|
|
409
581
|
</div>
|
|
410
582
|
</div>
|
|
411
583
|
</div>
|
|
412
|
-
</
|
|
584
|
+
</section>
|
|
413
585
|
);
|
|
414
586
|
}
|
|
415
587
|
`,
|
|
416
588
|
|
|
417
|
-
'
|
|
589
|
+
'components/Features.tsx': () => `import React from 'react';
|
|
590
|
+
import { Card, CardHeader, CardTitle, CardDescription } from './ui/card';
|
|
591
|
+
|
|
592
|
+
const features = [
|
|
593
|
+
{
|
|
594
|
+
icon: (
|
|
595
|
+
<svg className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
|
596
|
+
<path strokeLinecap="round" strokeLinejoin="round" d="M13 10V3L4 14h7v7l9-11h-7z" />
|
|
597
|
+
</svg>
|
|
598
|
+
),
|
|
599
|
+
title: 'Lightning Fast',
|
|
600
|
+
description: 'Powered by esbuild for instant builds and sub-second hot module replacement.',
|
|
601
|
+
},
|
|
602
|
+
{
|
|
603
|
+
icon: (
|
|
604
|
+
<svg className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
|
605
|
+
<path strokeLinecap="round" strokeLinejoin="round" d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z" />
|
|
606
|
+
</svg>
|
|
607
|
+
),
|
|
608
|
+
title: 'File-based Routing',
|
|
609
|
+
description: 'Create a file in pages/, get a route automatically. Simple and intuitive.',
|
|
610
|
+
},
|
|
611
|
+
{
|
|
612
|
+
icon: (
|
|
613
|
+
<svg className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
|
614
|
+
<path strokeLinecap="round" strokeLinejoin="round" d="M5 3v4M3 5h4M6 17v4m-2-2h4m5-16l2.286 6.857L21 12l-5.714 2.143L13 21l-2.286-6.857L5 12l5.714-2.143L13 3z" />
|
|
615
|
+
</svg>
|
|
616
|
+
),
|
|
617
|
+
title: 'Islands Architecture',
|
|
618
|
+
description: 'Partial hydration for minimal JavaScript. Only hydrate what needs interactivity.',
|
|
619
|
+
},
|
|
620
|
+
{
|
|
621
|
+
icon: (
|
|
622
|
+
<svg className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
|
623
|
+
<path strokeLinecap="round" strokeLinejoin="round" d="M5 12h14M5 12a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v4a2 2 0 01-2 2M5 12a2 2 0 00-2 2v4a2 2 0 002 2h14a2 2 0 002-2v-4a2 2 0 00-2-2m-2-4h.01M17 16h.01" />
|
|
624
|
+
</svg>
|
|
625
|
+
),
|
|
626
|
+
title: 'SSR & SSG',
|
|
627
|
+
description: 'Server-side rendering and static generation out of the box. SEO friendly.',
|
|
628
|
+
},
|
|
629
|
+
];
|
|
418
630
|
|
|
419
|
-
|
|
420
|
-
|
|
631
|
+
export function Features() {
|
|
632
|
+
return (
|
|
633
|
+
<section className="container mx-auto max-w-6xl px-4 py-24">
|
|
634
|
+
<div className="mb-12 text-center">
|
|
635
|
+
<h2 className="mb-4 text-3xl font-bold tracking-tight sm:text-4xl">
|
|
636
|
+
Everything you need
|
|
637
|
+
</h2>
|
|
638
|
+
<p className="mx-auto max-w-2xl text-muted-foreground">
|
|
639
|
+
A complete toolkit for building modern web applications with React.
|
|
640
|
+
</p>
|
|
641
|
+
</div>
|
|
642
|
+
|
|
643
|
+
<div className="grid gap-6 sm:grid-cols-2 lg:grid-cols-4">
|
|
644
|
+
{features.map((feature, index) => (
|
|
645
|
+
<Card key={index} className="group cursor-default">
|
|
646
|
+
<CardHeader>
|
|
647
|
+
<div className="mb-3 inline-flex h-12 w-12 items-center justify-center rounded-lg bg-primary/10 text-primary transition-colors group-hover:bg-primary group-hover:text-primary-foreground">
|
|
648
|
+
{feature.icon}
|
|
649
|
+
</div>
|
|
650
|
+
<CardTitle>{feature.title}</CardTitle>
|
|
651
|
+
<CardDescription>{feature.description}</CardDescription>
|
|
652
|
+
</CardHeader>
|
|
653
|
+
</Card>
|
|
654
|
+
))}
|
|
655
|
+
</div>
|
|
656
|
+
</section>
|
|
657
|
+
);
|
|
421
658
|
}
|
|
659
|
+
`,
|
|
422
660
|
|
|
423
|
-
|
|
661
|
+
'components/Footer.tsx': () => `import React from 'react';
|
|
662
|
+
|
|
663
|
+
export function Footer() {
|
|
424
664
|
return (
|
|
425
|
-
<
|
|
426
|
-
<
|
|
427
|
-
<
|
|
428
|
-
<
|
|
429
|
-
<
|
|
430
|
-
|
|
431
|
-
</
|
|
432
|
-
<
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
<a href="/" className="text-sm opacity-70 hover:opacity-100">Home</a>
|
|
436
|
-
<a href="/about" className="text-sm opacity-70 hover:opacity-100">About</a>
|
|
665
|
+
<footer className="border-t border-border">
|
|
666
|
+
<div className="container mx-auto max-w-6xl px-4 py-8">
|
|
667
|
+
<div className="flex flex-col items-center justify-between gap-4 sm:flex-row">
|
|
668
|
+
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
|
669
|
+
<span>Built with</span>
|
|
670
|
+
<span className="text-red-500">❤️</span>
|
|
671
|
+
<span>using</span>
|
|
672
|
+
<a href="https://github.com/flexireact/flexireact" className="font-medium text-foreground hover:text-primary transition-colors">
|
|
673
|
+
FlexiReact
|
|
674
|
+
</a>
|
|
437
675
|
</div>
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
676
|
+
<div className="flex items-center gap-4 text-sm text-muted-foreground">
|
|
677
|
+
<a href="https://github.com/flexireact/flexireact" className="hover:text-foreground transition-colors">
|
|
678
|
+
GitHub
|
|
679
|
+
</a>
|
|
680
|
+
<a href="https://github.com/flexireact/flexireact" className="hover:text-foreground transition-colors">
|
|
681
|
+
Documentation
|
|
682
|
+
</a>
|
|
683
|
+
</div>
|
|
684
|
+
</div>
|
|
685
|
+
</div>
|
|
686
|
+
</footer>
|
|
447
687
|
);
|
|
448
688
|
}
|
|
449
|
-
|
|
689
|
+
`,
|
|
690
|
+
|
|
691
|
+
'components/index.ts': () => `export { Navbar } from './Navbar';
|
|
692
|
+
export { Hero } from './Hero';
|
|
693
|
+
export { Features } from './Features';
|
|
694
|
+
export { Footer } from './Footer';
|
|
695
|
+
export { Button } from './ui/button';
|
|
696
|
+
export { Card, CardHeader, CardTitle, CardDescription, CardContent } from './ui/card';
|
|
697
|
+
export { Badge } from './ui/badge';
|
|
698
|
+
`,
|
|
699
|
+
|
|
700
|
+
// ============================================================================
|
|
701
|
+
// Lib
|
|
702
|
+
// ============================================================================
|
|
703
|
+
|
|
704
|
+
'lib/utils.ts': () => `import { clsx, type ClassValue } from 'clsx';
|
|
705
|
+
import { twMerge } from 'tailwind-merge';
|
|
706
|
+
|
|
707
|
+
export function cn(...inputs: ClassValue[]) {
|
|
708
|
+
return twMerge(clsx(inputs));
|
|
709
|
+
}
|
|
710
|
+
`,
|
|
711
|
+
|
|
712
|
+
// ============================================================================
|
|
713
|
+
// Pages & Layouts
|
|
714
|
+
// ============================================================================
|
|
715
|
+
|
|
716
|
+
'layouts/root.tsx': () => `import React from 'react';
|
|
717
|
+
import { Navbar } from '../components/Navbar';
|
|
718
|
+
import { Footer } from '../components/Footer';
|
|
450
719
|
|
|
451
720
|
interface LayoutProps {
|
|
452
721
|
children: React.ReactNode;
|
|
@@ -454,60 +723,90 @@ interface LayoutProps {
|
|
|
454
723
|
|
|
455
724
|
export default function RootLayout({ children }: LayoutProps) {
|
|
456
725
|
return (
|
|
457
|
-
<div className="min-h-screen bg-
|
|
458
|
-
<
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
<span className="text-emerald-400">⚡</span> FlexiReact
|
|
462
|
-
</a>
|
|
463
|
-
<div className="flex items-center gap-6">
|
|
464
|
-
<a href="/" className="text-sm text-slate-400 hover:text-white">Home</a>
|
|
465
|
-
<a href="/about" className="text-sm text-slate-400 hover:text-white">About</a>
|
|
466
|
-
<a href="https://github.com/flexireact/flexireact" className="text-sm px-4 py-2 bg-emerald-500 text-black rounded-lg font-medium hover:bg-emerald-400">
|
|
467
|
-
GitHub
|
|
468
|
-
</a>
|
|
469
|
-
</div>
|
|
470
|
-
</nav>
|
|
471
|
-
</header>
|
|
472
|
-
<main className="flex-1">
|
|
473
|
-
{children}
|
|
474
|
-
</main>
|
|
475
|
-
<footer className="border-t border-slate-700 py-8 text-center text-sm text-slate-500">
|
|
476
|
-
Built with ❤️ using FlexiReact
|
|
477
|
-
</footer>
|
|
726
|
+
<div className="relative min-h-screen bg-background text-foreground antialiased">
|
|
727
|
+
<Navbar />
|
|
728
|
+
<main>{children}</main>
|
|
729
|
+
<Footer />
|
|
478
730
|
</div>
|
|
479
731
|
);
|
|
480
732
|
}
|
|
481
733
|
`,
|
|
482
734
|
|
|
483
|
-
'
|
|
735
|
+
'pages/index.tsx': () => `import React from 'react';
|
|
736
|
+
import { Hero } from '../components/Hero';
|
|
737
|
+
import { Features } from '../components/Features';
|
|
738
|
+
|
|
739
|
+
export default function HomePage() {
|
|
740
|
+
return (
|
|
741
|
+
<>
|
|
742
|
+
<Hero />
|
|
743
|
+
<Features />
|
|
744
|
+
</>
|
|
745
|
+
);
|
|
746
|
+
}
|
|
747
|
+
`,
|
|
748
|
+
|
|
749
|
+
// ============================================================================
|
|
750
|
+
// Styles
|
|
751
|
+
// ============================================================================
|
|
752
|
+
|
|
753
|
+
'app/styles/globals.css': () => `@tailwind base;
|
|
484
754
|
@tailwind components;
|
|
485
755
|
@tailwind utilities;
|
|
486
756
|
|
|
487
757
|
@layer base {
|
|
488
758
|
:root {
|
|
489
|
-
--
|
|
490
|
-
--
|
|
491
|
-
--
|
|
492
|
-
--
|
|
493
|
-
--
|
|
494
|
-
--
|
|
495
|
-
--
|
|
759
|
+
--background: 240 10% 3.9%;
|
|
760
|
+
--foreground: 0 0% 98%;
|
|
761
|
+
--card: 240 10% 3.9%;
|
|
762
|
+
--card-foreground: 0 0% 98%;
|
|
763
|
+
--popover: 240 10% 3.9%;
|
|
764
|
+
--popover-foreground: 0 0% 98%;
|
|
765
|
+
--primary: 142.1 76.2% 36.3%;
|
|
766
|
+
--primary-foreground: 144.9 80.4% 10%;
|
|
767
|
+
--secondary: 240 3.7% 15.9%;
|
|
768
|
+
--secondary-foreground: 0 0% 98%;
|
|
769
|
+
--muted: 240 3.7% 15.9%;
|
|
770
|
+
--muted-foreground: 240 5% 64.9%;
|
|
771
|
+
--accent: 240 3.7% 15.9%;
|
|
772
|
+
--accent-foreground: 0 0% 98%;
|
|
773
|
+
--destructive: 0 62.8% 30.6%;
|
|
774
|
+
--destructive-foreground: 0 0% 98%;
|
|
775
|
+
--border: 240 3.7% 15.9%;
|
|
776
|
+
--input: 240 3.7% 15.9%;
|
|
777
|
+
--ring: 142.1 76.2% 36.3%;
|
|
778
|
+
--radius: 0.75rem;
|
|
496
779
|
}
|
|
497
|
-
|
|
780
|
+
|
|
781
|
+
* {
|
|
782
|
+
border-color: hsl(var(--border));
|
|
783
|
+
}
|
|
784
|
+
|
|
498
785
|
html {
|
|
499
786
|
scroll-behavior: smooth;
|
|
500
787
|
}
|
|
501
|
-
|
|
788
|
+
|
|
502
789
|
body {
|
|
503
|
-
font-family: 'Inter', system-ui, sans-serif;
|
|
504
|
-
background-color:
|
|
505
|
-
color:
|
|
790
|
+
font-family: 'Inter', system-ui, -apple-system, sans-serif;
|
|
791
|
+
background-color: hsl(var(--background));
|
|
792
|
+
color: hsl(var(--foreground));
|
|
506
793
|
min-height: 100vh;
|
|
794
|
+
-webkit-font-smoothing: antialiased;
|
|
795
|
+
-moz-osx-font-smoothing: grayscale;
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
@layer utilities {
|
|
800
|
+
.text-balance {
|
|
801
|
+
text-wrap: balance;
|
|
507
802
|
}
|
|
508
803
|
}
|
|
509
804
|
`,
|
|
510
805
|
|
|
806
|
+
// ============================================================================
|
|
807
|
+
// Public Assets
|
|
808
|
+
// ============================================================================
|
|
809
|
+
|
|
511
810
|
'public/.gitkeep': () => '',
|
|
512
811
|
|
|
513
812
|
'public/favicon.svg': () => `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
|
|
@@ -517,12 +816,57 @@ export default function RootLayout({ children }: LayoutProps) {
|
|
|
517
816
|
<stop offset="100%" style="stop-color:#06b6d4"/>
|
|
518
817
|
</linearGradient>
|
|
519
818
|
</defs>
|
|
520
|
-
<rect width="100" height="100" rx="20" fill="#
|
|
521
|
-
<path d="
|
|
522
|
-
<circle cx="65" cy="65" r="8" fill="url(#grad)"/>
|
|
819
|
+
<rect width="100" height="100" rx="20" fill="#0a0a0a"/>
|
|
820
|
+
<path d="M50 20L30 55h15v25l20-35H50V20z" fill="url(#grad)"/>
|
|
523
821
|
</svg>`,
|
|
524
822
|
};
|
|
525
823
|
|
|
824
|
+
// Minimal template files
|
|
825
|
+
const MINIMAL_FILES = {
|
|
826
|
+
'package.json': (name) => JSON.stringify({
|
|
827
|
+
name: name,
|
|
828
|
+
version: "1.0.0",
|
|
829
|
+
private: true,
|
|
830
|
+
type: "module",
|
|
831
|
+
scripts: {
|
|
832
|
+
dev: "flexireact dev",
|
|
833
|
+
build: "flexireact build",
|
|
834
|
+
start: "flexireact start"
|
|
835
|
+
},
|
|
836
|
+
dependencies: {
|
|
837
|
+
"react": "^18.2.0",
|
|
838
|
+
"react-dom": "^18.2.0",
|
|
839
|
+
"@flexireact/core": "^1.0.0"
|
|
840
|
+
},
|
|
841
|
+
devDependencies: {
|
|
842
|
+
"@types/react": "^18.2.0",
|
|
843
|
+
"@types/react-dom": "^18.2.0",
|
|
844
|
+
"typescript": "^5.3.0"
|
|
845
|
+
}
|
|
846
|
+
}, null, 2),
|
|
847
|
+
|
|
848
|
+
'tsconfig.json': TEMPLATE_FILES['tsconfig.json'],
|
|
849
|
+
|
|
850
|
+
'flexireact.config.js': () => `export default {
|
|
851
|
+
server: { port: 3000 }
|
|
852
|
+
};
|
|
853
|
+
`,
|
|
854
|
+
|
|
855
|
+
'pages/index.tsx': () => `import React from 'react';
|
|
856
|
+
|
|
857
|
+
export default function HomePage() {
|
|
858
|
+
return (
|
|
859
|
+
<div style={{ padding: '2rem', fontFamily: 'system-ui' }}>
|
|
860
|
+
<h1>Welcome to FlexiReact</h1>
|
|
861
|
+
<p>Edit pages/index.tsx to get started.</p>
|
|
862
|
+
</div>
|
|
863
|
+
);
|
|
864
|
+
}
|
|
865
|
+
`,
|
|
866
|
+
|
|
867
|
+
'public/.gitkeep': () => '',
|
|
868
|
+
};
|
|
869
|
+
|
|
526
870
|
// ============================================================================
|
|
527
871
|
// Main
|
|
528
872
|
// ============================================================================
|
|
@@ -548,50 +892,55 @@ async function main() {
|
|
|
548
892
|
|
|
549
893
|
// Check if directory exists
|
|
550
894
|
if (fs.existsSync(projectPath) && !isDirEmpty(projectPath)) {
|
|
551
|
-
error(`Directory
|
|
895
|
+
error(`Directory ${projectName} already exists and is not empty`);
|
|
552
896
|
process.exit(1);
|
|
553
897
|
}
|
|
554
898
|
|
|
555
|
-
console.log('');
|
|
556
|
-
|
|
557
899
|
// Select template
|
|
900
|
+
console.log('');
|
|
558
901
|
const templateOptions = Object.entries(TEMPLATES).map(([key, value]) => ({
|
|
559
902
|
key,
|
|
560
903
|
...value,
|
|
561
904
|
}));
|
|
562
905
|
|
|
563
906
|
const selectedTemplate = await select('Select a template:', templateOptions);
|
|
564
|
-
const
|
|
907
|
+
const templateKey = selectedTemplate.key;
|
|
565
908
|
|
|
566
909
|
console.log('');
|
|
567
|
-
|
|
910
|
+
log(`Creating project in ${c.cyan}${projectPath}${c.reset}`);
|
|
568
911
|
console.log('');
|
|
569
912
|
|
|
570
913
|
// Create project directory
|
|
571
914
|
const spinner1 = new Spinner('Creating project structure...');
|
|
572
915
|
spinner1.start();
|
|
573
|
-
|
|
916
|
+
|
|
574
917
|
try {
|
|
575
918
|
fs.mkdirSync(projectPath, { recursive: true });
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
919
|
+
|
|
920
|
+
// Create subdirectories
|
|
921
|
+
const dirs = templateKey === 'minimal'
|
|
922
|
+
? ['pages', 'public']
|
|
923
|
+
: ['pages', 'public', 'components', 'components/ui', 'layouts', 'app/styles', 'lib'];
|
|
924
|
+
|
|
925
|
+
for (const dir of dirs) {
|
|
926
|
+
fs.mkdirSync(path.join(projectPath, dir), { recursive: true });
|
|
927
|
+
}
|
|
581
928
|
|
|
582
929
|
spinner1.stop(true);
|
|
583
930
|
} catch (err) {
|
|
584
931
|
spinner1.stop(false);
|
|
585
|
-
error(`Failed to create
|
|
932
|
+
error(`Failed to create project structure: ${err.message}`);
|
|
586
933
|
process.exit(1);
|
|
587
934
|
}
|
|
588
935
|
|
|
589
936
|
// Write template files
|
|
590
937
|
const spinner2 = new Spinner('Writing template files...');
|
|
591
938
|
spinner2.start();
|
|
592
|
-
|
|
939
|
+
|
|
593
940
|
try {
|
|
594
|
-
|
|
941
|
+
const files = templateKey === 'minimal' ? MINIMAL_FILES : TEMPLATE_FILES;
|
|
942
|
+
|
|
943
|
+
for (const [filePath, contentFn] of Object.entries(files)) {
|
|
595
944
|
const fullPath = path.join(projectPath, filePath);
|
|
596
945
|
const dir = path.dirname(fullPath);
|
|
597
946
|
|
|
@@ -599,51 +948,25 @@ async function main() {
|
|
|
599
948
|
fs.mkdirSync(dir, { recursive: true });
|
|
600
949
|
}
|
|
601
950
|
|
|
602
|
-
const content =
|
|
951
|
+
const content = contentFn(projectName, templateKey);
|
|
603
952
|
fs.writeFileSync(fullPath, content);
|
|
604
953
|
}
|
|
605
954
|
|
|
606
955
|
spinner2.stop(true);
|
|
607
956
|
} catch (err) {
|
|
608
957
|
spinner2.stop(false);
|
|
609
|
-
error(`Failed to write files: ${err.message}`);
|
|
958
|
+
error(`Failed to write template files: ${err.message}`);
|
|
610
959
|
process.exit(1);
|
|
611
960
|
}
|
|
612
961
|
|
|
613
|
-
// Create
|
|
962
|
+
// Create README
|
|
614
963
|
const spinner3 = new Spinner('Creating configuration files...');
|
|
615
964
|
spinner3.start();
|
|
616
|
-
|
|
965
|
+
|
|
617
966
|
try {
|
|
618
|
-
|
|
619
|
-
node_modules/
|
|
620
|
-
|
|
621
|
-
# Build
|
|
622
|
-
.flexi/
|
|
623
|
-
dist/
|
|
624
|
-
build/
|
|
625
|
-
|
|
626
|
-
# Environment
|
|
627
|
-
.env
|
|
628
|
-
.env.local
|
|
629
|
-
.env.*.local
|
|
967
|
+
const readmeContent = `# ${projectName}
|
|
630
968
|
|
|
631
|
-
|
|
632
|
-
.vscode/
|
|
633
|
-
.idea/
|
|
634
|
-
|
|
635
|
-
# OS
|
|
636
|
-
.DS_Store
|
|
637
|
-
Thumbs.db
|
|
638
|
-
|
|
639
|
-
# Logs
|
|
640
|
-
*.log
|
|
641
|
-
npm-debug.log*
|
|
642
|
-
`);
|
|
643
|
-
|
|
644
|
-
fs.writeFileSync(path.join(projectPath, 'README.md'), `# ${projectName}
|
|
645
|
-
|
|
646
|
-
A FlexiReact application.
|
|
969
|
+
A modern web application built with [FlexiReact](https://github.com/flexireact/flexireact).
|
|
647
970
|
|
|
648
971
|
## Getting Started
|
|
649
972
|
|
|
@@ -657,9 +980,9 @@ Open [http://localhost:3000](http://localhost:3000) in your browser.
|
|
|
657
980
|
## Learn More
|
|
658
981
|
|
|
659
982
|
- [FlexiReact Documentation](https://github.com/flexireact/flexireact)
|
|
660
|
-
|
|
661
|
-
`);
|
|
983
|
+
`;
|
|
662
984
|
|
|
985
|
+
fs.writeFileSync(path.join(projectPath, 'README.md'), readmeContent);
|
|
663
986
|
spinner3.stop(true);
|
|
664
987
|
} catch (err) {
|
|
665
988
|
spinner3.stop(false);
|
|
@@ -667,7 +990,7 @@ Open [http://localhost:3000](http://localhost:3000) in your browser.
|
|
|
667
990
|
process.exit(1);
|
|
668
991
|
}
|
|
669
992
|
|
|
670
|
-
//
|
|
993
|
+
// Success message
|
|
671
994
|
console.log(SUCCESS_BANNER(projectName));
|
|
672
995
|
}
|
|
673
996
|
|