Skip to content

Commit 3edbc17

Browse files
authored
chore(arg-parser): include zod to yargs conversion output in snapshot (#2619)
Right now, we generate the yargs config based on the zod schema introduced in 57b6add as part of argument parsing. Since in practice, the yargs config used by mongosh is static, we can use currying to do that part of the call early and include its result in the startup snapshot, removing runtime execution cost.
1 parent 7fce51f commit 3edbc17

File tree

3 files changed

+89
-68
lines changed

3 files changed

+89
-68
lines changed

packages/arg-parser/src/arg-parser.spec.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,24 @@ import {
55
CliOptionsSchema,
66
generateYargsOptionsFromSchema,
77
getLocale,
8-
parseArgs,
9-
parseArgsWithCliOptions,
8+
createParseArgs,
9+
createParseArgsWithCliOptions,
1010
UnknownArgumentError,
1111
UnsupportedArgumentError,
12+
type ParserCreationOptions,
13+
type ArgsListOptions,
1214
} from './arg-parser';
1315
import { z } from 'zod/v4';
1416
import { coerceIfBoolean, coerceIfFalse } from './utils';
1517
import { InvalidArgumentError } from './arg-metadata';
1618

19+
const parseArgs = <T extends z.ZodObject>(
20+
opts: ParserCreationOptions<T> & ArgsListOptions
21+
) => createParseArgs(opts)(opts);
22+
const parseArgsWithCliOptions = <T extends z.ZodObject>(
23+
opts: Partial<ParserCreationOptions<T>> & ArgsListOptions
24+
) => createParseArgsWithCliOptions(opts)(opts);
25+
1726
describe('arg-parser', function () {
1827
describe('.getLocale', function () {
1928
context('when --locale is provided', function () {

packages/arg-parser/src/arg-parser.ts

Lines changed: 74 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -33,113 +33,122 @@ export const defaultParserOptions: Partial<YargsOptions> = {
3333
},
3434
};
3535

36-
export type ParserOptions = Partial<YargsOptions>;
37-
38-
export function parseArgs<T extends z.ZodObject>({
39-
args,
40-
schema,
41-
parserOptions,
42-
}: {
43-
args: string[];
44-
schema: T;
45-
parserOptions?: YargsOptions;
46-
}): {
36+
export type ParsedArgs<T extends z.ZodObject> = {
4737
/** Parsed options from the schema, including replaced deprecated arguments. */
4838
parsed: z.infer<T> & Omit<parser.Arguments, '_'>;
4939
/** Record of used deprecated arguments which have been replaced. */
5040
deprecated: Record<string, keyof z.infer<T>>;
5141
/** Positional arguments which were not parsed as options. */
5242
positional: parser.Arguments['_'];
53-
} {
43+
};
44+
45+
export type ArgsListOptions = {
46+
/** List of arguments to parse. */
47+
args: string[];
48+
};
49+
50+
export type ParserCreationOptions<T extends z.ZodObject> = {
51+
schema: T;
52+
parserOptions?: YargsOptions;
53+
};
54+
55+
export function createParseArgs<T extends z.ZodObject>({
56+
schema,
57+
parserOptions,
58+
}: ParserCreationOptions<T>): ({ args }: ArgsListOptions) => ParsedArgs<T> {
5459
const options = generateYargsOptionsFromSchema({
5560
schema,
5661
parserOptions,
5762
});
5863

59-
const { argv, error } = parser.detailed(args, {
60-
...options,
61-
});
62-
const { _: positional, ...parsedArgs } = argv;
64+
return ({ args }: { args: string[] }) => {
65+
const { argv, error } = parser.detailed(args, {
66+
...options,
67+
});
68+
const { _: positional, ...parsedArgs } = argv;
6369

64-
if (error) {
65-
if (error instanceof ZodError) {
66-
throw new InvalidArgumentError(error.message);
70+
if (error) {
71+
if (error instanceof ZodError) {
72+
throw new InvalidArgumentError(error.message);
73+
}
74+
throw error;
6775
}
68-
throw error;
69-
}
7076

71-
const allDeprecatedArgs = getDeprecatedArgsWithReplacement(schema);
72-
const usedDeprecatedArgs = {} as Record<string, keyof z.infer<typeof schema>>;
77+
const allDeprecatedArgs = getDeprecatedArgsWithReplacement(schema);
78+
const usedDeprecatedArgs = {} as Record<
79+
string,
80+
keyof z.infer<typeof schema>
81+
>;
7382

74-
for (const deprecated of Object.keys(allDeprecatedArgs)) {
75-
if (deprecated in parsedArgs) {
76-
const replacement = allDeprecatedArgs[deprecated];
83+
for (const deprecated of Object.keys(allDeprecatedArgs)) {
84+
if (deprecated in parsedArgs) {
85+
const replacement = allDeprecatedArgs[deprecated];
7786

78-
// This is a complicated type scenario.
79-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
80-
(parsedArgs as any)[replacement] =
81-
parsedArgs[deprecated as keyof typeof parsedArgs];
82-
usedDeprecatedArgs[deprecated] = replacement;
87+
// This is a complicated type scenario.
88+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
89+
(parsedArgs as any)[replacement] =
90+
parsedArgs[deprecated as keyof typeof parsedArgs];
91+
usedDeprecatedArgs[deprecated] = replacement;
8392

84-
delete parsedArgs[deprecated as keyof typeof parsedArgs];
93+
delete parsedArgs[deprecated as keyof typeof parsedArgs];
94+
}
8595
}
86-
}
8796

88-
for (const arg of positional) {
89-
if (typeof arg === 'string' && arg.startsWith('-')) {
90-
throw new UnknownArgumentError(arg);
97+
for (const arg of positional) {
98+
if (typeof arg === 'string' && arg.startsWith('-')) {
99+
throw new UnknownArgumentError(arg);
100+
}
91101
}
92-
}
93102

94-
const unsupportedArgs = getUnsupportedArgs(schema);
95-
for (const unsupported of unsupportedArgs) {
96-
if (unsupported in parsedArgs) {
97-
throw new UnsupportedArgumentError(unsupported);
103+
const unsupportedArgs = getUnsupportedArgs(schema);
104+
for (const unsupported of unsupportedArgs) {
105+
if (unsupported in parsedArgs) {
106+
throw new UnsupportedArgumentError(unsupported);
107+
}
98108
}
99-
}
100109

101-
return {
102-
parsed: parsedArgs as z.infer<T> & Omit<parser.Arguments, '_'>,
103-
deprecated: usedDeprecatedArgs,
104-
positional,
110+
return {
111+
parsed: parsedArgs as z.infer<T> & Omit<parser.Arguments, '_'>,
112+
deprecated: usedDeprecatedArgs,
113+
positional,
114+
};
105115
};
106116
}
107117

108118
/** Parses the arguments with special handling of mongosh CLI options fields. */
109-
export function parseArgsWithCliOptions<T extends z.ZodObject>({
110-
args,
119+
export function createParseArgsWithCliOptions<T extends z.ZodObject>({
111120
schema: schemaToExtend,
112121
parserOptions,
113-
}: {
114-
args: string[];
115-
/** Schema to extend the CLI options schema with. */
116-
schema?: T;
117-
parserOptions?: Partial<YargsOptions>;
118-
}): ReturnType<typeof parseArgs<T>> {
122+
}: Partial<ParserCreationOptions<T>> = {}): ({
123+
args,
124+
}: ArgsListOptions) => ParsedArgs<T> {
119125
const schema =
120126
schemaToExtend !== undefined
121127
? z.object({
122128
...CliOptionsSchema.shape,
123129
...schemaToExtend.shape,
124130
})
125131
: CliOptionsSchema;
126-
const { parsed, positional, deprecated } = parseArgs({
127-
args,
132+
const parser = createParseArgs({
128133
schema,
129134
parserOptions,
130135
});
131136

132-
const processed = processPositionalCliOptions({
133-
parsed,
134-
positional,
135-
});
137+
return ({ args }: { args: string[] }) => {
138+
const { parsed, positional, deprecated } = parser({ args });
139+
140+
const processed = processPositionalCliOptions({
141+
parsed,
142+
positional,
143+
});
136144

137-
validateCliOptions(processed);
145+
validateCliOptions(processed);
138146

139-
return {
140-
parsed: processed as z.infer<T> & Omit<parser.Arguments, '_'>,
141-
positional,
142-
deprecated,
147+
return {
148+
parsed: processed as z.infer<T> & Omit<parser.Arguments, '_'>,
149+
positional,
150+
deprecated,
151+
};
143152
};
144153
}
145154

packages/cli-repl/src/arg-parser.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import i18n from '@mongosh/i18n';
22
import {
3+
createParseArgsWithCliOptions,
34
getLocale,
4-
parseArgsWithCliOptions,
55
UnknownArgumentError,
66
UnsupportedArgumentError,
77
} from '@mongosh/arg-parser/arg-parser';
@@ -10,6 +10,9 @@ import { USAGE } from './constants';
1010
import type { CliOptions } from '@mongosh/arg-parser';
1111
import { CommonErrors, MongoshUnimplementedError } from '@mongosh/errors';
1212

13+
// Make sure this is part of the startup snapshot
14+
const parseArgsWithCliOptions = createParseArgsWithCliOptions();
15+
1316
/**
1417
* Unknown translation key.
1518
*/

0 commit comments

Comments
 (0)