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.
- package/README.md +49 -0
- package/index.js +662 -0
- 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
|
+
}
|