@flexireact/core 3.0.1 → 3.0.3
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/dist/cli/index.js +1514 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/core/client/index.js +373 -0
- package/dist/core/client/index.js.map +1 -0
- package/dist/core/index.js +6415 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/server/index.js +3094 -0
- package/dist/core/server/index.js.map +1 -0
- package/package.json +80 -80
- package/bin/flexireact.js +0 -23
- package/cli/generators.ts +0 -616
- package/cli/index.ts +0 -1182
- package/core/actions/index.ts +0 -364
- package/core/api.ts +0 -143
- package/core/build/index.ts +0 -425
- package/core/cli/logger.ts +0 -353
- package/core/client/Link.tsx +0 -345
- package/core/client/hydration.ts +0 -147
- package/core/client/index.ts +0 -12
- package/core/client/islands.ts +0 -143
- package/core/client/navigation.ts +0 -212
- package/core/client/runtime.ts +0 -52
- package/core/config.ts +0 -116
- package/core/context.ts +0 -83
- package/core/dev.ts +0 -47
- package/core/devtools/index.ts +0 -644
- package/core/edge/cache.ts +0 -344
- package/core/edge/fetch-polyfill.ts +0 -247
- package/core/edge/handler.ts +0 -248
- package/core/edge/index.ts +0 -81
- package/core/edge/ppr.ts +0 -264
- package/core/edge/runtime.ts +0 -161
- package/core/font/index.ts +0 -306
- package/core/helpers.ts +0 -494
- package/core/image/index.ts +0 -413
- package/core/index.ts +0 -218
- package/core/islands/index.ts +0 -293
- package/core/loader.ts +0 -111
- package/core/logger.ts +0 -242
- package/core/metadata/index.ts +0 -622
- package/core/middleware/index.ts +0 -416
- package/core/plugins/index.ts +0 -373
- package/core/render/index.ts +0 -1243
- package/core/render.ts +0 -136
- package/core/router/index.ts +0 -551
- package/core/router.ts +0 -141
- package/core/rsc/index.ts +0 -199
- package/core/server/index.ts +0 -779
- package/core/server.ts +0 -203
- package/core/ssg/index.ts +0 -346
- package/core/start-dev.ts +0 -6
- package/core/start-prod.ts +0 -6
- package/core/tsconfig.json +0 -30
- package/core/types.ts +0 -239
- package/core/utils.ts +0 -176
package/cli/generators.ts
DELETED
|
@@ -1,616 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* FlexiReact CLI Generators
|
|
3
|
-
* Scaffolding commands for rapid development
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import fs from 'fs';
|
|
7
|
-
import path from 'path';
|
|
8
|
-
import pc from 'picocolors';
|
|
9
|
-
import prompts from 'prompts';
|
|
10
|
-
|
|
11
|
-
const log = {
|
|
12
|
-
info: (msg: string) => console.log(`${pc.cyan('ℹ')} ${msg}`),
|
|
13
|
-
success: (msg: string) => console.log(`${pc.green('✓')} ${msg}`),
|
|
14
|
-
warn: (msg: string) => console.log(`${pc.yellow('⚠')} ${pc.yellow(msg)}`),
|
|
15
|
-
error: (msg: string) => console.log(`${pc.red('✗')} ${pc.red(msg)}`),
|
|
16
|
-
blank: () => console.log(''),
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
// ============================================================================
|
|
20
|
-
// Templates
|
|
21
|
-
// ============================================================================
|
|
22
|
-
|
|
23
|
-
const templates = {
|
|
24
|
-
// Page template
|
|
25
|
-
page: (name: string, options: { client?: boolean }) => `${options.client ? "'use client';\n\n" : ''}import React from 'react';
|
|
26
|
-
|
|
27
|
-
export default function ${toPascalCase(name)}Page() {
|
|
28
|
-
return (
|
|
29
|
-
<div className="min-h-screen p-8">
|
|
30
|
-
<h1 className="text-4xl font-bold">${toPascalCase(name)}</h1>
|
|
31
|
-
<p className="text-gray-400 mt-4">Welcome to ${name} page</p>
|
|
32
|
-
</div>
|
|
33
|
-
);
|
|
34
|
-
}
|
|
35
|
-
`,
|
|
36
|
-
|
|
37
|
-
// Layout template
|
|
38
|
-
layout: (name: string) => `import React from 'react';
|
|
39
|
-
|
|
40
|
-
interface ${toPascalCase(name)}LayoutProps {
|
|
41
|
-
children: React.ReactNode;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export default function ${toPascalCase(name)}Layout({ children }: ${toPascalCase(name)}LayoutProps) {
|
|
45
|
-
return (
|
|
46
|
-
<div className="${name}-layout">
|
|
47
|
-
{/* Add your layout wrapper here */}
|
|
48
|
-
{children}
|
|
49
|
-
</div>
|
|
50
|
-
);
|
|
51
|
-
}
|
|
52
|
-
`,
|
|
53
|
-
|
|
54
|
-
// Component template
|
|
55
|
-
component: (name: string, options: { client?: boolean; props?: boolean }) => {
|
|
56
|
-
const propsInterface = options.props ? `
|
|
57
|
-
interface ${toPascalCase(name)}Props {
|
|
58
|
-
className?: string;
|
|
59
|
-
children?: React.ReactNode;
|
|
60
|
-
}
|
|
61
|
-
` : '';
|
|
62
|
-
const propsType = options.props ? `{ className, children }: ${toPascalCase(name)}Props` : '{}';
|
|
63
|
-
|
|
64
|
-
return `${options.client ? "'use client';\n\n" : ''}import React from 'react';
|
|
65
|
-
import { cn } from '@/lib/utils';
|
|
66
|
-
${propsInterface}
|
|
67
|
-
export function ${toPascalCase(name)}(${propsType}) {
|
|
68
|
-
return (
|
|
69
|
-
<div className={cn('${toKebabCase(name)}', ${options.props ? 'className' : "''"})}>
|
|
70
|
-
${options.props ? '{children}' : `{/* ${toPascalCase(name)} content */}`}
|
|
71
|
-
</div>
|
|
72
|
-
);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
export default ${toPascalCase(name)};
|
|
76
|
-
`;
|
|
77
|
-
},
|
|
78
|
-
|
|
79
|
-
// Hook template
|
|
80
|
-
hook: (name: string) => `import { useState, useEffect, useCallback } from 'react';
|
|
81
|
-
|
|
82
|
-
export function use${toPascalCase(name)}() {
|
|
83
|
-
const [state, setState] = useState(null);
|
|
84
|
-
const [loading, setLoading] = useState(false);
|
|
85
|
-
const [error, setError] = useState<Error | null>(null);
|
|
86
|
-
|
|
87
|
-
const execute = useCallback(async () => {
|
|
88
|
-
setLoading(true);
|
|
89
|
-
setError(null);
|
|
90
|
-
try {
|
|
91
|
-
// Add your logic here
|
|
92
|
-
setState(null);
|
|
93
|
-
} catch (err) {
|
|
94
|
-
setError(err instanceof Error ? err : new Error('Unknown error'));
|
|
95
|
-
} finally {
|
|
96
|
-
setLoading(false);
|
|
97
|
-
}
|
|
98
|
-
}, []);
|
|
99
|
-
|
|
100
|
-
return { state, loading, error, execute };
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
export default use${toPascalCase(name)};
|
|
104
|
-
`,
|
|
105
|
-
|
|
106
|
-
// API route template
|
|
107
|
-
api: (name: string) => `import type { IncomingMessage, ServerResponse } from 'http';
|
|
108
|
-
|
|
109
|
-
export default async function handler(req: IncomingMessage, res: ServerResponse) {
|
|
110
|
-
const method = req.method;
|
|
111
|
-
|
|
112
|
-
switch (method) {
|
|
113
|
-
case 'GET':
|
|
114
|
-
return handleGet(req, res);
|
|
115
|
-
case 'POST':
|
|
116
|
-
return handlePost(req, res);
|
|
117
|
-
default:
|
|
118
|
-
res.statusCode = 405;
|
|
119
|
-
res.setHeader('Content-Type', 'application/json');
|
|
120
|
-
res.end(JSON.stringify({ error: 'Method not allowed' }));
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
async function handleGet(req: IncomingMessage, res: ServerResponse) {
|
|
125
|
-
res.statusCode = 200;
|
|
126
|
-
res.setHeader('Content-Type', 'application/json');
|
|
127
|
-
res.end(JSON.stringify({ message: '${name} API - GET' }));
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
async function handlePost(req: IncomingMessage, res: ServerResponse) {
|
|
131
|
-
// Parse body
|
|
132
|
-
let body = '';
|
|
133
|
-
for await (const chunk of req) {
|
|
134
|
-
body += chunk;
|
|
135
|
-
}
|
|
136
|
-
const data = body ? JSON.parse(body) : {};
|
|
137
|
-
|
|
138
|
-
res.statusCode = 200;
|
|
139
|
-
res.setHeader('Content-Type', 'application/json');
|
|
140
|
-
res.end(JSON.stringify({ message: '${name} API - POST', data }));
|
|
141
|
-
}
|
|
142
|
-
`,
|
|
143
|
-
|
|
144
|
-
// Server Action template
|
|
145
|
-
action: (name: string) => `'use server';
|
|
146
|
-
|
|
147
|
-
import { revalidatePath } from '@flexireact/core';
|
|
148
|
-
|
|
149
|
-
export async function ${toCamelCase(name)}Action(formData: FormData) {
|
|
150
|
-
// Validate input
|
|
151
|
-
const data = Object.fromEntries(formData);
|
|
152
|
-
|
|
153
|
-
try {
|
|
154
|
-
// Add your server logic here
|
|
155
|
-
console.log('${toPascalCase(name)} action executed:', data);
|
|
156
|
-
|
|
157
|
-
// Revalidate cache if needed
|
|
158
|
-
// revalidatePath('/');
|
|
159
|
-
|
|
160
|
-
return { success: true, data };
|
|
161
|
-
} catch (error) {
|
|
162
|
-
return {
|
|
163
|
-
success: false,
|
|
164
|
-
error: error instanceof Error ? error.message : 'Unknown error'
|
|
165
|
-
};
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
`,
|
|
169
|
-
|
|
170
|
-
// Middleware template
|
|
171
|
-
middleware: (name: string) => `import type { IncomingMessage, ServerResponse } from 'http';
|
|
172
|
-
|
|
173
|
-
export interface ${toPascalCase(name)}MiddlewareOptions {
|
|
174
|
-
// Add your options here
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
export function ${toCamelCase(name)}Middleware(options: ${toPascalCase(name)}MiddlewareOptions = {}) {
|
|
178
|
-
return async (
|
|
179
|
-
req: IncomingMessage,
|
|
180
|
-
res: ServerResponse,
|
|
181
|
-
next: () => Promise<void>
|
|
182
|
-
) => {
|
|
183
|
-
// Before request handling
|
|
184
|
-
console.log('[${toPascalCase(name)}] Request:', req.url);
|
|
185
|
-
|
|
186
|
-
// Continue to next middleware/handler
|
|
187
|
-
await next();
|
|
188
|
-
|
|
189
|
-
// After request handling (optional)
|
|
190
|
-
};
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
export default ${toCamelCase(name)}Middleware;
|
|
194
|
-
`,
|
|
195
|
-
|
|
196
|
-
// Context template
|
|
197
|
-
context: (name: string) => `'use client';
|
|
198
|
-
|
|
199
|
-
import React, { createContext, useContext, useState, useCallback } from 'react';
|
|
200
|
-
|
|
201
|
-
interface ${toPascalCase(name)}State {
|
|
202
|
-
// Add your state properties here
|
|
203
|
-
value: string | null;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
interface ${toPascalCase(name)}ContextValue extends ${toPascalCase(name)}State {
|
|
207
|
-
setValue: (value: string) => void;
|
|
208
|
-
reset: () => void;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
const ${toPascalCase(name)}Context = createContext<${toPascalCase(name)}ContextValue | null>(null);
|
|
212
|
-
|
|
213
|
-
export function ${toPascalCase(name)}Provider({ children }: { children: React.ReactNode }) {
|
|
214
|
-
const [state, setState] = useState<${toPascalCase(name)}State>({
|
|
215
|
-
value: null,
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
const setValue = useCallback((value: string) => {
|
|
219
|
-
setState(prev => ({ ...prev, value }));
|
|
220
|
-
}, []);
|
|
221
|
-
|
|
222
|
-
const reset = useCallback(() => {
|
|
223
|
-
setState({ value: null });
|
|
224
|
-
}, []);
|
|
225
|
-
|
|
226
|
-
return (
|
|
227
|
-
<${toPascalCase(name)}Context.Provider value={{ ...state, setValue, reset }}>
|
|
228
|
-
{children}
|
|
229
|
-
</${toPascalCase(name)}Context.Provider>
|
|
230
|
-
);
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
export function use${toPascalCase(name)}() {
|
|
234
|
-
const context = useContext(${toPascalCase(name)}Context);
|
|
235
|
-
if (!context) {
|
|
236
|
-
throw new Error('use${toPascalCase(name)} must be used within a ${toPascalCase(name)}Provider');
|
|
237
|
-
}
|
|
238
|
-
return context;
|
|
239
|
-
}
|
|
240
|
-
`,
|
|
241
|
-
|
|
242
|
-
// Loading template
|
|
243
|
-
loading: () => `import React from 'react';
|
|
244
|
-
import { Spinner } from '@flexireact/flexi-ui';
|
|
245
|
-
|
|
246
|
-
export default function Loading() {
|
|
247
|
-
return (
|
|
248
|
-
<div className="min-h-screen flex items-center justify-center">
|
|
249
|
-
<Spinner size="lg" />
|
|
250
|
-
</div>
|
|
251
|
-
);
|
|
252
|
-
}
|
|
253
|
-
`,
|
|
254
|
-
|
|
255
|
-
// Error template
|
|
256
|
-
error: () => `'use client';
|
|
257
|
-
|
|
258
|
-
import React from 'react';
|
|
259
|
-
import { Button, Alert } from '@flexireact/flexi-ui';
|
|
260
|
-
|
|
261
|
-
interface ErrorProps {
|
|
262
|
-
error: Error;
|
|
263
|
-
reset: () => void;
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
export default function Error({ error, reset }: ErrorProps) {
|
|
267
|
-
return (
|
|
268
|
-
<div className="min-h-screen flex flex-col items-center justify-center p-8">
|
|
269
|
-
<Alert variant="error" className="max-w-md mb-8">
|
|
270
|
-
<h2 className="font-bold text-lg mb-2">Something went wrong</h2>
|
|
271
|
-
<p className="text-sm">{error.message}</p>
|
|
272
|
-
</Alert>
|
|
273
|
-
<Button onClick={reset}>Try again</Button>
|
|
274
|
-
</div>
|
|
275
|
-
);
|
|
276
|
-
}
|
|
277
|
-
`,
|
|
278
|
-
|
|
279
|
-
// Not found template
|
|
280
|
-
notFound: () => `import React from 'react';
|
|
281
|
-
import { Button } from '@flexireact/flexi-ui';
|
|
282
|
-
|
|
283
|
-
export default function NotFound() {
|
|
284
|
-
return (
|
|
285
|
-
<div className="min-h-screen flex flex-col items-center justify-center p-8">
|
|
286
|
-
<h1 className="text-8xl font-bold text-primary mb-4">404</h1>
|
|
287
|
-
<p className="text-gray-400 text-xl mb-8">Page not found</p>
|
|
288
|
-
<Button asChild>
|
|
289
|
-
<a href="/">← Back Home</a>
|
|
290
|
-
</Button>
|
|
291
|
-
</div>
|
|
292
|
-
);
|
|
293
|
-
}
|
|
294
|
-
`,
|
|
295
|
-
};
|
|
296
|
-
|
|
297
|
-
// ============================================================================
|
|
298
|
-
// Utility Functions
|
|
299
|
-
// ============================================================================
|
|
300
|
-
|
|
301
|
-
function toPascalCase(str: string): string {
|
|
302
|
-
return str
|
|
303
|
-
.replace(/[-_](.)/g, (_, c) => c.toUpperCase())
|
|
304
|
-
.replace(/^(.)/, (_, c) => c.toUpperCase());
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
function toCamelCase(str: string): string {
|
|
308
|
-
return str
|
|
309
|
-
.replace(/[-_](.)/g, (_, c) => c.toUpperCase())
|
|
310
|
-
.replace(/^(.)/, (_, c) => c.toLowerCase());
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
function toKebabCase(str: string): string {
|
|
314
|
-
return str
|
|
315
|
-
.replace(/([a-z])([A-Z])/g, '$1-$2')
|
|
316
|
-
.replace(/[_\s]+/g, '-')
|
|
317
|
-
.toLowerCase();
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
function ensureDir(filePath: string): void {
|
|
321
|
-
const dir = path.dirname(filePath);
|
|
322
|
-
if (!fs.existsSync(dir)) {
|
|
323
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
function writeFile(filePath: string, content: string): void {
|
|
328
|
-
ensureDir(filePath);
|
|
329
|
-
fs.writeFileSync(filePath, content);
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
// ============================================================================
|
|
333
|
-
// Generator Commands
|
|
334
|
-
// ============================================================================
|
|
335
|
-
|
|
336
|
-
export type GeneratorType =
|
|
337
|
-
| 'page'
|
|
338
|
-
| 'layout'
|
|
339
|
-
| 'component'
|
|
340
|
-
| 'hook'
|
|
341
|
-
| 'api'
|
|
342
|
-
| 'action'
|
|
343
|
-
| 'middleware'
|
|
344
|
-
| 'context'
|
|
345
|
-
| 'loading'
|
|
346
|
-
| 'error'
|
|
347
|
-
| 'not-found';
|
|
348
|
-
|
|
349
|
-
export async function runGenerate(type?: string, name?: string): Promise<void> {
|
|
350
|
-
const cwd = process.cwd();
|
|
351
|
-
|
|
352
|
-
// Check if we're in a FlexiReact project
|
|
353
|
-
if (!fs.existsSync(path.join(cwd, 'package.json'))) {
|
|
354
|
-
log.error('Not in a FlexiReact project. Run this command in your project root.');
|
|
355
|
-
process.exit(1);
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
// Interactive mode if no type provided
|
|
359
|
-
if (!type) {
|
|
360
|
-
const response = await prompts([
|
|
361
|
-
{
|
|
362
|
-
type: 'select',
|
|
363
|
-
name: 'type',
|
|
364
|
-
message: 'What do you want to generate?',
|
|
365
|
-
choices: [
|
|
366
|
-
{ title: '📄 Page', value: 'page', description: 'A new page in app/ or pages/' },
|
|
367
|
-
{ title: '📐 Layout', value: 'layout', description: 'A layout wrapper component' },
|
|
368
|
-
{ title: '🧩 Component', value: 'component', description: 'A reusable React component' },
|
|
369
|
-
{ title: '🪝 Hook', value: 'hook', description: 'A custom React hook' },
|
|
370
|
-
{ title: '🔌 API Route', value: 'api', description: 'An API endpoint' },
|
|
371
|
-
{ title: '⚡ Server Action', value: 'action', description: 'A server action function' },
|
|
372
|
-
{ title: '🛡️ Middleware', value: 'middleware', description: 'Request middleware' },
|
|
373
|
-
{ title: '🌐 Context', value: 'context', description: 'React context provider' },
|
|
374
|
-
{ title: '⏳ Loading', value: 'loading', description: 'Loading state component' },
|
|
375
|
-
{ title: '❌ Error', value: 'error', description: 'Error boundary component' },
|
|
376
|
-
{ title: '🔍 Not Found', value: 'not-found', description: '404 page component' },
|
|
377
|
-
],
|
|
378
|
-
},
|
|
379
|
-
{
|
|
380
|
-
type: (prev) => ['loading', 'error', 'not-found'].includes(prev) ? null : 'text',
|
|
381
|
-
name: 'name',
|
|
382
|
-
message: 'Name:',
|
|
383
|
-
validate: (v: string) => v.length > 0 || 'Name is required',
|
|
384
|
-
},
|
|
385
|
-
]);
|
|
386
|
-
|
|
387
|
-
if (!response.type) process.exit(0);
|
|
388
|
-
type = response.type;
|
|
389
|
-
name = response.name;
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
// Validate type
|
|
393
|
-
const validTypes: GeneratorType[] = [
|
|
394
|
-
'page', 'layout', 'component', 'hook', 'api',
|
|
395
|
-
'action', 'middleware', 'context', 'loading', 'error', 'not-found'
|
|
396
|
-
];
|
|
397
|
-
|
|
398
|
-
if (!validTypes.includes(type as GeneratorType)) {
|
|
399
|
-
log.error(`Invalid type: ${type}`);
|
|
400
|
-
log.info(`Valid types: ${validTypes.join(', ')}`);
|
|
401
|
-
process.exit(1);
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
// Special types that don't need a name
|
|
405
|
-
if (type && ['loading', 'error', 'not-found'].includes(type)) {
|
|
406
|
-
await generateSpecialFile(type as 'loading' | 'error' | 'not-found', cwd);
|
|
407
|
-
return;
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
// Get name if not provided
|
|
411
|
-
let finalName = name;
|
|
412
|
-
if (!finalName) {
|
|
413
|
-
const response = await prompts({
|
|
414
|
-
type: 'text',
|
|
415
|
-
name: 'name',
|
|
416
|
-
message: `${toPascalCase(type || 'item')} name:`,
|
|
417
|
-
validate: (v: string) => v.length > 0 || 'Name is required',
|
|
418
|
-
});
|
|
419
|
-
finalName = response.name;
|
|
420
|
-
if (!finalName) process.exit(0);
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
// Generate based on type
|
|
424
|
-
switch (type) {
|
|
425
|
-
case 'page':
|
|
426
|
-
await generatePage(finalName, cwd);
|
|
427
|
-
break;
|
|
428
|
-
case 'layout':
|
|
429
|
-
await generateLayout(finalName, cwd);
|
|
430
|
-
break;
|
|
431
|
-
case 'component':
|
|
432
|
-
await generateComponent(finalName, cwd);
|
|
433
|
-
break;
|
|
434
|
-
case 'hook':
|
|
435
|
-
await generateHook(finalName, cwd);
|
|
436
|
-
break;
|
|
437
|
-
case 'api':
|
|
438
|
-
await generateApi(finalName, cwd);
|
|
439
|
-
break;
|
|
440
|
-
case 'action':
|
|
441
|
-
await generateAction(finalName, cwd);
|
|
442
|
-
break;
|
|
443
|
-
case 'middleware':
|
|
444
|
-
await generateMiddleware(finalName, cwd);
|
|
445
|
-
break;
|
|
446
|
-
case 'context':
|
|
447
|
-
await generateContext(finalName, cwd);
|
|
448
|
-
break;
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
async function generatePage(name: string, cwd: string): Promise<void> {
|
|
453
|
-
const response = await prompts([
|
|
454
|
-
{
|
|
455
|
-
type: 'select',
|
|
456
|
-
name: 'directory',
|
|
457
|
-
message: 'Where to create the page?',
|
|
458
|
-
choices: [
|
|
459
|
-
{ title: 'app/ (App Router)', value: 'app' },
|
|
460
|
-
{ title: 'pages/ (Pages Router)', value: 'pages' },
|
|
461
|
-
],
|
|
462
|
-
},
|
|
463
|
-
{
|
|
464
|
-
type: 'toggle',
|
|
465
|
-
name: 'client',
|
|
466
|
-
message: 'Client component? (use client)',
|
|
467
|
-
initial: false,
|
|
468
|
-
active: 'Yes',
|
|
469
|
-
inactive: 'No',
|
|
470
|
-
},
|
|
471
|
-
]);
|
|
472
|
-
|
|
473
|
-
const fileName = response.directory === 'app' ? 'page.tsx' : `${toKebabCase(name)}.tsx`;
|
|
474
|
-
const filePath = response.directory === 'app'
|
|
475
|
-
? path.join(cwd, 'app', toKebabCase(name), fileName)
|
|
476
|
-
: path.join(cwd, 'pages', fileName);
|
|
477
|
-
|
|
478
|
-
writeFile(filePath, templates.page(name, { client: response.client }));
|
|
479
|
-
log.success(`Created ${pc.cyan(path.relative(cwd, filePath))}`);
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
async function generateLayout(name: string, cwd: string): Promise<void> {
|
|
483
|
-
const filePath = path.join(cwd, 'app', toKebabCase(name), 'layout.tsx');
|
|
484
|
-
writeFile(filePath, templates.layout(name));
|
|
485
|
-
log.success(`Created ${pc.cyan(path.relative(cwd, filePath))}`);
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
async function generateComponent(name: string, cwd: string): Promise<void> {
|
|
489
|
-
const response = await prompts([
|
|
490
|
-
{
|
|
491
|
-
type: 'select',
|
|
492
|
-
name: 'directory',
|
|
493
|
-
message: 'Where to create the component?',
|
|
494
|
-
choices: [
|
|
495
|
-
{ title: 'components/', value: 'components' },
|
|
496
|
-
{ title: 'app/components/', value: 'app/components' },
|
|
497
|
-
],
|
|
498
|
-
},
|
|
499
|
-
{
|
|
500
|
-
type: 'toggle',
|
|
501
|
-
name: 'client',
|
|
502
|
-
message: 'Client component?',
|
|
503
|
-
initial: true,
|
|
504
|
-
active: 'Yes',
|
|
505
|
-
inactive: 'No',
|
|
506
|
-
},
|
|
507
|
-
{
|
|
508
|
-
type: 'toggle',
|
|
509
|
-
name: 'props',
|
|
510
|
-
message: 'Include props interface?',
|
|
511
|
-
initial: true,
|
|
512
|
-
active: 'Yes',
|
|
513
|
-
inactive: 'No',
|
|
514
|
-
},
|
|
515
|
-
]);
|
|
516
|
-
|
|
517
|
-
const filePath = path.join(cwd, response.directory, `${toPascalCase(name)}.tsx`);
|
|
518
|
-
writeFile(filePath, templates.component(name, { client: response.client, props: response.props }));
|
|
519
|
-
log.success(`Created ${pc.cyan(path.relative(cwd, filePath))}`);
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
async function generateHook(name: string, cwd: string): Promise<void> {
|
|
523
|
-
const hookName = name.startsWith('use') ? name : `use-${name}`;
|
|
524
|
-
const filePath = path.join(cwd, 'hooks', `${toKebabCase(hookName)}.ts`);
|
|
525
|
-
writeFile(filePath, templates.hook(hookName.replace(/^use-?/, '')));
|
|
526
|
-
log.success(`Created ${pc.cyan(path.relative(cwd, filePath))}`);
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
async function generateApi(name: string, cwd: string): Promise<void> {
|
|
530
|
-
const filePath = path.join(cwd, 'pages', 'api', `${toKebabCase(name)}.ts`);
|
|
531
|
-
writeFile(filePath, templates.api(name));
|
|
532
|
-
log.success(`Created ${pc.cyan(path.relative(cwd, filePath))}`);
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
async function generateAction(name: string, cwd: string): Promise<void> {
|
|
536
|
-
const filePath = path.join(cwd, 'actions', `${toKebabCase(name)}.ts`);
|
|
537
|
-
writeFile(filePath, templates.action(name));
|
|
538
|
-
log.success(`Created ${pc.cyan(path.relative(cwd, filePath))}`);
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
async function generateMiddleware(name: string, cwd: string): Promise<void> {
|
|
542
|
-
const filePath = path.join(cwd, 'middleware', `${toKebabCase(name)}.ts`);
|
|
543
|
-
writeFile(filePath, templates.middleware(name));
|
|
544
|
-
log.success(`Created ${pc.cyan(path.relative(cwd, filePath))}`);
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
async function generateContext(name: string, cwd: string): Promise<void> {
|
|
548
|
-
const filePath = path.join(cwd, 'contexts', `${toPascalCase(name)}Context.tsx`);
|
|
549
|
-
writeFile(filePath, templates.context(name));
|
|
550
|
-
log.success(`Created ${pc.cyan(path.relative(cwd, filePath))}`);
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
async function generateSpecialFile(type: 'loading' | 'error' | 'not-found', cwd: string): Promise<void> {
|
|
554
|
-
const response = await prompts({
|
|
555
|
-
type: 'text',
|
|
556
|
-
name: 'path',
|
|
557
|
-
message: 'Path (relative to app/):',
|
|
558
|
-
initial: '',
|
|
559
|
-
});
|
|
560
|
-
|
|
561
|
-
const basePath = response.path ? path.join(cwd, 'app', response.path) : path.join(cwd, 'app');
|
|
562
|
-
|
|
563
|
-
let fileName: string;
|
|
564
|
-
let content: string;
|
|
565
|
-
|
|
566
|
-
switch (type) {
|
|
567
|
-
case 'loading':
|
|
568
|
-
fileName = 'loading.tsx';
|
|
569
|
-
content = templates.loading();
|
|
570
|
-
break;
|
|
571
|
-
case 'error':
|
|
572
|
-
fileName = 'error.tsx';
|
|
573
|
-
content = templates.error();
|
|
574
|
-
break;
|
|
575
|
-
case 'not-found':
|
|
576
|
-
fileName = 'not-found.tsx';
|
|
577
|
-
content = templates.notFound();
|
|
578
|
-
break;
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
const filePath = path.join(basePath, fileName);
|
|
582
|
-
writeFile(filePath, content);
|
|
583
|
-
log.success(`Created ${pc.cyan(path.relative(cwd, filePath))}`);
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
// ============================================================================
|
|
587
|
-
// List Generators
|
|
588
|
-
// ============================================================================
|
|
589
|
-
|
|
590
|
-
export function listGenerators(): void {
|
|
591
|
-
console.log(`
|
|
592
|
-
${pc.bold('Available Generators:')}
|
|
593
|
-
|
|
594
|
-
${pc.cyan('page')} Create a new page (app/ or pages/)
|
|
595
|
-
${pc.cyan('layout')} Create a layout wrapper
|
|
596
|
-
${pc.cyan('component')} Create a React component
|
|
597
|
-
${pc.cyan('hook')} Create a custom hook
|
|
598
|
-
${pc.cyan('api')} Create an API route
|
|
599
|
-
${pc.cyan('action')} Create a server action
|
|
600
|
-
${pc.cyan('middleware')} Create request middleware
|
|
601
|
-
${pc.cyan('context')} Create a React context
|
|
602
|
-
${pc.cyan('loading')} Create a loading component
|
|
603
|
-
${pc.cyan('error')} Create an error boundary
|
|
604
|
-
${pc.cyan('not-found')} Create a 404 page
|
|
605
|
-
|
|
606
|
-
${pc.bold('Usage:')}
|
|
607
|
-
${pc.dim('$')} flexi generate ${pc.cyan('<type>')} ${pc.dim('[name]')}
|
|
608
|
-
${pc.dim('$')} flexi g ${pc.cyan('<type>')} ${pc.dim('[name]')}
|
|
609
|
-
|
|
610
|
-
${pc.bold('Examples:')}
|
|
611
|
-
${pc.dim('$')} flexi g page dashboard
|
|
612
|
-
${pc.dim('$')} flexi g component Button
|
|
613
|
-
${pc.dim('$')} flexi g hook auth
|
|
614
|
-
${pc.dim('$')} flexi g api users
|
|
615
|
-
`);
|
|
616
|
-
}
|