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