@hedystia/swagger 1.10.0 → 1.10.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_virtual/_rolldown/runtime.cjs +23 -0
- package/dist/index.cjs +3 -0
- package/dist/index.d.cts +2 -0
- package/dist/index.d.mts +2 -0
- package/dist/index.mjs +2 -0
- package/dist/plugin.cjs +41 -0
- package/dist/plugin.cjs.map +1 -0
- package/dist/plugin.d.cts +32 -0
- package/dist/plugin.d.mts +32 -0
- package/dist/plugin.mjs +40 -0
- package/dist/plugin.mjs.map +1 -0
- package/dist/swagger.cjs +751 -0
- package/dist/swagger.cjs.map +1 -0
- package/dist/swagger.d.cts +37 -0
- package/dist/swagger.d.mts +37 -0
- package/dist/{index.js → swagger.mjs} +271 -67
- package/dist/swagger.mjs.map +1 -0
- package/package.json +28 -12
- package/readme.md +5 -5
- package/dist/index.d.ts +0 -62
package/dist/swagger.cjs
ADDED
|
@@ -0,0 +1,751 @@
|
|
|
1
|
+
const require_runtime = require("./_virtual/_rolldown/runtime.cjs");
|
|
2
|
+
let _apidevtools_swagger_parser = require("@apidevtools/swagger-parser");
|
|
3
|
+
_apidevtools_swagger_parser = require_runtime.__toESM(_apidevtools_swagger_parser);
|
|
4
|
+
//#region src/swagger.ts
|
|
5
|
+
var Swagger = class {
|
|
6
|
+
spec = {
|
|
7
|
+
openapi: "3.0.0",
|
|
8
|
+
info: {
|
|
9
|
+
title: "API Documentation",
|
|
10
|
+
version: "1.0.0"
|
|
11
|
+
},
|
|
12
|
+
paths: {},
|
|
13
|
+
components: { schemas: {} }
|
|
14
|
+
};
|
|
15
|
+
host;
|
|
16
|
+
constructor(options = {}) {
|
|
17
|
+
this.spec.info.title = options.title || "API Documentation";
|
|
18
|
+
this.spec.info.description = options.description;
|
|
19
|
+
this.spec.info.version = options.version || "1.0.0";
|
|
20
|
+
this.host = options.host || "https://example.com";
|
|
21
|
+
if (options.tags) this.spec.tags = options.tags;
|
|
22
|
+
if (options.externalDocs) this.spec.externalDocs = options.externalDocs;
|
|
23
|
+
if (options.securityDefinitions) {
|
|
24
|
+
this.spec.components = this.spec.components || {};
|
|
25
|
+
this.spec.components.securitySchemes = options.securityDefinitions;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
addRoute(method, path, schema, summary, description, tags) {
|
|
29
|
+
if (!schema) return;
|
|
30
|
+
const normalizedPath = path.replace(/:([^/]+)/g, "{$1}");
|
|
31
|
+
if (!this.spec.paths) this.spec.paths = {};
|
|
32
|
+
if (!this.spec.paths[normalizedPath]) this.spec.paths[normalizedPath] = {};
|
|
33
|
+
const methodLower = method.toLowerCase();
|
|
34
|
+
const operationObject = {
|
|
35
|
+
summary: summary || `${method} ${path}`,
|
|
36
|
+
parameters: this.buildParameters(schema),
|
|
37
|
+
requestBody: this.buildRequestBody(schema),
|
|
38
|
+
responses: this.buildResponses(schema)
|
|
39
|
+
};
|
|
40
|
+
if (description) operationObject.description = description;
|
|
41
|
+
if (tags) operationObject.tags = tags;
|
|
42
|
+
this.spec.paths[normalizedPath][methodLower] = operationObject;
|
|
43
|
+
}
|
|
44
|
+
buildParameters(schema) {
|
|
45
|
+
const parameters = [];
|
|
46
|
+
if (schema.params) try {
|
|
47
|
+
const jsonSchema = schema.params;
|
|
48
|
+
if (jsonSchema.properties) Object.entries(jsonSchema.properties).forEach(([name, propSchema]) => {
|
|
49
|
+
parameters.push({
|
|
50
|
+
name,
|
|
51
|
+
in: "path",
|
|
52
|
+
required: jsonSchema.required?.includes(name) ?? true,
|
|
53
|
+
schema: propSchema
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
} catch (e) {
|
|
57
|
+
console.error("Failed to convert params schema:", e);
|
|
58
|
+
}
|
|
59
|
+
if (schema.query) try {
|
|
60
|
+
const jsonSchema = schema.query;
|
|
61
|
+
if (jsonSchema.properties) Object.entries(jsonSchema.properties).forEach(([name, propSchema]) => {
|
|
62
|
+
parameters.push({
|
|
63
|
+
name,
|
|
64
|
+
in: "query",
|
|
65
|
+
required: jsonSchema.required?.includes(name) ?? false,
|
|
66
|
+
schema: propSchema
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
} catch (e) {
|
|
70
|
+
console.error("Failed to convert query schema:", e);
|
|
71
|
+
}
|
|
72
|
+
return parameters.length > 0 ? parameters : void 0;
|
|
73
|
+
}
|
|
74
|
+
buildRequestBody(schema) {
|
|
75
|
+
if (!schema.body) return;
|
|
76
|
+
try {
|
|
77
|
+
const jsonSchema = schema.body;
|
|
78
|
+
return {
|
|
79
|
+
required: true,
|
|
80
|
+
content: { "application/json": { schema: jsonSchema } }
|
|
81
|
+
};
|
|
82
|
+
} catch (e) {
|
|
83
|
+
console.error("Failed to convert body schema:", e);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
buildResponses(schema) {
|
|
88
|
+
const responses = { "200": { description: "Successful response" } };
|
|
89
|
+
if (schema.response) try {
|
|
90
|
+
const jsonSchema = schema.response;
|
|
91
|
+
responses["200"].content = { "application/json": { schema: jsonSchema } };
|
|
92
|
+
} catch (e) {
|
|
93
|
+
console.error("Failed to convert response schema:", e);
|
|
94
|
+
}
|
|
95
|
+
return responses;
|
|
96
|
+
}
|
|
97
|
+
async validate() {
|
|
98
|
+
try {
|
|
99
|
+
await _apidevtools_swagger_parser.default.validate(this.spec);
|
|
100
|
+
return true;
|
|
101
|
+
} catch (error) {
|
|
102
|
+
console.error("Swagger validation error:", error);
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
getSpec() {
|
|
107
|
+
return this.spec;
|
|
108
|
+
}
|
|
109
|
+
generateHTML() {
|
|
110
|
+
const hostname = new URL(this.host).hostname;
|
|
111
|
+
const extractJsonSchema = (schema) => {
|
|
112
|
+
if (!schema) return {};
|
|
113
|
+
if (schema.jsonSchema) return schema.jsonSchema;
|
|
114
|
+
if (schema.type || schema.properties || schema.$ref) return schema;
|
|
115
|
+
return {};
|
|
116
|
+
};
|
|
117
|
+
const groupedPaths = {};
|
|
118
|
+
const defaultGroup = "API";
|
|
119
|
+
Object.entries(this.spec.paths || {}).forEach(([path, pathItem]) => {
|
|
120
|
+
Object.entries(pathItem).forEach(([method, operation]) => {
|
|
121
|
+
const primaryTag = (operation.tags || [defaultGroup])[0];
|
|
122
|
+
if (!groupedPaths[primaryTag]) groupedPaths[primaryTag] = [];
|
|
123
|
+
groupedPaths[primaryTag].push({
|
|
124
|
+
path,
|
|
125
|
+
method: method.toUpperCase(),
|
|
126
|
+
operation
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
const sidebarSections = Object.entries(groupedPaths).map(([tag, endpoints]) => {
|
|
131
|
+
const endpointCount = endpoints.length;
|
|
132
|
+
const endpointLinks = endpoints.map(({ path, method }) => {
|
|
133
|
+
return `<li><a href="#${`${method.toLowerCase()}-${path.replace(/[^a-zA-Z0-9]/g, "-")}`}" class="block px-3 py-1.5 rounded-lg hover:bg-dark-700/50 transition-colors text-sm text-gray-400 hover:text-white">${method} ${path}</a></li>`;
|
|
134
|
+
}).join("\n ");
|
|
135
|
+
return `
|
|
136
|
+
<div>
|
|
137
|
+
<button class="sidebar-group-toggle w-full flex items-center justify-between px-3 py-2 hover:bg-dark-700/30 rounded-lg transition-colors group" data-target="${tag.toLowerCase()}-group">
|
|
138
|
+
<div class="flex items-center space-x-2">
|
|
139
|
+
<h3 class="text-sm font-medium text-gray-300 group-hover:text-white">${tag}</h3>
|
|
140
|
+
<span class="text-xs text-gray-500 bg-dark-600 px-2 py-0.5 rounded-full">${endpointCount}</span>
|
|
141
|
+
<svg class="w-4 h-4 text-gray-400 transform transition-transform duration-200 group-hover:text-gray-300" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
142
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
|
|
143
|
+
</svg>
|
|
144
|
+
</div>
|
|
145
|
+
</button>
|
|
146
|
+
<div id="${tag.toLowerCase()}-group" class="sidebar-group-content max-h-0 overflow-hidden transition-all duration-300">
|
|
147
|
+
<ul class="ml-6 mt-1 space-y-1">
|
|
148
|
+
${endpointLinks}
|
|
149
|
+
</ul>
|
|
150
|
+
</div>
|
|
151
|
+
</div>
|
|
152
|
+
|
|
153
|
+
<div class="border-t border-dark-600 my-3"></div>`;
|
|
154
|
+
}).join("");
|
|
155
|
+
const contentSections = Object.entries(groupedPaths).map(([tag, endpoints]) => {
|
|
156
|
+
const tagDescription = this.spec.tags?.find((t) => t.name === tag)?.description || `Manage ${tag.toLowerCase()} operations`;
|
|
157
|
+
const endpointCards = endpoints.map(({ path, method, operation }) => {
|
|
158
|
+
return `
|
|
159
|
+
<a href="#${`${method.toLowerCase()}-${path.replace(/[^a-zA-Z0-9]/g, "-")}`}" class="block p-4 rounded-lg border border-dark-600 hover:border-blue-500 hover:bg-dark-700/30 transition-all group">
|
|
160
|
+
<div class="flex items-center justify-between mb-2">
|
|
161
|
+
<div class="flex items-center space-x-3">
|
|
162
|
+
<span class="${{
|
|
163
|
+
GET: "bg-green-600",
|
|
164
|
+
POST: "bg-blue-600",
|
|
165
|
+
PUT: "bg-yellow-600",
|
|
166
|
+
DELETE: "bg-red-600",
|
|
167
|
+
PATCH: "bg-purple-600",
|
|
168
|
+
WS: "bg-indigo-600",
|
|
169
|
+
SUB: "bg-pink-600"
|
|
170
|
+
}[method] || "bg-gray-600"} text-white px-3 py-1 rounded-lg text-sm font-medium">${method}</span>
|
|
171
|
+
<code class="text-blue-400 font-mono">${path}</code>
|
|
172
|
+
</div>
|
|
173
|
+
<svg class="w-5 h-5 text-gray-400 group-hover:text-blue-400 transition-colors" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
174
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
|
|
175
|
+
</svg>
|
|
176
|
+
</div>
|
|
177
|
+
<p class="text-gray-300 text-sm">${operation.summary || operation.description || `${method} ${path} operation`}</p>
|
|
178
|
+
</a>`;
|
|
179
|
+
}).join("\n");
|
|
180
|
+
const detailedEndpoints = endpoints.map(({ path, method, operation }) => {
|
|
181
|
+
const operationId = `${method.toLowerCase()}-${path.replace(/[^a-zA-Z0-9]/g, "-")}`;
|
|
182
|
+
const methodColor = {
|
|
183
|
+
GET: "bg-green-600",
|
|
184
|
+
POST: "bg-blue-600",
|
|
185
|
+
PUT: "bg-yellow-600",
|
|
186
|
+
DELETE: "bg-red-600",
|
|
187
|
+
PATCH: "bg-purple-600",
|
|
188
|
+
WS: "bg-indigo-600",
|
|
189
|
+
SUB: "bg-pink-600"
|
|
190
|
+
}[method] || "bg-gray-600";
|
|
191
|
+
let parametersSection = "";
|
|
192
|
+
if (operation.parameters && operation.parameters.length > 0) {
|
|
193
|
+
const pathParams = operation.parameters.filter((p) => p.in === "path");
|
|
194
|
+
const queryParams = operation.parameters.filter((p) => p.in === "query");
|
|
195
|
+
if (pathParams.length > 0) parametersSection += `
|
|
196
|
+
<div>
|
|
197
|
+
<h4 class="text-lg font-semibold text-white mb-3">Path Parameters</h4>
|
|
198
|
+
<div class="space-y-3">
|
|
199
|
+
${pathParams.map((param) => `
|
|
200
|
+
<div class="border border-dark-600 rounded-lg p-3">
|
|
201
|
+
<div class="flex items-center justify-between mb-1">
|
|
202
|
+
<code class="text-blue-400">${param.name}</code>
|
|
203
|
+
<span class="text-xs ${param.required ? "text-red-400" : "text-gray-400"}">${param.required ? "required" : "optional"}</span>
|
|
204
|
+
</div>
|
|
205
|
+
<p class="text-sm text-gray-300">${param.description || `${param.name} parameter`}</p>
|
|
206
|
+
</div>`).join("")}
|
|
207
|
+
</div>
|
|
208
|
+
</div>`;
|
|
209
|
+
if (queryParams.length > 0) parametersSection += `
|
|
210
|
+
<div>
|
|
211
|
+
<h4 class="text-lg font-semibold text-white mb-3">Query Parameters</h4>
|
|
212
|
+
<div class="space-y-3">
|
|
213
|
+
${queryParams.map((param) => `
|
|
214
|
+
<div class="border border-dark-600 rounded-lg p-3">
|
|
215
|
+
<div class="flex items-center justify-between mb-1">
|
|
216
|
+
<code class="text-blue-400">${param.name}</code>
|
|
217
|
+
<span class="text-xs ${param.required ? "text-red-400" : "text-gray-400"}">${param.required ? "required" : "optional"}</span>
|
|
218
|
+
</div>
|
|
219
|
+
<p class="text-sm text-gray-300">${param.description || `${param.name} parameter`}</p>
|
|
220
|
+
</div>`).join("")}
|
|
221
|
+
</div>
|
|
222
|
+
</div>`;
|
|
223
|
+
}
|
|
224
|
+
let requestBodySection = "";
|
|
225
|
+
if (operation.requestBody) {
|
|
226
|
+
const schema = operation.requestBody.content?.["application/json"]?.schema;
|
|
227
|
+
const cleanSchema = extractJsonSchema(schema);
|
|
228
|
+
if (cleanSchema.properties) requestBodySection = `
|
|
229
|
+
<div>
|
|
230
|
+
<h4 class="text-lg font-semibold text-white mb-3">Body Parameters</h4>
|
|
231
|
+
<div class="space-y-3">
|
|
232
|
+
${Object.entries(cleanSchema.properties).map(([propName, propSchema]) => `
|
|
233
|
+
<div class="border border-dark-600 rounded-lg p-3">
|
|
234
|
+
<div class="flex items-center justify-between mb-1">
|
|
235
|
+
<code class="text-blue-400">${propName}</code>
|
|
236
|
+
<span class="text-xs ${cleanSchema.required?.includes(propName) ? "text-red-400" : "text-gray-400"}">${cleanSchema.required?.includes(propName) ? "required" : "optional"}</span>
|
|
237
|
+
</div>
|
|
238
|
+
<p class="text-sm text-gray-300">${propSchema.description || `${propName} field`}</p>
|
|
239
|
+
</div>`).join("")}
|
|
240
|
+
</div>
|
|
241
|
+
</div>`;
|
|
242
|
+
}
|
|
243
|
+
const baseUrl = `https://${hostname}`;
|
|
244
|
+
let requestExample = "";
|
|
245
|
+
if (method !== "WS" && method !== "SUB") {
|
|
246
|
+
let curlCommand = `curl -X ${method} '${baseUrl}${path}'`;
|
|
247
|
+
if (operation.requestBody) {
|
|
248
|
+
curlCommand += ` \\\n -H 'Content-Type: application/json'`;
|
|
249
|
+
const schema = operation.requestBody.content?.["application/json"]?.schema;
|
|
250
|
+
const cleanSchema = extractJsonSchema(schema);
|
|
251
|
+
if (cleanSchema.properties) {
|
|
252
|
+
const exampleData = {};
|
|
253
|
+
Object.entries(cleanSchema.properties).forEach(([propName, propSchema]) => {
|
|
254
|
+
if (propSchema.type === "string") exampleData[propName] = propSchema.format === "email" ? "user@example.com" : `example ${propName}`;
|
|
255
|
+
else if (propSchema.type === "number") exampleData[propName] = 123;
|
|
256
|
+
else if (propSchema.type === "boolean") exampleData[propName] = true;
|
|
257
|
+
else exampleData[propName] = `example ${propName}`;
|
|
258
|
+
});
|
|
259
|
+
curlCommand += ` \\\n -d '${JSON.stringify(exampleData, null, 2).replace(/\n/g, "\\n ")}'`;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
curlCommand += ` \\\n -H 'Authorization: Bearer your-token'`;
|
|
263
|
+
requestExample = `
|
|
264
|
+
<div>
|
|
265
|
+
<div class="flex items-center justify-between mb-4">
|
|
266
|
+
<h4 class="text-lg font-semibold text-white">Request Example</h4>
|
|
267
|
+
<button class="copy-btn bg-dark-700 hover:bg-dark-600 px-3 py-1 rounded-lg text-sm transition-colors" data-copy="${curlCommand.replace(/'/g, "\\'")}">
|
|
268
|
+
Copy
|
|
269
|
+
</button>
|
|
270
|
+
</div>
|
|
271
|
+
<div class="code-block rounded-xl p-4 border border-dark-600">
|
|
272
|
+
<pre class="text-sm text-gray-300 overflow-x-auto"><code>${curlCommand}</code></pre>
|
|
273
|
+
</div>
|
|
274
|
+
</div>`;
|
|
275
|
+
}
|
|
276
|
+
let responseSection = "";
|
|
277
|
+
const response200 = operation.responses?.["200"];
|
|
278
|
+
if (response200?.content?.["application/json"]?.schema) {
|
|
279
|
+
const schema = response200.content["application/json"].schema;
|
|
280
|
+
const cleanSchema = extractJsonSchema(schema);
|
|
281
|
+
responseSection = `
|
|
282
|
+
<div>
|
|
283
|
+
<h4 class="text-lg font-semibold text-white mb-4">Response Schema</h4>
|
|
284
|
+
<div class="code-block rounded-xl p-4 border border-dark-600">
|
|
285
|
+
<pre class="text-sm text-gray-300 overflow-x-auto"><code>${JSON.stringify(cleanSchema, null, 2)}</code></pre>
|
|
286
|
+
</div>
|
|
287
|
+
</div>`;
|
|
288
|
+
}
|
|
289
|
+
return `
|
|
290
|
+
<section id="${operationId}" class="fade-in">
|
|
291
|
+
<div class="grid lg:grid-cols-3 gap-8">
|
|
292
|
+
<div class="lg:col-span-1 space-y-6">
|
|
293
|
+
<div>
|
|
294
|
+
<div class="flex items-center space-x-3 mb-4">
|
|
295
|
+
<span class="${methodColor} text-white px-3 py-1 rounded-lg text-sm font-medium">${method}</span>
|
|
296
|
+
<code class="text-blue-400 font-mono">${path}</code>
|
|
297
|
+
</div>
|
|
298
|
+
<p class="text-gray-300">${operation.description || operation.summary || `${method} ${path} operation`}</p>
|
|
299
|
+
</div>
|
|
300
|
+
|
|
301
|
+
${parametersSection}
|
|
302
|
+
${requestBodySection}
|
|
303
|
+
|
|
304
|
+
<div>
|
|
305
|
+
<h4 class="text-lg font-semibold text-white mb-3">Headers</h4>
|
|
306
|
+
<div class="border border-dark-600 rounded-lg p-3">
|
|
307
|
+
<div class="flex items-center justify-between mb-1">
|
|
308
|
+
<code class="text-blue-400">Authorization</code>
|
|
309
|
+
<span class="text-xs text-red-400">required</span>
|
|
310
|
+
</div>
|
|
311
|
+
<p class="text-sm text-gray-300">Bearer token for authentication</p>
|
|
312
|
+
</div>
|
|
313
|
+
</div>
|
|
314
|
+
|
|
315
|
+
<div>
|
|
316
|
+
<h4 class="text-lg font-semibold text-white mb-3">Responses</h4>
|
|
317
|
+
<div class="space-y-2">
|
|
318
|
+
${Object.entries(operation.responses || {}).map(([code, response]) => {
|
|
319
|
+
return `
|
|
320
|
+
<div class="flex items-center space-x-3">
|
|
321
|
+
<span class="${code.startsWith("2") ? "bg-green-600" : code.startsWith("4") ? "bg-red-600" : "bg-yellow-600"} text-white px-2 py-1 rounded text-xs">${code}</span>
|
|
322
|
+
<span class="text-gray-300 text-sm">${response.description || "Response"}</span>
|
|
323
|
+
</div>`;
|
|
324
|
+
}).join("")}
|
|
325
|
+
</div>
|
|
326
|
+
</div>
|
|
327
|
+
</div>
|
|
328
|
+
|
|
329
|
+
<div class="lg:col-span-2 space-y-6">
|
|
330
|
+
${requestExample}
|
|
331
|
+
${responseSection}
|
|
332
|
+
</div>
|
|
333
|
+
</div>
|
|
334
|
+
</section>`;
|
|
335
|
+
}).join("\n");
|
|
336
|
+
return `
|
|
337
|
+
<section id="${tag.toLowerCase()}-tag" class="fade-in">
|
|
338
|
+
<div class="grid lg:grid-cols-3 gap-8 mb-12">
|
|
339
|
+
<div class="lg:col-span-1">
|
|
340
|
+
<div class="sticky top-24">
|
|
341
|
+
<h2 class="text-3xl font-bold text-white mb-4">${tag}</h2>
|
|
342
|
+
<p class="text-gray-300 text-lg leading-relaxed">
|
|
343
|
+
${tagDescription}
|
|
344
|
+
</p>
|
|
345
|
+
<div class="mt-6 flex items-center space-x-2">
|
|
346
|
+
<span class="bg-blue-600 text-white px-3 py-1 rounded-full text-sm font-medium">${endpoints.length} endpoints</span>
|
|
347
|
+
<span class="bg-gray-600 text-white px-3 py-1 rounded-full text-sm font-medium">Authentication required</span>
|
|
348
|
+
</div>
|
|
349
|
+
</div>
|
|
350
|
+
</div>
|
|
351
|
+
|
|
352
|
+
<div class="lg:col-span-2">
|
|
353
|
+
<div class="bg-dark-800/30 backdrop-blur-sm rounded-xl p-6 border border-dark-700">
|
|
354
|
+
<h3 class="text-xl font-semibold text-white mb-6">Available Endpoints</h3>
|
|
355
|
+
<div class="space-y-4">
|
|
356
|
+
${endpointCards}
|
|
357
|
+
</div>
|
|
358
|
+
</div>
|
|
359
|
+
</div>
|
|
360
|
+
</div>
|
|
361
|
+
</section>
|
|
362
|
+
|
|
363
|
+
<hr class="border-dark-600">
|
|
364
|
+
|
|
365
|
+
<div class="space-y-16">
|
|
366
|
+
${detailedEndpoints}
|
|
367
|
+
</div>`;
|
|
368
|
+
}).join("\n\n");
|
|
369
|
+
return `<!DOCTYPE html>
|
|
370
|
+
<html lang="es" class="dark">
|
|
371
|
+
<head>
|
|
372
|
+
<meta charset="UTF-8">
|
|
373
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
374
|
+
<title>${this.spec.info.title}</title>
|
|
375
|
+
<script src="https://cdn.tailwindcss.com"><\/script>
|
|
376
|
+
<script>
|
|
377
|
+
tailwind.config = {
|
|
378
|
+
darkMode: 'class',
|
|
379
|
+
theme: {
|
|
380
|
+
extend: {
|
|
381
|
+
colors: {
|
|
382
|
+
dark: {
|
|
383
|
+
50: '#f8fafc',
|
|
384
|
+
100: '#f1f5f9',
|
|
385
|
+
200: '#e2e8f0',
|
|
386
|
+
300: '#cbd5e1',
|
|
387
|
+
400: '#94a3b8',
|
|
388
|
+
500: '#64748b',
|
|
389
|
+
600: '#475569',
|
|
390
|
+
700: '#334155',
|
|
391
|
+
800: '#1e293b',
|
|
392
|
+
900: '#0f172a',
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
<\/script>
|
|
399
|
+
<style>
|
|
400
|
+
.sidebar-transition {
|
|
401
|
+
transition: transform 0.3s ease-in-out, width 0.3s ease-in-out;
|
|
402
|
+
}
|
|
403
|
+
.fade-in {
|
|
404
|
+
animation: fadeIn 0.3s ease-in-out;
|
|
405
|
+
}
|
|
406
|
+
@keyframes fadeIn {
|
|
407
|
+
from { opacity: 0; transform: translateY(10px); }
|
|
408
|
+
to { opacity: 1; transform: translateY(0); }
|
|
409
|
+
}
|
|
410
|
+
.code-block {
|
|
411
|
+
background: linear-gradient(135deg, #1e293b 0%, #334155 100%);
|
|
412
|
+
}
|
|
413
|
+
</style>
|
|
414
|
+
</head>
|
|
415
|
+
<body class="bg-dark-900 text-gray-100 font-sans">
|
|
416
|
+
|
|
417
|
+
<nav class="fixed top-0 left-0 right-0 z-50 bg-dark-900/80 backdrop-blur-md border-b border-dark-700">
|
|
418
|
+
<div class="flex items-center justify-between px-4 py-3">
|
|
419
|
+
<div class="flex items-center space-x-4">
|
|
420
|
+
<button id="sidebarToggle" class="p-2 rounded-lg hover:bg-dark-700 transition-colors">
|
|
421
|
+
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
422
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"></path>
|
|
423
|
+
</svg>
|
|
424
|
+
</button>
|
|
425
|
+
<h1 class="text-xl font-bold text-blue-400">${this.spec.info.title}</h1>
|
|
426
|
+
</div>
|
|
427
|
+
|
|
428
|
+
<div class="flex items-center space-x-4">
|
|
429
|
+
<!-- <select id="languageSelect" class="bg-dark-800 border border-dark-600 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 transition-all">
|
|
430
|
+
<option value="shell">Shell</option>
|
|
431
|
+
<option value="nodejs">Node.js</option>
|
|
432
|
+
</select> -->
|
|
433
|
+
|
|
434
|
+
<button id="themeToggle" class="p-2 rounded-lg hover:bg-dark-700 transition-colors">
|
|
435
|
+
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
436
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z"></path>
|
|
437
|
+
</svg>
|
|
438
|
+
</button>
|
|
439
|
+
</div>
|
|
440
|
+
</div>
|
|
441
|
+
</nav>
|
|
442
|
+
|
|
443
|
+
<div class="flex pt-16">
|
|
444
|
+
|
|
445
|
+
<aside id="sidebar" class="fixed top-16 bottom-0 left-0 z-40 w-64 bg-dark-800/50 backdrop-blur-sm border-r border-dark-700 sidebar-transition transform translate-x-0">
|
|
446
|
+
<div class="p-4 h-full overflow-y-auto pt-6">
|
|
447
|
+
<div class="space-y-1">
|
|
448
|
+
${sidebarSections}
|
|
449
|
+
</div>
|
|
450
|
+
</div>
|
|
451
|
+
</aside>
|
|
452
|
+
|
|
453
|
+
<div id="sidebarOverlay" class="fixed top-16 bottom-0 left-0 right-0 bg-black/50 z-30 lg:hidden hidden"></div>
|
|
454
|
+
|
|
455
|
+
<main id="mainContent" class="flex-1 ml-64 transition-all duration-300">
|
|
456
|
+
<div class="max-w-7xl mx-auto px-4 py-8">
|
|
457
|
+
|
|
458
|
+
<div class="mb-12 fade-in">
|
|
459
|
+
<h1 class="text-4xl font-bold text-white mb-4">${this.spec.info.title}</h1>
|
|
460
|
+
<p class="text-xl text-gray-300 mb-4">${this.spec.info.description || "Complete reference for our REST API"}</p>
|
|
461
|
+
<span class="inline-block bg-blue-600 text-white px-3 py-1 rounded-full text-sm font-medium">v${this.spec.info.version}</span>
|
|
462
|
+
</div>
|
|
463
|
+
|
|
464
|
+
<div class="grid gap-8 mb-12 fade-in">
|
|
465
|
+
<div class="bg-dark-800/30 backdrop-blur-sm rounded-xl p-6 border border-dark-700">
|
|
466
|
+
<h3 class="text-xl font-semibold text-white mb-4">Server</h3>
|
|
467
|
+
<div class="space-y-3">
|
|
468
|
+
<a href="${this.host}" target="_blank" class="flex items-center space-x-3 hover:bg-dark-700/30 p-2 rounded-lg transition-colors group">
|
|
469
|
+
<div class="w-8 h-8 bg-blue-600 rounded-lg flex items-center justify-center">
|
|
470
|
+
<svg class="w-4 h-4 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
471
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"></path>
|
|
472
|
+
</svg>
|
|
473
|
+
</div>
|
|
474
|
+
<span class="text-gray-300 group-hover:text-white transition-colors">${this.host}</span>
|
|
475
|
+
</a>
|
|
476
|
+
</div>
|
|
477
|
+
</div>
|
|
478
|
+
</div>
|
|
479
|
+
|
|
480
|
+
<div class="space-y-20">
|
|
481
|
+
${contentSections}
|
|
482
|
+
</div>
|
|
483
|
+
</div>
|
|
484
|
+
</main>
|
|
485
|
+
</div>
|
|
486
|
+
|
|
487
|
+
<script>
|
|
488
|
+
const sidebarToggle = document.getElementById('sidebarToggle');
|
|
489
|
+
const sidebar = document.getElementById('sidebar');
|
|
490
|
+
const sidebarOverlay = document.getElementById('sidebarOverlay');
|
|
491
|
+
const mainContent = document.getElementById('mainContent');
|
|
492
|
+
const themeToggle = document.getElementById('themeToggle');
|
|
493
|
+
const html = document.documentElement;
|
|
494
|
+
|
|
495
|
+
themeToggle.addEventListener('click', () => {
|
|
496
|
+
if (html.classList.contains('dark')) {
|
|
497
|
+
html.classList.remove('dark');
|
|
498
|
+
html.classList.add('light');
|
|
499
|
+
document.body.className = 'bg-gray-50 text-gray-900 font-sans';
|
|
500
|
+
|
|
501
|
+
const navbar = document.querySelector('nav');
|
|
502
|
+
navbar.className = 'fixed top-0 left-0 right-0 z-50 bg-white/80 backdrop-blur-md border-b border-gray-200';
|
|
503
|
+
|
|
504
|
+
const sidebar = document.getElementById('sidebar');
|
|
505
|
+
sidebar.className = sidebar.className.replace('bg-dark-800/50 backdrop-blur-sm border-r border-dark-700', 'bg-white/50 backdrop-blur-sm border-r border-gray-200');
|
|
506
|
+
|
|
507
|
+
document.querySelectorAll('h1, h2, h3, h4').forEach(heading => {
|
|
508
|
+
heading.classList.remove('text-white', 'text-gray-100');
|
|
509
|
+
heading.classList.add('text-gray-900');
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
document.querySelectorAll('.text-gray-300').forEach(el => {
|
|
513
|
+
el.classList.remove('text-gray-300');
|
|
514
|
+
el.classList.add('text-gray-700');
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
document.querySelectorAll('.text-gray-400').forEach(el => {
|
|
518
|
+
el.classList.remove('text-gray-400');
|
|
519
|
+
el.classList.add('text-gray-600');
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
document.querySelectorAll('.border-dark-600').forEach(el => {
|
|
523
|
+
el.classList.remove('border-dark-600');
|
|
524
|
+
el.classList.add('border-gray-300');
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
document.querySelectorAll('.bg-dark-700\\/30').forEach(el => {
|
|
528
|
+
el.classList.remove('bg-dark-700/30');
|
|
529
|
+
el.classList.add('bg-gray-100/30');
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
document.querySelectorAll('.bg-dark-800\\/20').forEach(el => {
|
|
533
|
+
el.classList.remove('bg-dark-800/20');
|
|
534
|
+
el.classList.add('bg-gray-50/20');
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
document.querySelectorAll('.bg-dark-600').forEach(el => {
|
|
538
|
+
el.classList.remove('bg-dark-600');
|
|
539
|
+
el.classList.add('bg-gray-400');
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
document.querySelectorAll('.text-gray-500').forEach(el => {
|
|
543
|
+
el.classList.remove('text-gray-500');
|
|
544
|
+
el.classList.add('text-gray-600');
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
document.querySelectorAll('aside a').forEach(link => {
|
|
548
|
+
link.classList.add('text-gray-700', 'hover:text-gray-900', 'hover:bg-gray-100');
|
|
549
|
+
});
|
|
550
|
+
|
|
551
|
+
document.querySelectorAll('pre code').forEach(code => {
|
|
552
|
+
code.classList.remove('text-gray-300');
|
|
553
|
+
code.classList.add('text-gray-800');
|
|
554
|
+
});
|
|
555
|
+
|
|
556
|
+
document.querySelectorAll('.bg-dark-800\\/30').forEach(el => {
|
|
557
|
+
el.classList.remove('bg-dark-800/30', 'border-dark-700');
|
|
558
|
+
el.classList.add('bg-white/30', 'border-gray-200');
|
|
559
|
+
});
|
|
560
|
+
|
|
561
|
+
document.querySelectorAll('.code-block').forEach(el => {
|
|
562
|
+
el.style.background = 'linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%)';
|
|
563
|
+
el.classList.remove('border-dark-600');
|
|
564
|
+
el.classList.add('border-gray-300');
|
|
565
|
+
});
|
|
566
|
+
|
|
567
|
+
document.querySelectorAll('.border-dark-600').forEach(el => {
|
|
568
|
+
el.classList.remove('border-dark-600');
|
|
569
|
+
el.classList.add('border-gray-300');
|
|
570
|
+
});
|
|
571
|
+
|
|
572
|
+
} else {
|
|
573
|
+
html.classList.remove('light');
|
|
574
|
+
html.classList.add('dark');
|
|
575
|
+
document.body.className = 'bg-dark-900 text-gray-100 font-sans';
|
|
576
|
+
|
|
577
|
+
const navbar = document.querySelector('nav');
|
|
578
|
+
navbar.className = 'fixed top-0 left-0 right-0 z-50 bg-dark-900/80 backdrop-blur-md border-b border-dark-700';
|
|
579
|
+
|
|
580
|
+
const sidebar = document.getElementById('sidebar');
|
|
581
|
+
sidebar.className = sidebar.className.replace('bg-white/50 backdrop-blur-sm border-r border-gray-200', 'bg-dark-800/50 backdrop-blur-sm border-r border-dark-700');
|
|
582
|
+
|
|
583
|
+
document.querySelectorAll('h1, h2, h3, h4').forEach(heading => {
|
|
584
|
+
heading.classList.remove('text-gray-900');
|
|
585
|
+
heading.classList.add('text-white');
|
|
586
|
+
});
|
|
587
|
+
|
|
588
|
+
document.querySelectorAll('.text-gray-700').forEach(el => {
|
|
589
|
+
el.classList.remove('text-gray-700');
|
|
590
|
+
el.classList.add('text-gray-300');
|
|
591
|
+
});
|
|
592
|
+
|
|
593
|
+
document.querySelectorAll('.text-gray-600').forEach(el => {
|
|
594
|
+
el.classList.remove('text-gray-600');
|
|
595
|
+
el.classList.add('text-gray-400');
|
|
596
|
+
});
|
|
597
|
+
|
|
598
|
+
document.querySelectorAll('.border-gray-300').forEach(el => {
|
|
599
|
+
el.classList.remove('border-gray-300');
|
|
600
|
+
el.classList.add('border-dark-600');
|
|
601
|
+
});
|
|
602
|
+
|
|
603
|
+
document.querySelectorAll('.bg-gray-100\\/30').forEach(el => {
|
|
604
|
+
el.classList.remove('bg-gray-100/30');
|
|
605
|
+
el.classList.add('bg-dark-700/30');
|
|
606
|
+
});
|
|
607
|
+
|
|
608
|
+
document.querySelectorAll('.bg-gray-50\\/20').forEach(el => {
|
|
609
|
+
el.classList.remove('bg-gray-50/20');
|
|
610
|
+
el.classList.add('bg-dark-800/20');
|
|
611
|
+
});
|
|
612
|
+
|
|
613
|
+
document.querySelectorAll('.bg-gray-400').forEach(el => {
|
|
614
|
+
el.classList.remove('bg-gray-400');
|
|
615
|
+
el.classList.add('bg-dark-600');
|
|
616
|
+
});
|
|
617
|
+
|
|
618
|
+
document.querySelectorAll('.text-gray-600').forEach(el => {
|
|
619
|
+
el.classList.remove('text-gray-600');
|
|
620
|
+
el.classList.add('text-gray-500');
|
|
621
|
+
});
|
|
622
|
+
|
|
623
|
+
document.querySelectorAll('aside a').forEach(link => {
|
|
624
|
+
link.classList.remove('text-gray-700', 'hover:text-gray-900', 'hover:bg-gray-100');
|
|
625
|
+
});
|
|
626
|
+
|
|
627
|
+
document.querySelectorAll('pre code').forEach(code => {
|
|
628
|
+
code.classList.remove('text-gray-800');
|
|
629
|
+
code.classList.add('text-gray-300');
|
|
630
|
+
});
|
|
631
|
+
|
|
632
|
+
document.querySelectorAll('.bg-white\\/30').forEach(el => {
|
|
633
|
+
el.classList.remove('bg-white/30', 'border-gray-200');
|
|
634
|
+
el.classList.add('bg-dark-800/30', 'border-dark-700');
|
|
635
|
+
});
|
|
636
|
+
|
|
637
|
+
document.querySelectorAll('.code-block').forEach(el => {
|
|
638
|
+
el.style.background = 'linear-gradient(135deg, #1e293b 0%, #334155 100%)';
|
|
639
|
+
el.classList.remove('border-gray-300');
|
|
640
|
+
el.classList.add('border-dark-600');
|
|
641
|
+
});
|
|
642
|
+
|
|
643
|
+
document.querySelectorAll('.border-gray-300').forEach(el => {
|
|
644
|
+
el.classList.remove('border-gray-300');
|
|
645
|
+
el.classList.add('border-dark-600');
|
|
646
|
+
});
|
|
647
|
+
}
|
|
648
|
+
});
|
|
649
|
+
|
|
650
|
+
function toggleSidebar() {
|
|
651
|
+
const isHidden = sidebar.classList.contains('-translate-x-full');
|
|
652
|
+
|
|
653
|
+
if (isHidden) {
|
|
654
|
+
sidebar.classList.remove('-translate-x-full');
|
|
655
|
+
mainContent.classList.remove('ml-0');
|
|
656
|
+
mainContent.classList.add('ml-64');
|
|
657
|
+
} else {
|
|
658
|
+
sidebar.classList.add('-translate-x-full');
|
|
659
|
+
mainContent.classList.remove('ml-64');
|
|
660
|
+
mainContent.classList.add('ml-0');
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
if (window.innerWidth < 1024) {
|
|
664
|
+
sidebarOverlay.classList.toggle('hidden');
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
sidebarToggle.addEventListener('click', toggleSidebar);
|
|
669
|
+
sidebarOverlay.addEventListener('click', toggleSidebar);
|
|
670
|
+
|
|
671
|
+
document.querySelectorAll('.copy-btn').forEach(btn => {
|
|
672
|
+
btn.addEventListener('click', async () => {
|
|
673
|
+
const textToCopy = btn.getAttribute('data-copy');
|
|
674
|
+
try {
|
|
675
|
+
await navigator.clipboard.writeText(textToCopy);
|
|
676
|
+
btn.textContent = 'Copied!';
|
|
677
|
+
setTimeout(() => {
|
|
678
|
+
btn.textContent = 'Copy';
|
|
679
|
+
}, 2000);
|
|
680
|
+
} catch (err) {
|
|
681
|
+
console.error('Failed to copy text: ', err);
|
|
682
|
+
}
|
|
683
|
+
});
|
|
684
|
+
});
|
|
685
|
+
|
|
686
|
+
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
|
|
687
|
+
anchor.addEventListener('click', function (e) {
|
|
688
|
+
e.preventDefault();
|
|
689
|
+
const target = document.querySelector(this.getAttribute('href'));
|
|
690
|
+
if (target) {
|
|
691
|
+
target.scrollIntoView({
|
|
692
|
+
behavior: 'smooth',
|
|
693
|
+
block: 'start'
|
|
694
|
+
});
|
|
695
|
+
|
|
696
|
+
if (window.innerWidth < 1024) {
|
|
697
|
+
toggleSidebar();
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
});
|
|
701
|
+
});
|
|
702
|
+
|
|
703
|
+
document.querySelectorAll('.sidebar-group-toggle').forEach(button => {
|
|
704
|
+
button.addEventListener('click', () => {
|
|
705
|
+
const targetId = button.getAttribute('data-target');
|
|
706
|
+
const targetGroup = document.getElementById(targetId);
|
|
707
|
+
const arrow = button.querySelector('svg');
|
|
708
|
+
|
|
709
|
+
if (targetGroup.style.maxHeight && targetGroup.style.maxHeight !== '0px') {
|
|
710
|
+
targetGroup.style.maxHeight = '0px';
|
|
711
|
+
arrow.style.transform = 'rotate(0deg)';
|
|
712
|
+
} else {
|
|
713
|
+
targetGroup.style.maxHeight = targetGroup.scrollHeight + 'px';
|
|
714
|
+
arrow.style.transform = 'rotate(180deg)';
|
|
715
|
+
}
|
|
716
|
+
});
|
|
717
|
+
});
|
|
718
|
+
|
|
719
|
+
document.querySelectorAll('.sidebar-group-content').forEach(group => {
|
|
720
|
+
group.style.maxHeight = group.scrollHeight + 'px';
|
|
721
|
+
});
|
|
722
|
+
document.querySelectorAll('.sidebar-group-toggle svg').forEach(arrow => {
|
|
723
|
+
arrow.style.transform = 'rotate(180deg)';
|
|
724
|
+
});
|
|
725
|
+
|
|
726
|
+
function handleResize() {
|
|
727
|
+
if (window.innerWidth < 1024) {
|
|
728
|
+
sidebar.classList.add('-translate-x-full');
|
|
729
|
+
mainContent.classList.remove('ml-64');
|
|
730
|
+
mainContent.classList.add('ml-0');
|
|
731
|
+
} else {
|
|
732
|
+
if (!sidebar.hasAttribute('data-manually-hidden')) {
|
|
733
|
+
sidebar.classList.remove('-translate-x-full');
|
|
734
|
+
mainContent.classList.remove('ml-0');
|
|
735
|
+
mainContent.classList.add('ml-64');
|
|
736
|
+
}
|
|
737
|
+
sidebarOverlay.classList.add('hidden');
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
window.addEventListener('resize', handleResize);
|
|
742
|
+
handleResize();
|
|
743
|
+
<\/script>
|
|
744
|
+
</body>
|
|
745
|
+
</html>`;
|
|
746
|
+
}
|
|
747
|
+
};
|
|
748
|
+
//#endregion
|
|
749
|
+
exports.Swagger = Swagger;
|
|
750
|
+
|
|
751
|
+
//# sourceMappingURL=swagger.cjs.map
|