Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions packages/arg-parser/src/arg-parser.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,24 @@ import {
CliOptionsSchema,
generateYargsOptionsFromSchema,
getLocale,
parseArgs,
parseArgsWithCliOptions,
createParseArgs,
createParseArgsWithCliOptions,
UnknownArgumentError,
UnsupportedArgumentError,
type ParserCreationOptions,
type ArgsListOptions,
} from './arg-parser';
import { z } from 'zod/v4';
import { coerceIfBoolean, coerceIfFalse } from './utils';
import { InvalidArgumentError } from './arg-metadata';

const parseArgs = <T extends z.ZodObject>(
opts: ParserCreationOptions<T> & ArgsListOptions
) => createParseArgs(opts)(opts);
const parseArgsWithCliOptions = <T extends z.ZodObject>(
opts: Partial<ParserCreationOptions<T>> & ArgsListOptions
) => createParseArgsWithCliOptions(opts)(opts);
Comment on lines +19 to +24
Copy link

Copilot AI Dec 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test helper functions call createParseArgs and createParseArgsWithCliOptions on every test invocation, recreating the parser each time. Since these tests don't benefit from snapshot optimization and the approach differs from production usage (where the parser is created once), consider adding a comment explaining this test-only pattern to clarify why it differs from the production implementation in cli-repl/src/arg-parser.ts.

Copilot uses AI. Check for mistakes.

describe('arg-parser', function () {
describe('.getLocale', function () {
context('when --locale is provided', function () {
Expand Down
139 changes: 74 additions & 65 deletions packages/arg-parser/src/arg-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,113 +33,122 @@ export const defaultParserOptions: Partial<YargsOptions> = {
},
};

export type ParserOptions = Partial<YargsOptions>;

export function parseArgs<T extends z.ZodObject>({
args,
schema,
parserOptions,
}: {
args: string[];
schema: T;
parserOptions?: YargsOptions;
}): {
export type ParsedArgs<T extends z.ZodObject> = {
/** Parsed options from the schema, including replaced deprecated arguments. */
parsed: z.infer<T> & Omit<parser.Arguments, '_'>;
/** Record of used deprecated arguments which have been replaced. */
deprecated: Record<string, keyof z.infer<T>>;
/** Positional arguments which were not parsed as options. */
positional: parser.Arguments['_'];
} {
};

export type ArgsListOptions = {
/** List of arguments to parse. */
args: string[];
};

export type ParserCreationOptions<T extends z.ZodObject> = {
schema: T;
parserOptions?: YargsOptions;
};

export function createParseArgs<T extends z.ZodObject>({
schema,
parserOptions,
}: ParserCreationOptions<T>): ({ args }: ArgsListOptions) => ParsedArgs<T> {
const options = generateYargsOptionsFromSchema({
schema,
parserOptions,
});

const { argv, error } = parser.detailed(args, {
...options,
});
const { _: positional, ...parsedArgs } = argv;
return ({ args }: { args: string[] }) => {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Recommend reviewing this with whitespace diffs turned off (https://github.com/mongodb-js/mongosh/pull/2619/changes?w=1)

const { argv, error } = parser.detailed(args, {
...options,
});
const { _: positional, ...parsedArgs } = argv;

if (error) {
if (error instanceof ZodError) {
throw new InvalidArgumentError(error.message);
if (error) {
if (error instanceof ZodError) {
throw new InvalidArgumentError(error.message);
}
throw error;
}
throw error;
}

const allDeprecatedArgs = getDeprecatedArgsWithReplacement(schema);
const usedDeprecatedArgs = {} as Record<string, keyof z.infer<typeof schema>>;
const allDeprecatedArgs = getDeprecatedArgsWithReplacement(schema);
const usedDeprecatedArgs = {} as Record<
string,
keyof z.infer<typeof schema>
>;

for (const deprecated of Object.keys(allDeprecatedArgs)) {
if (deprecated in parsedArgs) {
const replacement = allDeprecatedArgs[deprecated];
for (const deprecated of Object.keys(allDeprecatedArgs)) {
if (deprecated in parsedArgs) {
const replacement = allDeprecatedArgs[deprecated];

// This is a complicated type scenario.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(parsedArgs as any)[replacement] =
parsedArgs[deprecated as keyof typeof parsedArgs];
usedDeprecatedArgs[deprecated] = replacement;
// This is a complicated type scenario.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(parsedArgs as any)[replacement] =
parsedArgs[deprecated as keyof typeof parsedArgs];
usedDeprecatedArgs[deprecated] = replacement;

delete parsedArgs[deprecated as keyof typeof parsedArgs];
delete parsedArgs[deprecated as keyof typeof parsedArgs];
}
}
}

for (const arg of positional) {
if (typeof arg === 'string' && arg.startsWith('-')) {
throw new UnknownArgumentError(arg);
for (const arg of positional) {
if (typeof arg === 'string' && arg.startsWith('-')) {
throw new UnknownArgumentError(arg);
}
}
}

const unsupportedArgs = getUnsupportedArgs(schema);
for (const unsupported of unsupportedArgs) {
if (unsupported in parsedArgs) {
throw new UnsupportedArgumentError(unsupported);
const unsupportedArgs = getUnsupportedArgs(schema);
for (const unsupported of unsupportedArgs) {
if (unsupported in parsedArgs) {
throw new UnsupportedArgumentError(unsupported);
}
}
}

return {
parsed: parsedArgs as z.infer<T> & Omit<parser.Arguments, '_'>,
deprecated: usedDeprecatedArgs,
positional,
return {
parsed: parsedArgs as z.infer<T> & Omit<parser.Arguments, '_'>,
deprecated: usedDeprecatedArgs,
positional,
};
};
}

/** Parses the arguments with special handling of mongosh CLI options fields. */
export function parseArgsWithCliOptions<T extends z.ZodObject>({
args,
export function createParseArgsWithCliOptions<T extends z.ZodObject>({
schema: schemaToExtend,
parserOptions,
}: {
args: string[];
/** Schema to extend the CLI options schema with. */
schema?: T;
parserOptions?: Partial<YargsOptions>;
}): ReturnType<typeof parseArgs<T>> {
}: Partial<ParserCreationOptions<T>> = {}): ({
args,
}: ArgsListOptions) => ParsedArgs<T> {
const schema =
schemaToExtend !== undefined
? z.object({
...CliOptionsSchema.shape,
...schemaToExtend.shape,
})
: CliOptionsSchema;
const { parsed, positional, deprecated } = parseArgs({
args,
const parser = createParseArgs({
schema,
parserOptions,
});

const processed = processPositionalCliOptions({
parsed,
positional,
});
return ({ args }: { args: string[] }) => {
const { parsed, positional, deprecated } = parser({ args });

const processed = processPositionalCliOptions({
parsed,
positional,
});

validateCliOptions(processed);
validateCliOptions(processed);

return {
parsed: processed as z.infer<T> & Omit<parser.Arguments, '_'>,
positional,
deprecated,
return {
parsed: processed as z.infer<T> & Omit<parser.Arguments, '_'>,
positional,
deprecated,
};
};
}

Expand Down
5 changes: 4 additions & 1 deletion packages/cli-repl/src/arg-parser.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import i18n from '@mongosh/i18n';
import {
createParseArgsWithCliOptions,
getLocale,
parseArgsWithCliOptions,
UnknownArgumentError,
UnsupportedArgumentError,
} from '@mongosh/arg-parser/arg-parser';
Expand All @@ -10,6 +10,9 @@ import { USAGE } from './constants';
import type { CliOptions } from '@mongosh/arg-parser';
import { CommonErrors, MongoshUnimplementedError } from '@mongosh/errors';

// Make sure this is part of the startup snapshot
const parseArgsWithCliOptions = createParseArgsWithCliOptions();

/**
* Unknown translation key.
*/
Expand Down
Loading