@openapi-qraft/plugin 1.8.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/CHANGELOG.md +7 -0
- package/README.md +1 -0
- package/dist/lib/GeneratorFile.d.ts +9 -0
- package/dist/lib/GeneratorFile.js +2 -0
- package/dist/lib/GeneratorFile.js.map +1 -0
- package/dist/lib/OutputOptions.d.ts +6 -0
- package/dist/lib/OutputOptions.js +2 -0
- package/dist/lib/OutputOptions.js.map +1 -0
- package/dist/lib/QraftCommand.d.ts +35 -0
- package/dist/lib/QraftCommand.js +103 -0
- package/dist/lib/QraftCommand.js.map +1 -0
- package/dist/lib/QraftCommandPlugin.d.ts +4 -0
- package/dist/lib/QraftCommandPlugin.js +2 -0
- package/dist/lib/QraftCommandPlugin.js.map +1 -0
- package/dist/lib/fileHeader.d.ts +1 -0
- package/dist/lib/fileHeader.js +3 -0
- package/dist/lib/fileHeader.js.map +1 -0
- package/dist/lib/open-api/OpenAPISchemaType.d.ts +31 -0
- package/dist/lib/open-api/OpenAPISchemaType.js +2 -0
- package/dist/lib/open-api/OpenAPISchemaType.js.map +1 -0
- package/dist/lib/open-api/getContent.d.ts +5 -0
- package/dist/lib/open-api/getContent.js +23 -0
- package/dist/lib/open-api/getContent.js.map +1 -0
- package/dist/lib/open-api/getDocumentServices.d.ts +12 -0
- package/dist/lib/open-api/getDocumentServices.js +7 -0
- package/dist/lib/open-api/getDocumentServices.js.map +1 -0
- package/dist/lib/open-api/getOperationName.d.ts +6 -0
- package/dist/lib/open-api/getOperationName.js +20 -0
- package/dist/lib/open-api/getOperationName.js.map +1 -0
- package/dist/lib/open-api/getServiceName.d.ts +5 -0
- package/dist/lib/open-api/getServiceName.js +13 -0
- package/dist/lib/open-api/getServiceName.js.map +1 -0
- package/dist/lib/open-api/getServiceNamesByOperationEndpoint.d.ts +4 -0
- package/dist/lib/open-api/getServiceNamesByOperationEndpoint.js +30 -0
- package/dist/lib/open-api/getServiceNamesByOperationEndpoint.js.map +1 -0
- package/dist/lib/open-api/getServiceNamesByOperationTags.d.ts +1 -0
- package/dist/lib/open-api/getServiceNamesByOperationTags.js +11 -0
- package/dist/lib/open-api/getServiceNamesByOperationTags.js.map +1 -0
- package/dist/lib/open-api/getServices.d.ts +39 -0
- package/dist/lib/open-api/getServices.js +112 -0
- package/dist/lib/open-api/getServices.js.map +1 -0
- package/dist/lib/open-api/readSchema.d.ts +7 -0
- package/dist/lib/open-api/readSchema.js +15 -0
- package/dist/lib/open-api/readSchema.js.map +1 -0
- package/dist/lib/writeGeneratorFiles.d.ts +6 -0
- package/dist/lib/writeGeneratorFiles.js +51 -0
- package/dist/lib/writeGeneratorFiles.js.map +1 -0
- package/package.json +53 -0
- package/src/lib/GeneratorFile.ts +5 -0
- package/src/lib/OutputOptions.ts +6 -0
- package/src/lib/QraftCommand.ts +192 -0
- package/src/lib/QraftCommandPlugin.ts +5 -0
- package/src/lib/fileHeader.ts +2 -0
- package/src/lib/open-api/OpenAPISchemaType.ts +31 -0
- package/src/lib/open-api/getContent.ts +26 -0
- package/src/lib/open-api/getDocumentServices.ts +19 -0
- package/src/lib/open-api/getOperationName.ts +28 -0
- package/src/lib/open-api/getServiceName.ts +14 -0
- package/src/lib/open-api/getServiceNamesByOperationEndpoint.ts +70 -0
- package/src/lib/open-api/getServiceNamesByOperationTags.ts +15 -0
- package/src/lib/open-api/getServices.ts +208 -0
- package/src/lib/open-api/readSchema.ts +27 -0
- package/src/lib/writeGeneratorFiles.ts +83 -0
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import camelCase from 'camelcase';
|
|
2
|
+
import * as console from 'console';
|
|
3
|
+
import micromatch from 'micromatch';
|
|
4
|
+
|
|
5
|
+
import { getContentMediaType } from './getContent.js';
|
|
6
|
+
import { getOperationName } from './getOperationName.js';
|
|
7
|
+
import {
|
|
8
|
+
getServiceNamesByOperationEndpoint,
|
|
9
|
+
ServiceBaseNameByEndpointOption,
|
|
10
|
+
} from './getServiceNamesByOperationEndpoint.js';
|
|
11
|
+
import { getServiceNamesByOperationTags } from './getServiceNamesByOperationTags.js';
|
|
12
|
+
import type { OpenAPISchemaType } from './OpenAPISchemaType.js';
|
|
13
|
+
|
|
14
|
+
export type ServiceBaseName = ServiceBaseNameByEndpointOption | 'tags';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @deprecated move to separate file
|
|
18
|
+
*/
|
|
19
|
+
export type Service = {
|
|
20
|
+
name: string;
|
|
21
|
+
variableName: string;
|
|
22
|
+
typeName: string;
|
|
23
|
+
fileBaseName: string;
|
|
24
|
+
operations: ServiceOperation[];
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @deprecated move to separate file
|
|
29
|
+
*/
|
|
30
|
+
export type ServiceOperation = {
|
|
31
|
+
method:
|
|
32
|
+
| 'get'
|
|
33
|
+
| 'put'
|
|
34
|
+
| 'post'
|
|
35
|
+
| 'patch'
|
|
36
|
+
| 'delete'
|
|
37
|
+
| 'options'
|
|
38
|
+
| 'head'
|
|
39
|
+
| 'trace';
|
|
40
|
+
path: string;
|
|
41
|
+
name: string;
|
|
42
|
+
description: string | undefined;
|
|
43
|
+
summary: string | undefined;
|
|
44
|
+
deprecated: boolean | undefined;
|
|
45
|
+
mediaType: string | undefined;
|
|
46
|
+
errors: Record<string, string | undefined>;
|
|
47
|
+
success: Record<string, string | undefined>;
|
|
48
|
+
parameters: Record<string, any> | undefined;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export interface ServiceOutputOptions {
|
|
52
|
+
postfixServices?: string; // todo::rename to `postfixService`
|
|
53
|
+
serviceNameBase?: ServiceBaseName;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export const getServices = (
|
|
57
|
+
openApiJson: OpenAPISchemaType,
|
|
58
|
+
{
|
|
59
|
+
postfixServices = 'Service',
|
|
60
|
+
serviceNameBase = 'endpoint[0]',
|
|
61
|
+
}: ServiceOutputOptions = {},
|
|
62
|
+
servicesGlob = ['**']
|
|
63
|
+
) => {
|
|
64
|
+
const paths = openApiJson.paths;
|
|
65
|
+
|
|
66
|
+
const services = new Map<string, Service>();
|
|
67
|
+
|
|
68
|
+
const isPathMatch = createServicePathMatch(servicesGlob);
|
|
69
|
+
|
|
70
|
+
for (const path in paths) {
|
|
71
|
+
if (!Object.prototype.hasOwnProperty.call(paths, path)) continue;
|
|
72
|
+
|
|
73
|
+
for (const method in paths[path]) {
|
|
74
|
+
if (!Object.prototype.hasOwnProperty.call(paths[path], method)) continue;
|
|
75
|
+
if (!isPathMatch(path)) continue;
|
|
76
|
+
|
|
77
|
+
if (!supportedMethod(method)) {
|
|
78
|
+
console.warn(
|
|
79
|
+
`The path "${path}" HTTP method "${method}" is not supported`
|
|
80
|
+
);
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const methodOperation = paths[path][method];
|
|
85
|
+
|
|
86
|
+
const { success, errors } = Object.entries(
|
|
87
|
+
methodOperation.responses
|
|
88
|
+
).reduce(
|
|
89
|
+
(acc, [statusCode, response]) => {
|
|
90
|
+
if (!response.content || typeof response.content !== 'object') {
|
|
91
|
+
return acc;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const statusType =
|
|
95
|
+
statusCode !== 'default' && // See "default" response https://swagger.io/docs/specification/describing-responses/#default
|
|
96
|
+
Number(statusCode) < 400
|
|
97
|
+
? 'success'
|
|
98
|
+
: 'errors';
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
...acc,
|
|
102
|
+
[statusType]: {
|
|
103
|
+
...acc[statusType],
|
|
104
|
+
[statusCode]: getContentMediaType(response.content),
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
},
|
|
108
|
+
{} as Record<'errors' | 'success', Record<string, string | undefined>>
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
const serviceFallbackBaseName = 'Default';
|
|
112
|
+
|
|
113
|
+
const serviceNames =
|
|
114
|
+
serviceNameBase === 'tags'
|
|
115
|
+
? getServiceNamesByOperationTags(
|
|
116
|
+
paths[path][method]?.tags,
|
|
117
|
+
serviceFallbackBaseName
|
|
118
|
+
)
|
|
119
|
+
: getServiceNamesByOperationEndpoint(
|
|
120
|
+
path,
|
|
121
|
+
serviceNameBase,
|
|
122
|
+
serviceFallbackBaseName
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
for (const name of serviceNames) {
|
|
126
|
+
if (!services.has(name)) {
|
|
127
|
+
services.set(name, {
|
|
128
|
+
name: camelCase(name),
|
|
129
|
+
variableName: `${camelCase(name, {
|
|
130
|
+
preserveConsecutiveUppercase: false,
|
|
131
|
+
})}${postfixServices}`,
|
|
132
|
+
typeName: `${name}${postfixServices}`,
|
|
133
|
+
fileBaseName: `${name}${postfixServices}`,
|
|
134
|
+
operations: [],
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
for (const name of serviceNames) {
|
|
140
|
+
services.get(name)!.operations.push({
|
|
141
|
+
method,
|
|
142
|
+
path,
|
|
143
|
+
errors,
|
|
144
|
+
name: getOperationName(path, method, methodOperation.operationId),
|
|
145
|
+
description: methodOperation.description,
|
|
146
|
+
summary: methodOperation.summary,
|
|
147
|
+
deprecated: methodOperation.deprecated,
|
|
148
|
+
parameters:
|
|
149
|
+
Array.isArray(methodOperation.parameters) &&
|
|
150
|
+
!methodOperation.parameters.length
|
|
151
|
+
? undefined
|
|
152
|
+
: methodOperation.parameters,
|
|
153
|
+
mediaType: methodOperation.requestBody?.content
|
|
154
|
+
? getContentMediaType(methodOperation.requestBody.content)
|
|
155
|
+
: undefined,
|
|
156
|
+
success: success,
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return Array.from(services.values());
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Create a function to match service paths
|
|
167
|
+
* @param servicesGlob
|
|
168
|
+
*/
|
|
169
|
+
export const createServicePathMatch = (servicesGlob: string[]) => {
|
|
170
|
+
const servicePathGlobs = servicesGlob.reduce<
|
|
171
|
+
Record<'match' | 'ignore', string[]>
|
|
172
|
+
>(
|
|
173
|
+
(acc, glob) => {
|
|
174
|
+
glob.startsWith('!')
|
|
175
|
+
? acc.ignore.push(glob.slice(1))
|
|
176
|
+
: acc.match.push(glob);
|
|
177
|
+
return acc;
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
match: [],
|
|
181
|
+
ignore: [],
|
|
182
|
+
}
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
return function isServicePatchMatch(path: string) {
|
|
186
|
+
return micromatch.isMatch(path, servicePathGlobs.match, {
|
|
187
|
+
ignore: servicePathGlobs.ignore,
|
|
188
|
+
});
|
|
189
|
+
};
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
export const supportedMethod = (
|
|
193
|
+
method: unknown
|
|
194
|
+
): method is (typeof supportedHTTPMethods)[number] =>
|
|
195
|
+
supportedHTTPMethods.includes(
|
|
196
|
+
method as (typeof supportedHTTPMethods)[number]
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
const supportedHTTPMethods = Object.values({
|
|
200
|
+
get: 'get',
|
|
201
|
+
put: 'put',
|
|
202
|
+
post: 'post',
|
|
203
|
+
patch: 'patch',
|
|
204
|
+
delete: 'delete',
|
|
205
|
+
options: 'options',
|
|
206
|
+
head: 'head',
|
|
207
|
+
trace: 'trace',
|
|
208
|
+
} satisfies { [key in ServiceOperation['method']]: key });
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { createConfig } from '@redocly/openapi-core';
|
|
2
|
+
|
|
3
|
+
import type { Readable } from 'node:stream';
|
|
4
|
+
import type { OpenAPI3 } from 'openapi-typescript';
|
|
5
|
+
import { validateAndBundle } from 'openapi-typescript/dist/lib/redoc.js';
|
|
6
|
+
|
|
7
|
+
import { OpenAPISchemaType } from './OpenAPISchemaType.js';
|
|
8
|
+
|
|
9
|
+
export const readSchema = async (
|
|
10
|
+
source: string | URL | OpenAPI3 | Readable | Buffer
|
|
11
|
+
) => {
|
|
12
|
+
const redoc = await createConfig(
|
|
13
|
+
{
|
|
14
|
+
rules: {
|
|
15
|
+
'operation-operationId-unique': { severity: 'error' }, // throw error on duplicate operationIDs
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
{ extends: ['minimal'] }
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
const parsed = await validateAndBundle(source, {
|
|
22
|
+
redoc,
|
|
23
|
+
silent: false,
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
return parsed as OpenAPISchemaType;
|
|
27
|
+
};
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import c from 'ansi-colors';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import { Ora } from 'ora';
|
|
4
|
+
|
|
5
|
+
import { GeneratorFile } from './GeneratorFile.js';
|
|
6
|
+
|
|
7
|
+
export const writeGeneratorFiles = async ({
|
|
8
|
+
spinner,
|
|
9
|
+
fileItems,
|
|
10
|
+
}: {
|
|
11
|
+
spinner: Ora;
|
|
12
|
+
fileItems: GeneratorFile[];
|
|
13
|
+
}) => {
|
|
14
|
+
await setupDirectories(spinner, fileItems);
|
|
15
|
+
await writeFiles(spinner, fileItems);
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const setupDirectories = async (spinner: Ora, fileItems: GeneratorFile[]) => {
|
|
19
|
+
for (const fileItem of fileItems) {
|
|
20
|
+
if (!('directory' in fileItem)) continue;
|
|
21
|
+
|
|
22
|
+
if (fileItem.clean) {
|
|
23
|
+
spinner.text = `Cleaning ${c.magenta(fileItem.directory.pathname)}`;
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
await fs.promises.rm(fileItem.directory, {
|
|
27
|
+
recursive: true,
|
|
28
|
+
force: true,
|
|
29
|
+
});
|
|
30
|
+
} catch (error) {
|
|
31
|
+
spinner.fail(
|
|
32
|
+
c.redBright(
|
|
33
|
+
`Error occurred during ${c.magenta(
|
|
34
|
+
fileItem.directory.pathname
|
|
35
|
+
)} directory cleaning`
|
|
36
|
+
)
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
throw error;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
spinner.text = `Creating directory ${c.magenta(fileItem.directory.pathname)}`;
|
|
45
|
+
|
|
46
|
+
await fs.promises.mkdir(fileItem.directory, { recursive: true });
|
|
47
|
+
} catch (error) {
|
|
48
|
+
spinner.fail(
|
|
49
|
+
c.redBright(
|
|
50
|
+
`Error occurred during ${c.magenta(
|
|
51
|
+
fileItem.directory.pathname
|
|
52
|
+
)} directory creation`
|
|
53
|
+
)
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
throw error;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const writeFiles = async (spinner: Ora, fileItems: GeneratorFile[]) => {
|
|
62
|
+
for (const fileItem of fileItems) {
|
|
63
|
+
if ('directory' in fileItem) continue;
|
|
64
|
+
|
|
65
|
+
const { file, code } = fileItem;
|
|
66
|
+
|
|
67
|
+
spinner.text = `Writing ${c.magenta(file.pathname)}`;
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
await fs.promises.writeFile(file, code, {
|
|
71
|
+
encoding: 'utf-8',
|
|
72
|
+
});
|
|
73
|
+
} catch (error) {
|
|
74
|
+
spinner.fail(
|
|
75
|
+
c.redBright(
|
|
76
|
+
`Error occurred during ${c.magenta(file.pathname)} file writing`
|
|
77
|
+
)
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
throw error;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
};
|