create-flexireact 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +49 -0
  2. package/index.js +662 -0
  3. package/package.json +42 -0
package/README.md ADDED
@@ -0,0 +1,49 @@
1
+ # create-flexireact
2
+
3
+ Create FlexiReact apps with one command.
4
+
5
+ ## Usage
6
+
7
+ ```bash
8
+ npx create-flexireact@latest my-app
9
+ ```
10
+
11
+ Or with npm:
12
+
13
+ ```bash
14
+ npm create flexireact@latest my-app
15
+ ```
16
+
17
+ ## Templates
18
+
19
+ - **Default** - Basic FlexiReact app with TypeScript and Tailwind
20
+ - **FlexiUI** - FlexiReact with FlexiUI component library
21
+ - **Minimal** - Bare minimum FlexiReact setup
22
+
23
+ ## What's included
24
+
25
+ - ⚡ **FlexiReact** - The Modern React Framework
26
+ - 📘 **TypeScript** - Full type safety
27
+ - 🎨 **Tailwind CSS** - Utility-first styling
28
+ - 📁 **File-based routing** - Create a file, get a route
29
+ - 🏝️ **Islands architecture** - Partial hydration
30
+ - 🖥️ **SSR** - Server-side rendering
31
+
32
+ ## After creation
33
+
34
+ ```bash
35
+ cd my-app
36
+ npm install
37
+ npm run dev
38
+ ```
39
+
40
+ Then open http://localhost:3000
41
+
42
+ ## Learn More
43
+
44
+ - [FlexiReact Documentation](https://github.com/flexireact/flexireact)
45
+ - [FlexiUI Components](https://github.com/flexireact/flexi-ui)
46
+
47
+ ## License
48
+
49
+ MIT
package/index.js ADDED
@@ -0,0 +1,662 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * create-flexireact
5
+ * Create FlexiReact apps with one command
6
+ *
7
+ * Usage: npx create-flexireact@latest my-app
8
+ */
9
+
10
+ import fs from 'fs';
11
+ import path from 'path';
12
+ import { execSync, spawn } from 'child_process';
13
+ import readline from 'readline';
14
+ import { fileURLToPath } from 'url';
15
+
16
+ const __filename = fileURLToPath(import.meta.url);
17
+ const __dirname = path.dirname(__filename);
18
+
19
+ // ============================================================================
20
+ // Colors & Styling
21
+ // ============================================================================
22
+
23
+ const c = {
24
+ reset: '\x1b[0m',
25
+ bold: '\x1b[1m',
26
+ dim: '\x1b[2m',
27
+ green: '\x1b[32m',
28
+ cyan: '\x1b[36m',
29
+ yellow: '\x1b[33m',
30
+ red: '\x1b[31m',
31
+ magenta: '\x1b[35m',
32
+ white: '\x1b[37m',
33
+ bgGreen: '\x1b[42m',
34
+ bgCyan: '\x1b[46m',
35
+ };
36
+
37
+ // ============================================================================
38
+ // ASCII Art & Branding
39
+ // ============================================================================
40
+
41
+ const BANNER = `
42
+ ${c.green} ╭─────────────────────────────────────────────────╮${c.reset}
43
+ ${c.green} │${c.reset} ${c.green}│${c.reset}
44
+ ${c.green} │${c.reset} ${c.green}⚡${c.reset} ${c.bold}${c.white}C R E A T E - F L E X I R E A C T${c.reset} ${c.green}│${c.reset}
45
+ ${c.green} │${c.reset} ${c.green}│${c.reset}
46
+ ${c.green} │${c.reset} ${c.dim}The Modern React Framework${c.reset} ${c.green}│${c.reset}
47
+ ${c.green} │${c.reset} ${c.dim}TypeScript • Tailwind • SSR • Islands${c.reset} ${c.green}│${c.reset}
48
+ ${c.green} │${c.reset} ${c.green}│${c.reset}
49
+ ${c.green} ╰─────────────────────────────────────────────────╯${c.reset}
50
+ `;
51
+
52
+ const SUCCESS_BANNER = (projectName) => `
53
+ ${c.green} ╭─────────────────────────────────────────────────╮${c.reset}
54
+ ${c.green} │${c.reset} ${c.green}│${c.reset}
55
+ ${c.green} │${c.reset} ${c.green}✓${c.reset} ${c.bold}Project created successfully!${c.reset} ${c.green}│${c.reset}
56
+ ${c.green} │${c.reset} ${c.green}│${c.reset}
57
+ ${c.green} ╰─────────────────────────────────────────────────╯${c.reset}
58
+
59
+ ${c.dim}Next steps:${c.reset}
60
+
61
+ ${c.cyan}cd${c.reset} ${projectName}
62
+ ${c.cyan}npm${c.reset} install
63
+ ${c.cyan}npm${c.reset} run dev
64
+
65
+ ${c.dim}Then open${c.reset} ${c.cyan}http://localhost:3000${c.reset}
66
+
67
+ ${c.dim}Documentation:${c.reset} ${c.cyan}https://github.com/flexireact/flexireact${c.reset}
68
+ `;
69
+
70
+ // ============================================================================
71
+ // Templates
72
+ // ============================================================================
73
+
74
+ const TEMPLATES = {
75
+ default: {
76
+ name: 'Default',
77
+ description: 'Basic FlexiReact app with TypeScript and Tailwind',
78
+ },
79
+ 'flexi-ui': {
80
+ name: 'FlexiUI',
81
+ description: 'FlexiReact with FlexiUI component library',
82
+ },
83
+ minimal: {
84
+ name: 'Minimal',
85
+ description: 'Bare minimum FlexiReact setup',
86
+ },
87
+ };
88
+
89
+ // ============================================================================
90
+ // Utilities
91
+ // ============================================================================
92
+
93
+ function log(msg) {
94
+ console.log(` ${msg}`);
95
+ }
96
+
97
+ function success(msg) {
98
+ console.log(` ${c.green}✓${c.reset} ${msg}`);
99
+ }
100
+
101
+ function error(msg) {
102
+ console.log(` ${c.red}✗${c.reset} ${c.red}${msg}${c.reset}`);
103
+ }
104
+
105
+ function info(msg) {
106
+ console.log(` ${c.cyan}ℹ${c.reset} ${msg}`);
107
+ }
108
+
109
+ function step(num, total, msg) {
110
+ console.log(` ${c.dim}[${num}/${total}]${c.reset} ${msg}`);
111
+ }
112
+
113
+ // Spinner
114
+ class Spinner {
115
+ constructor(message) {
116
+ this.message = message;
117
+ this.frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
118
+ this.current = 0;
119
+ this.interval = null;
120
+ }
121
+
122
+ start() {
123
+ process.stdout.write(` ${this.frames[0]} ${this.message}`);
124
+ this.interval = setInterval(() => {
125
+ this.current = (this.current + 1) % this.frames.length;
126
+ process.stdout.clearLine(0);
127
+ process.stdout.cursorTo(0);
128
+ process.stdout.write(` ${c.cyan}${this.frames[this.current]}${c.reset} ${this.message}`);
129
+ }, 80);
130
+ }
131
+
132
+ stop(success = true) {
133
+ clearInterval(this.interval);
134
+ process.stdout.clearLine(0);
135
+ process.stdout.cursorTo(0);
136
+ const icon = success ? `${c.green}✓${c.reset}` : `${c.red}✗${c.reset}`;
137
+ console.log(` ${icon} ${this.message}`);
138
+ }
139
+ }
140
+
141
+ // Prompt
142
+ async function prompt(question, defaultValue = '') {
143
+ const rl = readline.createInterface({
144
+ input: process.stdin,
145
+ output: process.stdout,
146
+ });
147
+
148
+ return new Promise((resolve) => {
149
+ const defaultStr = defaultValue ? ` ${c.dim}(${defaultValue})${c.reset}` : '';
150
+ rl.question(` ${c.cyan}?${c.reset} ${question}${defaultStr}: `, (answer) => {
151
+ rl.close();
152
+ resolve(answer.trim() || defaultValue);
153
+ });
154
+ });
155
+ }
156
+
157
+ // Select
158
+ async function select(question, options) {
159
+ console.log(` ${c.cyan}?${c.reset} ${question}`);
160
+ console.log('');
161
+
162
+ options.forEach((opt, i) => {
163
+ console.log(` ${c.cyan}${i + 1}.${c.reset} ${c.bold}${opt.name}${c.reset}`);
164
+ console.log(` ${c.dim}${opt.description}${c.reset}`);
165
+ });
166
+
167
+ console.log('');
168
+
169
+ const rl = readline.createInterface({
170
+ input: process.stdin,
171
+ output: process.stdout,
172
+ });
173
+
174
+ return new Promise((resolve) => {
175
+ rl.question(` ${c.dim}Enter number (1-${options.length}):${c.reset} `, (answer) => {
176
+ rl.close();
177
+ const index = parseInt(answer) - 1;
178
+ if (index >= 0 && index < options.length) {
179
+ resolve(options[index]);
180
+ } else {
181
+ resolve(options[0]);
182
+ }
183
+ });
184
+ });
185
+ }
186
+
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
+ // Check if directory is empty
208
+ function isDirEmpty(dir) {
209
+ if (!fs.existsSync(dir)) return true;
210
+ return fs.readdirSync(dir).length === 0;
211
+ }
212
+
213
+ // ============================================================================
214
+ // Template Files (Inline for npm package)
215
+ // ============================================================================
216
+
217
+ const TEMPLATE_FILES = {
218
+ 'package.json': (name, template) => JSON.stringify({
219
+ name: name,
220
+ version: "1.0.0",
221
+ private: true,
222
+ type: "module",
223
+ scripts: {
224
+ dev: "npm run css && flexireact dev",
225
+ build: "npm run css && flexireact build",
226
+ start: "flexireact start",
227
+ css: "npx tailwindcss -i ./app/styles/input.css -o ./public/styles.css --minify"
228
+ },
229
+ dependencies: {
230
+ react: "^18.2.0",
231
+ "react-dom": "^18.2.0",
232
+ "@flexireact/core": "^1.0.0",
233
+ ...(template === 'flexi-ui' && { "@flexireact/flexi-ui": "^1.0.0" })
234
+ },
235
+ devDependencies: {
236
+ "@types/react": "^18.2.0",
237
+ "@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"
242
+ }
243
+ }, null, 2),
244
+
245
+ 'tsconfig.json': () => JSON.stringify({
246
+ compilerOptions: {
247
+ target: "ES2020",
248
+ lib: ["DOM", "DOM.Iterable", "ES2020"],
249
+ module: "ESNext",
250
+ moduleResolution: "bundler",
251
+ jsx: "react-jsx",
252
+ strict: true,
253
+ skipLibCheck: true,
254
+ esModuleInterop: true,
255
+ allowSyntheticDefaultImports: true,
256
+ resolveJsonModule: true,
257
+ isolatedModules: true,
258
+ noEmit: true,
259
+ baseUrl: ".",
260
+ paths: {
261
+ "@/*": ["./*"]
262
+ }
263
+ },
264
+ include: ["**/*.ts", "**/*.tsx"],
265
+ exclude: ["node_modules", ".flexi"]
266
+ }, null, 2),
267
+
268
+ 'tailwind.config.js': (name, template) => `/** @type {import('tailwindcss').Config} */
269
+ ${template === 'flexi-ui' ? "const { flexiUIPlugin } = require('@flexireact/flexi-ui/tailwind');\n" : ''}
270
+ module.exports = {
271
+ darkMode: 'class',
272
+ content: [
273
+ './app/**/*.{js,ts,jsx,tsx}',
274
+ './pages/**/*.{js,ts,jsx,tsx}',
275
+ './components/**/*.{js,ts,jsx,tsx}',
276
+ './layouts/**/*.{js,ts,jsx,tsx}',
277
+ ${template === 'flexi-ui' ? "'./node_modules/@flexireact/flexi-ui/dist/**/*.js'," : ''}
278
+ ],
279
+ theme: {
280
+ extend: {
281
+ fontFamily: {
282
+ sans: ['Inter', 'system-ui', 'sans-serif'],
283
+ },
284
+ },
285
+ },
286
+ plugins: [${template === 'flexi-ui' ? 'flexiUIPlugin' : ''}],
287
+ };
288
+ `,
289
+
290
+ 'postcss.config.js': () => `module.exports = {
291
+ plugins: {
292
+ tailwindcss: {},
293
+ autoprefixer: {},
294
+ },
295
+ };
296
+ `,
297
+
298
+ 'flexireact.config.js': (name, template) => `/** @type {import('flexireact').Config} */
299
+ export default {
300
+ // Styles to include
301
+ styles: [
302
+ '/styles.css',
303
+ 'https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap'
304
+ ],
305
+
306
+ // Server options
307
+ server: {
308
+ port: 3000
309
+ },
310
+
311
+ // Islands (partial hydration)
312
+ islands: {
313
+ enabled: true
314
+ }
315
+ };
316
+ `,
317
+
318
+ 'pages/index.tsx': (name, template) => template === 'flexi-ui' ? `import React from 'react';
319
+
320
+ export default function HomePage() {
321
+ return (
322
+ <div className="py-20">
323
+ <div className="container mx-auto px-4 text-center">
324
+ <span className="inline-block px-4 py-1.5 text-sm font-medium rounded-full mb-6" style={{ backgroundColor: 'rgba(0,255,156,0.1)', color: '#00FF9C', border: '1px solid rgba(0,255,156,0.3)' }}>
325
+ ✨ Welcome to FlexiReact
326
+ </span>
327
+
328
+ <h1 className="text-4xl md:text-6xl font-bold mb-6">
329
+ Build amazing apps with{' '}
330
+ <span style={{ color: '#00FF9C' }}>FlexiReact</span>
331
+ </h1>
332
+
333
+ <p className="text-lg opacity-70 mb-8 max-w-2xl mx-auto">
334
+ The modern React framework with TypeScript, Tailwind CSS, SSR, and Islands architecture.
335
+ </p>
336
+
337
+ <div className="flex gap-4 justify-center">
338
+ <a href="/docs" className="px-6 py-3 font-medium rounded-xl text-black" style={{ backgroundColor: '#00FF9C' }}>
339
+ Get Started →
340
+ </a>
341
+ <a href="https://github.com/flexireact/flexireact" className="px-6 py-3 font-medium rounded-xl border border-[var(--flexi-border)] hover:bg-[var(--flexi-bg-muted)] transition-colors">
342
+ GitHub
343
+ </a>
344
+ </div>
345
+ </div>
346
+ </div>
347
+ );
348
+ }
349
+ ` : `import React from 'react';
350
+
351
+ export default function HomePage() {
352
+ return (
353
+ <div className="py-20 px-4">
354
+ <div className="max-w-4xl mx-auto text-center">
355
+ {/* Badge */}
356
+ <span className="inline-block px-4 py-2 text-sm font-medium rounded-full mb-8 bg-emerald-500/10 text-emerald-400 border border-emerald-500/30">
357
+ ⚡ The Modern React Framework
358
+ </span>
359
+
360
+ {/* Title */}
361
+ <h1 className="text-5xl md:text-7xl font-extrabold mb-6 text-white">
362
+ Build amazing apps with{' '}
363
+ <span className="text-transparent bg-clip-text bg-gradient-to-r from-emerald-400 to-cyan-400">
364
+ FlexiReact
365
+ </span>
366
+ </h1>
367
+
368
+ {/* Subtitle */}
369
+ <p className="text-xl text-slate-400 mb-10 max-w-2xl mx-auto leading-relaxed">
370
+ A blazing-fast React framework with TypeScript, Tailwind CSS, SSR, SSG,
371
+ Islands architecture, and file-based routing.
372
+ </p>
373
+
374
+ {/* Buttons */}
375
+ <div className="flex flex-wrap gap-4 justify-center mb-16">
376
+ <a
377
+ href="https://github.com/flexireact/flexireact"
378
+ className="px-8 py-4 bg-emerald-500 text-black font-semibold rounded-xl hover:bg-emerald-400 transition-all shadow-lg shadow-emerald-500/25"
379
+ >
380
+ Get Started →
381
+ </a>
382
+ <a
383
+ href="https://github.com/flexireact/flexireact"
384
+ className="px-8 py-4 bg-slate-800 text-white font-semibold rounded-xl border border-slate-700 hover:bg-slate-700 transition-all"
385
+ >
386
+ GitHub
387
+ </a>
388
+ </div>
389
+
390
+ {/* Features */}
391
+ <div className="grid md:grid-cols-3 gap-6 text-left">
392
+ <div className="p-6 rounded-2xl bg-slate-800/50 border border-slate-700">
393
+ <div className="text-3xl mb-3">⚡</div>
394
+ <h3 className="text-lg font-semibold text-white mb-2">Lightning Fast</h3>
395
+ <p className="text-slate-400 text-sm">Powered by esbuild for instant builds and sub-second HMR.</p>
396
+ </div>
397
+ <div className="p-6 rounded-2xl bg-slate-800/50 border border-slate-700">
398
+ <div className="text-3xl mb-3">🏝️</div>
399
+ <h3 className="text-lg font-semibold text-white mb-2">Islands Architecture</h3>
400
+ <p className="text-slate-400 text-sm">Partial hydration for minimal JavaScript and maximum performance.</p>
401
+ </div>
402
+ <div className="p-6 rounded-2xl bg-slate-800/50 border border-slate-700">
403
+ <div className="text-3xl mb-3">📁</div>
404
+ <h3 className="text-lg font-semibold text-white mb-2">File-based Routing</h3>
405
+ <p className="text-slate-400 text-sm">Create a file in pages/, get a route automatically.</p>
406
+ </div>
407
+ </div>
408
+ </div>
409
+ </div>
410
+ );
411
+ }
412
+ `,
413
+
414
+ 'layouts/root.tsx': (name, template) => template === 'flexi-ui' ? `import React from 'react';
415
+
416
+ interface LayoutProps {
417
+ children: React.ReactNode;
418
+ }
419
+
420
+ export default function RootLayout({ children }: LayoutProps) {
421
+ return (
422
+ <div className="min-h-screen flex flex-col" style={{ backgroundColor: 'var(--flexi-bg)', color: 'var(--flexi-fg)' }}>
423
+ <header className="sticky top-0 z-50 w-full border-b border-[var(--flexi-border)] backdrop-blur-sm" style={{ backgroundColor: 'rgba(2, 6, 23, 0.8)' }}>
424
+ <nav className="container mx-auto px-4 h-16 flex items-center justify-between">
425
+ <a href="/" className="flex items-center gap-2">
426
+ <div className="w-8 h-8 rounded-xl flex items-center justify-center" style={{ background: 'linear-gradient(135deg, #00FF9C, #00CC7D)' }}>
427
+ <span className="text-black font-bold text-sm">F</span>
428
+ </div>
429
+ <span className="font-bold text-xl">FlexiReact</span>
430
+ </a>
431
+ <div className="flex items-center gap-4">
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>
434
+ </div>
435
+ </nav>
436
+ </header>
437
+ <main className="flex-1">
438
+ {children}
439
+ </main>
440
+ <footer className="border-t border-[var(--flexi-border)] py-8 text-center text-sm opacity-70">
441
+ Built with FlexiReact
442
+ </footer>
443
+ </div>
444
+ );
445
+ }
446
+ ` : `import React from 'react';
447
+
448
+ interface LayoutProps {
449
+ children: React.ReactNode;
450
+ }
451
+
452
+ export default function RootLayout({ children }: LayoutProps) {
453
+ return (
454
+ <div className="min-h-screen bg-slate-900 text-white">
455
+ <header className="sticky top-0 z-50 border-b border-slate-700 bg-slate-900/80 backdrop-blur-sm">
456
+ <nav className="container mx-auto px-4 h-16 flex items-center justify-between">
457
+ <a href="/" className="flex items-center gap-2 font-bold text-xl">
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>
475
+ </div>
476
+ );
477
+ }
478
+ `,
479
+
480
+ 'app/styles/input.css': () => `@tailwind base;
481
+ @tailwind components;
482
+ @tailwind utilities;
483
+
484
+ @layer base {
485
+ :root {
486
+ --flexi-bg: #0f172a;
487
+ --flexi-fg: #f8fafc;
488
+ --flexi-bg-subtle: #1e293b;
489
+ --flexi-bg-muted: #334155;
490
+ --flexi-border: #475569;
491
+ --flexi-fg-muted: #94a3b8;
492
+ --flexi-primary: #10b981;
493
+ }
494
+
495
+ html {
496
+ scroll-behavior: smooth;
497
+ }
498
+
499
+ body {
500
+ font-family: 'Inter', system-ui, sans-serif;
501
+ background-color: #0f172a;
502
+ color: #f8fafc;
503
+ min-height: 100vh;
504
+ }
505
+ }
506
+ `,
507
+
508
+ 'public/.gitkeep': () => '',
509
+ };
510
+
511
+ // ============================================================================
512
+ // Main
513
+ // ============================================================================
514
+
515
+ async function main() {
516
+ console.clear();
517
+ console.log(BANNER);
518
+
519
+ // Get project name from args or prompt
520
+ let projectName = process.argv[2];
521
+
522
+ if (!projectName) {
523
+ projectName = await prompt('Project name', 'my-flexireact-app');
524
+ }
525
+
526
+ // Validate project name
527
+ if (!/^[a-zA-Z0-9-_]+$/.test(projectName)) {
528
+ error('Project name can only contain letters, numbers, dashes, and underscores');
529
+ process.exit(1);
530
+ }
531
+
532
+ const projectPath = path.resolve(process.cwd(), projectName);
533
+
534
+ // Check if directory exists
535
+ if (fs.existsSync(projectPath) && !isDirEmpty(projectPath)) {
536
+ error(`Directory "${projectName}" already exists and is not empty`);
537
+ process.exit(1);
538
+ }
539
+
540
+ console.log('');
541
+
542
+ // Select template
543
+ const templateOptions = Object.entries(TEMPLATES).map(([key, value]) => ({
544
+ key,
545
+ ...value,
546
+ }));
547
+
548
+ const selectedTemplate = await select('Select a template:', templateOptions);
549
+ const template = selectedTemplate.key;
550
+
551
+ console.log('');
552
+ console.log(` ${c.dim}Creating project in${c.reset} ${c.cyan}${projectPath}${c.reset}`);
553
+ console.log('');
554
+
555
+ // Create project directory
556
+ const spinner1 = new Spinner('Creating project structure...');
557
+ spinner1.start();
558
+
559
+ try {
560
+ fs.mkdirSync(projectPath, { recursive: true });
561
+ fs.mkdirSync(path.join(projectPath, 'pages'), { recursive: true });
562
+ fs.mkdirSync(path.join(projectPath, 'layouts'), { recursive: true });
563
+ fs.mkdirSync(path.join(projectPath, 'app', 'styles'), { recursive: true });
564
+ fs.mkdirSync(path.join(projectPath, 'public'), { recursive: true });
565
+ fs.mkdirSync(path.join(projectPath, 'components'), { recursive: true });
566
+
567
+ spinner1.stop(true);
568
+ } catch (err) {
569
+ spinner1.stop(false);
570
+ error(`Failed to create directory: ${err.message}`);
571
+ process.exit(1);
572
+ }
573
+
574
+ // Write template files
575
+ const spinner2 = new Spinner('Writing template files...');
576
+ spinner2.start();
577
+
578
+ try {
579
+ for (const [filePath, generator] of Object.entries(TEMPLATE_FILES)) {
580
+ const fullPath = path.join(projectPath, filePath);
581
+ const dir = path.dirname(fullPath);
582
+
583
+ if (!fs.existsSync(dir)) {
584
+ fs.mkdirSync(dir, { recursive: true });
585
+ }
586
+
587
+ const content = generator(projectName, template);
588
+ fs.writeFileSync(fullPath, content);
589
+ }
590
+
591
+ spinner2.stop(true);
592
+ } catch (err) {
593
+ spinner2.stop(false);
594
+ error(`Failed to write files: ${err.message}`);
595
+ process.exit(1);
596
+ }
597
+
598
+ // Create .gitignore
599
+ const spinner3 = new Spinner('Creating configuration files...');
600
+ spinner3.start();
601
+
602
+ try {
603
+ fs.writeFileSync(path.join(projectPath, '.gitignore'), `# Dependencies
604
+ node_modules/
605
+
606
+ # Build
607
+ .flexi/
608
+ dist/
609
+ build/
610
+
611
+ # Environment
612
+ .env
613
+ .env.local
614
+ .env.*.local
615
+
616
+ # IDE
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.
632
+
633
+ ## Getting Started
634
+
635
+ \`\`\`bash
636
+ npm install
637
+ npm run dev
638
+ \`\`\`
639
+
640
+ Open [http://localhost:3000](http://localhost:3000) in your browser.
641
+
642
+ ## Learn More
643
+
644
+ - [FlexiReact Documentation](https://github.com/flexireact/flexireact)
645
+ - [FlexiUI Components](https://github.com/flexireact/flexi-ui)
646
+ `);
647
+
648
+ spinner3.stop(true);
649
+ } catch (err) {
650
+ spinner3.stop(false);
651
+ error(`Failed to create config files: ${err.message}`);
652
+ process.exit(1);
653
+ }
654
+
655
+ // Done!
656
+ console.log(SUCCESS_BANNER(projectName));
657
+ }
658
+
659
+ main().catch((err) => {
660
+ error(err.message);
661
+ process.exit(1);
662
+ });
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "create-flexireact",
3
+ "version": "1.0.0",
4
+ "description": "Create FlexiReact apps with one command - The Modern React Framework",
5
+ "author": "FlexiReact Team",
6
+ "license": "MIT",
7
+ "type": "module",
8
+ "bin": {
9
+ "create-flexireact": "./index.js"
10
+ },
11
+ "files": [
12
+ "index.js"
13
+ ],
14
+ "keywords": [
15
+ "react",
16
+ "flexireact",
17
+ "framework",
18
+ "ssr",
19
+ "ssg",
20
+ "islands",
21
+ "typescript",
22
+ "tailwind",
23
+ "create-app",
24
+ "cli",
25
+ "scaffold",
26
+ "boilerplate"
27
+ ],
28
+ "repository": {
29
+ "type": "git",
30
+ "url": "git+https://github.com/flexireact/flexireact.git"
31
+ },
32
+ "bugs": {
33
+ "url": "https://github.com/flexireact/flexireact/issues"
34
+ },
35
+ "homepage": "https://github.com/flexireact/flexireact#readme",
36
+ "engines": {
37
+ "node": ">=18.0.0"
38
+ },
39
+ "publishConfig": {
40
+ "access": "public"
41
+ }
42
+ }