@xoong/cli 0.0.0-stage → 0.0.35
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 +21 -0
- package/README.md +26 -2
- package/package.json +47 -4
- package/src/body-schema.js +161 -0
- package/src/build.js +211 -0
- package/src/check.js +347 -0
- package/src/cli.js +101 -0
- package/src/commands.js +55 -0
- package/src/dev.js +48 -0
- package/src/html-data.js +85 -0
- package/src/i18n-extract.js +78 -0
- package/src/index.js +2 -0
- package/src/openapi.js +474 -0
- package/src/scaffold.js +159 -0
- package/src/swagger2.js +88 -0
package/src/swagger2.js
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
/**
|
|
3
|
+
* Swagger 2.0 -> OpenAPI 3.0 for what xoong reads: paths, parameters, request bodies, responses, schemas.
|
|
4
|
+
* Not a general converter (security schemes, examples, callbacks and `x-` extensions other than on schemas are dropped);
|
|
5
|
+
* for those use `npx swagger2openapi`. Pure function: returns a new document, never modifies the input.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const JSON_MIME = 'application/json';
|
|
9
|
+
|
|
10
|
+
/** A Swagger 2 `type: file` schema is a binary string in OpenAPI 3. */
|
|
11
|
+
const fileSchema = (schema) => (schema?.type === 'file' ? { type: 'string', format: 'binary' } : schema);
|
|
12
|
+
|
|
13
|
+
/** Rewrite "#/definitions/X" (and parameters/responses) references into their OpenAPI 3 homes. */
|
|
14
|
+
function rewriteRefs(node) {
|
|
15
|
+
if (Array.isArray(node)) return node.map(rewriteRefs);
|
|
16
|
+
if (!node || typeof node !== 'object') return node;
|
|
17
|
+
const out = {};
|
|
18
|
+
for (const [k, v] of Object.entries(node)) {
|
|
19
|
+
if (k === '$ref' && typeof v === 'string') out[k] = v.replace('#/definitions/', '#/components/schemas/').replace('#/parameters/', '#/components/parameters/').replace('#/responses/', '#/components/responses/');
|
|
20
|
+
else if (k === 'x-nullable') { if (v) out.nullable = true; } else out[k] = rewriteRefs(v);
|
|
21
|
+
}
|
|
22
|
+
return out;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** A Swagger 2 non-body parameter carries its schema inline: move it under `schema`. */
|
|
26
|
+
function parameter3(p, doc) {
|
|
27
|
+
if (p.$ref) return rewriteRefs(p);
|
|
28
|
+
const { name, in: where, description, required, ...rest } = p;
|
|
29
|
+
const schema = {};
|
|
30
|
+
for (const k of ['type', 'format', 'items', 'enum', 'default', 'minimum', 'maximum', 'minLength', 'maxLength', 'pattern']) if (k in rest) schema[k] = rest[k];
|
|
31
|
+
// Swagger 2 `collectionFormat`: csv (the default) is "a=1,2", multi is "a=1&a=2".
|
|
32
|
+
const style = schema.type === 'array' && (where === 'query' || where === 'header' || where === 'path')
|
|
33
|
+
? (rest.collectionFormat === 'multi' ? { style: 'form', explode: true } : rest.collectionFormat === 'ssv' ? { style: 'spaceDelimited', explode: false } : rest.collectionFormat === 'pipes' ? { style: 'pipeDelimited', explode: false } : { style: where === 'query' ? 'form' : 'simple', explode: false })
|
|
34
|
+
: {};
|
|
35
|
+
return { name, in: where, ...(description ? { description } : {}), ...(required || where === 'path' ? { required: true } : {}), ...style, schema: rewriteRefs(schema) };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** @param {any} doc a Swagger 2.0 document @returns {any} */
|
|
39
|
+
export function swagger2ToOpenApi3(doc) {
|
|
40
|
+
const globalConsumes = doc.consumes ?? [JSON_MIME];
|
|
41
|
+
const globalProduces = doc.produces ?? [JSON_MIME];
|
|
42
|
+
const resolveParam = (p) => (p.$ref ? doc.parameters?.[p.$ref.split('/').pop()] ?? p : p);
|
|
43
|
+
const paths = {};
|
|
44
|
+
for (const [path, item] of Object.entries(doc.paths ?? {})) {
|
|
45
|
+
const out = {};
|
|
46
|
+
const shared = (item.parameters ?? []).map(resolveParam);
|
|
47
|
+
for (const [method, op] of Object.entries(item)) {
|
|
48
|
+
if (!['get', 'put', 'post', 'delete', 'options', 'head', 'patch'].includes(method)) continue;
|
|
49
|
+
const params = [...shared.filter((p) => !(op.parameters ?? []).some((o) => resolveParam(o).name === p.name && resolveParam(o).in === p.in)), ...(op.parameters ?? []).map(resolveParam)];
|
|
50
|
+
const body = params.find((p) => p.in === 'body');
|
|
51
|
+
const form = params.filter((p) => p.in === 'formData');
|
|
52
|
+
const consumes = op.consumes ?? globalConsumes;
|
|
53
|
+
const produces = op.produces ?? globalProduces;
|
|
54
|
+
const o = { ...(op.operationId ? { operationId: op.operationId } : {}), ...(op.summary ? { summary: op.summary } : {}), ...(op.tags ? { tags: op.tags } : {}) };
|
|
55
|
+
const plain = params.filter((p) => p.in !== 'body' && p.in !== 'formData').map((p) => parameter3(p, doc));
|
|
56
|
+
if (plain.length) o.parameters = plain;
|
|
57
|
+
if (body) {
|
|
58
|
+
o.requestBody = { ...(body.required ? { required: true } : {}), content: Object.fromEntries((consumes.length ? consumes : [JSON_MIME]).map((m) => [m, { schema: rewriteRefs(body.schema) }])) };
|
|
59
|
+
} else if (form.length) {
|
|
60
|
+
const multipart = consumes.includes('multipart/form-data') || form.some((f) => f.type === 'file');
|
|
61
|
+
const properties = Object.fromEntries(form.map((f) => [f.name, f.type === 'file' ? { type: 'string', format: 'binary' } : rewriteRefs(Object.fromEntries(['type', 'format', 'enum', 'items', 'default', 'minimum', 'maximum', 'minLength', 'maxLength', 'pattern'].filter((k) => k in f).map((k) => [k, f[k]])))]));
|
|
62
|
+
const required = form.filter((f) => f.required).map((f) => f.name);
|
|
63
|
+
o.requestBody = { content: { [multipart ? 'multipart/form-data' : 'application/x-www-form-urlencoded']: { schema: { type: 'object', properties, ...(required.length ? { required } : {}) } } } };
|
|
64
|
+
}
|
|
65
|
+
o.responses = {};
|
|
66
|
+
for (const [code, r] of Object.entries(op.responses ?? {})) {
|
|
67
|
+
if (r.$ref) { o.responses[code] = rewriteRefs(r); continue; }
|
|
68
|
+
const res = { description: r.description ?? '' };
|
|
69
|
+
if (r.schema) res.content = Object.fromEntries((produces.length ? produces : [JSON_MIME]).map((m) => [m, { schema: rewriteRefs(fileSchema(r.schema)) }]));
|
|
70
|
+
o.responses[code] = res;
|
|
71
|
+
}
|
|
72
|
+
out[method] = o;
|
|
73
|
+
}
|
|
74
|
+
paths[path] = out;
|
|
75
|
+
}
|
|
76
|
+
const responses = Object.fromEntries(Object.entries(doc.responses ?? {}).map(([k, r]) => [k, { description: r.description ?? '', ...(r.schema ? { content: { [JSON_MIME]: { schema: rewriteRefs(r.schema) } } } : {}) }]));
|
|
77
|
+
return {
|
|
78
|
+
openapi: '3.0.3',
|
|
79
|
+
info: doc.info ?? { title: 'API', version: '0' },
|
|
80
|
+
...(doc.host ? { servers: [{ url: `${(doc.schemes ?? ['https'])[0]}://${doc.host}${doc.basePath ?? ''}` }] } : doc.basePath ? { servers: [{ url: doc.basePath }] } : {}),
|
|
81
|
+
paths,
|
|
82
|
+
components: {
|
|
83
|
+
schemas: rewriteRefs(doc.definitions ?? {}),
|
|
84
|
+
...(Object.keys(responses).length ? { responses } : {}),
|
|
85
|
+
...(doc.parameters ? { parameters: Object.fromEntries(Object.entries(doc.parameters).filter(([, p]) => p.in !== 'body').map(([k, p]) => [k, parameter3(p, doc)])) } : {}),
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
}
|