@xbg.solutions/create-frontend 1.1.1
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/lib/index.d.ts +1 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +4 -0
- package/lib/index.js.map +1 -0
- package/package.json +21 -0
- package/src/generate-component.cjs +602 -0
- package/src/generate-route.cjs +774 -0
- package/src/generate-service.cjs +1306 -0
- package/src/index.ts +2 -0
- package/src/manage-auth-users.cjs +410 -0
- package/src/setup.cjs +1049 -0
- package/src/validate-setup.cjs +341 -0
- package/tsconfig.json +20 -0
|
@@ -0,0 +1,774 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Route Generator
|
|
5
|
+
*
|
|
6
|
+
* Generates a new SvelteKit route with TypeScript support, layout, and load functions.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* node __scripts__/generate-route.js <route-path> [options]
|
|
10
|
+
* npm run generate:route <route-path> [options]
|
|
11
|
+
*
|
|
12
|
+
* Options:
|
|
13
|
+
* --type <type> Route type: page|layout|api|group (default: page)
|
|
14
|
+
* --auth Require authentication
|
|
15
|
+
* --roles <roles> Required roles (comma-separated)
|
|
16
|
+
* --with-load Generate load function
|
|
17
|
+
* --with-actions Generate form actions
|
|
18
|
+
* --with-error Generate error page
|
|
19
|
+
*
|
|
20
|
+
* Examples:
|
|
21
|
+
* node __scripts__/generate-route.js dashboard --auth --roles=user,admin
|
|
22
|
+
* node __scripts__/generate-route.js api/users --type=api --with-actions
|
|
23
|
+
* node __scripts__/generate-route.js admin --type=layout --auth --roles=admin
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
const fs = require('fs');
|
|
27
|
+
const path = require('path');
|
|
28
|
+
|
|
29
|
+
// Parse command line arguments
|
|
30
|
+
const args = process.argv.slice(2);
|
|
31
|
+
const routePath = args[0];
|
|
32
|
+
|
|
33
|
+
if (!routePath) {
|
|
34
|
+
console.error('ā Route path is required!');
|
|
35
|
+
console.log('Usage: node __scripts__/generate-route.js <route-path> [options]');
|
|
36
|
+
console.log('Examples:');
|
|
37
|
+
console.log(' node __scripts__/generate-route.js dashboard');
|
|
38
|
+
console.log(' node __scripts__/generate-route.js api/users --type=api');
|
|
39
|
+
console.log(' node __scripts__/generate-route.js admin --type=layout --auth');
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Parse options
|
|
44
|
+
const options = {
|
|
45
|
+
type: 'page',
|
|
46
|
+
auth: false,
|
|
47
|
+
roles: [],
|
|
48
|
+
withLoad: false,
|
|
49
|
+
withActions: false,
|
|
50
|
+
withError: false
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
args.slice(1).forEach(arg => {
|
|
54
|
+
if (arg.startsWith('--type=')) {
|
|
55
|
+
options.type = arg.split('=')[1];
|
|
56
|
+
} else if (arg.startsWith('--roles=')) {
|
|
57
|
+
options.roles = arg.split('=')[1].split(',').map(r => r.trim());
|
|
58
|
+
} else if (arg === '--auth') {
|
|
59
|
+
options.auth = true;
|
|
60
|
+
} else if (arg === '--with-load') {
|
|
61
|
+
options.withLoad = true;
|
|
62
|
+
} else if (arg === '--with-actions') {
|
|
63
|
+
options.withActions = true;
|
|
64
|
+
} else if (arg === '--with-error') {
|
|
65
|
+
options.withError = true;
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// Validate route type
|
|
70
|
+
if (!['page', 'layout', 'api', 'group'].includes(options.type)) {
|
|
71
|
+
console.error('ā Invalid route type. Must be: page, layout, api, or group');
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Generate file paths
|
|
76
|
+
const routeDir = path.join('src', 'routes', routePath);
|
|
77
|
+
const isApiRoute = routePath.startsWith('api/') || options.type === 'api';
|
|
78
|
+
|
|
79
|
+
// Ensure directory exists
|
|
80
|
+
if (!fs.existsSync(routeDir)) {
|
|
81
|
+
fs.mkdirSync(routeDir, { recursive: true });
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Generate route name for display
|
|
85
|
+
const routeName = routePath
|
|
86
|
+
.split('/')
|
|
87
|
+
.pop()
|
|
88
|
+
.split('-')
|
|
89
|
+
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
|
90
|
+
.join(' ');
|
|
91
|
+
|
|
92
|
+
// Generate page template
|
|
93
|
+
function generatePageTemplate() {
|
|
94
|
+
const authImport = options.auth ? `
|
|
95
|
+
import { authGuard } from '$lib/utils/auth-guard';
|
|
96
|
+
import { authService } from '$lib/services/auth';` : '';
|
|
97
|
+
|
|
98
|
+
const authCode = options.auth ? `
|
|
99
|
+
const user = authService.getUser();
|
|
100
|
+
const claims = authService.getUserClaims();
|
|
101
|
+
|
|
102
|
+
onMount(() => {
|
|
103
|
+
authGuard({
|
|
104
|
+
requiredRoles: ${JSON.stringify(options.roles)},
|
|
105
|
+
redirectTo: '/login',
|
|
106
|
+
currentPath: $page.url.pathname
|
|
107
|
+
});
|
|
108
|
+
});` : '';
|
|
109
|
+
|
|
110
|
+
const rolesCheck = options.roles.length > 0 ? `
|
|
111
|
+
$: hasRequiredRole = ${options.roles.map(role => `$claims?.roles?.includes('${role}')`).join(' || ')};` : '';
|
|
112
|
+
|
|
113
|
+
return `<script lang="ts">
|
|
114
|
+
import { page } from '$app/stores';
|
|
115
|
+
import { onMount } from 'svelte';${authImport}
|
|
116
|
+
import { Button } from '$lib/components/ui/button';
|
|
117
|
+
import { Card, CardHeader, CardTitle, CardContent } from '$lib/components/ui/card';
|
|
118
|
+
${options.withLoad ? "import type { PageData } from './$types';" : ''}
|
|
119
|
+
|
|
120
|
+
${options.withLoad ? 'export let data: PageData;' : ''}
|
|
121
|
+
|
|
122
|
+
// Page state
|
|
123
|
+
let loading = false;
|
|
124
|
+
let error: string | null = null;${authCode}${rolesCheck}
|
|
125
|
+
|
|
126
|
+
onMount(() => {
|
|
127
|
+
loadPageData();
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
async function loadPageData() {
|
|
131
|
+
loading = true;
|
|
132
|
+
error = null;
|
|
133
|
+
|
|
134
|
+
try {
|
|
135
|
+
// Load page-specific data
|
|
136
|
+
console.log('Loading ${routeName} data...');
|
|
137
|
+
} catch (err) {
|
|
138
|
+
error = 'Failed to load page data';
|
|
139
|
+
console.error('${routeName} error:', err);
|
|
140
|
+
} finally {
|
|
141
|
+
loading = false;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function handleRefresh() {
|
|
146
|
+
loadPageData();
|
|
147
|
+
}
|
|
148
|
+
</script>
|
|
149
|
+
|
|
150
|
+
<svelte:head>
|
|
151
|
+
<title>${routeName}</title>
|
|
152
|
+
<meta name="description" content="${routeName} page" />
|
|
153
|
+
</svelte:head>
|
|
154
|
+
|
|
155
|
+
<div class="container mx-auto py-6">
|
|
156
|
+
<div class="mb-6">
|
|
157
|
+
<h1 class="text-3xl font-bold">${routeName}</h1>
|
|
158
|
+
<p class="text-gray-600">Welcome to the ${routeName.toLowerCase()} page</p>
|
|
159
|
+
</div>
|
|
160
|
+
${options.auth && options.roles.length > 0 ? `
|
|
161
|
+
{#if !hasRequiredRole}
|
|
162
|
+
<Card>
|
|
163
|
+
<CardContent class="p-6 text-center">
|
|
164
|
+
<h2 class="text-xl font-semibold mb-2">Access Denied</h2>
|
|
165
|
+
<p class="text-gray-600">You don't have permission to view this page.</p>
|
|
166
|
+
<p class="text-sm text-gray-500 mt-2">Required roles: ${options.roles.join(', ')}</p>
|
|
167
|
+
<Button href="/dashboard" variant="outline" class="mt-4">
|
|
168
|
+
Go to Dashboard
|
|
169
|
+
</Button>
|
|
170
|
+
</CardContent>
|
|
171
|
+
</Card>
|
|
172
|
+
{:else}` : ''}
|
|
173
|
+
|
|
174
|
+
{#if loading}
|
|
175
|
+
<div class="flex justify-center py-12">
|
|
176
|
+
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-500"></div>
|
|
177
|
+
</div>
|
|
178
|
+
{:else if error}
|
|
179
|
+
<Card>
|
|
180
|
+
<CardContent class="p-6">
|
|
181
|
+
<div class="text-center">
|
|
182
|
+
<h2 class="text-xl font-semibold mb-2 text-red-600">Error</h2>
|
|
183
|
+
<p class="text-gray-600 mb-4">{error}</p>
|
|
184
|
+
<Button on:click={handleRefresh} variant="outline">
|
|
185
|
+
Try Again
|
|
186
|
+
</Button>
|
|
187
|
+
</div>
|
|
188
|
+
</CardContent>
|
|
189
|
+
</Card>
|
|
190
|
+
{:else}
|
|
191
|
+
<div class="grid gap-6">
|
|
192
|
+
<Card>
|
|
193
|
+
<CardHeader>
|
|
194
|
+
<CardTitle>${routeName} Content</CardTitle>
|
|
195
|
+
</CardHeader>
|
|
196
|
+
<CardContent>
|
|
197
|
+
<p>Add your page content here.</p>
|
|
198
|
+
${options.auth ? `
|
|
199
|
+
{#if $user}
|
|
200
|
+
<div class="mt-4 p-4 bg-green-50 rounded-lg">
|
|
201
|
+
<p class="text-green-800">Authenticated as: {$user.email}</p>
|
|
202
|
+
{#if $claims?.roles?.length}
|
|
203
|
+
<p class="text-green-600 text-sm">Roles: {$claims.roles.join(', ')}</p>
|
|
204
|
+
{/if}
|
|
205
|
+
</div>
|
|
206
|
+
{/if}` : ''}
|
|
207
|
+
</CardContent>
|
|
208
|
+
</Card>
|
|
209
|
+
</div>
|
|
210
|
+
{/if}${options.auth && options.roles.length > 0 ? `
|
|
211
|
+
{/if}` : ''}
|
|
212
|
+
</div>`;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Generate layout template
|
|
216
|
+
function generateLayoutTemplate() {
|
|
217
|
+
const authImport = options.auth ? `
|
|
218
|
+
import { authGuard } from '$lib/utils/auth-guard';
|
|
219
|
+
import { authService } from '$lib/services/auth';` : '';
|
|
220
|
+
|
|
221
|
+
const authCode = options.auth ? `
|
|
222
|
+
const user = authService.getUser();
|
|
223
|
+
const claims = authService.getUserClaims();
|
|
224
|
+
|
|
225
|
+
onMount(() => {
|
|
226
|
+
authGuard({
|
|
227
|
+
requiredRoles: ${JSON.stringify(options.roles)},
|
|
228
|
+
redirectTo: '/login',
|
|
229
|
+
currentPath: $page.url.pathname
|
|
230
|
+
});
|
|
231
|
+
});` : '';
|
|
232
|
+
|
|
233
|
+
return `<script lang="ts">
|
|
234
|
+
import { page } from '$app/stores';
|
|
235
|
+
import { onMount } from 'svelte';${authImport}
|
|
236
|
+
import { Button } from '$lib/components/ui/button';
|
|
237
|
+
${options.withLoad ? "import type { LayoutData } from './$types';" : ''}
|
|
238
|
+
|
|
239
|
+
${options.withLoad ? 'export let data: LayoutData;' : ''}${authCode}
|
|
240
|
+
</script>
|
|
241
|
+
|
|
242
|
+
<div class="min-h-screen bg-gray-50">
|
|
243
|
+
<!-- Navigation header -->
|
|
244
|
+
<header class="bg-white shadow-sm border-b">
|
|
245
|
+
<div class="container mx-auto px-4">
|
|
246
|
+
<div class="flex items-center justify-between h-16">
|
|
247
|
+
<div class="flex items-center">
|
|
248
|
+
<h1 class="text-xl font-semibold">${routeName}</h1>
|
|
249
|
+
<nav class="ml-8 space-x-4">
|
|
250
|
+
<!-- Add navigation links -->
|
|
251
|
+
</nav>
|
|
252
|
+
</div>
|
|
253
|
+
|
|
254
|
+
<div class="flex items-center space-x-4">
|
|
255
|
+
${options.auth ? `
|
|
256
|
+
{#if $user}
|
|
257
|
+
<span class="text-sm text-gray-600">Welcome, {$user.email}</span>
|
|
258
|
+
<Button variant="outline" size="sm" on:click={() => authService.logout()}>
|
|
259
|
+
Logout
|
|
260
|
+
</Button>
|
|
261
|
+
{:else}
|
|
262
|
+
<Button href="/login" size="sm">
|
|
263
|
+
Login
|
|
264
|
+
</Button>
|
|
265
|
+
{/if}` : ''}
|
|
266
|
+
</div>
|
|
267
|
+
</div>
|
|
268
|
+
</div>
|
|
269
|
+
</header>
|
|
270
|
+
|
|
271
|
+
<!-- Main content -->
|
|
272
|
+
<main>
|
|
273
|
+
<slot />
|
|
274
|
+
</main>
|
|
275
|
+
|
|
276
|
+
<!-- Footer -->
|
|
277
|
+
<footer class="bg-white border-t mt-auto">
|
|
278
|
+
<div class="container mx-auto px-4 py-4">
|
|
279
|
+
<p class="text-center text-gray-600 text-sm">
|
|
280
|
+
Ā© {new Date().getFullYear()} Your App. All rights reserved.
|
|
281
|
+
</p>
|
|
282
|
+
</div>
|
|
283
|
+
</footer>
|
|
284
|
+
</div>`;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Generate API route template
|
|
288
|
+
function generateApiTemplate() {
|
|
289
|
+
const methods = options.withActions ? ['GET', 'POST', 'PUT', 'DELETE'] : ['GET'];
|
|
290
|
+
|
|
291
|
+
let template = `import { json, error } from '@sveltejs/kit';
|
|
292
|
+
import type { RequestHandler } from './$types';
|
|
293
|
+
import { apiService } from '$lib/services/api/api.service';
|
|
294
|
+
import { handleError } from '$lib/utils/error-handler';
|
|
295
|
+
|
|
296
|
+
// Types
|
|
297
|
+
interface ${routeName}Item {
|
|
298
|
+
id: string;
|
|
299
|
+
// Add your data structure here
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
interface Create${routeName}Request {
|
|
303
|
+
// Add creation request structure
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
interface Update${routeName}Request {
|
|
307
|
+
// Add update request structure
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
`;
|
|
311
|
+
|
|
312
|
+
methods.forEach(method => {
|
|
313
|
+
switch (method) {
|
|
314
|
+
case 'GET':
|
|
315
|
+
template += `
|
|
316
|
+
// GET /${routePath}
|
|
317
|
+
export const GET: RequestHandler = async ({ url, locals }) => {
|
|
318
|
+
try {
|
|
319
|
+
// Optional: Check authentication
|
|
320
|
+
// if (!locals.user) {
|
|
321
|
+
// throw error(401, 'Unauthorized');
|
|
322
|
+
// }
|
|
323
|
+
|
|
324
|
+
// Parse query parameters
|
|
325
|
+
const limit = Number(url.searchParams.get('limit')) || 10;
|
|
326
|
+
const offset = Number(url.searchParams.get('offset')) || 0;
|
|
327
|
+
const search = url.searchParams.get('search') || '';
|
|
328
|
+
|
|
329
|
+
// Fetch data (replace with your data source)
|
|
330
|
+
const items: ${routeName}Item[] = [
|
|
331
|
+
// Mock data - replace with actual data fetching
|
|
332
|
+
];
|
|
333
|
+
|
|
334
|
+
// Apply search filter if provided
|
|
335
|
+
const filteredItems = search
|
|
336
|
+
? items.filter(item =>
|
|
337
|
+
Object.values(item).some(value =>
|
|
338
|
+
String(value).toLowerCase().includes(search.toLowerCase())
|
|
339
|
+
)
|
|
340
|
+
)
|
|
341
|
+
: items;
|
|
342
|
+
|
|
343
|
+
// Apply pagination
|
|
344
|
+
const paginatedItems = filteredItems.slice(offset, offset + limit);
|
|
345
|
+
|
|
346
|
+
return json({
|
|
347
|
+
data: paginatedItems,
|
|
348
|
+
meta: {
|
|
349
|
+
total: filteredItems.length,
|
|
350
|
+
limit,
|
|
351
|
+
offset,
|
|
352
|
+
hasMore: offset + limit < filteredItems.length
|
|
353
|
+
}
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
} catch (err) {
|
|
357
|
+
console.error('GET /${routePath} error:', err);
|
|
358
|
+
const errorResponse = handleError(err, 'Failed to fetch ${routeName.toLowerCase()}');
|
|
359
|
+
throw error(500, errorResponse);
|
|
360
|
+
}
|
|
361
|
+
};`;
|
|
362
|
+
break;
|
|
363
|
+
|
|
364
|
+
case 'POST':
|
|
365
|
+
template += `
|
|
366
|
+
|
|
367
|
+
// POST /${routePath}
|
|
368
|
+
export const POST: RequestHandler = async ({ request, locals }) => {
|
|
369
|
+
try {
|
|
370
|
+
// Check authentication
|
|
371
|
+
// if (!locals.user) {
|
|
372
|
+
// throw error(401, 'Unauthorized');
|
|
373
|
+
// }
|
|
374
|
+
|
|
375
|
+
const requestData: Create${routeName}Request = await request.json();
|
|
376
|
+
|
|
377
|
+
// Validate request data
|
|
378
|
+
if (!requestData) {
|
|
379
|
+
throw error(400, 'Invalid request data');
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// Create new item (replace with your data creation logic)
|
|
383
|
+
const newItem: ${routeName}Item = {
|
|
384
|
+
id: crypto.randomUUID(),
|
|
385
|
+
...requestData,
|
|
386
|
+
// Add other fields like timestamps, user ID, etc.
|
|
387
|
+
};
|
|
388
|
+
|
|
389
|
+
// Save to database/storage
|
|
390
|
+
// await saveItem(newItem);
|
|
391
|
+
|
|
392
|
+
return json({
|
|
393
|
+
data: newItem,
|
|
394
|
+
message: '${routeName} created successfully'
|
|
395
|
+
}, { status: 201 });
|
|
396
|
+
|
|
397
|
+
} catch (err) {
|
|
398
|
+
console.error('POST /${routePath} error:', err);
|
|
399
|
+
const errorResponse = handleError(err, 'Failed to create ${routeName.toLowerCase()}');
|
|
400
|
+
throw error(500, errorResponse);
|
|
401
|
+
}
|
|
402
|
+
};`;
|
|
403
|
+
break;
|
|
404
|
+
|
|
405
|
+
case 'PUT':
|
|
406
|
+
template += `
|
|
407
|
+
|
|
408
|
+
// PUT /${routePath}/[id]
|
|
409
|
+
export const PUT: RequestHandler = async ({ params, request, locals }) => {
|
|
410
|
+
try {
|
|
411
|
+
// Check authentication
|
|
412
|
+
// if (!locals.user) {
|
|
413
|
+
// throw error(401, 'Unauthorized');
|
|
414
|
+
// }
|
|
415
|
+
|
|
416
|
+
const { id } = params;
|
|
417
|
+
if (!id) {
|
|
418
|
+
throw error(400, 'ID parameter is required');
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
const requestData: Update${routeName}Request = await request.json();
|
|
422
|
+
|
|
423
|
+
// Find existing item
|
|
424
|
+
// const existingItem = await findItemById(id);
|
|
425
|
+
// if (!existingItem) {
|
|
426
|
+
// throw error(404, '${routeName} not found');
|
|
427
|
+
// }
|
|
428
|
+
|
|
429
|
+
// Update item (replace with your update logic)
|
|
430
|
+
const updatedItem: ${routeName}Item = {
|
|
431
|
+
id,
|
|
432
|
+
...requestData,
|
|
433
|
+
// Preserve certain fields or add timestamps
|
|
434
|
+
};
|
|
435
|
+
|
|
436
|
+
// Save updated item
|
|
437
|
+
// await updateItem(id, updatedItem);
|
|
438
|
+
|
|
439
|
+
return json({
|
|
440
|
+
data: updatedItem,
|
|
441
|
+
message: '${routeName} updated successfully'
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
} catch (err) {
|
|
445
|
+
console.error('PUT /${routePath}/[id] error:', err);
|
|
446
|
+
const errorResponse = handleError(err, 'Failed to update ${routeName.toLowerCase()}');
|
|
447
|
+
throw error(500, errorResponse);
|
|
448
|
+
}
|
|
449
|
+
};`;
|
|
450
|
+
break;
|
|
451
|
+
|
|
452
|
+
case 'DELETE':
|
|
453
|
+
template += `
|
|
454
|
+
|
|
455
|
+
// DELETE /${routePath}/[id]
|
|
456
|
+
export const DELETE: RequestHandler = async ({ params, locals }) => {
|
|
457
|
+
try {
|
|
458
|
+
// Check authentication
|
|
459
|
+
// if (!locals.user) {
|
|
460
|
+
// throw error(401, 'Unauthorized');
|
|
461
|
+
// }
|
|
462
|
+
|
|
463
|
+
const { id } = params;
|
|
464
|
+
if (!id) {
|
|
465
|
+
throw error(400, 'ID parameter is required');
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// Find existing item
|
|
469
|
+
// const existingItem = await findItemById(id);
|
|
470
|
+
// if (!existingItem) {
|
|
471
|
+
// throw error(404, '${routeName} not found');
|
|
472
|
+
// }
|
|
473
|
+
|
|
474
|
+
// Delete item (replace with your deletion logic)
|
|
475
|
+
// await deleteItem(id);
|
|
476
|
+
|
|
477
|
+
return json({
|
|
478
|
+
message: '${routeName} deleted successfully'
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
} catch (err) {
|
|
482
|
+
console.error('DELETE /${routePath}/[id] error:', err);
|
|
483
|
+
const errorResponse = handleError(err, 'Failed to delete ${routeName.toLowerCase()}');
|
|
484
|
+
throw error(500, errorResponse);
|
|
485
|
+
}
|
|
486
|
+
};`;
|
|
487
|
+
break;
|
|
488
|
+
}
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
return template;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// Generate load function
|
|
495
|
+
function generateLoadFunction() {
|
|
496
|
+
const fileName = options.type === 'layout' ? '+layout.ts' : '+page.ts';
|
|
497
|
+
|
|
498
|
+
const authCheck = options.auth ? `
|
|
499
|
+
// Check authentication
|
|
500
|
+
if (!locals.user) {
|
|
501
|
+
throw redirect(302, '/login');
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// Check role permissions
|
|
505
|
+
const userRoles = locals.claims?.roles || [];
|
|
506
|
+
const requiredRoles = ${JSON.stringify(options.roles)};
|
|
507
|
+
|
|
508
|
+
if (requiredRoles.length > 0 && !requiredRoles.some(role => userRoles.includes(role))) {
|
|
509
|
+
throw redirect(302, '/unauthorized');
|
|
510
|
+
}` : '';
|
|
511
|
+
|
|
512
|
+
return `import type { ${options.type === 'layout' ? 'LayoutLoad' : 'PageLoad'} } from './$types';
|
|
513
|
+
import { error, redirect } from '@sveltejs/kit';
|
|
514
|
+
import { apiService } from '$lib/services/api/api.service';
|
|
515
|
+
|
|
516
|
+
export const load: ${options.type === 'layout' ? 'LayoutLoad' : 'PageLoad'} = async ({ params, url, locals, fetch }) => {${authCheck}
|
|
517
|
+
|
|
518
|
+
try {
|
|
519
|
+
// Load page/layout data
|
|
520
|
+
const data = {
|
|
521
|
+
// Add your data loading logic here
|
|
522
|
+
title: '${routeName}',
|
|
523
|
+
user: locals.user || null,
|
|
524
|
+
claims: locals.claims || null
|
|
525
|
+
};
|
|
526
|
+
|
|
527
|
+
// Example: Load data from API
|
|
528
|
+
// const response = await apiService.get('/api/${routePath}', { fetch });
|
|
529
|
+
// data.items = response.data;
|
|
530
|
+
|
|
531
|
+
return data;
|
|
532
|
+
|
|
533
|
+
} catch (err) {
|
|
534
|
+
console.error('Load function error:', err);
|
|
535
|
+
throw error(500, 'Failed to load page data');
|
|
536
|
+
}
|
|
537
|
+
};`;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
// Generate form actions
|
|
541
|
+
function generateFormActions() {
|
|
542
|
+
return `import { fail, redirect } from '@sveltejs/kit';
|
|
543
|
+
import type { Actions } from './$types';
|
|
544
|
+
import { apiService } from '$lib/services/api/api.service';
|
|
545
|
+
import { handleError } from '$lib/utils/error-handler';
|
|
546
|
+
|
|
547
|
+
export const actions: Actions = {
|
|
548
|
+
// Default form action
|
|
549
|
+
default: async ({ request, locals }) => {
|
|
550
|
+
${options.auth ? `
|
|
551
|
+
if (!locals.user) {
|
|
552
|
+
throw redirect(302, '/login');
|
|
553
|
+
}` : ''}
|
|
554
|
+
|
|
555
|
+
const formData = await request.formData();
|
|
556
|
+
|
|
557
|
+
try {
|
|
558
|
+
// Process form data
|
|
559
|
+
const data = {
|
|
560
|
+
// Extract form fields
|
|
561
|
+
// name: formData.get('name')?.toString(),
|
|
562
|
+
// email: formData.get('email')?.toString(),
|
|
563
|
+
};
|
|
564
|
+
|
|
565
|
+
// Validate data
|
|
566
|
+
if (!data) {
|
|
567
|
+
return fail(400, {
|
|
568
|
+
error: 'Invalid form data',
|
|
569
|
+
data
|
|
570
|
+
});
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
// Process the form submission
|
|
574
|
+
// const result = await apiService.post('/api/${routePath}', data);
|
|
575
|
+
|
|
576
|
+
return {
|
|
577
|
+
success: true,
|
|
578
|
+
message: 'Form submitted successfully'
|
|
579
|
+
};
|
|
580
|
+
|
|
581
|
+
} catch (err) {
|
|
582
|
+
console.error('Form action error:', err);
|
|
583
|
+
return fail(500, {
|
|
584
|
+
error: handleError(err, 'Form submission failed'),
|
|
585
|
+
data: Object.fromEntries(formData)
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
},
|
|
589
|
+
|
|
590
|
+
// Create action
|
|
591
|
+
create: async ({ request, locals }) => {
|
|
592
|
+
${options.auth ? `
|
|
593
|
+
if (!locals.user) {
|
|
594
|
+
throw redirect(302, '/login');
|
|
595
|
+
}` : ''}
|
|
596
|
+
|
|
597
|
+
const formData = await request.formData();
|
|
598
|
+
|
|
599
|
+
try {
|
|
600
|
+
// Handle create logic
|
|
601
|
+
return { success: true };
|
|
602
|
+
} catch (err) {
|
|
603
|
+
return fail(500, { error: 'Create failed' });
|
|
604
|
+
}
|
|
605
|
+
},
|
|
606
|
+
|
|
607
|
+
// Update action
|
|
608
|
+
update: async ({ request, locals }) => {
|
|
609
|
+
${options.auth ? `
|
|
610
|
+
if (!locals.user) {
|
|
611
|
+
throw redirect(302, '/login');
|
|
612
|
+
}` : ''}
|
|
613
|
+
|
|
614
|
+
const formData = await request.formData();
|
|
615
|
+
|
|
616
|
+
try {
|
|
617
|
+
// Handle update logic
|
|
618
|
+
return { success: true };
|
|
619
|
+
} catch (err) {
|
|
620
|
+
return fail(500, { error: 'Update failed' });
|
|
621
|
+
}
|
|
622
|
+
},
|
|
623
|
+
|
|
624
|
+
// Delete action
|
|
625
|
+
delete: async ({ request, locals }) => {
|
|
626
|
+
${options.auth ? `
|
|
627
|
+
if (!locals.user) {
|
|
628
|
+
throw redirect(302, '/login');
|
|
629
|
+
}` : ''}
|
|
630
|
+
|
|
631
|
+
const formData = await request.formData();
|
|
632
|
+
const id = formData.get('id')?.toString();
|
|
633
|
+
|
|
634
|
+
if (!id) {
|
|
635
|
+
return fail(400, { error: 'ID is required' });
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
try {
|
|
639
|
+
// Handle delete logic
|
|
640
|
+
return { success: true };
|
|
641
|
+
} catch (err) {
|
|
642
|
+
return fail(500, { error: 'Delete failed' });
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
};`;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
// Generate error page
|
|
649
|
+
function generateErrorTemplate() {
|
|
650
|
+
return `<script lang="ts">
|
|
651
|
+
import { page } from '$app/stores';
|
|
652
|
+
import { Button } from '$lib/components/ui/button';
|
|
653
|
+
import { Card, CardHeader, CardTitle, CardContent } from '$lib/components/ui/card';
|
|
654
|
+
|
|
655
|
+
$: error = $page.error;
|
|
656
|
+
$: errorMessage = error?.message || 'An unexpected error occurred';
|
|
657
|
+
$: errorCode = error?.code || 500;
|
|
658
|
+
</script>
|
|
659
|
+
|
|
660
|
+
<svelte:head>
|
|
661
|
+
<title>Error {errorCode}</title>
|
|
662
|
+
</svelte:head>
|
|
663
|
+
|
|
664
|
+
<div class="min-h-screen flex items-center justify-center bg-gray-50 px-4">
|
|
665
|
+
<Card class="max-w-md w-full">
|
|
666
|
+
<CardHeader class="text-center">
|
|
667
|
+
<div class="w-20 h-20 bg-red-100 rounded-full flex items-center justify-center mx-auto mb-4">
|
|
668
|
+
<svg class="w-10 h-10 text-red-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
669
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
|
670
|
+
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z" />
|
|
671
|
+
</svg>
|
|
672
|
+
</div>
|
|
673
|
+
<CardTitle class="text-2xl text-red-600">Error {errorCode}</CardTitle>
|
|
674
|
+
</CardHeader>
|
|
675
|
+
|
|
676
|
+
<CardContent class="text-center space-y-4">
|
|
677
|
+
<p class="text-gray-600">{errorMessage}</p>
|
|
678
|
+
|
|
679
|
+
<div class="space-y-2">
|
|
680
|
+
<Button href="/" class="w-full">
|
|
681
|
+
Go Home
|
|
682
|
+
</Button>
|
|
683
|
+
<Button href="/dashboard" variant="outline" class="w-full">
|
|
684
|
+
Go to Dashboard
|
|
685
|
+
</Button>
|
|
686
|
+
</div>
|
|
687
|
+
|
|
688
|
+
<details class="text-left mt-4">
|
|
689
|
+
<summary class="cursor-pointer text-sm text-gray-500">
|
|
690
|
+
Error Details
|
|
691
|
+
</summary>
|
|
692
|
+
<pre class="mt-2 p-3 bg-gray-100 rounded text-xs overflow-auto">
|
|
693
|
+
{JSON.stringify(error, null, 2)}
|
|
694
|
+
</pre>
|
|
695
|
+
</details>
|
|
696
|
+
</CardContent>
|
|
697
|
+
</Card>
|
|
698
|
+
</div>`;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
// Write files
|
|
702
|
+
try {
|
|
703
|
+
console.log(`š Generating ${options.type} route: /${routePath}`);
|
|
704
|
+
|
|
705
|
+
let filesToCreate = [];
|
|
706
|
+
|
|
707
|
+
if (isApiRoute || options.type === 'api') {
|
|
708
|
+
// API route
|
|
709
|
+
const serverFile = path.join(routeDir, '+server.ts');
|
|
710
|
+
fs.writeFileSync(serverFile, generateApiTemplate());
|
|
711
|
+
filesToCreate.push(serverFile);
|
|
712
|
+
|
|
713
|
+
} else if (options.type === 'layout') {
|
|
714
|
+
// Layout route
|
|
715
|
+
const layoutFile = path.join(routeDir, '+layout.svelte');
|
|
716
|
+
fs.writeFileSync(layoutFile, generateLayoutTemplate());
|
|
717
|
+
filesToCreate.push(layoutFile);
|
|
718
|
+
|
|
719
|
+
if (options.withLoad) {
|
|
720
|
+
const loadFile = path.join(routeDir, '+layout.ts');
|
|
721
|
+
fs.writeFileSync(loadFile, generateLoadFunction());
|
|
722
|
+
filesToCreate.push(loadFile);
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
} else if (options.type === 'group') {
|
|
726
|
+
// Route group (just create directory with layout)
|
|
727
|
+
const layoutFile = path.join(routeDir, '+layout.svelte');
|
|
728
|
+
fs.writeFileSync(layoutFile, `<slot />`);
|
|
729
|
+
filesToCreate.push(layoutFile);
|
|
730
|
+
|
|
731
|
+
} else {
|
|
732
|
+
// Page route
|
|
733
|
+
const pageFile = path.join(routeDir, '+page.svelte');
|
|
734
|
+
fs.writeFileSync(pageFile, generatePageTemplate());
|
|
735
|
+
filesToCreate.push(pageFile);
|
|
736
|
+
|
|
737
|
+
if (options.withLoad) {
|
|
738
|
+
const loadFile = path.join(routeDir, '+page.ts');
|
|
739
|
+
fs.writeFileSync(loadFile, generateLoadFunction());
|
|
740
|
+
filesToCreate.push(loadFile);
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
if (options.withActions) {
|
|
744
|
+
const actionsFile = path.join(routeDir, '+page.server.ts');
|
|
745
|
+
fs.writeFileSync(actionsFile, generateFormActions());
|
|
746
|
+
filesToCreate.push(actionsFile);
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
if (options.withError) {
|
|
751
|
+
const errorFile = path.join(routeDir, '+error.svelte');
|
|
752
|
+
fs.writeFileSync(errorFile, generateErrorTemplate());
|
|
753
|
+
filesToCreate.push(errorFile);
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
console.log(`ā
Route files created:`);
|
|
757
|
+
filesToCreate.forEach(file => {
|
|
758
|
+
console.log(` ${file}`);
|
|
759
|
+
});
|
|
760
|
+
|
|
761
|
+
console.log(`\nš Route /${routePath} generated successfully!`);
|
|
762
|
+
console.log(`\nš View your route at: http://localhost:5173/${routePath}`);
|
|
763
|
+
|
|
764
|
+
if (options.auth) {
|
|
765
|
+
console.log(`\nš Authentication: Enabled`);
|
|
766
|
+
if (options.roles.length > 0) {
|
|
767
|
+
console.log(` Required roles: ${options.roles.join(', ')}`);
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
} catch (error) {
|
|
772
|
+
console.error('ā Error generating route:', error.message);
|
|
773
|
+
process.exit(1);
|
|
774
|
+
}
|