@openapi-qraft/plugin 2.3.2 → 2.4.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 +6 -0
- package/dist/lib/QraftCommand.d.ts +10 -3
- package/dist/lib/QraftCommand.d.ts.map +1 -1
- package/dist/lib/QraftCommand.js +83 -21
- package/dist/lib/QraftCommand.js.map +1 -1
- package/dist/lib/RedoclyConfigCommand.d.ts +22 -0
- package/dist/lib/RedoclyConfigCommand.d.ts.map +1 -0
- package/dist/lib/RedoclyConfigCommand.js +127 -0
- package/dist/lib/RedoclyConfigCommand.js.map +1 -0
- package/dist/lib/getRedocAPIsToQraft.d.ts +15 -0
- package/dist/lib/getRedocAPIsToQraft.d.ts.map +1 -0
- package/dist/lib/getRedocAPIsToQraft.js +45 -0
- package/dist/lib/getRedocAPIsToQraft.js.map +1 -0
- package/dist/lib/handleSchemaInput.js +1 -1
- package/dist/lib/loadRedoclyConfig.d.ts +12 -0
- package/dist/lib/loadRedoclyConfig.d.ts.map +1 -0
- package/dist/lib/loadRedoclyConfig.js +17 -0
- package/dist/lib/loadRedoclyConfig.js.map +1 -0
- package/dist/lib/maybeEscapeShellArg.d.ts +2 -0
- package/dist/lib/maybeEscapeShellArg.d.ts.map +1 -0
- package/dist/lib/maybeEscapeShellArg.js +9 -0
- package/dist/lib/maybeEscapeShellArg.js.map +1 -0
- package/dist/lib/open-api/readSchema.d.ts +2 -1
- package/dist/lib/open-api/readSchema.d.ts.map +1 -1
- package/dist/lib/open-api/readSchema.js +1 -7
- package/dist/lib/open-api/readSchema.js.map +1 -1
- package/dist/lib/parseConfigToArgs.d.ts +7 -0
- package/dist/lib/parseConfigToArgs.d.ts.map +1 -0
- package/dist/lib/parseConfigToArgs.js +130 -0
- package/dist/lib/parseConfigToArgs.js.map +1 -0
- package/dist/lib/shouldQuoteCommandLineArg.d.ts +11 -0
- package/dist/lib/shouldQuoteCommandLineArg.d.ts.map +1 -0
- package/dist/lib/shouldQuoteCommandLineArg.js +33 -0
- package/dist/lib/shouldQuoteCommandLineArg.js.map +1 -0
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.js +1 -1
- package/package.json +2 -2
- package/src/lib/QraftCommand.ts +139 -28
- package/src/lib/RedoclyConfigCommand.ts +221 -0
- package/src/lib/getRedocAPIsToQraft.ts +87 -0
- package/src/lib/handleSchemaInput.ts +1 -1
- package/src/lib/loadRedoclyConfig.ts +47 -0
- package/src/lib/maybeEscapeShellArg.ts +10 -0
- package/src/lib/open-api/readSchema.ts +3 -11
- package/src/lib/parseConfigToArgs.ts +134 -0
- package/src/lib/shouldQuoteCommandLineArg.ts +32 -0
- package/src/packageVersion.ts +1 -1
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { relative } from 'node:path';
|
|
2
|
+
import { fileURLToPath, pathToFileURL, URL } from 'node:url';
|
|
3
|
+
import { CONFIG_FILE_NAMES } from '@redocly/openapi-core';
|
|
4
|
+
import c from 'ansi-colors';
|
|
5
|
+
import { Command, CommanderError, Option, ParseOptions } from 'commander';
|
|
6
|
+
import { getRedocAPIsToQraft } from './getRedocAPIsToQraft.js';
|
|
7
|
+
import { loadRedoclyConfig } from './loadRedoclyConfig.js';
|
|
8
|
+
import { maybeEscapeShellArg } from './maybeEscapeShellArg.js';
|
|
9
|
+
import { parseConfigToArgs } from './parseConfigToArgs.js';
|
|
10
|
+
import { QraftCommand } from './QraftCommand.js';
|
|
11
|
+
|
|
12
|
+
export const redoclyOption = (() => {
|
|
13
|
+
const bin = c.gray.underline('bin');
|
|
14
|
+
const __redocly = c.yellow('--redocly');
|
|
15
|
+
|
|
16
|
+
const examples = [
|
|
17
|
+
`${bin} ${__redocly}`,
|
|
18
|
+
`${bin} ${c.green('my-api')} ${__redocly}`,
|
|
19
|
+
`${bin} ${c.green('my-api@v1 my-api@v2')} ${__redocly}`,
|
|
20
|
+
`${bin} ${__redocly} ${c.yellow.underline('./my-redocly-config.yaml')}`,
|
|
21
|
+
].map((example) => `${c.italic.yellow(example)}`);
|
|
22
|
+
|
|
23
|
+
return new Option(
|
|
24
|
+
'--redocly [config]',
|
|
25
|
+
[
|
|
26
|
+
c.bold(`Use the Redocly configuration to generate multiple API clients`),
|
|
27
|
+
`If the [config] parameter is not specified, the default Redocly configuration will be used: [${CONFIG_FILE_NAMES.join(' | ')}].`,
|
|
28
|
+
`For more information about this option, use the command: ${c.yellow.italic('--redocly-help')}`,
|
|
29
|
+
`${c.underline('Examples:')}`,
|
|
30
|
+
...examples.map((example) => `${c.gray('$')} ${example}`),
|
|
31
|
+
].join('\n')
|
|
32
|
+
);
|
|
33
|
+
})();
|
|
34
|
+
/**
|
|
35
|
+
* @param name - not used
|
|
36
|
+
*/
|
|
37
|
+
export class RedoclyConfigCommand extends Command {
|
|
38
|
+
protected readonly cwd: URL;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Redocly API item and QraftCommand arguments
|
|
42
|
+
*/
|
|
43
|
+
protected parsedAPIs: RedoclyQraftAPIs = {};
|
|
44
|
+
|
|
45
|
+
constructor(name?: string) {
|
|
46
|
+
super(name);
|
|
47
|
+
this.cwd = pathToFileURL(`${process.cwd()}/`);
|
|
48
|
+
|
|
49
|
+
this.usage(c.green('[apis...] --redocly <config>'))
|
|
50
|
+
.argument(
|
|
51
|
+
'[apis...]',
|
|
52
|
+
'Optional list of Redocly APIs for which to generate API clients. If not specified, clients for all APIs will be generated.'
|
|
53
|
+
)
|
|
54
|
+
.allowUnknownOption(true)
|
|
55
|
+
.helpOption('--redocly-help', 'Display help for the `--redocly` option')
|
|
56
|
+
.addOption(redoclyOption)
|
|
57
|
+
.action(async (apis = [], args) => {
|
|
58
|
+
const redocly = args.redocly;
|
|
59
|
+
|
|
60
|
+
if (!redocly) return;
|
|
61
|
+
|
|
62
|
+
const spinner = QraftCommand.spinner;
|
|
63
|
+
|
|
64
|
+
spinner.start('Loading Redocly configuration ⚙︎');
|
|
65
|
+
|
|
66
|
+
const redoc = await loadRedoclyConfig(redocly || true, this.cwd);
|
|
67
|
+
|
|
68
|
+
const redocConfigFile = redoc.configFile;
|
|
69
|
+
|
|
70
|
+
if (!redocConfigFile) {
|
|
71
|
+
spinner.fail(
|
|
72
|
+
'No "configFile" found in the Redocly configuration. Please specify the correct path to the Redocly configuration file.'
|
|
73
|
+
);
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const redocAPIsToQraftEntries = Object.entries(
|
|
78
|
+
getRedocAPIsToQraft(redoc, this.cwd, spinner)
|
|
79
|
+
).filter(([apiName]) => !apis.length || apis.includes(apiName));
|
|
80
|
+
|
|
81
|
+
if (apis.length) {
|
|
82
|
+
const notFoundAPIs = apis.filter(
|
|
83
|
+
(inputAPIName: string) =>
|
|
84
|
+
!redocAPIsToQraftEntries.some(
|
|
85
|
+
([apiName]) => apiName === inputAPIName
|
|
86
|
+
)
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
if (notFoundAPIs.length) {
|
|
90
|
+
spinner.fail(
|
|
91
|
+
`The specified API${
|
|
92
|
+
notFoundAPIs.length > 1 ? 's' : ''
|
|
93
|
+
} ${notFoundAPIs.map(c.red).join(', ')} ${notFoundAPIs.length > 1 ? 'were' : 'was'} not found in the Redocly configuration.`
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
throw new CommanderError(
|
|
97
|
+
1,
|
|
98
|
+
'ERR_API_NOT_FOUND',
|
|
99
|
+
`The specified API${
|
|
100
|
+
notFoundAPIs.length > 1 ? 's' : ''
|
|
101
|
+
} ${c.red(notFoundAPIs.join(', '))} ${notFoundAPIs.length > 1 ? 'were' : 'was'} not found in the Redocly configuration.`
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
redocAPIsToQraftEntries.forEach(([apiName, api]) => {
|
|
107
|
+
const {
|
|
108
|
+
['x-openapi-qraft']: { ['output-dir']: outputDir, ...qraftConfig },
|
|
109
|
+
} = api;
|
|
110
|
+
|
|
111
|
+
const cwd = fileURLToPath(this.cwd);
|
|
112
|
+
|
|
113
|
+
this.parsedAPIs[apiName] = [
|
|
114
|
+
apiName,
|
|
115
|
+
...parseConfigToArgs({
|
|
116
|
+
redocly: relative(cwd, redocConfigFile),
|
|
117
|
+
'output-dir': relative(cwd, outputDir),
|
|
118
|
+
...qraftConfig,
|
|
119
|
+
}),
|
|
120
|
+
];
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
spinner.stop();
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async parseConfig<T>(
|
|
128
|
+
callbackFn: CallbackFn<T>,
|
|
129
|
+
argv: readonly string[],
|
|
130
|
+
options?: ParseOptions
|
|
131
|
+
): Promise<T[] | undefined> {
|
|
132
|
+
await this.parseAsync(argv, options);
|
|
133
|
+
|
|
134
|
+
if (!Object.entries(this.parsedAPIs).length) return;
|
|
135
|
+
|
|
136
|
+
return RedoclyConfigCommand.forEachRedoclyAPIEntry(
|
|
137
|
+
this.parsedAPIs,
|
|
138
|
+
callbackFn
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
static async forEachRedoclyAPIEntry<T>(
|
|
143
|
+
redoclyAPIs: RedoclyQraftAPIs,
|
|
144
|
+
callbackFn: CallbackFn<T>
|
|
145
|
+
) {
|
|
146
|
+
const redoclyAPIsEntries = Object.entries(redoclyAPIs);
|
|
147
|
+
|
|
148
|
+
const spinner = QraftCommand.spinner;
|
|
149
|
+
|
|
150
|
+
spinner.info('Loading Redocly configuration...');
|
|
151
|
+
|
|
152
|
+
const { errors, results } = await Promise.allSettled(
|
|
153
|
+
redoclyAPIsEntries.map(
|
|
154
|
+
async ([apiName, [openAPIDocument, ...apiProcessArgv]]) => {
|
|
155
|
+
spinner.info(
|
|
156
|
+
`Generating API client for ${c.magenta(apiName)} with the following parameters:\n` +
|
|
157
|
+
c.gray.italic('bin ') +
|
|
158
|
+
`${c.green.italic(openAPIDocument)}` +
|
|
159
|
+
apiProcessArgv
|
|
160
|
+
.map((arg) =>
|
|
161
|
+
arg.startsWith('--')
|
|
162
|
+
? c.yellow.italic(arg)
|
|
163
|
+
: c.yellow.italic.underline(maybeEscapeShellArg(arg))
|
|
164
|
+
)
|
|
165
|
+
.join(' ')
|
|
166
|
+
);
|
|
167
|
+
spinner.text = '';
|
|
168
|
+
spinner.start();
|
|
169
|
+
|
|
170
|
+
return callbackFn([openAPIDocument, ...apiProcessArgv], {
|
|
171
|
+
from: 'user',
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
)
|
|
175
|
+
).then((results) =>
|
|
176
|
+
results.reduce<{ errors: unknown[]; results: T[] }>(
|
|
177
|
+
(acc, result) => {
|
|
178
|
+
if (result.status === 'rejected') acc.errors.push(result.reason);
|
|
179
|
+
else acc.results.push(result.value);
|
|
180
|
+
|
|
181
|
+
return acc;
|
|
182
|
+
},
|
|
183
|
+
{ errors: [], results: [] }
|
|
184
|
+
)
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
if (errors.length) {
|
|
188
|
+
throw new CommanderError(
|
|
189
|
+
1,
|
|
190
|
+
'ERROR',
|
|
191
|
+
'The following errors occurred during API client generation:\n' +
|
|
192
|
+
errors
|
|
193
|
+
.map((error) =>
|
|
194
|
+
error instanceof Error
|
|
195
|
+
? error.message + '\n' + error.stack
|
|
196
|
+
: JSON.stringify(error, null, 2)
|
|
197
|
+
)
|
|
198
|
+
.join('\n\n')
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
spinner.succeed(
|
|
203
|
+
c.green(`API clients successfully generated for: `) +
|
|
204
|
+
redoclyAPIsEntries
|
|
205
|
+
.map(([apiName]) => `"${c.magenta(apiName)}"`)
|
|
206
|
+
.join(', ') +
|
|
207
|
+
'.'
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
return results;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
type CallbackFn<T> = (
|
|
215
|
+
processArgv: string[],
|
|
216
|
+
processArgvParseOptions?: ParseOptions
|
|
217
|
+
) => T;
|
|
218
|
+
|
|
219
|
+
type RedoclyQraftAPIs = {
|
|
220
|
+
[apiName: string]: string[];
|
|
221
|
+
};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import type { Config } from '@redocly/openapi-core';
|
|
2
|
+
import { isAbsolute } from 'node:path';
|
|
3
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
4
|
+
import { CommanderError } from 'commander';
|
|
5
|
+
import { Ora } from 'ora';
|
|
6
|
+
|
|
7
|
+
export const QRAFT_REDOC_CONFIG_KEY = 'x-openapi-qraft';
|
|
8
|
+
|
|
9
|
+
export function getRedocAPIsToQraft(
|
|
10
|
+
redoc: Config,
|
|
11
|
+
cwd: URL,
|
|
12
|
+
spinner: Ora,
|
|
13
|
+
apiName?: string
|
|
14
|
+
) {
|
|
15
|
+
const hasRedoclyApis = Object.keys(redoc?.apis ?? {}).length > 0;
|
|
16
|
+
if (!hasRedoclyApis) {
|
|
17
|
+
throw new CommanderError(
|
|
18
|
+
1,
|
|
19
|
+
'ERR_MISSING_APIS',
|
|
20
|
+
'No "apis" found in Redocly config. Please specify the APIs to be generated.'
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
if (!redoc.configFile) {
|
|
24
|
+
throw new CommanderError(
|
|
25
|
+
1,
|
|
26
|
+
'ERR_MISSING_CONFIG_FILE',
|
|
27
|
+
'No "configFile" found in Redocly config. Please specify the path to the Redocly config file.'
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const configRoot = isAbsolute(redoc.configFile)
|
|
32
|
+
? new URL(pathToFileURL(redoc.configFile))
|
|
33
|
+
: new URL(redoc.configFile, cwd);
|
|
34
|
+
|
|
35
|
+
const apisToQraftEntries = Object.entries(redoc.apis)
|
|
36
|
+
.map(([itemPpiName, api]) => {
|
|
37
|
+
if (apiName && apiName !== itemPpiName) return;
|
|
38
|
+
|
|
39
|
+
const qraftConfig =
|
|
40
|
+
QRAFT_REDOC_CONFIG_KEY in api ? api[QRAFT_REDOC_CONFIG_KEY] : undefined;
|
|
41
|
+
|
|
42
|
+
if (!qraftConfig) {
|
|
43
|
+
return void spinner.warn(
|
|
44
|
+
`API ${itemPpiName} is missing an \`${QRAFT_REDOC_CONFIG_KEY}\` key. Skipping...`
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (typeof qraftConfig !== 'object') {
|
|
49
|
+
throw new CommanderError(
|
|
50
|
+
1,
|
|
51
|
+
'ERR_MISSING_API_CONFIG',
|
|
52
|
+
`API ${itemPpiName} is missing an \`${QRAFT_REDOC_CONFIG_KEY}\` key. See https://openapi-qraft.github.io/openapi-qraft/docs/codegen/CLI/redocly-config.`
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const {
|
|
57
|
+
o,
|
|
58
|
+
['output-dir']: outputPath = o,
|
|
59
|
+
...qraftConfigRest
|
|
60
|
+
} = qraftConfig as {
|
|
61
|
+
['output-dir']?: string;
|
|
62
|
+
o?: string;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
if (!outputPath) {
|
|
66
|
+
throw new CommanderError(
|
|
67
|
+
1,
|
|
68
|
+
'ERR_MISSING_API_OUTPUT',
|
|
69
|
+
`API ${itemPpiName} is missing an "output-dir" key. See https://openapi-qraft.github.io/openapi-qraft/docs/codegen/CLI/redocly-config.`
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return [
|
|
74
|
+
itemPpiName,
|
|
75
|
+
{
|
|
76
|
+
...api,
|
|
77
|
+
[QRAFT_REDOC_CONFIG_KEY]: {
|
|
78
|
+
...qraftConfigRest,
|
|
79
|
+
['output-dir']: fileURLToPath(new URL(outputPath, configRoot)),
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
] as const;
|
|
83
|
+
})
|
|
84
|
+
.filter((api) => !!api);
|
|
85
|
+
|
|
86
|
+
return Object.fromEntries(apisToQraftEntries);
|
|
87
|
+
}
|
|
@@ -17,7 +17,7 @@ export function handleSchemaInput(input: unknown, cwd: URL, spinner: Ora) {
|
|
|
17
17
|
*/
|
|
18
18
|
if (process.stdin.isTTY) {
|
|
19
19
|
spinner.fail(
|
|
20
|
-
'Input file not found or stdin is empty. Please specify
|
|
20
|
+
'Input file not found or stdin is empty. Please specify `input` argument or pipe OpenAPI Schema to stdin.'
|
|
21
21
|
);
|
|
22
22
|
|
|
23
23
|
throw new Error('Invalid input.');
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { fileURLToPath, URL } from 'node:url';
|
|
2
|
+
import {
|
|
3
|
+
Config,
|
|
4
|
+
CONFIG_FILE_NAMES,
|
|
5
|
+
findConfig,
|
|
6
|
+
loadConfig,
|
|
7
|
+
} from '@redocly/openapi-core';
|
|
8
|
+
import { CommanderError } from 'commander';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Loads Redocly config from the default config file:
|
|
12
|
+
* 'redocly.yaml', 'redocly.yml', '.redocly.yaml', '.redocly.yml'
|
|
13
|
+
*/
|
|
14
|
+
export async function loadRedoclyConfig(
|
|
15
|
+
configPath: null | undefined | true,
|
|
16
|
+
cwd: URL
|
|
17
|
+
): Promise<Config>;
|
|
18
|
+
/**
|
|
19
|
+
* Loads Redocly config from the specified path or from the default config file.
|
|
20
|
+
*/
|
|
21
|
+
export async function loadRedoclyConfig(
|
|
22
|
+
configPath: string,
|
|
23
|
+
cwd: URL
|
|
24
|
+
): Promise<Config>;
|
|
25
|
+
export async function loadRedoclyConfig(
|
|
26
|
+
configPath: string | null | undefined | true,
|
|
27
|
+
cwd: URL
|
|
28
|
+
): Promise<Config> {
|
|
29
|
+
const normalizedConfigPath =
|
|
30
|
+
typeof configPath === 'string'
|
|
31
|
+
? configPath
|
|
32
|
+
: findConfig(fileURLToPath(cwd));
|
|
33
|
+
|
|
34
|
+
if (!normalizedConfigPath) {
|
|
35
|
+
throw new CommanderError(
|
|
36
|
+
1,
|
|
37
|
+
'INVALID_REDOCLY_CONFIG_PATH',
|
|
38
|
+
typeof configPath === 'string'
|
|
39
|
+
? `Could not find Redocly config file at ${configPath}`
|
|
40
|
+
: `Could not find Redocly config file in ${cwd}. Searched in: ${CONFIG_FILE_NAMES.join(', ')}`
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return await loadConfig({
|
|
45
|
+
configPath: normalizedConfigPath,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { shouldQuoteCommandLineArg } from './shouldQuoteCommandLineArg.js';
|
|
2
|
+
|
|
3
|
+
export function maybeEscapeShellArg(arg: string): string {
|
|
4
|
+
if (shouldQuoteCommandLineArg(arg)) {
|
|
5
|
+
// bash-safe single-quote escaping
|
|
6
|
+
return `'${arg.replace(/'/g, `'\\''`)}'`;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
return arg;
|
|
10
|
+
}
|
|
@@ -1,20 +1,12 @@
|
|
|
1
1
|
import type { Readable } from 'node:stream';
|
|
2
|
+
import type { Config } from '@redocly/openapi-core';
|
|
2
3
|
import type { OpenAPI3 } from 'openapi-typescript';
|
|
3
|
-
import { createConfig } from '@redocly/openapi-core';
|
|
4
4
|
import { validateAndBundle } from 'openapi-typescript/dist/lib/redoc.js';
|
|
5
5
|
|
|
6
6
|
export const readSchema = async (
|
|
7
|
-
source: string | URL | OpenAPI3 | Readable | Buffer
|
|
7
|
+
source: string | URL | OpenAPI3 | Readable | Buffer,
|
|
8
|
+
redoc: Config
|
|
8
9
|
) => {
|
|
9
|
-
const redoc = await createConfig(
|
|
10
|
-
{
|
|
11
|
-
rules: {
|
|
12
|
-
'operation-operationId-unique': { severity: 'error' }, // throw error on duplicate operationIDs
|
|
13
|
-
},
|
|
14
|
-
},
|
|
15
|
-
{ extends: ['minimal'] }
|
|
16
|
-
);
|
|
17
|
-
|
|
18
10
|
const parsed = await validateAndBundle(source, {
|
|
19
11
|
redoc,
|
|
20
12
|
silent: false,
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Converts a parsed YAML configuration into command line arguments
|
|
3
|
+
* @param config The parsed YAML configuration object
|
|
4
|
+
* @returns Array of command line arguments
|
|
5
|
+
*/
|
|
6
|
+
export function parseConfigToArgs(config: Record<string, unknown>): string[] {
|
|
7
|
+
const args: string[] = [];
|
|
8
|
+
|
|
9
|
+
const isPrimitive = (val: any): val is string | number | boolean =>
|
|
10
|
+
typeof val === 'string' ||
|
|
11
|
+
typeof val === 'number' ||
|
|
12
|
+
typeof val === 'boolean';
|
|
13
|
+
|
|
14
|
+
function flattenMapping(node: any, prefixKeys: string[]): string[] {
|
|
15
|
+
const results: string[] = [];
|
|
16
|
+
|
|
17
|
+
if (isPrimitive(node)) {
|
|
18
|
+
if (typeof node === 'boolean') {
|
|
19
|
+
if (node) {
|
|
20
|
+
results.push(prefixKeys.join(':'));
|
|
21
|
+
}
|
|
22
|
+
} else {
|
|
23
|
+
results.push(`${prefixKeys.join(':')}:${node.toString()}`);
|
|
24
|
+
}
|
|
25
|
+
return results;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (Array.isArray(node)) {
|
|
29
|
+
if (node.every((el) => isPrimitive(el))) {
|
|
30
|
+
const primitives = node.filter(
|
|
31
|
+
(el) => el !== null && el !== undefined
|
|
32
|
+
) as (string | number | boolean)[];
|
|
33
|
+
const joined = primitives.map((el) => el.toString()).join(',');
|
|
34
|
+
if (joined) {
|
|
35
|
+
results.push(`${prefixKeys.join(':')}:${joined}`);
|
|
36
|
+
}
|
|
37
|
+
return results;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
for (const el of node) {
|
|
41
|
+
if (el == null) {
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
results.push(...flattenMapping(el, prefixKeys));
|
|
45
|
+
}
|
|
46
|
+
return results;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (typeof node === 'object' && node !== null) {
|
|
50
|
+
for (const [key, value] of Object.entries(node)) {
|
|
51
|
+
results.push(...flattenMapping(value, [...prefixKeys, key]));
|
|
52
|
+
}
|
|
53
|
+
return results;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return results;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
for (const [key, value] of Object.entries(config)) {
|
|
60
|
+
if (typeof value === 'string' || typeof value === 'number') {
|
|
61
|
+
args.push(`--${key}`, value.toString());
|
|
62
|
+
} else if (typeof value === 'boolean') {
|
|
63
|
+
if (value) {
|
|
64
|
+
args.push(`--${key}`);
|
|
65
|
+
}
|
|
66
|
+
} else if (value == null) {
|
|
67
|
+
continue;
|
|
68
|
+
} else if (Array.isArray(value)) {
|
|
69
|
+
if (value.every((el) => isPrimitive(el))) {
|
|
70
|
+
const primitives = value.filter(
|
|
71
|
+
(el) => el !== null && el !== undefined
|
|
72
|
+
) as (string | number | boolean)[];
|
|
73
|
+
args.push(`--${key}`, ...primitives.map((el) => el.toString()));
|
|
74
|
+
} else {
|
|
75
|
+
for (const el of value) {
|
|
76
|
+
if (el == null) {
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
if (typeof el === 'object') {
|
|
80
|
+
const mappingPaths = flattenMapping(el, []);
|
|
81
|
+
if (mappingPaths.length) {
|
|
82
|
+
args.push(`--${key}`, ...mappingPaths);
|
|
83
|
+
}
|
|
84
|
+
} else if (isPrimitive(el)) {
|
|
85
|
+
args.push(`--${key}`, el.toString());
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
} else if (typeof value === 'object') {
|
|
90
|
+
for (const [childKey, childValue] of Object.entries(
|
|
91
|
+
value as Record<string, unknown>
|
|
92
|
+
)) {
|
|
93
|
+
if (typeof childValue === 'string' || typeof childValue === 'number') {
|
|
94
|
+
args.push(`--${key}`, childKey, childValue.toString());
|
|
95
|
+
} else if (typeof childValue === 'boolean') {
|
|
96
|
+
if (childValue) {
|
|
97
|
+
args.push(`--${key}`, childKey);
|
|
98
|
+
}
|
|
99
|
+
} else if (childValue == null) {
|
|
100
|
+
continue;
|
|
101
|
+
} else if (Array.isArray(childValue)) {
|
|
102
|
+
if (childValue.every((el) => isPrimitive(el))) {
|
|
103
|
+
const primitives = childValue.filter(
|
|
104
|
+
(el) => el !== null && el !== undefined
|
|
105
|
+
) as (string | number | boolean)[];
|
|
106
|
+
const joined = primitives.map((el) => el.toString()).join(',');
|
|
107
|
+
args.push(`--${key}`, childKey, joined);
|
|
108
|
+
} else {
|
|
109
|
+
for (const el of childValue) {
|
|
110
|
+
if (el == null) {
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
if (typeof el === 'object') {
|
|
114
|
+
const mappingPaths = flattenMapping(el, [childKey]);
|
|
115
|
+
if (mappingPaths.length) {
|
|
116
|
+
args.push(`--${key}`, ...mappingPaths);
|
|
117
|
+
}
|
|
118
|
+
} else if (isPrimitive(el)) {
|
|
119
|
+
args.push(`--${key}`, childKey, el.toString());
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
} else if (typeof childValue === 'object') {
|
|
124
|
+
const mappingPaths = flattenMapping(childValue, []);
|
|
125
|
+
if (mappingPaths.length) {
|
|
126
|
+
args.push(`--${key}`, childKey, ...mappingPaths);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return args;
|
|
134
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Checks if a command-line argument needs to be quoted when used in a command line.
|
|
3
|
+
*
|
|
4
|
+
* Arguments containing special characters like spaces, quotes, or shell metacharacters
|
|
5
|
+
* need to be wrapped in quotes to be properly interpreted by the shell.
|
|
6
|
+
*
|
|
7
|
+
* @param arg The command-line argument to check
|
|
8
|
+
* @returns True if the argument needs quotes, false otherwise
|
|
9
|
+
*/
|
|
10
|
+
export function shouldQuoteCommandLineArg(arg: string): boolean {
|
|
11
|
+
if (!arg) return false;
|
|
12
|
+
|
|
13
|
+
// Check for spaces
|
|
14
|
+
if (arg.includes(' ')) return true;
|
|
15
|
+
|
|
16
|
+
// Check for quotes
|
|
17
|
+
if (arg.includes('"') || arg.includes("'")) return true;
|
|
18
|
+
|
|
19
|
+
// Check for shell metacharacters and special characters
|
|
20
|
+
const shellSpecialChars = /[&|<>(){}[\]$;!*?~`\n\r\t#]/;
|
|
21
|
+
if (shellSpecialChars.test(arg)) return true;
|
|
22
|
+
|
|
23
|
+
// Check for control characters
|
|
24
|
+
// eslint-disable-next-line no-control-regex
|
|
25
|
+
if (/[\x00-\x1F\x7F]/.test(arg)) return true;
|
|
26
|
+
|
|
27
|
+
// Check if argument starts with a dash (to avoid confusion with options)
|
|
28
|
+
if (arg.startsWith('-') && !arg.startsWith('--')) return true;
|
|
29
|
+
|
|
30
|
+
// Check for wildcards
|
|
31
|
+
return arg.includes('*') || arg.includes('?');
|
|
32
|
+
}
|
package/src/packageVersion.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// This file was generated automatically
|
|
2
|
-
export const packageVersion = '2.
|
|
2
|
+
export const packageVersion = '2.4.0';
|