@contractkit/plugin-openapi 0.8.1
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/.turbo/turbo-build$colon$ci.log +35 -0
- package/.turbo/turbo-build.log +15 -0
- package/.turbo/turbo-test$colon$ci.log +105 -0
- package/.turbo/turbo-test.log +14 -0
- package/CHANGELOG.md +103 -0
- package/README.md +78 -0
- package/coverage/base.css +224 -0
- package/coverage/block-navigation.js +87 -0
- package/coverage/clover.xml +403 -0
- package/coverage/coverage-final.json +3 -0
- package/coverage/favicon.png +0 -0
- package/coverage/index.html +131 -0
- package/coverage/prettify.css +1 -0
- package/coverage/prettify.js +2 -0
- package/coverage/sort-arrow-sprite.png +0 -0
- package/coverage/sorter.js +210 -0
- package/coverage/src/codegen-openapi.ts.html +2308 -0
- package/coverage/src/index.html +116 -0
- package/coverage/tests/helpers.ts.html +616 -0
- package/coverage/tests/index.html +116 -0
- package/dist/codegen-openapi.d.ts +44 -0
- package/dist/codegen-openapi.d.ts.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +701 -0
- package/dist/index.js.map +1 -0
- package/dist/src/codegen-openapi.d.ts +38 -0
- package/dist/src/codegen-openapi.d.ts.map +1 -0
- package/dist/src/index.d.ts +4 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/tests/codegen-openapi.test.d.ts +2 -0
- package/dist/tests/codegen-openapi.test.d.ts.map +1 -0
- package/dist/tests/helpers.d.ts +31 -0
- package/dist/tests/helpers.d.ts.map +1 -0
- package/eslint.config.js +6 -0
- package/package.json +43 -0
- package/src/codegen-openapi.ts +741 -0
- package/src/index.ts +43 -0
- package/tests/codegen-openapi.test.ts +767 -0
- package/tests/helpers.ts +177 -0
- package/tsconfig.json +9 -0
- package/vitest.config.ts +14 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,701 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
3
|
+
|
|
4
|
+
// src/index.ts
|
|
5
|
+
import { resolve } from "path";
|
|
6
|
+
|
|
7
|
+
// src/codegen-openapi.ts
|
|
8
|
+
import { resolveModifiers, resolveSecurity, SECURITY_NONE } from "@contractkit/core";
|
|
9
|
+
function collectRefsFromType(type, out) {
|
|
10
|
+
switch (type.kind) {
|
|
11
|
+
case "ref":
|
|
12
|
+
out.add(type.name);
|
|
13
|
+
break;
|
|
14
|
+
case "array":
|
|
15
|
+
collectRefsFromType(type.item, out);
|
|
16
|
+
break;
|
|
17
|
+
case "tuple":
|
|
18
|
+
for (const item of type.items) collectRefsFromType(item, out);
|
|
19
|
+
break;
|
|
20
|
+
case "record":
|
|
21
|
+
collectRefsFromType(type.value, out);
|
|
22
|
+
break;
|
|
23
|
+
case "union":
|
|
24
|
+
case "discriminatedUnion":
|
|
25
|
+
case "intersection":
|
|
26
|
+
for (const member of type.members) collectRefsFromType(member, out);
|
|
27
|
+
break;
|
|
28
|
+
case "lazy":
|
|
29
|
+
collectRefsFromType(type.inner, out);
|
|
30
|
+
break;
|
|
31
|
+
case "inlineObject":
|
|
32
|
+
for (const field of type.fields) collectRefsFromType(field.type, out);
|
|
33
|
+
break;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
__name(collectRefsFromType, "collectRefsFromType");
|
|
37
|
+
function collectParamSourceRefs(source, out) {
|
|
38
|
+
if (!source) return;
|
|
39
|
+
if (source.kind === "ref") {
|
|
40
|
+
out.add(source.name);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
if (source.kind === "params") {
|
|
44
|
+
for (const p of source.nodes) collectRefsFromType(p.type, out);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
collectRefsFromType(source.node, out);
|
|
48
|
+
}
|
|
49
|
+
__name(collectParamSourceRefs, "collectParamSourceRefs");
|
|
50
|
+
function collectPublicTypeRefs(opRoots, includeInternal = false) {
|
|
51
|
+
const refs = /* @__PURE__ */ new Set();
|
|
52
|
+
for (const opRoot of opRoots) {
|
|
53
|
+
for (const route of opRoot.routes) {
|
|
54
|
+
for (const op of route.operations) {
|
|
55
|
+
if (!includeInternal && resolveModifiers(route, op).includes("internal")) continue;
|
|
56
|
+
if (op.request) {
|
|
57
|
+
for (const body of op.request.bodies) collectRefsFromType(body.bodyType, refs);
|
|
58
|
+
}
|
|
59
|
+
for (const resp of op.responses) {
|
|
60
|
+
if (resp.bodyType) collectRefsFromType(resp.bodyType, refs);
|
|
61
|
+
if (resp.headers) {
|
|
62
|
+
for (const h of resp.headers) collectRefsFromType(h.type, refs);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
collectParamSourceRefs(route.params, refs);
|
|
66
|
+
collectParamSourceRefs(op.query, refs);
|
|
67
|
+
collectParamSourceRefs(op.headers, refs);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return refs;
|
|
72
|
+
}
|
|
73
|
+
__name(collectPublicTypeRefs, "collectPublicTypeRefs");
|
|
74
|
+
function computeReachableSchemas(seeds, modelMap) {
|
|
75
|
+
const reachable = new Set(seeds);
|
|
76
|
+
const frontier = [
|
|
77
|
+
...seeds
|
|
78
|
+
];
|
|
79
|
+
while (frontier.length > 0) {
|
|
80
|
+
const name = frontier.pop();
|
|
81
|
+
const model = modelMap.get(name);
|
|
82
|
+
if (!model) continue;
|
|
83
|
+
const refs = /* @__PURE__ */ new Set();
|
|
84
|
+
if (model.type) collectRefsFromType(model.type, refs);
|
|
85
|
+
for (const field of model.fields) collectRefsFromType(field.type, refs);
|
|
86
|
+
if (model.bases) for (const b of model.bases) refs.add(b);
|
|
87
|
+
for (const ref of refs) {
|
|
88
|
+
if (!reachable.has(ref)) {
|
|
89
|
+
reachable.add(ref);
|
|
90
|
+
frontier.push(ref);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return reachable;
|
|
95
|
+
}
|
|
96
|
+
__name(computeReachableSchemas, "computeReachableSchemas");
|
|
97
|
+
function generateOpenApi(ctx) {
|
|
98
|
+
const { contractRoots, opRoots, config, securitySchemes } = ctx;
|
|
99
|
+
const includeInternal = config.includeInternal ?? false;
|
|
100
|
+
const doc = {
|
|
101
|
+
openapi: "3.1.0",
|
|
102
|
+
info: {
|
|
103
|
+
title: config.info?.title ?? "API",
|
|
104
|
+
version: config.info?.version ?? "0.0.1",
|
|
105
|
+
...config.info?.description ? {
|
|
106
|
+
description: config.info.description
|
|
107
|
+
} : {}
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
if (config.servers && config.servers.length > 0) {
|
|
111
|
+
doc.servers = config.servers;
|
|
112
|
+
}
|
|
113
|
+
if (config.security && config.security.length > 0) {
|
|
114
|
+
doc.security = config.security;
|
|
115
|
+
}
|
|
116
|
+
const allSchemas = {};
|
|
117
|
+
const modelMap = /* @__PURE__ */ new Map();
|
|
118
|
+
for (const contractRoot of contractRoots) {
|
|
119
|
+
for (const model of contractRoot.models) {
|
|
120
|
+
modelMap.set(model.name, model);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
for (const contractRoot of contractRoots) {
|
|
124
|
+
for (const model of contractRoot.models) {
|
|
125
|
+
allSchemas[model.name] = modelToSchema(model, modelMap);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
const paths = {};
|
|
129
|
+
for (const opRoot of opRoots) {
|
|
130
|
+
for (const route of opRoot.routes) {
|
|
131
|
+
const oaPath = convertPath(route.path);
|
|
132
|
+
for (const op of route.operations) {
|
|
133
|
+
const mods = resolveModifiers(route, op);
|
|
134
|
+
if (!includeInternal && mods.includes("internal")) continue;
|
|
135
|
+
if (!paths[oaPath]) paths[oaPath] = {};
|
|
136
|
+
const operation = buildOperation(route, op);
|
|
137
|
+
if (mods.includes("deprecated")) operation.deprecated = true;
|
|
138
|
+
paths[oaPath][op.method] = operation;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
doc.paths = paths;
|
|
143
|
+
const schemas = opRoots.length > 0 ? (() => {
|
|
144
|
+
const reachable = computeReachableSchemas(collectPublicTypeRefs(opRoots, includeInternal), modelMap);
|
|
145
|
+
const filtered = {};
|
|
146
|
+
for (const [name, schema] of Object.entries(allSchemas)) {
|
|
147
|
+
if (reachable.has(name)) filtered[name] = schema;
|
|
148
|
+
}
|
|
149
|
+
return filtered;
|
|
150
|
+
})() : allSchemas;
|
|
151
|
+
const components = {};
|
|
152
|
+
if (Object.keys(schemas).length > 0) {
|
|
153
|
+
components.schemas = schemas;
|
|
154
|
+
}
|
|
155
|
+
if (securitySchemes && Object.keys(securitySchemes).length > 0) {
|
|
156
|
+
components.securitySchemes = securitySchemes;
|
|
157
|
+
}
|
|
158
|
+
if (Object.keys(components).length > 0) {
|
|
159
|
+
doc.components = components;
|
|
160
|
+
}
|
|
161
|
+
return toYaml(doc);
|
|
162
|
+
}
|
|
163
|
+
__name(generateOpenApi, "generateOpenApi");
|
|
164
|
+
function convertPath(path) {
|
|
165
|
+
return path;
|
|
166
|
+
}
|
|
167
|
+
__name(convertPath, "convertPath");
|
|
168
|
+
function modelToSchema(model, modelMap) {
|
|
169
|
+
if (model.type) {
|
|
170
|
+
const schema2 = typeToSchema(model.type, modelMap);
|
|
171
|
+
if (model.description) schema2.description = model.description;
|
|
172
|
+
return schema2;
|
|
173
|
+
}
|
|
174
|
+
const properties = {};
|
|
175
|
+
const required = [];
|
|
176
|
+
for (const field of model.fields) {
|
|
177
|
+
const prop = fieldToSchema(field, modelMap);
|
|
178
|
+
properties[field.name] = prop;
|
|
179
|
+
if (!field.optional) {
|
|
180
|
+
required.push(field.name);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
const schema = {
|
|
184
|
+
type: "object",
|
|
185
|
+
properties
|
|
186
|
+
};
|
|
187
|
+
if (required.length > 0) {
|
|
188
|
+
schema.required = required;
|
|
189
|
+
}
|
|
190
|
+
if (model.bases && model.bases.length > 0) {
|
|
191
|
+
return {
|
|
192
|
+
allOf: [
|
|
193
|
+
...model.bases.map((b) => ({
|
|
194
|
+
$ref: `#/components/schemas/${b}`
|
|
195
|
+
})),
|
|
196
|
+
schema
|
|
197
|
+
]
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
if (model.description) {
|
|
201
|
+
schema.description = model.description;
|
|
202
|
+
}
|
|
203
|
+
if (model.deprecated) {
|
|
204
|
+
schema.deprecated = true;
|
|
205
|
+
}
|
|
206
|
+
return schema;
|
|
207
|
+
}
|
|
208
|
+
__name(modelToSchema, "modelToSchema");
|
|
209
|
+
function fieldToSchema(field, modelMap) {
|
|
210
|
+
let schema = typeToSchema(field.type, modelMap);
|
|
211
|
+
if (field.nullable) {
|
|
212
|
+
schema = wrapNullable(schema);
|
|
213
|
+
}
|
|
214
|
+
if (field.visibility === "readonly") {
|
|
215
|
+
schema.readOnly = true;
|
|
216
|
+
} else if (field.visibility === "writeonly") {
|
|
217
|
+
schema.writeOnly = true;
|
|
218
|
+
}
|
|
219
|
+
if (field.default !== void 0) {
|
|
220
|
+
schema.default = field.default;
|
|
221
|
+
}
|
|
222
|
+
if (field.description) {
|
|
223
|
+
schema.description = field.description;
|
|
224
|
+
}
|
|
225
|
+
if (field.deprecated) {
|
|
226
|
+
schema.deprecated = true;
|
|
227
|
+
}
|
|
228
|
+
return schema;
|
|
229
|
+
}
|
|
230
|
+
__name(fieldToSchema, "fieldToSchema");
|
|
231
|
+
function typeToSchema(type, modelMap) {
|
|
232
|
+
switch (type.kind) {
|
|
233
|
+
case "scalar":
|
|
234
|
+
return scalarToSchema(type);
|
|
235
|
+
case "array":
|
|
236
|
+
return arrayToSchema(type, modelMap);
|
|
237
|
+
case "tuple":
|
|
238
|
+
return {
|
|
239
|
+
type: "array",
|
|
240
|
+
prefixItems: type.items.map((i) => typeToSchema(i, modelMap))
|
|
241
|
+
};
|
|
242
|
+
case "record":
|
|
243
|
+
return {
|
|
244
|
+
type: "object",
|
|
245
|
+
additionalProperties: typeToSchema(type.value, modelMap)
|
|
246
|
+
};
|
|
247
|
+
case "enum":
|
|
248
|
+
return {
|
|
249
|
+
type: "string",
|
|
250
|
+
enum: type.values
|
|
251
|
+
};
|
|
252
|
+
case "literal":
|
|
253
|
+
return {
|
|
254
|
+
const: type.value
|
|
255
|
+
};
|
|
256
|
+
case "union":
|
|
257
|
+
return {
|
|
258
|
+
oneOf: type.members.map((m) => typeToSchema(m, modelMap))
|
|
259
|
+
};
|
|
260
|
+
case "discriminatedUnion": {
|
|
261
|
+
const oneOf = type.members.map((m) => typeToSchema(m, modelMap));
|
|
262
|
+
const mapping = {};
|
|
263
|
+
for (const member of type.members) {
|
|
264
|
+
if (member.kind !== "ref") continue;
|
|
265
|
+
const literalValues = resolveDiscriminatorLiterals(member.name, type.discriminator, modelMap);
|
|
266
|
+
if (literalValues.length === 0) continue;
|
|
267
|
+
for (const v of literalValues) {
|
|
268
|
+
mapping[v] = `#/components/schemas/${member.name}`;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
const result = {
|
|
272
|
+
oneOf,
|
|
273
|
+
discriminator: {
|
|
274
|
+
propertyName: type.discriminator
|
|
275
|
+
}
|
|
276
|
+
};
|
|
277
|
+
if (Object.keys(mapping).length > 0) {
|
|
278
|
+
result.discriminator.mapping = mapping;
|
|
279
|
+
}
|
|
280
|
+
return result;
|
|
281
|
+
}
|
|
282
|
+
case "intersection":
|
|
283
|
+
return {
|
|
284
|
+
allOf: type.members.map((m) => typeToSchema(m, modelMap))
|
|
285
|
+
};
|
|
286
|
+
case "ref":
|
|
287
|
+
return {
|
|
288
|
+
$ref: `#/components/schemas/${type.name}`
|
|
289
|
+
};
|
|
290
|
+
case "inlineObject":
|
|
291
|
+
return inlineObjectToSchema(type.fields, modelMap);
|
|
292
|
+
case "lazy":
|
|
293
|
+
return typeToSchema(type.inner, modelMap);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
__name(typeToSchema, "typeToSchema");
|
|
297
|
+
function resolveDiscriminatorLiterals(modelName, discriminator, modelMap) {
|
|
298
|
+
if (!modelMap) return [];
|
|
299
|
+
const model = modelMap.get(modelName);
|
|
300
|
+
if (!model) return [];
|
|
301
|
+
const field = model.fields.find((f) => f.name === discriminator);
|
|
302
|
+
if (!field) return [];
|
|
303
|
+
if (field.type.kind === "literal") return [
|
|
304
|
+
String(field.type.value)
|
|
305
|
+
];
|
|
306
|
+
if (field.type.kind === "enum") return field.type.values;
|
|
307
|
+
return [];
|
|
308
|
+
}
|
|
309
|
+
__name(resolveDiscriminatorLiterals, "resolveDiscriminatorLiterals");
|
|
310
|
+
function scalarToSchema(type) {
|
|
311
|
+
const s = {};
|
|
312
|
+
switch (type.name) {
|
|
313
|
+
case "string":
|
|
314
|
+
s.type = "string";
|
|
315
|
+
if (type.min !== void 0) s.minLength = Number(type.min);
|
|
316
|
+
if (type.max !== void 0) s.maxLength = Number(type.max);
|
|
317
|
+
if (type.len !== void 0) {
|
|
318
|
+
s.minLength = type.len;
|
|
319
|
+
s.maxLength = type.len;
|
|
320
|
+
}
|
|
321
|
+
if (type.regex) s.pattern = type.regex;
|
|
322
|
+
break;
|
|
323
|
+
case "number":
|
|
324
|
+
s.type = "number";
|
|
325
|
+
if (type.min !== void 0) s.minimum = Number(type.min);
|
|
326
|
+
if (type.max !== void 0) s.maximum = Number(type.max);
|
|
327
|
+
break;
|
|
328
|
+
case "int":
|
|
329
|
+
s.type = "integer";
|
|
330
|
+
if (type.min !== void 0) s.minimum = Number(type.min);
|
|
331
|
+
if (type.max !== void 0) s.maximum = Number(type.max);
|
|
332
|
+
break;
|
|
333
|
+
case "bigint":
|
|
334
|
+
s.type = "integer";
|
|
335
|
+
s.format = "int64";
|
|
336
|
+
break;
|
|
337
|
+
case "boolean":
|
|
338
|
+
s.type = "boolean";
|
|
339
|
+
break;
|
|
340
|
+
case "date":
|
|
341
|
+
s.type = "string";
|
|
342
|
+
s.format = "date";
|
|
343
|
+
break;
|
|
344
|
+
case "datetime":
|
|
345
|
+
s.type = "string";
|
|
346
|
+
s.format = "date-time";
|
|
347
|
+
break;
|
|
348
|
+
case "duration":
|
|
349
|
+
s.type = "string";
|
|
350
|
+
s.format = "duration";
|
|
351
|
+
break;
|
|
352
|
+
case "email":
|
|
353
|
+
s.type = "string";
|
|
354
|
+
s.format = "email";
|
|
355
|
+
break;
|
|
356
|
+
case "url":
|
|
357
|
+
s.type = "string";
|
|
358
|
+
s.format = "uri";
|
|
359
|
+
break;
|
|
360
|
+
case "uuid":
|
|
361
|
+
s.type = "string";
|
|
362
|
+
s.format = "uuid";
|
|
363
|
+
break;
|
|
364
|
+
case "unknown":
|
|
365
|
+
break;
|
|
366
|
+
case "null":
|
|
367
|
+
s.type = "null";
|
|
368
|
+
break;
|
|
369
|
+
case "object":
|
|
370
|
+
s.type = "object";
|
|
371
|
+
break;
|
|
372
|
+
case "binary":
|
|
373
|
+
s.type = "string";
|
|
374
|
+
s.format = "binary";
|
|
375
|
+
break;
|
|
376
|
+
case "json":
|
|
377
|
+
break;
|
|
378
|
+
}
|
|
379
|
+
return s;
|
|
380
|
+
}
|
|
381
|
+
__name(scalarToSchema, "scalarToSchema");
|
|
382
|
+
function arrayToSchema(type, modelMap) {
|
|
383
|
+
const s = {
|
|
384
|
+
type: "array",
|
|
385
|
+
items: typeToSchema(type.item, modelMap)
|
|
386
|
+
};
|
|
387
|
+
if (type.min !== void 0) s.minItems = type.min;
|
|
388
|
+
if (type.max !== void 0) s.maxItems = type.max;
|
|
389
|
+
return s;
|
|
390
|
+
}
|
|
391
|
+
__name(arrayToSchema, "arrayToSchema");
|
|
392
|
+
function inlineObjectToSchema(fields, modelMap) {
|
|
393
|
+
const properties = {};
|
|
394
|
+
const required = [];
|
|
395
|
+
for (const field of fields) {
|
|
396
|
+
properties[field.name] = fieldToSchema(field, modelMap);
|
|
397
|
+
if (!field.optional) {
|
|
398
|
+
required.push(field.name);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
const schema = {
|
|
402
|
+
type: "object",
|
|
403
|
+
properties
|
|
404
|
+
};
|
|
405
|
+
if (required.length > 0) schema.required = required;
|
|
406
|
+
return schema;
|
|
407
|
+
}
|
|
408
|
+
__name(inlineObjectToSchema, "inlineObjectToSchema");
|
|
409
|
+
function wrapNullable(schema) {
|
|
410
|
+
if (schema.$ref) {
|
|
411
|
+
return {
|
|
412
|
+
oneOf: [
|
|
413
|
+
schema,
|
|
414
|
+
{
|
|
415
|
+
type: "null"
|
|
416
|
+
}
|
|
417
|
+
]
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
if (typeof schema.type === "string") {
|
|
421
|
+
schema.type = [
|
|
422
|
+
schema.type,
|
|
423
|
+
"null"
|
|
424
|
+
];
|
|
425
|
+
}
|
|
426
|
+
return schema;
|
|
427
|
+
}
|
|
428
|
+
__name(wrapNullable, "wrapNullable");
|
|
429
|
+
function buildOperation(route, op) {
|
|
430
|
+
const operation = {};
|
|
431
|
+
if (op.sdk) {
|
|
432
|
+
operation.operationId = op.sdk;
|
|
433
|
+
} else if (op.service) {
|
|
434
|
+
const methodPart = op.service.split(".").pop();
|
|
435
|
+
if (methodPart) operation.operationId = methodPart;
|
|
436
|
+
}
|
|
437
|
+
if (op.description) {
|
|
438
|
+
operation.description = op.description;
|
|
439
|
+
}
|
|
440
|
+
const parameters = [];
|
|
441
|
+
if (route.params) {
|
|
442
|
+
parameters.push(...paramSourceToParams(route.params, "path"));
|
|
443
|
+
}
|
|
444
|
+
if (op.query) {
|
|
445
|
+
parameters.push(...paramSourceToParams(op.query, "query"));
|
|
446
|
+
}
|
|
447
|
+
if (op.headers) {
|
|
448
|
+
parameters.push(...paramSourceToParams(op.headers, "header"));
|
|
449
|
+
}
|
|
450
|
+
if (parameters.length > 0) {
|
|
451
|
+
operation.parameters = parameters;
|
|
452
|
+
}
|
|
453
|
+
if (op.request && op.request.bodies.length > 0) {
|
|
454
|
+
const content = {};
|
|
455
|
+
for (const body of op.request.bodies) {
|
|
456
|
+
content[body.contentType] = {
|
|
457
|
+
schema: typeToSchema(body.bodyType)
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
operation.requestBody = {
|
|
461
|
+
required: true,
|
|
462
|
+
content
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
const effectiveSecurity = resolveSecurity(route, op);
|
|
466
|
+
if (effectiveSecurity === SECURITY_NONE) {
|
|
467
|
+
operation.security = [];
|
|
468
|
+
}
|
|
469
|
+
const responses = {};
|
|
470
|
+
for (const resp of op.responses) {
|
|
471
|
+
const statusKey = String(resp.statusCode);
|
|
472
|
+
const responseObject = {
|
|
473
|
+
description: statusDescription(resp.statusCode)
|
|
474
|
+
};
|
|
475
|
+
if (resp.bodyType && resp.contentType) {
|
|
476
|
+
responseObject.content = {
|
|
477
|
+
[resp.contentType]: {
|
|
478
|
+
schema: typeToSchema(resp.bodyType)
|
|
479
|
+
}
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
if (resp.headers && resp.headers.length > 0) {
|
|
483
|
+
const headers = {};
|
|
484
|
+
for (const h of resp.headers) {
|
|
485
|
+
const headerObject = {
|
|
486
|
+
schema: typeToSchema(h.type)
|
|
487
|
+
};
|
|
488
|
+
if (!h.optional) headerObject.required = true;
|
|
489
|
+
if (h.description) headerObject.description = h.description;
|
|
490
|
+
headers[h.name] = headerObject;
|
|
491
|
+
}
|
|
492
|
+
responseObject.headers = headers;
|
|
493
|
+
}
|
|
494
|
+
responses[statusKey] = responseObject;
|
|
495
|
+
}
|
|
496
|
+
if (Object.keys(responses).length > 0) {
|
|
497
|
+
operation.responses = responses;
|
|
498
|
+
}
|
|
499
|
+
return operation;
|
|
500
|
+
}
|
|
501
|
+
__name(buildOperation, "buildOperation");
|
|
502
|
+
function paramSourceToParams(source, location) {
|
|
503
|
+
if (source.kind === "ref") {
|
|
504
|
+
return [
|
|
505
|
+
{
|
|
506
|
+
name: source.name,
|
|
507
|
+
in: location,
|
|
508
|
+
required: location === "path",
|
|
509
|
+
schema: {
|
|
510
|
+
$ref: `#/components/schemas/${source.name}`
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
];
|
|
514
|
+
}
|
|
515
|
+
if (source.kind === "params") {
|
|
516
|
+
return source.nodes.map((p) => ({
|
|
517
|
+
name: p.name,
|
|
518
|
+
in: location,
|
|
519
|
+
required: location === "path",
|
|
520
|
+
schema: typeToSchema(p.type)
|
|
521
|
+
}));
|
|
522
|
+
}
|
|
523
|
+
if (source.node.kind === "inlineObject") {
|
|
524
|
+
return source.node.fields.map((f) => ({
|
|
525
|
+
name: f.name,
|
|
526
|
+
in: location,
|
|
527
|
+
required: location === "path" ? true : !f.optional,
|
|
528
|
+
schema: typeToSchema(f.type)
|
|
529
|
+
}));
|
|
530
|
+
}
|
|
531
|
+
if (source.node.kind === "ref") {
|
|
532
|
+
return [
|
|
533
|
+
{
|
|
534
|
+
name: source.node.name,
|
|
535
|
+
in: location,
|
|
536
|
+
required: location === "path",
|
|
537
|
+
schema: {
|
|
538
|
+
$ref: `#/components/schemas/${source.node.name}`
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
];
|
|
542
|
+
}
|
|
543
|
+
return [];
|
|
544
|
+
}
|
|
545
|
+
__name(paramSourceToParams, "paramSourceToParams");
|
|
546
|
+
function statusDescription(code) {
|
|
547
|
+
const descriptions = {
|
|
548
|
+
200: "Successful response",
|
|
549
|
+
201: "Created",
|
|
550
|
+
204: "No content",
|
|
551
|
+
400: "Bad request",
|
|
552
|
+
401: "Unauthorized",
|
|
553
|
+
403: "Forbidden",
|
|
554
|
+
404: "Not found",
|
|
555
|
+
409: "Conflict",
|
|
556
|
+
422: "Unprocessable entity",
|
|
557
|
+
500: "Internal server error"
|
|
558
|
+
};
|
|
559
|
+
return descriptions[code] ?? `Response ${code}`;
|
|
560
|
+
}
|
|
561
|
+
__name(statusDescription, "statusDescription");
|
|
562
|
+
function toYaml(value, indent = 0) {
|
|
563
|
+
if (value === null || value === void 0) return "null";
|
|
564
|
+
if (typeof value === "boolean") return value ? "true" : "false";
|
|
565
|
+
if (typeof value === "number") return String(value);
|
|
566
|
+
if (typeof value === "bigint") return String(value);
|
|
567
|
+
if (typeof value === "string") {
|
|
568
|
+
return yamlString(value);
|
|
569
|
+
}
|
|
570
|
+
if (Array.isArray(value)) {
|
|
571
|
+
if (value.length === 0) return "[]";
|
|
572
|
+
if (value.every((v) => typeof v === "string" || typeof v === "number" || typeof v === "boolean")) {
|
|
573
|
+
const items = value.map((v) => typeof v === "string" ? yamlString(v) : String(v));
|
|
574
|
+
const inline = `[${items.join(", ")}]`;
|
|
575
|
+
if (inline.length < 80) return inline;
|
|
576
|
+
}
|
|
577
|
+
const lines = [];
|
|
578
|
+
const pad = " ".repeat(indent);
|
|
579
|
+
for (const item of value) {
|
|
580
|
+
if (isPlainObject(item)) {
|
|
581
|
+
const entries = Object.entries(item);
|
|
582
|
+
if (entries.length > 0) {
|
|
583
|
+
const [firstKey, firstVal] = entries[0];
|
|
584
|
+
const firstValStr = isComplex(firstVal) ? `
|
|
585
|
+
${toYamlValue(firstVal, indent + 2)}` : ` ${toYaml(firstVal, indent + 2)}`;
|
|
586
|
+
lines.push(`${pad}- ${yamlKey(firstKey)}:${firstValStr}`);
|
|
587
|
+
for (let i = 1; i < entries.length; i++) {
|
|
588
|
+
const [k, v] = entries[i];
|
|
589
|
+
const valStr = isComplex(v) ? `
|
|
590
|
+
${toYamlValue(v, indent + 2)}` : ` ${toYaml(v, indent + 2)}`;
|
|
591
|
+
lines.push(`${pad} ${yamlKey(k)}:${valStr}`);
|
|
592
|
+
}
|
|
593
|
+
} else {
|
|
594
|
+
lines.push(`${pad}- {}`);
|
|
595
|
+
}
|
|
596
|
+
} else {
|
|
597
|
+
lines.push(`${pad}- ${toYaml(item, indent + 1)}`);
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
return lines.join("\n");
|
|
601
|
+
}
|
|
602
|
+
if (isPlainObject(value)) {
|
|
603
|
+
const obj = value;
|
|
604
|
+
const entries = Object.entries(obj);
|
|
605
|
+
if (entries.length === 0) return "{}";
|
|
606
|
+
const pad = " ".repeat(indent);
|
|
607
|
+
const lines = [];
|
|
608
|
+
for (const [key, val] of entries) {
|
|
609
|
+
if (isComplex(val)) {
|
|
610
|
+
lines.push(`${pad}${yamlKey(key)}:`);
|
|
611
|
+
lines.push(toYamlValue(val, indent + 1));
|
|
612
|
+
} else {
|
|
613
|
+
lines.push(`${pad}${yamlKey(key)}: ${toYaml(val, indent + 1)}`);
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
return lines.join("\n");
|
|
617
|
+
}
|
|
618
|
+
return String(value);
|
|
619
|
+
}
|
|
620
|
+
__name(toYaml, "toYaml");
|
|
621
|
+
function toYamlValue(value, indent) {
|
|
622
|
+
if (Array.isArray(value)) {
|
|
623
|
+
return toYaml(value, indent);
|
|
624
|
+
}
|
|
625
|
+
if (isPlainObject(value)) {
|
|
626
|
+
return toYaml(value, indent);
|
|
627
|
+
}
|
|
628
|
+
return " ".repeat(indent) + toYaml(value, indent);
|
|
629
|
+
}
|
|
630
|
+
__name(toYamlValue, "toYamlValue");
|
|
631
|
+
function isComplex(value) {
|
|
632
|
+
if (Array.isArray(value)) {
|
|
633
|
+
if (value.every((v) => typeof v === "string" || typeof v === "number" || typeof v === "boolean")) {
|
|
634
|
+
const items = value.map((v) => typeof v === "string" ? yamlString(v) : String(v));
|
|
635
|
+
return `[${items.join(", ")}]`.length >= 80;
|
|
636
|
+
}
|
|
637
|
+
return true;
|
|
638
|
+
}
|
|
639
|
+
return isPlainObject(value);
|
|
640
|
+
}
|
|
641
|
+
__name(isComplex, "isComplex");
|
|
642
|
+
function isPlainObject(value) {
|
|
643
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
644
|
+
}
|
|
645
|
+
__name(isPlainObject, "isPlainObject");
|
|
646
|
+
function yamlString(s) {
|
|
647
|
+
if (s === "") return "''";
|
|
648
|
+
if (/^[\w./-]+$/.test(s) && !/^(true|false|null|yes|no|on|off)$/i.test(s) && !/^\d/.test(s)) {
|
|
649
|
+
return s;
|
|
650
|
+
}
|
|
651
|
+
return `'${s.replace(/'/g, "''")}'`;
|
|
652
|
+
}
|
|
653
|
+
__name(yamlString, "yamlString");
|
|
654
|
+
function yamlKey(key) {
|
|
655
|
+
if (/^[\w-]+$/.test(key) && !/^(true|false|null|yes|no|on|off)$/i.test(key)) {
|
|
656
|
+
return key;
|
|
657
|
+
}
|
|
658
|
+
return `'${key.replace(/'/g, "''")}'`;
|
|
659
|
+
}
|
|
660
|
+
__name(yamlKey, "yamlKey");
|
|
661
|
+
|
|
662
|
+
// src/index.ts
|
|
663
|
+
var plugin = {
|
|
664
|
+
name: "openapi",
|
|
665
|
+
cacheKey: "openapi",
|
|
666
|
+
async generateTargets({ contractRoots, opRoots }, ctx) {
|
|
667
|
+
const { securitySchemes, ...openapiConfig } = ctx.options;
|
|
668
|
+
const base = openapiConfig.baseDir ? resolve(ctx.rootDir, openapiConfig.baseDir) : ctx.rootDir;
|
|
669
|
+
const outPath = resolve(base, openapiConfig.output ?? "openapi.yaml");
|
|
670
|
+
ctx.emitFile(outPath, generateOpenApi({
|
|
671
|
+
contractRoots,
|
|
672
|
+
opRoots,
|
|
673
|
+
config: openapiConfig,
|
|
674
|
+
securitySchemes
|
|
675
|
+
}));
|
|
676
|
+
}
|
|
677
|
+
};
|
|
678
|
+
var index_default = plugin;
|
|
679
|
+
function createOpenApiPlugin(openapiConfig, rootDir, securitySchemes) {
|
|
680
|
+
return {
|
|
681
|
+
name: "openapi",
|
|
682
|
+
cacheKey: `openapi:${JSON.stringify(openapiConfig)}`,
|
|
683
|
+
async generateTargets({ contractRoots, opRoots }, ctx) {
|
|
684
|
+
const base = openapiConfig.baseDir ? resolve(rootDir, openapiConfig.baseDir) : rootDir;
|
|
685
|
+
const outPath = resolve(base, openapiConfig.output ?? "openapi.yaml");
|
|
686
|
+
const content = generateOpenApi({
|
|
687
|
+
contractRoots,
|
|
688
|
+
opRoots,
|
|
689
|
+
config: openapiConfig,
|
|
690
|
+
securitySchemes
|
|
691
|
+
});
|
|
692
|
+
ctx.emitFile(outPath, content);
|
|
693
|
+
}
|
|
694
|
+
};
|
|
695
|
+
}
|
|
696
|
+
__name(createOpenApiPlugin, "createOpenApiPlugin");
|
|
697
|
+
export {
|
|
698
|
+
createOpenApiPlugin,
|
|
699
|
+
index_default as default
|
|
700
|
+
};
|
|
701
|
+
//# sourceMappingURL=index.js.map
|