@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.
Files changed (63) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/README.md +1 -0
  3. package/dist/lib/GeneratorFile.d.ts +9 -0
  4. package/dist/lib/GeneratorFile.js +2 -0
  5. package/dist/lib/GeneratorFile.js.map +1 -0
  6. package/dist/lib/OutputOptions.d.ts +6 -0
  7. package/dist/lib/OutputOptions.js +2 -0
  8. package/dist/lib/OutputOptions.js.map +1 -0
  9. package/dist/lib/QraftCommand.d.ts +35 -0
  10. package/dist/lib/QraftCommand.js +103 -0
  11. package/dist/lib/QraftCommand.js.map +1 -0
  12. package/dist/lib/QraftCommandPlugin.d.ts +4 -0
  13. package/dist/lib/QraftCommandPlugin.js +2 -0
  14. package/dist/lib/QraftCommandPlugin.js.map +1 -0
  15. package/dist/lib/fileHeader.d.ts +1 -0
  16. package/dist/lib/fileHeader.js +3 -0
  17. package/dist/lib/fileHeader.js.map +1 -0
  18. package/dist/lib/open-api/OpenAPISchemaType.d.ts +31 -0
  19. package/dist/lib/open-api/OpenAPISchemaType.js +2 -0
  20. package/dist/lib/open-api/OpenAPISchemaType.js.map +1 -0
  21. package/dist/lib/open-api/getContent.d.ts +5 -0
  22. package/dist/lib/open-api/getContent.js +23 -0
  23. package/dist/lib/open-api/getContent.js.map +1 -0
  24. package/dist/lib/open-api/getDocumentServices.d.ts +12 -0
  25. package/dist/lib/open-api/getDocumentServices.js +7 -0
  26. package/dist/lib/open-api/getDocumentServices.js.map +1 -0
  27. package/dist/lib/open-api/getOperationName.d.ts +6 -0
  28. package/dist/lib/open-api/getOperationName.js +20 -0
  29. package/dist/lib/open-api/getOperationName.js.map +1 -0
  30. package/dist/lib/open-api/getServiceName.d.ts +5 -0
  31. package/dist/lib/open-api/getServiceName.js +13 -0
  32. package/dist/lib/open-api/getServiceName.js.map +1 -0
  33. package/dist/lib/open-api/getServiceNamesByOperationEndpoint.d.ts +4 -0
  34. package/dist/lib/open-api/getServiceNamesByOperationEndpoint.js +30 -0
  35. package/dist/lib/open-api/getServiceNamesByOperationEndpoint.js.map +1 -0
  36. package/dist/lib/open-api/getServiceNamesByOperationTags.d.ts +1 -0
  37. package/dist/lib/open-api/getServiceNamesByOperationTags.js +11 -0
  38. package/dist/lib/open-api/getServiceNamesByOperationTags.js.map +1 -0
  39. package/dist/lib/open-api/getServices.d.ts +39 -0
  40. package/dist/lib/open-api/getServices.js +112 -0
  41. package/dist/lib/open-api/getServices.js.map +1 -0
  42. package/dist/lib/open-api/readSchema.d.ts +7 -0
  43. package/dist/lib/open-api/readSchema.js +15 -0
  44. package/dist/lib/open-api/readSchema.js.map +1 -0
  45. package/dist/lib/writeGeneratorFiles.d.ts +6 -0
  46. package/dist/lib/writeGeneratorFiles.js +51 -0
  47. package/dist/lib/writeGeneratorFiles.js.map +1 -0
  48. package/package.json +53 -0
  49. package/src/lib/GeneratorFile.ts +5 -0
  50. package/src/lib/OutputOptions.ts +6 -0
  51. package/src/lib/QraftCommand.ts +192 -0
  52. package/src/lib/QraftCommandPlugin.ts +5 -0
  53. package/src/lib/fileHeader.ts +2 -0
  54. package/src/lib/open-api/OpenAPISchemaType.ts +31 -0
  55. package/src/lib/open-api/getContent.ts +26 -0
  56. package/src/lib/open-api/getDocumentServices.ts +19 -0
  57. package/src/lib/open-api/getOperationName.ts +28 -0
  58. package/src/lib/open-api/getServiceName.ts +14 -0
  59. package/src/lib/open-api/getServiceNamesByOperationEndpoint.ts +70 -0
  60. package/src/lib/open-api/getServiceNamesByOperationTags.ts +15 -0
  61. package/src/lib/open-api/getServices.ts +208 -0
  62. package/src/lib/open-api/readSchema.ts +27 -0
  63. 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
+ };