tina4-nodejs 3.13.83 → 3.13.85
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/CLAUDE.md +2 -2
- package/package.json +11 -4
- package/packages/cli/dist/bin.js +40866 -0
- package/packages/cli/src/bin.ts +66 -10
- package/packages/cli/src/commands/init.ts +1 -1
- package/packages/core/dist/index.js +38006 -0
- package/packages/frond/dist/index.js +2543 -0
- package/packages/orm/dist/index.js +37850 -0
- package/packages/swagger/dist/index.js +445 -0
- package/packages/cli/bin/tina4nodejs +0 -10
- package/packages/core/public/js/tina4-dev-admin.js +0 -1124
|
@@ -0,0 +1,445 @@
|
|
|
1
|
+
// src/generator.ts
|
|
2
|
+
var WRITE_METHODS = /* @__PURE__ */ new Set(["post", "put", "patch", "delete"]);
|
|
3
|
+
var registeredSchemes = {};
|
|
4
|
+
var registeredSchemas = {};
|
|
5
|
+
function addSecurityScheme(name, definition) {
|
|
6
|
+
registeredSchemes[name] = definition;
|
|
7
|
+
}
|
|
8
|
+
function addSchema(name, schema) {
|
|
9
|
+
registeredSchemas[name] = schema;
|
|
10
|
+
}
|
|
11
|
+
function resetRegistry() {
|
|
12
|
+
for (const k of Object.keys(registeredSchemes)) delete registeredSchemes[k];
|
|
13
|
+
for (const k of Object.keys(registeredSchemas)) delete registeredSchemas[k];
|
|
14
|
+
}
|
|
15
|
+
function resolveOpenApiVersion() {
|
|
16
|
+
const v = (process.env.TINA4_SWAGGER_OPENAPI ?? "").trim();
|
|
17
|
+
if (!v) return "3.0.3";
|
|
18
|
+
if (v === "3.1" || v === "3.1.0") return "3.1.0";
|
|
19
|
+
if (v === "3.0" || v === "3.0.3") return "3.0.3";
|
|
20
|
+
return v;
|
|
21
|
+
}
|
|
22
|
+
function csv(val) {
|
|
23
|
+
return (val ?? "").split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
24
|
+
}
|
|
25
|
+
function resolveSecuritySchemes() {
|
|
26
|
+
const bearerFormat = process.env.TINA4_SWAGGER_BEARER_FORMAT ?? "JWT";
|
|
27
|
+
const schemes = {
|
|
28
|
+
bearerAuth: { type: "http", scheme: "bearer", bearerFormat }
|
|
29
|
+
};
|
|
30
|
+
const apiKeyName = (process.env.TINA4_SWAGGER_API_KEY_NAME ?? "").trim();
|
|
31
|
+
if (apiKeyName.length > 0) {
|
|
32
|
+
const rawIn = process.env.TINA4_SWAGGER_API_KEY_IN ?? "header";
|
|
33
|
+
const apiKeyIn = ["header", "query", "cookie"].includes(rawIn) ? rawIn : "header";
|
|
34
|
+
schemes.apiKeyAuth = { type: "apiKey", name: apiKeyName, in: apiKeyIn };
|
|
35
|
+
}
|
|
36
|
+
for (const [name, def] of Object.entries(registeredSchemes)) {
|
|
37
|
+
schemes[name] = def;
|
|
38
|
+
}
|
|
39
|
+
return schemes;
|
|
40
|
+
}
|
|
41
|
+
function normalizeSecurity(value, scopes) {
|
|
42
|
+
if ((value === "public" || value === "none" || value === void 0 || value === null) && (!scopes || scopes.length === 0)) {
|
|
43
|
+
return [];
|
|
44
|
+
}
|
|
45
|
+
if (typeof value === "string") {
|
|
46
|
+
return [{ [value]: [...scopes ?? []] }];
|
|
47
|
+
}
|
|
48
|
+
if (Array.isArray(value)) {
|
|
49
|
+
if (value.length === 0) return [];
|
|
50
|
+
return value.map((req) => normalizeRequirementMap(req));
|
|
51
|
+
}
|
|
52
|
+
if (value !== null && typeof value === "object") {
|
|
53
|
+
return [normalizeRequirementMap(value)];
|
|
54
|
+
}
|
|
55
|
+
return [];
|
|
56
|
+
}
|
|
57
|
+
function normalizeRequirementMap(req) {
|
|
58
|
+
const out = {};
|
|
59
|
+
for (const [k, v] of Object.entries(req)) out[k] = [...v ?? []];
|
|
60
|
+
return out;
|
|
61
|
+
}
|
|
62
|
+
function sanitizeSecurity(reqs, schemes) {
|
|
63
|
+
const scopeOk = /* @__PURE__ */ new Set(["oauth2", "openIdConnect"]);
|
|
64
|
+
return reqs.map((req) => {
|
|
65
|
+
const clean = {};
|
|
66
|
+
for (const [name, scopes] of Object.entries(req)) {
|
|
67
|
+
const stype = schemes[name]?.type;
|
|
68
|
+
clean[name] = scopeOk.has(stype) ? [...scopes] : [];
|
|
69
|
+
}
|
|
70
|
+
return clean;
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
function generate(routes, models = []) {
|
|
74
|
+
const info = {
|
|
75
|
+
title: process.env.TINA4_SWAGGER_TITLE ?? "Tina4 API",
|
|
76
|
+
version: process.env.TINA4_SWAGGER_VERSION ?? "0.0.1",
|
|
77
|
+
description: process.env.TINA4_SWAGGER_DESCRIPTION ?? "Auto-generated API documentation"
|
|
78
|
+
};
|
|
79
|
+
const contactEmail = (process.env.TINA4_SWAGGER_CONTACT_EMAIL ?? "").trim();
|
|
80
|
+
const contactName = (process.env.TINA4_SWAGGER_CONTACT_TEAM ?? "").trim();
|
|
81
|
+
const contactUrl = (process.env.TINA4_SWAGGER_CONTACT_URL ?? "").trim();
|
|
82
|
+
const contact = {};
|
|
83
|
+
if (contactName.length > 0) contact.name = contactName;
|
|
84
|
+
if (contactUrl.length > 0) contact.url = contactUrl;
|
|
85
|
+
if (contactEmail.length > 0) contact.email = contactEmail;
|
|
86
|
+
if (Object.keys(contact).length > 0) info.contact = contact;
|
|
87
|
+
const licenseRaw = (process.env.TINA4_SWAGGER_LICENSE ?? "").trim();
|
|
88
|
+
if (licenseRaw.length > 0) {
|
|
89
|
+
const [name, url] = licenseRaw.split("|").map((s) => s.trim());
|
|
90
|
+
info.license = url ? { name, url } : { name };
|
|
91
|
+
}
|
|
92
|
+
const schemes = resolveSecuritySchemes();
|
|
93
|
+
const spec = {
|
|
94
|
+
openapi: resolveOpenApiVersion(),
|
|
95
|
+
info,
|
|
96
|
+
servers: resolveServers(),
|
|
97
|
+
paths: {},
|
|
98
|
+
components: {
|
|
99
|
+
schemas: {},
|
|
100
|
+
// Configurable security schemes (v3.13.42): bearerFormat via env, optional
|
|
101
|
+
// apiKey scheme, plus any programmatically-registered schemes (which may
|
|
102
|
+
// override bearerAuth — e.g. an oauth2 scheme with scopes).
|
|
103
|
+
securitySchemes: schemes
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
const defaultScheme = process.env.TINA4_SWAGGER_DEFAULT_SCHEME ?? "bearerAuth";
|
|
107
|
+
const includePrefixes = csv(process.env.TINA4_SWAGGER_INCLUDE);
|
|
108
|
+
const excludePrefixes = csv(process.env.TINA4_SWAGGER_EXCLUDE);
|
|
109
|
+
const refSchemas = /* @__PURE__ */ new Set();
|
|
110
|
+
for (const model of models) {
|
|
111
|
+
const schema = modelToSchema(model);
|
|
112
|
+
spec.components.schemas[model.tableName] = schema;
|
|
113
|
+
}
|
|
114
|
+
const usedTags = [];
|
|
115
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
116
|
+
for (const route of routes) {
|
|
117
|
+
if (!isIncludedPath(route.pattern, includePrefixes, excludePrefixes)) continue;
|
|
118
|
+
const openApiPath = patternToOpenAPI(route.pattern);
|
|
119
|
+
const method = route.method.toLowerCase();
|
|
120
|
+
if (!spec.paths[openApiPath]) {
|
|
121
|
+
spec.paths[openApiPath] = {};
|
|
122
|
+
}
|
|
123
|
+
const tags = route.meta?.tags ?? inferTags(route.pattern);
|
|
124
|
+
for (const t of tags) {
|
|
125
|
+
if (!usedTags.includes(t)) usedTags.push(t);
|
|
126
|
+
}
|
|
127
|
+
const operation = {
|
|
128
|
+
operationId: uniqueOperationId(method, openApiPath, seenIds),
|
|
129
|
+
summary: route.meta?.summary ?? `${route.method} ${route.pattern}`,
|
|
130
|
+
tags,
|
|
131
|
+
responses: route.meta?.responses ?? {
|
|
132
|
+
"200": { description: "Successful response" }
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
if (route.meta?.description) operation.description = route.meta.description;
|
|
136
|
+
if (route.meta?.deprecated) operation.deprecated = true;
|
|
137
|
+
const pathParams = extractPathParams(route.pattern);
|
|
138
|
+
if (pathParams.length > 0) {
|
|
139
|
+
operation.parameters = pathParams.map((name) => ({
|
|
140
|
+
name,
|
|
141
|
+
in: "path",
|
|
142
|
+
required: true,
|
|
143
|
+
schema: { type: "string" }
|
|
144
|
+
}));
|
|
145
|
+
}
|
|
146
|
+
if (method === "get" && !route.pattern.includes("[id]") && !route.pattern.includes("[...")) {
|
|
147
|
+
const modelName = inferModelFromPath(route.pattern);
|
|
148
|
+
if (modelName && models.some((m) => m.tableName === modelName)) {
|
|
149
|
+
operation.parameters = [
|
|
150
|
+
...operation.parameters ?? [],
|
|
151
|
+
{ name: "page", in: "query", schema: { type: "integer", default: 1 } },
|
|
152
|
+
{ name: "limit", in: "query", schema: { type: "integer", default: 20 } },
|
|
153
|
+
{ name: "sort", in: "query", schema: { type: "string" }, description: "Sort fields (prefix with - for descending)" }
|
|
154
|
+
];
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
const reqSchemaRef = parseRequestSchema(route.meta?.requestSchema);
|
|
158
|
+
if (reqSchemaRef && (method === "post" || method === "put" || method === "patch")) {
|
|
159
|
+
refSchemas.add(reqSchemaRef.name);
|
|
160
|
+
const media = {
|
|
161
|
+
schema: { $ref: `#/components/schemas/${reqSchemaRef.name}` }
|
|
162
|
+
};
|
|
163
|
+
if (route.meta?.example !== void 0) media.example = route.meta.example;
|
|
164
|
+
operation.requestBody = {
|
|
165
|
+
content: { [reqSchemaRef.contentType]: media }
|
|
166
|
+
};
|
|
167
|
+
} else if (method === "post" || method === "put") {
|
|
168
|
+
const modelName = inferModelFromPath(route.pattern);
|
|
169
|
+
if (modelName && models.some((m) => m.tableName === modelName)) {
|
|
170
|
+
const media = {
|
|
171
|
+
schema: { $ref: `#/components/schemas/${modelName}` }
|
|
172
|
+
};
|
|
173
|
+
if (route.meta?.example !== void 0) media.example = route.meta.example;
|
|
174
|
+
operation.requestBody = {
|
|
175
|
+
required: true,
|
|
176
|
+
content: { "application/json": media }
|
|
177
|
+
};
|
|
178
|
+
operation.responses = {
|
|
179
|
+
...method === "post" ? { "201": { description: "Created", content: { "application/json": { schema: { $ref: `#/components/schemas/${modelName}` } } } } } : { "200": { description: "Updated", content: { "application/json": { schema: { $ref: `#/components/schemas/${modelName}` } } } } },
|
|
180
|
+
"422": { description: "Validation failed" }
|
|
181
|
+
};
|
|
182
|
+
} else if (route.meta?.example !== void 0) {
|
|
183
|
+
operation.requestBody = {
|
|
184
|
+
content: { "application/json": { schema: inferSchema(route.meta.example), example: route.meta.example } }
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
const respSchemas = parseResponseSchemas(route.meta?.responseSchemas);
|
|
189
|
+
if (respSchemas.length > 0) {
|
|
190
|
+
const responses = operation.responses;
|
|
191
|
+
for (const { status, name, isList } of respSchemas) {
|
|
192
|
+
refSchemas.add(name);
|
|
193
|
+
const sref = `#/components/schemas/${name}`;
|
|
194
|
+
const schema = isList ? { type: "array", items: { $ref: sref } } : { $ref: sref };
|
|
195
|
+
responses[status] = {
|
|
196
|
+
description: status.startsWith("2") ? "Successful response" : "Response",
|
|
197
|
+
content: { "application/json": { schema } }
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
const hasExplicitSecurity = route.meta?.security !== void 0 || route.meta?.scopes !== void 0 && route.meta.scopes.length > 0;
|
|
202
|
+
if (hasExplicitSecurity) {
|
|
203
|
+
const normalized = normalizeSecurity(route.meta?.security, route.meta?.scopes);
|
|
204
|
+
operation.security = normalized.length > 0 ? sanitizeSecurity(normalized, schemes) : [];
|
|
205
|
+
if (normalized.length > 0) {
|
|
206
|
+
const responses = operation.responses;
|
|
207
|
+
if (!responses["401"]) responses["401"] = { description: "Unauthorized" };
|
|
208
|
+
}
|
|
209
|
+
} else if (routeRequiresAuth(route, method)) {
|
|
210
|
+
operation.security = sanitizeSecurity([{ [defaultScheme]: [] }], schemes);
|
|
211
|
+
const responses = operation.responses;
|
|
212
|
+
if (!responses["401"]) responses["401"] = { description: "Unauthorized" };
|
|
213
|
+
}
|
|
214
|
+
spec.paths[openApiPath][method] = operation;
|
|
215
|
+
}
|
|
216
|
+
if (refSchemas.size > 0) {
|
|
217
|
+
const schemas = spec.components.schemas;
|
|
218
|
+
for (const name of refSchemas) {
|
|
219
|
+
if (name in registeredSchemas && !(name in schemas)) {
|
|
220
|
+
schemas[name] = registeredSchemas[name];
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
if (usedTags.length > 0) {
|
|
225
|
+
spec.tags = usedTags.map((name) => ({ name }));
|
|
226
|
+
}
|
|
227
|
+
return spec;
|
|
228
|
+
}
|
|
229
|
+
function routeRequiresAuth(route, method) {
|
|
230
|
+
if (route.noAuth) return false;
|
|
231
|
+
if (WRITE_METHODS.has(method)) return true;
|
|
232
|
+
return route.secure === true;
|
|
233
|
+
}
|
|
234
|
+
function isIncludedPath(rawPath, include, exclude) {
|
|
235
|
+
for (const internal of ["/swagger", "/__dev"]) {
|
|
236
|
+
if (rawPath === internal || rawPath.startsWith(internal + "/")) return false;
|
|
237
|
+
}
|
|
238
|
+
if (include.length > 0 && !include.some((p) => rawPath === p || rawPath.startsWith(p))) {
|
|
239
|
+
return false;
|
|
240
|
+
}
|
|
241
|
+
if (exclude.some((p) => rawPath === p || rawPath.startsWith(p))) return false;
|
|
242
|
+
return true;
|
|
243
|
+
}
|
|
244
|
+
function parseRequestSchema(spec) {
|
|
245
|
+
if (spec === void 0) return null;
|
|
246
|
+
if (typeof spec === "string") return { name: spec, contentType: "application/json" };
|
|
247
|
+
return { name: spec.name, contentType: spec.contentType ?? "application/json" };
|
|
248
|
+
}
|
|
249
|
+
function parseResponseSchemas(spec) {
|
|
250
|
+
if (!spec) return [];
|
|
251
|
+
const out = [];
|
|
252
|
+
for (const [status, value] of Object.entries(spec)) {
|
|
253
|
+
if (typeof value === "string") {
|
|
254
|
+
out.push({ status, name: value, isList: false });
|
|
255
|
+
} else {
|
|
256
|
+
out.push({ status, name: value.name, isList: value.isList === true });
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
return out;
|
|
260
|
+
}
|
|
261
|
+
function resolveServers() {
|
|
262
|
+
const raw = (process.env.TINA4_SWAGGER_SERVERS ?? "").trim();
|
|
263
|
+
const urls = raw.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
264
|
+
if (urls.length > 0) return urls.map((url) => ({ url }));
|
|
265
|
+
const dev = (process.env.SWAGGER_DEV_URL ?? "").trim();
|
|
266
|
+
return dev.length > 0 ? [{ url: dev }] : [{ url: "/" }];
|
|
267
|
+
}
|
|
268
|
+
function modelToSchema(model) {
|
|
269
|
+
const properties = {};
|
|
270
|
+
const required = [];
|
|
271
|
+
for (const [name, def] of Object.entries(model.fields)) {
|
|
272
|
+
properties[name] = fieldToSchemaProperty(def);
|
|
273
|
+
if (def.required && !def.primaryKey) {
|
|
274
|
+
required.push(name);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
return {
|
|
278
|
+
type: "object",
|
|
279
|
+
properties,
|
|
280
|
+
...required.length > 0 ? { required } : {}
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
function fieldToSchemaProperty(def) {
|
|
284
|
+
const prop = {};
|
|
285
|
+
switch (def.type) {
|
|
286
|
+
case "string":
|
|
287
|
+
case "text":
|
|
288
|
+
prop.type = "string";
|
|
289
|
+
if (def.maxLength) prop.maxLength = def.maxLength;
|
|
290
|
+
if (def.minLength) prop.minLength = def.minLength;
|
|
291
|
+
if (def.pattern) prop.pattern = def.pattern;
|
|
292
|
+
break;
|
|
293
|
+
case "integer":
|
|
294
|
+
prop.type = "integer";
|
|
295
|
+
if (def.min !== void 0) prop.minimum = def.min;
|
|
296
|
+
if (def.max !== void 0) prop.maximum = def.max;
|
|
297
|
+
break;
|
|
298
|
+
case "number":
|
|
299
|
+
case "numeric":
|
|
300
|
+
prop.type = "number";
|
|
301
|
+
if (def.min !== void 0) prop.minimum = def.min;
|
|
302
|
+
if (def.max !== void 0) prop.maximum = def.max;
|
|
303
|
+
break;
|
|
304
|
+
case "boolean":
|
|
305
|
+
prop.type = "boolean";
|
|
306
|
+
break;
|
|
307
|
+
case "datetime":
|
|
308
|
+
prop.type = "string";
|
|
309
|
+
prop.format = "date-time";
|
|
310
|
+
break;
|
|
311
|
+
case "foreignKey":
|
|
312
|
+
prop.type = "integer";
|
|
313
|
+
break;
|
|
314
|
+
case "json":
|
|
315
|
+
prop.type = "object";
|
|
316
|
+
break;
|
|
317
|
+
default:
|
|
318
|
+
prop.type = "string";
|
|
319
|
+
}
|
|
320
|
+
if (def.default !== void 0) prop.default = def.default;
|
|
321
|
+
if (def.primaryKey && def.autoIncrement) {
|
|
322
|
+
prop.readOnly = true;
|
|
323
|
+
}
|
|
324
|
+
return prop;
|
|
325
|
+
}
|
|
326
|
+
function inferSchema(value) {
|
|
327
|
+
if (Array.isArray(value)) {
|
|
328
|
+
return { type: "array", items: value.length > 0 ? inferSchema(value[0]) : {} };
|
|
329
|
+
}
|
|
330
|
+
if (value !== null && typeof value === "object") {
|
|
331
|
+
const properties = {};
|
|
332
|
+
for (const [k, v] of Object.entries(value)) {
|
|
333
|
+
properties[k] = inferSchema(v);
|
|
334
|
+
}
|
|
335
|
+
return { type: "object", properties };
|
|
336
|
+
}
|
|
337
|
+
if (typeof value === "boolean") return { type: "boolean" };
|
|
338
|
+
if (typeof value === "number") return { type: Number.isInteger(value) ? "integer" : "number" };
|
|
339
|
+
return { type: "string" };
|
|
340
|
+
}
|
|
341
|
+
function patternToOpenAPI(pattern) {
|
|
342
|
+
return pattern.replace(/\[\.\.\.(\w+)\]/g, "{$1}").replace(/\[(\w+)\]/g, "{$1}");
|
|
343
|
+
}
|
|
344
|
+
function extractPathParams(pattern) {
|
|
345
|
+
const params = [];
|
|
346
|
+
const regex = /\[(?:\.\.\.)?(\w+)\]/g;
|
|
347
|
+
let match;
|
|
348
|
+
while ((match = regex.exec(pattern)) !== null) {
|
|
349
|
+
params.push(match[1]);
|
|
350
|
+
}
|
|
351
|
+
return params;
|
|
352
|
+
}
|
|
353
|
+
function inferTags(pattern) {
|
|
354
|
+
const parts = pattern.split("/").filter(Boolean);
|
|
355
|
+
const apiIndex = parts.indexOf("api");
|
|
356
|
+
if (apiIndex !== -1 && parts[apiIndex + 1]) {
|
|
357
|
+
return [parts[apiIndex + 1]];
|
|
358
|
+
}
|
|
359
|
+
return parts.length > 0 ? [parts[0]] : ["default"];
|
|
360
|
+
}
|
|
361
|
+
function inferModelFromPath(pattern) {
|
|
362
|
+
const parts = pattern.split("/").filter(Boolean);
|
|
363
|
+
const apiIndex = parts.indexOf("api");
|
|
364
|
+
if (apiIndex === -1 || !parts[apiIndex + 1]) return null;
|
|
365
|
+
const candidate = parts[apiIndex + 1];
|
|
366
|
+
const rest = parts.slice(apiIndex + 2);
|
|
367
|
+
if (rest.length === 0) return candidate;
|
|
368
|
+
if (rest.length === 1 && /^[[{]\.{0,3}\w+[\]}]$/.test(rest[0])) return candidate;
|
|
369
|
+
return null;
|
|
370
|
+
}
|
|
371
|
+
function uniqueOperationId(method, openApiPath, seen) {
|
|
372
|
+
const base = (method + openApiPath.replace(/[/{}]/g, "_")).replace(/_+/g, "_").replace(/_$/, "");
|
|
373
|
+
let oid = base;
|
|
374
|
+
let n = 2;
|
|
375
|
+
while (seen.has(oid)) {
|
|
376
|
+
oid = `${base}_${n}`;
|
|
377
|
+
n += 1;
|
|
378
|
+
}
|
|
379
|
+
seen.add(oid);
|
|
380
|
+
return oid;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// src/ui.ts
|
|
384
|
+
function swaggerUiCdn() {
|
|
385
|
+
return (process.env.TINA4_SWAGGER_UI_CDN ?? "https://unpkg.com/swagger-ui-dist@5").replace(/\/+$/, "");
|
|
386
|
+
}
|
|
387
|
+
var SWAGGER_UI_HTML = (specUrl) => `<!DOCTYPE html>
|
|
388
|
+
<html lang="en">
|
|
389
|
+
<head>
|
|
390
|
+
<meta charset="UTF-8">
|
|
391
|
+
<title>Tina4 API Documentation</title>
|
|
392
|
+
<link rel="stylesheet" href="${swaggerUiCdn()}/swagger-ui.css">
|
|
393
|
+
<style>
|
|
394
|
+
body { margin: 0; background: #fafafa; }
|
|
395
|
+
.topbar { display: none !important; }
|
|
396
|
+
</style>
|
|
397
|
+
</head>
|
|
398
|
+
<body>
|
|
399
|
+
<div id="swagger-ui"></div>
|
|
400
|
+
<script src="${swaggerUiCdn()}/swagger-ui-bundle.js"></script>
|
|
401
|
+
<script>
|
|
402
|
+
SwaggerUIBundle({
|
|
403
|
+
url: "${specUrl}",
|
|
404
|
+
dom_id: '#swagger-ui',
|
|
405
|
+
deepLinking: true,
|
|
406
|
+
presets: [SwaggerUIBundle.presets.apis, SwaggerUIBundle.SwaggerUIStandalonePreset],
|
|
407
|
+
layout: "BaseLayout",
|
|
408
|
+
});
|
|
409
|
+
</script>
|
|
410
|
+
</body>
|
|
411
|
+
</html>`;
|
|
412
|
+
function swaggerEnabled() {
|
|
413
|
+
const raw = (process.env.TINA4_SWAGGER_ENABLED ?? "").trim().toLowerCase();
|
|
414
|
+
if (raw === "") {
|
|
415
|
+
const debug = (process.env.TINA4_DEBUG ?? "").trim().toLowerCase();
|
|
416
|
+
return ["true", "1", "yes", "on"].includes(debug);
|
|
417
|
+
}
|
|
418
|
+
return ["true", "1", "yes", "on"].includes(raw);
|
|
419
|
+
}
|
|
420
|
+
function createSwaggerRoutes(getSpec) {
|
|
421
|
+
return [
|
|
422
|
+
{
|
|
423
|
+
method: "GET",
|
|
424
|
+
pattern: "/swagger",
|
|
425
|
+
handler: async (_req, res) => {
|
|
426
|
+
res.html(SWAGGER_UI_HTML("/swagger/openapi.json"));
|
|
427
|
+
}
|
|
428
|
+
},
|
|
429
|
+
{
|
|
430
|
+
method: "GET",
|
|
431
|
+
pattern: "/swagger/openapi.json",
|
|
432
|
+
handler: async (_req, res) => {
|
|
433
|
+
res.json(getSpec());
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
];
|
|
437
|
+
}
|
|
438
|
+
export {
|
|
439
|
+
addSchema,
|
|
440
|
+
addSecurityScheme,
|
|
441
|
+
createSwaggerRoutes,
|
|
442
|
+
generate,
|
|
443
|
+
resetRegistry,
|
|
444
|
+
swaggerEnabled
|
|
445
|
+
};
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
#!/bin/sh
|
|
2
|
-
# Resolve symlinks to find the real script location
|
|
3
|
-
SCRIPT="$0"
|
|
4
|
-
while [ -L "$SCRIPT" ]; do
|
|
5
|
-
DIR="$(cd -P "$(dirname "$SCRIPT")" && pwd)"
|
|
6
|
-
SCRIPT="$(readlink "$SCRIPT")"
|
|
7
|
-
[ "${SCRIPT%"${SCRIPT#?}"}" != "/" ] && SCRIPT="$DIR/$SCRIPT"
|
|
8
|
-
done
|
|
9
|
-
DIR="$(cd -P "$(dirname "$SCRIPT")" && pwd)"
|
|
10
|
-
exec npx tsx "$DIR/../src/bin.ts" "$@"
|