@openapi-qraft/plugin 1.13.1 → 1.14.0-beta.2

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 (41) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/lib/QraftCommand.d.ts.map +1 -1
  3. package/dist/lib/QraftCommand.js +27 -8
  4. package/dist/lib/QraftCommand.js.map +1 -1
  5. package/dist/lib/createServicePathMatch.d.ts +0 -9
  6. package/dist/lib/createServicePathMatch.d.ts.map +1 -1
  7. package/dist/lib/createServicePathMatch.js +0 -15
  8. package/dist/lib/createServicePathMatch.js.map +1 -1
  9. package/dist/lib/filterDocumentPaths.d.ts +1 -1
  10. package/dist/lib/filterDocumentPaths.d.ts.map +1 -1
  11. package/dist/lib/filterDocumentPaths.js +1 -1
  12. package/dist/lib/filterDocumentPaths.js.map +1 -1
  13. package/dist/lib/parseOperationGlobs.d.ts +17 -0
  14. package/dist/lib/parseOperationGlobs.d.ts.map +1 -0
  15. package/dist/lib/parseOperationGlobs.js +37 -0
  16. package/dist/lib/parseOperationGlobs.js.map +1 -0
  17. package/dist/lib/predefineSchemaParameters.d.ts +15 -11
  18. package/dist/lib/predefineSchemaParameters.d.ts.map +1 -1
  19. package/dist/lib/predefineSchemaParameters.js +50 -30
  20. package/dist/lib/predefineSchemaParameters.js.map +1 -1
  21. package/dist/lib/processOperationNameModifierOption.d.ts +57 -0
  22. package/dist/lib/processOperationNameModifierOption.d.ts.map +1 -0
  23. package/dist/lib/processOperationNameModifierOption.js +102 -0
  24. package/dist/lib/processOperationNameModifierOption.js.map +1 -0
  25. package/dist/lib/splitCommaSeparatedGlobs.d.ts +7 -0
  26. package/dist/lib/splitCommaSeparatedGlobs.d.ts.map +1 -0
  27. package/dist/lib/splitCommaSeparatedGlobs.js +16 -0
  28. package/dist/lib/splitCommaSeparatedGlobs.js.map +1 -0
  29. package/dist/packageVersion.d.ts +1 -1
  30. package/dist/packageVersion.d.ts.map +1 -1
  31. package/dist/packageVersion.js +1 -1
  32. package/dist/packageVersion.js.map +1 -1
  33. package/package.json +3 -3
  34. package/src/lib/QraftCommand.ts +59 -10
  35. package/src/lib/createServicePathMatch.ts +0 -18
  36. package/src/lib/filterDocumentPaths.ts +2 -2
  37. package/src/lib/parseOperationGlobs.ts +55 -0
  38. package/src/lib/predefineSchemaParameters.ts +108 -71
  39. package/src/lib/processOperationNameModifierOption.ts +205 -0
  40. package/src/lib/splitCommaSeparatedGlobs.ts +16 -0
  41. package/src/packageVersion.ts +1 -1
@@ -4,14 +4,16 @@ import {
4
4
  PathItemObject,
5
5
  ReferenceObject,
6
6
  } from 'openapi-typescript/src/types.js';
7
+ import { createServicePathMatch } from './createServicePathMatch.js';
7
8
  import {
8
- createServicePathMatch,
9
- parsePathGlobs,
10
- } from './createServicePathMatch.js';
9
+ OperationGlobMethods,
10
+ parseOperationGlobs,
11
+ } from './parseOperationGlobs.js';
12
+ import { splitCommaSeparatedGlobs } from './splitCommaSeparatedGlobs.js';
11
13
 
12
14
  export function predefineSchemaParameters(
13
15
  schema: OpenAPI3,
14
- predefinedParametersGlobMap: PredefinedParametersGlobMap
16
+ predefinedParametersGlobs: PredefinedParametersGlob[]
15
17
  ): OpenAPI3 {
16
18
  const predefinedSchemaPaths: typeof schema.paths = {};
17
19
 
@@ -31,9 +33,7 @@ export function predefineSchemaParameters(
31
33
  const operation = service[method];
32
34
  assertIsOperationObject(operation);
33
35
 
34
- for (const predefinedParametersGlob of Object.values(
35
- predefinedParametersGlobMap
36
- )) {
36
+ for (const predefinedParametersGlob of predefinedParametersGlobs) {
37
37
  if (!predefinedParametersGlob.paths.includes(path)) {
38
38
  continue;
39
39
  }
@@ -82,11 +82,6 @@ export function predefineSchemaParameters(
82
82
  };
83
83
  }
84
84
 
85
- type PredefinedParametersOptionMap = Record<
86
- string,
87
- Pick<ParameterObject, 'in' | 'name'>[]
88
- >;
89
-
90
85
  /**
91
86
  * Parses the given configuration string into a structured format.
92
87
  *
@@ -100,80 +95,90 @@ type PredefinedParametersOptionMap = Record<
100
95
  */
101
96
  function parseOperationPredefinedParametersOption(
102
97
  ...optionItems: string[]
103
- ): PredefinedParametersOptionMap {
104
- const globParametersMap: PredefinedParametersOptionMap = {};
98
+ ): ParsedPredefinedParametersOption[] {
99
+ const globParametersMap = new Map<string, ParsedPredefinedParametersOption>();
105
100
 
106
101
  for (const optionItem of optionItems) {
107
- const [pathGlob, options] = optionItem.split(':').map((s) => s.trim());
102
+ const operationGlobsRaw = optionItem.slice(
103
+ 0,
104
+ Math.max(0, optionItem.indexOf(':')) || optionItem.length // the second part after the `:` is optional for the predefined parameters option
105
+ );
106
+ const parameterGlobs = optionItem.slice(operationGlobsRaw.length + 1);
107
+
108
108
  const parameters: Record<'header' | 'query' | 'cookie', string[]> = {
109
109
  header: [],
110
110
  query: [],
111
111
  cookie: [],
112
112
  };
113
113
 
114
- if (options) {
115
- options
116
- .split(',')
117
- .map((option) => option.trim())
118
- .filter(Boolean)
119
- .forEach((option) => {
120
- const [type, key] = option.split('.').map((s) => s.trim());
121
-
122
- if (type !== 'header' && type !== 'query' && type !== 'cookie')
123
- throw new Error(
124
- `Invalid option type: ${type} in ${option}. Must be one of 'header', 'query', or 'cookie'`
125
- );
114
+ if (parameterGlobs) {
115
+ splitCommaSeparatedGlobs(parameterGlobs).forEach((option) => {
116
+ const [type, key] = option.split('.').map((s) => s.trim());
126
117
 
127
- if (!parameters[type].includes(key)) parameters[type].push(key);
128
- });
118
+ if (type !== 'header' && type !== 'query' && type !== 'cookie')
119
+ throw new Error(
120
+ `Invalid option type: ${type} in ${option}. Must be one of 'header', 'query', or 'cookie'`
121
+ );
122
+
123
+ if (!parameters[type].includes(key)) parameters[type].push(key);
124
+ });
129
125
  }
130
126
 
131
- if (pathGlob in globParametersMap) {
132
- throw new Error(`Duplicate path: ${pathGlob} in config string.`);
127
+ const { pathGlobs, methods: methodList } =
128
+ parseOperationGlobs(operationGlobsRaw);
129
+
130
+ const methods = methodList ? methodList.join(',') : '';
131
+
132
+ const globKey = `${methods}${pathGlobs}`;
133
+
134
+ if (globParametersMap.has(globKey)) {
135
+ throw new Error(
136
+ `Duplicate path: ${methods ? methods + ' ' : ''}${pathGlobs} in config string.`
137
+ );
133
138
  }
134
139
 
135
- globParametersMap[pathGlob] = Object.entries(parameters).flatMap(
136
- ([inLocation, names]) =>
140
+ globParametersMap.set(globKey, {
141
+ parameters: Object.entries(parameters).flatMap(([inLocation, names]) =>
137
142
  names.map((name) => ({ in: inLocation as ParameterObject['in'], name }))
138
- );
143
+ ),
144
+ methods: methodList,
145
+ pathGlobs,
146
+ });
139
147
  }
140
148
 
141
- return globParametersMap;
149
+ return Array.from(globParametersMap.values());
142
150
  }
143
151
 
144
- type PredefinedParametersGlobMap = {
145
- [glob: string]: {
146
- paths: string[];
147
- parameters: ParameterObject[];
148
- errors: string[];
149
- };
150
- };
151
-
152
- export function createPredefinedParametersGlobMap(
152
+ export function createPredefinedParametersGlobs(
153
153
  schema: OpenAPI3,
154
- predefinedParameters: PredefinedParametersOptionMap
155
- ): PredefinedParametersGlobMap {
156
- const predefinedParametersGlobMap: PredefinedParametersGlobMap = {};
157
-
158
- const servicePredefinedParametersList = Object.entries(
159
- predefinedParameters
160
- ).map(([pathGlobs, parameters]) => {
161
- const globs = parsePathGlobs(pathGlobs);
162
- if (!globs)
163
- throw new Error(
164
- `Invalid path glob: ${pathGlobs}. Must be a comma-separated list of globs.`
165
- );
154
+ predefinedParameters: ParsedPredefinedParametersOption[]
155
+ ): Array<PredefinedParametersGlob> {
156
+ const predefinedParametersGlobMap = new Map<
157
+ string,
158
+ PredefinedParametersGlob
159
+ >();
160
+
161
+ const servicePredefinedParametersList = predefinedParameters.map(
162
+ ({ pathGlobs, parameters, methods }) => {
163
+ const globs = splitCommaSeparatedGlobs(pathGlobs);
164
+ if (!globs.length)
165
+ throw new Error(
166
+ `Invalid path glob: ${pathGlobs}. Must be a comma-separated list of globs.`
167
+ );
166
168
 
167
- return {
168
- pathGlobs,
169
- parameters,
170
- isPathMatch: createServicePathMatch(globs),
171
- };
172
- });
169
+ return {
170
+ methods,
171
+ pathGlobs,
172
+ parameters,
173
+ isPathMatch: createServicePathMatch(globs),
174
+ };
175
+ }
176
+ );
173
177
 
174
178
  for (const path in schema.paths) {
175
179
  if (!Object.prototype.hasOwnProperty.call(schema.paths, path)) continue;
176
180
  for (const {
181
+ methods,
177
182
  pathGlobs,
178
183
  parameters: parametersToPredefine,
179
184
  isPathMatch,
@@ -187,6 +192,11 @@ export function createPredefinedParametersGlobMap(
187
192
  for (const _method in service) {
188
193
  const method = _method as keyof typeof service;
189
194
  if (!Object.prototype.hasOwnProperty.call(service, method)) continue;
195
+ if (
196
+ methods !== undefined &&
197
+ !methods.includes(method as OperationGlobMethods)
198
+ )
199
+ continue;
190
200
 
191
201
  const operation = service[method];
192
202
  assertIsOperationObject(operation);
@@ -194,14 +204,18 @@ export function createPredefinedParametersGlobMap(
194
204
  const operationParameters = operation.parameters ?? [];
195
205
  assertIsParameterObjects(operationParameters);
196
206
 
197
- if (!(pathGlobs in predefinedParametersGlobMap))
198
- predefinedParametersGlobMap[pathGlobs] = {
207
+ if (!predefinedParametersGlobMap.has(pathGlobs))
208
+ predefinedParametersGlobMap.set(pathGlobs, {
209
+ methods: [],
199
210
  paths: [],
200
211
  parameters: [],
201
212
  errors: [],
202
- };
213
+ });
203
214
 
204
- const predefinedPathGlob = predefinedParametersGlobMap[pathGlobs];
215
+ const predefinedPathGlob = predefinedParametersGlobMap.get(pathGlobs);
216
+
217
+ if (!predefinedPathGlob)
218
+ throw new Error('predefinedPathGlob not found');
205
219
 
206
220
  for (const parameterSchemaToPredefine of parametersToPredefine) {
207
221
  const parameterToPredefine = findPredefinedOperationParameter(
@@ -209,6 +223,15 @@ export function createPredefinedParametersGlobMap(
209
223
  operationParameters
210
224
  );
211
225
 
226
+ if (
227
+ !predefinedPathGlob.methods?.includes(
228
+ method as OperationGlobMethods
229
+ )
230
+ ) {
231
+ if (!predefinedPathGlob.methods) predefinedPathGlob.methods = [];
232
+ predefinedPathGlob.methods.push(method as OperationGlobMethods);
233
+ }
234
+
212
235
  if (!parameterToPredefine) {
213
236
  predefinedPathGlob.errors.push(
214
237
  `Missing predefined parameter '${parameterSchemaToPredefine.in}' '${parameterSchemaToPredefine.name}' in '${method} ${path}' in '${pathGlobs}'`
@@ -246,17 +269,18 @@ export function createPredefinedParametersGlobMap(
246
269
  }
247
270
  }
248
271
 
249
- Object.entries(predefinedParameters).forEach(([pathGlobs]) => {
250
- if (!predefinedParametersGlobMap[pathGlobs]) {
251
- predefinedParametersGlobMap[pathGlobs] = {
272
+ predefinedParameters.forEach(({ pathGlobs, methods }) => {
273
+ if (!predefinedParametersGlobMap.has(pathGlobs)) {
274
+ predefinedParametersGlobMap.set(pathGlobs, {
275
+ methods,
252
276
  paths: [],
253
277
  parameters: [],
254
278
  errors: [`No matching paths found for '${pathGlobs}'`],
255
- };
279
+ });
256
280
  }
257
281
  });
258
282
 
259
- return predefinedParametersGlobMap;
283
+ return Array.from(predefinedParametersGlobMap.values());
260
284
  }
261
285
 
262
286
  function findPredefinedOperationParameter(
@@ -307,4 +331,17 @@ export function assertIsParameterObjects(
307
331
  });
308
332
  }
309
333
 
334
+ type ParsedPredefinedParametersOption = {
335
+ methods: OperationGlobMethods[] | undefined;
336
+ pathGlobs: string;
337
+ parameters: Pick<ParameterObject, 'in' | 'name'>[];
338
+ };
339
+
340
+ type PredefinedParametersGlob = {
341
+ methods: OperationGlobMethods[] | undefined;
342
+ paths: string[];
343
+ parameters: Pick<ParameterObject, 'in' | 'name'>[];
344
+ errors: string[];
345
+ };
346
+
310
347
  export { parseOperationPredefinedParametersOption };
@@ -0,0 +1,205 @@
1
+ import { createServicePathMatch } from './createServicePathMatch.js';
2
+ import { Service } from './open-api/getServices.js';
3
+ import {
4
+ OperationGlobMethods,
5
+ parseOperationGlobs,
6
+ } from './parseOperationGlobs.js';
7
+ import { splitCommaSeparatedGlobs } from './splitCommaSeparatedGlobs.js';
8
+
9
+ export const processOperationNameModifierOption = (
10
+ modifiers: OperationNameModifier[],
11
+ services: Service[]
12
+ ) => {
13
+ if (!modifiers.length)
14
+ return {
15
+ errors: [],
16
+ services,
17
+ };
18
+
19
+ const operationNameModifiers = modifiers.map((modifier) => ({
20
+ ...modifier,
21
+ isPathMatch: createServicePathMatch(
22
+ splitCommaSeparatedGlobs(modifier.pathGlobs)
23
+ ),
24
+ }));
25
+
26
+ type ServiceName = string;
27
+ type ReplacedOperationNameMap = string;
28
+ type OriginalOperationName = string;
29
+
30
+ const serviceOperationReplacements = new Map<
31
+ ServiceName,
32
+ Map<
33
+ ReplacedOperationNameMap,
34
+ Map<OriginalOperationName, OperationNameModifier[]>
35
+ >
36
+ >();
37
+
38
+ const replacedOperationNamesServices = services.map((service) => {
39
+ serviceOperationReplacements.set(service.name, new Map());
40
+
41
+ return {
42
+ ...service,
43
+ operations: service.operations.map((operation) => {
44
+ const operationReplacements = serviceOperationReplacements.get(
45
+ service.name
46
+ );
47
+
48
+ if (!operationReplacements)
49
+ throw new Error('operationReplacements not found');
50
+
51
+ const operationModifiers = operationNameModifiers.filter(
52
+ (modifier) =>
53
+ modifier.isPathMatch(operation.path) &&
54
+ (modifier.methods === undefined ||
55
+ modifier.methods.includes(operation.method)) &&
56
+ normalizeOperationNameModifierRegex(
57
+ modifier.operationNameModifierRegex
58
+ ).test(operation.name)
59
+ );
60
+
61
+ const replacedOperationName = operationModifiers.reduce<string>(
62
+ (operationName, modifier) =>
63
+ operationName.replace(
64
+ normalizeOperationNameModifierRegex(
65
+ modifier.operationNameModifierRegex
66
+ ),
67
+ modifier.operationNameModifierReplace
68
+ ),
69
+ operation.name
70
+ );
71
+
72
+ if (replacedOperationName !== operation.name) {
73
+ if (!operationReplacements.has(replacedOperationName))
74
+ operationReplacements.set(replacedOperationName, new Map());
75
+ operationReplacements.get(replacedOperationName)?.set(
76
+ operation.name,
77
+ operationModifiers.map(
78
+ ({ isPathMatch: _, ...operationModifiersRest }) =>
79
+ operationModifiersRest
80
+ )
81
+ );
82
+ }
83
+
84
+ return {
85
+ ...operation,
86
+ name: replacedOperationName,
87
+ };
88
+ }),
89
+ };
90
+ });
91
+
92
+ const errors = Array.from(serviceOperationReplacements).flatMap(
93
+ ([serviceName, modifiedOperations]) =>
94
+ Array.from(modifiedOperations).flatMap(
95
+ ([replacedOperationName, operationModifierMap]) => {
96
+ if (operationModifierMap.size > 1) {
97
+ return Array.from(operationModifierMap).map(
98
+ ([originalOperationName, modifiers]) => ({
99
+ serviceName,
100
+ replacedOperationName,
101
+ originalOperationName,
102
+ modifiers,
103
+ })
104
+ );
105
+ }
106
+
107
+ const [originalOperationName, modifiers] =
108
+ Array.from(operationModifierMap).at(0) ?? [];
109
+
110
+ if (modifiers && modifiers.length > 1) {
111
+ return [
112
+ {
113
+ serviceName,
114
+ replacedOperationName,
115
+ originalOperationName,
116
+ modifiers,
117
+ },
118
+ ];
119
+ }
120
+
121
+ return [];
122
+ }
123
+ )
124
+ );
125
+
126
+ return {
127
+ errors,
128
+ services: replacedOperationNamesServices,
129
+ };
130
+ };
131
+
132
+ function normalizeOperationNameModifierRegex(regex: string) {
133
+ return new RegExp(regex, 'g');
134
+ }
135
+
136
+ export type OperationNameModifier = {
137
+ methods: OperationGlobMethods[] | undefined;
138
+ /**
139
+ * Glob pattern to match service paths, eg: `/foo,/bar/**`
140
+ */
141
+ pathGlobs: string;
142
+ /**
143
+ * Regular expression to match operation names
144
+ */
145
+ operationNameModifierRegex: string;
146
+ /**
147
+ * Replacement string for matched operation names
148
+ */
149
+ operationNameModifierReplace: string;
150
+ };
151
+
152
+ /**
153
+ * Parses the given options string into a structured format.
154
+ *
155
+ * @param optionItems - The configuration string to parse. Eg. `/foo,/bar/**:post[A-Z][A-Za-z]+sId[A-Z][A-Za-z]+ ==> createOne` or `post /** ==> createOne`
156
+ * @returns The parsed configuration.
157
+ *
158
+ * @example
159
+ * ```ts
160
+ * const parsedConfig = parseOperationNameModifier("/foo,/bar/**:/**:post[A-Z][A-Za-z]+sId[A-Z][A-Za-z]+ ==> createOne");
161
+ * ```
162
+ */
163
+ export function parseOperationNameModifier(
164
+ ...optionItems: string[]
165
+ ): OperationNameModifier[] {
166
+ const globOperationNameModifierMap: Array<OperationNameModifier> = [];
167
+
168
+ for (const optionItem of optionItems) {
169
+ const operationGlobsRaw = optionItem.slice(
170
+ 0,
171
+ Math.max(0, optionItem.indexOf(':'))
172
+ );
173
+
174
+ if (!operationGlobsRaw.length)
175
+ throw new Error(
176
+ `Invalid operation name modifier: ${optionItem}. Must be in the format of 'pathGlobs:operationNameModifierRegex ==> operationNameModifierReplace'`
177
+ );
178
+
179
+ const modifierPattern = optionItem.slice(operationGlobsRaw.length + 1);
180
+
181
+ const patternRegex = modifierPattern.slice(
182
+ 0,
183
+ modifierPattern.indexOf('==>')
184
+ );
185
+ const patternReplace = modifierPattern.slice(
186
+ modifierPattern.indexOf('==>') + '==>'.length + 1
187
+ );
188
+
189
+ const { methods, pathGlobs } = parseOperationGlobs(operationGlobsRaw);
190
+
191
+ if (!splitCommaSeparatedGlobs(pathGlobs).length)
192
+ throw new Error(
193
+ `Invalid path glob: ${pathGlobs}. Must be a comma-separated list of globs.`
194
+ );
195
+
196
+ globOperationNameModifierMap.push({
197
+ methods,
198
+ pathGlobs,
199
+ operationNameModifierRegex: patternRegex.trim(),
200
+ operationNameModifierReplace: patternReplace.trim(),
201
+ });
202
+ }
203
+
204
+ return globOperationNameModifierMap;
205
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Normalize a comma-separated list of globs
3
+ * @param commaSeparatedGlobs - Example: `/foo , /bar/**, ,`
4
+ * @returns Example: `/foo,/bar/**`
5
+ */
6
+ export function splitCommaSeparatedGlobs(
7
+ commaSeparatedGlobs: string | undefined
8
+ ): string[] {
9
+ if (commaSeparatedGlobs === undefined) return [];
10
+ if (typeof commaSeparatedGlobs !== 'string') throw new Error('Invalid input');
11
+
12
+ return commaSeparatedGlobs
13
+ .split(',')
14
+ .map((path) => path.trim())
15
+ .filter(Boolean);
16
+ }
@@ -1,2 +1,2 @@
1
1
  // This file was generated automatically
2
- export const packageVersion = '1.13.1';
2
+ export const packageVersion = '1.14.0-beta.2';