From ee7e6d4641409dc671a88cc2ff54439268c43037 Mon Sep 17 00:00:00 2001 From: Anna Henningsen Date: Wed, 19 May 2021 18:30:17 +0200 Subject: [PATCH] feat(cli-repl): add isInteractive() support MONGOSH-620 --- .../src/open-context-runtime.ts | 1 + packages/cli-repl/src/cli-repl.spec.ts | 55 +++++++++++++++++++ packages/cli-repl/src/cli-repl.ts | 3 + packages/cli-repl/src/mongosh-repl.ts | 4 ++ .../test/fixtures/load/printisinteractive.js | 2 + packages/i18n/src/locales/en_US.ts | 3 + packages/shell-api/src/shell-api.spec.ts | 7 +++ packages/shell-api/src/shell-api.ts | 4 ++ .../shell-api/src/shell-internal-state.ts | 1 + 9 files changed, 80 insertions(+) create mode 100644 packages/cli-repl/test/fixtures/load/printisinteractive.js diff --git a/packages/browser-runtime-core/src/open-context-runtime.ts b/packages/browser-runtime-core/src/open-context-runtime.ts index cd9fc12a8e..fc95113006 100644 --- a/packages/browser-runtime-core/src/open-context-runtime.ts +++ b/packages/browser-runtime-core/src/open-context-runtime.ts @@ -37,6 +37,7 @@ export class OpenContextRuntime implements Runtime { ) { this.interpreterEnvironment = interpreterEnvironment; this.internalState = new ShellInternalState(serviceProvider, messageBus || new EventEmitter()); + this.internalState.isInteractive = true; this.shellEvaluator = new ShellEvaluator(this.internalState); this.internalState.setCtx(this.interpreterEnvironment.getContextObject()); this.interpreter = new Interpreter(this.interpreterEnvironment); diff --git a/packages/cli-repl/src/cli-repl.spec.ts b/packages/cli-repl/src/cli-repl.spec.ts index 0ef948768d..df9ac75ffe 100644 --- a/packages/cli-repl/src/cli-repl.spec.ts +++ b/packages/cli-repl/src/cli-repl.spec.ts @@ -810,6 +810,61 @@ describe('CliRepl', () => { 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'); + cliRepl = new CliRepl(cliReplOptions); + await startWithExpectedImmediateExit(cliRepl, await testServer.connectionString()); + expect(output).to.match(/isInteractive=false/); + expect(exitCode).to.equal(0); + }); + + 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.shell = true; + cliRepl = new CliRepl(cliReplOptions); + await cliRepl.start(await testServer.connectionString(), {}); + expect(output).to.match(/isInteractive=true/); + expect(exitCode).to.equal(null); + + input.write('exit\n'); + await waitBus(cliRepl.bus, 'mongosh:closed'); + expect(exitCode).to.equal(0); + }); + + it('isInteractive() is false for loaded file without --shell', async() => { + const filename1 = path.resolve(__dirname, '..', 'test', 'fixtures', 'load', 'printisinteractive.js'); + cliReplOptions.shellCliOptions._.push(filename1); + cliRepl = new CliRepl(cliReplOptions); + await startWithExpectedImmediateExit(cliRepl, await testServer.connectionString()); + expect(output).to.match(/isInteractive=false/); + expect(exitCode).to.equal(0); + }); + + 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.shell = true; + cliRepl = new CliRepl(cliReplOptions); + await cliRepl.start(await testServer.connectionString(), {}); + expect(output).to.match(/isInteractive=true/); + expect(exitCode).to.equal(null); + + input.write('exit\n'); + await waitBus(cliRepl.bus, 'mongosh:closed'); + expect(exitCode).to.equal(0); + }); + + it('isInteractive() is true for plain shell', async() => { + cliRepl = new CliRepl(cliReplOptions); + await cliRepl.start(await testServer.connectionString(), {}); + + input.write('print("isInteractive=" + isInteractive())\n'); + await waitEval(cliRepl.bus); + expect(output).to.match(/isInteractive=true/); + }); }); context('with a user-provided prompt', () => { diff --git a/packages/cli-repl/src/cli-repl.ts b/packages/cli-repl/src/cli-repl.ts index a0f81b1b7a..ffba85cd87 100644 --- a/packages/cli-repl/src/cli-repl.ts +++ b/packages/cli-repl/src/cli-repl.ts @@ -168,12 +168,15 @@ class CliRepl { const initialized = await this.mongoshRepl.initialize(initialServiceProvider); const commandLineLoadFiles = this.listCommandLineLoadFiles(); 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 }); await this.loadCommandLineFilesAndEval(commandLineLoadFiles); if (!this.cliOptions.shell) { await this.exit(0); return; } + } else { + this.mongoshRepl.setIsInteractive(true); } await this.loadRcFiles(); this.bus.emit('mongosh:start-mongosh-repl', { version }); diff --git a/packages/cli-repl/src/mongosh-repl.ts b/packages/cli-repl/src/mongosh-repl.ts index ebd9e0d564..2f7995d039 100644 --- a/packages/cli-repl/src/mongosh-repl.ts +++ b/packages/cli-repl/src/mongosh-repl.ts @@ -123,6 +123,10 @@ class MongoshNodeRepl implements EvaluationListener { this._runtimeState = null; } + setIsInteractive(value: boolean): void { + this.runtimeState().internalState.isInteractive = value; + } + async initialize(serviceProvider: ServiceProvider): Promise { const internalState = new ShellInternalState(serviceProvider, this.bus, this.shellCliOptions); const shellEvaluator = new ShellEvaluator(internalState); diff --git a/packages/cli-repl/test/fixtures/load/printisinteractive.js b/packages/cli-repl/test/fixtures/load/printisinteractive.js new file mode 100644 index 0000000000..2f95748cfa --- /dev/null +++ b/packages/cli-repl/test/fixtures/load/printisinteractive.js @@ -0,0 +1,2 @@ +/* eslint-disable */ +print(`isInteractive=${isInteractive()}`) diff --git a/packages/i18n/src/locales/en_US.ts b/packages/i18n/src/locales/en_US.ts index 4106b5d79b..96cc3d8fb5 100644 --- a/packages/i18n/src/locales/en_US.ts +++ b/packages/i18n/src/locales/en_US.ts @@ -154,6 +154,9 @@ const translations: Catalog = { }, printjson: { description: 'Alias for print()' + }, + isInteractive: { + description: 'Returns whether the shell will enter or has entered interactive mode' } } }, diff --git a/packages/shell-api/src/shell-api.spec.ts b/packages/shell-api/src/shell-api.spec.ts index 67823e3e2d..312a2c475f 100644 --- a/packages/shell-api/src/shell-api.spec.ts +++ b/packages/shell-api/src/shell-api.spec.ts @@ -519,6 +519,13 @@ describe('ShellApi', () => { expect(version).to.equal(expected); }); }); + describe('isInteractive', () => { + it('returns a boolean', () => { + expect(internalState.context.isInteractive()).to.equal(false); + internalState.isInteractive = true; + expect(internalState.context.isInteractive()).to.equal(true); + }); + }); for (const cmd of ['exit', 'quit']) { // eslint-disable-next-line no-loop-func describe(cmd, () => { diff --git a/packages/shell-api/src/shell-api.ts b/packages/shell-api/src/shell-api.ts index ddfe4e96c5..a1c29bd6b4 100644 --- a/packages/shell-api/src/shell-api.ts +++ b/packages/shell-api/src/shell-api.ts @@ -286,4 +286,8 @@ export default class ShellApi extends ShellApiClass { const { evaluationListener } = this._internalState; await evaluationListener.onClearCommand?.(); } + + isInteractive(): boolean { + return this._internalState.isInteractive; + } } diff --git a/packages/shell-api/src/shell-internal-state.ts b/packages/shell-api/src/shell-internal-state.ts index dd12164230..65faa2c6e9 100644 --- a/packages/shell-api/src/shell-internal-state.ts +++ b/packages/shell-api/src/shell-internal-state.ts @@ -106,6 +106,7 @@ export default class ShellInternalState { public evaluationListener: EvaluationListener; public mongocryptdSpawnPath: string | null; public batchSizeFromDBQuery: number | undefined = undefined; + public isInteractive = false; public readonly interrupted = new InterruptFlag(); public resumeMongosAfterInterrupt: Array<{