diff --git a/package-lock.json b/package-lock.json index edb112180..bf89b241d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9457,6 +9457,130 @@ "dev": true, "license": "MIT" }, + "node_modules/@testing-library/react": { + "version": "12.1.5", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-12.1.5.tgz", + "integrity": "sha512-OfTXCJUFgjd/digLUuPxa0+/3ZxsQmE7ub9kcbW/wi96Bh3o/p5vrETcBGfP17NWPGqeYYl5LTRpwyGoMC4ysg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5", + "@testing-library/dom": "^8.0.0", + "@types/react-dom": "<18.0.0" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "react": "<18.0.0", + "react-dom": "<18.0.0" + } + }, + "node_modules/@testing-library/react/node_modules/@testing-library/dom": { + "version": "8.20.1", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.20.1.tgz", + "integrity": "sha512-/DiOQ5xBxgdYRC8LNk7U+RWat0S3qRLeIw3ZIkMQ9kkVlRmwD/Eg8k8CqIpD6GW7u20JIUOfMKbxtiLutpjQ4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.1.3", + "chalk": "^4.1.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@testing-library/react/node_modules/@types/react": { + "version": "17.0.83", + "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.83.tgz", + "integrity": "sha512-l0m4ArKJvmFtR4e8UmKrj1pB4tUgOhJITf+mADyF/p69Ts1YAR/E+G9XEM0mHXKVRa1dQNHseyyDNzeuAXfXQw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@types/prop-types": "*", + "@types/scheduler": "^0.16", + "csstype": "^3.0.2" + } + }, + "node_modules/@testing-library/react/node_modules/@types/react-dom": { + "version": "17.0.26", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.26.tgz", + "integrity": "sha512-Z+2VcYXJwOqQ79HreLU/1fyQ88eXSSFh6I3JdrEHQIfYSI0kCQpTGvOrbE6jFGGYXKsHuwY9tBa/w5Uo6KzrEg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^17.0.0" + } + }, + "node_modules/@testing-library/react/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@testing-library/react/node_modules/aria-query": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz", + "integrity": "sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "deep-equal": "^2.0.5" + } + }, + "node_modules/@testing-library/react/node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@testing-library/react/node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@testing-library/user-event": { + "version": "13.5.0", + "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-13.5.0.tgz", + "integrity": "sha512-5Kwtbo3Y/NowpkbRuSepbyMFkZmHgD+vPzYB/RJ4oxt5Gj/avFFBYjhw27cqSVPVw/3a67NK1PbiIr9k4Gwmdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + }, + "peerDependencies": { + "@testing-library/dom": ">=7.21.4" + } + }, "node_modules/@tootallnate/once": { "version": "2.0.0", "license": "MIT", @@ -28915,6 +29039,9 @@ "@mongodb-js/prettier-config-devtools": "^1.0.1", "@mongodb-js/tsconfig-mongosh": "^1.0.0", "@pmmmwh/react-refresh-webpack-plugin": "^0.5.8", + "@testing-library/dom": "^8.20.1", + "@testing-library/react": "^12.1.5", + "@testing-library/user-event": "^13.5.0", "@types/numeral": "^2.0.2", "@types/react": "^16.9.17", "@types/react-dom": "^18.0.8", @@ -28935,6 +29062,7 @@ "karma-mocha-reporter": "^2.2.5", "karma-typescript": "^5.5.4", "karma-webpack": "^5.0.0", + "lodash": "^4.17.21", "mongodb": "^6.12.0", "path-browserify": "^1.0.1", "prettier": "^2.8.8", @@ -28961,11 +29089,54 @@ "react-dom": "^17.0.2" } }, + "packages/browser-repl/node_modules/@testing-library/dom": { + "version": "8.20.1", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.20.1.tgz", + "integrity": "sha512-/DiOQ5xBxgdYRC8LNk7U+RWat0S3qRLeIw3ZIkMQ9kkVlRmwD/Eg8k8CqIpD6GW7u20JIUOfMKbxtiLutpjQ4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.1.3", + "chalk": "^4.1.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=12" + } + }, "packages/browser-repl/node_modules/@types/sinon": { "version": "7.5.2", "dev": true, "license": "MIT" }, + "packages/browser-repl/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "packages/browser-repl/node_modules/aria-query": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz", + "integrity": "sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "deep-equal": "^2.0.5" + } + }, "packages/browser-repl/node_modules/buffer": { "version": "6.0.3", "dev": true, @@ -29008,6 +29179,28 @@ ], "license": "BSD-3-Clause" }, + "packages/browser-repl/node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "packages/browser-repl/node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true, + "license": "MIT" + }, "packages/browser-runtime-core": { "name": "@mongosh/browser-runtime-core", "version": "0.0.0-dev.0", diff --git a/packages/browser-repl/README.md b/packages/browser-repl/README.md index 36799ad13..0203ef7c8 100644 --- a/packages/browser-repl/README.md +++ b/packages/browser-repl/README.md @@ -31,14 +31,20 @@ const runtime = new IframeRuntime(serviceProvider); Shell is a React component with the following properties: - `runtime: Runtime`: The runtime used to evaluate code. -- `onOutputChanged?: (output: readonly ShellOutputEntry[]) => void`: A function called each time the output changes with an array of `ShellOutputEntryes`. -- `onHistoryChanged?: (history: readonly string[]) => void`: A function called each time the history changes with an array of history entries ordered from the most recent to the oldest entry. +- `onOutputChanged?: (output: ShellOutputEntry[]) => void`: A function called each time the output changes with an array of `ShellOutputEntries`. +- `onHistoryChanged?: (history: string[]) => void`: A function called each time the history changes with an array of history entries ordered from the most recent to the oldest entry. +- `onEditorChanged?: (editor: EditorRef | null) => void`: A function called each time the editor ref changes. Can be used to call editor methods. +- `onOperationStarted?: () => void`: A function called when an operation has begun. +- `onOperationEnd?: () => void`: A function called when an operation has completed (both error and success). - `redactInfo?: boolean`: If set, the shell will omit or redact entries containing sensitive info from history. Defaults to `false`. - `maxOutputLength?: number`: The maxiumum number of lines to keep in the output. Defaults to `1000`. - `maxHistoryLength?: number`: The maxiumum number of lines to keep in the history. Defaults to `1000`. -- `initialOutput?: readonly ShellOutputEntry[]`: An array of entries to be displayed in the output area. Can be used to restore the output between sessions, or to setup a greeting message. **Note**: new entries will not be appended to the array. -- `initialHistory?: readonly string[]`: An array of history entries to prepopulate the history. +- `initialEvaluate?: string|string[]`: A set of input strings to evaluate right after shell is mounted. +- `initialText?: string`: The initial text for the input field. +- `output?: ShellOutputEntry[]`: An array of entries to be displayed in the output area. Can be used to restore the output between sessions, or to setup a greeting message. **Note**: new entries will not be appended to the array. +- `history?: readonly string[]`: An array of history entries to prepopulate the history. Can be used to restore the history between sessions. Entries must be ordered from the most recent to the oldest. Note: new entries will not be appended to the array. +- `isOperationInProgress?: boolean`: Can be used to restore the value between sessions. ### `ShellOutputEntry` diff --git a/packages/browser-repl/package.json b/packages/browser-repl/package.json index a29851d7f..b4dfeeed0 100644 --- a/packages/browser-repl/package.json +++ b/packages/browser-repl/package.json @@ -35,7 +35,8 @@ "depcheck": "depcheck", "compile": "tsc -p tsconfig.json", "prettier": "prettier", - "reformat": "npm run prettier -- --write . && npm run eslint --fix" + "reformat": "npm run prettier -- --write . && npm run eslint --fix", + "sync-to-compass": "node scripts/sync-to-compass.js" }, "config": { "unsafe-perm": true @@ -76,6 +77,9 @@ "@mongodb-js/prettier-config-devtools": "^1.0.1", "@mongodb-js/tsconfig-mongosh": "^1.0.0", "@pmmmwh/react-refresh-webpack-plugin": "^0.5.8", + "@testing-library/dom": "^8.20.1", + "@testing-library/react": "^12.1.5", + "@testing-library/user-event": "^13.5.0", "@types/numeral": "^2.0.2", "@types/react": "^16.9.17", "@types/react-dom": "^18.0.8", @@ -96,6 +100,7 @@ "karma-mocha-reporter": "^2.2.5", "karma-typescript": "^5.5.4", "karma-webpack": "^5.0.0", + "lodash": "^4.17.21", "path-browserify": "^1.0.1", "prettier": "^2.8.8", "prop-types": "^15.7.2", diff --git a/packages/browser-repl/scripts/sync-to-compass.js b/packages/browser-repl/scripts/sync-to-compass.js new file mode 100644 index 000000000..da2c83143 --- /dev/null +++ b/packages/browser-repl/scripts/sync-to-compass.js @@ -0,0 +1,74 @@ +/* eslint-disable no-console */ +'use strict'; +const fs = require('fs'); +const path = require('path'); +const child_process = require('child_process'); +const { debounce } = require('lodash'); + +if (!process.env.COMPASS_HOME) { + throw new Error('Missing required environment variable $COMPASS_HOME.'); +} + +const packageDir = path.resolve(__dirname, '..'); +const srcDir = path.resolve(__dirname, '..', 'src'); +const libDir = path.resolve(__dirname, '..', 'lib'); + +const destDir = path.dirname( + child_process.execFileSync( + 'node', + ['-p', "require.resolve('@mongosh/browser-repl')"], + { cwd: process.env.COMPASS_HOME, encoding: 'utf-8' } + ) +); + +console.log({ packageDir, srcDir, libDir, destDir }); + +const compileAndCopy = debounce( + function () { + try { + child_process.execFileSync('npm', ['run', 'compile'], { + cwd: packageDir, + encoding: 'utf-8', + }); + } catch (err) { + if (err.code) { + // Spawning child process failed + console.error(err.code); + } else { + // Child was spawned but exited with non-zero exit code + // Error contains any stdout and stderr from the child + const { stdout, stderr } = err; + + console.log(stdout); + console.error(stderr); + } + } + fs.cpSync(libDir, destDir, { recursive: true }); + console.log('done.'); + }, + 1_000, + { + leading: true, + trailing: true, + } +); + +const srcWatcher = fs.watch( + srcDir, + { recursive: true }, + function (eventType, filename) { + console.log(eventType, filename); + compileAndCopy(); + } +); + +function cleanup() { + srcWatcher.close(); +} + +for (const evt of ['SIGINT', 'SIGTERM']) { + process.on(evt, cleanup); +} + +// do an initial copy on startup +compileAndCopy(); diff --git a/packages/browser-repl/src/components/password-prompt.tsx b/packages/browser-repl/src/components/password-prompt.tsx index 88222f905..d172428b5 100644 --- a/packages/browser-repl/src/components/password-prompt.tsx +++ b/packages/browser-repl/src/components/password-prompt.tsx @@ -39,10 +39,11 @@ export class PasswordPrompt extends Component { render(): JSX.Element { return ( -