create-specra 0.1.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/LICENSE.MD +21 -0
- package/README.md +137 -0
- package/package.json +42 -0
- package/templates/minimal/README.md +132 -0
- package/templates/minimal/app/api/mdx-watch/route.ts +80 -0
- package/templates/minimal/app/docs/[version]/[...slug]/loading.tsx +7 -0
- package/templates/minimal/app/docs/[version]/[...slug]/page.tsx +212 -0
- package/templates/minimal/app/docs/[version]/not-found.tsx +10 -0
- package/templates/minimal/app/docs/[version]/page.tsx +27 -0
- package/templates/minimal/app/globals.css +1 -0
- package/templates/minimal/app/layout.tsx +89 -0
- package/templates/minimal/app/page.tsx +185 -0
- package/templates/minimal/docs/v1.0.0/about.mdx +57 -0
- package/templates/minimal/docs/v1.0.0/components/_category_.json +8 -0
- package/templates/minimal/docs/v1.0.0/components/callout.mdx +83 -0
- package/templates/minimal/docs/v1.0.0/components/code-block.mdx +103 -0
- package/templates/minimal/docs/v1.0.0/components/index.mdx +8 -0
- package/templates/minimal/docs/v1.0.0/components/tabs.mdx +92 -0
- package/templates/minimal/docs/v1.0.0/configuration.mdx +322 -0
- package/templates/minimal/docs/v1.0.0/features.mdx +197 -0
- package/templates/minimal/docs/v1.0.0/getting-started.mdx +183 -0
- package/templates/minimal/docs/v1.0.0/index.mdx +29 -0
- package/templates/minimal/middleware.ts +23 -0
- package/templates/minimal/next.config.default.mjs +36 -0
- package/templates/minimal/next.config.export.mjs +62 -0
- package/templates/minimal/next.config.mjs +18 -0
- package/templates/minimal/package-lock.json +7338 -0
- package/templates/minimal/package.json +32 -0
- package/templates/minimal/postcss.config.mjs +8 -0
- package/templates/minimal/public/api-specs/openapi-example.json +259 -0
- package/templates/minimal/public/api-specs/postman-example.json +205 -0
- package/templates/minimal/public/api-specs/test-api.json +256 -0
- package/templates/minimal/public/api-specs/users-api.json +264 -0
- package/templates/minimal/scripts/generate-redirects.mjs +88 -0
- package/templates/minimal/scripts/index-search.ts +159 -0
- package/templates/minimal/scripts/test-search.ts +83 -0
- package/templates/minimal/specra.config.json +124 -0
- package/templates/minimal/tsconfig.json +41 -0
- package/templates/minimal/yarn.lock +3909 -0
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "1.0.0",
|
|
3
|
+
"title": "Test API",
|
|
4
|
+
"description": "A simple test API for demonstrating the API documentation system",
|
|
5
|
+
"baseUrl": "https://jsonplaceholder.typicode.com",
|
|
6
|
+
"auth": {
|
|
7
|
+
"type": "apiKey",
|
|
8
|
+
"description": "This is a public API - no authentication required for testing",
|
|
9
|
+
"headerName": "X-API-Key"
|
|
10
|
+
},
|
|
11
|
+
"endpoints": [
|
|
12
|
+
{
|
|
13
|
+
"title": "Get All Posts",
|
|
14
|
+
"method": "GET",
|
|
15
|
+
"path": "/posts",
|
|
16
|
+
"description": "Retrieve a list of all blog posts",
|
|
17
|
+
"queryParams": [
|
|
18
|
+
{
|
|
19
|
+
"name": "_limit",
|
|
20
|
+
"type": "number",
|
|
21
|
+
"description": "Limit the number of results",
|
|
22
|
+
"example": 5
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"name": "userId",
|
|
26
|
+
"type": "number",
|
|
27
|
+
"description": "Filter posts by user ID",
|
|
28
|
+
"example": 1
|
|
29
|
+
}
|
|
30
|
+
],
|
|
31
|
+
"successResponse": {
|
|
32
|
+
"status": 200,
|
|
33
|
+
"description": "List of posts retrieved successfully",
|
|
34
|
+
"example": [
|
|
35
|
+
{
|
|
36
|
+
"userId": 1,
|
|
37
|
+
"id": 1,
|
|
38
|
+
"title": "sunt aut facere repellat provident",
|
|
39
|
+
"body": "quia et suscipit..."
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"userId": 1,
|
|
43
|
+
"id": 2,
|
|
44
|
+
"title": "qui est esse",
|
|
45
|
+
"body": "est rerum tempore vitae..."
|
|
46
|
+
}
|
|
47
|
+
]
|
|
48
|
+
},
|
|
49
|
+
"examples": [
|
|
50
|
+
{
|
|
51
|
+
"title": "cURL",
|
|
52
|
+
"language": "bash",
|
|
53
|
+
"code": "curl https://jsonplaceholder.typicode.com/posts?_limit=5"
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"title": "JavaScript",
|
|
57
|
+
"language": "javascript",
|
|
58
|
+
"code": "const response = await fetch('https://jsonplaceholder.typicode.com/posts?_limit=5');\nconst posts = await response.json();\nconsole.log(posts);"
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"title": "Python",
|
|
62
|
+
"language": "python",
|
|
63
|
+
"code": "import requests\n\nresponse = requests.get('https://jsonplaceholder.typicode.com/posts', params={'_limit': 5})\nposts = response.json()\nprint(posts)"
|
|
64
|
+
}
|
|
65
|
+
]
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"title": "Get Post by ID",
|
|
69
|
+
"method": "GET",
|
|
70
|
+
"path": "/posts/:id",
|
|
71
|
+
"description": "Retrieve a single post by its ID",
|
|
72
|
+
"pathParams": [
|
|
73
|
+
{
|
|
74
|
+
"name": "id",
|
|
75
|
+
"type": "number",
|
|
76
|
+
"required": true,
|
|
77
|
+
"description": "The ID of the post to retrieve",
|
|
78
|
+
"example": 1
|
|
79
|
+
}
|
|
80
|
+
],
|
|
81
|
+
"successResponse": {
|
|
82
|
+
"status": 200,
|
|
83
|
+
"description": "Post retrieved successfully",
|
|
84
|
+
"example": {
|
|
85
|
+
"userId": 1,
|
|
86
|
+
"id": 1,
|
|
87
|
+
"title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
|
|
88
|
+
"body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
"errorResponses": [
|
|
92
|
+
{
|
|
93
|
+
"status": 404,
|
|
94
|
+
"description": "Post not found",
|
|
95
|
+
"example": {}
|
|
96
|
+
}
|
|
97
|
+
]
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
"title": "Create New Post",
|
|
101
|
+
"method": "POST",
|
|
102
|
+
"path": "/posts",
|
|
103
|
+
"description": "Create a new blog post",
|
|
104
|
+
"body": {
|
|
105
|
+
"description": "Post data to create",
|
|
106
|
+
"example": {
|
|
107
|
+
"title": "My New Post",
|
|
108
|
+
"body": "This is the content of my new post",
|
|
109
|
+
"userId": 1
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
"successResponse": {
|
|
113
|
+
"status": 201,
|
|
114
|
+
"description": "Post created successfully",
|
|
115
|
+
"example": {
|
|
116
|
+
"id": 101,
|
|
117
|
+
"title": "My New Post",
|
|
118
|
+
"body": "This is the content of my new post",
|
|
119
|
+
"userId": 1
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
"examples": [
|
|
123
|
+
{
|
|
124
|
+
"title": "cURL",
|
|
125
|
+
"language": "bash",
|
|
126
|
+
"code": "curl -X POST https://jsonplaceholder.typicode.com/posts \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"title\": \"My New Post\",\n \"body\": \"This is the content\",\n \"userId\": 1\n }'"
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
"title": "JavaScript",
|
|
130
|
+
"language": "javascript",
|
|
131
|
+
"code": "const response = await fetch('https://jsonplaceholder.typicode.com/posts', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n title: 'My New Post',\n body: 'This is the content',\n userId: 1\n })\n});\nconst post = await response.json();\nconsole.log(post);"
|
|
132
|
+
}
|
|
133
|
+
]
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
"title": "Update Post",
|
|
137
|
+
"method": "PUT",
|
|
138
|
+
"path": "/posts/:id",
|
|
139
|
+
"description": "Update an existing post (replaces all fields)",
|
|
140
|
+
"pathParams": [
|
|
141
|
+
{
|
|
142
|
+
"name": "id",
|
|
143
|
+
"type": "number",
|
|
144
|
+
"required": true,
|
|
145
|
+
"description": "The ID of the post to update",
|
|
146
|
+
"example": 1
|
|
147
|
+
}
|
|
148
|
+
],
|
|
149
|
+
"body": {
|
|
150
|
+
"description": "Complete post data",
|
|
151
|
+
"example": {
|
|
152
|
+
"id": 1,
|
|
153
|
+
"title": "Updated Title",
|
|
154
|
+
"body": "Updated content",
|
|
155
|
+
"userId": 1
|
|
156
|
+
}
|
|
157
|
+
},
|
|
158
|
+
"successResponse": {
|
|
159
|
+
"status": 200,
|
|
160
|
+
"description": "Post updated successfully",
|
|
161
|
+
"example": {
|
|
162
|
+
"id": 1,
|
|
163
|
+
"title": "Updated Title",
|
|
164
|
+
"body": "Updated content",
|
|
165
|
+
"userId": 1
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
"title": "Partially Update Post",
|
|
171
|
+
"method": "PATCH",
|
|
172
|
+
"path": "/posts/:id",
|
|
173
|
+
"description": "Partially update a post (only updates provided fields)",
|
|
174
|
+
"pathParams": [
|
|
175
|
+
{
|
|
176
|
+
"name": "id",
|
|
177
|
+
"type": "number",
|
|
178
|
+
"required": true,
|
|
179
|
+
"description": "The ID of the post to update",
|
|
180
|
+
"example": 1
|
|
181
|
+
}
|
|
182
|
+
],
|
|
183
|
+
"body": {
|
|
184
|
+
"description": "Fields to update (all optional)",
|
|
185
|
+
"example": {
|
|
186
|
+
"title": "New Title Only"
|
|
187
|
+
}
|
|
188
|
+
},
|
|
189
|
+
"successResponse": {
|
|
190
|
+
"status": 200,
|
|
191
|
+
"description": "Post updated successfully",
|
|
192
|
+
"example": {
|
|
193
|
+
"userId": 1,
|
|
194
|
+
"id": 1,
|
|
195
|
+
"title": "New Title Only",
|
|
196
|
+
"body": "quia et suscipit..."
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
"title": "Delete Post",
|
|
202
|
+
"method": "DELETE",
|
|
203
|
+
"path": "/posts/:id",
|
|
204
|
+
"description": "Delete a post",
|
|
205
|
+
"pathParams": [
|
|
206
|
+
{
|
|
207
|
+
"name": "id",
|
|
208
|
+
"type": "number",
|
|
209
|
+
"required": true,
|
|
210
|
+
"description": "The ID of the post to delete",
|
|
211
|
+
"example": 1
|
|
212
|
+
}
|
|
213
|
+
],
|
|
214
|
+
"successResponse": {
|
|
215
|
+
"status": 200,
|
|
216
|
+
"description": "Post deleted successfully",
|
|
217
|
+
"example": {}
|
|
218
|
+
}
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
"title": "Get Comments for Post",
|
|
222
|
+
"method": "GET",
|
|
223
|
+
"path": "/posts/:id/comments",
|
|
224
|
+
"description": "Retrieve all comments for a specific post",
|
|
225
|
+
"pathParams": [
|
|
226
|
+
{
|
|
227
|
+
"name": "id",
|
|
228
|
+
"type": "number",
|
|
229
|
+
"required": true,
|
|
230
|
+
"description": "The post ID",
|
|
231
|
+
"example": 1
|
|
232
|
+
}
|
|
233
|
+
],
|
|
234
|
+
"successResponse": {
|
|
235
|
+
"status": 200,
|
|
236
|
+
"description": "Comments retrieved successfully",
|
|
237
|
+
"example": [
|
|
238
|
+
{
|
|
239
|
+
"postId": 1,
|
|
240
|
+
"id": 1,
|
|
241
|
+
"name": "id labore ex et quam laborum",
|
|
242
|
+
"email": "Eliseo@gardner.biz",
|
|
243
|
+
"body": "laudantium enim quasi est quidem magnam..."
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
"postId": 1,
|
|
247
|
+
"id": 2,
|
|
248
|
+
"name": "quo vero reiciendis velit similique earum",
|
|
249
|
+
"email": "Jayne_Kuhic@sydney.com",
|
|
250
|
+
"body": "est natus enim nihil est dolore omnis..."
|
|
251
|
+
}
|
|
252
|
+
]
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
]
|
|
256
|
+
}
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "1.0.0",
|
|
3
|
+
"title": "Users API",
|
|
4
|
+
"description": "Complete API reference for managing users in the system",
|
|
5
|
+
"baseUrl": "https://api.example.com/v1",
|
|
6
|
+
"env": {
|
|
7
|
+
"AUTH_TOKEN": "your_api_token_here"
|
|
8
|
+
},
|
|
9
|
+
"auth": {
|
|
10
|
+
"type": "bearer",
|
|
11
|
+
"description": "All requests require a valid API token in the Authorization header",
|
|
12
|
+
"tokenPrefix": "Bearer"
|
|
13
|
+
},
|
|
14
|
+
"globalHeaders": [
|
|
15
|
+
{
|
|
16
|
+
"name": "Authorization",
|
|
17
|
+
"value": "Bearer {AUTH_TOKEN}",
|
|
18
|
+
"description": "Your API authentication token"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"name": "Content-Type",
|
|
22
|
+
"value": "application/json"
|
|
23
|
+
}
|
|
24
|
+
],
|
|
25
|
+
"endpoints": [
|
|
26
|
+
{
|
|
27
|
+
"title": "Get User by ID",
|
|
28
|
+
"method": "GET",
|
|
29
|
+
"path": "/users/:id",
|
|
30
|
+
"description": "Retrieve detailed information about a specific user",
|
|
31
|
+
"pathParams": [
|
|
32
|
+
{
|
|
33
|
+
"name": "id",
|
|
34
|
+
"type": "string",
|
|
35
|
+
"required": true,
|
|
36
|
+
"description": "The unique identifier of the user",
|
|
37
|
+
"example": "user_123"
|
|
38
|
+
}
|
|
39
|
+
],
|
|
40
|
+
"queryParams": [
|
|
41
|
+
{
|
|
42
|
+
"name": "fields",
|
|
43
|
+
"type": "string",
|
|
44
|
+
"description": "Comma-separated list of fields to include",
|
|
45
|
+
"example": "name,email,created_at"
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"name": "include_metadata",
|
|
49
|
+
"type": "boolean",
|
|
50
|
+
"description": "Include additional metadata in the response",
|
|
51
|
+
"default": false
|
|
52
|
+
}
|
|
53
|
+
],
|
|
54
|
+
"successResponse": {
|
|
55
|
+
"status": 200,
|
|
56
|
+
"description": "User retrieved successfully",
|
|
57
|
+
"example": {
|
|
58
|
+
"id": "user_123",
|
|
59
|
+
"name": "John Doe",
|
|
60
|
+
"email": "john@example.com",
|
|
61
|
+
"role": "admin",
|
|
62
|
+
"created_at": "2024-01-15T10:30:00Z",
|
|
63
|
+
"updated_at": "2024-01-20T14:22:00Z"
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
"errorResponses": [
|
|
67
|
+
{
|
|
68
|
+
"status": 404,
|
|
69
|
+
"description": "User not found",
|
|
70
|
+
"example": {
|
|
71
|
+
"error": "User not found",
|
|
72
|
+
"code": "USER_NOT_FOUND",
|
|
73
|
+
"message": "No user exists with the provided ID"
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
"status": 401,
|
|
78
|
+
"description": "Unauthorized",
|
|
79
|
+
"example": {
|
|
80
|
+
"error": "Unauthorized",
|
|
81
|
+
"code": "UNAUTHORIZED",
|
|
82
|
+
"message": "Invalid or missing authentication token"
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
],
|
|
86
|
+
"examples": [
|
|
87
|
+
{
|
|
88
|
+
"title": "cURL",
|
|
89
|
+
"language": "bash",
|
|
90
|
+
"code": "curl -X GET \"https://api.example.com/v1/users/user_123\" \\\n -H \"Authorization: Bearer your_api_token_here\""
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
"title": "JavaScript (fetch)",
|
|
94
|
+
"language": "javascript",
|
|
95
|
+
"code": "const response = await fetch('https://api.example.com/v1/users/user_123', {\n headers: {\n 'Authorization': 'Bearer your_api_token_here'\n }\n});\nconst user = await response.json();"
|
|
96
|
+
}
|
|
97
|
+
]
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
"title": "List All Users",
|
|
101
|
+
"method": "GET",
|
|
102
|
+
"path": "/users",
|
|
103
|
+
"description": "Retrieve a paginated list of all users",
|
|
104
|
+
"queryParams": [
|
|
105
|
+
{
|
|
106
|
+
"name": "page",
|
|
107
|
+
"type": "number",
|
|
108
|
+
"description": "Page number for pagination",
|
|
109
|
+
"default": 1
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
"name": "limit",
|
|
113
|
+
"type": "number",
|
|
114
|
+
"description": "Number of users per page",
|
|
115
|
+
"default": 20
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
"name": "role",
|
|
119
|
+
"type": "string",
|
|
120
|
+
"description": "Filter users by role",
|
|
121
|
+
"example": "admin"
|
|
122
|
+
}
|
|
123
|
+
],
|
|
124
|
+
"successResponse": {
|
|
125
|
+
"status": 200,
|
|
126
|
+
"description": "List of users retrieved successfully",
|
|
127
|
+
"example": {
|
|
128
|
+
"data": [
|
|
129
|
+
{
|
|
130
|
+
"id": "user_123",
|
|
131
|
+
"name": "John Doe",
|
|
132
|
+
"email": "john@example.com",
|
|
133
|
+
"role": "admin"
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
"id": "user_124",
|
|
137
|
+
"name": "Jane Smith",
|
|
138
|
+
"email": "jane@example.com",
|
|
139
|
+
"role": "user"
|
|
140
|
+
}
|
|
141
|
+
],
|
|
142
|
+
"pagination": {
|
|
143
|
+
"page": 1,
|
|
144
|
+
"limit": 20,
|
|
145
|
+
"total": 42,
|
|
146
|
+
"pages": 3
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
"title": "Create New User",
|
|
153
|
+
"method": "POST",
|
|
154
|
+
"path": "/users",
|
|
155
|
+
"description": "Create a new user account",
|
|
156
|
+
"body": {
|
|
157
|
+
"description": "User data to create",
|
|
158
|
+
"example": {
|
|
159
|
+
"name": "Alice Johnson",
|
|
160
|
+
"email": "alice@example.com",
|
|
161
|
+
"role": "user",
|
|
162
|
+
"password": "secure_password_123"
|
|
163
|
+
}
|
|
164
|
+
},
|
|
165
|
+
"successResponse": {
|
|
166
|
+
"status": 201,
|
|
167
|
+
"description": "User created successfully",
|
|
168
|
+
"example": {
|
|
169
|
+
"id": "user_125",
|
|
170
|
+
"name": "Alice Johnson",
|
|
171
|
+
"email": "alice@example.com",
|
|
172
|
+
"role": "user",
|
|
173
|
+
"created_at": "2024-01-21T09:15:00Z"
|
|
174
|
+
}
|
|
175
|
+
},
|
|
176
|
+
"errorResponses": [
|
|
177
|
+
{
|
|
178
|
+
"status": 400,
|
|
179
|
+
"description": "Validation error",
|
|
180
|
+
"example": {
|
|
181
|
+
"error": "Validation failed",
|
|
182
|
+
"code": "VALIDATION_ERROR",
|
|
183
|
+
"details": {
|
|
184
|
+
"email": "Email address already exists"
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
],
|
|
189
|
+
"examples": [
|
|
190
|
+
{
|
|
191
|
+
"title": "cURL",
|
|
192
|
+
"language": "bash",
|
|
193
|
+
"code": "curl -X POST \"https://api.example.com/v1/users\" \\\n -H \"Authorization: Bearer your_api_token_here\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"name\": \"Alice Johnson\",\n \"email\": \"alice@example.com\",\n \"role\": \"user\",\n \"password\": \"secure_password_123\"\n }'"
|
|
194
|
+
}
|
|
195
|
+
]
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
"title": "Update User",
|
|
199
|
+
"method": "PATCH",
|
|
200
|
+
"path": "/users/:id",
|
|
201
|
+
"description": "Update user information",
|
|
202
|
+
"pathParams": [
|
|
203
|
+
{
|
|
204
|
+
"name": "id",
|
|
205
|
+
"type": "string",
|
|
206
|
+
"required": true,
|
|
207
|
+
"description": "User ID to update"
|
|
208
|
+
}
|
|
209
|
+
],
|
|
210
|
+
"body": {
|
|
211
|
+
"description": "Fields to update (all optional)",
|
|
212
|
+
"example": {
|
|
213
|
+
"name": "John Updated",
|
|
214
|
+
"email": "john.updated@example.com",
|
|
215
|
+
"role": "moderator"
|
|
216
|
+
}
|
|
217
|
+
},
|
|
218
|
+
"successResponse": {
|
|
219
|
+
"status": 200,
|
|
220
|
+
"description": "User updated successfully",
|
|
221
|
+
"example": {
|
|
222
|
+
"id": "user_123",
|
|
223
|
+
"name": "John Updated",
|
|
224
|
+
"email": "john.updated@example.com",
|
|
225
|
+
"role": "moderator",
|
|
226
|
+
"updated_at": "2024-01-21T10:00:00Z"
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
"title": "Delete User",
|
|
232
|
+
"method": "DELETE",
|
|
233
|
+
"path": "/users/:id",
|
|
234
|
+
"description": "Permanently delete a user account",
|
|
235
|
+
"pathParams": [
|
|
236
|
+
{
|
|
237
|
+
"name": "id",
|
|
238
|
+
"type": "string",
|
|
239
|
+
"required": true,
|
|
240
|
+
"description": "User ID to delete"
|
|
241
|
+
}
|
|
242
|
+
],
|
|
243
|
+
"successResponse": {
|
|
244
|
+
"status": 204,
|
|
245
|
+
"description": "User deleted successfully"
|
|
246
|
+
},
|
|
247
|
+
"errorResponses": [
|
|
248
|
+
{
|
|
249
|
+
"status": 404,
|
|
250
|
+
"description": "User not found"
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
"status": 403,
|
|
254
|
+
"description": "Forbidden - Cannot delete this user",
|
|
255
|
+
"example": {
|
|
256
|
+
"error": "Forbidden",
|
|
257
|
+
"code": "CANNOT_DELETE_ADMIN",
|
|
258
|
+
"message": "Cannot delete the primary admin account"
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
]
|
|
262
|
+
}
|
|
263
|
+
]
|
|
264
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import fs from "fs"
|
|
2
|
+
import path from "path"
|
|
3
|
+
import matter from "gray-matter"
|
|
4
|
+
import { fileURLToPath } from "url"
|
|
5
|
+
|
|
6
|
+
const __filename = fileURLToPath(import.meta.url)
|
|
7
|
+
const __dirname = path.dirname(__filename)
|
|
8
|
+
|
|
9
|
+
const DOCS_DIR = path.join(__dirname, "..", "docs")
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Recursively find all MDX files
|
|
13
|
+
*/
|
|
14
|
+
function findMdxFiles(dir, baseDir = dir) {
|
|
15
|
+
const files = []
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true })
|
|
19
|
+
|
|
20
|
+
for (const entry of entries) {
|
|
21
|
+
const fullPath = path.join(dir, entry.name)
|
|
22
|
+
|
|
23
|
+
if (entry.isDirectory()) {
|
|
24
|
+
files.push(...findMdxFiles(fullPath, baseDir))
|
|
25
|
+
} else if (entry.isFile() && entry.name.endsWith(".mdx")) {
|
|
26
|
+
const relativePath = path.relative(baseDir, fullPath)
|
|
27
|
+
files.push(relativePath)
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
} catch (error) {
|
|
31
|
+
console.error(`Error reading directory ${dir}:`, error)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return files
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Build redirect mappings from frontmatter
|
|
39
|
+
*/
|
|
40
|
+
async function buildRedirects() {
|
|
41
|
+
const versions = fs.readdirSync(DOCS_DIR).filter((v) => {
|
|
42
|
+
const stat = fs.statSync(path.join(DOCS_DIR, v))
|
|
43
|
+
return stat.isDirectory()
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
const redirects = []
|
|
47
|
+
|
|
48
|
+
for (const version of versions) {
|
|
49
|
+
const versionDir = path.join(DOCS_DIR, version)
|
|
50
|
+
const mdxFiles = findMdxFiles(versionDir)
|
|
51
|
+
|
|
52
|
+
for (const file of mdxFiles) {
|
|
53
|
+
const filePath = path.join(versionDir, file)
|
|
54
|
+
const fileContents = fs.readFileSync(filePath, "utf8")
|
|
55
|
+
const { data } = matter(fileContents)
|
|
56
|
+
|
|
57
|
+
if (data.redirect_from && Array.isArray(data.redirect_from)) {
|
|
58
|
+
const slug = file.replace(/\.mdx$/, "")
|
|
59
|
+
const destination = `/docs/${version}/${slug}`
|
|
60
|
+
|
|
61
|
+
for (const source of data.redirect_from) {
|
|
62
|
+
redirects.push({
|
|
63
|
+
source,
|
|
64
|
+
destination,
|
|
65
|
+
permanent: true,
|
|
66
|
+
})
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return redirects
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Generate redirects and write to a JSON file
|
|
77
|
+
*/
|
|
78
|
+
async function main() {
|
|
79
|
+
const redirects = await buildRedirects()
|
|
80
|
+
|
|
81
|
+
const outputPath = path.join(__dirname, "..", "redirects.json")
|
|
82
|
+
fs.writeFileSync(outputPath, JSON.stringify(redirects, null, 2))
|
|
83
|
+
|
|
84
|
+
console.log(`✅ Generated ${redirects.length} redirects`)
|
|
85
|
+
console.log(`📝 Saved to: ${outputPath}`)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
main().catch(console.error)
|