@openapi-qraft/plugin 2.3.2 → 2.4.1

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 (47) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/lib/QraftCommand.d.ts +10 -3
  3. package/dist/lib/QraftCommand.d.ts.map +1 -1
  4. package/dist/lib/QraftCommand.js +83 -21
  5. package/dist/lib/QraftCommand.js.map +1 -1
  6. package/dist/lib/RedoclyConfigCommand.d.ts +22 -0
  7. package/dist/lib/RedoclyConfigCommand.d.ts.map +1 -0
  8. package/dist/lib/RedoclyConfigCommand.js +127 -0
  9. package/dist/lib/RedoclyConfigCommand.js.map +1 -0
  10. package/dist/lib/getRedocAPIsToQraft.d.ts +15 -0
  11. package/dist/lib/getRedocAPIsToQraft.d.ts.map +1 -0
  12. package/dist/lib/getRedocAPIsToQraft.js +45 -0
  13. package/dist/lib/getRedocAPIsToQraft.js.map +1 -0
  14. package/dist/lib/handleSchemaInput.js +1 -1
  15. package/dist/lib/loadRedoclyConfig.d.ts +12 -0
  16. package/dist/lib/loadRedoclyConfig.d.ts.map +1 -0
  17. package/dist/lib/loadRedoclyConfig.js +17 -0
  18. package/dist/lib/loadRedoclyConfig.js.map +1 -0
  19. package/dist/lib/maybeEscapeShellArg.d.ts +2 -0
  20. package/dist/lib/maybeEscapeShellArg.d.ts.map +1 -0
  21. package/dist/lib/maybeEscapeShellArg.js +9 -0
  22. package/dist/lib/maybeEscapeShellArg.js.map +1 -0
  23. package/dist/lib/open-api/readSchema.d.ts +2 -1
  24. package/dist/lib/open-api/readSchema.d.ts.map +1 -1
  25. package/dist/lib/open-api/readSchema.js +1 -7
  26. package/dist/lib/open-api/readSchema.js.map +1 -1
  27. package/dist/lib/parseConfigToArgs.d.ts +7 -0
  28. package/dist/lib/parseConfigToArgs.d.ts.map +1 -0
  29. package/dist/lib/parseConfigToArgs.js +130 -0
  30. package/dist/lib/parseConfigToArgs.js.map +1 -0
  31. package/dist/lib/shouldQuoteCommandLineArg.d.ts +11 -0
  32. package/dist/lib/shouldQuoteCommandLineArg.d.ts.map +1 -0
  33. package/dist/lib/shouldQuoteCommandLineArg.js +33 -0
  34. package/dist/lib/shouldQuoteCommandLineArg.js.map +1 -0
  35. package/dist/packageVersion.d.ts +1 -1
  36. package/dist/packageVersion.js +1 -1
  37. package/package.json +2 -2
  38. package/src/lib/QraftCommand.ts +139 -28
  39. package/src/lib/RedoclyConfigCommand.ts +221 -0
  40. package/src/lib/getRedocAPIsToQraft.ts +87 -0
  41. package/src/lib/handleSchemaInput.ts +1 -1
  42. package/src/lib/loadRedoclyConfig.ts +47 -0
  43. package/src/lib/maybeEscapeShellArg.ts +10 -0
  44. package/src/lib/open-api/readSchema.ts +3 -11
  45. package/src/lib/parseConfigToArgs.ts +134 -0
  46. package/src/lib/shouldQuoteCommandLineArg.ts +32 -0
  47. 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 `--input` option or pipe OpenAPI Schema to stdin.'
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
+ }
@@ -1,2 +1,2 @@
1
1
  // This file was generated automatically
2
- export const packageVersion = '2.3.2';
2
+ export const packageVersion = '2.4.1';