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
2 changes: 1 addition & 1 deletion packages/arg-parser/src/cli-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export interface CliOptions {
csfleLibraryPath?: string;
cryptSharedLibPath?: string;
db?: string;
eval?: string;
eval?: string[];
gssapiServiceName?: string;
sspiHostnameCanonicalization?: string;
sspiRealmOverride?: string;
Expand Down
16 changes: 14 additions & 2 deletions packages/cli-repl/src/arg-parser.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,15 +194,27 @@ describe('arg-parser', () => {
});
});

context('when providing --eval', () => {
context('when providing --eval (single value)', () => {
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');
expect(parseCliArgs(argv).eval).to.deep.equal(['1+1']);
});
});

context('when providing --eval (multiple values)', () => {
const argv = [ ...baseArgv, uri, '--eval', '1+1', '--eval', '2+2' ];

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.deep.equal(['1+1', '2+2']);
});
});

Expand Down
2 changes: 1 addition & 1 deletion packages/cli-repl/src/arg-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ const OPTIONS = {
'csfleLibraryPath',
'cryptSharedLibPath',
'db',
'eval',
'gssapiHostName',
'gssapiServiceName',
'sspiHostnameCanonicalization',
Expand Down Expand Up @@ -76,6 +75,7 @@ const OPTIONS = {
'version'
],
array: [
'eval',
'file'
],
alias: {
Expand Down
49 changes: 31 additions & 18 deletions packages/cli-repl/src/cli-repl.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -482,16 +482,16 @@ describe('CliRepl', () => {
expect(output).not.to.include('uh oh');
});

it('evaluates code passed through --eval', async() => {
cliReplOptions.shellCliOptions.eval = '"i am" + " being evaluated"';
it('evaluates code passed through --eval (single argument)', async() => {
cliReplOptions.shellCliOptions.eval = ['"i am" + " being evaluated"'];
cliRepl = new CliRepl(cliReplOptions);
await startWithExpectedImmediateExit(cliRepl, '');
expect(output).to.include('i am being evaluated');
expect(exitCode).to.equal(0);
});

it('forwards the error if the script passed to --eval throws', async() => {
cliReplOptions.shellCliOptions.eval = 'throw new Error("oh no")';
it('forwards the error if the script passed to --eval throws (single argument)', async() => {
cliReplOptions.shellCliOptions.eval = ['throw new Error("oh no")'];
cliRepl = new CliRepl(cliReplOptions);
try {
await cliRepl.start('', {});
Expand All @@ -500,6 +500,27 @@ describe('CliRepl', () => {
}
expect(output).not.to.include('oh no');
});

it('evaluates code passed through --eval (multiple arguments)', async() => {
cliReplOptions.shellCliOptions.eval = ['X = "i am"; "asdfghjkl"', 'X + " being evaluated"'];
cliRepl = new CliRepl(cliReplOptions);
await startWithExpectedImmediateExit(cliRepl, '');
expect(output).to.not.include('asdfghjkl');
expect(output).to.include('i am being evaluated');
expect(exitCode).to.equal(0);
});

it('forwards the error if the script passed to --eval throws (multiple arguments)', async() => {
cliReplOptions.shellCliOptions.eval = ['throw new Error("oh no")', 'asdfghjkl'];
cliRepl = new CliRepl(cliReplOptions);
try {
await cliRepl.start('', {});
} catch (err: any) {
expect(err.message).to.include('oh no');
}
expect(output).to.not.include('asdfghjkl');
expect(output).not.to.include('oh no');
});
});

context('with a global configuration file', () => {
Expand Down Expand Up @@ -919,7 +940,7 @@ describe('CliRepl', () => {
});

it('sends out telemetry data for command line scripts', async() => {
cliReplOptions.shellCliOptions.eval = 'db.hello()';
cliReplOptions.shellCliOptions.eval = ['db.hello()'];
cliRepl = new CliRepl(cliReplOptions);
await startWithExpectedImmediateExit(cliRepl, await testServer.connectionString());
expect(requests).to.have.lengthOf(2);
Expand Down Expand Up @@ -948,14 +969,14 @@ describe('CliRepl', () => {
});

it('does not send out telemetry if the user only runs a script for disabling telemetry', async() => {
cliReplOptions.shellCliOptions.eval = 'disableTelemetry()';
cliReplOptions.shellCliOptions.eval = ['disableTelemetry()'];
cliRepl = new CliRepl(cliReplOptions);
await startWithExpectedImmediateExit(cliRepl, await testServer.connectionString());
expect(requests).to.have.lengthOf(0);
});

it('does not send out telemetry if the user runs a script for disabling telemetry and drops into the shell', async() => {
cliReplOptions.shellCliOptions.eval = 'disableTelemetry()';
cliReplOptions.shellCliOptions.eval = ['disableTelemetry()'];
cliReplOptions.shellCliOptions.shell = true;
cliRepl = new CliRepl(cliReplOptions);
await cliRepl.start(await testServer.connectionString(), {});
Expand Down Expand Up @@ -1052,7 +1073,7 @@ describe('CliRepl', () => {

it('allows doing db ops (--eval variant)', async() => {
const filename1 = path.resolve(__dirname, '..', 'test', 'fixtures', 'load', 'insertintotest.js');
cliReplOptions.shellCliOptions.eval = await fs.readFile(filename1, 'utf8');
cliReplOptions.shellCliOptions.eval = [await fs.readFile(filename1, 'utf8')];
cliRepl = new CliRepl(cliReplOptions);
await startWithExpectedImmediateExit(cliRepl, await testServer.connectionString());
expect(output).to.match(/Inserted: ObjectId\("[a-z0-9]{24}"\)/);
Expand Down Expand Up @@ -1107,17 +1128,9 @@ describe('CliRepl', () => {
expect(exitCode).to.equal(0);
});

it('warns if --eval is passed an empty string', async() => {
cliReplOptions.shellCliOptions.eval = '';
cliRepl = new CliRepl(cliReplOptions);
await startWithExpectedImmediateExit(cliRepl, await testServer.connectionString());
expect(output).to.include('--eval requires an argument, but no argument was given');
expect(exitCode).to.equal(0);
});

it('isInteractive() is false for --eval without --shell', async() => {
const filename1 = path.resolve(__dirname, '..', 'test', 'fixtures', 'load', 'printisinteractive.js');
cliReplOptions.shellCliOptions.eval = await fs.readFile(filename1, 'utf8');
cliReplOptions.shellCliOptions.eval = [await fs.readFile(filename1, 'utf8')];
cliRepl = new CliRepl(cliReplOptions);
await startWithExpectedImmediateExit(cliRepl, await testServer.connectionString());
expect(output).to.match(/isInteractive=false/);
Expand All @@ -1126,7 +1139,7 @@ describe('CliRepl', () => {

it('isInteractive() is true for --eval with --shell', async() => {
const filename1 = path.resolve(__dirname, '..', 'test', 'fixtures', 'load', 'printisinteractive.js');
cliReplOptions.shellCliOptions.eval = await fs.readFile(filename1, 'utf8');
cliReplOptions.shellCliOptions.eval = [await fs.readFile(filename1, 'utf8')];
cliReplOptions.shellCliOptions.shell = true;
cliRepl = new CliRepl(cliReplOptions);
await cliRepl.start(await testServer.connectionString(), {});
Expand Down
23 changes: 11 additions & 12 deletions packages/cli-repl/src/cli-repl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,8 @@ class CliRepl implements MongoshIOProvider {
const initialized = await this.mongoshRepl.initialize(initialServiceProvider);

const commandLineLoadFiles = this.cliOptions.fileNames ?? [];
const willExecuteCommandLineScripts = commandLineLoadFiles.length > 0 || this.cliOptions.eval !== undefined;
const evalScripts = this.cliOptions.eval ?? [];
const willExecuteCommandLineScripts = commandLineLoadFiles.length > 0 || evalScripts.length > 0;
const willEnterInteractiveMode = !willExecuteCommandLineScripts || !!this.cliOptions.shell;

let snippetManager: SnippetManager | undefined;
Expand All @@ -280,7 +281,7 @@ class CliRepl implements MongoshIOProvider {
if (willExecuteCommandLineScripts) {
this.mongoshRepl.setIsInteractive(willEnterInteractiveMode);
this.bus.emit('mongosh:start-loading-cli-scripts', { usesShellOption: !!this.cliOptions.shell });
await this.loadCommandLineFilesAndEval(commandLineLoadFiles);
await this.loadCommandLineFilesAndEval(commandLineLoadFiles, evalScripts);
if (!this.cliOptions.shell) {
// We flush the telemetry data as part of exiting. Make sure we have
// the right config value.
Expand Down Expand Up @@ -335,18 +336,16 @@ class CliRepl implements MongoshIOProvider {
}
}

async loadCommandLineFilesAndEval(files: string[]) {
if (this.cliOptions.eval) {
async loadCommandLineFilesAndEval(files: string[], evalScripts: string[]) {
let lastEvalResult;
for (const script of evalScripts) {
this.bus.emit('mongosh:eval-cli-script');
const evalResult = await this.mongoshRepl.loadExternalCode(this.cliOptions.eval, '@(shell eval)');
this.output.write(this.mongoshRepl.writer(evalResult) + '\n');
} else if (this.cliOptions.eval === '') {
// This happens e.g. when --eval is followed by another option, for example
// when running `mongosh --eval --shell "eval script"`, which can happen
// if you're like me and sometimes insert options in the wrong place
const msg = 'Warning: --eval requires an argument, but no argument was given\n';
this.output.write(this.clr(msg, 'mongosh:warning'));
lastEvalResult = await this.mongoshRepl.loadExternalCode(script, '@(shell eval)');
}
if (lastEvalResult !== undefined) {
this.output.write(this.mongoshRepl.writer(lastEvalResult) + '\n');
}

for (const file of files) {
if (!this.cliOptions.quiet) {
this.output.write(`Loading file: ${this.clr(file, 'mongosh:filename')}\n`);
Expand Down