@openapi-qraft/plugin 1.8.0 → 1.9.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 (70) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/index.d.ts +3 -0
  3. package/dist/index.d.ts.map +1 -0
  4. package/dist/index.js +2 -0
  5. package/dist/index.js.map +1 -0
  6. package/dist/lib/GeneratorFile.d.ts +1 -0
  7. package/dist/lib/GeneratorFile.d.ts.map +1 -0
  8. package/dist/lib/OutputOptions.d.ts +1 -0
  9. package/dist/lib/OutputOptions.d.ts.map +1 -0
  10. package/dist/lib/QraftCommand.d.ts +35 -7
  11. package/dist/lib/QraftCommand.d.ts.map +1 -0
  12. package/dist/lib/QraftCommand.js +165 -56
  13. package/dist/lib/QraftCommand.js.map +1 -1
  14. package/dist/lib/QraftCommandPlugin.d.ts +6 -1
  15. package/dist/lib/QraftCommandPlugin.d.ts.map +1 -0
  16. package/dist/lib/fileHeader.d.ts +2 -1
  17. package/dist/lib/fileHeader.d.ts.map +1 -0
  18. package/dist/lib/fileHeader.js +6 -2
  19. package/dist/lib/fileHeader.js.map +1 -1
  20. package/dist/lib/filterDocumentPaths.d.ts +18 -0
  21. package/dist/lib/filterDocumentPaths.d.ts.map +1 -0
  22. package/dist/lib/filterDocumentPaths.js +46 -0
  23. package/dist/lib/filterDocumentPaths.js.map +1 -0
  24. package/dist/lib/formatFileHeader.d.ts +2 -0
  25. package/dist/lib/formatFileHeader.d.ts.map +1 -0
  26. package/dist/lib/formatFileHeader.js +6 -0
  27. package/dist/lib/formatFileHeader.js.map +1 -0
  28. package/dist/lib/handleSchemaInput.d.ts +12 -0
  29. package/dist/lib/handleSchemaInput.d.ts.map +1 -0
  30. package/dist/lib/handleSchemaInput.js +40 -0
  31. package/dist/lib/handleSchemaInput.js.map +1 -0
  32. package/dist/lib/open-api/OpenAPISchemaType.d.ts +6 -0
  33. package/dist/lib/open-api/OpenAPISchemaType.d.ts.map +1 -0
  34. package/dist/lib/open-api/getContent.d.ts +1 -0
  35. package/dist/lib/open-api/getContent.d.ts.map +1 -0
  36. package/dist/lib/open-api/getOperationName.d.ts +1 -0
  37. package/dist/lib/open-api/getOperationName.d.ts.map +1 -0
  38. package/dist/lib/open-api/getServiceName.d.ts +1 -0
  39. package/dist/lib/open-api/getServiceName.d.ts.map +1 -0
  40. package/dist/lib/open-api/getServiceNamesByOperationEndpoint.d.ts +1 -0
  41. package/dist/lib/open-api/getServiceNamesByOperationEndpoint.d.ts.map +1 -0
  42. package/dist/lib/open-api/getServiceNamesByOperationTags.d.ts +1 -0
  43. package/dist/lib/open-api/getServiceNamesByOperationTags.d.ts.map +1 -0
  44. package/dist/lib/open-api/getServices.d.ts +2 -6
  45. package/dist/lib/open-api/getServices.d.ts.map +1 -0
  46. package/dist/lib/open-api/getServices.js +1 -26
  47. package/dist/lib/open-api/getServices.js.map +1 -1
  48. package/dist/lib/open-api/readSchema.d.ts +1 -1
  49. package/dist/lib/open-api/readSchema.d.ts.map +1 -0
  50. package/dist/lib/vitestFsMock.d.ts +2 -0
  51. package/dist/lib/vitestFsMock.d.ts.map +1 -0
  52. package/dist/lib/vitestFsMock.js +16 -0
  53. package/dist/lib/vitestFsMock.js.map +1 -0
  54. package/dist/lib/writeGeneratorFiles.d.ts +1 -0
  55. package/dist/lib/writeGeneratorFiles.d.ts.map +1 -0
  56. package/package.json +4 -2
  57. package/src/index.ts +2 -0
  58. package/src/lib/QraftCommand.ts +316 -77
  59. package/src/lib/QraftCommandPlugin.ts +9 -1
  60. package/src/lib/fileHeader.ts +6 -2
  61. package/src/lib/filterDocumentPaths.ts +58 -0
  62. package/src/lib/formatFileHeader.ts +4 -0
  63. package/src/lib/handleSchemaInput.ts +53 -0
  64. package/src/lib/open-api/OpenAPISchemaType.ts +5 -0
  65. package/src/lib/open-api/getServices.ts +1 -34
  66. package/src/lib/vitestFsMock.ts +23 -0
  67. package/dist/lib/open-api/getDocumentServices.d.ts +0 -12
  68. package/dist/lib/open-api/getDocumentServices.js +0 -7
  69. package/dist/lib/open-api/getDocumentServices.js.map +0 -1
  70. package/src/lib/open-api/getDocumentServices.ts +0 -19
@@ -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 { getDocumentServices } from './open-api/getDocumentServices.js';
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 Schema file path, URL (json, yml)',
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
- return super.action(async (...actionArgs) => {
49
- const { version: packageVersion } = await import(
50
- '@openapi-qraft/plugin/package.json',
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
- const inputs = actionArgs.filter(
57
- (arg) => typeof arg === 'string'
58
- ) as string[];
59
- const args = actionArgs.find(
60
- (arg) => arg && typeof arg === 'object'
61
- ) as Record<string, any>;
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
- if (!args) throw new Error('Arguments object not found');
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
- if (args.version) {
66
- console.info(`v${packageVersion}`);
67
- process.exit(0);
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
- console.info(`✨ ${c.bold(`OpenAPI Qraft ${packageVersion}`)}`);
71
-
72
- const spinner = ora('Starting the Qraft ⛏︎').start();
73
-
74
- await callback(
75
- {
76
- inputs,
77
- args,
78
- spinner,
79
- services: await this.#getServices(inputs[0], args),
80
- output: {
81
- dir: normalizeOutputDirPath(args.outputDir),
82
- clean: args.clean,
83
- },
84
- },
85
- async function resolveGeneratorFiles(fileItems) {
86
- try {
87
- await writeGeneratorFiles({
88
- fileItems,
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
- spinner.succeed(c.green('Qraft has been finished'));
92
- } catch (error) {
93
- spinner.fail(c.red('Error occurred during generation'));
94
-
95
- if (error instanceof Error) {
96
- console.error(c.red(error.message), c.red(error.stack ?? ''));
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
- async #getServices(input: unknown, args: Record<string, any>) {
108
- const cwd = `${process.cwd()}/`;
152
+ protected registerPluginAction(callback: QraftCommandActionCallback) {
153
+ this.registeredPluginActions.push(callback);
154
+ }
109
155
 
110
- const source =
111
- input && typeof input === 'string'
112
- ? new URL(input, pathToFileURL(cwd))
113
- : process.stdin;
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
- if (source === process.stdin && source.isTTY) {
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
- 'Input file not found or stdin is empty. Please specify `--input` option or pipe OpenAPI Schema to stdin.'
256
+ error instanceof Error
257
+ ? error.message
258
+ : 'Error occurred during option setup'
119
259
  )
120
260
  );
121
261
 
122
- process.exit(1);
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[]) => Promise<void>
192
- ) => void | Promise<void>;
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
  }
@@ -1,2 +1,6 @@
1
- export const fileHeader = `/* generated using @openapi-qraft/cli -- do no edit */
2
- /* istanbul ignore file */`;
1
+ export const fileHeader = `/**
2
+ * This file was auto-generated by @openapi-qraft/cli.
3
+ * Do not make direct changes to the file.
4
+ */
5
+
6
+ `;
@@ -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,4 @@
1
+ export const formatFileHeader = (fileHeader: string | undefined) => {
2
+ if (!fileHeader) return '';
3
+ return `${fileHeader}${fileHeader.endsWith('\n') ? '' : '\n'}`;
4
+ };
@@ -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]: {
@@ -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';
@@ -58,21 +56,17 @@ export const getServices = (
58
56
  {
59
57
  postfixServices = 'Service',
60
58
  serviceNameBase = 'endpoint[0]',
61
- }: ServiceOutputOptions = {},
62
- servicesGlob = ['**']
59
+ }: ServiceOutputOptions = {}
63
60
  ) => {
64
61
  const paths = openApiJson.paths;
65
62
 
66
63
  const services = new Map<string, Service>();
67
64
 
68
- const isPathMatch = createServicePathMatch(servicesGlob);
69
-
70
65
  for (const path in paths) {
71
66
  if (!Object.prototype.hasOwnProperty.call(paths, path)) continue;
72
67
 
73
68
  for (const method in paths[path]) {
74
69
  if (!Object.prototype.hasOwnProperty.call(paths[path], method)) continue;
75
- if (!isPathMatch(path)) continue;
76
70
 
77
71
  if (!supportedMethod(method)) {
78
72
  console.warn(
@@ -162,33 +156,6 @@ export const getServices = (
162
156
  return Array.from(services.values());
163
157
  };
164
158
 
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
159
  export const supportedMethod = (
193
160
  method: unknown
194
161
  ): method is (typeof supportedHTTPMethods)[number] =>