@burger-api/cli 0.6.6 → 0.7.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/CHANGELOG.md +66 -57
- package/README.md +656 -656
- package/package.json +50 -50
- package/src/commands/add.ts +201 -201
- package/src/commands/build.ts +250 -250
- package/src/commands/create.ts +229 -229
- package/src/commands/list.ts +88 -88
- package/src/commands/serve.ts +100 -100
- package/src/index.ts +59 -59
- package/src/types/index.ts +53 -53
- package/src/utils/github.ts +260 -260
- package/src/utils/logger.ts +478 -478
- package/src/utils/templates.ts +1116 -1120
package/src/utils/templates.ts
CHANGED
|
@@ -1,1120 +1,1116 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Template Management System
|
|
3
|
-
*
|
|
4
|
-
* Handles downloading and caching project templates.
|
|
5
|
-
* Templates are the starter projects users get when running `burger-api create`
|
|
6
|
-
*
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { join } from 'path';
|
|
10
|
-
|
|
11
|
-
import type { CreateOptions } from '../types/index';
|
|
12
|
-
import { spinner } from './logger';
|
|
13
|
-
import { downloadFile } from './github';
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Generate package.json content for a new project
|
|
17
|
-
* This includes the burger-api dependency and basic scripts
|
|
18
|
-
*
|
|
19
|
-
* @param projectName - Name of the project
|
|
20
|
-
* @returns package.json content as a string
|
|
21
|
-
*/
|
|
22
|
-
export function generatePackageJson(projectName: string): string {
|
|
23
|
-
const packageJson = {
|
|
24
|
-
name: projectName,
|
|
25
|
-
version: '0.1.0',
|
|
26
|
-
type: 'module',
|
|
27
|
-
scripts: {
|
|
28
|
-
dev: 'bun --watch src/index.ts',
|
|
29
|
-
start: 'bun src/index.ts',
|
|
30
|
-
build: 'bun build src/index.ts --outdir ./dist',
|
|
31
|
-
},
|
|
32
|
-
dependencies: {
|
|
33
|
-
'burger-api': '^0.6.2',
|
|
34
|
-
},
|
|
35
|
-
devDependencies: {
|
|
36
|
-
'@types/bun': 'latest',
|
|
37
|
-
typescript: '^5',
|
|
38
|
-
},
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
return JSON.stringify(packageJson, null, 2);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Generate tsconfig.json content for a new project
|
|
46
|
-
* This sets up TypeScript properly for Bun
|
|
47
|
-
*
|
|
48
|
-
* @returns tsconfig.json content as a string
|
|
49
|
-
*/
|
|
50
|
-
export function generateTsConfig(): string {
|
|
51
|
-
const tsconfig = {
|
|
52
|
-
compilerOptions: {
|
|
53
|
-
lib: ['ESNext'],
|
|
54
|
-
target: 'ESNext',
|
|
55
|
-
module: 'ESNext',
|
|
56
|
-
moduleDetection: 'force',
|
|
57
|
-
jsx: 'react-jsx',
|
|
58
|
-
allowJs: true,
|
|
59
|
-
|
|
60
|
-
// Best practices for type safety
|
|
61
|
-
strict: true,
|
|
62
|
-
noUncheckedIndexedAccess: true,
|
|
63
|
-
noImplicitOverride: true,
|
|
64
|
-
|
|
65
|
-
// Module resolution for Bun
|
|
66
|
-
moduleResolution: 'bundler',
|
|
67
|
-
allowImportingTsExtensions: true,
|
|
68
|
-
verbatimModuleSyntax: true,
|
|
69
|
-
noEmit: true,
|
|
70
|
-
|
|
71
|
-
// Interop
|
|
72
|
-
allowSyntheticDefaultImports: true,
|
|
73
|
-
esModuleInterop: true,
|
|
74
|
-
forceConsistentCasingInFileNames: true,
|
|
75
|
-
|
|
76
|
-
// Skip type checking for dependencies
|
|
77
|
-
skipLibCheck: true,
|
|
78
|
-
|
|
79
|
-
// Types
|
|
80
|
-
types: ['bun-types'],
|
|
81
|
-
},
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
return JSON.stringify(tsconfig, null, 2);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Generate .gitignore content
|
|
89
|
-
*
|
|
90
|
-
* @returns .gitignore content as a string
|
|
91
|
-
*/
|
|
92
|
-
export function generateGitIgnore(): string {
|
|
93
|
-
return `# Bun
|
|
94
|
-
node_modules/
|
|
95
|
-
bun.lockb
|
|
96
|
-
.env*
|
|
97
|
-
|
|
98
|
-
# Build output
|
|
99
|
-
dist/
|
|
100
|
-
.build/
|
|
101
|
-
*.exe
|
|
102
|
-
|
|
103
|
-
# OS files
|
|
104
|
-
.DS_Store
|
|
105
|
-
Thumbs.db
|
|
106
|
-
|
|
107
|
-
# Editor
|
|
108
|
-
.vscode/
|
|
109
|
-
.idea/
|
|
110
|
-
*.swp
|
|
111
|
-
*.swo
|
|
112
|
-
`;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* Generate .prettierrc content
|
|
117
|
-
* This matches the burger-api project style
|
|
118
|
-
*
|
|
119
|
-
* @returns .prettierrc content as a string
|
|
120
|
-
*/
|
|
121
|
-
export function generatePrettierConfig(): string {
|
|
122
|
-
const prettierConfig = {
|
|
123
|
-
semi: true,
|
|
124
|
-
singleQuote: true,
|
|
125
|
-
tabWidth: 4,
|
|
126
|
-
trailingComma: 'es5',
|
|
127
|
-
printWidth: 80,
|
|
128
|
-
arrowParens: 'always',
|
|
129
|
-
};
|
|
130
|
-
|
|
131
|
-
return JSON.stringify(prettierConfig, null, 2);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
* Generate index.ts content based on user options
|
|
136
|
-
* This is the main entry point for the user's project
|
|
137
|
-
*
|
|
138
|
-
* @param options - Project configuration from user prompts
|
|
139
|
-
* @returns index.ts content as a string
|
|
140
|
-
*/
|
|
141
|
-
export function generateIndexFile(options: CreateOptions): string {
|
|
142
|
-
const lines: string[] = [];
|
|
143
|
-
|
|
144
|
-
// Import statement
|
|
145
|
-
lines.push("import { Burger } from 'burger-api';");
|
|
146
|
-
lines.push('');
|
|
147
|
-
|
|
148
|
-
// Configuration object
|
|
149
|
-
lines.push('const app = new Burger({');
|
|
150
|
-
|
|
151
|
-
if (options.useApi) {
|
|
152
|
-
lines.push(` apiDir: './src/${options.apiDir || 'api'}',`);
|
|
153
|
-
if (options.apiPrefix && options.apiPrefix !== '/api') {
|
|
154
|
-
lines.push(` apiPrefix: '${options.apiPrefix}',`);
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
if (options.usePages) {
|
|
159
|
-
lines.push(` pageDir: './src/${options.pageDir || 'pages'}',`);
|
|
160
|
-
if (options.pagePrefix && options.pagePrefix !== '/') {
|
|
161
|
-
lines.push(` pagePrefix: '${options.pagePrefix}',`);
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
if (options.debug) {
|
|
166
|
-
lines.push(' debug: true,');
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
lines.push(' globalMiddleware: [],');
|
|
170
|
-
lines.push('});');
|
|
171
|
-
lines.push('');
|
|
172
|
-
|
|
173
|
-
// Start server - uses PORT env variable for flexibility (e.g., burger-api serve --port 4000)
|
|
174
|
-
lines.push('const port = Number(process.env.PORT) || 4000;');
|
|
175
|
-
lines.push('app.serve(port, () => {');
|
|
176
|
-
lines.push(
|
|
177
|
-
' console.log(`Server running on http://localhost:${port}`);'
|
|
178
|
-
);
|
|
179
|
-
lines.push('});');
|
|
180
|
-
|
|
181
|
-
return lines.join('\n');
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
/**
|
|
185
|
-
* Generate a comprehensive API route file with full examples
|
|
186
|
-
* Includes: OpenAPI metadata, Zod schemas, all HTTP methods, middleware
|
|
187
|
-
* Every line has beginner-friendly comments explaining what it does
|
|
188
|
-
*
|
|
189
|
-
* @returns route.ts content as a string
|
|
190
|
-
*/
|
|
191
|
-
export function generateApiRoute(): string {
|
|
192
|
-
return `/**
|
|
193
|
-
* =============================================================================
|
|
194
|
-
* BURGER API - EXAMPLE ROUTE FILE
|
|
195
|
-
* =============================================================================
|
|
196
|
-
*
|
|
197
|
-
* This file shows you everything you can do with BurgerAPI routes!
|
|
198
|
-
*
|
|
199
|
-
* KEY CONCEPTS:
|
|
200
|
-
* - This file is automatically loaded because it's named "route.ts"
|
|
201
|
-
* - The folder path becomes the URL path (e.g., /api/route.ts → /api)
|
|
202
|
-
* - Export functions named after HTTP methods: GET, POST, PUT, DELETE, etc.
|
|
203
|
-
*
|
|
204
|
-
* =============================================================================
|
|
205
|
-
*/
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
import { z } from 'zod';
|
|
209
|
-
import type { BurgerRequest, Middleware, BurgerNext } from 'burger-api';
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
/*
|
|
213
|
-
-----------------------------------------------------------------------------
|
|
214
|
-
OPENAPI METADATA (Optional but recommended!)
|
|
215
|
-
-----------------------------------------------------------------------------
|
|
216
|
-
|
|
217
|
-
- This creates automatic documentation for your API!
|
|
218
|
-
- Visit /docs in your browser to see beautiful Swagger UI documentation.
|
|
219
|
-
- Each HTTP method (get, post, put, delete) can have its own documentation.
|
|
220
|
-
-----------------------------------------------------------------------------
|
|
221
|
-
*/
|
|
222
|
-
export const openapi = {
|
|
223
|
-
// Documentation for the GET method
|
|
224
|
-
get: {
|
|
225
|
-
// 'summary' - A short title shown in the docs (keep it brief!)
|
|
226
|
-
summary: 'Get all items',
|
|
227
|
-
|
|
228
|
-
// 'description' - A longer explanation of what this endpoint does
|
|
229
|
-
description: 'Fetches a list of items. You can filter results using query parameters.',
|
|
230
|
-
|
|
231
|
-
// 'tags' - Groups related endpoints together in the docs
|
|
232
|
-
// All endpoints with the same tag appear in the same section
|
|
233
|
-
tags: ['Items'],
|
|
234
|
-
|
|
235
|
-
// 'operationId' - A unique ID for this endpoint (useful for code generation)
|
|
236
|
-
operationId: 'getItems',
|
|
237
|
-
|
|
238
|
-
// 'responses' - Documents what responses the endpoint can return
|
|
239
|
-
responses: {
|
|
240
|
-
'200': { description: 'Successfully retrieved items' },
|
|
241
|
-
'400': { description: 'Invalid query parameters' },
|
|
242
|
-
},
|
|
243
|
-
},
|
|
244
|
-
|
|
245
|
-
// Documentation for the POST method
|
|
246
|
-
post: {
|
|
247
|
-
summary: 'Create a new item',
|
|
248
|
-
description: 'Creates a new item with the provided data. Returns the created item.',
|
|
249
|
-
tags: ['Items'],
|
|
250
|
-
operationId: 'createItem',
|
|
251
|
-
responses: {
|
|
252
|
-
'201': { description: 'Item created successfully' },
|
|
253
|
-
'400': { description: 'Invalid request body' },
|
|
254
|
-
},
|
|
255
|
-
},
|
|
256
|
-
|
|
257
|
-
// Documentation for the PUT method
|
|
258
|
-
put: {
|
|
259
|
-
summary: 'Update an item',
|
|
260
|
-
description: 'Updates an existing item. Provide the item ID in the query string.',
|
|
261
|
-
tags: ['Items'],
|
|
262
|
-
operationId: 'updateItem',
|
|
263
|
-
responses: {
|
|
264
|
-
'200': { description: 'Item updated successfully' },
|
|
265
|
-
'400': { description: 'Invalid request data' },
|
|
266
|
-
'404': { description: 'Item not found' },
|
|
267
|
-
},
|
|
268
|
-
},
|
|
269
|
-
|
|
270
|
-
// Documentation for the DELETE method
|
|
271
|
-
delete: {
|
|
272
|
-
summary: 'Delete an item',
|
|
273
|
-
description: 'Permanently deletes an item by ID.',
|
|
274
|
-
tags: ['Items'],
|
|
275
|
-
operationId: 'deleteItem',
|
|
276
|
-
responses: {
|
|
277
|
-
'200': { description: 'Item deleted successfully' },
|
|
278
|
-
'404': { description: 'Item not found' },
|
|
279
|
-
},
|
|
280
|
-
},
|
|
281
|
-
};
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
/*
|
|
285
|
-
-----------------------------------------------------------------------------
|
|
286
|
-
SCHEMA VALIDATION (Using Zod)
|
|
287
|
-
-----------------------------------------------------------------------------
|
|
288
|
-
|
|
289
|
-
- Schemas define what data your API accepts. BurgerAPI automatically:
|
|
290
|
-
- Validates incoming data against these schemas
|
|
291
|
-
- Returns a 400 error if validation fails
|
|
292
|
-
- Puts the validated data in req.validated for you to use
|
|
293
|
-
|
|
294
|
-
- You can validate:
|
|
295
|
-
- 'query' → URL query parameters like ?search=hello&page=1
|
|
296
|
-
- 'body' → Request body (for POST/PUT requests)
|
|
297
|
-
- 'params' → URL parameters like /items/[id] → { id: "123" }
|
|
298
|
-
-----------------------------------------------------------------------------
|
|
299
|
-
*/
|
|
300
|
-
export const schema = {
|
|
301
|
-
// Schema for GET requests - validates query parameters
|
|
302
|
-
get: {
|
|
303
|
-
// 'query' - Validates the URL query string
|
|
304
|
-
// Example URL: /api?search=burger&limit=10&page=2
|
|
305
|
-
query: z.object({
|
|
306
|
-
// 'search' - Optional text to search for
|
|
307
|
-
// .optional() means this field isn't required
|
|
308
|
-
search: z.string().optional(),
|
|
309
|
-
|
|
310
|
-
// 'limit' - How many items to return (default: 10)
|
|
311
|
-
// .coerce.number() converts string "10" to number 10
|
|
312
|
-
// .min(1) means it must be at least 1
|
|
313
|
-
// .max(100) means it can't be more than 100
|
|
314
|
-
// .default(10) uses 10 if not provided
|
|
315
|
-
limit: z.coerce.number().min(1).max(100).default(10),
|
|
316
|
-
|
|
317
|
-
// 'page' - Which page of results to return
|
|
318
|
-
page: z.coerce.number().min(1).default(1),
|
|
319
|
-
}),
|
|
320
|
-
},
|
|
321
|
-
|
|
322
|
-
// Schema for POST requests - validates the request body
|
|
323
|
-
post: {
|
|
324
|
-
// 'body' - Validates JSON data sent in the request body
|
|
325
|
-
body: z.object({
|
|
326
|
-
// 'name' - Required, must be at least 1 character
|
|
327
|
-
// .min(1, '...') shows a custom error message if too short
|
|
328
|
-
name: z.string().min(1, 'Name is required'),
|
|
329
|
-
|
|
330
|
-
// 'description' - Optional text field
|
|
331
|
-
description: z.string().optional(),
|
|
332
|
-
|
|
333
|
-
// 'price' - Required, must be a positive number
|
|
334
|
-
// .positive() ensures the number is greater than 0
|
|
335
|
-
price: z.number().positive('Price must be greater than 0'),
|
|
336
|
-
|
|
337
|
-
// 'category' - Must be one of these specific values
|
|
338
|
-
// .enum() only allows the listed values
|
|
339
|
-
category: z.enum(['food', 'drink', 'dessert']),
|
|
340
|
-
|
|
341
|
-
// 'isAvailable' - Optional boolean, defaults to true
|
|
342
|
-
isAvailable: z.boolean().default(true),
|
|
343
|
-
}),
|
|
344
|
-
},
|
|
345
|
-
|
|
346
|
-
// Schema for PUT requests - validates both query and body
|
|
347
|
-
put: {
|
|
348
|
-
// Which item to update (ID in query string)
|
|
349
|
-
query: z.object({
|
|
350
|
-
id: z.string().min(1, 'Item ID is required'),
|
|
351
|
-
}),
|
|
352
|
-
|
|
353
|
-
// What to update (in the request body)
|
|
354
|
-
// .partial() makes all fields optional (for partial updates)
|
|
355
|
-
body: z.object({
|
|
356
|
-
name: z.string().min(1),
|
|
357
|
-
description: z.string(),
|
|
358
|
-
price: z.number().positive(),
|
|
359
|
-
category: z.enum(['food', 'drink', 'dessert']),
|
|
360
|
-
isAvailable: z.boolean(),
|
|
361
|
-
}).partial(), // .partial() = all fields become optional
|
|
362
|
-
},
|
|
363
|
-
|
|
364
|
-
// Schema for DELETE requests - validates query parameters
|
|
365
|
-
delete: {
|
|
366
|
-
query: z.object({
|
|
367
|
-
id: z.string().min(1, 'Item ID is required'),
|
|
368
|
-
}),
|
|
369
|
-
},
|
|
370
|
-
};
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
/*
|
|
374
|
-
-----------------------------------------------------------------------------
|
|
375
|
-
ROUTE-SPECIFIC MIDDLEWARE (Optional)
|
|
376
|
-
-----------------------------------------------------------------------------
|
|
377
|
-
|
|
378
|
-
- Middleware runs BEFORE your route handler. Use it for:
|
|
379
|
-
- Logging requests
|
|
380
|
-
- Checking authentication
|
|
381
|
-
- Modifying the request
|
|
382
|
-
- Blocking unauthorized access
|
|
383
|
-
|
|
384
|
-
- Return 'undefined' to continue to the next middleware/handler
|
|
385
|
-
- Return a 'Response' to stop and send that response immediately
|
|
386
|
-
-----------------------------------------------------------------------------
|
|
387
|
-
*/
|
|
388
|
-
export const middleware: Middleware[] = [
|
|
389
|
-
// Example: Log every request to this route
|
|
390
|
-
async (req: BurgerRequest): Promise<BurgerNext> => {
|
|
391
|
-
console.log(\`[\${new Date().toISOString()}] \${req.method} \${req.url}\`);
|
|
392
|
-
|
|
393
|
-
// Return undefined to continue to the next middleware/handler
|
|
394
|
-
// If you return a Response here, it stops and sends that response
|
|
395
|
-
return undefined;
|
|
396
|
-
},
|
|
397
|
-
];
|
|
398
|
-
|
|
399
|
-
/*
|
|
400
|
-
-----------------------------------------------------------------------------
|
|
401
|
-
HTTP HANDLERS
|
|
402
|
-
-----------------------------------------------------------------------------
|
|
403
|
-
|
|
404
|
-
- These functions handle the actual requests. They receive:
|
|
405
|
-
- req: The request object with validated data in req.validated
|
|
406
|
-
- They must return a Response object. Use Response.json() for JSON responses.
|
|
407
|
-
-----------------------------------------------------------------------------
|
|
408
|
-
*/
|
|
409
|
-
|
|
410
|
-
/**
|
|
411
|
-
* GET - Fetch items with optional filtering
|
|
412
|
-
*
|
|
413
|
-
* Example requests:
|
|
414
|
-
* - GET /api → Get first 10 items
|
|
415
|
-
* - GET /api?limit=5 → Get first 5 items
|
|
416
|
-
* - GET /api?search=burger&page=2 → Search for "burger", page 2
|
|
417
|
-
*/
|
|
418
|
-
export async function GET(req: BurgerRequest<{ query: z.infer<typeof schema.get.query> }>) {
|
|
419
|
-
// Access validated query parameters from the schema
|
|
420
|
-
const { search, limit, page } = req.validated.query;
|
|
421
|
-
|
|
422
|
-
// Mock data (replace with your database query)
|
|
423
|
-
const mockItems = [
|
|
424
|
-
{ id: '1', name: 'Classic Burger', price: 9.99, category: 'food' },
|
|
425
|
-
{ id: '2', name: 'Cheese Burger', price: 11.99, category: 'food' },
|
|
426
|
-
{ id: '3', name: 'Cola', price: 2.99, category: 'drink' },
|
|
427
|
-
];
|
|
428
|
-
|
|
429
|
-
// Filter items if search is provided
|
|
430
|
-
let items = mockItems;
|
|
431
|
-
if (search) {
|
|
432
|
-
items = items.filter(item =>
|
|
433
|
-
item.name.toLowerCase().includes(search.toLowerCase())
|
|
434
|
-
);
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
// Calculate pagination
|
|
438
|
-
const startIndex = (page - 1) * limit;
|
|
439
|
-
const paginatedItems = items.slice(startIndex, startIndex + limit);
|
|
440
|
-
|
|
441
|
-
// Return JSON response with status 200 (default)
|
|
442
|
-
return Response.json({
|
|
443
|
-
success: true,
|
|
444
|
-
data: paginatedItems,
|
|
445
|
-
pagination: {
|
|
446
|
-
page,
|
|
447
|
-
limit,
|
|
448
|
-
total: items.length,
|
|
449
|
-
totalPages: Math.ceil(items.length / limit),
|
|
450
|
-
},
|
|
451
|
-
});
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
/**
|
|
455
|
-
* POST - Create a new item
|
|
456
|
-
*
|
|
457
|
-
* Example request body:
|
|
458
|
-
* {
|
|
459
|
-
* "name": "Veggie Burger",
|
|
460
|
-
* "description": "Delicious plant-based burger",
|
|
461
|
-
* "price": 12.99,
|
|
462
|
-
* "category": "food"
|
|
463
|
-
* }
|
|
464
|
-
*/
|
|
465
|
-
export async function POST(req: BurgerRequest<{ body: z.infer<typeof schema.post.body> }>) {
|
|
466
|
-
// Get validated body data - already checked by Zod schema!
|
|
467
|
-
const { name, description, price, category, isAvailable } = req.validated.body;
|
|
468
|
-
|
|
469
|
-
// Create the item (replace with your database insert)
|
|
470
|
-
const newItem = {
|
|
471
|
-
id: crypto.randomUUID(), // Generate unique ID
|
|
472
|
-
name,
|
|
473
|
-
description: description || null,
|
|
474
|
-
price,
|
|
475
|
-
category,
|
|
476
|
-
isAvailable,
|
|
477
|
-
createdAt: new Date().toISOString(),
|
|
478
|
-
};
|
|
479
|
-
|
|
480
|
-
// Return the created item with status 201 (Created)
|
|
481
|
-
return Response.json({
|
|
482
|
-
success: true,
|
|
483
|
-
message: 'Item created successfully',
|
|
484
|
-
data: newItem,
|
|
485
|
-
}, { status: 201 });
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
/**
|
|
489
|
-
* PUT - Update an existing item
|
|
490
|
-
*
|
|
491
|
-
* Example: PUT /api?id=123
|
|
492
|
-
* Body: { "name": "Updated Name", "price": 15.99 }
|
|
493
|
-
*/
|
|
494
|
-
export async function PUT(req: BurgerRequest<{ query: z.infer<typeof schema.put.query>, body: z.infer<typeof schema.put.body> }>) {
|
|
495
|
-
// Get the item ID from query parameters
|
|
496
|
-
const { id } = req.validated.query;
|
|
497
|
-
|
|
498
|
-
// Get the fields to update from the request body
|
|
499
|
-
const updates = req.validated.body;
|
|
500
|
-
|
|
501
|
-
// Find and update the item (replace with your database update)
|
|
502
|
-
// Here we're just simulating an update
|
|
503
|
-
const updatedItem = {
|
|
504
|
-
id,
|
|
505
|
-
...updates,
|
|
506
|
-
updatedAt: new Date().toISOString(),
|
|
507
|
-
};
|
|
508
|
-
|
|
509
|
-
return Response.json({
|
|
510
|
-
success: true,
|
|
511
|
-
message: 'Item updated successfully',
|
|
512
|
-
data: updatedItem,
|
|
513
|
-
});
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
/**
|
|
517
|
-
* DELETE - Remove an item
|
|
518
|
-
*
|
|
519
|
-
* Example: DELETE /api?id=123
|
|
520
|
-
*/
|
|
521
|
-
export async function DELETE(req: BurgerRequest<{ query: z.infer<typeof schema.delete.query> }>) {
|
|
522
|
-
// Get the item ID from query parameters
|
|
523
|
-
const { id } = req.validated.query;
|
|
524
|
-
|
|
525
|
-
// Delete the item (replace with your database delete)
|
|
526
|
-
// Here we're just returning a success message
|
|
527
|
-
return Response.json({
|
|
528
|
-
success: true,
|
|
529
|
-
message: \`Item \${id} deleted successfully\`,
|
|
530
|
-
});
|
|
531
|
-
}
|
|
532
|
-
`;
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
/**
|
|
536
|
-
* Generate a CSS file with modern styling for the landing page
|
|
537
|
-
*
|
|
538
|
-
* @returns style.css content as a string
|
|
539
|
-
*/
|
|
540
|
-
export function generateSampleCss(): string {
|
|
541
|
-
return `
|
|
542
|
-
:root {
|
|
543
|
-
--color-primary: hsl(30, 75%, 90%);
|
|
544
|
-
--color-primary-dark: hsl(30, 75%, 80%);
|
|
545
|
-
--color-bg: #09090b;
|
|
546
|
-
--color-surface: hsl(240, 10%, 3.9%);
|
|
547
|
-
--color-border: hsl(240, 3.7%, 15.9%);
|
|
548
|
-
--color-success: hsl(120, 50%, 40%);
|
|
549
|
-
--color-text-muted: hsl(240, 5%, 50%);
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
* {
|
|
553
|
-
margin: 0;
|
|
554
|
-
padding: 0;
|
|
555
|
-
box-sizing: border-box;
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
body {
|
|
559
|
-
font-family: 'Poppins', system-ui, sans-serif;
|
|
560
|
-
min-height: 100vh;
|
|
561
|
-
background: var(--color-bg);
|
|
562
|
-
color: #fff;
|
|
563
|
-
display: flex;
|
|
564
|
-
flex-direction: column;
|
|
565
|
-
align-items: center;
|
|
566
|
-
padding: 60px 20px 40px;
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
.hero {
|
|
570
|
-
text-align: center;
|
|
571
|
-
max-width: 600px;
|
|
572
|
-
margin-bottom: 48px;
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
.logo-wrapper {
|
|
576
|
-
display: flex;
|
|
577
|
-
flex-wrap: wrap;
|
|
578
|
-
margin-bottom: 32px;
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
.logo {
|
|
582
|
-
width: 80px;
|
|
583
|
-
height: 80px;
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
.logo-text {
|
|
587
|
-
font-size: 3.5rem;
|
|
588
|
-
font-weight: 600;
|
|
589
|
-
color: var(--color-primary);
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
h1 {
|
|
593
|
-
font-size: 2.5rem;
|
|
594
|
-
font-weight: 600;
|
|
595
|
-
margin-bottom: 12px;
|
|
596
|
-
color: #fff;
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
.subtitle {
|
|
600
|
-
color: var(--color-text-muted);
|
|
601
|
-
font-size: 1.1rem;
|
|
602
|
-
margin-bottom: 24px;
|
|
603
|
-
}
|
|
604
|
-
|
|
605
|
-
.status {
|
|
606
|
-
display: inline-flex;
|
|
607
|
-
align-items: center;
|
|
608
|
-
gap: 8px;
|
|
609
|
-
background: hsla(120, 50%, 40%, 0.1);
|
|
610
|
-
border: 1px solid hsla(120, 50%, 40%, 0.3);
|
|
611
|
-
padding: 8px 16px;
|
|
612
|
-
border-radius: 20px;
|
|
613
|
-
font-size: 0.875rem;
|
|
614
|
-
color: var(--color-success);
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
.status::before {
|
|
618
|
-
content: '';
|
|
619
|
-
width: 8px;
|
|
620
|
-
height: 8px;
|
|
621
|
-
background: var(--color-success);
|
|
622
|
-
border-radius: 50%;
|
|
623
|
-
animation: pulse 2s infinite;
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
@keyframes pulse {
|
|
627
|
-
0%, 100% { opacity: 1; }
|
|
628
|
-
50% { opacity: 0.5; }
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
/* Edit hint section */
|
|
632
|
-
.edit-hint {
|
|
633
|
-
background: var(--color-surface);
|
|
634
|
-
border: 1px solid var(--color-border);
|
|
635
|
-
border-radius: 12px;
|
|
636
|
-
padding: 24px 32px;
|
|
637
|
-
margin-bottom: 48px;
|
|
638
|
-
max-width: 500px;
|
|
639
|
-
text-align: center;
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
.edit-hint p {
|
|
643
|
-
color: var(--color-text-muted);
|
|
644
|
-
font-size: 0.95rem;
|
|
645
|
-
margin-bottom: 8px;
|
|
646
|
-
}
|
|
647
|
-
|
|
648
|
-
.edit-hint code {
|
|
649
|
-
color: var(--color-primary);
|
|
650
|
-
font-family: 'JetBrains Mono', monospace;
|
|
651
|
-
font-size: 0.9rem;
|
|
652
|
-
}
|
|
653
|
-
|
|
654
|
-
.edit-hint .hint {
|
|
655
|
-
font-size: 0.8rem;
|
|
656
|
-
color: hsl(240, 5%, 40%);
|
|
657
|
-
margin-top: 12px;
|
|
658
|
-
}
|
|
659
|
-
|
|
660
|
-
/* Quick start section */
|
|
661
|
-
.quick-start {
|
|
662
|
-
max-width: 500px;
|
|
663
|
-
width: 100%;
|
|
664
|
-
margin-bottom: 48px;
|
|
665
|
-
}
|
|
666
|
-
|
|
667
|
-
.quick-start h2 {
|
|
668
|
-
font-size: 1rem;
|
|
669
|
-
font-weight: 500;
|
|
670
|
-
color: var(--color-text-muted);
|
|
671
|
-
margin-bottom: 16px;
|
|
672
|
-
text-align: center;
|
|
673
|
-
}
|
|
674
|
-
|
|
675
|
-
.commands {
|
|
676
|
-
display: flex;
|
|
677
|
-
flex-direction: column;
|
|
678
|
-
gap: 8px;
|
|
679
|
-
}
|
|
680
|
-
|
|
681
|
-
.command {
|
|
682
|
-
display: flex;
|
|
683
|
-
align-items: center;
|
|
684
|
-
background: var(--color-surface);
|
|
685
|
-
border: 1px solid var(--color-border);
|
|
686
|
-
border-radius: 8px;
|
|
687
|
-
padding: 12px 16px;
|
|
688
|
-
font-family: 'JetBrains Mono', monospace;
|
|
689
|
-
font-size: 0.85rem;
|
|
690
|
-
transition: border-color 0.2s;
|
|
691
|
-
}
|
|
692
|
-
|
|
693
|
-
.command:hover {
|
|
694
|
-
border-color: var(--color-primary-dark);
|
|
695
|
-
}
|
|
696
|
-
|
|
697
|
-
.command .prefix {
|
|
698
|
-
color: var(--color-success);
|
|
699
|
-
margin-right: 8px;
|
|
700
|
-
}
|
|
701
|
-
|
|
702
|
-
.command .cmd {
|
|
703
|
-
color: var(--color-primary);
|
|
704
|
-
}
|
|
705
|
-
|
|
706
|
-
.command .comment {
|
|
707
|
-
color: var(--color-text-muted);
|
|
708
|
-
margin-left: auto;
|
|
709
|
-
font-size: 0.75rem;
|
|
710
|
-
}
|
|
711
|
-
|
|
712
|
-
/* Links section */
|
|
713
|
-
.links {
|
|
714
|
-
display: flex;
|
|
715
|
-
gap: 12px;
|
|
716
|
-
justify-content: center;
|
|
717
|
-
flex-wrap: wrap;
|
|
718
|
-
margin-bottom: 48px;
|
|
719
|
-
}
|
|
720
|
-
|
|
721
|
-
.link {
|
|
722
|
-
color: var(--color-text-muted);
|
|
723
|
-
text-decoration: none;
|
|
724
|
-
font-size: 0.9rem;
|
|
725
|
-
padding: 10px 20px;
|
|
726
|
-
border: 1px solid var(--color-border);
|
|
727
|
-
border-radius: 8px;
|
|
728
|
-
transition: all 0.2s;
|
|
729
|
-
}
|
|
730
|
-
|
|
731
|
-
.link:hover {
|
|
732
|
-
color: var(--color-primary);
|
|
733
|
-
border-color: var(--color-primary-dark);
|
|
734
|
-
background: var(--color-surface);
|
|
735
|
-
}
|
|
736
|
-
|
|
737
|
-
.link.primary {
|
|
738
|
-
background: var(--color-primary);
|
|
739
|
-
border-color: var(--color-primary);
|
|
740
|
-
color: #000;
|
|
741
|
-
}
|
|
742
|
-
|
|
743
|
-
.link.primary:hover {
|
|
744
|
-
background: var(--color-primary-dark);
|
|
745
|
-
border-color: var(--color-primary-dark);
|
|
746
|
-
}
|
|
747
|
-
|
|
748
|
-
/* Documentation links */
|
|
749
|
-
.docs-links {
|
|
750
|
-
display: flex;
|
|
751
|
-
gap: 32px;
|
|
752
|
-
justify-content: center;
|
|
753
|
-
flex-wrap: wrap;
|
|
754
|
-
margin-bottom: 48px;
|
|
755
|
-
padding-top: 32px;
|
|
756
|
-
border-top: 1px solid var(--color-border);
|
|
757
|
-
max-width: 600px;
|
|
758
|
-
width: 100%;
|
|
759
|
-
}
|
|
760
|
-
|
|
761
|
-
.docs-section h3 {
|
|
762
|
-
font-size: 0.8rem;
|
|
763
|
-
font-weight: 500;
|
|
764
|
-
color: var(--color-text-muted);
|
|
765
|
-
margin-bottom: 12px;
|
|
766
|
-
text-transform: uppercase;
|
|
767
|
-
letter-spacing: 0.5px;
|
|
768
|
-
}
|
|
769
|
-
|
|
770
|
-
.docs-section a {
|
|
771
|
-
display: block;
|
|
772
|
-
color: hsl(240, 5%, 60%);
|
|
773
|
-
text-decoration: none;
|
|
774
|
-
font-size: 0.85rem;
|
|
775
|
-
padding: 4px 0;
|
|
776
|
-
transition: color 0.2s;
|
|
777
|
-
}
|
|
778
|
-
|
|
779
|
-
.docs-section a:hover {
|
|
780
|
-
color: var(--color-primary);
|
|
781
|
-
}
|
|
782
|
-
|
|
783
|
-
/* Footer */
|
|
784
|
-
.footer {
|
|
785
|
-
margin-top: auto;
|
|
786
|
-
text-align: center;
|
|
787
|
-
padding-top: 32px;
|
|
788
|
-
}
|
|
789
|
-
|
|
790
|
-
.version {
|
|
791
|
-
font-size: 0.75rem;
|
|
792
|
-
color: hsl(240, 5%, 35%);
|
|
793
|
-
margin-bottom: 16px;
|
|
794
|
-
}
|
|
795
|
-
|
|
796
|
-
.social-links {
|
|
797
|
-
display: flex;
|
|
798
|
-
gap: 16px;
|
|
799
|
-
justify-content: center;
|
|
800
|
-
margin-bottom: 16px;
|
|
801
|
-
}
|
|
802
|
-
|
|
803
|
-
.social-links a {
|
|
804
|
-
color: var(--color-text-muted);
|
|
805
|
-
text-decoration: none;
|
|
806
|
-
font-size: 0.85rem;
|
|
807
|
-
transition: color 0.2s;
|
|
808
|
-
}
|
|
809
|
-
|
|
810
|
-
.social-links a:hover {
|
|
811
|
-
color: var(--color-primary);
|
|
812
|
-
}
|
|
813
|
-
|
|
814
|
-
.powered-by {
|
|
815
|
-
color: hsl(240, 5%, 35%);
|
|
816
|
-
font-size: 0.8rem;
|
|
817
|
-
}
|
|
818
|
-
|
|
819
|
-
.powered-by a {
|
|
820
|
-
color: var(--color-primary-dark);
|
|
821
|
-
text-decoration: none;
|
|
822
|
-
}
|
|
823
|
-
|
|
824
|
-
.powered-by a:hover {
|
|
825
|
-
color: var(--color-primary);
|
|
826
|
-
}
|
|
827
|
-
|
|
828
|
-
@media (max-width: 600px) {
|
|
829
|
-
h1 { font-size: 2rem; }
|
|
830
|
-
.docs-links { flex-direction: column; gap: 24px; text-align: center; }
|
|
831
|
-
.command .comment { display: none; }
|
|
832
|
-
}
|
|
833
|
-
`;
|
|
834
|
-
}
|
|
835
|
-
|
|
836
|
-
/**
|
|
837
|
-
* Generate a sample JavaScript file with useful utilities
|
|
838
|
-
*
|
|
839
|
-
* @returns app.js content as a string
|
|
840
|
-
*/
|
|
841
|
-
export function generateSampleJs(): string {
|
|
842
|
-
return 'console.log("Hello from app.js");';
|
|
843
|
-
}
|
|
844
|
-
|
|
845
|
-
/**
|
|
846
|
-
* Generate a minimal, clean landing page
|
|
847
|
-
* Uses official BurgerAPI color scheme
|
|
848
|
-
*
|
|
849
|
-
* @param projectName - Name of the project
|
|
850
|
-
* @returns index.html content as a string
|
|
851
|
-
*/
|
|
852
|
-
export function generateIndexPage(projectName: string): string {
|
|
853
|
-
return `<!DOCTYPE html>
|
|
854
|
-
<html lang="en">
|
|
855
|
-
<head>
|
|
856
|
-
<meta charset="UTF-8">
|
|
857
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
858
|
-
<title>${projectName}</title>
|
|
859
|
-
<link rel="icon" type="image/png" href="https://burger-api.com/img/logo.png">
|
|
860
|
-
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
861
|
-
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
862
|
-
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600&family=JetBrains+Mono&display=swap" rel="stylesheet">
|
|
863
|
-
<!-- Assets: Styles -->
|
|
864
|
-
<link rel="stylesheet" href="./assets/css/style.css" />
|
|
865
|
-
<!-- Assets: Scripts -->
|
|
866
|
-
<script src="./assets/js/app.js" type="module"></script>
|
|
867
|
-
</head>
|
|
868
|
-
<body>
|
|
869
|
-
<!-- Hero Section -->
|
|
870
|
-
<section class="hero">
|
|
871
|
-
<div class="logo-wrapper">
|
|
872
|
-
<img src="https://burger-api.com/img/logo.png" alt="BurgerAPI Logo" class="logo">
|
|
873
|
-
<span class="logo-text">BurgerAPI</span>
|
|
874
|
-
</div>
|
|
875
|
-
<p class="subtitle">Your Project ${projectName} is ready</p>
|
|
876
|
-
<div class="status">Server running</div>
|
|
877
|
-
</section>
|
|
878
|
-
|
|
879
|
-
<!-- Edit Hint -->
|
|
880
|
-
<div class="edit-hint">
|
|
881
|
-
<p>Edit <code>src/pages/index.html</code> and save to reload the page.</p>
|
|
882
|
-
<p>Edit <code>src/api/route.ts</code> and save to reload the API endpoint.</p>
|
|
883
|
-
<p class="hint">Your changes will automatically refresh the server.</p>
|
|
884
|
-
</div>
|
|
885
|
-
|
|
886
|
-
<!-- Quick Start Commands -->
|
|
887
|
-
<section class="quick-start">
|
|
888
|
-
<h2>Quick Start</h2>
|
|
889
|
-
<div class="commands">
|
|
890
|
-
<div class="command">
|
|
891
|
-
<span class="prefix">$</span>
|
|
892
|
-
<span class="cmd">burger-api add cors logger</span>
|
|
893
|
-
<span class="comment"># Add middleware</span>
|
|
894
|
-
</div>
|
|
895
|
-
<div class="command">
|
|
896
|
-
<span class="prefix">$</span>
|
|
897
|
-
<span class="cmd">burger-api build src/index.ts</span>
|
|
898
|
-
<span class="comment"># Build for production</span>
|
|
899
|
-
</div>
|
|
900
|
-
</div>
|
|
901
|
-
</section>
|
|
902
|
-
|
|
903
|
-
<!-- Action Links -->
|
|
904
|
-
<div class="links">
|
|
905
|
-
<a href="/docs" class="link primary">API Docs</a>
|
|
906
|
-
<a href="/api" class="link">Try API</a>
|
|
907
|
-
<a href="/openapi.json" class="link">OpenAPI</a>
|
|
908
|
-
</div>
|
|
909
|
-
|
|
910
|
-
<!-- Documentation Links -->
|
|
911
|
-
<div class="docs-links">
|
|
912
|
-
<div class="docs-section">
|
|
913
|
-
<h3>Documentation</h3>
|
|
914
|
-
<a href="https://burger-api.com/docs" target="_blank">Getting Started</a>
|
|
915
|
-
<a href="https://burger-api.com/docs/core/configuration" target="_blank">Configuration</a>
|
|
916
|
-
<a href="https://burger-api.com/docs/request-handling/middleware" target="_blank">Middleware</a>
|
|
917
|
-
</div>
|
|
918
|
-
<div class="docs-section">
|
|
919
|
-
<h3>Resources</h3>
|
|
920
|
-
<a href="https://github.com/isfhan/burger-api" target="_blank">GitHub</a>
|
|
921
|
-
<a href="https://github.com/isfhan/burger-api/issues" target="_blank">Report Issue</a>
|
|
922
|
-
<a href="https://www.npmjs.com/package/burger-api" target="_blank">NPM Package</a>
|
|
923
|
-
</div>
|
|
924
|
-
<div class="docs-section">
|
|
925
|
-
<h3>Community</h3>
|
|
926
|
-
<a href="https://github.com/isfhan/burger-api" target="_blank">Contribute</a>
|
|
927
|
-
<a href="https://github.com/isfhan/burger-api/discussions" target="_blank">Discussions</a>
|
|
928
|
-
<a href="https://github.com/isfhan/burger-api/stargazers" target="_blank">Star on GitHub</a>
|
|
929
|
-
</div>
|
|
930
|
-
</div>
|
|
931
|
-
|
|
932
|
-
<!-- Footer -->
|
|
933
|
-
<footer class="footer">
|
|
934
|
-
<div class="version">BurgerAPI v0.
|
|
935
|
-
<div class="social-links">
|
|
936
|
-
<a href="https://github.com/isfhan/burger-api" target="_blank">GitHub</a>
|
|
937
|
-
<a href="https://www.npmjs.com/package/burger-api" target="_blank">NPM</a>
|
|
938
|
-
<a href="https://burger-api.com" target="_blank">Website</a>
|
|
939
|
-
</div>
|
|
940
|
-
<p class="powered-by">
|
|
941
|
-
Built with ❤️ using <a href="https://burger-api.com">BurgerAPI</a>
|
|
942
|
-
</p>
|
|
943
|
-
</footer>
|
|
944
|
-
</body>
|
|
945
|
-
</html>
|
|
946
|
-
`;
|
|
947
|
-
}
|
|
948
|
-
|
|
949
|
-
/**
|
|
950
|
-
* Generate middleware index file
|
|
951
|
-
* This is where users will export their middleware
|
|
952
|
-
*
|
|
953
|
-
* @returns middleware/index.ts content as a string
|
|
954
|
-
*/
|
|
955
|
-
export function generateMiddlewareIndex(): string {
|
|
956
|
-
return `/**
|
|
957
|
-
* Global Middleware Configuration
|
|
958
|
-
*
|
|
959
|
-
* Import and export middleware here to use them in your app.
|
|
960
|
-
* Example:
|
|
961
|
-
*
|
|
962
|
-
* import { cors } from './cors/cors';
|
|
963
|
-
* import { logger } from './logger/logger';
|
|
964
|
-
*
|
|
965
|
-
* export const globalMiddleware = [
|
|
966
|
-
* logger(),
|
|
967
|
-
* cors(),
|
|
968
|
-
* ];
|
|
969
|
-
*/
|
|
970
|
-
|
|
971
|
-
export const globalMiddleware: any[] = [];
|
|
972
|
-
`;
|
|
973
|
-
}
|
|
974
|
-
|
|
975
|
-
/**
|
|
976
|
-
* Download the .llm-context folder from GitHub to the target project
|
|
977
|
-
* This includes context files for AI assistants working with Burger API
|
|
978
|
-
*
|
|
979
|
-
* @param targetDir - Where to create the project
|
|
980
|
-
*/
|
|
981
|
-
async function downloadLlmFolder(targetDir: string): Promise<void> {
|
|
982
|
-
try {
|
|
983
|
-
const llmDir = join(targetDir, 'ecosystem', '.llm-context');
|
|
984
|
-
|
|
985
|
-
// Download all three .llm files from GitHub
|
|
986
|
-
const files = [
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
*
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
await Bun.write(
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
await Bun.write(
|
|
1059
|
-
join(pagesDir, 'assets', '
|
|
1060
|
-
|
|
1061
|
-
);
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
'
|
|
1074
|
-
|
|
1075
|
-
);
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
spin.stop('Failed to install dependencies', true);
|
|
1118
|
-
throw err;
|
|
1119
|
-
}
|
|
1120
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Template Management System
|
|
3
|
+
*
|
|
4
|
+
* Handles downloading and caching project templates.
|
|
5
|
+
* Templates are the starter projects users get when running `burger-api create`
|
|
6
|
+
*
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { join } from 'path';
|
|
10
|
+
|
|
11
|
+
import type { CreateOptions } from '../types/index';
|
|
12
|
+
import { spinner } from './logger';
|
|
13
|
+
import { downloadFile } from './github';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Generate package.json content for a new project
|
|
17
|
+
* This includes the burger-api dependency and basic scripts
|
|
18
|
+
*
|
|
19
|
+
* @param projectName - Name of the project
|
|
20
|
+
* @returns package.json content as a string
|
|
21
|
+
*/
|
|
22
|
+
export function generatePackageJson(projectName: string): string {
|
|
23
|
+
const packageJson = {
|
|
24
|
+
name: projectName,
|
|
25
|
+
version: '0.1.0',
|
|
26
|
+
type: 'module',
|
|
27
|
+
scripts: {
|
|
28
|
+
dev: 'bun --watch src/index.ts',
|
|
29
|
+
start: 'bun src/index.ts',
|
|
30
|
+
build: 'bun build src/index.ts --outdir ./dist',
|
|
31
|
+
},
|
|
32
|
+
dependencies: {
|
|
33
|
+
'burger-api': '^0.6.2',
|
|
34
|
+
},
|
|
35
|
+
devDependencies: {
|
|
36
|
+
'@types/bun': 'latest',
|
|
37
|
+
typescript: '^5',
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
return JSON.stringify(packageJson, null, 2);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Generate tsconfig.json content for a new project
|
|
46
|
+
* This sets up TypeScript properly for Bun
|
|
47
|
+
*
|
|
48
|
+
* @returns tsconfig.json content as a string
|
|
49
|
+
*/
|
|
50
|
+
export function generateTsConfig(): string {
|
|
51
|
+
const tsconfig = {
|
|
52
|
+
compilerOptions: {
|
|
53
|
+
lib: ['ESNext'],
|
|
54
|
+
target: 'ESNext',
|
|
55
|
+
module: 'ESNext',
|
|
56
|
+
moduleDetection: 'force',
|
|
57
|
+
jsx: 'react-jsx',
|
|
58
|
+
allowJs: true,
|
|
59
|
+
|
|
60
|
+
// Best practices for type safety
|
|
61
|
+
strict: true,
|
|
62
|
+
noUncheckedIndexedAccess: true,
|
|
63
|
+
noImplicitOverride: true,
|
|
64
|
+
|
|
65
|
+
// Module resolution for Bun
|
|
66
|
+
moduleResolution: 'bundler',
|
|
67
|
+
allowImportingTsExtensions: true,
|
|
68
|
+
verbatimModuleSyntax: true,
|
|
69
|
+
noEmit: true,
|
|
70
|
+
|
|
71
|
+
// Interop
|
|
72
|
+
allowSyntheticDefaultImports: true,
|
|
73
|
+
esModuleInterop: true,
|
|
74
|
+
forceConsistentCasingInFileNames: true,
|
|
75
|
+
|
|
76
|
+
// Skip type checking for dependencies
|
|
77
|
+
skipLibCheck: true,
|
|
78
|
+
|
|
79
|
+
// Types
|
|
80
|
+
types: ['bun-types'],
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
return JSON.stringify(tsconfig, null, 2);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Generate .gitignore content
|
|
89
|
+
*
|
|
90
|
+
* @returns .gitignore content as a string
|
|
91
|
+
*/
|
|
92
|
+
export function generateGitIgnore(): string {
|
|
93
|
+
return `# Bun
|
|
94
|
+
node_modules/
|
|
95
|
+
bun.lockb
|
|
96
|
+
.env*
|
|
97
|
+
|
|
98
|
+
# Build output
|
|
99
|
+
dist/
|
|
100
|
+
.build/
|
|
101
|
+
*.exe
|
|
102
|
+
|
|
103
|
+
# OS files
|
|
104
|
+
.DS_Store
|
|
105
|
+
Thumbs.db
|
|
106
|
+
|
|
107
|
+
# Editor
|
|
108
|
+
.vscode/
|
|
109
|
+
.idea/
|
|
110
|
+
*.swp
|
|
111
|
+
*.swo
|
|
112
|
+
`;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Generate .prettierrc content
|
|
117
|
+
* This matches the burger-api project style
|
|
118
|
+
*
|
|
119
|
+
* @returns .prettierrc content as a string
|
|
120
|
+
*/
|
|
121
|
+
export function generatePrettierConfig(): string {
|
|
122
|
+
const prettierConfig = {
|
|
123
|
+
semi: true,
|
|
124
|
+
singleQuote: true,
|
|
125
|
+
tabWidth: 4,
|
|
126
|
+
trailingComma: 'es5',
|
|
127
|
+
printWidth: 80,
|
|
128
|
+
arrowParens: 'always',
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
return JSON.stringify(prettierConfig, null, 2);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Generate index.ts content based on user options
|
|
136
|
+
* This is the main entry point for the user's project
|
|
137
|
+
*
|
|
138
|
+
* @param options - Project configuration from user prompts
|
|
139
|
+
* @returns index.ts content as a string
|
|
140
|
+
*/
|
|
141
|
+
export function generateIndexFile(options: CreateOptions): string {
|
|
142
|
+
const lines: string[] = [];
|
|
143
|
+
|
|
144
|
+
// Import statement
|
|
145
|
+
lines.push("import { Burger } from 'burger-api';");
|
|
146
|
+
lines.push('');
|
|
147
|
+
|
|
148
|
+
// Configuration object
|
|
149
|
+
lines.push('const app = new Burger({');
|
|
150
|
+
|
|
151
|
+
if (options.useApi) {
|
|
152
|
+
lines.push(` apiDir: './src/${options.apiDir || 'api'}',`);
|
|
153
|
+
if (options.apiPrefix && options.apiPrefix !== '/api') {
|
|
154
|
+
lines.push(` apiPrefix: '${options.apiPrefix}',`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (options.usePages) {
|
|
159
|
+
lines.push(` pageDir: './src/${options.pageDir || 'pages'}',`);
|
|
160
|
+
if (options.pagePrefix && options.pagePrefix !== '/') {
|
|
161
|
+
lines.push(` pagePrefix: '${options.pagePrefix}',`);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (options.debug) {
|
|
166
|
+
lines.push(' debug: true,');
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
lines.push(' globalMiddleware: [],');
|
|
170
|
+
lines.push('});');
|
|
171
|
+
lines.push('');
|
|
172
|
+
|
|
173
|
+
// Start server - uses PORT env variable for flexibility (e.g., burger-api serve --port 4000)
|
|
174
|
+
lines.push('const port = Number(process.env.PORT) || 4000;');
|
|
175
|
+
lines.push('app.serve(port, () => {');
|
|
176
|
+
lines.push(
|
|
177
|
+
' console.log(`Server running on http://localhost:${port}`);'
|
|
178
|
+
);
|
|
179
|
+
lines.push('});');
|
|
180
|
+
|
|
181
|
+
return lines.join('\n');
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Generate a comprehensive API route file with full examples
|
|
186
|
+
* Includes: OpenAPI metadata, Zod schemas, all HTTP methods, middleware
|
|
187
|
+
* Every line has beginner-friendly comments explaining what it does
|
|
188
|
+
*
|
|
189
|
+
* @returns route.ts content as a string
|
|
190
|
+
*/
|
|
191
|
+
export function generateApiRoute(): string {
|
|
192
|
+
return `/**
|
|
193
|
+
* =============================================================================
|
|
194
|
+
* BURGER API - EXAMPLE ROUTE FILE
|
|
195
|
+
* =============================================================================
|
|
196
|
+
*
|
|
197
|
+
* This file shows you everything you can do with BurgerAPI routes!
|
|
198
|
+
*
|
|
199
|
+
* KEY CONCEPTS:
|
|
200
|
+
* - This file is automatically loaded because it's named "route.ts"
|
|
201
|
+
* - The folder path becomes the URL path (e.g., /api/route.ts → /api)
|
|
202
|
+
* - Export functions named after HTTP methods: GET, POST, PUT, DELETE, etc.
|
|
203
|
+
*
|
|
204
|
+
* =============================================================================
|
|
205
|
+
*/
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
import { z } from 'zod';
|
|
209
|
+
import type { BurgerRequest, Middleware, BurgerNext } from 'burger-api';
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
/*
|
|
213
|
+
-----------------------------------------------------------------------------
|
|
214
|
+
OPENAPI METADATA (Optional but recommended!)
|
|
215
|
+
-----------------------------------------------------------------------------
|
|
216
|
+
|
|
217
|
+
- This creates automatic documentation for your API!
|
|
218
|
+
- Visit /docs in your browser to see beautiful Swagger UI documentation.
|
|
219
|
+
- Each HTTP method (get, post, put, delete) can have its own documentation.
|
|
220
|
+
-----------------------------------------------------------------------------
|
|
221
|
+
*/
|
|
222
|
+
export const openapi = {
|
|
223
|
+
// Documentation for the GET method
|
|
224
|
+
get: {
|
|
225
|
+
// 'summary' - A short title shown in the docs (keep it brief!)
|
|
226
|
+
summary: 'Get all items',
|
|
227
|
+
|
|
228
|
+
// 'description' - A longer explanation of what this endpoint does
|
|
229
|
+
description: 'Fetches a list of items. You can filter results using query parameters.',
|
|
230
|
+
|
|
231
|
+
// 'tags' - Groups related endpoints together in the docs
|
|
232
|
+
// All endpoints with the same tag appear in the same section
|
|
233
|
+
tags: ['Items'],
|
|
234
|
+
|
|
235
|
+
// 'operationId' - A unique ID for this endpoint (useful for code generation)
|
|
236
|
+
operationId: 'getItems',
|
|
237
|
+
|
|
238
|
+
// 'responses' - Documents what responses the endpoint can return
|
|
239
|
+
responses: {
|
|
240
|
+
'200': { description: 'Successfully retrieved items' },
|
|
241
|
+
'400': { description: 'Invalid query parameters' },
|
|
242
|
+
},
|
|
243
|
+
},
|
|
244
|
+
|
|
245
|
+
// Documentation for the POST method
|
|
246
|
+
post: {
|
|
247
|
+
summary: 'Create a new item',
|
|
248
|
+
description: 'Creates a new item with the provided data. Returns the created item.',
|
|
249
|
+
tags: ['Items'],
|
|
250
|
+
operationId: 'createItem',
|
|
251
|
+
responses: {
|
|
252
|
+
'201': { description: 'Item created successfully' },
|
|
253
|
+
'400': { description: 'Invalid request body' },
|
|
254
|
+
},
|
|
255
|
+
},
|
|
256
|
+
|
|
257
|
+
// Documentation for the PUT method
|
|
258
|
+
put: {
|
|
259
|
+
summary: 'Update an item',
|
|
260
|
+
description: 'Updates an existing item. Provide the item ID in the query string.',
|
|
261
|
+
tags: ['Items'],
|
|
262
|
+
operationId: 'updateItem',
|
|
263
|
+
responses: {
|
|
264
|
+
'200': { description: 'Item updated successfully' },
|
|
265
|
+
'400': { description: 'Invalid request data' },
|
|
266
|
+
'404': { description: 'Item not found' },
|
|
267
|
+
},
|
|
268
|
+
},
|
|
269
|
+
|
|
270
|
+
// Documentation for the DELETE method
|
|
271
|
+
delete: {
|
|
272
|
+
summary: 'Delete an item',
|
|
273
|
+
description: 'Permanently deletes an item by ID.',
|
|
274
|
+
tags: ['Items'],
|
|
275
|
+
operationId: 'deleteItem',
|
|
276
|
+
responses: {
|
|
277
|
+
'200': { description: 'Item deleted successfully' },
|
|
278
|
+
'404': { description: 'Item not found' },
|
|
279
|
+
},
|
|
280
|
+
},
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
/*
|
|
285
|
+
-----------------------------------------------------------------------------
|
|
286
|
+
SCHEMA VALIDATION (Using Zod)
|
|
287
|
+
-----------------------------------------------------------------------------
|
|
288
|
+
|
|
289
|
+
- Schemas define what data your API accepts. BurgerAPI automatically:
|
|
290
|
+
- Validates incoming data against these schemas
|
|
291
|
+
- Returns a 400 error if validation fails
|
|
292
|
+
- Puts the validated data in req.validated for you to use
|
|
293
|
+
|
|
294
|
+
- You can validate:
|
|
295
|
+
- 'query' → URL query parameters like ?search=hello&page=1
|
|
296
|
+
- 'body' → Request body (for POST/PUT requests)
|
|
297
|
+
- 'params' → URL parameters like /items/[id] → { id: "123" }
|
|
298
|
+
-----------------------------------------------------------------------------
|
|
299
|
+
*/
|
|
300
|
+
export const schema = {
|
|
301
|
+
// Schema for GET requests - validates query parameters
|
|
302
|
+
get: {
|
|
303
|
+
// 'query' - Validates the URL query string
|
|
304
|
+
// Example URL: /api?search=burger&limit=10&page=2
|
|
305
|
+
query: z.object({
|
|
306
|
+
// 'search' - Optional text to search for
|
|
307
|
+
// .optional() means this field isn't required
|
|
308
|
+
search: z.string().optional(),
|
|
309
|
+
|
|
310
|
+
// 'limit' - How many items to return (default: 10)
|
|
311
|
+
// .coerce.number() converts string "10" to number 10
|
|
312
|
+
// .min(1) means it must be at least 1
|
|
313
|
+
// .max(100) means it can't be more than 100
|
|
314
|
+
// .default(10) uses 10 if not provided
|
|
315
|
+
limit: z.coerce.number().min(1).max(100).default(10),
|
|
316
|
+
|
|
317
|
+
// 'page' - Which page of results to return
|
|
318
|
+
page: z.coerce.number().min(1).default(1),
|
|
319
|
+
}),
|
|
320
|
+
},
|
|
321
|
+
|
|
322
|
+
// Schema for POST requests - validates the request body
|
|
323
|
+
post: {
|
|
324
|
+
// 'body' - Validates JSON data sent in the request body
|
|
325
|
+
body: z.object({
|
|
326
|
+
// 'name' - Required, must be at least 1 character
|
|
327
|
+
// .min(1, '...') shows a custom error message if too short
|
|
328
|
+
name: z.string().min(1, 'Name is required'),
|
|
329
|
+
|
|
330
|
+
// 'description' - Optional text field
|
|
331
|
+
description: z.string().optional(),
|
|
332
|
+
|
|
333
|
+
// 'price' - Required, must be a positive number
|
|
334
|
+
// .positive() ensures the number is greater than 0
|
|
335
|
+
price: z.number().positive('Price must be greater than 0'),
|
|
336
|
+
|
|
337
|
+
// 'category' - Must be one of these specific values
|
|
338
|
+
// .enum() only allows the listed values
|
|
339
|
+
category: z.enum(['food', 'drink', 'dessert']),
|
|
340
|
+
|
|
341
|
+
// 'isAvailable' - Optional boolean, defaults to true
|
|
342
|
+
isAvailable: z.boolean().default(true),
|
|
343
|
+
}),
|
|
344
|
+
},
|
|
345
|
+
|
|
346
|
+
// Schema for PUT requests - validates both query and body
|
|
347
|
+
put: {
|
|
348
|
+
// Which item to update (ID in query string)
|
|
349
|
+
query: z.object({
|
|
350
|
+
id: z.string().min(1, 'Item ID is required'),
|
|
351
|
+
}),
|
|
352
|
+
|
|
353
|
+
// What to update (in the request body)
|
|
354
|
+
// .partial() makes all fields optional (for partial updates)
|
|
355
|
+
body: z.object({
|
|
356
|
+
name: z.string().min(1),
|
|
357
|
+
description: z.string(),
|
|
358
|
+
price: z.number().positive(),
|
|
359
|
+
category: z.enum(['food', 'drink', 'dessert']),
|
|
360
|
+
isAvailable: z.boolean(),
|
|
361
|
+
}).partial(), // .partial() = all fields become optional
|
|
362
|
+
},
|
|
363
|
+
|
|
364
|
+
// Schema for DELETE requests - validates query parameters
|
|
365
|
+
delete: {
|
|
366
|
+
query: z.object({
|
|
367
|
+
id: z.string().min(1, 'Item ID is required'),
|
|
368
|
+
}),
|
|
369
|
+
},
|
|
370
|
+
};
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
/*
|
|
374
|
+
-----------------------------------------------------------------------------
|
|
375
|
+
ROUTE-SPECIFIC MIDDLEWARE (Optional)
|
|
376
|
+
-----------------------------------------------------------------------------
|
|
377
|
+
|
|
378
|
+
- Middleware runs BEFORE your route handler. Use it for:
|
|
379
|
+
- Logging requests
|
|
380
|
+
- Checking authentication
|
|
381
|
+
- Modifying the request
|
|
382
|
+
- Blocking unauthorized access
|
|
383
|
+
|
|
384
|
+
- Return 'undefined' to continue to the next middleware/handler
|
|
385
|
+
- Return a 'Response' to stop and send that response immediately
|
|
386
|
+
-----------------------------------------------------------------------------
|
|
387
|
+
*/
|
|
388
|
+
export const middleware: Middleware[] = [
|
|
389
|
+
// Example: Log every request to this route
|
|
390
|
+
async (req: BurgerRequest): Promise<BurgerNext> => {
|
|
391
|
+
console.log(\`[\${new Date().toISOString()}] \${req.method} \${req.url}\`);
|
|
392
|
+
|
|
393
|
+
// Return undefined to continue to the next middleware/handler
|
|
394
|
+
// If you return a Response here, it stops and sends that response
|
|
395
|
+
return undefined;
|
|
396
|
+
},
|
|
397
|
+
];
|
|
398
|
+
|
|
399
|
+
/*
|
|
400
|
+
-----------------------------------------------------------------------------
|
|
401
|
+
HTTP HANDLERS
|
|
402
|
+
-----------------------------------------------------------------------------
|
|
403
|
+
|
|
404
|
+
- These functions handle the actual requests. They receive:
|
|
405
|
+
- req: The request object with validated data in req.validated
|
|
406
|
+
- They must return a Response object. Use Response.json() for JSON responses.
|
|
407
|
+
-----------------------------------------------------------------------------
|
|
408
|
+
*/
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* GET - Fetch items with optional filtering
|
|
412
|
+
*
|
|
413
|
+
* Example requests:
|
|
414
|
+
* - GET /api → Get first 10 items
|
|
415
|
+
* - GET /api?limit=5 → Get first 5 items
|
|
416
|
+
* - GET /api?search=burger&page=2 → Search for "burger", page 2
|
|
417
|
+
*/
|
|
418
|
+
export async function GET(req: BurgerRequest<{ query: z.infer<typeof schema.get.query> }>) {
|
|
419
|
+
// Access validated query parameters from the schema
|
|
420
|
+
const { search, limit, page } = req.validated.query;
|
|
421
|
+
|
|
422
|
+
// Mock data (replace with your database query)
|
|
423
|
+
const mockItems = [
|
|
424
|
+
{ id: '1', name: 'Classic Burger', price: 9.99, category: 'food' },
|
|
425
|
+
{ id: '2', name: 'Cheese Burger', price: 11.99, category: 'food' },
|
|
426
|
+
{ id: '3', name: 'Cola', price: 2.99, category: 'drink' },
|
|
427
|
+
];
|
|
428
|
+
|
|
429
|
+
// Filter items if search is provided
|
|
430
|
+
let items = mockItems;
|
|
431
|
+
if (search) {
|
|
432
|
+
items = items.filter(item =>
|
|
433
|
+
item.name.toLowerCase().includes(search.toLowerCase())
|
|
434
|
+
);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// Calculate pagination
|
|
438
|
+
const startIndex = (page - 1) * limit;
|
|
439
|
+
const paginatedItems = items.slice(startIndex, startIndex + limit);
|
|
440
|
+
|
|
441
|
+
// Return JSON response with status 200 (default)
|
|
442
|
+
return Response.json({
|
|
443
|
+
success: true,
|
|
444
|
+
data: paginatedItems,
|
|
445
|
+
pagination: {
|
|
446
|
+
page,
|
|
447
|
+
limit,
|
|
448
|
+
total: items.length,
|
|
449
|
+
totalPages: Math.ceil(items.length / limit),
|
|
450
|
+
},
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/**
|
|
455
|
+
* POST - Create a new item
|
|
456
|
+
*
|
|
457
|
+
* Example request body:
|
|
458
|
+
* {
|
|
459
|
+
* "name": "Veggie Burger",
|
|
460
|
+
* "description": "Delicious plant-based burger",
|
|
461
|
+
* "price": 12.99,
|
|
462
|
+
* "category": "food"
|
|
463
|
+
* }
|
|
464
|
+
*/
|
|
465
|
+
export async function POST(req: BurgerRequest<{ body: z.infer<typeof schema.post.body> }>) {
|
|
466
|
+
// Get validated body data - already checked by Zod schema!
|
|
467
|
+
const { name, description, price, category, isAvailable } = req.validated.body;
|
|
468
|
+
|
|
469
|
+
// Create the item (replace with your database insert)
|
|
470
|
+
const newItem = {
|
|
471
|
+
id: crypto.randomUUID(), // Generate unique ID
|
|
472
|
+
name,
|
|
473
|
+
description: description || null,
|
|
474
|
+
price,
|
|
475
|
+
category,
|
|
476
|
+
isAvailable,
|
|
477
|
+
createdAt: new Date().toISOString(),
|
|
478
|
+
};
|
|
479
|
+
|
|
480
|
+
// Return the created item with status 201 (Created)
|
|
481
|
+
return Response.json({
|
|
482
|
+
success: true,
|
|
483
|
+
message: 'Item created successfully',
|
|
484
|
+
data: newItem,
|
|
485
|
+
}, { status: 201 });
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* PUT - Update an existing item
|
|
490
|
+
*
|
|
491
|
+
* Example: PUT /api?id=123
|
|
492
|
+
* Body: { "name": "Updated Name", "price": 15.99 }
|
|
493
|
+
*/
|
|
494
|
+
export async function PUT(req: BurgerRequest<{ query: z.infer<typeof schema.put.query>, body: z.infer<typeof schema.put.body> }>) {
|
|
495
|
+
// Get the item ID from query parameters
|
|
496
|
+
const { id } = req.validated.query;
|
|
497
|
+
|
|
498
|
+
// Get the fields to update from the request body
|
|
499
|
+
const updates = req.validated.body;
|
|
500
|
+
|
|
501
|
+
// Find and update the item (replace with your database update)
|
|
502
|
+
// Here we're just simulating an update
|
|
503
|
+
const updatedItem = {
|
|
504
|
+
id,
|
|
505
|
+
...updates,
|
|
506
|
+
updatedAt: new Date().toISOString(),
|
|
507
|
+
};
|
|
508
|
+
|
|
509
|
+
return Response.json({
|
|
510
|
+
success: true,
|
|
511
|
+
message: 'Item updated successfully',
|
|
512
|
+
data: updatedItem,
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
/**
|
|
517
|
+
* DELETE - Remove an item
|
|
518
|
+
*
|
|
519
|
+
* Example: DELETE /api?id=123
|
|
520
|
+
*/
|
|
521
|
+
export async function DELETE(req: BurgerRequest<{ query: z.infer<typeof schema.delete.query> }>) {
|
|
522
|
+
// Get the item ID from query parameters
|
|
523
|
+
const { id } = req.validated.query;
|
|
524
|
+
|
|
525
|
+
// Delete the item (replace with your database delete)
|
|
526
|
+
// Here we're just returning a success message
|
|
527
|
+
return Response.json({
|
|
528
|
+
success: true,
|
|
529
|
+
message: \`Item \${id} deleted successfully\`,
|
|
530
|
+
});
|
|
531
|
+
}
|
|
532
|
+
`;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
/**
|
|
536
|
+
* Generate a CSS file with modern styling for the landing page
|
|
537
|
+
*
|
|
538
|
+
* @returns style.css content as a string
|
|
539
|
+
*/
|
|
540
|
+
export function generateSampleCss(): string {
|
|
541
|
+
return `
|
|
542
|
+
:root {
|
|
543
|
+
--color-primary: hsl(30, 75%, 90%);
|
|
544
|
+
--color-primary-dark: hsl(30, 75%, 80%);
|
|
545
|
+
--color-bg: #09090b;
|
|
546
|
+
--color-surface: hsl(240, 10%, 3.9%);
|
|
547
|
+
--color-border: hsl(240, 3.7%, 15.9%);
|
|
548
|
+
--color-success: hsl(120, 50%, 40%);
|
|
549
|
+
--color-text-muted: hsl(240, 5%, 50%);
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
* {
|
|
553
|
+
margin: 0;
|
|
554
|
+
padding: 0;
|
|
555
|
+
box-sizing: border-box;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
body {
|
|
559
|
+
font-family: 'Poppins', system-ui, sans-serif;
|
|
560
|
+
min-height: 100vh;
|
|
561
|
+
background: var(--color-bg);
|
|
562
|
+
color: #fff;
|
|
563
|
+
display: flex;
|
|
564
|
+
flex-direction: column;
|
|
565
|
+
align-items: center;
|
|
566
|
+
padding: 60px 20px 40px;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
.hero {
|
|
570
|
+
text-align: center;
|
|
571
|
+
max-width: 600px;
|
|
572
|
+
margin-bottom: 48px;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
.logo-wrapper {
|
|
576
|
+
display: flex;
|
|
577
|
+
flex-wrap: wrap;
|
|
578
|
+
margin-bottom: 32px;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
.logo {
|
|
582
|
+
width: 80px;
|
|
583
|
+
height: 80px;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
.logo-text {
|
|
587
|
+
font-size: 3.5rem;
|
|
588
|
+
font-weight: 600;
|
|
589
|
+
color: var(--color-primary);
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
h1 {
|
|
593
|
+
font-size: 2.5rem;
|
|
594
|
+
font-weight: 600;
|
|
595
|
+
margin-bottom: 12px;
|
|
596
|
+
color: #fff;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
.subtitle {
|
|
600
|
+
color: var(--color-text-muted);
|
|
601
|
+
font-size: 1.1rem;
|
|
602
|
+
margin-bottom: 24px;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
.status {
|
|
606
|
+
display: inline-flex;
|
|
607
|
+
align-items: center;
|
|
608
|
+
gap: 8px;
|
|
609
|
+
background: hsla(120, 50%, 40%, 0.1);
|
|
610
|
+
border: 1px solid hsla(120, 50%, 40%, 0.3);
|
|
611
|
+
padding: 8px 16px;
|
|
612
|
+
border-radius: 20px;
|
|
613
|
+
font-size: 0.875rem;
|
|
614
|
+
color: var(--color-success);
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
.status::before {
|
|
618
|
+
content: '';
|
|
619
|
+
width: 8px;
|
|
620
|
+
height: 8px;
|
|
621
|
+
background: var(--color-success);
|
|
622
|
+
border-radius: 50%;
|
|
623
|
+
animation: pulse 2s infinite;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
@keyframes pulse {
|
|
627
|
+
0%, 100% { opacity: 1; }
|
|
628
|
+
50% { opacity: 0.5; }
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
/* Edit hint section */
|
|
632
|
+
.edit-hint {
|
|
633
|
+
background: var(--color-surface);
|
|
634
|
+
border: 1px solid var(--color-border);
|
|
635
|
+
border-radius: 12px;
|
|
636
|
+
padding: 24px 32px;
|
|
637
|
+
margin-bottom: 48px;
|
|
638
|
+
max-width: 500px;
|
|
639
|
+
text-align: center;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
.edit-hint p {
|
|
643
|
+
color: var(--color-text-muted);
|
|
644
|
+
font-size: 0.95rem;
|
|
645
|
+
margin-bottom: 8px;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
.edit-hint code {
|
|
649
|
+
color: var(--color-primary);
|
|
650
|
+
font-family: 'JetBrains Mono', monospace;
|
|
651
|
+
font-size: 0.9rem;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
.edit-hint .hint {
|
|
655
|
+
font-size: 0.8rem;
|
|
656
|
+
color: hsl(240, 5%, 40%);
|
|
657
|
+
margin-top: 12px;
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
/* Quick start section */
|
|
661
|
+
.quick-start {
|
|
662
|
+
max-width: 500px;
|
|
663
|
+
width: 100%;
|
|
664
|
+
margin-bottom: 48px;
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
.quick-start h2 {
|
|
668
|
+
font-size: 1rem;
|
|
669
|
+
font-weight: 500;
|
|
670
|
+
color: var(--color-text-muted);
|
|
671
|
+
margin-bottom: 16px;
|
|
672
|
+
text-align: center;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
.commands {
|
|
676
|
+
display: flex;
|
|
677
|
+
flex-direction: column;
|
|
678
|
+
gap: 8px;
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
.command {
|
|
682
|
+
display: flex;
|
|
683
|
+
align-items: center;
|
|
684
|
+
background: var(--color-surface);
|
|
685
|
+
border: 1px solid var(--color-border);
|
|
686
|
+
border-radius: 8px;
|
|
687
|
+
padding: 12px 16px;
|
|
688
|
+
font-family: 'JetBrains Mono', monospace;
|
|
689
|
+
font-size: 0.85rem;
|
|
690
|
+
transition: border-color 0.2s;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
.command:hover {
|
|
694
|
+
border-color: var(--color-primary-dark);
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
.command .prefix {
|
|
698
|
+
color: var(--color-success);
|
|
699
|
+
margin-right: 8px;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
.command .cmd {
|
|
703
|
+
color: var(--color-primary);
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
.command .comment {
|
|
707
|
+
color: var(--color-text-muted);
|
|
708
|
+
margin-left: auto;
|
|
709
|
+
font-size: 0.75rem;
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
/* Links section */
|
|
713
|
+
.links {
|
|
714
|
+
display: flex;
|
|
715
|
+
gap: 12px;
|
|
716
|
+
justify-content: center;
|
|
717
|
+
flex-wrap: wrap;
|
|
718
|
+
margin-bottom: 48px;
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
.link {
|
|
722
|
+
color: var(--color-text-muted);
|
|
723
|
+
text-decoration: none;
|
|
724
|
+
font-size: 0.9rem;
|
|
725
|
+
padding: 10px 20px;
|
|
726
|
+
border: 1px solid var(--color-border);
|
|
727
|
+
border-radius: 8px;
|
|
728
|
+
transition: all 0.2s;
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
.link:hover {
|
|
732
|
+
color: var(--color-primary);
|
|
733
|
+
border-color: var(--color-primary-dark);
|
|
734
|
+
background: var(--color-surface);
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
.link.primary {
|
|
738
|
+
background: var(--color-primary);
|
|
739
|
+
border-color: var(--color-primary);
|
|
740
|
+
color: #000;
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
.link.primary:hover {
|
|
744
|
+
background: var(--color-primary-dark);
|
|
745
|
+
border-color: var(--color-primary-dark);
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
/* Documentation links */
|
|
749
|
+
.docs-links {
|
|
750
|
+
display: flex;
|
|
751
|
+
gap: 32px;
|
|
752
|
+
justify-content: center;
|
|
753
|
+
flex-wrap: wrap;
|
|
754
|
+
margin-bottom: 48px;
|
|
755
|
+
padding-top: 32px;
|
|
756
|
+
border-top: 1px solid var(--color-border);
|
|
757
|
+
max-width: 600px;
|
|
758
|
+
width: 100%;
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
.docs-section h3 {
|
|
762
|
+
font-size: 0.8rem;
|
|
763
|
+
font-weight: 500;
|
|
764
|
+
color: var(--color-text-muted);
|
|
765
|
+
margin-bottom: 12px;
|
|
766
|
+
text-transform: uppercase;
|
|
767
|
+
letter-spacing: 0.5px;
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
.docs-section a {
|
|
771
|
+
display: block;
|
|
772
|
+
color: hsl(240, 5%, 60%);
|
|
773
|
+
text-decoration: none;
|
|
774
|
+
font-size: 0.85rem;
|
|
775
|
+
padding: 4px 0;
|
|
776
|
+
transition: color 0.2s;
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
.docs-section a:hover {
|
|
780
|
+
color: var(--color-primary);
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
/* Footer */
|
|
784
|
+
.footer {
|
|
785
|
+
margin-top: auto;
|
|
786
|
+
text-align: center;
|
|
787
|
+
padding-top: 32px;
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
.version {
|
|
791
|
+
font-size: 0.75rem;
|
|
792
|
+
color: hsl(240, 5%, 35%);
|
|
793
|
+
margin-bottom: 16px;
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
.social-links {
|
|
797
|
+
display: flex;
|
|
798
|
+
gap: 16px;
|
|
799
|
+
justify-content: center;
|
|
800
|
+
margin-bottom: 16px;
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
.social-links a {
|
|
804
|
+
color: var(--color-text-muted);
|
|
805
|
+
text-decoration: none;
|
|
806
|
+
font-size: 0.85rem;
|
|
807
|
+
transition: color 0.2s;
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
.social-links a:hover {
|
|
811
|
+
color: var(--color-primary);
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
.powered-by {
|
|
815
|
+
color: hsl(240, 5%, 35%);
|
|
816
|
+
font-size: 0.8rem;
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
.powered-by a {
|
|
820
|
+
color: var(--color-primary-dark);
|
|
821
|
+
text-decoration: none;
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
.powered-by a:hover {
|
|
825
|
+
color: var(--color-primary);
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
@media (max-width: 600px) {
|
|
829
|
+
h1 { font-size: 2rem; }
|
|
830
|
+
.docs-links { flex-direction: column; gap: 24px; text-align: center; }
|
|
831
|
+
.command .comment { display: none; }
|
|
832
|
+
}
|
|
833
|
+
`;
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
/**
|
|
837
|
+
* Generate a sample JavaScript file with useful utilities
|
|
838
|
+
*
|
|
839
|
+
* @returns app.js content as a string
|
|
840
|
+
*/
|
|
841
|
+
export function generateSampleJs(): string {
|
|
842
|
+
return 'console.log("Hello from app.js");';
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
/**
|
|
846
|
+
* Generate a minimal, clean landing page
|
|
847
|
+
* Uses official BurgerAPI color scheme
|
|
848
|
+
*
|
|
849
|
+
* @param projectName - Name of the project
|
|
850
|
+
* @returns index.html content as a string
|
|
851
|
+
*/
|
|
852
|
+
export function generateIndexPage(projectName: string): string {
|
|
853
|
+
return `<!DOCTYPE html>
|
|
854
|
+
<html lang="en">
|
|
855
|
+
<head>
|
|
856
|
+
<meta charset="UTF-8">
|
|
857
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
858
|
+
<title>${projectName}</title>
|
|
859
|
+
<link rel="icon" type="image/png" href="https://burger-api.com/img/logo.png">
|
|
860
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
861
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
862
|
+
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600&family=JetBrains+Mono&display=swap" rel="stylesheet">
|
|
863
|
+
<!-- Assets: Styles -->
|
|
864
|
+
<link rel="stylesheet" href="./assets/css/style.css" />
|
|
865
|
+
<!-- Assets: Scripts -->
|
|
866
|
+
<script src="./assets/js/app.js" type="module"></script>
|
|
867
|
+
</head>
|
|
868
|
+
<body>
|
|
869
|
+
<!-- Hero Section -->
|
|
870
|
+
<section class="hero">
|
|
871
|
+
<div class="logo-wrapper">
|
|
872
|
+
<img src="https://burger-api.com/img/logo.png" alt="BurgerAPI Logo" class="logo">
|
|
873
|
+
<span class="logo-text">BurgerAPI</span>
|
|
874
|
+
</div>
|
|
875
|
+
<p class="subtitle">Your Project ${projectName} is ready</p>
|
|
876
|
+
<div class="status">Server running</div>
|
|
877
|
+
</section>
|
|
878
|
+
|
|
879
|
+
<!-- Edit Hint -->
|
|
880
|
+
<div class="edit-hint">
|
|
881
|
+
<p>Edit <code>src/pages/index.html</code> and save to reload the page.</p>
|
|
882
|
+
<p>Edit <code>src/api/route.ts</code> and save to reload the API endpoint.</p>
|
|
883
|
+
<p class="hint">Your changes will automatically refresh the server.</p>
|
|
884
|
+
</div>
|
|
885
|
+
|
|
886
|
+
<!-- Quick Start Commands -->
|
|
887
|
+
<section class="quick-start">
|
|
888
|
+
<h2>Quick Start</h2>
|
|
889
|
+
<div class="commands">
|
|
890
|
+
<div class="command">
|
|
891
|
+
<span class="prefix">$</span>
|
|
892
|
+
<span class="cmd">burger-api add cors logger</span>
|
|
893
|
+
<span class="comment"># Add middleware</span>
|
|
894
|
+
</div>
|
|
895
|
+
<div class="command">
|
|
896
|
+
<span class="prefix">$</span>
|
|
897
|
+
<span class="cmd">burger-api build src/index.ts</span>
|
|
898
|
+
<span class="comment"># Build for production</span>
|
|
899
|
+
</div>
|
|
900
|
+
</div>
|
|
901
|
+
</section>
|
|
902
|
+
|
|
903
|
+
<!-- Action Links -->
|
|
904
|
+
<div class="links">
|
|
905
|
+
<a href="/docs" class="link primary">API Docs</a>
|
|
906
|
+
<a href="/api" class="link">Try API</a>
|
|
907
|
+
<a href="/openapi.json" class="link">OpenAPI</a>
|
|
908
|
+
</div>
|
|
909
|
+
|
|
910
|
+
<!-- Documentation Links -->
|
|
911
|
+
<div class="docs-links">
|
|
912
|
+
<div class="docs-section">
|
|
913
|
+
<h3>Documentation</h3>
|
|
914
|
+
<a href="https://burger-api.com/docs" target="_blank">Getting Started</a>
|
|
915
|
+
<a href="https://burger-api.com/docs/core/configuration" target="_blank">Configuration</a>
|
|
916
|
+
<a href="https://burger-api.com/docs/request-handling/middleware" target="_blank">Middleware</a>
|
|
917
|
+
</div>
|
|
918
|
+
<div class="docs-section">
|
|
919
|
+
<h3>Resources</h3>
|
|
920
|
+
<a href="https://github.com/isfhan/burger-api" target="_blank">GitHub</a>
|
|
921
|
+
<a href="https://github.com/isfhan/burger-api/issues" target="_blank">Report Issue</a>
|
|
922
|
+
<a href="https://www.npmjs.com/package/burger-api" target="_blank">NPM Package</a>
|
|
923
|
+
</div>
|
|
924
|
+
<div class="docs-section">
|
|
925
|
+
<h3>Community</h3>
|
|
926
|
+
<a href="https://github.com/isfhan/burger-api" target="_blank">Contribute</a>
|
|
927
|
+
<a href="https://github.com/isfhan/burger-api/discussions" target="_blank">Discussions</a>
|
|
928
|
+
<a href="https://github.com/isfhan/burger-api/stargazers" target="_blank">Star on GitHub</a>
|
|
929
|
+
</div>
|
|
930
|
+
</div>
|
|
931
|
+
|
|
932
|
+
<!-- Footer -->
|
|
933
|
+
<footer class="footer">
|
|
934
|
+
<div class="version">BurgerAPI v0.7.0 • Bun v1.3+</div>
|
|
935
|
+
<div class="social-links">
|
|
936
|
+
<a href="https://github.com/isfhan/burger-api" target="_blank">GitHub</a>
|
|
937
|
+
<a href="https://www.npmjs.com/package/burger-api" target="_blank">NPM</a>
|
|
938
|
+
<a href="https://burger-api.com" target="_blank">Website</a>
|
|
939
|
+
</div>
|
|
940
|
+
<p class="powered-by">
|
|
941
|
+
Built with ❤️ using <a href="https://burger-api.com">BurgerAPI</a>
|
|
942
|
+
</p>
|
|
943
|
+
</footer>
|
|
944
|
+
</body>
|
|
945
|
+
</html>
|
|
946
|
+
`;
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
/**
|
|
950
|
+
* Generate middleware index file
|
|
951
|
+
* This is where users will export their middleware
|
|
952
|
+
*
|
|
953
|
+
* @returns middleware/index.ts content as a string
|
|
954
|
+
*/
|
|
955
|
+
export function generateMiddlewareIndex(): string {
|
|
956
|
+
return `/**
|
|
957
|
+
* Global Middleware Configuration
|
|
958
|
+
*
|
|
959
|
+
* Import and export middleware here to use them in your app.
|
|
960
|
+
* Example:
|
|
961
|
+
*
|
|
962
|
+
* import { cors } from './cors/cors';
|
|
963
|
+
* import { logger } from './logger/logger';
|
|
964
|
+
*
|
|
965
|
+
* export const globalMiddleware = [
|
|
966
|
+
* logger(),
|
|
967
|
+
* cors(),
|
|
968
|
+
* ];
|
|
969
|
+
*/
|
|
970
|
+
|
|
971
|
+
export const globalMiddleware: any[] = [];
|
|
972
|
+
`;
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
/**
|
|
976
|
+
* Download the .llm-context folder from GitHub to the target project
|
|
977
|
+
* This includes context files for AI assistants working with Burger API
|
|
978
|
+
*
|
|
979
|
+
* @param targetDir - Where to create the project
|
|
980
|
+
*/
|
|
981
|
+
async function downloadLlmFolder(targetDir: string): Promise<void> {
|
|
982
|
+
try {
|
|
983
|
+
const llmDir = join(targetDir, 'ecosystem', '.llm-context');
|
|
984
|
+
|
|
985
|
+
// Download all three .llm files from GitHub
|
|
986
|
+
const files = ['llms.txt', 'llms-small.txt', 'llms-full.txt'];
|
|
987
|
+
|
|
988
|
+
for (const fileName of files) {
|
|
989
|
+
const sourcePath = `ecosystem/.llm-context/${fileName}`;
|
|
990
|
+
const destPath = join(llmDir, fileName);
|
|
991
|
+
await downloadFile(sourcePath, destPath);
|
|
992
|
+
}
|
|
993
|
+
} catch (err) {
|
|
994
|
+
// Log warning but don't fail project creation if download fails
|
|
995
|
+
// This allows projects to be created even if GitHub is unreachable
|
|
996
|
+
console.warn(
|
|
997
|
+
`Warning: Could not download .llm-context folder: ${
|
|
998
|
+
err instanceof Error ? err.message : 'Unknown error'
|
|
999
|
+
}`
|
|
1000
|
+
);
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
/**
|
|
1005
|
+
* Create a new project with all necessary files
|
|
1006
|
+
* This is the main function that sets up everything
|
|
1007
|
+
*
|
|
1008
|
+
* @param targetDir - Where to create the project
|
|
1009
|
+
* @param options - Project configuration from user prompts
|
|
1010
|
+
*/
|
|
1011
|
+
export async function createProject(
|
|
1012
|
+
targetDir: string,
|
|
1013
|
+
options: CreateOptions
|
|
1014
|
+
): Promise<void> {
|
|
1015
|
+
const spin = spinner('Creating project structure...');
|
|
1016
|
+
|
|
1017
|
+
try {
|
|
1018
|
+
// Create base files that every project needs
|
|
1019
|
+
await Bun.write(
|
|
1020
|
+
join(targetDir, 'package.json'),
|
|
1021
|
+
generatePackageJson(options.name)
|
|
1022
|
+
);
|
|
1023
|
+
await Bun.write(join(targetDir, 'tsconfig.json'), generateTsConfig());
|
|
1024
|
+
await Bun.write(join(targetDir, '.gitignore'), generateGitIgnore());
|
|
1025
|
+
await Bun.write(
|
|
1026
|
+
join(targetDir, '.prettierrc'),
|
|
1027
|
+
generatePrettierConfig()
|
|
1028
|
+
);
|
|
1029
|
+
|
|
1030
|
+
// Create src directory and index file
|
|
1031
|
+
await Bun.write(
|
|
1032
|
+
join(targetDir, 'src', 'index.ts'),
|
|
1033
|
+
generateIndexFile(options)
|
|
1034
|
+
);
|
|
1035
|
+
|
|
1036
|
+
// Create API directory and files if requested
|
|
1037
|
+
if (options.useApi) {
|
|
1038
|
+
const apiDir = join(targetDir, 'src', options.apiDir || 'api');
|
|
1039
|
+
await Bun.write(join(apiDir, 'route.ts'), generateApiRoute());
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
// Create Pages directory and files if requested
|
|
1043
|
+
if (options.usePages) {
|
|
1044
|
+
const pagesDir = join(targetDir, 'src', options.pageDir || 'pages');
|
|
1045
|
+
await Bun.write(
|
|
1046
|
+
join(pagesDir, 'index.html'),
|
|
1047
|
+
generateIndexPage(options.name)
|
|
1048
|
+
);
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
// Create sample assets inside pages directory (so they're served by page router)
|
|
1052
|
+
if (options.usePages) {
|
|
1053
|
+
const pagesDir = join(targetDir, 'src', options.pageDir || 'pages');
|
|
1054
|
+
await Bun.write(
|
|
1055
|
+
join(pagesDir, 'assets', 'css', 'style.css'),
|
|
1056
|
+
generateSampleCss()
|
|
1057
|
+
);
|
|
1058
|
+
await Bun.write(
|
|
1059
|
+
join(pagesDir, 'assets', 'js', 'app.js'),
|
|
1060
|
+
generateSampleJs()
|
|
1061
|
+
);
|
|
1062
|
+
// Logo is loaded from https://burger-api.com/img/logo.png
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
// Create ecosystem/middleware directory for installed middleware
|
|
1066
|
+
// Users can also create their own middleware/ folder for custom middleware
|
|
1067
|
+
const ecosystemMiddlewareDir = join(
|
|
1068
|
+
targetDir,
|
|
1069
|
+
'ecosystem',
|
|
1070
|
+
'middleware'
|
|
1071
|
+
);
|
|
1072
|
+
await Bun.write(
|
|
1073
|
+
join(ecosystemMiddlewareDir, 'index.ts'),
|
|
1074
|
+
generateMiddlewareIndex()
|
|
1075
|
+
);
|
|
1076
|
+
|
|
1077
|
+
// Download .llm-context folder with context files for AI assistants
|
|
1078
|
+
await downloadLlmFolder(targetDir);
|
|
1079
|
+
|
|
1080
|
+
spin.stop('Project created successfully!');
|
|
1081
|
+
} catch (err) {
|
|
1082
|
+
spin.stop('Failed to create project', true);
|
|
1083
|
+
throw err;
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
/**
|
|
1088
|
+
* Install dependencies in a project directory
|
|
1089
|
+
* Runs `bun install` to install all packages
|
|
1090
|
+
*
|
|
1091
|
+
* @param projectDir - Directory containing package.json
|
|
1092
|
+
*/
|
|
1093
|
+
export async function installDependencies(projectDir: string): Promise<void> {
|
|
1094
|
+
const spin = spinner('Installing dependencies...');
|
|
1095
|
+
|
|
1096
|
+
try {
|
|
1097
|
+
// Run bun install using Bun.spawn
|
|
1098
|
+
const proc = Bun.spawn(['bun', 'install'], {
|
|
1099
|
+
cwd: projectDir,
|
|
1100
|
+
stdout: 'ignore',
|
|
1101
|
+
stderr: 'pipe',
|
|
1102
|
+
});
|
|
1103
|
+
|
|
1104
|
+
// Wait for it to complete
|
|
1105
|
+
const exitCode = await proc.exited;
|
|
1106
|
+
|
|
1107
|
+
if (exitCode !== 0) {
|
|
1108
|
+
throw new Error('bun install failed');
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
spin.stop('Dependencies installed!');
|
|
1112
|
+
} catch (err) {
|
|
1113
|
+
spin.stop('Failed to install dependencies', true);
|
|
1114
|
+
throw err;
|
|
1115
|
+
}
|
|
1116
|
+
}
|