diff --git a/package-lock.json b/package-lock.json index 33f62e3..43e21af 100644 --- a/package-lock.json +++ b/package-lock.json @@ -40,6 +40,15 @@ "@types/node": "*" } }, + "@types/keytar": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@types/keytar/-/keytar-4.4.2.tgz", + "integrity": "sha512-xtQcDj9ruGnMwvSu1E2BH4SFa5Dv2PvSPd0CKEBLN5hEj/v5YpXJY+B6hAfuKIbvEomD7vJTc/P1s1xPNh2kRw==", + "dev": true, + "requires": { + "keytar": "*" + } + }, "@types/minimatch": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", @@ -103,6 +112,22 @@ "picomatch": "^2.0.4" } }, + "aproba": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", + "dev": true + }, + "are-we-there-yet": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", + "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", + "dev": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, "argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -130,6 +155,12 @@ "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", "dev": true }, + "base64-js": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", + "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==", + "dev": true + }, "big.js": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", @@ -142,6 +173,30 @@ "integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==", "dev": true }, + "bl": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.0.2.tgz", + "integrity": "sha512-j4OH8f6Qg2bGuWfRiltT2HYGx0e1QcBTrK9KAHNMwMZdQnDZFk0ZSYIpADjYCB3U12nicC5tVJwSIhwOWjb4RQ==", + "dev": true, + "requires": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, "boolbase": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", @@ -173,6 +228,16 @@ "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", "dev": true }, + "buffer": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.6.0.tgz", + "integrity": "sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==", + "dev": true, + "requires": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4" + } + }, "buffer-crc32": { "version": "0.2.13", "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", @@ -243,6 +308,12 @@ "readdirp": "~3.2.0" } }, + "chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true + }, "cliui": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", @@ -282,6 +353,12 @@ } } }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "dev": true + }, "color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -309,6 +386,12 @@ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "dev": true }, + "console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", + "dev": true + }, "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", @@ -348,6 +431,21 @@ "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", "dev": true }, + "decompress-response": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz", + "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==", + "dev": true, + "requires": { + "mimic-response": "^2.0.0" + } + }, + "deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true + }, "define-properties": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", @@ -357,12 +455,24 @@ "object-keys": "^1.0.12" } }, + "delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", + "dev": true + }, "denodeify": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/denodeify/-/denodeify-1.2.1.tgz", "integrity": "sha1-OjYof1A05pnnV3kBBSwubJQlFjE=", "dev": true }, + "detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", + "dev": true + }, "diff": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", @@ -416,6 +526,15 @@ "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", "dev": true }, + "end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "requires": { + "once": "^1.4.0" + } + }, "enhanced-resolve": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.1.1.tgz", @@ -499,6 +618,12 @@ "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "dev": true }, + "expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "dev": true + }, "fd-slicer": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", @@ -535,6 +660,12 @@ "is-buffer": "~2.0.3" } }, + "fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "dev": true + }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -554,12 +685,71 @@ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", "dev": true }, + "gauge": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "dev": 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" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + } + } + }, "get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true }, + "github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha1-l/tdlr/eiXMxPyDoKI75oWf6ZM4=", + "dev": true + }, "glob": { "version": "7.1.6", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", @@ -616,6 +806,12 @@ "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", "dev": true }, + "has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", + "dev": true + }, "he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", @@ -686,6 +882,12 @@ "debug": "^3.1.0" } }, + "ieee754": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", + "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==", + "dev": true + }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -702,6 +904,12 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true }, + "ini": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", + "dev": true + }, "is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -811,6 +1019,16 @@ "minimist": "^1.2.0" } }, + "keytar": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/keytar/-/keytar-6.0.1.tgz", + "integrity": "sha512-1Ihpf2tdM3sLwGMkYHXYhVC/hx5BDR7CWFL4IrBA3IDZo0xHhS2nM+tU9Y+u/U7okNfbVkwmKsieLkcWRMh93g==", + "dev": true, + "requires": { + "node-addon-api": "^3.0.0", + "prebuild-install": "5.3.4" + } + }, "leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", @@ -915,6 +1133,12 @@ "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", "dev": true }, + "mimic-response": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", + "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==", + "dev": true + }, "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", @@ -939,6 +1163,12 @@ "minimist": "^1.2.5" } }, + "mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "dev": true + }, "mocha": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/mocha/-/mocha-7.2.0.tgz", @@ -999,6 +1229,27 @@ "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", "dev": true }, + "napi-build-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", + "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==", + "dev": true + }, + "node-abi": { + "version": "2.18.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.18.0.tgz", + "integrity": "sha512-yi05ZoiuNNEbyT/xXfSySZE+yVnQW6fxPZuFbLyS1s6b5Kw3HzV2PHOM4XR+nsjzkHxByK+2Wg+yCQbe35l8dw==", + "dev": true, + "requires": { + "semver": "^5.4.1" + } + }, + "node-addon-api": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.0.0.tgz", + "integrity": "sha512-sSHCgWfJ+Lui/u+0msF3oyCgvdkhxDbkCS6Q8uiJquzOimkJBvX6hl5aSSA7DR1XbMpdM8r7phjcF63sF4rkKg==", + "dev": true + }, "node-environment-flags": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.6.tgz", @@ -1009,12 +1260,30 @@ "semver": "^5.7.0" } }, + "noop-logger": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/noop-logger/-/noop-logger-0.1.1.tgz", + "integrity": "sha1-lKKxYzxPExdVMAfYlm/Q6EG2pMI=", + "dev": true + }, "normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true }, + "npmlog": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "dev": true, + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, "nth-check": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", @@ -1024,6 +1293,18 @@ "boolbase": "~1.0.0" } }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true + }, "object-inspect": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", @@ -1167,6 +1448,29 @@ "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", "dev": true }, + "prebuild-install": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-5.3.4.tgz", + "integrity": "sha512-AkKN+pf4fSEihjapLEEj8n85YIw/tN6BQqkhzbDc0RvEZGdkpJBGMUYx66AAMcPG2KzmPQS7Cm16an4HVBRRMA==", + "dev": true, + "requires": { + "detect-libc": "^1.0.3", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp": "^0.5.1", + "napi-build-utils": "^1.0.1", + "node-abi": "^2.7.0", + "noop-logger": "^0.1.1", + "npmlog": "^4.0.1", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^3.0.3", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0", + "which-pm-runs": "^1.0.0" + } + }, "process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -1179,6 +1483,28 @@ "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=", "dev": true }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + } + }, "read": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz", @@ -1260,6 +1586,29 @@ "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", "dev": true }, + "signal-exit": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", + "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", + "dev": true + }, + "simple-concat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.0.tgz", + "integrity": "sha1-c0TLuLbib7J9ZrL8hvn21Zl1IcY=", + "dev": true + }, + "simple-get": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.0.tgz", + "integrity": "sha512-bCR6cP+aTdScaQCnQKbPKtJOKDp/hj9EDLJo3Nw4y1QksqaovlW/bnptB6/c1e+qmNIDHRK+oXFDdEqBT8WzUA==", + "dev": true, + "requires": { + "decompress-response": "^4.2.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -1357,6 +1706,44 @@ "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==", "dev": true }, + "tar-fs": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.0.tgz", + "integrity": "sha512-9uW5iDvrIMCVpvasdFHW0wJPez0K4JnMZtsuIeDI7HyMGJNxmDZDOCQROr7lXyS+iL/QMpj07qcjGYTSdRFXUg==", + "dev": true, + "requires": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.0.0" + } + }, + "tar-stream": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.1.3.tgz", + "integrity": "sha512-Z9yri56Dih8IaK8gncVPx4Wqt86NDmQTSh49XLZgjWpGZL9GK9HKParS2scqHCC4w6X9Gh2jwaU45V47XTKwVA==", + "dev": true, + "requires": { + "bl": "^4.0.1", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, "tmp": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.29.tgz", @@ -1446,6 +1833,15 @@ "integrity": "sha1-LTeFoVjBdMmhbcLARuxfxfF0IhM=", "dev": true }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "dev": true, + "requires": { + "safe-buffer": "^5.0.1" + } + }, "typed-rest-client": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/typed-rest-client/-/typed-rest-client-1.2.0.tgz", @@ -1540,6 +1936,12 @@ "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", "dev": true }, + "which-pm-runs": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/which-pm-runs/-/which-pm-runs-1.0.0.tgz", + "integrity": "sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs=", + "dev": true + }, "wide-align": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", diff --git a/package.json b/package.json index 37519ba..fa564f7 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,7 @@ "@types/glob": "^7.1.1", "@types/mocha": "^5.2.6", "@types/node": "^8.10.60", + "@types/keytar": "^4.4.2", "glob": "^7.1.6", "mocha": "^7.1.2", "ts-loader": "^6.2.2", @@ -56,7 +57,13 @@ "vscode-test": "^1.3.0" }, "main": "./out/extension", - "activationEvents": [], + "activationEvents": [ + "onCommand:intersystems-community.servermanager.testPickServer", + "onCommand:intersystems-community.servermanager.testPickServerFlushingCachedCredentials", + "onCommand:intersystems-community.servermanager.testPickServerDetailed", + "onCommand:intersystems-community.servermanager.storePassword", + "onCommand:intersystems-community.servermanager.clearPassword" + ], "contributes": { "configuration": { "title": "InterSystems® Server Manager", @@ -73,7 +80,7 @@ "host": "127.0.0.1", "port": 52773 }, - "description": "Connection to default local InterSystems IRIS™ installation. Delete if unwanted." + "description": "Connection to local InterSystems IRIS™ installed with default settings." }, "cache": { "webServer": { @@ -81,7 +88,7 @@ "host": "127.0.0.1", "port": 57772 }, - "description": "Connection to default local InterSystems Caché® installation. Delete if unwanted." + "description": "Connection to local InterSystems Caché® installed with default settings." }, "ensemble": { "webServer": { @@ -89,7 +96,7 @@ "host": "127.0.0.1", "port": 57772 }, - "description": "Connection to default local InterSystems Ensemble® installation. Delete if unwanted." + "description": "Connection to local InterSystems Ensemble® installed with default settings." }, "/default": "iris" }, @@ -151,7 +158,8 @@ }, "password": { "type": "string", - "description": "Password of username. If not set here it must be provided when connecting." + "description": "Password of username.", + "deprecationMessage": "Storing password in plaintext is not recommended. Instead, use the Command Palette command to store it in your keychain." }, "description": { "type": "string", @@ -173,6 +181,33 @@ "additionalProperties": false } } - } + }, + "commands": [ + { + "command": "intersystems-community.servermanager.storePassword", + "category": "InterSystems Server Manager", + "title": "Store Password in Keychain" + }, + { + "command": "intersystems-community.servermanager.clearPassword", + "category": "InterSystems Server Manager", + "title": "Clear Password from Keychain" + }, + { + "command": "intersystems-community.servermanager.testPickServer", + "category": "InterSystems Server Manager", + "title": "Test Server Selection" + }, + { + "command": "intersystems-community.servermanager.testPickServerFlushingCachedCredentials", + "category": "InterSystems Server Manager", + "title": "Test Server Selection (flush cached credentials)" + }, + { + "command": "intersystems-community.servermanager.testPickServerDetailed", + "category": "InterSystems Server Manager", + "title": "Test Server Selection with Details" + } + ] } } diff --git a/src/api/getServerNames.ts b/src/api/getServerNames.ts new file mode 100644 index 0000000..0a28613 --- /dev/null +++ b/src/api/getServerNames.ts @@ -0,0 +1,32 @@ +import * as vscode from 'vscode'; +import { ServerName, ServerSpec } from '../extension'; + +export function getServerNames(scope?: vscode.ConfigurationScope): ServerName[] { + let names: ServerName[] = []; + const servers = vscode.workspace.getConfiguration('intersystems', scope).get('servers'); + + if (typeof servers === 'object' && servers) { + const defaultName: string = servers['/default'] || ''; + if (defaultName.length > 0 && servers[defaultName]) { + names.push({ + name: defaultName, + description: `${servers[defaultName].description || ''} (default)`, + detail: serverDetail(servers[defaultName]) + }); + } + for (const key in servers) { + if (!key.startsWith('/') && key !== defaultName) { + names.push({ + name: key, + description: servers[key].description || '', + detail: serverDetail(servers[key]) + }); + } + } + } + return names; +} + +function serverDetail(connSpec: ServerSpec): string { + return `${connSpec.webServer.scheme || 'http'}://${connSpec.webServer.host}:${connSpec.webServer.port}/${connSpec.webServer.pathPrefix || ''}`; +} \ No newline at end of file diff --git a/src/api/getServerSpec.ts b/src/api/getServerSpec.ts new file mode 100644 index 0000000..52db7e7 --- /dev/null +++ b/src/api/getServerSpec.ts @@ -0,0 +1,89 @@ +import * as vscode from 'vscode'; +import { ServerSpec } from '../extension'; +import { Keychain } from '../keychain'; + +interface CredentialSet { + username: string, + password: string +} + +export let credentialCache = new Map(); + +export async function getServerSpec(name: string, scope?: vscode.ConfigurationScope, flushCredentialCache: boolean = false): Promise { + if (flushCredentialCache) { + credentialCache[name] = undefined; + } + let server: ServerSpec | undefined = vscode.workspace.getConfiguration('intersystems.servers', scope).get(name); + + // Unknown server + if (!server) { + return undefined; + } + + server.name = name; + server.description = server.description || ''; + server.webServer.scheme = server.webServer.scheme || 'http'; + server.webServer.pathPrefix = server.webServer.pathPrefix || ''; + + // Obtain a username (including blank to try connecting anonymously) + if (!server.username) { + await vscode.window + .showInputBox({ + placeHolder: `Username to connect to InterSystems server '${name}' as`, + prompt: 'Leave empty to attempt unauthenticated access', + ignoreFocusOut: true, + }) + .then((username) => { + if (username && server) { + server.username = username; + } else { + return undefined; + } + }); + if (!server.username) { + server.username = ''; + server.password = ''; + } + } + + // Obtain password from session cache or keychain unless trying to connect anonymously + if (server.username && !server.password) { + if (credentialCache[name] && credentialCache[name].username === server.username) { + server.password = credentialCache[name]; + } else { + const keychain = new Keychain(name); + const password = await keychain.getPassword().then(result => { + if (typeof result === 'string') { + return result; + } else { + return undefined; + } + }); + if (password) { + server.password = password; + credentialCache[name] = {username: server.username, password: password}; + } + } + + } + if (server.username && !server.password) { + await vscode.window + .showInputBox({ + password: true, + placeHolder: `Password for user '${server.username}' on InterSystems server '${name}'`, + validateInput: (value => { + return value.length > 0 ? '' : 'Mandatory field'; + }), + ignoreFocusOut: true, + }) + .then((password) => { + if (password && server) { + server.password = password; + credentialCache[name] = {username: server.username, password: password}; + } else { + server = undefined; + } + }) + } + return server; +} diff --git a/src/api/pickServer.ts b/src/api/pickServer.ts new file mode 100644 index 0000000..225b402 --- /dev/null +++ b/src/api/pickServer.ts @@ -0,0 +1,19 @@ +import * as vscode from 'vscode'; +import { getServerNames } from './getServerNames'; + +export async function pickServer(scope?: vscode.ConfigurationScope, options: vscode.QuickPickOptions = {}): Promise { + const names = getServerNames(scope); + + let qpItems: vscode.QuickPickItem[] = []; + + options.matchOnDescription = options?.matchOnDescription || true; + options.placeHolder = options?.placeHolder || 'Pick an InterSystems server'; + options.canPickMany = false; + + names.forEach(element => { + qpItems.push({label: element.name, description: element.description, detail: options?.matchOnDetail ? element.detail : undefined}); + }); + return await vscode.window.showQuickPick(qpItems, options).then(item => { + return item ? item.label : undefined; + }); +} \ No newline at end of file diff --git a/src/commands/managePasswords.ts b/src/commands/managePasswords.ts new file mode 100644 index 0000000..3776bd4 --- /dev/null +++ b/src/commands/managePasswords.ts @@ -0,0 +1,59 @@ +import * as vscode from 'vscode'; +import { extensionId } from '../extension'; +import { Keychain } from '../keychain'; +import { credentialCache } from '../api/getServerSpec'; + +export async function storePassword() { + const name = await commonPickServer({matchOnDetail: true}); + if (name) { + await vscode.window + .showInputBox({ + password: true, + placeHolder: 'Password to store in keychain', + prompt: `For connection to InterSystems server '${name}'`, + validateInput: (value => { + return value.length > 0 ? '' : 'Mandatory field'; + }), + ignoreFocusOut: true, + }) + .then((password) => { + if (password) { + credentialCache[name] = undefined; + new Keychain(name).setPassword(password).then(() => { + vscode.window.showInformationMessage(`Password for '${name}' stored in keychain.`); + }); + } + }) + + } +} + +export async function clearPassword() { + const name = await commonPickServer({matchOnDetail: true}); + if (name) { + credentialCache[name] = undefined; + const keychain = new Keychain(name); + if (!await keychain.getPassword()) { + vscode.window.showWarningMessage(`No password for '${name}' found in keychain.`); + } else if (await keychain.deletePassword()) { + vscode.window.showInformationMessage(`Password for '${name}' removed from keychain.`); + } else { + vscode.window.showWarningMessage(`Failed to remove password for '${name}' from keychain.`); + } + } +} + +async function commonPickServer(options?: vscode.QuickPickOptions): Promise { + // Deliberately uses its own API to illustrate how other extensions would + const serverManagerExtension = vscode.extensions.getExtension(extensionId); + if (!serverManagerExtension) { + vscode.window.showErrorMessage(`Extension '${extensionId}' is not installed, or has been disabled.`) + return; + } + if (!serverManagerExtension.isActive) { + serverManagerExtension.activate(); + } + const myApi = serverManagerExtension.exports; + + return await myApi.pickServer(undefined, options); +} \ No newline at end of file diff --git a/src/commands/testPickServer.ts b/src/commands/testPickServer.ts new file mode 100644 index 0000000..f334b51 --- /dev/null +++ b/src/commands/testPickServer.ts @@ -0,0 +1,35 @@ +import * as vscode from 'vscode'; +import { extensionId, ServerSpec } from '../extension'; + +export async function testPickServer() { + await commonTestPickServer(); +} + +export async function testPickServerWithoutCachedCredentials() { + await commonTestPickServer(undefined, true); +} + +export async function testPickServerDetailed() { + await commonTestPickServer({matchOnDetail: true}); +} + +async function commonTestPickServer(options?: vscode.QuickPickOptions, flushCredentialCache: boolean = false) { + // Deliberately uses its own API in the same way as other extensions would + const serverManagerExtension = vscode.extensions.getExtension(extensionId); + if (!serverManagerExtension) { + vscode.window.showErrorMessage(`Extension '${extensionId}' is not installed, or has been disabled.`) + return + } + if (!serverManagerExtension.isActive) { + serverManagerExtension.activate(); + } + const myApi = serverManagerExtension.exports; + + const name: string = await myApi.pickServer(undefined, options); + if (name) { + const connSpec: ServerSpec = await myApi.getServerSpec(name, undefined, flushCredentialCache); + if (connSpec) { + vscode.window.showInformationMessage(`Picked server '${connSpec.name}' at ${connSpec.webServer.scheme}://${connSpec.webServer.host}:${connSpec.webServer.port}/${connSpec.webServer.pathPrefix} ${!connSpec.username ? 'with unauthenticated access' : 'as user ' + connSpec.username }.`, 'OK'); + } + } +} \ No newline at end of file diff --git a/src/extension.ts b/src/extension.ts index b57bb6e..ce5c3b0 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,9 +1,82 @@ 'use strict'; -export const extensionId = "intersystems-community.servermanager"; +export const extensionId = 'intersystems-community.servermanager'; import * as vscode from 'vscode'; +import { testPickServer, testPickServerWithoutCachedCredentials as testPickServerFlushingCachedCredentials, testPickServerDetailed } from './commands/testPickServer'; +import { pickServer } from './api/pickServer'; +import { getServerNames } from './api/getServerNames'; +import { getServerSpec } from './api/getServerSpec'; +import { storePassword, clearPassword } from './commands/managePasswords'; + +export interface ServerName { + name: string, + description: string, + detail: string +} + +export interface WebServerSpec { + scheme: string, + host: string, + port: number, + pathPrefix: string +} + +export interface ServerSpec { + name: string, + webServer: WebServerSpec, + username: string, + password: string, + description: string +} export function activate(context: vscode.ExtensionContext) { + + + // Register the commands + context.subscriptions.push( + vscode.commands.registerCommand(`${extensionId}.storePassword`, () => { + storePassword(); + }) + ); + context.subscriptions.push( + vscode.commands.registerCommand(`${extensionId}.clearPassword`, () => { + clearPassword(); + }) + ); + + context.subscriptions.push( + vscode.commands.registerCommand(`${extensionId}.testPickServer`, () => { + testPickServer(); + }) + ); + context.subscriptions.push( + vscode.commands.registerCommand(`${extensionId}.testPickServerFlushingCachedCredentials`, () => { + testPickServerFlushingCachedCredentials(); + }) + ); + context.subscriptions.push( + vscode.commands.registerCommand(`${extensionId}.testPickServerDetailed`, () => { + testPickServerDetailed(); + }) + ); + + let api = { + async pickServer(scope?: vscode.ConfigurationScope, options: vscode.QuickPickOptions = {}): Promise { + return await pickServer(scope, options); + + }, + getServerNames(scope?: vscode.ConfigurationScope): ServerName[] { + return getServerNames(scope); + }, + + async getServerSpec(name: string, scope?: vscode.ConfigurationScope, flushCredentialCache: boolean = false): Promise { + return await getServerSpec(name, scope, flushCredentialCache); + } + + }; + + // 'export' public api-surface + return api; } export function deactivate() { diff --git a/src/keychain.ts b/src/keychain.ts new file mode 100644 index 0000000..06faea3 --- /dev/null +++ b/src/keychain.ts @@ -0,0 +1,67 @@ +// Access the keytar native module shipped in vscode +import type * as keytarType from 'keytar'; +import * as vscode from 'vscode'; +import logger from './logger'; +import { extensionId } from './extension'; + +function getKeytar(): Keytar | undefined { + try { + return require('keytar'); + } catch (err) { + console.log(err); + } + + return undefined; +} + +export type Keytar = { + getPassword: typeof keytarType['getPassword']; + setPassword: typeof keytarType['setPassword']; + deletePassword: typeof keytarType['deletePassword']; +}; + +export class Keychain { + private keytar: Keytar; + private serviceId: string; + private accountId: string; + + constructor(connectionName: string) { + const keytar = getKeytar(); + if (!keytar) { + throw new Error('System keychain unavailable'); + } + + this.keytar = keytar; + this.serviceId = `${vscode.env.uriScheme}-${extensionId}:password`; + this.accountId = connectionName; + } + + async setPassword(password: string): Promise { + try { + return await this.keytar.setPassword(this.serviceId, this.accountId, password); + } catch (e) { + // Ignore + await vscode.window.showErrorMessage(`Writing password to the keychain failed with error '{0}'.`, e.message); + } + } + + async getPassword(): Promise { + try { + return await this.keytar.getPassword(this.serviceId, this.accountId); + } catch (e) { + // Ignore + logger.error(`Getting password failed: ${e}`); + return Promise.resolve(undefined); + } + } + + async deletePassword(): Promise { + try { + return await this.keytar.deletePassword(this.serviceId, this.accountId); + } catch (e) { + // Ignore + logger.error(`Deleting password failed: ${e}`); + return Promise.resolve(undefined); + } + } +} \ No newline at end of file diff --git a/src/logger.ts b/src/logger.ts new file mode 100644 index 0000000..cf6c404 --- /dev/null +++ b/src/logger.ts @@ -0,0 +1,74 @@ +import * as vscode from 'vscode'; + +export interface Logger { + trace(message: string, ...args: any[]): void; + debug(message: string, ...args: any[]): void; + info(message: string, ...args: any[]): void; + warn(message: string, ...args: any[]): void; + error(message: string | Error, ...args: any[]): void; + critical(message: string | Error, ...args: any[]): void; +} + +class VSCodeLogger implements Logger { + private _outputChannel: vscode.OutputChannel; + private _createDate: Date = new Date(); + + trace(message: string, ...args: any[]):Date { + return this._commonTrace(undefined, message, ...args); + } + + endTrace(since: Date, message: string, ...args: any[]):Date { + return this._commonTrace(since, message, ...args); + } + + private _commonTrace(since: Date | undefined, message: string, ...args: any[]):Date { + const now = new Date(); + const sinceStart = `${((now.valueOf() - this._createDate.valueOf()) / 1000).toFixed(3)} `; + const sinceLastTrace = (since) ? `(+${(now.valueOf() - since.valueOf()) / 1000}) ` : ''; + this._print(`[trace@${sinceStart}${sinceLastTrace}]`, message, ...args); + return now; + } + + debug(message: string, ...args: any[]) { + this._print('[debug]', message, ...args); + } + + info(message: string, ...args: any[]) { + this._print('[info]', message, ...args); + } + + warn(message: string, ...args: any[]) { + this._print('[warn]', message, ...args); + } + + error(message: string | Error, ...args: any[]) { + this._print('[error]', message, ...args); + } + + critical(message: string | Error, ...args: any[]) { + this._print('[critical]', message, ...args); + } + + private _print(...args: any[]) { + if (!this._outputChannel) { + this._outputChannel = vscode.window.createOutputChannel('InterSystems Server Manager'); + } + + const msg = args + .map(arg => { + if (arg instanceof Error) { + return arg.stack; + } else if (typeof arg === 'object') { + return JSON.stringify(arg); + } + return arg; + }) + .join(' '); + + this._outputChannel.appendLine(msg); + } +} + +const logger = new VSCodeLogger(); + +export default logger; diff --git a/src/test/suite/extension.test.ts b/src/test/suite/extension.test.ts index 5c708d1..b35569e 100644 --- a/src/test/suite/extension.test.ts +++ b/src/test/suite/extension.test.ts @@ -8,7 +8,7 @@ import { extensionId } from "../../extension"; suite("Extension Test Suite", () => { suiteSetup(async function () { - // make sure git is activated + // make sure extension is activated const ext = extensions.getExtension(extensionId); await ext?.activate(); });