diff --git a/README.md b/README.md index e780353beb..c227ca32af 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ variable. For detailed instructions for each of our supported platforms, please ## CLI Usage ```shell - $ mongosh [options] [db address] + $ mongosh [options] [db address] [file names (ending in .js or .mongodb)] Options: diff --git a/packages/cli-repl/README.md b/packages/cli-repl/README.md index 5ad27999c8..a56062d6d0 100644 --- a/packages/cli-repl/README.md +++ b/packages/cli-repl/README.md @@ -6,7 +6,7 @@ CLI interface for [MongoDB Shell][mongosh], an extension to Node.js REPL with Mo ## Usage ```shell - $ mongosh [options] [db address] + $ mongosh [options] [db address] [file names (ending in .js or .mongodb)] Options: diff --git a/packages/cli-repl/package.json b/packages/cli-repl/package.json index bfdf41735d..e2ff765a62 100644 --- a/packages/cli-repl/package.json +++ b/packages/cli-repl/package.json @@ -19,8 +19,7 @@ }, "scripts": { "compile-ts": "tsc -p tsconfig.json", - "start": "node bin/mongosh.js start", - "start-async": "node bin/mongosh.js start --async", + "start": "node bin/mongosh.js", "pretest": "npm run compile-ts", "test": "cross-env TS_NODE_PROJECT=../../config/tsconfig.test.json mocha -r \"../../scripts/import-expansions.js\" --timeout 60000 --colors -r ts-node/register \"./{src,test}/**/*.spec.ts\"", "test-ci": "cross-env TS_NODE_PROJECT=../../config/tsconfig.test.json mocha -r \"../../scripts/import-expansions.js\" --timeout 60000 -r ts-node/register \"./{src,test}/**/*.spec.ts\"", diff --git a/packages/cli-repl/src/arg-parser.spec.ts b/packages/cli-repl/src/arg-parser.spec.ts index 405b99eb97..060256b20f 100644 --- a/packages/cli-repl/src/arg-parser.spec.ts +++ b/packages/cli-repl/src/arg-parser.spec.ts @@ -3,10 +3,6 @@ import { expect } from 'chai'; import stripAnsi from 'strip-ansi'; import { getLocale, parseCliArgs } from './arg-parser'; -const NODE = 'node'; -const MONGOSH = 'mongosh'; -const START = 'start'; - describe('arg-parser', () => { describe('.getLocale', () => { context('when --locale is provided', () => { @@ -75,613 +71,709 @@ describe('arg-parser', () => { }); describe('.parse', () => { - [ - { contextDescription: 'when running from a linked bin script or executable', baseArgv: [NODE, MONGOSH] }, - { contextDescription: 'when running via npm start', baseArgv: [ NODE, MONGOSH, START ] }, - ].forEach(({ contextDescription, baseArgv }) => { - context(contextDescription, () => { - context('when providing only a URI', () => { - const uri = 'mongodb://domain.com:20000'; - const argv = [ ...baseArgv, uri]; + const baseArgv = ['node', 'mongosh']; + context('when providing only a URI', () => { + const uri = 'mongodb://domain.com:20000'; + const argv = [ ...baseArgv, uri]; + + it('returns the URI in the object', () => { + expect(parseCliArgs(argv).connectionSpecifier).to.equal(uri); + }); + }); + + context('when providing a URI + options', () => { + const uri = 'mongodb://domain.com:20000'; + + context('when providing general options', () => { + context('when providing --ipv6', () => { + const argv = [ ...baseArgv, uri, '--ipv6' ]; + + it('returns the URI in the object', () => { + expect(parseCliArgs(argv).connectionSpecifier).to.equal(uri); + }); + + it('sets the ipv6 value in the object', () => { + expect(parseCliArgs(argv).ipv6).to.equal(true); + }); + }); + + context('when providing -h', () => { + const argv = [ ...baseArgv, uri, '-h' ]; + + it('returns the URI in the object', () => { + expect(parseCliArgs(argv).connectionSpecifier).to.equal(uri); + }); + + it('sets the help value in the object', () => { + expect(parseCliArgs(argv).help).to.equal(true); + }); + }); + + context('when providing --help', () => { + const argv = [ ...baseArgv, uri, '--help' ]; + + it('returns the URI in the object', () => { + expect(parseCliArgs(argv).connectionSpecifier).to.equal(uri); + }); + + it('sets the help value in the object', () => { + expect(parseCliArgs(argv).help).to.equal(true); + }); + }); + + context('when providing --version', () => { + const argv = [ ...baseArgv, uri, '--version' ]; + + it('returns the URI in the object', () => { + expect(parseCliArgs(argv).connectionSpecifier).to.equal(uri); + }); + + it('sets the version value in the object', () => { + expect(parseCliArgs(argv).version).to.equal(true); + }); + }); + + context('when providing --verbose', () => { + const argv = [ ...baseArgv, uri, '--verbose' ]; + + it('returns the URI in the object', () => { + expect(parseCliArgs(argv).connectionSpecifier).to.equal(uri); + }); + + it('sets the verbose value in the object', () => { + expect(parseCliArgs(argv).verbose).to.equal(true); + }); + }); + + context('when providing --shell', () => { + const argv = [ ...baseArgv, uri, '--shell' ]; + + it('returns the URI in the object', () => { + expect(parseCliArgs(argv).connectionSpecifier).to.equal(uri); + }); + + it('sets the shell value in the object', () => { + expect(parseCliArgs(argv).shell).to.equal(true); + }); + }); + + context('when providing --nodb', () => { + const argv = [ ...baseArgv, uri, '--nodb' ]; + + it('does not return the URI in the object', () => { + expect(parseCliArgs(argv).connectionSpecifier).to.equal(undefined); + expect(parseCliArgs(argv).fileNames).to.deep.equal([uri]); + }); + + it('sets the nodb value in the object', () => { + expect(parseCliArgs(argv).nodb).to.equal(true); + }); + }); + + context('when providing --norc', () => { + const argv = [ ...baseArgv, uri, '--norc' ]; + + it('returns the URI in the object', () => { + expect(parseCliArgs(argv).connectionSpecifier).to.equal(uri); + }); + + it('sets the norc value in the object', () => { + expect(parseCliArgs(argv).norc).to.equal(true); + }); + }); + + context('when providing --quiet', () => { + const argv = [ ...baseArgv, uri, '--quiet' ]; + + it('returns the URI in the object', () => { + expect(parseCliArgs(argv).connectionSpecifier).to.equal(uri); + }); + + it('sets the quiet value in the object', () => { + expect(parseCliArgs(argv).quiet).to.equal(true); + }); + }); + + context('when providing --eval', () => { + const argv = [ ...baseArgv, uri, '--eval', '1+1' ]; + + it('returns the URI in the object', () => { + expect(parseCliArgs(argv).connectionSpecifier).to.equal(uri); + }); + + it('sets the eval value in the object', () => { + expect(parseCliArgs(argv).eval).to.equal('1+1'); + }); + }); + + context('when providing --retryWrites', () => { + const argv = [ ...baseArgv, uri, '--retryWrites' ]; + + it('returns the URI in the object', () => { + expect(parseCliArgs(argv).connectionSpecifier).to.equal(uri); + }); + + it('sets the retryWrites value in the object', () => { + expect(parseCliArgs(argv).retryWrites).to.equal(true); + }); + }); + + context('when providing an unknown parameter', () => { + const argv = [ ...baseArgv, uri, '--what' ]; + + it('raises an error', () => { + try { + parseCliArgs(argv); + } catch (err) { + return expect( + stripAnsi(err.message) + ).to.contain('Error parsing command line: unrecognized option: --what'); + } + expect.fail('parsing unknown parameter did not throw'); + }); + }); + }); + + context('when providing authentication options', () => { + context('when providing -u', () => { + const argv = [ ...baseArgv, uri, '-u', 'richard' ]; + + it('returns the URI in the object', () => { + expect(parseCliArgs(argv).connectionSpecifier).to.equal(uri); + }); + + it('sets the username in the object', () => { + expect(parseCliArgs(argv).username).to.equal('richard'); + }); + }); + + context('when providing --username', () => { + const argv = [ ...baseArgv, uri, '--username', 'richard' ]; + + it('returns the URI in the object', () => { + expect(parseCliArgs(argv).connectionSpecifier).to.equal(uri); + }); + + it('sets the username in the object', () => { + expect(parseCliArgs(argv).username).to.equal('richard'); + }); + }); + + context('when providing -p', () => { + const argv = [ ...baseArgv, uri, '-p', 'pw' ]; + + it('returns the URI in the object', () => { + expect(parseCliArgs(argv).connectionSpecifier).to.equal(uri); + }); + + it('sets the password in the object', () => { + expect(parseCliArgs(argv).password).to.equal('pw'); + }); + }); + + context('when providing --password', () => { + const argv = [ ...baseArgv, uri, '--password', 'pw' ]; + + it('returns the URI in the object', () => { + expect(parseCliArgs(argv).connectionSpecifier).to.equal(uri); + }); + + it('sets the password in the object', () => { + expect(parseCliArgs(argv).password).to.equal('pw'); + }); + }); + + context('when providing --authenticationDatabase', () => { + const argv = [ ...baseArgv, uri, '--authenticationDatabase', 'db' ]; + + it('returns the URI in the object', () => { + expect(parseCliArgs(argv).connectionSpecifier).to.equal(uri); + }); + + it('sets the authenticationDatabase in the object', () => { + expect(parseCliArgs(argv).authenticationDatabase).to.equal('db'); + }); + }); + + context('when providing --authenticationMechanism', () => { + const argv = [ ...baseArgv, uri, '--authenticationMechanism', 'SCRAM-SHA-256' ]; + + it('returns the URI in the object', () => { + expect(parseCliArgs(argv).connectionSpecifier).to.equal(uri); + }); + + it('sets the authenticationMechanism in the object', () => { + expect(parseCliArgs(argv).authenticationMechanism).to.equal('SCRAM-SHA-256'); + }); + }); + + context('when providing --gssapiServiceName', () => { + const argv = [ ...baseArgv, uri, '--gssapiServiceName', 'mongosh' ]; + + it('returns the URI in the object', () => { + expect(parseCliArgs(argv).connectionSpecifier).to.equal(uri); + }); + + it('sets the gssapiServiceName in the object', () => { + expect(parseCliArgs(argv).gssapiServiceName).to.equal('mongosh'); + }); + }); + + context('when providing --gssapiHostName', () => { + const argv = [ ...baseArgv, uri, '--gssapiHostName', 'example.com' ]; + + it('returns the URI in the object', () => { + expect(parseCliArgs(argv).connectionSpecifier).to.equal(uri); + }); + + it('sets the gssapiHostName in the object', () => { + expect(parseCliArgs(argv).gssapiHostName).to.equal('example.com'); + }); + }); + + context('when providing --awsIamSessionToken', () => { + const argv = [ ...baseArgv, uri, '--awsIamSessionToken', 'tok' ]; + + it('returns the URI in the object', () => { + expect(parseCliArgs(argv).connectionSpecifier).to.equal(uri); + }); + + it('sets the awsIamSessionToken in the object', () => { + expect(parseCliArgs(argv).awsIamSessionToken).to.equal('tok'); + }); + }); + }); + + context('when providing TLS options', () => { + context('when providing --tls', () => { + const argv = [ ...baseArgv, uri, '--tls' ]; + + it('returns the URI in the object', () => { + expect(parseCliArgs(argv).connectionSpecifier).to.equal(uri); + }); + + it('sets the tls in the object', () => { + expect(parseCliArgs(argv).tls).to.equal(true); + }); + }); + + context('when providing --tlsCertificateKeyFile', () => { + const argv = [ ...baseArgv, uri, '--tlsCertificateKeyFile', 'test' ]; + + it('returns the URI in the object', () => { + expect(parseCliArgs(argv).connectionSpecifier).to.equal(uri); + }); + + it('sets the tlsCertificateKeyFile in the object', () => { + expect(parseCliArgs(argv).tlsCertificateKeyFile).to.equal('test'); + }); + }); + + context('when providing --tlsCertificateKeyFilePassword', () => { + const argv = [ ...baseArgv, uri, '--tlsCertificateKeyFilePassword', 'test' ]; + + it('returns the URI in the object', () => { + expect(parseCliArgs(argv).connectionSpecifier).to.equal(uri); + }); + + it('sets the tlsCertificateKeyFilePassword in the object', () => { + expect(parseCliArgs(argv).tlsCertificateKeyFilePassword).to.equal('test'); + }); + }); + + context('when providing --tlsCAFile', () => { + const argv = [ ...baseArgv, uri, '--tlsCAFile', 'test' ]; + + it('returns the URI in the object', () => { + expect(parseCliArgs(argv).connectionSpecifier).to.equal(uri); + }); + + it('sets the tlsCAFile in the object', () => { + expect(parseCliArgs(argv).tlsCAFile).to.equal('test'); + }); + }); + + context('when providing --tlsCRLFile', () => { + const argv = [ ...baseArgv, uri, '--tlsCRLFile', 'test' ]; + + it('returns the URI in the object', () => { + expect(parseCliArgs(argv).connectionSpecifier).to.equal(uri); + }); + + it('sets the tlsCRLFile in the object', () => { + expect(parseCliArgs(argv).tlsCRLFile).to.equal('test'); + }); + }); + + context('when providing --tlsAllowInvalidHostnames', () => { + const argv = [ ...baseArgv, uri, '--tlsAllowInvalidHostnames' ]; + + it('returns the URI in the object', () => { + expect(parseCliArgs(argv).connectionSpecifier).to.equal(uri); + }); + + it('sets the tlsAllowInvalidHostnames in the object', () => { + expect(parseCliArgs(argv).tlsAllowInvalidHostnames).to.equal(true); + }); + }); + + context('when providing --tlsAllowInvalidCertificates', () => { + const argv = [ ...baseArgv, uri, '--tlsAllowInvalidCertificates' ]; it('returns the URI in the object', () => { - expect(parseCliArgs(argv)._[0]).to.equal(uri); + expect(parseCliArgs(argv).connectionSpecifier).to.equal(uri); + }); + + it('sets the tlsAllowInvalidCertificates in the object', () => { + expect(parseCliArgs(argv).tlsAllowInvalidCertificates).to.equal(true); }); }); - context('when providing a URI + options', () => { - const uri = 'mongodb://domain.com:20000'; + context('when providing --tlsFIPSMode', () => { + const argv = [ ...baseArgv, uri, '--tlsFIPSMode' ]; + + it('throws an error since it is not yet supported', () => { + try { + parseCliArgs(argv); + } catch (e) { + expect(e).to.be.instanceOf(MongoshUnimplementedError); + expect(e.message).to.include('Argument --tlsFIPSMode is not yet supported in mongosh'); + return; + } + expect.fail('Expected error'); + }); + + // it('returns the URI in the object', () => { + // expect(parseCliArgs(argv).connectionSpecifier).to.equal(uri); + // }); + + // it('sets the tlsFIPSMode in the object', () => { + // expect(parseCliArgs(argv).tlsFIPSMode).to.equal(true); + // }); + }); + + context('when providing --tlsCertificateSelector', () => { + const argv = [ ...baseArgv, uri, '--tlsCertificateSelector', 'test' ]; - context('when providing general options', () => { - context('when providing --ipv6', () => { - const argv = [ ...baseArgv, uri, '--ipv6' ]; + it('returns the URI in the object', () => { + expect(parseCliArgs(argv).connectionSpecifier).to.equal(uri); + }); - it('returns the URI in the object', () => { - expect(parseCliArgs(argv)._[0]).to.equal(uri); - }); + it('sets the tlsCertificateSelector in the object', () => { + expect(parseCliArgs(argv).tlsCertificateSelector).to.equal('test'); + }); + }); - it('sets the ipv6 value in the object', () => { - expect(parseCliArgs(argv).ipv6).to.equal(true); - }); - }); + context('when providing --tlsDisabledProtocols', () => { + const argv = [ ...baseArgv, uri, '--tlsDisabledProtocols', 'TLS1_0,TLS2_0' ]; - context('when providing -h', () => { - const argv = [ ...baseArgv, uri, '-h' ]; + it('returns the URI in the object', () => { + expect(parseCliArgs(argv).connectionSpecifier).to.equal(uri); + }); - it('returns the URI in the object', () => { - expect(parseCliArgs(argv)._[0]).to.equal(uri); - }); + it('sets the tlsDisabledProtocols in the object', () => { + expect(parseCliArgs(argv).tlsDisabledProtocols).to.equal('TLS1_0,TLS2_0'); + }); + }); + }); - it('sets the help value in the object', () => { - expect(parseCliArgs(argv).help).to.equal(true); - }); - }); + context('when providing FLE options', () => { + context('when providing --awsAccessKeyId', () => { + const argv = [ ...baseArgv, uri, '--awsAccessKeyId', 'foo' ]; - context('when providing --help', () => { - const argv = [ ...baseArgv, uri, '--help' ]; + it('returns the URI in the object', () => { + expect(parseCliArgs(argv).connectionSpecifier).to.equal(uri); + }); - it('returns the URI in the object', () => { - expect(parseCliArgs(argv)._[0]).to.equal(uri); - }); + it('sets the awsAccessKeyId in the object', () => { + expect(parseCliArgs(argv).awsAccessKeyId).to.equal('foo'); + }); + }); - it('sets the help value in the object', () => { - expect(parseCliArgs(argv).help).to.equal(true); - }); - }); + context('when providing --awsSecretAccessKey', () => { + const argv = [ ...baseArgv, uri, '--awsSecretAccessKey', 'foo' ]; - context('when providing --version', () => { - const argv = [ ...baseArgv, uri, '--version' ]; + it('returns the URI in the object', () => { + expect(parseCliArgs(argv).connectionSpecifier).to.equal(uri); + }); - it('returns the URI in the object', () => { - expect(parseCliArgs(argv)._[0]).to.equal(uri); - }); + it('sets the awsSecretAccessKey in the object', () => { + expect(parseCliArgs(argv).awsSecretAccessKey).to.equal('foo'); + }); + }); - it('sets the version value in the object', () => { - expect(parseCliArgs(argv).version).to.equal(true); - }); - }); + context('when providing --awsSessionToken', () => { + const argv = [ ...baseArgv, uri, '--awsSessionToken', 'foo' ]; - context('when providing --verbose', () => { - const argv = [ ...baseArgv, uri, '--verbose' ]; + it('returns the URI in the object', () => { + expect(parseCliArgs(argv).connectionSpecifier).to.equal(uri); + }); - it('returns the URI in the object', () => { - expect(parseCliArgs(argv)._[0]).to.equal(uri); - }); - - it('sets the verbose value in the object', () => { - expect(parseCliArgs(argv).verbose).to.equal(true); - }); - }); - - context('when providing --shell', () => { - const argv = [ ...baseArgv, uri, '--shell' ]; - - it('returns the URI in the object', () => { - expect(parseCliArgs(argv)._[0]).to.equal(uri); - }); - - it('sets the shell value in the object', () => { - expect(parseCliArgs(argv).shell).to.equal(true); - }); - }); - - context('when providing --nodb', () => { - const argv = [ ...baseArgv, uri, '--nodb' ]; - - it('returns the URI in the object', () => { - expect(parseCliArgs(argv)._[0]).to.equal(uri); - }); - - it('sets the nodb value in the object', () => { - expect(parseCliArgs(argv).nodb).to.equal(true); - }); - }); - - context('when providing --norc', () => { - const argv = [ ...baseArgv, uri, '--norc' ]; - - it('returns the URI in the object', () => { - expect(parseCliArgs(argv)._[0]).to.equal(uri); - }); + it('sets the awsSessionToken in the object', () => { + expect(parseCliArgs(argv).awsSessionToken).to.equal('foo'); + }); + }); - it('sets the norc value in the object', () => { - expect(parseCliArgs(argv).norc).to.equal(true); - }); - }); - - context('when providing --quiet', () => { - const argv = [ ...baseArgv, uri, '--quiet' ]; - - it('returns the URI in the object', () => { - expect(parseCliArgs(argv)._[0]).to.equal(uri); - }); - - it('sets the quiet value in the object', () => { - expect(parseCliArgs(argv).quiet).to.equal(true); - }); - }); - - context('when providing --eval', () => { - const argv = [ ...baseArgv, uri, '--eval', '1+1' ]; - - it('returns the URI in the object', () => { - expect(parseCliArgs(argv)._[0]).to.equal(uri); - }); - - it('sets the eval value in the object', () => { - expect(parseCliArgs(argv).eval).to.equal('1+1'); - }); - }); - - context('when providing --retryWrites', () => { - const argv = [ ...baseArgv, uri, '--retryWrites' ]; - - it('returns the URI in the object', () => { - expect(parseCliArgs(argv)._[0]).to.equal(uri); - }); - - it('sets the retryWrites value in the object', () => { - expect(parseCliArgs(argv).retryWrites).to.equal(true); - }); - }); - - context('when providing an unknown parameter', () => { - const argv = [ ...baseArgv, uri, '--what' ]; - - it('raises an error', () => { - try { - parseCliArgs(argv); - } catch (err) { - return expect( - stripAnsi(err.message) - ).to.contain('Error parsing command line: unrecognized option: --what'); - } - expect.fail('parsing unknown parameter did not throw'); - }); - }); - - context('when providing a non-string parameter', () => { - const argv = [ ...baseArgv, uri, '1234' ]; - - it('raises an error', () => { - try { - parseCliArgs(argv); - } catch (err) { - return expect( - stripAnsi(err.message) - ).to.contain('Error parsing command line: unrecognized option: 1234'); - } - expect.fail('parsing unknown parameter did not throw'); - }); - }); - }); - - context('when providing authentication options', () => { - context('when providing -u', () => { - const argv = [ ...baseArgv, uri, '-u', 'richard' ]; - - it('returns the URI in the object', () => { - expect(parseCliArgs(argv)._[0]).to.equal(uri); - }); - - it('sets the username in the object', () => { - expect(parseCliArgs(argv).username).to.equal('richard'); - }); - }); - - context('when providing --username', () => { - const argv = [ ...baseArgv, uri, '--username', 'richard' ]; - - it('returns the URI in the object', () => { - expect(parseCliArgs(argv)._[0]).to.equal(uri); - }); - - it('sets the username in the object', () => { - expect(parseCliArgs(argv).username).to.equal('richard'); - }); - }); - - context('when providing -p', () => { - const argv = [ ...baseArgv, uri, '-p', 'pw' ]; - - it('returns the URI in the object', () => { - expect(parseCliArgs(argv)._[0]).to.equal(uri); - }); - - it('sets the password in the object', () => { - expect(parseCliArgs(argv).password).to.equal('pw'); - }); - }); - - context('when providing --password', () => { - const argv = [ ...baseArgv, uri, '--password', 'pw' ]; - - it('returns the URI in the object', () => { - expect(parseCliArgs(argv)._[0]).to.equal(uri); - }); - - it('sets the password in the object', () => { - expect(parseCliArgs(argv).password).to.equal('pw'); - }); - }); - - context('when providing --authenticationDatabase', () => { - const argv = [ ...baseArgv, uri, '--authenticationDatabase', 'db' ]; - - it('returns the URI in the object', () => { - expect(parseCliArgs(argv)._[0]).to.equal(uri); - }); - - it('sets the authenticationDatabase in the object', () => { - expect(parseCliArgs(argv).authenticationDatabase).to.equal('db'); - }); - }); - - context('when providing --authenticationMechanism', () => { - const argv = [ ...baseArgv, uri, '--authenticationMechanism', 'SCRAM-SHA-256' ]; - - it('returns the URI in the object', () => { - expect(parseCliArgs(argv)._[0]).to.equal(uri); - }); - - it('sets the authenticationMechanism in the object', () => { - expect(parseCliArgs(argv).authenticationMechanism).to.equal('SCRAM-SHA-256'); - }); - }); - - context('when providing --gssapiServiceName', () => { - const argv = [ ...baseArgv, uri, '--gssapiServiceName', 'mongosh' ]; - - it('returns the URI in the object', () => { - expect(parseCliArgs(argv)._[0]).to.equal(uri); - }); - - it('sets the gssapiServiceName in the object', () => { - expect(parseCliArgs(argv).gssapiServiceName).to.equal('mongosh'); - }); - }); - - context('when providing --gssapiHostName', () => { - const argv = [ ...baseArgv, uri, '--gssapiHostName', 'example.com' ]; - - it('returns the URI in the object', () => { - expect(parseCliArgs(argv)._[0]).to.equal(uri); - }); + context('when providing --keyVaultNamespace', () => { + const argv = [ ...baseArgv, uri, '--keyVaultNamespace', 'foo.bar' ]; - it('sets the gssapiHostName in the object', () => { - expect(parseCliArgs(argv).gssapiHostName).to.equal('example.com'); - }); - }); + it('returns the URI in the object', () => { + expect(parseCliArgs(argv).connectionSpecifier).to.equal(uri); + }); - context('when providing --awsIamSessionToken', () => { - const argv = [ ...baseArgv, uri, '--awsIamSessionToken', 'tok' ]; + it('sets the keyVaultNamespace in the object', () => { + expect(parseCliArgs(argv).keyVaultNamespace).to.equal('foo.bar'); + }); + }); - it('returns the URI in the object', () => { - expect(parseCliArgs(argv)._[0]).to.equal(uri); - }); + context('when providing --kmsURL', () => { + const argv = [ ...baseArgv, uri, '--kmsURL', 'example.com' ]; - it('sets the awsIamSessionToken in the object', () => { - expect(parseCliArgs(argv).awsIamSessionToken).to.equal('tok'); - }); - }); - }); - - context('when providing TLS options', () => { - context('when providing --tls', () => { - const argv = [ ...baseArgv, uri, '--tls' ]; - - it('returns the URI in the object', () => { - expect(parseCliArgs(argv)._[0]).to.equal(uri); - }); - - it('sets the tls in the object', () => { - expect(parseCliArgs(argv).tls).to.equal(true); - }); - }); - - context('when providing --tlsCertificateKeyFile', () => { - const argv = [ ...baseArgv, uri, '--tlsCertificateKeyFile', 'test' ]; - - it('returns the URI in the object', () => { - expect(parseCliArgs(argv)._[0]).to.equal(uri); - }); - - it('sets the tlsCertificateKeyFile in the object', () => { - expect(parseCliArgs(argv).tlsCertificateKeyFile).to.equal('test'); - }); - }); - - context('when providing --tlsCertificateKeyFilePassword', () => { - const argv = [ ...baseArgv, uri, '--tlsCertificateKeyFilePassword', 'test' ]; - - it('returns the URI in the object', () => { - expect(parseCliArgs(argv)._[0]).to.equal(uri); - }); - - it('sets the tlsCertificateKeyFilePassword in the object', () => { - expect(parseCliArgs(argv).tlsCertificateKeyFilePassword).to.equal('test'); - }); - }); + it('returns the URI in the object', () => { + expect(parseCliArgs(argv).connectionSpecifier).to.equal(uri); + }); - context('when providing --tlsCAFile', () => { - const argv = [ ...baseArgv, uri, '--tlsCAFile', 'test' ]; - - it('returns the URI in the object', () => { - expect(parseCliArgs(argv)._[0]).to.equal(uri); - }); - - it('sets the tlsCAFile in the object', () => { - expect(parseCliArgs(argv).tlsCAFile).to.equal('test'); - }); - }); + it('sets the kmsURL in the object', () => { + expect(parseCliArgs(argv).kmsURL).to.equal('example.com'); + }); + }); + }); - context('when providing --tlsCRLFile', () => { - const argv = [ ...baseArgv, uri, '--tlsCRLFile', 'test' ]; - - it('returns the URI in the object', () => { - expect(parseCliArgs(argv)._[0]).to.equal(uri); - }); - - it('sets the tlsCRLFile in the object', () => { - expect(parseCliArgs(argv).tlsCRLFile).to.equal('test'); - }); - }); + context('when providing versioned API options', () => { + context('when providing --apiVersion', () => { + const argv = [ ...baseArgv, uri, '--apiVersion', '1' ]; - context('when providing --tlsAllowInvalidHostnames', () => { - const argv = [ ...baseArgv, uri, '--tlsAllowInvalidHostnames' ]; - - it('returns the URI in the object', () => { - expect(parseCliArgs(argv)._[0]).to.equal(uri); - }); - - it('sets the tlsAllowInvalidHostnames in the object', () => { - expect(parseCliArgs(argv).tlsAllowInvalidHostnames).to.equal(true); - }); - }); + it('returns the URI in the object', () => { + expect(parseCliArgs(argv).connectionSpecifier).to.equal(uri); + }); - context('when providing --tlsAllowInvalidCertificates', () => { - const argv = [ ...baseArgv, uri, '--tlsAllowInvalidCertificates' ]; - - it('returns the URI in the object', () => { - expect(parseCliArgs(argv)._[0]).to.equal(uri); - }); - - it('sets the tlsAllowInvalidCertificates in the object', () => { - expect(parseCliArgs(argv).tlsAllowInvalidCertificates).to.equal(true); - }); - }); + it('sets the apiVersion in the object', () => { + expect(parseCliArgs(argv).apiVersion).to.equal('1'); + }); + }); - context('when providing --tlsFIPSMode', () => { - const argv = [ ...baseArgv, uri, '--tlsFIPSMode' ]; - - it('throws an error since it is not yet supported', () => { - try { - parseCliArgs(argv); - } catch (e) { - expect(e).to.be.instanceOf(MongoshUnimplementedError); - expect(e.message).to.include('Argument --tlsFIPSMode is not yet supported in mongosh'); - return; - } - expect.fail('Expected error'); - }); + context('when providing --apiDeprecationErrors', () => { + const argv = [ ...baseArgv, uri, '--apiDeprecationErrors' ]; - // it('returns the URI in the object', () => { - // expect(parseCliArgs(argv)._[0]).to.equal(uri); - // }); + it('returns the URI in the object', () => { + expect(parseCliArgs(argv).connectionSpecifier).to.equal(uri); + }); - // it('sets the tlsFIPSMode in the object', () => { - // expect(parseCliArgs(argv).tlsFIPSMode).to.equal(true); - // }); - }); + it('sets the apiVersion in the object', () => { + expect(parseCliArgs(argv).apiDeprecationErrors).to.equal(true); + }); + }); - context('when providing --tlsCertificateSelector', () => { - const argv = [ ...baseArgv, uri, '--tlsCertificateSelector', 'test' ]; + context('when providing --apiStrict', () => { + const argv = [ ...baseArgv, uri, '--apiStrict' ]; - it('returns the URI in the object', () => { - expect(parseCliArgs(argv)._[0]).to.equal(uri); - }); + it('returns the URI in the object', () => { + expect(parseCliArgs(argv).connectionSpecifier).to.equal(uri); + }); - it('sets the tlsCertificateSelector in the object', () => { - expect(parseCliArgs(argv).tlsCertificateSelector).to.equal('test'); - }); - }); + it('sets the apiVersion in the object', () => { + expect(parseCliArgs(argv).apiStrict).to.equal(true); + }); + }); + }); - context('when providing --tlsDisabledProtocols', () => { - const argv = [ ...baseArgv, uri, '--tlsDisabledProtocols', 'TLS1_0,TLS2_0' ]; + context('when providing filenames after an URI', () => { + context('when the filenames end in .js', () => { + const argv = [ ...baseArgv, uri, 'test1.js', 'test2.js' ]; - it('returns the URI in the object', () => { - expect(parseCliArgs(argv)._[0]).to.equal(uri); - }); + it('returns the URI in the object', () => { + expect(parseCliArgs(argv).connectionSpecifier).to.equal(uri); + }); - it('sets the tlsDisabledProtocols in the object', () => { - expect(parseCliArgs(argv).tlsDisabledProtocols).to.equal('TLS1_0,TLS2_0'); - }); - }); + it('sets the filenames', () => { + expect(parseCliArgs(argv).fileNames[0]).to.equal('test1.js'); + expect(parseCliArgs(argv).fileNames[1]).to.equal('test2.js'); }); + }); - context('when providing FLE options', () => { - context('when providing --awsAccessKeyId', () => { - const argv = [ ...baseArgv, uri, '--awsAccessKeyId', 'foo' ]; + context('when the filenames end in .mongodb', () => { + const argv = [ ...baseArgv, uri, 'test1.mongodb', 'test2.mongodb' ]; - it('returns the URI in the object', () => { - expect(parseCliArgs(argv)._[0]).to.equal(uri); - }); + it('returns the URI in the object', () => { + expect(parseCliArgs(argv).connectionSpecifier).to.equal(uri); + }); - it('sets the awsAccessKeyId in the object', () => { - expect(parseCliArgs(argv).awsAccessKeyId).to.equal('foo'); - }); - }); - - context('when providing --awsSecretAccessKey', () => { - const argv = [ ...baseArgv, uri, '--awsSecretAccessKey', 'foo' ]; + it('sets the filenames', () => { + expect(parseCliArgs(argv).fileNames[0]).to.equal('test1.mongodb'); + expect(parseCliArgs(argv).fileNames[1]).to.equal('test2.mongodb'); + }); + }); + + context('when the filenames end in other extensions', () => { + const argv = [ ...baseArgv, uri, 'test1.txt', 'test2.txt' ]; - it('returns the URI in the object', () => { - expect(parseCliArgs(argv)._[0]).to.equal(uri); - }); + it('returns the URI in the object', () => { + expect(parseCliArgs(argv).connectionSpecifier).to.equal(uri); + }); - it('sets the awsSecretAccessKey in the object', () => { - expect(parseCliArgs(argv).awsSecretAccessKey).to.equal('foo'); - }); - }); + it('sets the filenames', () => { + expect(parseCliArgs(argv).fileNames[0]).to.equal('test1.txt'); + expect(parseCliArgs(argv).fileNames[1]).to.equal('test2.txt'); + }); + }); - context('when providing --awsSessionToken', () => { - const argv = [ ...baseArgv, uri, '--awsSessionToken', 'foo' ]; + context('when filenames are specified using -f', () => { + const argv = [ ...baseArgv, uri, '-f', 'test1.txt', '-f', 'test2.txt' ]; - it('returns the URI in the object', () => { - expect(parseCliArgs(argv)._[0]).to.equal(uri); - }); + it('returns the URI in the object', () => { + expect(parseCliArgs(argv).connectionSpecifier).to.equal(uri); + }); - it('sets the awsSessionToken in the object', () => { - expect(parseCliArgs(argv).awsSessionToken).to.equal('foo'); - }); - }); + it('sets the filenames', () => { + expect(parseCliArgs(argv).fileNames[0]).to.equal('test1.txt'); + expect(parseCliArgs(argv).fileNames[1]).to.equal('test2.txt'); + }); + }); - context('when providing --keyVaultNamespace', () => { - const argv = [ ...baseArgv, uri, '--keyVaultNamespace', 'foo.bar' ]; + context('when filenames are specified using -f/--file', () => { + const argv = [ ...baseArgv, uri, '-f', 'test1.txt', '--file', 'test2.txt' ]; - it('returns the URI in the object', () => { - expect(parseCliArgs(argv)._[0]).to.equal(uri); - }); + it('returns the URI in the object', () => { + expect(parseCliArgs(argv).connectionSpecifier).to.equal(uri); + }); - it('sets the keyVaultNamespace in the object', () => { - expect(parseCliArgs(argv).keyVaultNamespace).to.equal('foo.bar'); - }); - }); + it('sets the filenames', () => { + expect(parseCliArgs(argv).fileNames[0]).to.equal('test1.txt'); + expect(parseCliArgs(argv).fileNames[1]).to.equal('test2.txt'); + }); + }); + }); - context('when providing --kmsURL', () => { - const argv = [ ...baseArgv, uri, '--kmsURL', 'example.com' ]; + context('when providing filenames without an URI', () => { + context('when the filenames end in .js', () => { + const argv = [ ...baseArgv, 'test1.js', 'test2.js' ]; - it('returns the URI in the object', () => { - expect(parseCliArgs(argv)._[0]).to.equal(uri); - }); + it('returns no URI in the object', () => { + expect(parseCliArgs(argv).connectionSpecifier).to.equal(undefined); + }); - it('sets the kmsURL in the object', () => { - expect(parseCliArgs(argv).kmsURL).to.equal('example.com'); - }); - }); + it('sets the filenames', () => { + expect(parseCliArgs(argv).fileNames[0]).to.equal('test1.js'); + expect(parseCliArgs(argv).fileNames[1]).to.equal('test2.js'); }); + }); - context('when providing versioned API options', () => { - context('when providing --apiVersion', () => { - const argv = [ ...baseArgv, uri, '--apiVersion', '1' ]; + context('when the filenames end in .mongodb', () => { + const argv = [ ...baseArgv, 'test1.mongodb', 'test2.mongodb' ]; - it('returns the URI in the object', () => { - expect(parseCliArgs(argv)._[0]).to.equal(uri); - }); + it('returns no URI in the object', () => { + expect(parseCliArgs(argv).connectionSpecifier).to.equal(undefined); + }); - it('sets the apiVersion in the object', () => { - expect(parseCliArgs(argv).apiVersion).to.equal('1'); - }); - }); + it('sets the filenames', () => { + expect(parseCliArgs(argv).fileNames[0]).to.equal('test1.mongodb'); + expect(parseCliArgs(argv).fileNames[1]).to.equal('test2.mongodb'); + }); + }); - context('when providing --apiDeprecationErrors', () => { - const argv = [ ...baseArgv, uri, '--apiDeprecationErrors' ]; + context('when the filenames end in other extensions', () => { + const argv = [ ...baseArgv, 'test1.txt', 'test2.txt' ]; - it('returns the URI in the object', () => { - expect(parseCliArgs(argv)._[0]).to.equal(uri); - }); + it('returns the first filename as an URI', () => { + expect(parseCliArgs(argv).connectionSpecifier).to.equal('test1.txt'); + }); - it('sets the apiVersion in the object', () => { - expect(parseCliArgs(argv).apiDeprecationErrors).to.equal(true); - }); - }); + it('uses the remainder as filenames', () => { + expect(parseCliArgs(argv).fileNames[0]).to.equal('test2.txt'); + }); + }); - context('when providing --apiStrict', () => { - const argv = [ ...baseArgv, uri, '--apiStrict' ]; + context('when the first argument is an URI ending in .js', () => { + const argv = [ ...baseArgv, 'mongodb://domain.foo.js', 'test2.txt' ]; - it('returns the URI in the object', () => { - expect(parseCliArgs(argv)._[0]).to.equal(uri); - }); + it('returns the first filename as an URI', () => { + expect(parseCliArgs(argv).connectionSpecifier).to.equal('mongodb://domain.foo.js'); + }); - it('sets the apiVersion in the object', () => { - expect(parseCliArgs(argv).apiStrict).to.equal(true); - }); - }); + it('uses the remainder as filenames', () => { + expect(parseCliArgs(argv).fileNames[0]).to.equal('test2.txt'); }); + }); - context('when providing filenames', () => { - context('when the filenames end in .js', () => { - const argv = [ ...baseArgv, uri, 'test1.js', 'test2.js' ]; + context('when the first argument is an URI ending in .js but --file is used', () => { + const argv = [ ...baseArgv, '--file', 'mongodb://domain.foo.js', 'mongodb://domain.bar.js' ]; - it('returns the URI in the object', () => { - expect(parseCliArgs(argv)._[0]).to.equal(uri); - }); + it('returns the first filename as an URI', () => { + expect(parseCliArgs(argv).connectionSpecifier).to.equal('mongodb://domain.bar.js'); + }); - it('sets the filenames', () => { - expect(parseCliArgs(argv)._[1]).to.equal('test1.js'); - expect(parseCliArgs(argv)._[2]).to.equal('test2.js'); - }); - }); - }); + it('uses the remainder as filenames', () => { + expect(parseCliArgs(argv).fileNames[0]).to.equal('mongodb://domain.foo.js'); + }); }); + }); + }); - context('when providing no URI', () => { - context('when providing a DB address', () => { - context('when only a db name is provided', () => { - const db = 'foo'; - const argv = [ ...baseArgv, db ]; - - it('sets the db in the object', () => { - expect(parseCliArgs(argv)._[0]).to.equal(db); - }); - }); + context('when providing no URI', () => { + context('when providing a DB address', () => { + context('when only a db name is provided', () => { + const db = 'foo'; + const argv = [ ...baseArgv, db ]; + + it('sets the db in the object', () => { + expect(parseCliArgs(argv).connectionSpecifier).to.equal(db); + }); + }); - context('when a db address is provided without a scheme', () => { - const db = '192.168.0.5:9999/foo'; - const argv = [ ...baseArgv, db ]; + context('when a db address is provided without a scheme', () => { + const db = '192.168.0.5:9999/foo'; + const argv = [ ...baseArgv, db ]; - it('sets the db in the object', () => { - expect(parseCliArgs(argv)._[0]).to.equal(db); - }); - }); + it('sets the db in the object', () => { + expect(parseCliArgs(argv).connectionSpecifier).to.equal(db); }); + }); + }); - context('when providing no DB address', () => { - context('when providing a host', () => { - const argv = [ ...baseArgv, '--host', 'example.com' ]; - - it('sets the host value in the object', () => { - expect(parseCliArgs(argv).host).to.equal('example.com'); - }); - }); - - context('when providing a port', () => { - const argv = [ ...baseArgv, '--port', '20000' ]; + context('when providing no DB address', () => { + context('when providing a host', () => { + const argv = [ ...baseArgv, '--host', 'example.com' ]; - it('sets the port value in the object', () => { - expect(parseCliArgs(argv).port).to.equal('20000'); - }); - }); - }); + it('sets the host value in the object', () => { + expect(parseCliArgs(argv).host).to.equal('example.com'); + }); }); - context('when providing a deprecated argument', () => { - [ - { deprecated: 'ssl', replacement: 'tls' }, - { deprecated: 'sslAllowInvalidCertificates', replacement: 'tlsAllowInvalidCertificates' }, - { deprecated: 'sslAllowInvalidCertificates', replacement: 'tlsAllowInvalidCertificates' }, - { deprecated: 'sslAllowInvalidHostname', replacement: 'tlsAllowInvalidHostname' }, - // { deprecated: 'sslFIPSMode', replacement: 'tlsFIPSMode' }, <<-- FIPS is currently not supported right now - { deprecated: 'sslPEMKeyFile', replacement: 'tlsCertificateKeyFile', value: 'pemKeyFile' }, - { deprecated: 'sslPEMKeyPassword', replacement: 'tlsCertificateKeyFilePassword', value: 'pemKeyPass' }, - { deprecated: 'sslCAFile', replacement: 'tlsCAFile', value: 'caFile' }, - // { deprecated: 'sslCertificateSelector', replacement: 'tlsCertificateSelector', value: 'certSelector' }, <<-- Certificate selector not supported right now - { deprecated: 'sslCRLFile', replacement: 'tlsCRLFile', value: 'crlFile' }, - { deprecated: 'sslDisabledProtocols', replacement: 'tlsDisabledProtocols', value: 'disabledProtos' } - ].forEach(({ deprecated, replacement, value }) => { - it(`replaces --${deprecated} with --${replacement}`, () => { - const argv = [...baseArgv, `--${deprecated}`]; - if (value) { - argv.push(value); - } - - const args = parseCliArgs(argv); - expect(args).to.not.have.property(deprecated); - expect(args[replacement]).to.equal(value ?? true); - }); + context('when providing a port', () => { + const argv = [ ...baseArgv, '--port', '20000' ]; + + it('sets the port value in the object', () => { + expect(parseCliArgs(argv).port).to.equal('20000'); }); }); }); }); + + context('when providing a deprecated argument', () => { + [ + { deprecated: 'ssl', replacement: 'tls' }, + { deprecated: 'sslAllowInvalidCertificates', replacement: 'tlsAllowInvalidCertificates' }, + { deprecated: 'sslAllowInvalidCertificates', replacement: 'tlsAllowInvalidCertificates' }, + { deprecated: 'sslAllowInvalidHostnames', replacement: 'tlsAllowInvalidHostnames' }, + // { deprecated: 'sslFIPSMode', replacement: 'tlsFIPSMode' }, <<-- FIPS is currently not supported right now + { deprecated: 'sslPEMKeyFile', replacement: 'tlsCertificateKeyFile', value: 'pemKeyFile' }, + { deprecated: 'sslPEMKeyPassword', replacement: 'tlsCertificateKeyFilePassword', value: 'pemKeyPass' }, + { deprecated: 'sslCAFile', replacement: 'tlsCAFile', value: 'caFile' }, + // { deprecated: 'sslCertificateSelector', replacement: 'tlsCertificateSelector', value: 'certSelector' }, <<-- Certificate selector not supported right now + { deprecated: 'sslCRLFile', replacement: 'tlsCRLFile', value: 'crlFile' }, + { deprecated: 'sslDisabledProtocols', replacement: 'tlsDisabledProtocols', value: 'disabledProtos' } + ].forEach(({ deprecated, replacement, value }) => { + it(`replaces --${deprecated} with --${replacement}`, () => { + const argv = [...baseArgv, `--${deprecated}`]; + if (value) { + argv.push(value); + } + + const args = parseCliArgs(argv); + expect(args).to.not.have.property(deprecated); + expect(args[replacement]).to.equal(value ?? true); + }); + }); + }); }); }); diff --git a/packages/cli-repl/src/arg-parser.ts b/packages/cli-repl/src/arg-parser.ts index 25f2550eea..011944f259 100644 --- a/packages/cli-repl/src/arg-parser.ts +++ b/packages/cli-repl/src/arg-parser.ts @@ -10,11 +10,6 @@ import { USAGE } from './constants'; */ const UNKNOWN = 'cli-repl.arg-parser.unknown-option'; -/** - * npm start constant. - */ -const START = 'start'; - /** * The yargs-parser options configuration. */ @@ -66,7 +61,7 @@ const OPTIONS = { 'smokeTests', 'ssl', 'sslAllowInvalidCertificates', - 'sslAllowInvalidHostname', + 'sslAllowInvalidHostnames', 'sslFIPSMode', 'tls', 'tlsAllowInvalidCertificates', @@ -75,24 +70,31 @@ const OPTIONS = { 'verbose', 'version' ], + array: [ + 'file' + ], alias: { h: 'help', p: 'password', - u: 'username' + u: 'username', + f: 'file' }, configuration: { 'camel-case-expansion': false, - 'unknown-options-as-args': true + 'unknown-options-as-args': true, + 'parse-positional-numbers': false, + 'parse-numbers': false, + 'greedy-arrays': false } }; /** * Maps deprecated arguments to their new counterparts. */ -const DEPRECATED_ARGS_WITH_REPLACEMENT: Record = { +const DEPRECATED_ARGS_WITH_REPLACEMENT: Record = { ssl: 'tls', sslAllowInvalidCertificates: 'tlsAllowInvalidCertificates', - sslAllowInvalidHostname: 'tlsAllowInvalidHostname', + sslAllowInvalidHostnames: 'tlsAllowInvalidHostnames', sslFIPSMode: 'tlsFIPSMode', sslPEMKeyFile: 'tlsCertificateKeyFile', sslPEMKeyPassword: 'tlsCertificateKeyFilePassword', @@ -126,6 +128,13 @@ export function getLocale(args: string[], env: any): string { return lang ? lang.split('.')[0] : lang; } +function isConnectionSpecifier(arg?: string): boolean { + return typeof arg === 'string' && + (arg.startsWith('mongodb://') || + arg.startsWith('mongodb+srv://') || + !(arg.endsWith('.js') || arg.endsWith('.mongodb'))); +} + /** * Parses arguments into a JS object. * @@ -137,28 +146,37 @@ export function parseCliArgs(args: string[]): (CliOptions & { smokeTests: boolea const programArgs = args.slice(2); i18n.setLocale(getLocale(programArgs, process.env)); - const parsed = parser(programArgs, OPTIONS); - parsed._ = parsed._.filter(arg => { - if (arg === START) { - return false; - } - // mongosh is currently not taking any numerical positional arguments. - if (typeof arg === 'string' && !arg.startsWith('-')) { - return true; + const parsed = parser(programArgs, OPTIONS) as unknown as CliOptions & { + smokeTests: boolean; + _?: string[]; + file?: string[]; + }; + const positionalArguments = parsed._ ?? []; + for (const arg of positionalArguments) { + if (arg.startsWith('-')) { + throw new Error( + ` ${clr(i18n.__(UNKNOWN), ['red', 'bold'])} ${clr(String(arg), 'bold')} + ${USAGE}` + ); } - throw new Error( - ` ${clr(i18n.__(UNKNOWN), ['red', 'bold'])} ${clr(String(arg), 'bold')} - ${USAGE}` - ); - }); + } + + if (!parsed.nodb && isConnectionSpecifier(positionalArguments[0])) { + parsed.connectionSpecifier = positionalArguments.shift(); + } + parsed.fileNames = [...(parsed.file ?? []), ...positionalArguments]; + + // All positional arguments are either in connectionSpecifier or fileNames, + // and should only be accessed that way now. + delete parsed._; const messages = verifyCliArguments(parsed); messages.forEach(m => console.warn(m)); - return parsed as unknown as (CliOptions & { smokeTests: boolean }); + return parsed; } -export function verifyCliArguments(args: parser.Arguments): string[] { +export function verifyCliArguments(args: any /* CliOptions */): string[] { for (const unsupported of UNSUPPORTED_ARGS) { if (unsupported in args) { throw new MongoshUnimplementedError( diff --git a/packages/cli-repl/src/cli-repl.spec.ts b/packages/cli-repl/src/cli-repl.spec.ts index df9ac75ffe..6a3ee61375 100644 --- a/packages/cli-repl/src/cli-repl.spec.ts +++ b/packages/cli-repl/src/cli-repl.spec.ts @@ -335,7 +335,7 @@ describe('CliRepl', () => { context('files loaded from command line', () => { it('load a file if it has been specified on the command line', async() => { const filename1 = path.resolve(__dirname, '..', 'test', 'fixtures', 'load', 'hello1.js'); - cliReplOptions.shellCliOptions._ = [filename1]; + cliReplOptions.shellCliOptions.fileNames = [filename1]; cliRepl = new CliRepl(cliReplOptions); await startWithExpectedImmediateExit(cliRepl, ''); expect(output).to.include(`Loading file: ${filename1}`); @@ -346,7 +346,7 @@ describe('CliRepl', () => { it('load two files if it has been specified on the command line', async() => { const filename1 = path.resolve(__dirname, '..', 'test', 'fixtures', 'load', 'hello1.js'); const filename2 = path.resolve(__dirname, '..', 'test', 'fixtures', 'load', 'hello2.js'); - cliReplOptions.shellCliOptions._ = [filename1, filename2]; + cliReplOptions.shellCliOptions.fileNames = [filename1, filename2]; cliRepl = new CliRepl(cliReplOptions); await startWithExpectedImmediateExit(cliRepl, ''); expect(output).to.include(`Loading file: ${filename1}`); @@ -358,7 +358,7 @@ describe('CliRepl', () => { it('does not print filenames if --quiet is passed', async() => { const filename1 = path.resolve(__dirname, '..', 'test', 'fixtures', 'load', 'hello1.js'); - cliReplOptions.shellCliOptions._ = [filename1]; + cliReplOptions.shellCliOptions.fileNames = [filename1]; cliReplOptions.shellCliOptions.quiet = true; cliRepl = new CliRepl(cliReplOptions); await startWithExpectedImmediateExit(cliRepl, ''); @@ -369,7 +369,7 @@ describe('CliRepl', () => { it('forwards the error it if loading the file throws', async() => { const filename1 = path.resolve(__dirname, '..', 'test', 'fixtures', 'load', 'throw.js'); - cliReplOptions.shellCliOptions._ = [filename1]; + cliReplOptions.shellCliOptions.fileNames = [filename1]; cliRepl = new CliRepl(cliReplOptions); try { await cliRepl.start('', {}); @@ -464,7 +464,7 @@ describe('CliRepl', () => { let cliRepl: CliRepl; beforeEach(async() => { - cliReplOptions.shellCliOptions._ = [await testServer.connectionString()]; + cliReplOptions.shellCliOptions.connectionSpecifier = await testServer.connectionString(); cliRepl = new CliRepl(cliReplOptions); }); @@ -716,7 +716,7 @@ describe('CliRepl', () => { context('files loaded from command line', () => { it('load a file if it has been specified on the command line', async() => { const filename1 = path.resolve(__dirname, '..', 'test', 'fixtures', 'load', 'hello1.js'); - cliReplOptions.shellCliOptions._.push(filename1); + cliReplOptions.shellCliOptions.fileNames = [filename1]; cliRepl = new CliRepl(cliReplOptions); await startWithExpectedImmediateExit(cliRepl, await testServer.connectionString()); expect(output).to.include(`Loading file: ${filename1}`); @@ -727,7 +727,7 @@ describe('CliRepl', () => { it('load two files if it has been specified on the command line', async() => { const filename1 = path.resolve(__dirname, '..', 'test', 'fixtures', 'load', 'hello1.js'); const filename2 = path.resolve(__dirname, '..', 'test', 'fixtures', 'load', 'hello2.js'); - cliReplOptions.shellCliOptions._.push(filename1, filename2); + cliReplOptions.shellCliOptions.fileNames = [filename1, filename2]; cliRepl = new CliRepl(cliReplOptions); await startWithExpectedImmediateExit(cliRepl, await testServer.connectionString()); expect(output).to.include(`Loading file: ${filename1}`); @@ -739,7 +739,7 @@ describe('CliRepl', () => { it('allows doing db ops', async() => { const filename1 = path.resolve(__dirname, '..', 'test', 'fixtures', 'load', 'insertintotest.js'); - cliReplOptions.shellCliOptions._.push(filename1, filename1); + cliReplOptions.shellCliOptions.fileNames = [filename1, filename1]; cliRepl = new CliRepl(cliReplOptions); await startWithExpectedImmediateExit(cliRepl, await testServer.connectionString()); expect(output).to.match(/Inserted: ObjectId\("[a-z0-9]{24}"\)/); @@ -757,7 +757,7 @@ describe('CliRepl', () => { it('drops into a shell if --shell is passed', async() => { const filename1 = path.resolve(__dirname, '..', 'test', 'fixtures', 'load', 'insertintotest.js'); - cliReplOptions.shellCliOptions._.push(filename1); + cliReplOptions.shellCliOptions.fileNames = [filename1]; cliReplOptions.shellCliOptions.shell = true; cliRepl = new CliRepl(cliReplOptions); @@ -777,7 +777,7 @@ describe('CliRepl', () => { it('does not read .mongoshrc.js if --shell is not passed', async() => { await fs.writeFile(path.join(tmpdir.path, '.mongoshrc.js'), 'print("hi from mongoshrc")'); const filename1 = path.resolve(__dirname, '..', 'test', 'fixtures', 'load', 'hello1.js'); - cliReplOptions.shellCliOptions._.push(filename1); + cliReplOptions.shellCliOptions.fileNames = [filename1]; cliRepl = new CliRepl(cliReplOptions); await startWithExpectedImmediateExit(cliRepl, await testServer.connectionString()); @@ -789,7 +789,7 @@ describe('CliRepl', () => { it('does read .mongoshrc.js if --shell is passed', async() => { await fs.writeFile(path.join(tmpdir.path, '.mongoshrc.js'), 'print("hi from mongoshrc")'); const filename1 = path.resolve(__dirname, '..', 'test', 'fixtures', 'load', 'hello1.js'); - cliReplOptions.shellCliOptions._.push(filename1); + cliReplOptions.shellCliOptions.fileNames = [filename1]; cliReplOptions.shellCliOptions.shell = true; cliRepl = new CliRepl(cliReplOptions); @@ -836,7 +836,7 @@ describe('CliRepl', () => { it('isInteractive() is false for loaded file without --shell', async() => { const filename1 = path.resolve(__dirname, '..', 'test', 'fixtures', 'load', 'printisinteractive.js'); - cliReplOptions.shellCliOptions._.push(filename1); + cliReplOptions.shellCliOptions.fileNames = [filename1]; cliRepl = new CliRepl(cliReplOptions); await startWithExpectedImmediateExit(cliRepl, await testServer.connectionString()); expect(output).to.match(/isInteractive=false/); @@ -845,7 +845,7 @@ describe('CliRepl', () => { it('isInteractive() is true for --eval with --shell', async() => { const filename1 = path.resolve(__dirname, '..', 'test', 'fixtures', 'load', 'printisinteractive.js'); - cliReplOptions.shellCliOptions._.push(filename1); + cliReplOptions.shellCliOptions.fileNames = [filename1]; cliReplOptions.shellCliOptions.shell = true; cliRepl = new CliRepl(cliReplOptions); await cliRepl.start(await testServer.connectionString(), {}); diff --git a/packages/cli-repl/src/cli-repl.ts b/packages/cli-repl/src/cli-repl.ts index ffba85cd87..a050980bba 100644 --- a/packages/cli-repl/src/cli-repl.ts +++ b/packages/cli-repl/src/cli-repl.ts @@ -166,7 +166,7 @@ class CliRepl { const initialServiceProvider = await this.connect(driverUri, driverOptions); const initialized = await this.mongoshRepl.initialize(initialServiceProvider); - const commandLineLoadFiles = this.listCommandLineLoadFiles(); + const commandLineLoadFiles = this.cliOptions.fileNames ?? []; if (commandLineLoadFiles.length > 0 || this.cliOptions.eval !== undefined) { this.mongoshRepl.setIsInteractive(!!this.cliOptions.shell); this.bus.emit('mongosh:start-loading-cli-scripts', { usesShellOption: !!this.cliOptions.shell }); @@ -183,11 +183,6 @@ class CliRepl { await this.mongoshRepl.startRepl(initialized); } - listCommandLineLoadFiles(): string[] { - const startIndex = this.cliOptions.nodb ? 0 : 1; - return (this.cliOptions._ ?? []).slice(startIndex); - } - async loadCommandLineFilesAndEval(files: string[]) { if (this.cliOptions.eval) { this.bus.emit('mongosh:eval-cli-script'); diff --git a/packages/i18n/src/locales/en_US.ts b/packages/i18n/src/locales/en_US.ts index 96cc3d8fb5..8e38f6c21e 100644 --- a/packages/i18n/src/locales/en_US.ts +++ b/packages/i18n/src/locales/en_US.ts @@ -8,7 +8,7 @@ const translations: Catalog = { build: {}, 'cli-repl': { args: { - usage: '$ mongosh [options] [db address]', + usage: '$ mongosh [options] [db address] [file names (ending in .js or .mongodb)]', options: 'Options:', help: 'Show this usage information', ipv6: 'Enable IPv6 support (disabled by default)', diff --git a/packages/service-provider-core/src/cli-options.ts b/packages/service-provider-core/src/cli-options.ts index a86553b4b0..fca0d051e4 100644 --- a/packages/service-provider-core/src/cli-options.ts +++ b/packages/service-provider-core/src/cli-options.ts @@ -2,7 +2,11 @@ * Valid options that can be parsed from the command line. */ export default interface CliOptions { - _?: string[]; + // Positional arguments: + connectionSpecifier?: string; + fileNames?: string[]; + + // Non-positional arguments: apiDeprecationErrors?: boolean; apiStrict?: boolean; apiVersion?: string; @@ -16,7 +20,6 @@ export default interface CliOptions { eval?: string; gssapiHostName?: string; gssapiServiceName?: string; - h?: boolean; help?: boolean; host?: string; ipv6?: boolean; @@ -24,7 +27,6 @@ export default interface CliOptions { kmsURL?: string; nodb?: boolean; norc?: boolean; - p?: string; password?: string; port?: string; quiet?: boolean; @@ -42,7 +44,6 @@ export default interface CliOptions { tlsDisabledProtocols?: boolean; tlsFIPSMode?: boolean; username?: string; - u?: string; verbose?: boolean; version?: boolean; } diff --git a/packages/service-provider-core/src/uri-generator.spec.ts b/packages/service-provider-core/src/uri-generator.spec.ts index ac0f7ba147..e1e1b3237b 100644 --- a/packages/service-provider-core/src/uri-generator.spec.ts +++ b/packages/service-provider-core/src/uri-generator.spec.ts @@ -4,7 +4,7 @@ import generateUri from './uri-generator'; describe('uri-generator.generate-uri', () => { context('when no arguments are provided', () => { - const options = { _: [] }; + const options = { connectionSpecifier: undefined }; it('returns the default uri', () => { expect(generateUri(options)).to.equal('mongodb://127.0.0.1:27017/?directConnection=true&serverSelectionTimeoutMS=2000'); @@ -13,20 +13,20 @@ describe('uri-generator.generate-uri', () => { context('when no URI is provided', () => { it('handles host', () => { - expect(generateUri({ _: [], host: 'localhost' })).to.equal('mongodb://localhost:27017/?directConnection=true&serverSelectionTimeoutMS=2000'); + expect(generateUri({ connectionSpecifier: undefined, host: 'localhost' })).to.equal('mongodb://localhost:27017/?directConnection=true&serverSelectionTimeoutMS=2000'); }); it('handles port', () => { - expect(generateUri({ _: [], port: '27018' })).to.equal('mongodb://127.0.0.1:27018/?directConnection=true&serverSelectionTimeoutMS=2000'); + expect(generateUri({ connectionSpecifier: undefined, port: '27018' })).to.equal('mongodb://127.0.0.1:27018/?directConnection=true&serverSelectionTimeoutMS=2000'); }); it('handles both host and port', () => { - expect(generateUri({ _: [], host: 'localhost', port: '27018' })).to.equal('mongodb://localhost:27018/?directConnection=true&serverSelectionTimeoutMS=2000'); + expect(generateUri({ connectionSpecifier: undefined, host: 'localhost', port: '27018' })).to.equal('mongodb://localhost:27018/?directConnection=true&serverSelectionTimeoutMS=2000'); }); it('handles host with port included', () => { - expect(generateUri({ _: [], host: 'localhost:27018' })).to.equal('mongodb://localhost:27018/?directConnection=true&serverSelectionTimeoutMS=2000'); + expect(generateUri({ connectionSpecifier: undefined, host: 'localhost:27018' })).to.equal('mongodb://localhost:27018/?directConnection=true&serverSelectionTimeoutMS=2000'); }); it('throws if host has port AND port set to other value', () => { try { - generateUri({ _: [], host: 'localhost:27018', port: '27019' }); + generateUri({ connectionSpecifier: undefined, host: 'localhost:27018', port: '27019' }); expect.fail('expected error'); } catch (e) { expect(e).to.be.instanceOf(MongoshInvalidInputError); @@ -34,13 +34,13 @@ describe('uri-generator.generate-uri', () => { } }); it('handles host has port AND port set to equal value', () => { - expect(generateUri({ _: [], host: 'localhost:27018', port: '27018' })).to.equal('mongodb://localhost:27018/?directConnection=true&serverSelectionTimeoutMS=2000'); + expect(generateUri({ connectionSpecifier: undefined, host: 'localhost:27018', port: '27018' })).to.equal('mongodb://localhost:27018/?directConnection=true&serverSelectionTimeoutMS=2000'); }); }); context('when a full URI is provided', () => { context('when no additional options are provided', () => { - const options = { _: ['mongodb://192.0.0.1:27018/foo'] }; + const options = { connectionSpecifier: 'mongodb://192.0.0.1:27018/foo' }; it('returns the uri', () => { expect(generateUri(options)).to.equal('mongodb://192.0.0.1:27018/foo?directConnection=true'); @@ -50,7 +50,7 @@ describe('uri-generator.generate-uri', () => { context('when additional options are provided', () => { context('when providing host with URI', () => { const uri = 'mongodb://192.0.0.1:27018/foo'; - const options = { _: [uri], host: '127.0.0.1' }; + const options = { connectionSpecifier: uri, host: '127.0.0.1' }; it('throws an exception', () => { try { @@ -65,7 +65,7 @@ describe('uri-generator.generate-uri', () => { context('when providing port with URI', () => { const uri = 'mongodb://192.0.0.1:27018/foo'; - const options = { _: [uri], port: '27018' }; + const options = { connectionSpecifier: uri, port: '27018' }; it('throws an exception', () => { try { @@ -82,7 +82,7 @@ describe('uri-generator.generate-uri', () => { context('when providing a URI with query parameters', () => { context('that do not conflict with directConnection', () => { const uri = 'mongodb://192.0.0.1:27018?readPreference=primary'; - const options = { _: [uri] }; + const options = { connectionSpecifier: uri }; it('still includes directConnection', () => { expect(generateUri(options)).to.equal('mongodb://192.0.0.1:27018/?readPreference=primary&directConnection=true'); }); @@ -90,7 +90,7 @@ describe('uri-generator.generate-uri', () => { context('including replicaSet', () => { const uri = 'mongodb://192.0.0.1:27018/db?replicaSet=replicaset'; - const options = { _: [uri] }; + const options = { connectionSpecifier: uri }; it('does not add the directConnection parameter', () => { expect(generateUri(options)).to.equal(uri); }); @@ -98,7 +98,7 @@ describe('uri-generator.generate-uri', () => { context('including explicit directConnection', () => { const uri = 'mongodb://192.0.0.1:27018/db?directConnection=false'; - const options = { _: [uri] }; + const options = { connectionSpecifier: uri }; it('does not change the directConnection parameter', () => { expect(generateUri(options)).to.equal(uri); }); @@ -107,7 +107,7 @@ describe('uri-generator.generate-uri', () => { context('when providing a URI with SRV record', () => { const uri = 'mongodb+srv://somehost/?readPreference=primary'; - const options = { _: [uri] }; + const options = { connectionSpecifier: uri }; it('no directConnection is added', () => { expect(generateUri(options)).to.equal(uri); }); @@ -115,7 +115,7 @@ describe('uri-generator.generate-uri', () => { context('when providing a URI with multiple seeds', () => { const uri = 'mongodb://192.42.42.42:27017,192.0.0.1:27018/db?readPreference=primary'; - const options = { _: [uri] }; + const options = { connectionSpecifier: uri }; it('no directConnection is added', () => { expect(generateUri(options)).to.equal(uri); }); @@ -125,7 +125,7 @@ describe('uri-generator.generate-uri', () => { context('when a URI is provided without a scheme', () => { context('when providing host', () => { const uri = '192.0.0.1'; - const options = { _: [uri] }; + const options = { connectionSpecifier: uri }; it('returns the uri with the scheme', () => { expect(generateUri(options)).to.equal(`mongodb://${uri}:27017/test?directConnection=true`); @@ -134,7 +134,7 @@ describe('uri-generator.generate-uri', () => { context('when providing host:port', () => { const uri = '192.0.0.1:27018'; - const options = { _: [uri] }; + const options = { connectionSpecifier: uri }; it('returns the uri with the scheme', () => { expect(generateUri(options)).to.equal(`mongodb://${uri}/test?directConnection=true`); @@ -143,7 +143,7 @@ describe('uri-generator.generate-uri', () => { context('when proving host + port option', () => { const uri = '192.0.0.1'; - const options = { _: [uri], port: '27018' }; + const options = { connectionSpecifier: uri, port: '27018' }; it('throws an exception', () => { try { @@ -158,7 +158,7 @@ describe('uri-generator.generate-uri', () => { context('when no additional options are provided without db', () => { const uri = '192.0.0.1:27018'; - const options = { _: [uri] }; + const options = { connectionSpecifier: uri }; it('returns the uri with the scheme', () => { expect(generateUri(options)).to.equal(`mongodb://${uri}/test?directConnection=true`); @@ -167,7 +167,7 @@ describe('uri-generator.generate-uri', () => { context('when no additional options are provided with empty db', () => { const uri = '192.0.0.1:27018/'; - const options = { _: [uri] }; + const options = { connectionSpecifier: uri }; it('returns the uri with the scheme', () => { expect(generateUri(options)).to.equal(`mongodb://${uri}test?directConnection=true`); @@ -176,7 +176,7 @@ describe('uri-generator.generate-uri', () => { context('when no additional options are provided with db', () => { const uri = '192.0.0.1:27018/foo'; - const options = { _: [uri] }; + const options = { connectionSpecifier: uri }; it('returns the uri with the scheme', () => { expect(generateUri(options)).to.equal(`mongodb://${uri}?directConnection=true`); @@ -186,7 +186,7 @@ describe('uri-generator.generate-uri', () => { context('when additional options are provided', () => { context('when providing host with URI', () => { const uri = '192.0.0.1:27018/foo'; - const options = { _: [uri], host: '127.0.0.1' }; + const options = { connectionSpecifier: uri, host: '127.0.0.1' }; it('throws an exception', () => { try { @@ -201,7 +201,7 @@ describe('uri-generator.generate-uri', () => { context('when providing host with db', () => { const uri = 'foo'; - const options = { _: [uri], host: '127.0.0.2' }; + const options = { connectionSpecifier: uri, host: '127.0.0.2' }; it('uses the provided host with default port', () => { expect(generateUri(options)).to.equal('mongodb://127.0.0.2:27017/foo?directConnection=true'); @@ -210,7 +210,7 @@ describe('uri-generator.generate-uri', () => { context('when providing port with URI', () => { const uri = '192.0.0.1:27018/foo'; - const options = { _: [uri], port: '27018' }; + const options = { connectionSpecifier: uri, port: '27018' }; it('throws an exception', () => { try { @@ -225,7 +225,7 @@ describe('uri-generator.generate-uri', () => { context('when providing port with db', () => { const uri = 'foo'; - const options = { _: [uri], port: '27018' }; + const options = { connectionSpecifier: uri, port: '27018' }; it('uses the provided host with default port', () => { expect(generateUri(options)).to.equal('mongodb://127.0.0.1:27018/foo?directConnection=true&serverSelectionTimeoutMS=2000'); @@ -234,7 +234,7 @@ describe('uri-generator.generate-uri', () => { context('when providing port with only a host URI', () => { const uri = '127.0.0.2/foo'; - const options = { _: [uri], port: '27018' }; + const options = { connectionSpecifier: uri, port: '27018' }; it('throws an exception', () => { try { @@ -249,7 +249,7 @@ describe('uri-generator.generate-uri', () => { context('when providing nodb', () => { const uri = 'mongodb://127.0.0.2/foo'; - const options = { _: [uri], nodb: true }; + const options = { connectionSpecifier: uri, nodb: true }; it('returns an empty string', () => { expect(generateUri(options)).to.equal(''); @@ -258,7 +258,7 @@ describe('uri-generator.generate-uri', () => { context('when providing explicit serverSelectionTimeoutMS', () => { const uri = 'mongodb://127.0.0.2/foo?serverSelectionTimeoutMS=10'; - const options = { _: [uri] }; + const options = { connectionSpecifier: uri }; it('does not override the existing value', () => { expect(generateUri(options)).to.equal('mongodb://127.0.0.2/foo?serverSelectionTimeoutMS=10&directConnection=true'); @@ -267,7 +267,7 @@ describe('uri-generator.generate-uri', () => { context('when providing explicit serverSelectionTimeoutMS (different case)', () => { const uri = 'mongodb://127.0.0.2/foo?SERVERSELECTIONTIMEOUTMS=10'; - const options = { _: [uri] }; + const options = { connectionSpecifier: uri }; it('does not override the existing value', () => { expect(generateUri(options)).to.equal('mongodb://127.0.0.2/foo?SERVERSELECTIONTIMEOUTMS=10&directConnection=true'); @@ -278,7 +278,7 @@ describe('uri-generator.generate-uri', () => { context('when providing a URI with query parameters', () => { context('that do not conflict with directConnection', () => { const uri = '192.0.0.1:27018?readPreference=primary'; - const options = { _: [uri] }; + const options = { connectionSpecifier: uri }; it('still includes directConnection', () => { expect(generateUri(options)).to.equal('mongodb://192.0.0.1:27018/?readPreference=primary&directConnection=true'); }); @@ -286,7 +286,7 @@ describe('uri-generator.generate-uri', () => { context('including replicaSet', () => { const uri = '192.0.0.1:27018/db?replicaSet=replicaset'; - const options = { _: [uri] }; + const options = { connectionSpecifier: uri }; it('does not add the directConnection parameter', () => { expect(generateUri(options)).to.equal(`mongodb://${uri}`); }); @@ -294,7 +294,7 @@ describe('uri-generator.generate-uri', () => { context('including explicit directConnection', () => { const uri = '192.0.0.1:27018?directConnection=false'; - const options = { _: [uri] }; + const options = { connectionSpecifier: uri }; it('does not change the directConnection parameter', () => { expect(generateUri(options)).to.equal('mongodb://192.0.0.1:27018/?directConnection=false'); }); @@ -305,7 +305,7 @@ describe('uri-generator.generate-uri', () => { context('when an invalid URI is provided', () => { const uri = '/x'; - const options = { _: [uri] }; + const options = { connectionSpecifier: uri }; it('returns the uri', () => { try { @@ -343,7 +343,7 @@ describe('uri-generator.generate-uri', () => { }); it('returns a URI for the hosts and ports specified in --host and database name', () => { - const options = { host: 'replsetname/host1:123,host2,host3:456', _: ['admin'] }; + const options = { host: 'replsetname/host1:123,host2,host3:456', connectionSpecifier: 'admin' }; expect(generateUri(options)).to.equal('mongodb://host1:123,host2,host3:456/admin?replicaSet=replsetname'); }); }); diff --git a/packages/service-provider-core/src/uri-generator.ts b/packages/service-provider-core/src/uri-generator.ts index 1a68f1f594..40f45274e3 100644 --- a/packages/service-provider-core/src/uri-generator.ts +++ b/packages/service-provider-core/src/uri-generator.ts @@ -139,7 +139,7 @@ function generateUri(options: CliOptions): string { return connectionString.toString(); } function generateUriNormalized(options: CliOptions): ConnectionString { - const uri = options._?.[0]; + const uri = options.connectionSpecifier; // If the --host argument contains /, it has the format // /<:port>,<:port>,<...> diff --git a/packages/shell-api/src/mongo.ts b/packages/shell-api/src/mongo.ts index e2f00bd7e7..20837a9f99 100644 --- a/packages/shell-api/src/mongo.ts +++ b/packages/shell-api/src/mongo.ts @@ -82,9 +82,9 @@ export default class Mongo extends ShellApiClass { this.__serviceProvider = sp; } if (typeof uri === 'string') { - this._uri = generateUri({ _: [uri] }); + this._uri = generateUri({ connectionSpecifier: uri }); } else { - this._uri = sp?.getURI?.() ?? generateUri({ _: ['mongodb://localhost/'] }); + this._uri = sp?.getURI?.() ?? generateUri({ connectionSpecifier: 'mongodb://localhost/' }); } this._readPreferenceWasExplicitlyRequested = /\breadPreference=/.test(this._uri); if (fleOptions) {