@girardmedia/bootspring 1.1.2 → 1.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -1
- package/agents/README.md +93 -0
- package/agents/api-expert/context.md +416 -0
- package/agents/architecture-expert/context.md +454 -0
- package/agents/backend-expert/context.md +483 -0
- package/agents/code-review-expert/context.md +365 -0
- package/agents/database-expert/context.md +250 -0
- package/agents/devops-expert/context.md +446 -0
- package/agents/frontend-expert/context.md +364 -0
- package/agents/index.js +140 -0
- package/agents/performance-expert/context.md +377 -0
- package/agents/security-expert/context.md +343 -0
- package/agents/testing-expert/context.md +414 -0
- package/agents/ui-ux-expert/context.md +448 -0
- package/agents/vercel-expert/context.md +426 -0
- package/bin/bootspring.js +10 -4
- package/cli/agent.js +15 -34
- package/cli/doctor.js +437 -0
- package/cli/init.js +83 -41
- package/cli/loop.js +12 -1
- package/cli/orchestrator.js +25 -0
- package/cli/skill.js +5 -0
- package/core/api-client.js +22 -3
- package/core/auth.js +111 -1
- package/core/utils.js +52 -1
- package/generators/generate.js +595 -0
- package/generators/index.js +18 -0
- package/generators/presets/full.js +28 -0
- package/generators/presets/index.js +12 -0
- package/generators/presets/minimal.js +29 -0
- package/generators/presets/standard.js +28 -0
- package/generators/questionnaire.js +322 -0
- package/generators/sections/advanced.js +136 -0
- package/generators/sections/business.js +118 -0
- package/generators/sections/identity.js +76 -0
- package/generators/sections/index.js +17 -0
- package/generators/sections/instructions.js +146 -0
- package/generators/sections/plugins.js +141 -0
- package/generators/sections/pre-build.js +130 -0
- package/generators/sections/technical.js +171 -0
- package/generators/sections/workflow.js +104 -0
- package/generators/templates/claude.template.js +241 -0
- package/generators/templates/index.js +11 -0
- package/generators/templates/seed.template.js +109 -0
- package/hooks/context-detector.js +471 -0
- package/hooks/index.js +35 -0
- package/hooks/prompt-enhancer.js +288 -0
- package/intelligence/git-memory.js +549 -0
- package/intelligence/index.js +84 -0
- package/intelligence/learning/index.js +14 -0
- package/intelligence/learning/insights.json +12 -0
- package/intelligence/learning/pattern-learner.js +460 -0
- package/intelligence/memory/decision-tracker.js +439 -0
- package/intelligence/memory/index.js +14 -0
- package/intelligence/memory/patterns.json +11 -0
- package/intelligence/orchestrator.js +964 -0
- package/intelligence/prd.js +447 -0
- package/intelligence/recommendation-weights.json +18 -0
- package/intelligence/recommendations.js +234 -0
- package/mcp/contracts/mcp-contract.v1.json +44 -1
- package/mcp/registry.js +13 -0
- package/mcp/response-formatter.js +186 -1
- package/mcp/server.js +47 -1
- package/mcp/tools/assist-tool.js +343 -0
- package/package.json +12 -14
- package/plugins/index.js +141 -0
- package/quality/index.js +380 -0
- package/quality/lint-budgets.json +19 -0
- package/skills/index.js +787 -0
- package/skills/patterns/README.md +163 -0
- package/skills/patterns/api/route-handler.md +217 -0
- package/skills/patterns/api/server-action.md +249 -0
- package/skills/patterns/auth/clerk.md +132 -0
- package/skills/patterns/database/prisma.md +180 -0
- package/skills/patterns/payments/stripe.md +272 -0
- package/skills/patterns/security/validation.md +268 -0
- package/skills/patterns/testing/vitest.md +307 -0
package/README.md
CHANGED
|
@@ -30,6 +30,37 @@ npm install -g @girardmedia/bootspring
|
|
|
30
30
|
bootspring init
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
+
## Authentication
|
|
34
|
+
|
|
35
|
+
Bootspring uses account-based authentication with device fingerprinting for security.
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
# Register a new account
|
|
39
|
+
bootspring auth register
|
|
40
|
+
|
|
41
|
+
# Login to your account
|
|
42
|
+
bootspring auth login
|
|
43
|
+
|
|
44
|
+
# View account status
|
|
45
|
+
bootspring auth status
|
|
46
|
+
|
|
47
|
+
# Logout
|
|
48
|
+
bootspring auth logout
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Device Management
|
|
52
|
+
|
|
53
|
+
Each account has a device limit based on your subscription tier:
|
|
54
|
+
|
|
55
|
+
| Tier | Max Devices |
|
|
56
|
+
|------|-------------|
|
|
57
|
+
| Free | 2 |
|
|
58
|
+
| Pro | 5 |
|
|
59
|
+
| Team | 10 |
|
|
60
|
+
| Enterprise | 50 |
|
|
61
|
+
|
|
62
|
+
Your devices are automatically tracked when you login. If you hit your device limit, logout from another device or upgrade your plan.
|
|
63
|
+
|
|
33
64
|
## Commands
|
|
34
65
|
|
|
35
66
|
### Getting Started
|
|
@@ -55,13 +86,16 @@ bootspring context validate # Validate project setup
|
|
|
55
86
|
```bash
|
|
56
87
|
bootspring agent list # List available agents
|
|
57
88
|
bootspring agent show <name> # Show agent details
|
|
58
|
-
bootspring agent invoke <n> # Get specialized help
|
|
89
|
+
bootspring agent invoke <n> # Get specialized help (MCP required)
|
|
59
90
|
bootspring skill search <q> # Find code patterns
|
|
91
|
+
bootspring skill show <name> # View skill content (MCP required)
|
|
60
92
|
bootspring skill list --external # Include curated external catalog
|
|
61
93
|
bootspring orchestrator workflows # List workflows and premium packs
|
|
62
94
|
bootspring telemetry status # Inspect local telemetry pipeline
|
|
63
95
|
```
|
|
64
96
|
|
|
97
|
+
> **Note:** Commands marked "MCP required" only work when invoked through an MCP-compatible AI assistant (Claude Code, etc). This protects proprietary prompts and agent configurations from being exposed in standalone CLI mode.
|
|
98
|
+
|
|
65
99
|
Premium workflow packs:
|
|
66
100
|
|
|
67
101
|
- `launch-pack` (pro)
|
package/agents/README.md
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# Bootspring Agents
|
|
2
|
+
|
|
3
|
+
Specialized AI agents for different development domains. Each agent provides focused expertise, context, and patterns for its area.
|
|
4
|
+
|
|
5
|
+
## Available Agents
|
|
6
|
+
|
|
7
|
+
| Agent | Domain | Use When |
|
|
8
|
+
|-------|--------|----------|
|
|
9
|
+
| [database-expert](./database-expert/) | Database & ORM | Schema design, queries, migrations |
|
|
10
|
+
| [security-expert](./security-expert/) | Security & Auth | Auth flows, vulnerabilities, OWASP |
|
|
11
|
+
| [frontend-expert](./frontend-expert/) | UI & React | Components, state, styling |
|
|
12
|
+
| [backend-expert](./backend-expert/) | Server Logic | API routes, server actions, business logic |
|
|
13
|
+
| [api-expert](./api-expert/) | API Design | REST, GraphQL, route handlers |
|
|
14
|
+
| [testing-expert](./testing-expert/) | Testing | Unit, integration, E2E tests |
|
|
15
|
+
| [performance-expert](./performance-expert/) | Performance | Optimization, caching, profiling |
|
|
16
|
+
| [devops-expert](./devops-expert/) | DevOps & CI/CD | Deployment, pipelines, infrastructure |
|
|
17
|
+
| [ui-ux-expert](./ui-ux-expert/) | Design & UX | User experience, accessibility, design |
|
|
18
|
+
| [architecture-expert](./architecture-expert/) | Architecture | System design, patterns, scaling |
|
|
19
|
+
| [code-review-expert](./code-review-expert/) | Code Quality | Reviews, best practices, refactoring |
|
|
20
|
+
| [vercel-expert](./vercel-expert/) | Vercel Platform | Deployment, edge, serverless |
|
|
21
|
+
|
|
22
|
+
## Using Agents
|
|
23
|
+
|
|
24
|
+
### Via CLI
|
|
25
|
+
```bash
|
|
26
|
+
# List all agents
|
|
27
|
+
bootspring agent list
|
|
28
|
+
|
|
29
|
+
# Show agent details
|
|
30
|
+
bootspring agent show database-expert
|
|
31
|
+
|
|
32
|
+
# Invoke agent for current context
|
|
33
|
+
bootspring agent invoke security-expert
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Via MCP
|
|
37
|
+
When using Claude Code, agents are automatically suggested based on your task context. You can also explicitly request an agent:
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
@bootspring agent database-expert
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Agent Selection
|
|
44
|
+
|
|
45
|
+
Bootspring automatically detects which agents are relevant based on keywords in your request:
|
|
46
|
+
|
|
47
|
+
- **database-expert**: database, schema, prisma, drizzle, migration, query, model
|
|
48
|
+
- **security-expert**: auth, login, security, password, jwt, csrf, xss, owasp
|
|
49
|
+
- **frontend-expert**: component, react, ui, tailwind, state, client, hook
|
|
50
|
+
- **backend-expert**: api, server, action, business, logic, service
|
|
51
|
+
- **testing-expert**: test, spec, jest, vitest, playwright, coverage
|
|
52
|
+
- **performance-expert**: performance, optimize, cache, slow, fast, bundle
|
|
53
|
+
- **devops-expert**: deploy, ci, cd, docker, kubernetes, pipeline
|
|
54
|
+
- **architecture-expert**: architecture, design, pattern, scale, structure
|
|
55
|
+
|
|
56
|
+
## Agent Context Files
|
|
57
|
+
|
|
58
|
+
Each agent directory contains:
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
agents/{name}/
|
|
62
|
+
├── context.md # Full agent context and expertise
|
|
63
|
+
├── patterns.md # Common patterns this agent uses
|
|
64
|
+
└── checklist.md # Review checklist for this domain
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Creating Custom Agents
|
|
68
|
+
|
|
69
|
+
Add a new directory under `agents/` with at minimum a `context.md` file:
|
|
70
|
+
|
|
71
|
+
```markdown
|
|
72
|
+
# Custom Agent Name
|
|
73
|
+
|
|
74
|
+
## Role
|
|
75
|
+
Brief description of what this agent does.
|
|
76
|
+
|
|
77
|
+
## Expertise
|
|
78
|
+
- Area 1
|
|
79
|
+
- Area 2
|
|
80
|
+
|
|
81
|
+
## Context
|
|
82
|
+
Detailed knowledge and patterns...
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Then register it in your `bootspring.config.js`:
|
|
86
|
+
|
|
87
|
+
```javascript
|
|
88
|
+
module.exports = {
|
|
89
|
+
agents: {
|
|
90
|
+
custom: ['my-custom-agent']
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
```
|
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
# API Expert Agent
|
|
2
|
+
|
|
3
|
+
## Role
|
|
4
|
+
Specialized in API design, RESTful patterns, GraphQL, route handler implementation, and API documentation for modern web applications.
|
|
5
|
+
|
|
6
|
+
## Core Expertise
|
|
7
|
+
|
|
8
|
+
### RESTful API Design
|
|
9
|
+
|
|
10
|
+
#### Resource Naming
|
|
11
|
+
```
|
|
12
|
+
# Good: Plural nouns, hierarchical
|
|
13
|
+
GET /api/users
|
|
14
|
+
GET /api/users/{id}
|
|
15
|
+
GET /api/users/{id}/posts
|
|
16
|
+
POST /api/users
|
|
17
|
+
PATCH /api/users/{id}
|
|
18
|
+
DELETE /api/users/{id}
|
|
19
|
+
|
|
20
|
+
# Avoid: Verbs in URLs
|
|
21
|
+
GET /api/getUsers ❌
|
|
22
|
+
POST /api/createUser ❌
|
|
23
|
+
POST /api/users/delete/1 ❌
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
#### HTTP Methods & Status Codes
|
|
27
|
+
```typescript
|
|
28
|
+
// GET: Retrieve resource(s)
|
|
29
|
+
// 200 OK - Success
|
|
30
|
+
// 404 Not Found - Resource doesn't exist
|
|
31
|
+
|
|
32
|
+
// POST: Create resource
|
|
33
|
+
// 201 Created - Resource created
|
|
34
|
+
// 400 Bad Request - Validation failed
|
|
35
|
+
// 409 Conflict - Duplicate (unique constraint)
|
|
36
|
+
|
|
37
|
+
// PATCH: Partial update
|
|
38
|
+
// 200 OK - Updated
|
|
39
|
+
// 404 Not Found - Resource doesn't exist
|
|
40
|
+
|
|
41
|
+
// PUT: Full replacement
|
|
42
|
+
// 200 OK - Replaced
|
|
43
|
+
// 201 Created - Created if didn't exist
|
|
44
|
+
|
|
45
|
+
// DELETE: Remove resource
|
|
46
|
+
// 204 No Content - Deleted
|
|
47
|
+
// 404 Not Found - Resource doesn't exist
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Route Handler Patterns
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
// app/api/posts/route.ts
|
|
54
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
55
|
+
import { auth } from '@clerk/nextjs/server';
|
|
56
|
+
import { prisma } from '@/lib/prisma';
|
|
57
|
+
import { z } from 'zod';
|
|
58
|
+
|
|
59
|
+
// Pagination helper
|
|
60
|
+
function parsePagination(searchParams: URLSearchParams) {
|
|
61
|
+
return {
|
|
62
|
+
page: Math.max(1, parseInt(searchParams.get('page') ?? '1')),
|
|
63
|
+
limit: Math.min(100, Math.max(1, parseInt(searchParams.get('limit') ?? '20'))),
|
|
64
|
+
sort: searchParams.get('sort') ?? 'createdAt',
|
|
65
|
+
order: (searchParams.get('order') ?? 'desc') as 'asc' | 'desc',
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// GET with filtering, sorting, pagination
|
|
70
|
+
export async function GET(request: NextRequest) {
|
|
71
|
+
const { searchParams } = new URL(request.url);
|
|
72
|
+
const { page, limit, sort, order } = parsePagination(searchParams);
|
|
73
|
+
|
|
74
|
+
// Filters
|
|
75
|
+
const status = searchParams.get('status');
|
|
76
|
+
const authorId = searchParams.get('authorId');
|
|
77
|
+
const search = searchParams.get('q');
|
|
78
|
+
|
|
79
|
+
const where = {
|
|
80
|
+
...(status && { status }),
|
|
81
|
+
...(authorId && { authorId }),
|
|
82
|
+
...(search && {
|
|
83
|
+
OR: [
|
|
84
|
+
{ title: { contains: search, mode: 'insensitive' } },
|
|
85
|
+
{ content: { contains: search, mode: 'insensitive' } },
|
|
86
|
+
],
|
|
87
|
+
}),
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const [posts, total] = await Promise.all([
|
|
91
|
+
prisma.post.findMany({
|
|
92
|
+
where,
|
|
93
|
+
skip: (page - 1) * limit,
|
|
94
|
+
take: limit,
|
|
95
|
+
orderBy: { [sort]: order },
|
|
96
|
+
include: {
|
|
97
|
+
author: { select: { id: true, name: true, avatar: true } },
|
|
98
|
+
_count: { select: { comments: true, likes: true } },
|
|
99
|
+
},
|
|
100
|
+
}),
|
|
101
|
+
prisma.post.count({ where }),
|
|
102
|
+
]);
|
|
103
|
+
|
|
104
|
+
return NextResponse.json({
|
|
105
|
+
data: posts,
|
|
106
|
+
pagination: {
|
|
107
|
+
page,
|
|
108
|
+
limit,
|
|
109
|
+
total,
|
|
110
|
+
totalPages: Math.ceil(total / limit),
|
|
111
|
+
hasNext: page * limit < total,
|
|
112
|
+
hasPrev: page > 1,
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// POST with validation
|
|
118
|
+
const CreatePostSchema = z.object({
|
|
119
|
+
title: z.string().min(1).max(200),
|
|
120
|
+
content: z.string().min(1),
|
|
121
|
+
status: z.enum(['draft', 'published']).default('draft'),
|
|
122
|
+
tags: z.array(z.string()).optional(),
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
export async function POST(request: NextRequest) {
|
|
126
|
+
const { userId } = await auth();
|
|
127
|
+
if (!userId) {
|
|
128
|
+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
let body;
|
|
132
|
+
try {
|
|
133
|
+
body = await request.json();
|
|
134
|
+
} catch {
|
|
135
|
+
return NextResponse.json({ error: 'Invalid JSON' }, { status: 400 });
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const result = CreatePostSchema.safeParse(body);
|
|
139
|
+
if (!result.success) {
|
|
140
|
+
return NextResponse.json(
|
|
141
|
+
{ error: 'Validation failed', details: result.error.flatten() },
|
|
142
|
+
{ status: 400 }
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const post = await prisma.post.create({
|
|
147
|
+
data: {
|
|
148
|
+
...result.data,
|
|
149
|
+
authorId: userId,
|
|
150
|
+
},
|
|
151
|
+
include: {
|
|
152
|
+
author: { select: { id: true, name: true } },
|
|
153
|
+
},
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
return NextResponse.json(post, { status: 201 });
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### API Response Formats
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
// Consistent response envelope
|
|
164
|
+
interface ApiResponse<T> {
|
|
165
|
+
data: T;
|
|
166
|
+
meta?: {
|
|
167
|
+
pagination?: Pagination;
|
|
168
|
+
[key: string]: unknown;
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
interface ApiError {
|
|
173
|
+
error: string;
|
|
174
|
+
code?: string;
|
|
175
|
+
details?: Record<string, string[]>;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Helper function
|
|
179
|
+
function apiResponse<T>(data: T, meta?: Record<string, unknown>): NextResponse {
|
|
180
|
+
return NextResponse.json({ data, ...(meta && { meta }) });
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function apiError(
|
|
184
|
+
message: string,
|
|
185
|
+
status: number,
|
|
186
|
+
details?: Record<string, unknown>
|
|
187
|
+
): NextResponse {
|
|
188
|
+
return NextResponse.json(
|
|
189
|
+
{ error: message, ...details },
|
|
190
|
+
{ status }
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Usage
|
|
195
|
+
return apiResponse(posts, { pagination });
|
|
196
|
+
return apiError('Validation failed', 400, { details: errors });
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### API Versioning
|
|
200
|
+
|
|
201
|
+
```typescript
|
|
202
|
+
// app/api/v1/users/route.ts
|
|
203
|
+
// app/api/v2/users/route.ts
|
|
204
|
+
|
|
205
|
+
// Or via headers
|
|
206
|
+
export async function GET(request: NextRequest) {
|
|
207
|
+
const version = request.headers.get('api-version') ?? 'v1';
|
|
208
|
+
|
|
209
|
+
if (version === 'v2') {
|
|
210
|
+
// V2 response format
|
|
211
|
+
return NextResponse.json({ users: data, meta: {} });
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// V1 response format (default)
|
|
215
|
+
return NextResponse.json(data);
|
|
216
|
+
}
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
### Rate Limiting
|
|
220
|
+
|
|
221
|
+
```typescript
|
|
222
|
+
// lib/rate-limit.ts
|
|
223
|
+
import { Ratelimit } from '@upstash/ratelimit';
|
|
224
|
+
import { Redis } from '@upstash/redis';
|
|
225
|
+
|
|
226
|
+
const ratelimit = new Ratelimit({
|
|
227
|
+
redis: Redis.fromEnv(),
|
|
228
|
+
limiter: Ratelimit.slidingWindow(100, '1 m'),
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
export async function rateLimit(identifier: string) {
|
|
232
|
+
const { success, limit, remaining, reset } = await ratelimit.limit(identifier);
|
|
233
|
+
|
|
234
|
+
return {
|
|
235
|
+
success,
|
|
236
|
+
headers: {
|
|
237
|
+
'X-RateLimit-Limit': limit.toString(),
|
|
238
|
+
'X-RateLimit-Remaining': remaining.toString(),
|
|
239
|
+
'X-RateLimit-Reset': reset.toString(),
|
|
240
|
+
},
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Usage in route
|
|
245
|
+
export async function POST(request: NextRequest) {
|
|
246
|
+
const ip = request.headers.get('x-forwarded-for') ?? 'anonymous';
|
|
247
|
+
const { success, headers } = await rateLimit(`api:${ip}`);
|
|
248
|
+
|
|
249
|
+
if (!success) {
|
|
250
|
+
return NextResponse.json(
|
|
251
|
+
{ error: 'Too many requests' },
|
|
252
|
+
{ status: 429, headers }
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Process request...
|
|
257
|
+
const response = NextResponse.json(data);
|
|
258
|
+
Object.entries(headers).forEach(([key, value]) => {
|
|
259
|
+
response.headers.set(key, value);
|
|
260
|
+
});
|
|
261
|
+
return response;
|
|
262
|
+
}
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
### CORS Configuration
|
|
266
|
+
|
|
267
|
+
```typescript
|
|
268
|
+
// middleware.ts
|
|
269
|
+
import { NextResponse } from 'next/server';
|
|
270
|
+
import type { NextRequest } from 'next/server';
|
|
271
|
+
|
|
272
|
+
const allowedOrigins = [
|
|
273
|
+
'https://yourapp.com',
|
|
274
|
+
'https://admin.yourapp.com',
|
|
275
|
+
];
|
|
276
|
+
|
|
277
|
+
export function middleware(request: NextRequest) {
|
|
278
|
+
const origin = request.headers.get('origin');
|
|
279
|
+
const isApiRoute = request.nextUrl.pathname.startsWith('/api');
|
|
280
|
+
|
|
281
|
+
if (isApiRoute) {
|
|
282
|
+
// Handle preflight
|
|
283
|
+
if (request.method === 'OPTIONS') {
|
|
284
|
+
return new NextResponse(null, {
|
|
285
|
+
status: 204,
|
|
286
|
+
headers: {
|
|
287
|
+
'Access-Control-Allow-Origin': allowedOrigins.includes(origin ?? '') ? origin! : '',
|
|
288
|
+
'Access-Control-Allow-Methods': 'GET, POST, PUT, PATCH, DELETE, OPTIONS',
|
|
289
|
+
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
|
|
290
|
+
'Access-Control-Max-Age': '86400',
|
|
291
|
+
},
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Add CORS headers to response
|
|
296
|
+
const response = NextResponse.next();
|
|
297
|
+
if (origin && allowedOrigins.includes(origin)) {
|
|
298
|
+
response.headers.set('Access-Control-Allow-Origin', origin);
|
|
299
|
+
}
|
|
300
|
+
return response;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
return NextResponse.next();
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
export const config = {
|
|
307
|
+
matcher: '/api/:path*',
|
|
308
|
+
};
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
### OpenAPI Documentation
|
|
312
|
+
|
|
313
|
+
```typescript
|
|
314
|
+
// Using next-swagger-doc
|
|
315
|
+
// lib/swagger.ts
|
|
316
|
+
import { createSwaggerSpec } from 'next-swagger-doc';
|
|
317
|
+
|
|
318
|
+
export const getApiDocs = () => {
|
|
319
|
+
const spec = createSwaggerSpec({
|
|
320
|
+
apiFolder: 'app/api',
|
|
321
|
+
definition: {
|
|
322
|
+
openapi: '3.0.0',
|
|
323
|
+
info: {
|
|
324
|
+
title: 'Your API',
|
|
325
|
+
version: '1.0.0',
|
|
326
|
+
},
|
|
327
|
+
components: {
|
|
328
|
+
securitySchemes: {
|
|
329
|
+
bearerAuth: {
|
|
330
|
+
type: 'http',
|
|
331
|
+
scheme: 'bearer',
|
|
332
|
+
},
|
|
333
|
+
},
|
|
334
|
+
},
|
|
335
|
+
security: [{ bearerAuth: [] }],
|
|
336
|
+
},
|
|
337
|
+
});
|
|
338
|
+
return spec;
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
// JSDoc comments in route handlers
|
|
342
|
+
/**
|
|
343
|
+
* @swagger
|
|
344
|
+
* /api/posts:
|
|
345
|
+
* get:
|
|
346
|
+
* summary: List posts
|
|
347
|
+
* tags: [Posts]
|
|
348
|
+
* parameters:
|
|
349
|
+
* - in: query
|
|
350
|
+
* name: page
|
|
351
|
+
* schema:
|
|
352
|
+
* type: integer
|
|
353
|
+
* responses:
|
|
354
|
+
* 200:
|
|
355
|
+
* description: List of posts
|
|
356
|
+
*/
|
|
357
|
+
export async function GET(request: NextRequest) {
|
|
358
|
+
// ...
|
|
359
|
+
}
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
### File Upload API
|
|
363
|
+
|
|
364
|
+
```typescript
|
|
365
|
+
// app/api/upload/route.ts
|
|
366
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
367
|
+
import { put } from '@vercel/blob';
|
|
368
|
+
|
|
369
|
+
export async function POST(request: NextRequest) {
|
|
370
|
+
const { userId } = await auth();
|
|
371
|
+
if (!userId) {
|
|
372
|
+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
const formData = await request.formData();
|
|
376
|
+
const file = formData.get('file') as File;
|
|
377
|
+
|
|
378
|
+
if (!file) {
|
|
379
|
+
return NextResponse.json({ error: 'No file provided' }, { status: 400 });
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// Validate file
|
|
383
|
+
const maxSize = 5 * 1024 * 1024; // 5MB
|
|
384
|
+
if (file.size > maxSize) {
|
|
385
|
+
return NextResponse.json({ error: 'File too large' }, { status: 400 });
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
const allowedTypes = ['image/jpeg', 'image/png', 'image/webp'];
|
|
389
|
+
if (!allowedTypes.includes(file.type)) {
|
|
390
|
+
return NextResponse.json({ error: 'Invalid file type' }, { status: 400 });
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// Upload to Vercel Blob
|
|
394
|
+
const blob = await put(file.name, file, {
|
|
395
|
+
access: 'public',
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
return NextResponse.json({ url: blob.url }, { status: 201 });
|
|
399
|
+
}
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
## API Checklist
|
|
403
|
+
|
|
404
|
+
- [ ] RESTful naming conventions
|
|
405
|
+
- [ ] Proper HTTP methods used
|
|
406
|
+
- [ ] Consistent response format
|
|
407
|
+
- [ ] Input validation with Zod
|
|
408
|
+
- [ ] Authentication required
|
|
409
|
+
- [ ] Rate limiting configured
|
|
410
|
+
- [ ] CORS headers set
|
|
411
|
+
- [ ] Error responses standardized
|
|
412
|
+
- [ ] Pagination implemented
|
|
413
|
+
- [ ] API documentation added
|
|
414
|
+
|
|
415
|
+
## Trigger Keywords
|
|
416
|
+
api, rest, endpoint, route, handler, get, post, patch, delete, request, response, json, cors, rate limit, pagination, filter, sort, openapi, swagger, webhook
|