@openapi-qraft/plugin 1.8.0 → 1.10.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 +19 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/GeneratorFile.d.ts +1 -0
- package/dist/lib/GeneratorFile.d.ts.map +1 -0
- package/dist/lib/OutputOptions.d.ts +1 -0
- package/dist/lib/OutputOptions.d.ts.map +1 -0
- package/dist/lib/QraftCommand.d.ts +35 -7
- package/dist/lib/QraftCommand.d.ts.map +1 -0
- package/dist/lib/QraftCommand.js +165 -56
- package/dist/lib/QraftCommand.js.map +1 -1
- package/dist/lib/QraftCommandPlugin.d.ts +6 -1
- package/dist/lib/QraftCommandPlugin.d.ts.map +1 -0
- package/dist/lib/fileHeader.d.ts +2 -1
- package/dist/lib/fileHeader.d.ts.map +1 -0
- package/dist/lib/fileHeader.js +6 -2
- package/dist/lib/fileHeader.js.map +1 -1
- package/dist/lib/filterDocumentPaths.d.ts +18 -0
- package/dist/lib/filterDocumentPaths.d.ts.map +1 -0
- package/dist/lib/filterDocumentPaths.js +46 -0
- package/dist/lib/filterDocumentPaths.js.map +1 -0
- package/dist/lib/formatFileHeader.d.ts +2 -0
- package/dist/lib/formatFileHeader.d.ts.map +1 -0
- package/dist/lib/formatFileHeader.js +6 -0
- package/dist/lib/formatFileHeader.js.map +1 -0
- package/dist/lib/handleSchemaInput.d.ts +12 -0
- package/dist/lib/handleSchemaInput.d.ts.map +1 -0
- package/dist/lib/handleSchemaInput.js +40 -0
- package/dist/lib/handleSchemaInput.js.map +1 -0
- package/dist/lib/open-api/OpenAPISchemaType.d.ts +7 -0
- package/dist/lib/open-api/OpenAPISchemaType.d.ts.map +1 -0
- package/dist/lib/open-api/getContent.d.ts +1 -0
- package/dist/lib/open-api/getContent.d.ts.map +1 -0
- package/dist/lib/open-api/getOperationName.d.ts +1 -0
- package/dist/lib/open-api/getOperationName.d.ts.map +1 -0
- package/dist/lib/open-api/getServiceName.d.ts +1 -0
- package/dist/lib/open-api/getServiceName.d.ts.map +1 -0
- package/dist/lib/open-api/getServiceNamesByOperationEndpoint.d.ts +1 -0
- package/dist/lib/open-api/getServiceNamesByOperationEndpoint.d.ts.map +1 -0
- package/dist/lib/open-api/getServiceNamesByOperationTags.d.ts +1 -0
- package/dist/lib/open-api/getServiceNamesByOperationTags.d.ts.map +1 -0
- package/dist/lib/open-api/getServices.d.ts +3 -6
- package/dist/lib/open-api/getServices.d.ts.map +1 -0
- package/dist/lib/open-api/getServices.js +2 -26
- package/dist/lib/open-api/getServices.js.map +1 -1
- package/dist/lib/open-api/readSchema.d.ts +1 -1
- package/dist/lib/open-api/readSchema.d.ts.map +1 -0
- package/dist/lib/vitestFsMock.d.ts +2 -0
- package/dist/lib/vitestFsMock.d.ts.map +1 -0
- package/dist/lib/vitestFsMock.js +16 -0
- package/dist/lib/vitestFsMock.js.map +1 -0
- package/dist/lib/writeGeneratorFiles.d.ts +1 -0
- package/dist/lib/writeGeneratorFiles.d.ts.map +1 -0
- package/package.json +4 -2
- package/src/index.ts +2 -0
- package/src/lib/QraftCommand.ts +316 -77
- package/src/lib/QraftCommandPlugin.ts +9 -1
- package/src/lib/fileHeader.ts +6 -2
- package/src/lib/filterDocumentPaths.ts +58 -0
- package/src/lib/formatFileHeader.ts +4 -0
- package/src/lib/handleSchemaInput.ts +53 -0
- package/src/lib/open-api/OpenAPISchemaType.ts +6 -0
- package/src/lib/open-api/getServices.ts +3 -34
- package/src/lib/vitestFsMock.ts +23 -0
- package/dist/lib/open-api/getDocumentServices.d.ts +0 -12
- package/dist/lib/open-api/getDocumentServices.js +0 -7
- package/dist/lib/open-api/getDocumentServices.js.map +0 -1
- package/src/lib/open-api/getDocumentServices.ts +0 -19
package/src/lib/QraftCommand.ts
CHANGED
|
@@ -1,24 +1,32 @@
|
|
|
1
1
|
import c from 'ansi-colors';
|
|
2
|
-
import { Command } from 'commander';
|
|
2
|
+
import { Command, type Option } from 'commander';
|
|
3
|
+
import * as console from 'node:console';
|
|
3
4
|
import { sep } from 'node:path';
|
|
4
5
|
import process from 'node:process';
|
|
5
6
|
import { pathToFileURL, URL } from 'node:url';
|
|
6
7
|
import ora, { Ora } from 'ora';
|
|
7
8
|
|
|
9
|
+
import { filterDocumentPaths } from './filterDocumentPaths.js';
|
|
8
10
|
import { GeneratorFile } from './GeneratorFile.js';
|
|
9
|
-
import {
|
|
10
|
-
import { Service } from './open-api/getServices.js';
|
|
11
|
+
import { handleSchemaInput } from './handleSchemaInput.js';
|
|
12
|
+
import { getServices, type Service } from './open-api/getServices.js';
|
|
13
|
+
import { OpenAPISchemaType } from './open-api/OpenAPISchemaType.js';
|
|
14
|
+
import { readSchema } from './open-api/readSchema.js';
|
|
11
15
|
import { OutputOptions } from './OutputOptions.js';
|
|
12
16
|
import { writeGeneratorFiles } from './writeGeneratorFiles.js';
|
|
13
17
|
|
|
14
18
|
export class QraftCommand extends Command {
|
|
19
|
+
protected readonly cwd: URL;
|
|
20
|
+
protected registeredPluginActions: QraftCommandActionCallback[] = [];
|
|
21
|
+
|
|
15
22
|
constructor() {
|
|
16
23
|
super();
|
|
24
|
+
this.cwd = pathToFileURL(`${process.cwd()}/`);
|
|
17
25
|
|
|
18
26
|
this.usage('[input] [options]')
|
|
19
27
|
.argument(
|
|
20
28
|
'[input]',
|
|
21
|
-
'Input OpenAPI
|
|
29
|
+
'Input OpenAPI Document file path, URL (json, yml)',
|
|
22
30
|
null
|
|
23
31
|
)
|
|
24
32
|
.requiredOption(
|
|
@@ -41,95 +49,218 @@ export class QraftCommand extends Command {
|
|
|
41
49
|
'--service-name-base <endpoint[<index>] | tags>',
|
|
42
50
|
'Use OpenAPI Operation `endpoint[<index>]` path part (e.g.: "/0/1/2") or `tags` as the base name of the service.',
|
|
43
51
|
'endpoint[0]'
|
|
52
|
+
)
|
|
53
|
+
.option(
|
|
54
|
+
'--file-header <string>',
|
|
55
|
+
'Header to be added to the generated file (eg: /* eslint-disable */)'
|
|
44
56
|
);
|
|
45
57
|
}
|
|
46
58
|
|
|
47
59
|
action(callback: QraftCommandActionCallback): this {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
{
|
|
52
|
-
assert: { type: 'json' },
|
|
53
|
-
}
|
|
54
|
-
).then(({ default: packageJSON }) => packageJSON);
|
|
60
|
+
this.registerPluginAction(callback);
|
|
61
|
+
return super.action(this.actionCallback.bind(this));
|
|
62
|
+
}
|
|
55
63
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
64
|
+
async actionCallback(...actionArgs: any[]) {
|
|
65
|
+
const { version: packageVersion } = await import(
|
|
66
|
+
'@openapi-qraft/plugin/package.json',
|
|
67
|
+
{
|
|
68
|
+
assert: { type: 'json' },
|
|
69
|
+
}
|
|
70
|
+
).then(({ default: packageJSON }) => packageJSON);
|
|
62
71
|
|
|
63
|
-
|
|
72
|
+
const inputs = actionArgs.filter(
|
|
73
|
+
(arg) => typeof arg === 'string'
|
|
74
|
+
) as string[];
|
|
75
|
+
const args = actionArgs.find(
|
|
76
|
+
(arg) => arg && typeof arg === 'object'
|
|
77
|
+
) as Record<string, any>;
|
|
64
78
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
}
|
|
79
|
+
if (!args) throw new Error('Arguments object not found');
|
|
80
|
+
|
|
81
|
+
if (args.version) {
|
|
82
|
+
console.info(`v${packageVersion}`); // todo::add displaying of the used plugin version
|
|
83
|
+
process.exit(0);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
console.info(`✨ ${c.bold(`OpenAPI Qraft ${packageVersion}`)}`);
|
|
69
87
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
88
|
+
const spinner = ora('Starting ⛏︎').start();
|
|
89
|
+
|
|
90
|
+
const input = handleSchemaInput(inputs[0], this.cwd, spinner);
|
|
91
|
+
|
|
92
|
+
spinner.text = 'Reading OpenAPI Schema';
|
|
93
|
+
const schema = filterDocumentPaths(
|
|
94
|
+
await readSchema(input),
|
|
95
|
+
parseServicesFilterOption(args.filterServices)
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
spinner.text = 'Getting OpenAPI Services';
|
|
99
|
+
const services = getServices(schema, {
|
|
100
|
+
postfixServices: args.postfixServices,
|
|
101
|
+
serviceNameBase: args.serviceNameBase,
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
spinner.text = 'Generating code';
|
|
105
|
+
|
|
106
|
+
const outputDir = normalizeOutputDirPath(args.outputDir);
|
|
107
|
+
|
|
108
|
+
for (const pluginAction of this.registeredPluginActions) {
|
|
109
|
+
const fileItems = await new Promise<GeneratorFile[]>(
|
|
110
|
+
(resolve, reject) => {
|
|
111
|
+
pluginAction(
|
|
112
|
+
{
|
|
113
|
+
inputs,
|
|
114
|
+
args,
|
|
89
115
|
spinner,
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
console.error(error);
|
|
100
|
-
process.exit(1);
|
|
101
|
-
}
|
|
116
|
+
services,
|
|
117
|
+
schema,
|
|
118
|
+
output: {
|
|
119
|
+
dir: outputDir,
|
|
120
|
+
clean: args.clean,
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
resolve
|
|
124
|
+
).catch(reject);
|
|
102
125
|
}
|
|
103
126
|
);
|
|
104
|
-
|
|
127
|
+
|
|
128
|
+
try {
|
|
129
|
+
if (this.registeredPluginActions.indexOf(pluginAction) === 0) {
|
|
130
|
+
await writeGeneratorFiles({
|
|
131
|
+
// Create output directory first, but for the first plugin only
|
|
132
|
+
fileItems: [{ directory: outputDir, clean: false }, ...fileItems],
|
|
133
|
+
spinner,
|
|
134
|
+
});
|
|
135
|
+
} else {
|
|
136
|
+
await writeGeneratorFiles({ fileItems, spinner });
|
|
137
|
+
}
|
|
138
|
+
} catch (error) {
|
|
139
|
+
spinner.fail(c.red('Error occurred during generation'));
|
|
140
|
+
|
|
141
|
+
if (error instanceof Error) {
|
|
142
|
+
console.error(c.red(error.message), c.red(error.stack ?? ''));
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
throw error;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
spinner.succeed(c.green('Qraft has been finished'));
|
|
105
150
|
}
|
|
106
151
|
|
|
107
|
-
|
|
108
|
-
|
|
152
|
+
protected registerPluginAction(callback: QraftCommandActionCallback) {
|
|
153
|
+
this.registeredPluginActions.push(callback);
|
|
154
|
+
}
|
|
109
155
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
156
|
+
option(
|
|
157
|
+
flags: string,
|
|
158
|
+
description?: string,
|
|
159
|
+
defaultValue?: string | boolean | string[]
|
|
160
|
+
): this;
|
|
161
|
+
option<T>(
|
|
162
|
+
flags: string,
|
|
163
|
+
description: string,
|
|
164
|
+
parseArg: (value: string, previous: T) => T,
|
|
165
|
+
defaultValue?: T
|
|
166
|
+
): this;
|
|
167
|
+
/** @deprecated since v7, instead use choices or a custom function */
|
|
168
|
+
option(
|
|
169
|
+
flags: string,
|
|
170
|
+
description: string,
|
|
171
|
+
regexp: RegExp,
|
|
172
|
+
defaultValue?: string | boolean | string[]
|
|
173
|
+
): this;
|
|
174
|
+
option<T>(
|
|
175
|
+
flags: string,
|
|
176
|
+
description?: string,
|
|
177
|
+
parseArg?:
|
|
178
|
+
| ((value: string, previous: T) => T)
|
|
179
|
+
| string
|
|
180
|
+
| boolean
|
|
181
|
+
| string[]
|
|
182
|
+
| RegExp,
|
|
183
|
+
defaultValue?: T
|
|
184
|
+
): this {
|
|
185
|
+
if (
|
|
186
|
+
this.findSimilarOption({
|
|
187
|
+
flags,
|
|
188
|
+
mandatory: false,
|
|
189
|
+
})
|
|
190
|
+
) {
|
|
191
|
+
return this;
|
|
192
|
+
}
|
|
114
193
|
|
|
115
|
-
|
|
194
|
+
return super.option(
|
|
195
|
+
flags,
|
|
196
|
+
// @ts-expect-error - Issues with overloading
|
|
197
|
+
description,
|
|
198
|
+
parseArg,
|
|
199
|
+
defaultValue
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
requiredOption(
|
|
204
|
+
flags: string,
|
|
205
|
+
description?: string,
|
|
206
|
+
defaultValue?: string | boolean | string[]
|
|
207
|
+
): this;
|
|
208
|
+
requiredOption<T>(
|
|
209
|
+
flags: string,
|
|
210
|
+
description: string,
|
|
211
|
+
parseArg: (value: string, previous: T) => T,
|
|
212
|
+
defaultValue?: T
|
|
213
|
+
): this;
|
|
214
|
+
/** @deprecated since v7, instead use choices or a custom function */
|
|
215
|
+
requiredOption(
|
|
216
|
+
flags: string,
|
|
217
|
+
description: string,
|
|
218
|
+
regexp: RegExp,
|
|
219
|
+
defaultValue?: string | boolean | string[]
|
|
220
|
+
): this;
|
|
221
|
+
requiredOption<T>(
|
|
222
|
+
flags: string,
|
|
223
|
+
description?: string,
|
|
224
|
+
regexpOrDefaultValue?:
|
|
225
|
+
| string
|
|
226
|
+
| boolean
|
|
227
|
+
| string[]
|
|
228
|
+
| RegExp
|
|
229
|
+
| ((value: string, previous: T) => T),
|
|
230
|
+
defaultValue?: string | boolean | string[]
|
|
231
|
+
): this {
|
|
232
|
+
if (
|
|
233
|
+
this.findSimilarOption({
|
|
234
|
+
flags,
|
|
235
|
+
mandatory: true,
|
|
236
|
+
})
|
|
237
|
+
) {
|
|
238
|
+
return this;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return super.requiredOption(
|
|
242
|
+
flags,
|
|
243
|
+
// @ts-expect-error - Issues with overloading
|
|
244
|
+
description,
|
|
245
|
+
regexpOrDefaultValue,
|
|
246
|
+
defaultValue
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
protected findSimilarOption(option: { flags: string; mandatory: boolean }) {
|
|
251
|
+
try {
|
|
252
|
+
return findSimilarOption(option, this.options);
|
|
253
|
+
} catch (error) {
|
|
116
254
|
console.error(
|
|
117
255
|
c.red(
|
|
118
|
-
|
|
256
|
+
error instanceof Error
|
|
257
|
+
? error.message
|
|
258
|
+
: 'Error occurred during option setup'
|
|
119
259
|
)
|
|
120
260
|
);
|
|
121
261
|
|
|
122
|
-
|
|
262
|
+
throw error;
|
|
123
263
|
}
|
|
124
|
-
|
|
125
|
-
return await getDocumentServices({
|
|
126
|
-
output: {
|
|
127
|
-
postfixServices: args.postfixServices,
|
|
128
|
-
serviceNameBase: args.serviceNameBase,
|
|
129
|
-
},
|
|
130
|
-
servicesGlob: parseServicesFilterOption(args.filterServices),
|
|
131
|
-
source,
|
|
132
|
-
});
|
|
133
264
|
}
|
|
134
265
|
}
|
|
135
266
|
|
|
@@ -172,9 +303,6 @@ export type QraftCommandActionCallback = (
|
|
|
172
303
|
* Command arguments, e.g. `bin <input> [options]` where `[options]` is the `args`
|
|
173
304
|
*/
|
|
174
305
|
args: Record<string, any>;
|
|
175
|
-
/**
|
|
176
|
-
* Resolve the generator files
|
|
177
|
-
*/
|
|
178
306
|
/**
|
|
179
307
|
* Spinner instance
|
|
180
308
|
*/
|
|
@@ -183,10 +311,121 @@ export type QraftCommandActionCallback = (
|
|
|
183
311
|
* OpenAPI services
|
|
184
312
|
*/
|
|
185
313
|
services: Service[];
|
|
314
|
+
/**
|
|
315
|
+
* OpenAPI schema
|
|
316
|
+
*/
|
|
317
|
+
schema: OpenAPISchemaType;
|
|
186
318
|
/**
|
|
187
319
|
* Output options
|
|
188
320
|
*/
|
|
189
321
|
output: OutputOptions;
|
|
190
322
|
},
|
|
191
|
-
resolve: (files: GeneratorFile[]) =>
|
|
192
|
-
) =>
|
|
323
|
+
resolve: (files: GeneratorFile[]) => void
|
|
324
|
+
) => Promise<void>;
|
|
325
|
+
|
|
326
|
+
function findSimilarOption(
|
|
327
|
+
{
|
|
328
|
+
flags,
|
|
329
|
+
mandatory,
|
|
330
|
+
}: {
|
|
331
|
+
flags: string;
|
|
332
|
+
mandatory: boolean;
|
|
333
|
+
},
|
|
334
|
+
options: readonly Option[]
|
|
335
|
+
) {
|
|
336
|
+
const newOptionParsedFlags = splitOptionFlags(flags);
|
|
337
|
+
/** @see {@link https://github.com/tj/commander.js/blob/83c3f4e391754d2f80b179acc4bccc2d4d0c863d/lib/option.js#L15} Source implementation */
|
|
338
|
+
const optional = flags.includes('['); // A value is optional when the option is specified.
|
|
339
|
+
const required = flags.includes('<'); // A value must be supplied when the option is specified.
|
|
340
|
+
// variadic test ignores <value,...> et al which might be used to describe custom splitting of single argument
|
|
341
|
+
const variadic = /\w\.\.\.[>\]]$/.test(flags); // The option can take multiple values.
|
|
342
|
+
|
|
343
|
+
return options.find((existingOption) => {
|
|
344
|
+
const existingOptionParsedFlags = splitOptionFlags(existingOption.flags);
|
|
345
|
+
|
|
346
|
+
if (
|
|
347
|
+
!(
|
|
348
|
+
(existingOptionParsedFlags.longFlag !== undefined &&
|
|
349
|
+
existingOptionParsedFlags.longFlag ===
|
|
350
|
+
newOptionParsedFlags.longFlag) ||
|
|
351
|
+
(existingOptionParsedFlags.shortFlag !== undefined &&
|
|
352
|
+
existingOptionParsedFlags.shortFlag ===
|
|
353
|
+
newOptionParsedFlags.shortFlag)
|
|
354
|
+
)
|
|
355
|
+
) {
|
|
356
|
+
return false;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
if (
|
|
360
|
+
existingOptionParsedFlags.longFlag !== undefined &&
|
|
361
|
+
newOptionParsedFlags.longFlag !== undefined &&
|
|
362
|
+
existingOptionParsedFlags.longFlag !== newOptionParsedFlags.longFlag
|
|
363
|
+
) {
|
|
364
|
+
throw new Error(
|
|
365
|
+
`Long flag ${flags} already exists in the option list with flags: "${existingOption.flags}" and description: "${existingOption.description}"`
|
|
366
|
+
);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
if (
|
|
370
|
+
existingOptionParsedFlags.shortFlag !== undefined &&
|
|
371
|
+
newOptionParsedFlags.shortFlag !== undefined &&
|
|
372
|
+
existingOptionParsedFlags.shortFlag !== newOptionParsedFlags.shortFlag
|
|
373
|
+
) {
|
|
374
|
+
throw new Error(
|
|
375
|
+
`Short flag ${flags} already exists in the option list with flags: "${existingOption.flags}" and description: "${existingOption.description}"`
|
|
376
|
+
);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
if (required !== existingOption.required) {
|
|
380
|
+
throw new Error(
|
|
381
|
+
`Flag ${flags} already exists in the option list with flags: "${existingOption.flags}" and description: "${existingOption.description}" but with different required status`
|
|
382
|
+
);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
if (optional !== existingOption.optional) {
|
|
386
|
+
throw new Error(
|
|
387
|
+
`Flag ${flags} already exists in the option list with flags: "${existingOption.flags}" and description: "${existingOption.description}" but with different optional status`
|
|
388
|
+
);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
if (mandatory !== existingOption.mandatory) {
|
|
392
|
+
throw new Error(
|
|
393
|
+
`Flag ${flags} already exists in the option list with flags: "${existingOption.flags}" and description: "${existingOption.description}" but with different mandatory status`
|
|
394
|
+
);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
if (variadic !== existingOption.variadic) {
|
|
398
|
+
throw new Error(
|
|
399
|
+
`Flag ${flags} already exists in the option list with flags: "${existingOption.flags}" and description: "${existingOption.description}" but with different variadic status`
|
|
400
|
+
);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
return existingOption;
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* Split the short and long flag out of something like '-m,--mixed <value>'
|
|
409
|
+
* @link https://github.com/tj/commander.js/blob/83c3f4e391754d2f80b179acc4bccc2d4d0c863d/lib/option.js#L310
|
|
410
|
+
*/
|
|
411
|
+
export function splitOptionFlags(flags: string) {
|
|
412
|
+
let shortFlag;
|
|
413
|
+
let longFlag;
|
|
414
|
+
|
|
415
|
+
// Use original very loose parsing to maintain backwards compatibility for now,
|
|
416
|
+
// which allowed, for example, unintended `-sw, --short-word` [sic].
|
|
417
|
+
const flagParts = flags.split(/[ |,]+/);
|
|
418
|
+
|
|
419
|
+
if (flagParts.length > 1 && !/^[[<]/.test(flagParts[1]))
|
|
420
|
+
shortFlag = flagParts.shift();
|
|
421
|
+
|
|
422
|
+
longFlag = flagParts.shift();
|
|
423
|
+
|
|
424
|
+
// Add support for a lone short flag without significantly changing parsing!
|
|
425
|
+
if (!shortFlag && /^-[^-]$/.test(longFlag ?? '')) {
|
|
426
|
+
shortFlag = longFlag;
|
|
427
|
+
longFlag = undefined;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
return { shortFlag, longFlag };
|
|
431
|
+
}
|
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
import { QraftCommand } from './QraftCommand.js';
|
|
2
2
|
|
|
3
3
|
export interface QraftCommandPlugin {
|
|
4
|
-
setupCommand(command: QraftCommand): void
|
|
4
|
+
setupCommand(command: QraftCommand): void | Promise<void>;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Called after all plugins have been set up
|
|
8
|
+
*/
|
|
9
|
+
postSetupCommand?(
|
|
10
|
+
command: QraftCommand,
|
|
11
|
+
plugins: string[]
|
|
12
|
+
): void | Promise<void>;
|
|
5
13
|
}
|
package/src/lib/fileHeader.ts
CHANGED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import micromatch from 'micromatch';
|
|
2
|
+
|
|
3
|
+
import type { OpenAPISchemaType } from './open-api/OpenAPISchemaType.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Filter the schema to only include paths that match the servicesGlob
|
|
7
|
+
* @param schema OpenAPI schema
|
|
8
|
+
* @param servicesGlob Glob pattern to match services. E.g.: `['**']`
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* filterDocumentPaths(schema, ['/user/**', '/post/**', '!/user/internal'])
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
export const filterDocumentPaths = (
|
|
16
|
+
schema: OpenAPISchemaType,
|
|
17
|
+
servicesGlob: string[] | undefined
|
|
18
|
+
) => {
|
|
19
|
+
if (!servicesGlob) return schema;
|
|
20
|
+
|
|
21
|
+
const isPathMatch = createServicePathMatch(servicesGlob);
|
|
22
|
+
const paths: typeof schema.paths = {};
|
|
23
|
+
|
|
24
|
+
for (const path in schema.paths) {
|
|
25
|
+
if (!Object.prototype.hasOwnProperty.call(schema.paths, path)) continue;
|
|
26
|
+
if (!isPathMatch(path)) continue;
|
|
27
|
+
paths[path] = schema.paths[path];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return { ...schema, paths };
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Create a function to match service paths
|
|
35
|
+
* @param servicesGlob
|
|
36
|
+
*/
|
|
37
|
+
export const createServicePathMatch = (servicesGlob: string[]) => {
|
|
38
|
+
const servicePathGlobs = servicesGlob.reduce<
|
|
39
|
+
Record<'match' | 'ignore', string[]>
|
|
40
|
+
>(
|
|
41
|
+
(acc, glob) => {
|
|
42
|
+
glob.startsWith('!')
|
|
43
|
+
? acc.ignore.push(glob.slice(1))
|
|
44
|
+
: acc.match.push(glob);
|
|
45
|
+
return acc;
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
match: [],
|
|
49
|
+
ignore: [],
|
|
50
|
+
}
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
return function isServicePatchMatch(path: string) {
|
|
54
|
+
return micromatch.isMatch(path, servicePathGlobs.match, {
|
|
55
|
+
ignore: servicePathGlobs.ignore,
|
|
56
|
+
});
|
|
57
|
+
};
|
|
58
|
+
};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import process from 'node:process';
|
|
2
|
+
import { URL } from 'node:url';
|
|
3
|
+
import type { Ora } from 'ora';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Validates Schema's `input` and return `Readable` stream or `URL`
|
|
7
|
+
* @throws {Error} if input is invalid
|
|
8
|
+
*/
|
|
9
|
+
export function handleSchemaInput(input: unknown, cwd: URL, spinner: Ora) {
|
|
10
|
+
if (!input) {
|
|
11
|
+
/**
|
|
12
|
+
* Handle `stdin`, if no input file is provided
|
|
13
|
+
* @example
|
|
14
|
+
* ```bash
|
|
15
|
+
* cat schema.json | script.js
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
if (process.stdin.isTTY) {
|
|
19
|
+
spinner.fail(
|
|
20
|
+
'Input file not found or stdin is empty. Please specify `--input` option or pipe OpenAPI Schema to stdin.'
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
throw new Error('Invalid input.');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return process.stdin;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Handle single file
|
|
31
|
+
* @example
|
|
32
|
+
* ```bash
|
|
33
|
+
* script.js schema.json
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
if (typeof input !== 'string') {
|
|
38
|
+
spinner.fail('Invalid input. Please specify a single file or URL.');
|
|
39
|
+
|
|
40
|
+
throw new Error('Invalid input.');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// throw error on glob
|
|
44
|
+
if (input.includes('*')) {
|
|
45
|
+
spinner.fail(
|
|
46
|
+
'Globbing is not supported. Please specify a single file or URL.'
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
throw new Error('Globbing is not supported.');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return new URL(input, cwd);
|
|
53
|
+
}
|
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
export type OpenAPISchemaType = {
|
|
2
|
+
openapi: string;
|
|
3
|
+
info: {
|
|
4
|
+
title: string;
|
|
5
|
+
version: string;
|
|
6
|
+
};
|
|
2
7
|
paths: {
|
|
3
8
|
[path: string]: {
|
|
4
9
|
[method: string]: {
|
|
@@ -25,6 +30,7 @@ export type OpenAPISchemaType = {
|
|
|
25
30
|
};
|
|
26
31
|
};
|
|
27
32
|
};
|
|
33
|
+
security?: Array<Record<string, string[] | undefined>>;
|
|
28
34
|
};
|
|
29
35
|
};
|
|
30
36
|
};
|
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
import camelCase from 'camelcase';
|
|
2
|
-
import * as console from 'console';
|
|
3
|
-
import micromatch from 'micromatch';
|
|
4
2
|
|
|
5
3
|
import { getContentMediaType } from './getContent.js';
|
|
6
4
|
import { getOperationName } from './getOperationName.js';
|
|
@@ -46,6 +44,7 @@ export type ServiceOperation = {
|
|
|
46
44
|
errors: Record<string, string | undefined>;
|
|
47
45
|
success: Record<string, string | undefined>;
|
|
48
46
|
parameters: Record<string, any> | undefined;
|
|
47
|
+
security: Array<Record<string, string[] | undefined>> | undefined;
|
|
49
48
|
};
|
|
50
49
|
|
|
51
50
|
export interface ServiceOutputOptions {
|
|
@@ -58,21 +57,17 @@ export const getServices = (
|
|
|
58
57
|
{
|
|
59
58
|
postfixServices = 'Service',
|
|
60
59
|
serviceNameBase = 'endpoint[0]',
|
|
61
|
-
}: ServiceOutputOptions = {}
|
|
62
|
-
servicesGlob = ['**']
|
|
60
|
+
}: ServiceOutputOptions = {}
|
|
63
61
|
) => {
|
|
64
62
|
const paths = openApiJson.paths;
|
|
65
63
|
|
|
66
64
|
const services = new Map<string, Service>();
|
|
67
65
|
|
|
68
|
-
const isPathMatch = createServicePathMatch(servicesGlob);
|
|
69
|
-
|
|
70
66
|
for (const path in paths) {
|
|
71
67
|
if (!Object.prototype.hasOwnProperty.call(paths, path)) continue;
|
|
72
68
|
|
|
73
69
|
for (const method in paths[path]) {
|
|
74
70
|
if (!Object.prototype.hasOwnProperty.call(paths[path], method)) continue;
|
|
75
|
-
if (!isPathMatch(path)) continue;
|
|
76
71
|
|
|
77
72
|
if (!supportedMethod(method)) {
|
|
78
73
|
console.warn(
|
|
@@ -154,6 +149,7 @@ export const getServices = (
|
|
|
154
149
|
? getContentMediaType(methodOperation.requestBody.content)
|
|
155
150
|
: undefined,
|
|
156
151
|
success: success,
|
|
152
|
+
security: methodOperation.security,
|
|
157
153
|
});
|
|
158
154
|
}
|
|
159
155
|
}
|
|
@@ -162,33 +158,6 @@ export const getServices = (
|
|
|
162
158
|
return Array.from(services.values());
|
|
163
159
|
};
|
|
164
160
|
|
|
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
161
|
export const supportedMethod = (
|
|
193
162
|
method: unknown
|
|
194
163
|
): method is (typeof supportedHTTPMethods)[number] =>
|