create-flexireact 1.0.0 → 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 +571 -233
- 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,155 +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
|
-
// Server options
|
|
333
|
+
favicon: '/favicon.svg',
|
|
307
334
|
server: {
|
|
308
335
|
port: 3000
|
|
309
336
|
},
|
|
310
|
-
|
|
311
|
-
// Islands (partial hydration)
|
|
312
337
|
islands: {
|
|
313
338
|
enabled: true
|
|
314
339
|
}
|
|
315
340
|
};
|
|
316
341
|
`,
|
|
317
342
|
|
|
318
|
-
|
|
343
|
+
// ============================================================================
|
|
344
|
+
// Components
|
|
345
|
+
// ============================================================================
|
|
319
346
|
|
|
320
|
-
|
|
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) {
|
|
321
363
|
return (
|
|
322
|
-
<
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
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
|
+
`,
|
|
336
386
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
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}
|
|
346
404
|
</div>
|
|
347
405
|
);
|
|
348
406
|
}
|
|
349
|
-
` : `import React from 'react';
|
|
350
407
|
|
|
351
|
-
export
|
|
408
|
+
export function CardHeader({ className, children, ...props }: CardProps) {
|
|
352
409
|
return (
|
|
353
|
-
<div className=
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
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>
|
|
367
484
|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
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
|
+
`,
|
|
373
503
|
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
504
|
+
'components/Hero.tsx': () => `import React from 'react';
|
|
505
|
+
import { Button } from './ui/button';
|
|
506
|
+
import { Badge } from './ui/badge';
|
|
507
|
+
|
|
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" />
|
|
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" />
|
|
388
518
|
</div>
|
|
519
|
+
</div>
|
|
389
520
|
|
|
390
|
-
|
|
391
|
-
<div className="
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
<
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
<
|
|
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>
|
|
401
559
|
</div>
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
<
|
|
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>
|
|
406
581
|
</div>
|
|
407
582
|
</div>
|
|
408
583
|
</div>
|
|
409
|
-
</
|
|
584
|
+
</section>
|
|
410
585
|
);
|
|
411
586
|
}
|
|
412
587
|
`,
|
|
413
588
|
|
|
414
|
-
'
|
|
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
|
+
];
|
|
415
630
|
|
|
416
|
-
|
|
417
|
-
|
|
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
|
+
);
|
|
418
658
|
}
|
|
659
|
+
`,
|
|
419
660
|
|
|
420
|
-
|
|
661
|
+
'components/Footer.tsx': () => `import React from 'react';
|
|
662
|
+
|
|
663
|
+
export function Footer() {
|
|
421
664
|
return (
|
|
422
|
-
<
|
|
423
|
-
<
|
|
424
|
-
<
|
|
425
|
-
<
|
|
426
|
-
<
|
|
427
|
-
|
|
428
|
-
</
|
|
429
|
-
<
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
<a href="/" className="text-sm opacity-70 hover:opacity-100">Home</a>
|
|
433
|
-
<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>
|
|
434
675
|
</div>
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
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>
|
|
444
687
|
);
|
|
445
688
|
}
|
|
446
|
-
|
|
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';
|
|
447
719
|
|
|
448
720
|
interface LayoutProps {
|
|
449
721
|
children: React.ReactNode;
|
|
@@ -451,58 +723,145 @@ interface LayoutProps {
|
|
|
451
723
|
|
|
452
724
|
export default function RootLayout({ children }: LayoutProps) {
|
|
453
725
|
return (
|
|
454
|
-
<div className="min-h-screen bg-
|
|
455
|
-
<
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
<span className="text-emerald-400">⚡</span> FlexiReact
|
|
459
|
-
</a>
|
|
460
|
-
<div className="flex items-center gap-6">
|
|
461
|
-
<a href="/" className="text-sm text-slate-400 hover:text-white">Home</a>
|
|
462
|
-
<a href="/about" className="text-sm text-slate-400 hover:text-white">About</a>
|
|
463
|
-
<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">
|
|
464
|
-
GitHub
|
|
465
|
-
</a>
|
|
466
|
-
</div>
|
|
467
|
-
</nav>
|
|
468
|
-
</header>
|
|
469
|
-
<main className="flex-1">
|
|
470
|
-
{children}
|
|
471
|
-
</main>
|
|
472
|
-
<footer className="border-t border-slate-700 py-8 text-center text-sm text-slate-500">
|
|
473
|
-
Built with ❤️ using FlexiReact
|
|
474
|
-
</footer>
|
|
726
|
+
<div className="relative min-h-screen bg-background text-foreground antialiased">
|
|
727
|
+
<Navbar />
|
|
728
|
+
<main>{children}</main>
|
|
729
|
+
<Footer />
|
|
475
730
|
</div>
|
|
476
731
|
);
|
|
477
732
|
}
|
|
478
733
|
`,
|
|
479
734
|
|
|
480
|
-
'
|
|
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;
|
|
481
754
|
@tailwind components;
|
|
482
755
|
@tailwind utilities;
|
|
483
756
|
|
|
484
757
|
@layer base {
|
|
485
758
|
:root {
|
|
486
|
-
--
|
|
487
|
-
--
|
|
488
|
-
--
|
|
489
|
-
--
|
|
490
|
-
--
|
|
491
|
-
--
|
|
492
|
-
--
|
|
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;
|
|
493
779
|
}
|
|
494
|
-
|
|
780
|
+
|
|
781
|
+
* {
|
|
782
|
+
border-color: hsl(var(--border));
|
|
783
|
+
}
|
|
784
|
+
|
|
495
785
|
html {
|
|
496
786
|
scroll-behavior: smooth;
|
|
497
787
|
}
|
|
498
|
-
|
|
788
|
+
|
|
499
789
|
body {
|
|
500
|
-
font-family: 'Inter', system-ui, sans-serif;
|
|
501
|
-
background-color:
|
|
502
|
-
color:
|
|
790
|
+
font-family: 'Inter', system-ui, -apple-system, sans-serif;
|
|
791
|
+
background-color: hsl(var(--background));
|
|
792
|
+
color: hsl(var(--foreground));
|
|
503
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;
|
|
504
802
|
}
|
|
505
803
|
}
|
|
804
|
+
`,
|
|
805
|
+
|
|
806
|
+
// ============================================================================
|
|
807
|
+
// Public Assets
|
|
808
|
+
// ============================================================================
|
|
809
|
+
|
|
810
|
+
'public/.gitkeep': () => '',
|
|
811
|
+
|
|
812
|
+
'public/favicon.svg': () => `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
|
|
813
|
+
<defs>
|
|
814
|
+
<linearGradient id="grad" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
815
|
+
<stop offset="0%" style="stop-color:#10b981"/>
|
|
816
|
+
<stop offset="100%" style="stop-color:#06b6d4"/>
|
|
817
|
+
</linearGradient>
|
|
818
|
+
</defs>
|
|
819
|
+
<rect width="100" height="100" rx="20" fill="#0a0a0a"/>
|
|
820
|
+
<path d="M50 20L30 55h15v25l20-35H50V20z" fill="url(#grad)"/>
|
|
821
|
+
</svg>`,
|
|
822
|
+
};
|
|
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
|
+
}
|
|
506
865
|
`,
|
|
507
866
|
|
|
508
867
|
'public/.gitkeep': () => '',
|
|
@@ -533,50 +892,55 @@ async function main() {
|
|
|
533
892
|
|
|
534
893
|
// Check if directory exists
|
|
535
894
|
if (fs.existsSync(projectPath) && !isDirEmpty(projectPath)) {
|
|
536
|
-
error(`Directory
|
|
895
|
+
error(`Directory ${projectName} already exists and is not empty`);
|
|
537
896
|
process.exit(1);
|
|
538
897
|
}
|
|
539
898
|
|
|
540
|
-
console.log('');
|
|
541
|
-
|
|
542
899
|
// Select template
|
|
900
|
+
console.log('');
|
|
543
901
|
const templateOptions = Object.entries(TEMPLATES).map(([key, value]) => ({
|
|
544
902
|
key,
|
|
545
903
|
...value,
|
|
546
904
|
}));
|
|
547
905
|
|
|
548
906
|
const selectedTemplate = await select('Select a template:', templateOptions);
|
|
549
|
-
const
|
|
907
|
+
const templateKey = selectedTemplate.key;
|
|
550
908
|
|
|
551
909
|
console.log('');
|
|
552
|
-
|
|
910
|
+
log(`Creating project in ${c.cyan}${projectPath}${c.reset}`);
|
|
553
911
|
console.log('');
|
|
554
912
|
|
|
555
913
|
// Create project directory
|
|
556
914
|
const spinner1 = new Spinner('Creating project structure...');
|
|
557
915
|
spinner1.start();
|
|
558
|
-
|
|
916
|
+
|
|
559
917
|
try {
|
|
560
918
|
fs.mkdirSync(projectPath, { recursive: true });
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
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
|
+
}
|
|
566
928
|
|
|
567
929
|
spinner1.stop(true);
|
|
568
930
|
} catch (err) {
|
|
569
931
|
spinner1.stop(false);
|
|
570
|
-
error(`Failed to create
|
|
932
|
+
error(`Failed to create project structure: ${err.message}`);
|
|
571
933
|
process.exit(1);
|
|
572
934
|
}
|
|
573
935
|
|
|
574
936
|
// Write template files
|
|
575
937
|
const spinner2 = new Spinner('Writing template files...');
|
|
576
938
|
spinner2.start();
|
|
577
|
-
|
|
939
|
+
|
|
578
940
|
try {
|
|
579
|
-
|
|
941
|
+
const files = templateKey === 'minimal' ? MINIMAL_FILES : TEMPLATE_FILES;
|
|
942
|
+
|
|
943
|
+
for (const [filePath, contentFn] of Object.entries(files)) {
|
|
580
944
|
const fullPath = path.join(projectPath, filePath);
|
|
581
945
|
const dir = path.dirname(fullPath);
|
|
582
946
|
|
|
@@ -584,51 +948,25 @@ async function main() {
|
|
|
584
948
|
fs.mkdirSync(dir, { recursive: true });
|
|
585
949
|
}
|
|
586
950
|
|
|
587
|
-
const content =
|
|
951
|
+
const content = contentFn(projectName, templateKey);
|
|
588
952
|
fs.writeFileSync(fullPath, content);
|
|
589
953
|
}
|
|
590
954
|
|
|
591
955
|
spinner2.stop(true);
|
|
592
956
|
} catch (err) {
|
|
593
957
|
spinner2.stop(false);
|
|
594
|
-
error(`Failed to write files: ${err.message}`);
|
|
958
|
+
error(`Failed to write template files: ${err.message}`);
|
|
595
959
|
process.exit(1);
|
|
596
960
|
}
|
|
597
961
|
|
|
598
|
-
// Create
|
|
962
|
+
// Create README
|
|
599
963
|
const spinner3 = new Spinner('Creating configuration files...');
|
|
600
964
|
spinner3.start();
|
|
601
|
-
|
|
965
|
+
|
|
602
966
|
try {
|
|
603
|
-
|
|
604
|
-
node_modules/
|
|
605
|
-
|
|
606
|
-
# Build
|
|
607
|
-
.flexi/
|
|
608
|
-
dist/
|
|
609
|
-
build/
|
|
610
|
-
|
|
611
|
-
# Environment
|
|
612
|
-
.env
|
|
613
|
-
.env.local
|
|
614
|
-
.env.*.local
|
|
967
|
+
const readmeContent = `# ${projectName}
|
|
615
968
|
|
|
616
|
-
|
|
617
|
-
.vscode/
|
|
618
|
-
.idea/
|
|
619
|
-
|
|
620
|
-
# OS
|
|
621
|
-
.DS_Store
|
|
622
|
-
Thumbs.db
|
|
623
|
-
|
|
624
|
-
# Logs
|
|
625
|
-
*.log
|
|
626
|
-
npm-debug.log*
|
|
627
|
-
`);
|
|
628
|
-
|
|
629
|
-
fs.writeFileSync(path.join(projectPath, 'README.md'), `# ${projectName}
|
|
630
|
-
|
|
631
|
-
A FlexiReact application.
|
|
969
|
+
A modern web application built with [FlexiReact](https://github.com/flexireact/flexireact).
|
|
632
970
|
|
|
633
971
|
## Getting Started
|
|
634
972
|
|
|
@@ -642,9 +980,9 @@ Open [http://localhost:3000](http://localhost:3000) in your browser.
|
|
|
642
980
|
## Learn More
|
|
643
981
|
|
|
644
982
|
- [FlexiReact Documentation](https://github.com/flexireact/flexireact)
|
|
645
|
-
|
|
646
|
-
`);
|
|
983
|
+
`;
|
|
647
984
|
|
|
985
|
+
fs.writeFileSync(path.join(projectPath, 'README.md'), readmeContent);
|
|
648
986
|
spinner3.stop(true);
|
|
649
987
|
} catch (err) {
|
|
650
988
|
spinner3.stop(false);
|
|
@@ -652,7 +990,7 @@ Open [http://localhost:3000](http://localhost:3000) in your browser.
|
|
|
652
990
|
process.exit(1);
|
|
653
991
|
}
|
|
654
992
|
|
|
655
|
-
//
|
|
993
|
+
// Success message
|
|
656
994
|
console.log(SUCCESS_BANNER(projectName));
|
|
657
995
|
}
|
|
658
996
|
|