diff --git a/config/config.extensions.js b/config/config.extensions.js new file mode 100644 index 000000000..df926579f --- /dev/null +++ b/config/config.extensions.js @@ -0,0 +1,6 @@ + +module.exports = config => { + config.extensions = {}; + config.extensions.InteractiveComputeHost = process.env.DEEPFORGE_INTERACTIVE_COMPUTE_HOST; + return config; +}; diff --git a/config/config.webgme.js b/config/config.webgme.js index 50eae288b..07aa81844 100644 --- a/config/config.webgme.js +++ b/config/config.webgme.js @@ -51,6 +51,11 @@ config.rest.components['SciServerAuth'] = { mount: 'routers/SciServerAuth', options: {} }; +config.rest.components['InteractiveCompute'] = { + src: __dirname + '/../src/routers/InteractiveCompute/InteractiveCompute.js', + mount: 'routers/InteractiveCompute', + options: {} +}; // Visualizer descriptors config.visualization.visualizerDescriptors.push(__dirname + '/../src/visualizers/Visualizers.json'); diff --git a/config/index.js b/config/index.js index 4586eaef9..d7842c13a 100644 --- a/config/index.js +++ b/config/index.js @@ -11,5 +11,6 @@ var env = process.env.NODE_ENV || 'default', config = require(configFilename), validateConfig = require('webgme/config/validator'); -validateConfig(configFilename); +validateConfig(config); +config = require(__dirname + '/config.extensions.js')(config); module.exports = config; diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index f90a5e1ec..de6078371 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -9,11 +9,13 @@ services: environment: - "MONGO_URI=mongodb://mongo:27017/deepforge" - "DEEPFORGE_HOST=https://dev.deepforge.org" + - "DEEPFORGE_INTERACTIVE_COMPUTE_HOST=https://dev-compute.deepforge.org" - "DEEPFORGE_PUBLIC_KEY=/token_keys/public_key" - "DEEPFORGE_PRIVATE_KEY=/token_keys/private_key" image: deepforge/kitchen-sink:latest ports: - "8888:8888" + - "8889:8889" volumes: - "$HOME/.deepforge/blob:/data/blob" - "${TOKEN_KEYS_DIR}:/token_keys" diff --git a/package-lock.json b/package-lock.json index f3dd19086..48a62ccb4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -986,11 +986,6 @@ } } }, - "acorn-walk": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.1.1.tgz", - "integrity": "sha512-wdlPY2tm/9XBr7QkKlq0WQVgiuGTX6YWPyRyBviSoScBuLfTVQhvwg6wJ369GJ/1nPfTLMfnrFIfjqVg6d+jQQ==" - }, "address": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/address/-/address-1.0.3.tgz", @@ -1270,9 +1265,9 @@ "integrity": "sha1-GdOGodntxufByF04iu28xW0zYC0=" }, "async-limiter": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", - "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==" + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", + "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==" }, "asynckit": { "version": "0.4.0", @@ -1886,11 +1881,6 @@ "node-releases": "^1.1.40" } }, - "bson": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/bson/-/bson-1.0.9.tgz", - "integrity": "sha512-IQX9/h7WdMBIW/q/++tGd+emQr0XMdeZ6icnT/74Xk9fnabWn+gZgpE+9V+gujL3hhJOoNrnDVY7tWdzc7NUTg==" - }, "buffer": { "version": "4.9.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", @@ -1916,11 +1906,6 @@ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.0.0.tgz", "integrity": "sha512-83apNb8KK0Se60UE1+4Ukbe3HbfELJ6UlI4ldtOGs7So4KD26orJM8hIY9lxdzP+UpItH1Yh/Y8GUvNFWFFRxA==" }, - "buffer-shims": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/buffer-shims/-/buffer-shims-1.0.0.tgz", - "integrity": "sha1-mXjOMXOIxkmth5MCjDR37wRKi1E=" - }, "buffer-xor": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", @@ -2026,9 +2011,9 @@ } }, "chance": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/chance/-/chance-1.0.12.tgz", - "integrity": "sha512-W99uMuboG5CT1iToDmizEH6yQYqICzZnrSRbbXPuJErzFWLPaoiEDvwnKbESjDo/8st1n3pyh70VBMmfqPmf+Q==" + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chance/-/chance-1.1.4.tgz", + "integrity": "sha512-pXPDSu3knKlb6H7ahQfpq//J9mSOxYK8SMtp8MV/nRJh8aLRDIl0ipLH8At8+nVogVwtvPZzyIzY/EbcY/cLuQ==" }, "change-case": { "version": "2.3.1", @@ -2598,8 +2583,8 @@ "version": "github:deepforge-dev/user-management-page#4872b8fd124b31381ee74ce32dabcdf137490be5", "from": "github:deepforge-dev/user-management-page", "requires": { - "body-parser": "^1.18.3", - "ejs": "^2.4.2", + "body-parser": "^1.19.0", + "ejs": "^2.7.4", "express": "4.16.3" }, "dependencies": { @@ -3269,6 +3254,16 @@ "requires": { "ms": "2.0.0" } + }, + "ws": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", + "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", + "requires": { + "async-limiter": "~1.0.0", + "safe-buffer": "~5.1.0", + "ultron": "~1.1.0" + } } } }, @@ -3297,6 +3292,16 @@ "requires": { "ms": "2.0.0" } + }, + "ws": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", + "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", + "requires": { + "async-limiter": "~1.0.0", + "safe-buffer": "~5.1.0", + "ultron": "~1.1.0" + } } } }, @@ -3982,558 +3987,78 @@ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, "fsevents": { - "version": "1.2.11", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.11.tgz", - "integrity": "sha512-+ux3lx6peh0BpvY0JebGyZoiR4D+oYzdPZMKJwkZ+sFkNJzpL7tXc/wehS49gUAxg3tmMHPHZkA8JU2rhhgDHw==", + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", + "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", "optional": true, "requires": { "bindings": "^1.5.0", - "nan": "^2.12.1", - "node-pre-gyp": "*" - }, - "dependencies": { - "abbrev": { - "version": "1.1.1", - "bundled": true, - "optional": true - }, - "ansi-regex": { - "version": "2.1.1", - "bundled": true, - "optional": true - }, - "aproba": { - "version": "1.2.0", - "bundled": true, - "optional": true - }, - "are-we-there-yet": { - "version": "1.1.5", - "bundled": true, - "optional": true, - "requires": { - "delegates": "^1.0.0", - "readable-stream": "^2.0.6" - } - }, - "balanced-match": { - "version": "1.0.0", - "bundled": true, - "optional": true - }, - "brace-expansion": { - "version": "1.1.11", - "bundled": true, - "optional": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "chownr": { - "version": "1.1.3", - "bundled": true, - "optional": true - }, - "code-point-at": { - "version": "1.1.0", - "bundled": true, - "optional": true - }, - "concat-map": { - "version": "0.0.1", - "bundled": true, - "optional": true - }, - "console-control-strings": { - "version": "1.1.0", - "bundled": true, - "optional": true - }, - "core-util-is": { - "version": "1.0.2", - "bundled": true, - "optional": true - }, - "debug": { - "version": "3.2.6", - "bundled": true, - "optional": true, - "requires": { - "ms": "^2.1.1" - } - }, - "deep-extend": { - "version": "0.6.0", - "bundled": true, - "optional": true - }, - "delegates": { - "version": "1.0.0", - "bundled": true, - "optional": true - }, - "detect-libc": { - "version": "1.0.3", - "bundled": true, - "optional": true - }, - "fs-minipass": { - "version": "1.2.7", - "bundled": true, - "optional": true, - "requires": { - "minipass": "^2.6.0" - } - }, - "fs.realpath": { - "version": "1.0.0", - "bundled": true, - "optional": true - }, - "gauge": { - "version": "2.7.4", - "bundled": true, - "optional": true, - "requires": { - "aproba": "^1.0.3", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.0", - "object-assign": "^4.1.0", - "signal-exit": "^3.0.0", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wide-align": "^1.1.0" - } - }, - "glob": { - "version": "7.1.6", - "bundled": true, - "optional": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "has-unicode": { - "version": "2.0.1", - "bundled": true, - "optional": true - }, - "iconv-lite": { - "version": "0.4.24", - "bundled": true, - "optional": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "ignore-walk": { - "version": "3.0.3", - "bundled": true, - "optional": true, - "requires": { - "minimatch": "^3.0.4" - } - }, - "inflight": { - "version": "1.0.6", - "bundled": true, - "optional": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "bundled": true, - "optional": true - }, - "ini": { - "version": "1.3.5", - "bundled": true, - "optional": true - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "bundled": true, - "optional": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "isarray": { - "version": "1.0.0", - "bundled": true, - "optional": true - }, - "minimatch": { - "version": "3.0.4", - "bundled": true, - "optional": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "0.0.8", - "bundled": true, - "optional": true - }, - "minipass": { - "version": "2.9.0", - "bundled": true, - "optional": true, - "requires": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" - } - }, - "minizlib": { - "version": "1.3.3", - "bundled": true, - "optional": true, - "requires": { - "minipass": "^2.9.0" - } - }, - "mkdirp": { - "version": "0.5.1", - "bundled": true, - "optional": true, - "requires": { - "minimist": "0.0.8" - } - }, - "ms": { - "version": "2.1.2", - "bundled": true, - "optional": true - }, - "needle": { - "version": "2.4.0", - "bundled": true, - "optional": true, - "requires": { - "debug": "^3.2.6", - "iconv-lite": "^0.4.4", - "sax": "^1.2.4" - } - }, - "node-pre-gyp": { - "version": "0.14.0", - "bundled": true, - "optional": true, - "requires": { - "detect-libc": "^1.0.2", - "mkdirp": "^0.5.1", - "needle": "^2.2.1", - "nopt": "^4.0.1", - "npm-packlist": "^1.1.6", - "npmlog": "^4.0.2", - "rc": "^1.2.7", - "rimraf": "^2.6.1", - "semver": "^5.3.0", - "tar": "^4.4.2" - } - }, - "nopt": { - "version": "4.0.1", - "bundled": true, - "optional": true, - "requires": { - "abbrev": "1", - "osenv": "^0.1.4" - } - }, - "npm-bundled": { - "version": "1.1.1", - "bundled": true, - "optional": true, - "requires": { - "npm-normalize-package-bin": "^1.0.1" - } - }, - "npm-normalize-package-bin": { - "version": "1.0.1", - "bundled": true, - "optional": true - }, - "npm-packlist": { - "version": "1.4.7", - "bundled": true, - "optional": true, - "requires": { - "ignore-walk": "^3.0.1", - "npm-bundled": "^1.0.1" - } - }, - "npmlog": { - "version": "4.1.2", - "bundled": true, - "optional": true, - "requires": { - "are-we-there-yet": "~1.1.2", - "console-control-strings": "~1.1.0", - "gauge": "~2.7.3", - "set-blocking": "~2.0.0" - } - }, - "number-is-nan": { - "version": "1.0.1", - "bundled": true, - "optional": true - }, - "object-assign": { - "version": "4.1.1", - "bundled": true, - "optional": true - }, - "once": { - "version": "1.4.0", - "bundled": true, - "optional": true, - "requires": { - "wrappy": "1" - } - }, - "os-homedir": { - "version": "1.0.2", - "bundled": true, - "optional": true - }, - "os-tmpdir": { - "version": "1.0.2", - "bundled": true, - "optional": true - }, - "osenv": { - "version": "0.1.5", - "bundled": true, - "optional": true, - "requires": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.0" - } - }, - "path-is-absolute": { - "version": "1.0.1", - "bundled": true, - "optional": true - }, - "process-nextick-args": { - "version": "2.0.1", - "bundled": true, - "optional": true - }, - "rc": { - "version": "1.2.8", - "bundled": true, - "optional": true, - "requires": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "bundled": true, - "optional": true - } - } - }, - "readable-stream": { - "version": "2.3.6", - "bundled": true, - "optional": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "rimraf": { - "version": "2.7.1", - "bundled": true, - "optional": true, - "requires": { - "glob": "^7.1.3" - } - }, - "safe-buffer": { - "version": "5.1.2", - "bundled": true, - "optional": true - }, - "safer-buffer": { - "version": "2.1.2", - "bundled": true, - "optional": true - }, - "sax": { - "version": "1.2.4", - "bundled": true, - "optional": true - }, - "semver": { - "version": "5.7.1", - "bundled": true, - "optional": true - }, - "set-blocking": { - "version": "2.0.0", - "bundled": true, - "optional": true - }, - "signal-exit": { - "version": "3.0.2", - "bundled": true, - "optional": true - }, - "string-width": { - "version": "1.0.2", - "bundled": true, - "optional": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - }, - "string_decoder": { - "version": "1.1.1", - "bundled": true, - "optional": true, - "requires": { - "safe-buffer": "~5.1.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "bundled": true, - "optional": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "strip-json-comments": { - "version": "2.0.1", - "bundled": true, - "optional": true - }, - "tar": { - "version": "4.4.13", - "bundled": true, - "optional": true, - "requires": { - "chownr": "^1.1.1", - "fs-minipass": "^1.2.5", - "minipass": "^2.8.6", - "minizlib": "^1.2.1", - "mkdirp": "^0.5.0", - "safe-buffer": "^5.1.2", - "yallist": "^3.0.3" - } - }, - "util-deprecate": { - "version": "1.0.2", - "bundled": true, - "optional": true - }, - "wide-align": { - "version": "1.1.3", - "bundled": true, - "optional": true, - "requires": { - "string-width": "^1.0.2 || 2" - } - }, - "wrappy": { - "version": "1.0.2", - "bundled": true, - "optional": true - }, - "yallist": { - "version": "3.1.1", - "bundled": true, - "optional": true - } - } - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" - }, - "get-stdin": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", - "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=", - "dev": true - }, - "get-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", - "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", - "dev": true - }, - "getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", - "dev": true, - "requires": { - "assert-plus": "^1.0.0" - } - }, - "glob": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", - "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "glob-base": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz", - "integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=", - "requires": { - "glob-parent": "^2.0.0", - "is-glob": "^2.0.0" - } - }, - "glob-parent": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", - "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", - "requires": { - "is-glob": "^2.0.0" - } - }, - "global-dirs": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-0.1.1.tgz", - "integrity": "sha1-sxnA3UYH81PzvpzKTHL8FIxJ9EU=", - "dev": true, - "requires": { - "ini": "^1.3.4" + "nan": "^2.12.1" + } + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "get-stdin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", + "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=", + "dev": true + }, + "get-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", + "dev": true + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "glob": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", + "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-base": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz", + "integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=", + "requires": { + "glob-parent": "^2.0.0", + "is-glob": "^2.0.0" + } + }, + "glob-parent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", + "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", + "requires": { + "is-glob": "^2.0.0" + } + }, + "global-dirs": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-0.1.1.tgz", + "integrity": "sha1-sxnA3UYH81PzvpzKTHL8FIxJ9EU=", + "dev": true, + "requires": { + "ini": "^1.3.4" }, "dependencies": { "ini": { @@ -5529,14 +5054,6 @@ "astw": "^2.0.0" } }, - "linkify-it": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-2.2.0.tgz", - "integrity": "sha512-GnAl/knGn+i1U/wjBz3akz2stz+HrHLsxMwHQGofCDfPvlf+gDKN58UtfmUquTY4/MXeE2x7k19KQmeoZi94Iw==", - "requires": { - "uc.micro": "^1.0.1" - } - }, "lodash": { "version": "4.17.4", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", @@ -5774,23 +5291,6 @@ "pify": "^3.0.0" } }, - "markdown-it": { - "version": "8.4.2", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-8.4.2.tgz", - "integrity": "sha512-GcRz3AWTqSUphY3vsUqQSFMbgR38a4Lh3GWlHRh/7MRwz8mcu9n2IO7HOh+bXHrR9kOPDl5RNCaEsrneb+xhHQ==", - "requires": { - "argparse": "^1.0.7", - "entities": "~1.1.1", - "linkify-it": "^2.0.0", - "mdurl": "^1.0.1", - "uc.micro": "^1.0.5" - } - }, - "markdown-it-anchor": { - "version": "5.2.7", - "resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-5.2.7.tgz", - "integrity": "sha512-REFmIaSS6szaD1bye80DMbp7ePwsPNvLTR5HunsUcZ0SG0rWJQ+Pz24R4UlTKtjKBPhxo0v0tOBDYjZQQknW8Q==" - }, "marked": { "version": "0.3.17", "resolved": "https://registry.npmjs.org/marked/-/marked-0.3.17.tgz", @@ -5805,11 +5305,6 @@ "inherits": "^2.0.1" } }, - "mdurl": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", - "integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=" - }, "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -6175,15 +5670,6 @@ } } }, - "mongodb-core": { - "version": "2.1.19", - "resolved": "https://registry.npmjs.org/mongodb-core/-/mongodb-core-2.1.19.tgz", - "integrity": "sha512-Jt4AtWUkpuW03kRdYGxga4O65O1UHlFfvvInslEfLlGi+zDMxbBe3J2NVmN9qPJ957Mn6Iz0UpMtV80cmxCVxw==", - "requires": { - "bson": "~1.0.4", - "require_optional": "~1.0.0" - } - }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -6220,9 +5706,9 @@ "dev": true }, "nan": { - "version": "2.14.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", - "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==", + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.1.tgz", + "integrity": "sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw==", "optional": true }, "ncp": { @@ -10526,32 +10012,11 @@ "resolve-from": "^1.0.0" } }, - "require_optional": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/require_optional/-/require_optional-1.0.1.tgz", - "integrity": "sha512-qhM/y57enGWHAe3v/NcwML6a3/vfESLe/sGM2dII+gEO0BpKRUkWZow/tyloNqJyN6kXSl3RyyM8Ll5D/sJP8g==", - "requires": { - "resolve-from": "^2.0.0", - "semver": "^5.1.0" - }, - "dependencies": { - "resolve-from": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-2.0.0.tgz", - "integrity": "sha1-lICrIOlP+h2egKgEx+oUdhGWa1c=" - } - } - }, "requirejs": { "version": "2.1.20", "resolved": "https://registry.npmjs.org/requirejs/-/requirejs-2.1.20.tgz", "integrity": "sha1-EUgiyRfsh5NFCy2qoeubvxEB6TE=" }, - "requirejs-text": { - "version": "2.0.15", - "resolved": "https://registry.npmjs.org/requirejs-text/-/requirejs-text-2.0.15.tgz", - "integrity": "sha1-ExOHM2E/xEV7fhJH6Mt1HfeqVCk=" - }, "requizzle": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.1.tgz", @@ -11453,11 +10918,6 @@ "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" }, - "uc.micro": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", - "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==" - }, "uid2": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/uid2/-/uid2-0.0.3.tgz", @@ -11782,8 +11242,7 @@ "q": "1.5.1", "require-uncached": "1.0.3", "requirejs": "2.3.5", - "webgme-engine": "github:webgme/webgme-engine#master", - "webgme-user-management-page": "0.4.3" + "webgme-user-management-page": "^0.5.0" }, "dependencies": { "acorn": { @@ -12076,6 +11535,23 @@ "debug": "~3.1.0", "engine.io-parser": "~2.1.0", "ws": "~3.3.1" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "ws": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", + "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", + "requires": { + "async-limiter": "~1.0.0", + "safe-buffer": "~5.1.0", + "ultron": "~1.1.0" + } + } } }, "engine.io-client": { @@ -12094,6 +11570,23 @@ "ws": "~3.3.1", "xmlhttprequest-ssl": "~1.5.4", "yeast": "0.1.2" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "ws": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", + "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", + "requires": { + "async-limiter": "~1.0.0", + "safe-buffer": "~5.1.0", + "ultron": "~1.1.0" + } + } } }, "es6-promise": { @@ -12323,6 +11816,19 @@ "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" }, + "mime-db": { + "version": "1.44.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", + "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==" + }, + "mime-types": { + "version": "2.1.27", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", + "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", + "requires": { + "mime-db": "1.44.0" + } + }, "minimist": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", @@ -12572,7 +12078,7 @@ }, "webgme-engine": { "version": "github:webgme/webgme-engine#319eb95678cd3f23f1087f5912fc24ef7b8b73a0", - "from": "github:webgme/webgme-engine#master", + "from": "webgme-engine@github:webgme/webgme-engine#319eb95678cd3f23f1087f5912fc24ef7b8b73a0", "requires": { "adm-zip": "0.4.11", "agentkeepalive": "3.4.1", @@ -12618,6 +12124,108 @@ "winston": "2.4.3" } }, + "webgme-user-management-page": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/webgme-user-management-page/-/webgme-user-management-page-0.5.0.tgz", + "integrity": "sha512-sk/sYFTiVf5ntGoNi/ZdZQsufG5UasnZCjThZW0IsW8WeuD0Njak6gVQwMLOid5XX2nLNpAmjGKSrowF0YyoLw==", + "requires": { + "body-parser": "^1.19.0", + "ejs": "^2.7.4", + "express": "4.16.3" + }, + "dependencies": { + "body-parser": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", + "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", + "requires": { + "bytes": "3.1.0", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.2", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "on-finished": "~2.3.0", + "qs": "6.7.0", + "raw-body": "2.4.0", + "type-is": "~1.6.17" + } + }, + "bytes": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "ejs": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-2.7.4.tgz", + "integrity": "sha512-7vmuyh5+kuUyJKePhQfRQBhXV5Ce+RnaeeQArKu1EAMpL3WbgMt5WG6uQZpEVvYSSsxMXRKOewtDk9RaTKXRlA==" + }, + "http-errors": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", + "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + } + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "qs": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", + "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" + }, + "raw-body": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", + "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", + "requires": { + "bytes": "3.1.0", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + } + }, + "setprototypeof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" + }, + "type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + } + } + } + }, "webgme-webhook-manager": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/webgme-webhook-manager/-/webgme-webhook-manager-0.1.1.tgz", @@ -14849,6 +14457,11 @@ "xmlbuilder": "4.2.1" } }, + "chance": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/chance/-/chance-1.0.12.tgz", + "integrity": "sha512-W99uMuboG5CT1iToDmizEH6yQYqICzZnrSRbbXPuJErzFWLPaoiEDvwnKbESjDo/8st1n3pyh70VBMmfqPmf+Q==" + }, "commander": { "version": "2.11.0", "resolved": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz", @@ -14892,238 +14505,11 @@ "resolved": "https://registry.npmjs.org/webgme-ot/-/webgme-ot-0.0.16.tgz", "integrity": "sha512-Aict9Ka1tDDXZ9mZ9BX/4F3AV/KVf1qSoxK0UHtfxM1sPuGr3a4nXAhnccl/3jbHEjSHNphn71lmfff6y3+HmA==" }, - "webgme-rust-components": { - "version": "github:webgme/webgme-rust-components#ad446234b6c02fd722e7e454015857ee523fb172", - "from": "github:webgme/webgme-rust-components" - }, "webgme-simple-nodes": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/webgme-simple-nodes/-/webgme-simple-nodes-2.1.3.tgz", "integrity": "sha512-BpnbOp4OBz/OWwsnEn41XyBcpwkPs6vn/h+hs3KAUNVQm6k3CdyIKPKa9WUYw5SEEmJD00alfGPTbNA+YmSn8A==" }, - "webgme-user-management-page": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/webgme-user-management-page/-/webgme-user-management-page-0.4.3.tgz", - "integrity": "sha512-nNFmHJF0lobNZCdPEF06SJYNTFU6NtfoBmYiMZAsE1ZCFBsyL9qsr/TmiAP/e2/U+dSeRFYXoY7EdPi9Sb7P/A==", - "requires": { - "body-parser": "^1.18.3", - "ejs": "^2.4.2", - "express": "4.16.3" - }, - "dependencies": { - "body-parser": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", - "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", - "requires": { - "bytes": "3.1.0", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "~1.1.2", - "http-errors": "1.7.2", - "iconv-lite": "0.4.24", - "on-finished": "~2.3.0", - "qs": "6.7.0", - "raw-body": "2.4.0", - "type-is": "~1.6.17" - } - }, - "bytes": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", - "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" - }, - "express": { - "version": "4.16.3", - "resolved": "https://registry.npmjs.org/express/-/express-4.16.3.tgz", - "integrity": "sha1-avilAjUNsyRuzEvs9rWjTSL37VM=", - "requires": { - "accepts": "~1.3.5", - "array-flatten": "1.1.1", - "body-parser": "1.18.2", - "content-disposition": "0.5.2", - "content-type": "~1.0.4", - "cookie": "0.3.1", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "~1.1.2", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.1.1", - "fresh": "0.5.2", - "merge-descriptors": "1.0.1", - "methods": "~1.1.2", - "on-finished": "~2.3.0", - "parseurl": "~1.3.2", - "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.3", - "qs": "6.5.1", - "range-parser": "~1.2.0", - "safe-buffer": "5.1.1", - "send": "0.16.2", - "serve-static": "1.13.2", - "setprototypeof": "1.1.0", - "statuses": "~1.4.0", - "type-is": "~1.6.16", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "dependencies": { - "body-parser": { - "version": "1.18.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.2.tgz", - "integrity": "sha1-h2eKGdhLR9hZuDGZvVm84iKxBFQ=", - "requires": { - "bytes": "3.0.0", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "~1.1.1", - "http-errors": "~1.6.2", - "iconv-lite": "0.4.19", - "on-finished": "~2.3.0", - "qs": "6.5.1", - "raw-body": "2.3.2", - "type-is": "~1.6.15" - } - }, - "bytes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" - }, - "http-errors": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", - "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", - "requires": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.0", - "statuses": ">= 1.4.0 < 2" - } - }, - "iconv-lite": { - "version": "0.4.19", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", - "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==" - }, - "qs": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", - "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==" - }, - "raw-body": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.2.tgz", - "integrity": "sha1-vNYMd9Prk83gBQKVw/N5OJvIj4k=", - "requires": { - "bytes": "3.0.0", - "http-errors": "1.6.2", - "iconv-lite": "0.4.19", - "unpipe": "1.0.0" - }, - "dependencies": { - "depd": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", - "integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k=" - }, - "http-errors": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", - "integrity": "sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=", - "requires": { - "depd": "1.1.1", - "inherits": "2.0.3", - "setprototypeof": "1.0.3", - "statuses": ">= 1.3.1 < 2" - } - }, - "setprototypeof": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", - "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=" - } - } - }, - "setprototypeof": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" - }, - "statuses": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", - "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" - } - } - }, - "http-errors": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", - "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", - "requires": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.1", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.0" - } - }, - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "mime-db": { - "version": "1.43.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.43.0.tgz", - "integrity": "sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ==" - }, - "mime-types": { - "version": "2.1.26", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.26.tgz", - "integrity": "sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ==", - "requires": { - "mime-db": "1.43.0" - } - }, - "qs": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", - "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" - }, - "raw-body": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", - "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", - "requires": { - "bytes": "3.1.0", - "http-errors": "1.7.2", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - } - }, - "setprototypeof": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", - "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" - }, - "type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "requires": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - } - } - } - }, "webgme-webhook-manager": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/webgme-webhook-manager/-/webgme-webhook-manager-0.1.0.tgz", @@ -15285,14 +14671,9 @@ "dev": true }, "ws": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", - "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", - "requires": { - "async-limiter": "~1.0.0", - "safe-buffer": "~5.1.0", - "ultron": "~1.1.0" - } + "version": "7.2.5", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.2.5.tgz", + "integrity": "sha512-C34cIU4+DB2vMyAbmEKossWq2ZQDr6QEyuuCzWrM9zfw1sGc0mYiJ0UnG9zzNykt49C2Fi34hvr2vssFQRS6EA==" }, "xml2js": { "version": "0.4.17", diff --git a/package.json b/package.json index 0ecb52f8e..a703e90fe 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "@babel/preset-env": "^7.7.1", "@babel/runtime": "^7.7.2", "aws-sdk": "^2.680.0", + "chance": "^1.1.4", "commander": "^2.20.3", "deepforge-user-management-page": "github:deepforge-dev/user-management-page", "dotenv": "^2.0.0", @@ -53,7 +54,8 @@ "webgme-cli": "^2.7.3", "webgme-easydag": "github:dfst/webgme-easydag", "webgme-fab": "github:dfst/webgme-fab", - "webgme-simple-nodes": "^2.1.3" + "webgme-simple-nodes": "^2.1.3", + "ws": "^7.2.5" }, "devDependencies": { "chai": "^3.0.0", diff --git a/src/common/EventEmitter.js b/src/common/EventEmitter.js new file mode 100644 index 000000000..29d48460e --- /dev/null +++ b/src/common/EventEmitter.js @@ -0,0 +1,26 @@ +/* globals define */ +define([ +], function( +) { + class EventEmitter { + constructor() { + this._handlers = {}; + } + + on(event, fn) { + if (!this._handlers[event]) { + this._handlers[event] = []; + } + this._handlers[event].push(fn); + } + + emit(event) { + const handlers = this._handlers[event] || []; + const args = Array.prototype.slice.call(arguments, 1); + handlers.forEach(fn => fn.apply(null, args)); + } + // TODO: Can I make this an official stream in node? + } + + return EventEmitter; +}); diff --git a/src/common/compute/backends/ComputeClient.js b/src/common/compute/backends/ComputeClient.js index c1abe2300..102873dda 100644 --- a/src/common/compute/backends/ComputeClient.js +++ b/src/common/compute/backends/ComputeClient.js @@ -46,13 +46,18 @@ define([], function() { } } - ComputeClient.prototype.QUEUED = 'queued'; - ComputeClient.prototype.PENDING = 'pending'; - ComputeClient.prototype.RUNNING = 'running'; - ComputeClient.prototype.SUCCESS = 'success'; - ComputeClient.prototype.FAILED = 'failed'; - ComputeClient.prototype.CANCELED = 'canceled'; - ComputeClient.prototype.NOT_FOUND = 'NOT_FOUND'; + const Constants = { + QUEUED: 'queued', + PENDING: 'pending', + RUNNING: 'running', + SUCCESS: 'success', + FAILED: 'failed', + CANCELED: 'canceled', + NOT_FOUND: 'NOT_FOUND', + }; + + Object.assign(ComputeClient, Constants); + Object.assign(ComputeClient.prototype, Constants); function unimplemented(logger, name) { const msg = `${name} is not implemented for current compute backend!`; diff --git a/src/common/compute/interactive/errors.js b/src/common/compute/interactive/errors.js new file mode 100644 index 000000000..4f05764f5 --- /dev/null +++ b/src/common/compute/interactive/errors.js @@ -0,0 +1,16 @@ +/* globals define */ +define([ +], function( +) { + class CommandFailedError extends Error { + constructor(cmd, result) { + const {exitCode, stderr} = result; + const msg = stderr ? + `Command "${cmd}" failed with exit code ${exitCode}:\n${stderr}` : + `Command "${cmd}" failed with exit code ${exitCode}.`; + super(msg); + } + } + + return {CommandFailedError}; +}); diff --git a/src/common/compute/interactive/message.js b/src/common/compute/interactive/message.js new file mode 100644 index 000000000..b98d87d65 --- /dev/null +++ b/src/common/compute/interactive/message.js @@ -0,0 +1,47 @@ +/* globals define */ +(function(root, factory){ + if(typeof define === 'function' && define.amd) { + define([], function(){ + return (root.utils = factory()); + }); + } else if(typeof module === 'object' && module.exports) { + module.exports = (root.utils = factory()); + } +}(this, function() { + const Constants = makeEnum('STDOUT', 'STDERR', 'RUN', 'ADD_ARTIFACT', + 'ADD_FILE', 'ADD_USER_DATA', 'COMPLETE', 'ERROR'); + + function makeEnum() { + const names = Array.prototype.slice.call(arguments); + const obj = {}; + names.forEach((name, i) => obj[name] = i); + return obj; + } + + class Message { + constructor(type, data) { + this.type = type; + this.data = data; + } + + static decode(serialized) { + const {type, data} = JSON.parse(serialized); + return new Message(type, data); + } + + encode() { + return Message.encode(this.type, this.data); + } + + static encode(type, data=0) { + if (typeof Buffer !== 'undefined' && data instanceof Buffer) { + data = data.toString(); + } + return JSON.stringify({type, data}); + } + } + Object.assign(Message, Constants); + Message.Constants = Constants; + + return Message; +})); diff --git a/src/common/compute/interactive/session-with-queue.js b/src/common/compute/interactive/session-with-queue.js new file mode 100644 index 000000000..453d153cb --- /dev/null +++ b/src/common/compute/interactive/session-with-queue.js @@ -0,0 +1,75 @@ +/* globals define*/ +define([ + 'deepforge/compute/interactive/session', + 'deepforge/compute/interactive/task', + 'deepforge/compute/interactive/message', + 'deepforge/compute/interactive/errors', + 'deepforge/utils', +], function( + Session, + Task, + Message, + Errors, + utils, +) { + const {defer} = utils; + class SessionWithQueue extends Session { + constructor(computeID, config, size=20) { + super(computeID, config); + this.size = size; + this.tasks = []; + } + + async runTask(task) { + const queuedTask = this.queueTask(task); + await queuedTask.promise; + } + + queueTask(task) { + const queuedTask = new QueuedTask(task); + this.tasks.push(queuedTask); + this.checkTaskQueue(); + return queuedTask; + } + + async checkTaskQueue() { + if (this.isIdle() && this.tasks.length) { + this.runNextTask(); + } + } + + ensureIdle(action) { + if (action === 'run task') { + super.ensureIdle(action); + } + } + + async runNextTask() { + const queuedTask = this.tasks[0]; + await super.runTask(queuedTask.unwrap()); + this.tasks.shift(); + queuedTask.resolve(); + this.checkTaskQueue(); + } + + static async new(id, config) { + return await Session.new(id, config, SessionWithQueue); + } + } + + class QueuedTask { + constructor(task) { + this.innerTask = task; + const deferred = defer(); + this.promise = deferred.promise; + this.resolve = deferred.resolve; + this.reject = deferred.reject; + } + + unwrap() { + return this.innerTask; + } + } + + return SessionWithQueue; +}); diff --git a/src/common/compute/interactive/session.js b/src/common/compute/interactive/session.js new file mode 100644 index 000000000..458b05969 --- /dev/null +++ b/src/common/compute/interactive/session.js @@ -0,0 +1,158 @@ +/* globals define */ +define([ + 'deepforge/utils', + 'deepforge/compute/interactive/task', + 'deepforge/compute/interactive/message', + 'deepforge/compute/interactive/errors', + 'deepforge/gmeConfig', +], function( + utils, + Task, + Message, + Errors, + gmeConfig, +) { + const {defer} = utils; + const {CommandFailedError} = Errors; + const isNodeJs = typeof window === 'undefined'; + const WebSocket = isNodeJs ? require('ws') : window.WebSocket; + + class InteractiveSession { + constructor(computeID, config={}) { + this.currentTask = null; + const address = gmeConfig.extensions.InteractiveComputeHost || + this.getDefaultServerURL(); + this.ws = new WebSocket(address); + this.connected = defer(); + this.ws.onopen = () => { + this.ws.send(JSON.stringify([computeID, config, this.getGMEToken()])); + this.ws.onmessage = async (wsMsg) => { + const data = await Task.getMessageData(wsMsg); + + const msg = Message.decode(data); + if (msg.type === Message.COMPLETE) { + const err = msg.data; + this.ws.onmessage = null; + if (err) { + this.connected.reject(err); + } else { + this.connected.resolve(); + this.checkReady(); + } + } + }; + }; + + this.ready = null; + } + + getDefaultServerURL() { + const isSecure = !isNodeJs && location.protocol.includes('s'); + const protocol = isSecure ? 'wss' : 'ws'; + const defaultHost = isNodeJs ? '127.0.0.1' : + location.origin + .replace(location.protocol + '//', '') + .replace(/:[0-9]+$/, ''); + return `${protocol}://${defaultHost}:${gmeConfig.server.port + 1}`; + } + + getGMEToken() { + if (isNodeJs) { + return ''; + } + + const [, token] = (document.cookie || '').split('='); + return token; + } + + checkReady() { + if (this.isIdle() && this.ready) { + this.ready.resolve(); + } + } + + isIdle() { + return !this.currentTask && this.ws.readyState === WebSocket.OPEN; + } + + ensureIdle(action) { + if (!this.isIdle()) { + throw new Error(`Cannot ${action} when not idle.`); + } + } + + spawn(cmd) { + this.ensureIdle('spawn a task'); + + const msg = new Message(Message.RUN, cmd); + const task = new Task(this.ws, msg); + this.runTask(task); + return task; + } + + async runTask(task) { + this.ensureIdle('run task'); + + this.currentTask = task; + await task.run(); + this.currentTask = null; + this.checkReady(); + } + + async whenConnected() { + return this.connected.promise; + } + + async whenReady() { + this.ready = this.ready || defer(); + return this.ready.promise; + } + + async exec(cmd) { + this.ensureIdle('exec a task'); + const msg = new Message(Message.RUN, cmd); + const task = new Task(this.ws, msg); + const result = { + stdout: '', + stderr: '', + exitCode: 0 + }; + task.on(Message.STDOUT, data => result.stdout += data.toString()); + task.on(Message.STDERR, data => result.stderr += data.toString()); + task.on(Message.COMPLETE, code => result.exitCode = code); + await this.runTask(task); + if (result.exitCode) { + throw new CommandFailedError(cmd, result); + } + return result; + } + + async addArtifact(name, dataInfo, type, auth) { + this.ensureIdle('add artifact'); + const msg = new Message(Message.ADD_ARTIFACT, [name, dataInfo, type, auth]); + const task = new Task(this.ws, msg); + await this.runTask(task); + } + + async addFile(filepath, content) { + this.ensureIdle('add file'); + const msg = new Message(Message.ADD_FILE, [filepath, content]); + const task = new Task(this.ws, msg); + await this.runTask(task); + } + + close() { + this.ws.close(); + } + + static async new(computeID, config={}, SessionClass=InteractiveSession) { + const session = new SessionClass(computeID, config); + await session.whenConnected(); + return session; + } + } + + Object.assign(InteractiveSession, Message.Constants); + + return InteractiveSession; +}); diff --git a/src/common/compute/interactive/task.js b/src/common/compute/interactive/task.js new file mode 100644 index 000000000..702404272 --- /dev/null +++ b/src/common/compute/interactive/task.js @@ -0,0 +1,55 @@ +/* globals define */ +define([ + 'deepforge/EventEmitter', + 'deepforge/compute/interactive/message', + 'deepforge/utils', +], function( + EventEmitter, + Message, + utils, +) { + + const isNodeJs = typeof window === 'undefined'; + class Task extends EventEmitter { + constructor(ws, msg) { + super(); + this.ws = ws; + this.msg = msg; + } + + async run() { + const deferred = utils.defer(); + + this.ws.send(this.msg.encode()); + this.ws.onmessage = async wsMsg => { + const data = await Task.getMessageData(wsMsg); + + const msg = Message.decode(data); + this.emitMessage(msg); + if (msg.type === Message.COMPLETE) { + this.ws.onmessage = null; + deferred.resolve(); + } + }; + + return deferred.promise; + } + + emitMessage(msg) { + this.emit(msg.type, msg.data); + } + + static async getMessageData(wsMsg) { + if (isNodeJs) { + return wsMsg.data; + } + + const data = wsMsg.data instanceof Blob ? + await wsMsg.data.text() : wsMsg.data; + return data; + } + + } + + return Task; +}); diff --git a/src/routers/InteractiveCompute/Channel.js b/src/routers/InteractiveCompute/Channel.js new file mode 100644 index 000000000..09a03ede0 --- /dev/null +++ b/src/routers/InteractiveCompute/Channel.js @@ -0,0 +1,23 @@ +/* globals requireJS */ +const EventEmitter = requireJS('deepforge/EventEmitter'); + +class Channel extends EventEmitter { + constructor(ws1, ws2) { + super(); + this.ws1 = ws1; + this.ws2 = ws2; + this.ws1.on('message', data => this.ws2.send(data)); + this.ws2.on('message', data => this.ws1.send(data)); + this.ws1.onclose = + this.ws2.onclose = () => this.close(); + } + + close () { + this.ws1.close(); + this.ws2.close(); + this.emit('close'); + } +} + +Channel.CLOSE = 'close'; +module.exports = Channel; diff --git a/src/routers/InteractiveCompute/InteractiveCompute.js b/src/routers/InteractiveCompute/InteractiveCompute.js new file mode 100644 index 000000000..29aebabcb --- /dev/null +++ b/src/routers/InteractiveCompute/InteractiveCompute.js @@ -0,0 +1,97 @@ +/* globals requireJS */ +'use strict'; + +const WebSocket = require('ws'); +const router = require('express').Router(); +const Compute = requireJS('deepforge/compute/index'); +const BlobClient = requireJS('blob/BlobClient'); +const Message = requireJS('deepforge/compute/interactive/message'); +const InteractiveSession = require('./Session'); + +class ComputeBroker { + constructor(logger) { + this.logger = logger.fork('broker'); + this.initSessions = []; + this.wss = null; + } + + listen (port) { + this.wss = new WebSocket.Server({port}); + + this.wss.on('connection', ws => { + ws.once('message', data => { + const isClient = data.startsWith('['); + if (isClient) { + this.onClientConnected(port, ws, ...JSON.parse(data)); + } else { + this.onWorkerConnected(ws, data); + } + }); + }); + } + + stop () { + this.wss.close(); + } + + onClientConnected (port, ws, id, config, gmeToken) { + try { + const backend = Compute.getBackend(id); + const blobClient = new BlobClient({ + logger: this.logger.fork('BlobClient'), + serverPort: port-1, + server: '127.0.0.1', + httpsecure: false, + webgmeToken: gmeToken + }); + const client = backend.getClient(this.logger, blobClient, config); + const session = new InteractiveSession(blobClient, client, ws); + this.initSessions.push(session); + } catch (err) { + ws.send(Message.encode(Message.COMPLETE, err.message)); + this.logger.warn(`Error creating session: ${err}`); + ws.close(); + } + } + + onWorkerConnected (ws, id) { + const index = this.initSessions.findIndex(session => session.id === id); + if (index > -1) { + const [session] = this.initSessions.splice(index, 1); + session.setWorkerWebSocket(ws); + } else { + this.logger.warn(`Session not found for ${id}`); + ws.close(); + } + } +} + +let broker = null; +let gmeConfig; +function initialize(middlewareOpts) { + const logger = middlewareOpts.logger.fork('InteractiveCompute'); + + gmeConfig = middlewareOpts.gmeConfig; + broker = new ComputeBroker(logger); + logger.debug('initializing ...'); + + logger.debug('ready'); +} + +function start(callback) { + broker.listen(gmeConfig.server.port + 1); + callback(); +} + +function stop(callback) { + broker.stop(); + callback(); +} + + +module.exports = { + initialize: initialize, + router: router, + start: start, + stop: stop +}; diff --git a/src/routers/InteractiveCompute/Session.js b/src/routers/InteractiveCompute/Session.js new file mode 100644 index 000000000..d7bb6ee5f --- /dev/null +++ b/src/routers/InteractiveCompute/Session.js @@ -0,0 +1,54 @@ +/* globals requireJS */ +const WebSocket = require('ws'); +const JobFiles = require('./job-files'); +const chance = require('chance')(); +const gmeConfig = requireJS('deepforge/gmeConfig'); +const SERVER_URL = gmeConfig.extensions.InteractiveComputeHost || + `http://localhost:${gmeConfig.server.port + 1}`; +const Channel = require('./Channel'); +const EventEmitter = requireJS('deepforge/EventEmitter'); +const Message = requireJS('deepforge/compute/interactive/message'); +const ComputeClient = requireJS('deepforge/compute/backends/ComputeClient'); + +class Session extends EventEmitter { + constructor(blobClient, compute, clientSocket) { + super(); + this.compute = compute; + this.clientSocket = clientSocket; + this.id = chance.guid(); + this.wsChannel = null; + this.initialize(blobClient); + } + + setWorkerWebSocket(socket) { + this.workerSocket = socket; + this.emit('connected'); + + this.clientSocket.send(Message.encode(Message.COMPLETE)); + this.queuedMsgs.forEach(msg => this.workerSocket.send(msg)); + this.wsChannel = new Channel(this.clientSocket, this.workerSocket); + this.wsChannel.on(Channel.CLOSE, () => this.close()); + } + + async initialize(blobClient) { + this.queuedMsgs = []; + this.clientSocket.on('message', data => this.queuedMsgs.push(data)); + + const files = new JobFiles(blobClient, SERVER_URL, this.id); + const hash = await files.upload(); + this.jobInfo = this.compute.createJob(hash); + this.compute.on('end', (id, info) => { + const isError = this.clientSocket.readyState === WebSocket.OPEN && + info.status !== ComputeClient.SUCCESS; + if (isError) { + this.clientSocket.send(Message.encode(Message.ERROR, info)); + } + }); + } + + async close () { + await this.compute.cancelJob(await this.jobInfo); + } +} + +module.exports = Session; diff --git a/src/routers/InteractiveCompute/job-files/index.js b/src/routers/InteractiveCompute/job-files/index.js new file mode 100644 index 000000000..2ce7ebe13 --- /dev/null +++ b/src/routers/InteractiveCompute/job-files/index.js @@ -0,0 +1,42 @@ +/* globals requireJS */ +const Files = requireJS('deepforge/plugin/GeneratedFiles'); +const Templates = requireJS('plugin/GenerateJob/GenerateJob/templates/index'); +const fs = require('fs'); +const path = require('path'); +const START_SESSION = fs.readFileSync(path.join(__dirname, 'start.js'), 'utf8'); +const srcDir = path.join(__dirname, '..', '..', '..'); +const interactiveDir = path.join(srcDir, 'common', 'compute', 'interactive'); +const MESSAGE = fs.readFileSync(path.join(interactiveDir, 'message.js'), 'utf8'); +const _ = requireJS('underscore'); +const CONSTANTS = requireJS('deepforge/Constants'); + +class StartSessionFiles extends Files { + constructor(blobClient, url, id) { + super(blobClient); + this.id = id; + this.createFiles(url, id); + } + + createFiles(url, id) { + const config = JSON.stringify({ + cmd: 'node', + args: ['start.js', url, id], + outputInterval: -1, + resultArtifacts: [] + }, null, 2); + this.addFile('executor_config.json', config); + this.addFile('start.js', START_SESSION); + this.addFile('message.js', MESSAGE); + this.addFile('utils.build.js', Templates.UTILS); + this.addFile('deepforge/__init__.py', Templates.DEEPFORGE_INIT); + const serializeTpl = _.template(Templates.DEEPFORGE_SERIALIZATION); + this.addFile('deepforge/serialization.py', serializeTpl(CONSTANTS)); + } + + async upload() { + const name = `interactive-session-init-${this.id}`; + return await this.save(name); + } +} + +module.exports = StartSessionFiles; diff --git a/src/routers/InteractiveCompute/job-files/start.js b/src/routers/InteractiveCompute/job-files/start.js new file mode 100644 index 000000000..a3362ef7f --- /dev/null +++ b/src/routers/InteractiveCompute/job-files/start.js @@ -0,0 +1,137 @@ +const {spawn} = require('child_process'); +const WebSocket = require('ws'); +const fs = require('fs').promises; +const path = require('path'); +const requirejs = require('requirejs'); +let Message; + +class InteractiveClient { + constructor(id, host) { + this.id = id; + this.host = host; + this.ws = null; + } + + connect() { + this.ws = new WebSocket(this.host); + this.ws.on('open', () => this.ws.send(this.id)); + this.ws.on('message', data => this.onMessage(Message.decode(data))); + } + + async sendMessage(type, data) { + this.ws.send(Message.encode(type, data)); + } + + async onMessage(msg) { + if (msg.type === Message.RUN) { + const [cmd, ...opts] = InteractiveClient.parseCommand(msg.data); + const subprocess = spawn(cmd, opts); + subprocess.on('close', code => this.sendMessage(Message.COMPLETE, code)); + subprocess.stdout.on('data', data => this.sendMessage(Message.STDOUT, data)); + subprocess.stderr.on('data', data => this.sendMessage(Message.STDERR, data)); + } else if (msg.type === Message.ADD_ARTIFACT) { + const [name, dataInfo, type, config={}] = msg.data; + const dirs = ['artifacts', name]; + await mkdirp(...dirs); + requirejs([ + './utils.build', + ], ( + Utils, + ) => { + const {Storage} = Utils; + + async function saveArtifact() { + const client = await Storage.getClient(dataInfo.backend, null, config); + const dataPath = path.join(...dirs.concat('data')); + const buffer = await client.getFile(dataInfo); + await fs.writeFile(dataPath, buffer); + const filePath = path.join(...dirs.concat('__init__.py')); + await fs.writeFile(filePath, initFile(name, type)); + } + + this.runTask(saveArtifact); + }); + } else if (msg.type === Message.ADD_FILE) { + this.runTask(() => this.writeFile(msg)); + } + } + + async writeFile(msg) { + const [filepath, content] = msg.data; + const dirs = path.dirname(filepath).split(path.sep); + await mkdirp(...dirs); + await fs.writeFile(filepath, content); + } + + async runTask(fn) { + let exitCode = 0; + try { + await fn(); + } catch (err) { + exitCode = 1; + console.log('Task failed with error:', err); + } + this.sendMessage(Message.COMPLETE, exitCode); + } + + static parseCommand(cmd) { + const chunks = ['']; + let quoteChar = null; + for (let i = 0; i < cmd.length; i++) { + const letter = cmd[i]; + const isQuoteChar = letter === '"' || letter === '\''; + const isInQuotes = !!quoteChar; + if (!isInQuotes && isQuoteChar) { + quoteChar = letter; + } else if (quoteChar === letter) { + quoteChar = null; + } else { + const isNewChunk = letter === ' ' && !isInQuotes; + if (isNewChunk) { + chunks.push(''); + } else { + const lastChunk = chunks[chunks.length - 1]; + chunks[chunks.length - 1] = lastChunk + letter; + } + } + } + return chunks; + } + +} + +async function mkdirp() { + const dirs = Array.prototype.slice.call(arguments); + await dirs.reduce(async (lastDirPromise, nextDir) => { + const dir = path.join(await lastDirPromise, nextDir); + try { + await fs.mkdir(dir); + } catch (err) { + if (err.code !== 'EEXIST') { + throw err; + } + } + return dir; + }, process.cwd()); +} + +function initFile(name, type) { + const dataPathCode = `path.join(path.dirname(__file__), 'data')`; + return [ + 'import deepforge', + 'from os import path', + `name = '${name}'`, + `type = '${type}'`, + `data = deepforge.serialization.load('${type}', open(${dataPathCode}, 'rb'))` + ].join('\n'); +} + +module.exports = {InteractiveClient}; + +const isImportedModule = require.main !== module; +if (!isImportedModule) { + Message = require('./message'); + const [, , SERVER_URL, ID] = process.argv; + const client = new InteractiveClient(ID, SERVER_URL); + client.connect(); +} diff --git a/test/integration/InteractiveCompute.js b/test/integration/InteractiveCompute.js new file mode 100644 index 000000000..87cd62521 --- /dev/null +++ b/test/integration/InteractiveCompute.js @@ -0,0 +1,45 @@ +describe('InteractiveCompute', function() { + const assert = require('assert').strict; + const {promisify} = require('util'); + const testFixture = require('../globals'); + const gmeConfig = testFixture.getGmeConfig(); + const server = new testFixture.WebGME.standaloneServer(gmeConfig); + server.start = promisify(server.start); + server.stop = promisify(server.stop); + let session; + + before(async function() { + await server.start(); + }); + after(async function() { + await server.stop(); + }); + + beforeEach(async function() { + const Session = testFixture.requirejs('deepforge/compute/interactive/session-with-queue'); + session = await Session.new('local'); + }); + afterEach(() => session.close()); + + it('should be able to exec commands', async function() { + const {exitCode, stdout} = await session.exec('ls'); + assert.equal(exitCode, 0); + const files = stdout.split('\n'); + assert(files.includes('start.js')); + }); + + it('should be able to spawn commands', function(done) { + const Message = testFixture.requirejs('deepforge/compute/interactive/message'); + const task = session.spawn('ls'); + task.on(Message.COMPLETE, exitCode => { + assert.equal(exitCode, 0); + done(); + }); + }); + + it('should be able to add files', async function() { + await session.addFile('test.txt', 'hello world'); + const {stdout} = await session.exec('cat test.txt'); + assert.equal(stdout, 'hello world'); + }); +}); diff --git a/test/unit/common/api/JobOriginClient.spec.js b/test/unit/common/api/JobOriginClient.spec.js index 54fd63a51..67f9949f0 100644 --- a/test/unit/common/api/JobOriginClient.spec.js +++ b/test/unit/common/api/JobOriginClient.spec.js @@ -1,18 +1,13 @@ var testFixture = require('../../../globals'), expect = testFixture.expect, gmeConfig = testFixture.getGmeConfig(), - server = testFixture.WebGME.standaloneServer(gmeConfig), Logger = require('webgme-engine/src/server/logger'), logger = Logger.createWithGmeConfig('gme', gmeConfig, true), JobOriginClient = testFixture.requirejs('deepforge/api/JobOriginClient'); describe('JobOriginClient', function() { - var client = new JobOriginClient({ - logger: logger, - origin: server.getUrl(), - projectId: 'testProject', - branchName: 'master' - }), + var server, + client, hashes = {}, getJobInfo = function() { var hash = 'hashOrigin'+Math.ceil(Math.random()*100000); @@ -31,6 +26,13 @@ describe('JobOriginClient', function() { }; before(function(done) { + server = testFixture.WebGME.standaloneServer(gmeConfig), + client = new JobOriginClient({ + logger: logger, + origin: server.getUrl(), + projectId: 'testProject', + branchName: 'master' + }); server.start(done); }); diff --git a/test/unit/plugins/ExecutePipeline/ExecutePipeline.js b/test/unit/plugins/ExecutePipeline/ExecutePipeline.js index 746b91734..08fa50cb0 100644 --- a/test/unit/plugins/ExecutePipeline/ExecutePipeline.js +++ b/test/unit/plugins/ExecutePipeline/ExecutePipeline.js @@ -32,11 +32,12 @@ describe('ExecutePipeline', function () { Pipeline.ComplexPipeline = '/f/3'; Pipeline.ExportPlugin = '/f/s'; - const server = new testFixture.WebGME.standaloneServer(gmeConfig); - server.start = promisify(server.start); - server.stop = promisify(server.stop); + let server; before(async function () { + server = new testFixture.WebGME.standaloneServer(gmeConfig); + server.start = promisify(server.start); + server.stop = promisify(server.stop); gmeAuth = await testFixture.clearDBAndGetGMEAuth(gmeConfig, projectName); // This uses in memory storage. Use testFixture.getMongoStorage to persist test to database. storage = testFixture.getMemoryStorage(logger, gmeConfig, gmeAuth); diff --git a/test/unit/routers/ExecPulse/ExecPulse.spec.js b/test/unit/routers/ExecPulse/ExecPulse.spec.js index 0b3ea9922..be907e169 100644 --- a/test/unit/routers/ExecPulse/ExecPulse.spec.js +++ b/test/unit/routers/ExecPulse/ExecPulse.spec.js @@ -5,8 +5,8 @@ describe('ExecPulse', function() { superagent = testFixture.superagent, expect = testFixture.expect, gmeConfig = testFixture.getGmeConfig(), - server = testFixture.WebGME.standaloneServer(gmeConfig), mntPt = require('../../../../webgme-setup.json').components.routers.ExecPulse.mount, + server, urlFor = function(action) { return [ server.getUrl(), @@ -20,6 +20,7 @@ describe('ExecPulse', function() { }; before(function(done) { + server = testFixture.WebGME.standaloneServer(gmeConfig), server.start(done); }); diff --git a/test/unit/routers/InteractiveCompute/start.js b/test/unit/routers/InteractiveCompute/start.js new file mode 100644 index 000000000..7e6428eba --- /dev/null +++ b/test/unit/routers/InteractiveCompute/start.js @@ -0,0 +1,31 @@ +describe('InteractiveClient worker script', function() { + const testFixture = require('../../../globals'); + const assert = require('assert').strict; + let InteractiveClient; + + before(() => { + InteractiveClient = require(testFixture.PROJECT_ROOT + '/src/routers/InteractiveCompute/job-files/start').InteractiveClient; + }); + + describe('parseCommand', function() { + it('should parse separate words ("ab cd efg h")', function() { + const cmd = 'ab cd efg h'; + const chunks = InteractiveClient.parseCommand(cmd); + assert.equal(chunks.join(' '), cmd); + }); + + it('should parse "ab \'cd efg h\'"', function() { + const cmd = 'ab \'cd efg h\''; + const chunks = InteractiveClient.parseCommand(cmd); + assert.equal(chunks.length, 2); + assert.equal(chunks[0], 'ab'); + }); + + it('should parse "ab "cd efg h""', function() { + const cmd = 'ab "cd efg h"'; + const chunks = InteractiveClient.parseCommand(cmd); + assert.equal(chunks.length, 2); + assert.equal(chunks[0], 'ab'); + }); + }); +}); diff --git a/test/unit/routers/JobLogsAPI/JobLogsAPI.spec.js b/test/unit/routers/JobLogsAPI/JobLogsAPI.spec.js index 007bbc944..7e50de030 100644 --- a/test/unit/routers/JobLogsAPI/JobLogsAPI.spec.js +++ b/test/unit/routers/JobLogsAPI/JobLogsAPI.spec.js @@ -6,7 +6,6 @@ var testFixture = require('../../../globals'), path = testFixture.path, gmeConfig = testFixture.getGmeConfig(), blobDir = gmeConfig.blob.fsDir, - server = testFixture.WebGME.standaloneServer(gmeConfig), mntPt = 'execution/logs', rm_rf = require('rimraf'), exists = require('exists-file'); @@ -16,6 +15,12 @@ describe('JobLogsAPI', function() { branch = 'master', jobId = encodeURIComponent('/4/q/l'), firstLog = 'hello world', + server, + url; + + before(function(done) { + testFixture.mkdir(blobDir); + server = testFixture.WebGME.standaloneServer(gmeConfig); url = [ server.getUrl(), mntPt, @@ -23,9 +28,6 @@ describe('JobLogsAPI', function() { branch, jobId ].join('/'); - - before(function(done) { - testFixture.mkdir(blobDir); server.start(done); }); @@ -78,15 +80,16 @@ describe('JobLogsAPI', function() { }); describe('delete', function() { - var delUrl = [ - server.getUrl(), - mntPt, - 'testProject', - 'other', - encodeURIComponent('/4/8/l') - ].join('/'); + var delUrl; before(function(done) { + delUrl = [ + server.getUrl(), + mntPt, + 'testProject', + 'other', + encodeURIComponent('/4/8/l') + ].join('/'); superagent.patch(delUrl) .send({patch: firstLog}) .end(function (err, res) { diff --git a/test/unit/routers/JobOriginAPI/JobOriginAPI.spec.js b/test/unit/routers/JobOriginAPI/JobOriginAPI.spec.js index 435c91d81..7cf30bd0f 100644 --- a/test/unit/routers/JobOriginAPI/JobOriginAPI.spec.js +++ b/test/unit/routers/JobOriginAPI/JobOriginAPI.spec.js @@ -2,18 +2,12 @@ var testFixture = require('../../../globals'), superagent = testFixture.superagent, expect = testFixture.expect, gmeConfig = testFixture.getGmeConfig(), - server = testFixture.WebGME.standaloneServer(gmeConfig), mntPt = 'job/origins'; describe('JobOriginAPI', function() { - var hashes = {}, - getUrl = function(hash) { - return [ - server.getUrl(), - mntPt, - hash - ].join('/'); - }, + var server, + getUrl, + hashes = {}, getJobInfo = function() { var hash = 'hash'+Math.ceil(Math.random()*100000); @@ -33,6 +27,14 @@ describe('JobOriginAPI', function() { }; before(function(done) { + server = testFixture.WebGME.standaloneServer(gmeConfig); + getUrl = function(hash) { + return [ + server.getUrl(), + mntPt, + hash + ].join('/'); + }; server.start(done); }); diff --git a/test/unit/utils/utils.spec.js b/test/unit/utils/utils.spec.js index 51cae25f1..1e6bdf730 100644 --- a/test/unit/utils/utils.spec.js +++ b/test/unit/utils/utils.spec.js @@ -1,13 +1,13 @@ /*jshint node:true, mocha:true*/ 'use strict'; -var testFixture = require('../../globals'), - path = testFixture.path, - assert = require('assert'), - SEED_DIR = testFixture.DF_SEED_DIR, - fs = require('fs'); +describe('misc utils', function () { + var testFixture = require('../../globals'), + path = testFixture.path, + assert = require('assert'), + SEED_DIR = testFixture.DF_SEED_DIR, + fs = require('fs'); -describe('utils', function () { var gmeConfig = testFixture.getGmeConfig(), GraphChecker = testFixture.requirejs('deepforge/GraphChecker'), MODELS_DIR = path.join(__dirname, '..', 'test-cases', 'models'), @@ -22,58 +22,37 @@ describe('utils', function () { commitHash, checker; - before(function (done) { - testFixture.clearDBAndGetGMEAuth(gmeConfig, projectName) - .then(function (gmeAuth_) { - gmeAuth = gmeAuth_; - // This uses in memory storage. Use testFixture.getMongoStorage to persist test to database. - storage = testFixture.getMemoryStorage(logger, gmeConfig, gmeAuth); - return storage.openDatabase(); - }) - .then(function () { - var importParam = { - projectSeed: path.join(SEED_DIR, 'devUtilTests', 'devUtilTests.webgmex'), - projectName: projectName, - branchName: 'master', - logger: logger, - gmeConfig: gmeConfig - }; + before(async function () { + gmeAuth = await testFixture.clearDBAndGetGMEAuth(gmeConfig, projectName); + storage = testFixture.getMemoryStorage(logger, gmeConfig, gmeAuth); + await storage.openDatabase(); + const importParam = { + projectSeed: path.join(SEED_DIR, 'devUtilTests', 'devUtilTests.webgmex'), + projectName: projectName, + branchName: 'master', + logger: logger, + gmeConfig: gmeConfig + }; - return testFixture.importProject(storage, importParam); - }) - .then(function (importResult) { - project = importResult.project; - core = importResult.core; - checker = new GraphChecker({ - core: core, - ignore: { - attributes: ['calculateDimensionality', 'dimensionalityTransform'] - } - }); - commitHash = importResult.commitHash; - return project.createBranch('test', commitHash); - }) - .then(function () { - return project.getBranchHash('test'); - }) - .then(function (branchHash) { - return Q.ninvoke(project, 'loadObject', branchHash); - }) - .then(function (commitObject) { - return Q.ninvoke(core, 'loadRoot', commitObject.root); - }) - .then(function (root) { - rootNode = root; - }) - .nodeify(done); + const importResult = await testFixture.importProject(storage, importParam); + project = importResult.project; + core = importResult.core; + checker = new GraphChecker({ + core: core, + ignore: { + attributes: ['calculateDimensionality', 'dimensionalityTransform'] + } + }); + commitHash = importResult.commitHash; + await project.createBranch('test', commitHash); + const branchHash = await project.getBranchHash('test'); + const commitObject = await Q.ninvoke(project, 'loadObject', branchHash); + rootNode = await Q.ninvoke(core, 'loadRoot', commitObject.root); }); - after(function (done) { - storage.closeDatabase() - .then(function () { - return gmeAuth.unload(); - }) - .nodeify(done); + after(async function () { + await storage.closeDatabase(); + await gmeAuth.unload(); }); var run = function(nodePath, filename, result, done) {