diff --git a/.eslintrc.json b/.eslintrc.json index 51e623e8f1bdc..ac07b6d0e48cd 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -825,10 +825,12 @@ "target": "**/vs/server/**", "restrictions": [ "vs/nls", + "**/vs/code/**/{common,server,browser,node,electron-sandbox,electron-browser}/**", "**/vs/base/**/{common,node}/**", "**/vs/base/parts/**/{common,node}/**", "**/vs/platform/**/{common,node}/**", "**/vs/workbench/**/{common,node}/**", + "**/vs/workbench/workbench.web.api", "**/vs/server/**", "*" // node modules ] diff --git a/.gitignore b/.gitignore index 11a7486bf533c..1eb2d8963240c 100644 --- a/.gitignore +++ b/.gitignore @@ -7,7 +7,8 @@ node_modules/ extensions/**/dist/ /out*/ /extensions/**/out/ -src/vs/server +# @coder: The server directory is omitted upstream. +# src/vs/server resources/server build/node_modules coverage/ diff --git a/.vscode/settings.json b/.vscode/settings.json index a97841683c0ef..d0ffadd1d5d69 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -84,6 +84,12 @@ "editor.defaultFormatter": "vscode.typescript-language-features", "editor.formatOnSave": true, }, + "typescript.format.insertSpaceAfterConstructor": false, + "javascript.format.insertSpaceAfterConstructor": false, + "javascript.format.insertSpaceBeforeFunctionParenthesis": false, + "typescript.format.insertSpaceBeforeFunctionParenthesis": false, + "typescript.format.insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets": false, + "javascript.format.insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets": false, "typescript.tsc.autoDetect": "off", "notebook.experimental.useMarkdownRenderer": true, "testing.autoRun.mode": "rerun", diff --git a/build/hygiene.js b/build/hygiene.js index 9d6756dbc86d7..024d12c0c2616 100644 --- a/build/hygiene.js +++ b/build/hygiene.js @@ -19,6 +19,18 @@ const copyrightHeaderLines = [ ' *--------------------------------------------------------------------------------------------*/', ]; +/** + * @remark While this helps delineate Coder's additions to the upstream project, + * this notice should be examined within the context of the application. + * Code from both maintainers often overlaps. + */ +const coderCopyrightHeaderLines = [ + '/*---------------------------------------------------------------------------------------------', + ' * Copyright (c) Coder Technologies. All rights reserved.', + ' * Licensed under the MIT License. See License.txt in the project root for license information.', + ' *--------------------------------------------------------------------------------------------*/', +]; + function hygiene(some, linting = true) { const gulpeslint = require('gulp-eslint'); const tsfmt = require('typescript-formatter'); @@ -62,7 +74,7 @@ function hygiene(some, linting = true) { const lines = file.__lines; for (let i = 0; i < copyrightHeaderLines.length; i++) { - if (lines[i] !== copyrightHeaderLines[i]) { + if (lines[i] !== copyrightHeaderLines[i] && lines[i] !== coderCopyrightHeaderLines[i]) { console.error(file.relative + ': Missing or bad copyright statement'); errorCount++; break; diff --git a/build/lib/extensions.ts b/build/lib/extensions.ts index bd7b508bb03c5..dcfaeb01656ea 100644 --- a/build/lib/extensions.ts +++ b/build/lib/extensions.ts @@ -67,7 +67,7 @@ function fromLocal(extensionPath: string, forWeb: boolean): Stream { if (isWebPacked) { input = updateExtensionPackageJSON(input, (data: any) => { delete data.scripts; - delete data.dependencies; + // https://github.com/cdr/code-server/pull/2041#issuecomment-685910322 delete data.devDependencies; if (data.main) { data.main = data.main.replace('/out/', /dist/); diff --git a/build/lib/util.js b/build/lib/util.js index 8d0294e4cebc0..b58fb754d2bb8 100644 --- a/build/lib/util.js +++ b/build/lib/util.js @@ -269,6 +269,8 @@ function streamToPromise(stream) { } exports.streamToPromise = streamToPromise; function getElectronVersion() { + // NOTE@coder: Fix version due to .yarnrc removal. + return process.versions.node; const yarnrc = fs.readFileSync(path.join(root, '.yarnrc'), 'utf8'); const target = /^target "(.*)"$/m.exec(yarnrc)[1]; return target; diff --git a/build/package.json b/build/package.json index c1910d8b2012a..2a38c628d8b68 100644 --- a/build/package.json +++ b/build/package.json @@ -3,8 +3,6 @@ "version": "1.0.0", "license": "MIT", "devDependencies": { - "@azure/cosmos": "^3.9.3", - "@azure/storage-blob": "^12.4.0", "@types/ansi-colors": "^3.2.0", "@types/azure": "0.9.19", "@types/byline": "^4.2.32", @@ -37,7 +35,6 @@ "@typescript-eslint/experimental-utils": "~2.13.0", "@typescript-eslint/parser": "^3.3.0", "applicationinsights": "1.0.8", - "azure-storage": "^2.1.0", "byline": "^5.0.0", "colors": "^1.4.0", "commander": "^7.0.0", @@ -50,7 +47,6 @@ "mime": "^1.4.1", "mkdirp": "^1.0.4", "p-limit": "^3.1.0", - "plist": "^3.0.1", "source-map": "0.6.1", "typescript": "^4.4.0-dev.20210607", "vsce": "1.48.0", diff --git a/build/yarn.lock b/build/yarn.lock index ef2db1ff90cde..5f866f43e1ddc 100644 --- a/build/yarn.lock +++ b/build/yarn.lock @@ -2,112 +2,6 @@ # yarn lockfile v1 -"@azure/abort-controller@^1.0.0": - version "1.0.2" - resolved "https://registry.yarnpkg.com/@azure/abort-controller/-/abort-controller-1.0.2.tgz#822405c966b2aec16fb62c1b19d37eaccf231995" - integrity sha512-XUyTo+bcyxHEf+jlN2MXA7YU9nxVehaubngHV1MIZZaqYmZqykkoeAz/JMMEeR7t3TcyDwbFa3Zw8BZywmIx4g== - dependencies: - tslib "^2.0.0" - -"@azure/core-asynciterator-polyfill@^1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@azure/core-asynciterator-polyfill/-/core-asynciterator-polyfill-1.0.0.tgz#dcccebb88406e5c76e0e1d52e8cc4c43a68b3ee7" - integrity sha512-kmv8CGrPfN9SwMwrkiBK9VTQYxdFQEGe0BmQk+M8io56P9KNzpAxcWE/1fxJj7uouwN4kXF0BHW8DNlgx+wtCg== - -"@azure/core-auth@^1.1.3": - version "1.1.4" - resolved "https://registry.yarnpkg.com/@azure/core-auth/-/core-auth-1.1.4.tgz#af9a334acf3cb9c49e6013e6caf6dc9d43476030" - integrity sha512-+j1embyH1jqf04AIfJPdLafd5SC1y6z1Jz4i+USR1XkTp6KM8P5u4/AjmWMVoEQdM/M29PJcRDZcCEWjK9S1bw== - dependencies: - "@azure/abort-controller" "^1.0.0" - tslib "^2.0.0" - -"@azure/core-http@^1.2.0": - version "1.2.2" - resolved "https://registry.yarnpkg.com/@azure/core-http/-/core-http-1.2.2.tgz#a6f7717184fd2657d3acabd1d64dfdc0bd531ce3" - integrity sha512-9eu2OcbR7e44gqBy4U1Uv8NTWgLIMwKXMEGgO2MahsJy5rdTiAhs5fJHQffPq8uX2MFh21iBODwO9R/Xlov88A== - dependencies: - "@azure/abort-controller" "^1.0.0" - "@azure/core-auth" "^1.1.3" - "@azure/core-tracing" "1.0.0-preview.9" - "@azure/logger" "^1.0.0" - "@opentelemetry/api" "^0.10.2" - "@types/node-fetch" "^2.5.0" - "@types/tunnel" "^0.0.1" - form-data "^3.0.0" - node-fetch "^2.6.0" - process "^0.11.10" - tough-cookie "^4.0.0" - tslib "^2.0.0" - tunnel "^0.0.6" - uuid "^8.3.0" - xml2js "^0.4.19" - -"@azure/core-lro@^1.0.2": - version "1.0.3" - resolved "https://registry.yarnpkg.com/@azure/core-lro/-/core-lro-1.0.3.tgz#1ddfb4ecdb81ce87b5f5d972ffe2acbbc46e524e" - integrity sha512-Py2crJ84qx1rXkzIwfKw5Ni4WJuzVU7KAF6i1yP3ce8fbynUeu8eEWS4JGtSQgU7xv02G55iPDROifmSDbxeHA== - dependencies: - "@azure/abort-controller" "^1.0.0" - "@azure/core-http" "^1.2.0" - events "^3.0.0" - tslib "^2.0.0" - -"@azure/core-paging@^1.1.1": - version "1.1.3" - resolved "https://registry.yarnpkg.com/@azure/core-paging/-/core-paging-1.1.3.tgz#3587c9898a0530cacb64bab216d7318468aa5efc" - integrity sha512-his7Ah40ThEYORSpIAwuh6B8wkGwO/zG7gqVtmSE4WAJ46e36zUDXTKReUCLBDc6HmjjApQQxxcRFy5FruG79A== - dependencies: - "@azure/core-asynciterator-polyfill" "^1.0.0" - -"@azure/core-tracing@1.0.0-preview.9": - version "1.0.0-preview.9" - resolved "https://registry.yarnpkg.com/@azure/core-tracing/-/core-tracing-1.0.0-preview.9.tgz#84f3b85572013f9d9b85e1e5d89787aa180787eb" - integrity sha512-zczolCLJ5QG42AEPQ+Qg9SRYNUyB+yZ5dzof4YEc+dyWczO9G2sBqbAjLB7IqrsdHN2apkiB2oXeDKCsq48jug== - dependencies: - "@opencensus/web-types" "0.0.7" - "@opentelemetry/api" "^0.10.2" - tslib "^2.0.0" - -"@azure/cosmos@^3.9.3": - version "3.9.3" - resolved "https://registry.yarnpkg.com/@azure/cosmos/-/cosmos-3.9.3.tgz#7e95ff92e5c3e9da7e8316bc50c9cc928be6c1d6" - integrity sha512-1mh8a6LAIykz24tJvQpafXiABUfq+HSAZBFJVZXea0Rd0qG8Ia9z8AK9FtPbC1nPvDC2RID2mRIjJvYbxRM/BA== - dependencies: - "@types/debug" "^4.1.4" - debug "^4.1.1" - fast-json-stable-stringify "^2.0.0" - jsbi "^3.1.3" - node-abort-controller "^1.0.4" - node-fetch "^2.6.0" - priorityqueuejs "^1.0.0" - semaphore "^1.0.5" - tslib "^2.0.0" - universal-user-agent "^6.0.0" - uuid "^8.3.0" - -"@azure/logger@^1.0.0": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@azure/logger/-/logger-1.0.1.tgz#19b333203d1b2931353d8879e814b64a7274837a" - integrity sha512-QYQeaJ+A5x6aMNu8BG5qdsVBnYBop9UMwgUvGihSjf1PdZZXB+c/oMdM2ajKwzobLBh9e9QuMQkN9iL+IxLBLA== - dependencies: - tslib "^2.0.0" - -"@azure/storage-blob@^12.4.0": - version "12.4.0" - resolved "https://registry.yarnpkg.com/@azure/storage-blob/-/storage-blob-12.4.0.tgz#7127ddd9f413105e2c3688691bc4c6245d0806b3" - integrity sha512-OnhVSoKD1HzBB79/rFzPbC4w9TdzFXeoOwkX+aIu3rb8qvN0VaqvUqZXSrBCyG2LcLyVkY4MPCJQBrmEUm9kvw== - dependencies: - "@azure/abort-controller" "^1.0.0" - "@azure/core-http" "^1.2.0" - "@azure/core-lro" "^1.0.2" - "@azure/core-paging" "^1.1.1" - "@azure/core-tracing" "1.0.0-preview.9" - "@azure/logger" "^1.0.0" - "@opentelemetry/api" "^0.10.2" - events "^3.0.0" - tslib "^2.0.0" - "@malept/cross-spawn-promise@^1.1.0": version "1.1.1" resolved "https://registry.yarnpkg.com/@malept/cross-spawn-promise/-/cross-spawn-promise-1.1.1.tgz#504af200af6b98e198bce768bc1730c6936ae01d" @@ -115,23 +9,6 @@ dependencies: cross-spawn "^7.0.1" -"@opencensus/web-types@0.0.7": - version "0.0.7" - resolved "https://registry.yarnpkg.com/@opencensus/web-types/-/web-types-0.0.7.tgz#4426de1fe5aa8f624db395d2152b902874f0570a" - integrity sha512-xB+w7ZDAu3YBzqH44rCmG9/RlrOmFuDPt/bpf17eJr8eZSrLt7nc7LnWdxM9Mmoj/YKMHpxRg28txu3TcpiL+g== - -"@opentelemetry/api@^0.10.2": - version "0.10.2" - resolved "https://registry.yarnpkg.com/@opentelemetry/api/-/api-0.10.2.tgz#9647b881f3e1654089ff7ea59d587b2d35060654" - integrity sha512-GtpMGd6vkzDMYcpu2t9LlhEgMy/SzBwRnz48EejlRArYqZzqSzAsKmegUK7zHgl+EOIaK9mKHhnRaQu3qw20cA== - dependencies: - "@opentelemetry/context-base" "^0.10.2" - -"@opentelemetry/context-base@^0.10.2": - version "0.10.2" - resolved "https://registry.yarnpkg.com/@opentelemetry/context-base/-/context-base-0.10.2.tgz#55bea904b2b91aa8a8675df9eaba5961bddb1def" - integrity sha512-hZNKjKOYsckoOEgBziGMnBcX0M7EtstnCmwz5jZUOUYwlZ+/xxX6z3jPu1XVO2Jivk0eLfuP9GP+vFD49CMetw== - "@sindresorhus/is@^4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-4.0.0.tgz#2ff674e9611b45b528896d820d3d7a812de2f0e4" @@ -191,7 +68,7 @@ resolved "https://registry.yarnpkg.com/@types/debounce/-/debounce-1.0.0.tgz#417560200331e1bb84d72da85391102c2fcd61b7" integrity sha1-QXVgIAMx4buE1y2oU5EQLC/NYbc= -"@types/debug@^4.1.4", "@types/debug@^4.1.5": +"@types/debug@^4.1.5": version "4.1.5" resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.5.tgz#b14efa8852b7768d898906613c23f688713e02cd" integrity sha512-Q1y515GcOdTHgagaVFhHnIFQ38ygs/kmxdNpvpou+raI9UO3YZcHDngBSYKQklcKlvA7iuQlmIKbzvmxcOE9CQ== @@ -358,14 +235,6 @@ resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-8.2.0.tgz#3eb56d13a1de1d347ecb1957c6860c911704bc44" integrity sha512-/Sge3BymXo4lKc31C8OINJgXLaw+7vL1/L1pGiBNpGrBiT8FQiaFpSYV0uhTaG4y78vcMBTMFsWaHDvuD+xGzQ== -"@types/node-fetch@^2.5.0": - version "2.5.8" - resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.5.8.tgz#e199c835d234c7eb0846f6618012e558544ee2fb" - integrity sha512-fbjI6ja0N5ZA8TV53RUqzsKNkl9fv8Oj3T7zxW7FGv1GSH7gwJaNF8dzCjrqKaxKeUpTz4yT1DaJFq/omNpGfw== - dependencies: - "@types/node" "*" - form-data "^3.0.0" - "@types/node@*": version "8.0.51" resolved "https://registry.yarnpkg.com/@types/node/-/node-8.0.51.tgz#b31d716fb8d58eeb95c068a039b9b6292817d5fb" @@ -447,13 +316,6 @@ resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-2.3.2.tgz#e0d481d8bb282ad8a8c9e100ceb72c995fb5e709" integrity sha512-vOVmaruQG5EatOU/jM6yU2uCp3Lz6mK1P5Ztu4iJjfM4SVHU9XYktPUQtKlIXuahqXHdEyUarMrBEwg5Cwu+bA== -"@types/tunnel@^0.0.1": - version "0.0.1" - resolved "https://registry.yarnpkg.com/@types/tunnel/-/tunnel-0.0.1.tgz#0d72774768b73df26f25df9184273a42da72b19c" - integrity sha512-AOqu6bQu5MSWwYvehMXLukFHnupHrpZ8nvgae5Ggie9UwzDR1CCwoXgSSWNZJuyOlCdfdsWMA5F2LlmvyoTv8A== - dependencies: - "@types/node" "*" - "@types/underscore@^1.8.9": version "1.8.9" resolved "https://registry.yarnpkg.com/@types/underscore/-/underscore-1.8.9.tgz#fef41f800cd23db1b4f262ddefe49cd952d82323" @@ -564,16 +426,6 @@ dependencies: eslint-visitor-keys "^1.1.0" -ajv@^6.12.3: - version "6.12.6" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" - integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== - dependencies: - fast-deep-equal "^3.1.1" - fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.4.1" - uri-js "^4.2.2" - applicationinsights@1.0.8: version "1.0.8" resolved "https://registry.yarnpkg.com/applicationinsights/-/applicationinsights-1.0.8.tgz#db6e3d983cf9f9405fe1ee5ba30ac6e1914537b5" @@ -602,71 +454,20 @@ asar@^3.0.3: optionalDependencies: "@types/glob" "^7.1.1" -asn1@~0.2.3: - version "0.2.4" - resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136" - integrity sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg== - dependencies: - safer-buffer "~2.1.0" - -assert-plus@1.0.0, assert-plus@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" - integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU= - -asynckit@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" - integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= - at-least-node@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2" integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg== -aws-sign2@~0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" - integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg= - -aws4@^1.8.0: - version "1.11.0" - resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59" - integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA== - -azure-storage@^2.1.0: - version "2.10.3" - resolved "https://registry.yarnpkg.com/azure-storage/-/azure-storage-2.10.3.tgz#c5966bf929d87587d78f6847040ea9a4b1d4a50a" - integrity sha512-IGLs5Xj6kO8Ii90KerQrrwuJKexLgSwYC4oLWmc11mzKe7Jt2E5IVg+ZQ8K53YWZACtVTMBNO3iGuA+4ipjJxQ== - dependencies: - browserify-mime "~1.2.9" - extend "^3.0.2" - json-edm-parser "0.1.2" - md5.js "1.3.4" - readable-stream "~2.0.0" - request "^2.86.0" - underscore "~1.8.3" - uuid "^3.0.0" - validator "~9.4.1" - xml2js "0.2.8" - xmlbuilder "^9.0.7" - balanced-match@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= -base64-js@^1.2.3: - version "1.3.1" - resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1" - integrity sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g== - -bcrypt-pbkdf@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" - integrity sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4= - dependencies: - tweetnacl "^0.14.3" +base64-js@^1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== bluebird@^3.5.0: version "3.7.2" @@ -686,11 +487,6 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" -browserify-mime@~1.2.9: - version "1.2.9" - resolved "https://registry.yarnpkg.com/browserify-mime/-/browserify-mime-1.2.9.tgz#aeb1af28de6c0d7a6a2ce40adb68ff18422af31f" - integrity sha1-rrGvKN5sDXpqLOQK22j/GEIq8x8= - buffer-alloc-unsafe@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz#bd7dc26ae2972d0eda253be061dba992349c19f0" @@ -742,11 +538,6 @@ cacheable-request@^7.0.1: normalize-url "^4.1.0" responselike "^2.0.0" -caseless@~0.12.0: - version "0.12.0" - resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" - integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= - cheerio@^1.0.0-rc.1: version "1.0.0-rc.2" resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-1.0.0-rc.2.tgz#4b9f53a81b27e4d5dac31c0ffd0cfa03cc6830db" @@ -781,13 +572,6 @@ colors@^1.4.0: resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA== -combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6: - version "1.0.8" - resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" - integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== - dependencies: - delayed-stream "~1.0.0" - commander@2.9.0: version "2.9.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.9.0.tgz#9c99094176e12240cb22d6c5146098400fe0f7d4" @@ -796,9 +580,9 @@ commander@2.9.0: graceful-readlink ">= 1.0.0" commander@^2.8.1: - version "2.19.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.19.0.tgz#f6198aa84e5b83c46054b94ddedbfed5ee9ff12a" - integrity sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg== + version "2.20.3" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" + integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== commander@^5.0.0: version "5.1.0" @@ -820,11 +604,6 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= -core-util-is@1.0.2, core-util-is@~1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" - integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= - cross-spawn@^7.0.1: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" @@ -849,13 +628,6 @@ css-what@2.1: resolved "https://registry.yarnpkg.com/css-what/-/css-what-2.1.2.tgz#c0876d9d0480927d7d4920dcd72af3595649554d" integrity sha512-wan8dMWQ0GUeF7DGEPVjhHemVW/vy6xUYmFzRY8RYqgA0JtXC9rJmbScBjqSu6dg9q0lwPQy6ZAmJVr3PPTvqQ== -dashdash@^1.12.0: - version "1.14.1" - resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" - integrity sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA= - dependencies: - assert-plus "^1.0.0" - debug@^2.6.8: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" @@ -882,11 +654,6 @@ defer-to-connect@^2.0.0: resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-2.0.0.tgz#83d6b199db041593ac84d781b5222308ccf4c2c1" integrity sha512-bYL2d05vOSf1JEZNx5vSAtPuBMkX8K9EUutg7zlKvTqKXHt7RhWJFbmd7qakVuf13i+IkGmp6FwSsONOf6VYIg== -delayed-stream@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" - integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= - denodeify@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/denodeify/-/denodeify-1.2.1.tgz#3a36287f5034e699e7577901052c2e6c94251631" @@ -960,18 +727,10 @@ domutils@^1.5.1: dom-serializer "0" domelementtype "1" -ecc-jsbn@~0.1.1: - version "0.1.2" - resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" - integrity sha1-OoOpBOVDUyh4dMVkt1SThoSamMk= - dependencies: - jsbn "~0.1.0" - safer-buffer "^2.1.0" - electron-osx-sign@^0.4.16: - version "0.4.16" - resolved "https://registry.yarnpkg.com/electron-osx-sign/-/electron-osx-sign-0.4.16.tgz#0be8e579b2d9fa4c12d2a21f063898294b3434aa" - integrity sha512-ziMWfc3NmQlwnWLW6EaZq8nH2BWVng/atX5GWsGwhexJYpdW6hsg//MkAfRTRx1kR3Veiqkeiog1ibkbA4x0rg== + version "0.4.17" + resolved "https://registry.yarnpkg.com/electron-osx-sign/-/electron-osx-sign-0.4.17.tgz#2727ca0c79e1e4e5ccd3861fb3da9c3c913b006c" + integrity sha512-wUJPmZJQCs1zgdlQgeIpRcvrf7M5/COQaOV68Va1J/SgmWx5KL2otgg+fAae7luw6qz9R8Gvu/Qpe9tAOu/3xQ== dependencies: bluebird "^3.5.0" compare-version "^0.1.2" @@ -993,9 +752,9 @@ entities@^1.1.1, entities@~1.1.1: integrity sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w== esbuild@^0.12.6: - version "0.12.6" - resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.12.6.tgz#85bc755c7cf3005d4f34b4f10f98049ce0ee67ce" - integrity sha512-RDvVLvAjsq/kIZJoneMiUOH7EE7t2QaW7T3Q7EdQij14+bZbDq5sndb0tTanmHIFSqZVMBMMyqzVHkS3dJobeA== + version "0.12.16" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.12.16.tgz#c397144ce13b445a6ead9c1f747da11f79ec5e67" + integrity sha512-XqI9cXP2bmQ6MREIqrYBb13KfYFSERsV1+e5jSVWps8dNlLZK+hln7d0mznzDIpfISsg/AgQW0DW3kSInXWhrg== eslint-scope@^5.0.0: version "5.0.0" @@ -1029,36 +788,6 @@ estraverse@^4.1.0, estraverse@^4.1.1: resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== -events@^3.0.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/events/-/events-3.2.0.tgz#93b87c18f8efcd4202a461aec4dfc0556b639379" - integrity sha512-/46HWwbfCX2xTawVfkKLGxMifJYQBWMwY1mjywRtb4c9x8l5NP3KoJtnIOiL1hfdRkIuYhETxQlo62IF8tcnlg== - -extend@^3.0.2, extend@~3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" - integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== - -extsprintf@1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" - integrity sha1-lpGEQOMEGnpBT4xS48V06zw+HgU= - -extsprintf@^1.2.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" - integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8= - -fast-deep-equal@^3.1.1: - version "3.1.3" - resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" - integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== - -fast-json-stable-stringify@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" - integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== - fd-slicer@~1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e" @@ -1066,29 +795,6 @@ fd-slicer@~1.1.0: dependencies: pend "~1.2.0" -forever-agent@~0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" - integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= - -form-data@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.0.tgz#31b7e39c85f1355b7139ee0c647cf0de7f83c682" - integrity sha512-CKMFDglpbMi6PyN+brwB9Q/GOw0eAnsrEZDgcsH5Krhz5Od/haKHAX0NmQfha2zPPz0JpWzA7GJHGSnvCRLWsg== - dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.8" - mime-types "^2.1.12" - -form-data@~2.3.2: - version "2.3.3" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" - integrity sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ== - dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.6" - mime-types "^2.1.12" - fs-extra@^9.0.1, fs-extra@^9.1.0: version "9.1.0" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d" @@ -1111,13 +817,6 @@ get-stream@^5.1.0: dependencies: pump "^3.0.0" -getpass@^0.1.1: - version "0.1.7" - resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" - integrity sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo= - dependencies: - assert-plus "^1.0.0" - glob@^7.0.6: version "7.1.3" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1" @@ -1169,28 +868,6 @@ graceful-fs@^4.1.6, graceful-fs@^4.2.0: resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725" integrity sha1-TK+tdrxi8C+gObL5Tpo906ORpyU= -har-schema@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" - integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI= - -har-validator@~5.1.3: - version "5.1.5" - resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.5.tgz#1f0803b9f8cb20c0fa13822df1ecddb36bde1efd" - integrity sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w== - dependencies: - ajv "^6.12.3" - har-schema "^2.0.0" - -hash-base@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.1.0.tgz#55c381d9e06e1d2997a883b4a3fddfe7f0d3af33" - integrity sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA== - dependencies: - inherits "^2.0.4" - readable-stream "^3.6.0" - safe-buffer "^5.2.0" - htmlparser2@^3.9.1: version "3.10.0" resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.10.0.tgz#5f5e422dcf6119c0d983ed36260ce9ded0bee464" @@ -1208,15 +885,6 @@ http-cache-semantics@^4.0.0: resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390" integrity sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ== -http-signature@~1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" - integrity sha1-muzZJRFHcvPZW2WmCruPfBj7rOE= - dependencies: - assert-plus "^1.0.0" - jsprim "^1.2.2" - sshpk "^1.7.0" - http2-wrapper@^1.0.0-beta.5.2: version "1.0.0-beta.5.2" resolved "https://registry.yarnpkg.com/http2-wrapper/-/http2-wrapper-1.0.0-beta.5.2.tgz#8b923deb90144aea65cf834b016a340fc98556f3" @@ -1238,7 +906,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@^2.0.1, inherits@^2.0.4, inherits@~2.0.1: +inherits@2, inherits@^2.0.1: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -1260,16 +928,6 @@ is-glob@^4.0.1: dependencies: is-extglob "^2.1.1" -is-typedarray@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" - integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= - -isarray@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" - integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= - isbinaryfile@^3.0.2: version "3.0.3" resolved "https://registry.yarnpkg.com/isbinaryfile/-/isbinaryfile-3.0.3.tgz#5d6def3edebf6e8ca8cae9c30183a804b5f8be80" @@ -1282,48 +940,11 @@ isexe@^2.0.0: resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= -isstream@~0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" - integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= - -jsbi@^3.1.3: - version "3.1.4" - resolved "https://registry.yarnpkg.com/jsbi/-/jsbi-3.1.4.tgz#9654dd02207a66a4911b4e4bb74265bc2cbc9dd0" - integrity sha512-52QRRFSsi9impURE8ZUbzAMCLjPm4THO7H2fcuIvaaeFTbSysvkodbQQXIVsNgq/ypDbq6dJiuGKL0vZ/i9hUg== - -jsbn@~0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" - integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= - json-buffer@3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== -json-edm-parser@0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/json-edm-parser/-/json-edm-parser-0.1.2.tgz#1e60b0fef1bc0af67bc0d146dfdde5486cd615b4" - integrity sha1-HmCw/vG8CvZ7wNFG393lSGzWFbQ= - dependencies: - jsonparse "~1.2.0" - -json-schema-traverse@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" - integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== - -json-schema@0.2.3: - version "0.2.3" - resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" - integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM= - -json-stringify-safe@~5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" - integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= - jsonc-parser@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-2.3.0.tgz#7c7fc988ee1486d35734faaaa866fadb00fa91ee" @@ -1338,21 +959,6 @@ jsonfile@^6.0.1: optionalDependencies: graceful-fs "^4.1.6" -jsonparse@~1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.2.0.tgz#5c0c5685107160e72fe7489bddea0b44c2bc67bd" - integrity sha1-XAxWhRBxYOcv50ib3eoLRMK8Z70= - -jsprim@^1.2.2: - version "1.4.1" - resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" - integrity sha1-MT5mvB5cwG5Di8G3SZwuXFastqI= - dependencies: - assert-plus "1.0.0" - extsprintf "1.3.0" - json-schema "0.2.3" - verror "1.10.0" - keyv@^4.0.0: version "4.0.3" resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.0.3.tgz#4f3aa98de254803cafcd2896734108daa35e4254" @@ -1400,31 +1006,11 @@ markdown-it@^8.3.1: mdurl "^1.0.1" uc.micro "^1.0.5" -md5.js@1.3.4: - version "1.3.4" - resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.4.tgz#e9bdbde94a20a5ac18b04340fc5764d5b09d901d" - integrity sha1-6b296UogpawYsENA/Fdk1bCdkB0= - dependencies: - hash-base "^3.0.0" - inherits "^2.0.1" - mdurl@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e" integrity sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4= -mime-db@1.45.0: - version "1.45.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.45.0.tgz#cceeda21ccd7c3a745eba2decd55d4b73e7879ea" - integrity sha512-CkqLUxUk15hofLoLyljJSrukZi8mAtgd+yE5uO4tqRZsdsAJKv0O+rFMhVDRJgozy+yG6md5KwuXhD4ocIoP+w== - -mime-types@^2.1.12, mime-types@~2.1.19: - version "2.1.28" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.28.tgz#1160c4757eab2c5363888e005273ecf79d2a0ecd" - integrity sha512-0TO2yJ5YHYr7M2zzT7gDU1tbwHxEUWBCLt0lscSNpcdAfFyJOVEpRYNS7EXVcTLNj/25QO8gulHC5JtTzSE2UQ== - dependencies: - mime-db "1.45.0" - mime@^1.3.4: version "1.4.1" resolved "https://registry.yarnpkg.com/mime/-/mime-1.4.1.tgz#121f9ebc49e3766f311a76e1fa1c8003c4b03aa6" @@ -1453,9 +1039,9 @@ minimatch@3.0.4, minimatch@^3.0.3, minimatch@^3.0.4: brace-expansion "^1.1.7" minimist@^1.2.0: - version "1.2.3" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.3.tgz#3db5c0765545ab8637be71f333a104a965a9ca3f" - integrity sha512-+bMdgqjMN/Z77a6NlY/I3U5LlRDbnmaAk6lDveAPKwSpcPM4tKAuYsvYF8xjhOPXhOYGe/73vVLVez5PW+jqhw== + version "1.2.5" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" + integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== mkdirp@^1.0.4: version "1.0.4" @@ -1477,16 +1063,6 @@ mute-stream@~0.0.4: resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" integrity sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s= -node-abort-controller@^1.0.4: - version "1.1.0" - resolved "https://registry.yarnpkg.com/node-abort-controller/-/node-abort-controller-1.1.0.tgz#8a734a631b022af29963be7245c1483cbb9e070d" - integrity sha512-dEYmUqjtbivotqjraOe8UvhT/poFfog1BQRNsZm/MSEDDESk2cQ1tvD8kGyuN07TM/zoW+n42odL8zTeJupYdQ== - -node-fetch@^2.6.0: - version "2.6.1" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" - integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw== - normalize-url@^4.1.0: version "4.5.1" resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.1.tgz#0dd90cf1288ee1d1313b87081c9a5932ee48518a" @@ -1499,11 +1075,6 @@ nth-check@~1.0.1: dependencies: boolbase "~1.0.0" -oauth-sign@~0.9.0: - version "0.9.0" - resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" - integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== - once@^1.3.0, once@^1.3.1, once@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" @@ -1570,39 +1141,14 @@ pend@~1.2.0: resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" integrity sha1-elfrVQpng/kRUzH89GY9XI4AelA= -performance-now@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" - integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= - plist@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/plist/-/plist-3.0.1.tgz#a9b931d17c304e8912ef0ba3bdd6182baf2e1f8c" - integrity sha512-GpgvHHocGRyQm74b6FWEZZVRroHKE1I0/BTjAmySaohK+cUn+hZpbqXkc3KWgW3gQYkqcQej35FohcT0FRlkRQ== + version "3.0.2" + resolved "https://registry.yarnpkg.com/plist/-/plist-3.0.2.tgz#74bbf011124b90421c22d15779cee60060ba95bc" + integrity sha512-MSrkwZBdQ6YapHy87/8hDU8MnIcyxBKjeF+McXnr5A9MtffPewTs7G3hlpodT5TacyfIyFTaJEhh3GGcmasTgQ== dependencies: - base64-js "^1.2.3" + base64-js "^1.5.1" xmlbuilder "^9.0.7" - xmldom "0.1.x" - -priorityqueuejs@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/priorityqueuejs/-/priorityqueuejs-1.0.0.tgz#2ee4f23c2560913e08c07ce5ccdd6de3df2c5af8" - integrity sha1-LuTyPCVgkT4IwHzlzN1t498sWvg= - -process-nextick-args@~1.0.6: - version "1.0.7" - resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3" - integrity sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M= - -process@^0.11.10: - version "0.11.10" - resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" - integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI= - -psl@^1.1.28, psl@^1.1.33: - version "1.8.0" - resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24" - integrity sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ== + xmldom "^0.5.0" pump@^3.0.0: version "3.0.0" @@ -1612,21 +1158,11 @@ pump@^3.0.0: end-of-stream "^1.1.0" once "^1.3.1" -punycode@^2.1.0, punycode@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" - integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== - q@^1.0.1: version "1.5.1" resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" integrity sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc= -qs@~6.5.2: - version "6.5.2" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" - integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== - quick-lru@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932" @@ -1648,53 +1184,6 @@ readable-stream@^3.0.6: string_decoder "^1.1.1" util-deprecate "^1.0.1" -readable-stream@^3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" - integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== - dependencies: - inherits "^2.0.3" - string_decoder "^1.1.1" - util-deprecate "^1.0.1" - -readable-stream@~2.0.0: - version "2.0.6" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.0.6.tgz#8f90341e68a53ccc928788dacfcd11b36eb9b78e" - integrity sha1-j5A0HmilPMySh4jaz80Rs265t44= - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.1" - isarray "~1.0.0" - process-nextick-args "~1.0.6" - string_decoder "~0.10.x" - util-deprecate "~1.0.1" - -request@^2.86.0: - version "2.88.2" - resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" - integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== - dependencies: - aws-sign2 "~0.7.0" - aws4 "^1.8.0" - caseless "~0.12.0" - combined-stream "~1.0.6" - extend "~3.0.2" - forever-agent "~0.6.1" - form-data "~2.3.2" - har-validator "~5.1.3" - http-signature "~1.2.0" - is-typedarray "~1.0.0" - isstream "~0.1.2" - json-stringify-safe "~5.0.1" - mime-types "~2.1.19" - oauth-sign "~0.9.0" - performance-now "^2.1.0" - qs "~6.5.2" - safe-buffer "^5.1.2" - tough-cookie "~2.5.0" - tunnel-agent "^0.6.0" - uuid "^3.3.2" - resolve-alpn@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/resolve-alpn/-/resolve-alpn-1.0.0.tgz#745ad60b3d6aff4b4a48e01b8c0bdc70959e0e8c" @@ -1707,36 +1196,11 @@ responselike@^2.0.0: dependencies: lowercase-keys "^2.0.0" -safe-buffer@^5.0.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0: - version "5.2.1" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" - integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== - safe-buffer@~5.1.0: version "5.1.2" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== -safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: - version "2.1.2" - resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" - integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== - -sax@0.5.x: - version "0.5.8" - resolved "https://registry.yarnpkg.com/sax/-/sax-0.5.8.tgz#d472db228eb331c2506b0e8c15524adb939d12c1" - integrity sha1-1HLbIo6zMcJQaw6MFVJK25OdEsE= - -sax@>=0.6.0: - version "1.2.4" - resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" - integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== - -semaphore@^1.0.5: - version "1.1.0" - resolved "https://registry.yarnpkg.com/semaphore/-/semaphore-1.1.0.tgz#aaad8b86b20fe8e9b32b16dc2ee682a8cd26a8aa" - integrity sha512-O4OZEaNtkMd/K0i6js9SL+gqy0ZCBMgUvlSqHKi4IBdjhe7wB8pwztUk1BbZ1fmrvpwFrPbHzqd2w5pTcJH6LA== - semver@^5.1.0, semver@^5.3.0: version "5.6.0" resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004" @@ -1776,21 +1240,6 @@ sprintf-js@~1.0.2: resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= -sshpk@^1.7.0: - version "1.16.1" - resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877" - integrity sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg== - dependencies: - asn1 "~0.2.3" - assert-plus "^1.0.0" - bcrypt-pbkdf "^1.0.0" - dashdash "^1.12.0" - ecc-jsbn "~0.1.1" - getpass "^0.1.1" - jsbn "~0.1.0" - safer-buffer "^2.0.2" - tweetnacl "~0.14.0" - string_decoder@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" @@ -1798,11 +1247,6 @@ string_decoder@^1.1.1: dependencies: safe-buffer "~5.1.0" -string_decoder@~0.10.x: - version "0.10.31" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" - integrity sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ= - tmp@0.0.29: version "0.0.29" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.29.tgz#f25125ff0dd9da3ccb0c2dd371ee1288bb9128c0" @@ -1810,33 +1254,11 @@ tmp@0.0.29: dependencies: os-tmpdir "~1.0.1" -tough-cookie@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.0.0.tgz#d822234eeca882f991f0f908824ad2622ddbece4" - integrity sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg== - dependencies: - psl "^1.1.33" - punycode "^2.1.1" - universalify "^0.1.2" - -tough-cookie@~2.5.0: - version "2.5.0" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" - integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g== - dependencies: - psl "^1.1.28" - punycode "^2.1.1" - tslib@^1.8.1: version "1.9.3" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286" integrity sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ== -tslib@^2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.0.3.tgz#8e0741ac45fc0c226e58a17bfc3e64b9bc6ca61c" - integrity sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ== - tsutils@^3.17.1: version "3.17.1" resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.17.1.tgz#ed719917f11ca0dee586272b2ac49e015a2dd759" @@ -1844,28 +1266,11 @@ tsutils@^3.17.1: dependencies: tslib "^1.8.1" -tunnel-agent@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" - integrity sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0= - dependencies: - safe-buffer "^5.0.1" - tunnel@0.0.4: version "0.0.4" resolved "https://registry.yarnpkg.com/tunnel/-/tunnel-0.0.4.tgz#2d3785a158c174c9a16dc2c046ec5fc5f1742213" integrity sha1-LTeFoVjBdMmhbcLARuxfxfF0IhM= -tunnel@^0.0.6: - version "0.0.6" - resolved "https://registry.yarnpkg.com/tunnel/-/tunnel-0.0.6.tgz#72f1314b34a5b192db012324df2cc587ca47f92c" - integrity sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg== - -tweetnacl@^0.14.3, tweetnacl@~0.14.0: - version "0.14.5" - resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" - integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= - typed-rest-client@^0.9.0: version "0.9.0" resolved "https://registry.yarnpkg.com/typed-rest-client/-/typed-rest-client-0.9.0.tgz#f768cc0dc3f4e950f06e04825c36b3e7834aa1f2" @@ -1889,72 +1294,31 @@ uc.micro@^1.0.1, uc.micro@^1.0.5: resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.5.tgz#0c65f15f815aa08b560a61ce8b4db7ffc3f45376" integrity sha512-JoLI4g5zv5qNyT09f4YAvEZIIV1oOjqnewYg5D38dkQljIzpPT296dbIGvKro3digYI1bkb7W6EP1y4uDlmzLg== -underscore@1.8.3, underscore@~1.8.3: +underscore@1.8.3: version "1.8.3" resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.8.3.tgz#4f3fb53b106e6097fcf9cb4109f2a5e9bdfa5022" integrity sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI= underscore@^1.8.3: - version "1.9.1" - resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.9.1.tgz#06dce34a0e68a7babc29b365b8e74b8925203961" - integrity sha512-5/4etnCkd9c8gwgowi5/om/mYO5ajCaOgdzj/oW+0eQV9WxKBDZw5+ycmKmeaTXjInS/W0BzpGLo2xR2aBwZdg== - -universal-user-agent@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/universal-user-agent/-/universal-user-agent-6.0.0.tgz#3381f8503b251c0d9cd21bc1de939ec9df5480ee" - integrity sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w== - -universalify@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" - integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== + version "1.13.1" + resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.13.1.tgz#0c1c6bd2df54b6b69f2314066d65b6cde6fcf9d1" + integrity sha512-hzSoAVtJF+3ZtiFX0VgfFPHEDRm7Y/QPjGyNo4TVdnDTdft3tr8hEkD25a1jC+TjTuE7tkHGKkhwCgs9dgBB2g== universalify@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717" integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ== -uri-js@^4.2.2: - version "4.4.1" - resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" - integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== - dependencies: - punycode "^2.1.0" - url-join@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/url-join/-/url-join-1.1.0.tgz#741c6c2f4596c4830d6718460920d0c92202dc78" integrity sha1-dBxsL0WWxIMNZxhGCSDQySIC3Hg= -util-deprecate@^1.0.1, util-deprecate@~1.0.1: +util-deprecate@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= -uuid@^3.0.0, uuid@^3.3.2: - version "3.4.0" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" - integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== - -uuid@^8.3.0: - version "8.3.1" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.1.tgz#2ba2e6ca000da60fce5a196954ab241131e05a31" - integrity sha512-FOmRr+FmWEIG8uhZv6C2bTgEVXsHk08kE7mPlrBbEe+c3r9pjceVPgupIfNIhc4yx55H69OXANrUaSuu9eInKg== - -validator@~9.4.1: - version "9.4.1" - resolved "https://registry.yarnpkg.com/validator/-/validator-9.4.1.tgz#abf466d398b561cd243050112c6ff1de6cc12663" - integrity sha512-YV5KjzvRmSyJ1ee/Dm5UED0G+1L4GZnLN3w6/T+zZm8scVua4sOhYKWTUrKa0H/tMiJyO9QLHMPN+9mB/aMunA== - -verror@1.10.0: - version "1.10.0" - resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" - integrity sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA= - dependencies: - assert-plus "^1.0.0" - core-util-is "1.0.2" - extsprintf "^1.2.0" - vsce@1.48.0: version "1.48.0" resolved "https://registry.yarnpkg.com/vsce/-/vsce-1.48.0.tgz#31c1a4c6909c3b8bdc48b3d32cc8c8e94c7113a2" @@ -2014,21 +1378,6 @@ wrappy@1: resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= -xml2js@0.2.8: - version "0.2.8" - resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.2.8.tgz#9b81690931631ff09d1957549faf54f4f980b3c2" - integrity sha1-m4FpCTFjH/CdGVdUn69U9PmAs8I= - dependencies: - sax "0.5.x" - -xml2js@^0.4.19: - version "0.4.23" - resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.23.tgz#a0c69516752421eb2ac758ee4d4ccf58843eac66" - integrity sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug== - dependencies: - sax ">=0.6.0" - xmlbuilder "~11.0.0" - xmlbuilder@>=11.0.1: version "15.1.1" resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-15.1.1.tgz#9dcdce49eea66d8d10b42cae94a79c3c8d0c2ec5" @@ -2039,15 +1388,10 @@ xmlbuilder@^9.0.7: resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-9.0.7.tgz#132ee63d2ec5565c557e20f4c22df9aca686b10d" integrity sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0= -xmlbuilder@~11.0.0: - version "11.0.1" - resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-11.0.1.tgz#be9bae1c8a046e76b31127726347d0ad7002beb3" - integrity sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA== - -xmldom@0.1.x: - version "0.1.31" - resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.1.31.tgz#b76c9a1bd9f0a9737e5a72dc37231cf38375e2ff" - integrity sha512-yS2uJflVQs6n+CyjHoaBmVSqIDevTAWrzMmjG1Gc7h1qQ7uVozNhEPJAwZXWyGQ/Gafo3fCwrcaokezLPupVyQ== +xmldom@^0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.5.0.tgz#193cb96b84aa3486127ea6272c4596354cb4962e" + integrity sha512-Foaj5FXVzgn7xFzsKeNIde9g6aFBxTPi37iwsno8QvApmtg7KYrr+OPyRHcJF7dud2a5nGRBXK3n0dL62Gf7PA== yallist@^4.0.0: version "4.0.0" diff --git a/coder.js b/coder.js new file mode 100644 index 0000000000000..e487491df8258 --- /dev/null +++ b/coder.js @@ -0,0 +1,71 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Coder Technologies. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// This must be ran from VS Code's root. +const gulp = require('gulp'); +const path = require('path'); +const _ = require('underscore'); +const buildfile = require('./src/buildfile'); +const common = require('./build/lib/optimize'); +const util = require('./build/lib/util'); + +const vscodeEntryPoints = _.flatten([ + buildfile.entrypoint('vs/workbench/workbench.web.api'), + buildfile.entrypoint('vs/server/entry'), + buildfile.base, + buildfile.workbenchWeb, + buildfile.workerExtensionHost, + buildfile.workerNotebook, + buildfile.keyboardMaps, + // See ./src/vs/workbench/buildfile.desktop.js + buildfile.entrypoint('vs/platform/files/node/watcher/unix/watcherApp'), + buildfile.entrypoint('vs/platform/files/node/watcher/nsfw/watcherApp'), + buildfile.entrypoint(`vs/platform/terminal/node/ptyHostMain`), + buildfile.entrypoint('vs/workbench/services/extensions/node/extensionHostProcess'), +]); + +// See ./build/gulpfile.vscode.js +const vscodeResources = [ + 'out-build/vs/server/fork.js', + '!out-build/vs/server/doc/**', + 'out-build/vs/workbench/services/extensions/worker/extensionHostWorkerMain.js', + 'out-build/bootstrap.js', + 'out-build/bootstrap-fork.js', + 'out-build/bootstrap-amd.js', + 'out-build/bootstrap-node.js', + 'out-build/vs/**/*.{svg,png,html,ttf,jpg}', + '!out-build/vs/code/browser/workbench/*.html', + '!out-build/vs/code/electron-browser/**', + 'out-build/vs/base/common/performance.js', + 'out-build/vs/base/node/languagePacks.js', + 'out-build/vs/base/browser/ui/codicons/codicon/**', + 'out-build/vs/base/node/userDataPath.js', + 'out-build/vs/workbench/browser/media/*-theme.css', + 'out-build/vs/workbench/contrib/debug/**/*.json', + 'out-build/vs/workbench/contrib/externalTerminal/**/*.scpt', + 'out-build/vs/workbench/contrib/webview/browser/pre/*.js', + 'out-build/vs/**/markdown.css', + 'out-build/vs/workbench/contrib/tasks/**/*.json', + 'out-build/vs/platform/files/**/*.md', + '!**/test/**' +]; + +gulp.task('optimize', gulp.series( + util.rimraf('out-vscode'), + common.optimizeTask({ + src: 'out-build', + entryPoints: vscodeEntryPoints, + resources: vscodeResources, + loaderConfig: common.loaderConfig(), + out: 'out-vscode', + inlineAmdImages: true, + bundleInfo: undefined + }), +)); + +gulp.task('minify', gulp.series( + util.rimraf('out-vscode-min'), + common.minifyTask('out-vscode') +)); diff --git a/extensions/github-authentication/src/githubServer.ts b/extensions/github-authentication/src/githubServer.ts index 74aa5cc3f8d80..b0c57ee9810b4 100644 --- a/extensions/github-authentication/src/githubServer.ts +++ b/extensions/github-authentication/src/githubServer.ts @@ -15,7 +15,11 @@ import { AuthProviderType } from './github'; const localize = nls.loadMessageBundle(); export const NETWORK_ERROR = 'network error'; -const AUTH_RELAY_SERVER = 'vscode-auth.github.com'; +/** + * @coder Domain to our own auth relay. + * @remark `AUTH_RELAY_STAGING_SERVER` comes from Microsoft's + */ +const AUTH_RELAY_SERVER = 'auth.code-server.dev'; // const AUTH_RELAY_STAGING_SERVER = 'client-auth-staging-14a768b.herokuapp.com'; class UriEventHandler extends vscode.EventEmitter implements vscode.UriHandler { diff --git a/extensions/postinstall.js b/extensions/postinstall.js index da4fa3e9d0443..0d43b3a200685 100644 --- a/extensions/postinstall.js +++ b/extensions/postinstall.js @@ -24,6 +24,9 @@ function processRoot() { rimraf.sync(filePath); } } + + /** @coder: Delete .bin so it doesn't contain broken symlinks that trip up nfpm. */ + rimraf.sync(path.join(__dirname, 'node_modules', '.bin')); } function processLib() { diff --git a/package.json b/package.json index 563b835f33f6a..1c77389c21fde 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "web": "node resources/web/code-web.js", "compile-web": "node --max_old_space_size=4095 ./node_modules/gulp/bin/gulp.js compile-web", "watch-web": "node --max_old_space_size=4095 ./node_modules/gulp/bin/gulp.js watch-web", + "server": "node --max_old_space_size=4095 out/vs/server/entry.js", "eslint": "node build/eslint", "playwright-install": "node build/azure-pipelines/common/installPlaywright.js", "compile-build": "node --max_old_space_size=4095 ./node_modules/gulp/bin/gulp.js compile-build", @@ -57,6 +58,8 @@ "extensions-ci": "node --max_old_space_size=4095 ./node_modules/gulp/bin/gulp.js extensions-ci" }, "dependencies": { + "@types/proxy-from-env": "^1.0.1", + "@types/tar-stream": "^2.2.1", "applicationinsights": "1.0.8", "chokidar": "3.5.1", "graceful-fs": "4.2.6", @@ -71,8 +74,11 @@ "native-watchdog": "1.3.0", "node-pty": "0.11.0-beta7", "nsfw": "2.1.2", + "proxy-agent": "^4.0.1", + "proxy-from-env": "^1.1.0", "spdlog": "^0.13.0", "sudo-prompt": "9.2.1", + "tar-stream": "^2.2.0", "tas-client-umd": "0.1.4", "v8-inspect-profiler": "^0.0.21", "vscode-oniguruma": "1.5.1", @@ -113,6 +119,7 @@ "@types/windows-mutex": "^0.4.0", "@types/windows-process-tree": "^0.2.0", "@types/winreg": "^1.2.30", + "@types/ws": "^7.4.7", "@types/yauzl": "^2.9.1", "@types/yazl": "^2.4.2", "@typescript-eslint/eslint-plugin": "3.2.0", diff --git a/resources/web/code-web.d.ts b/resources/web/code-web.d.ts new file mode 100644 index 0000000000000..3ea14c7bc26d7 --- /dev/null +++ b/resources/web/code-web.d.ts @@ -0,0 +1,4 @@ +import * as http from 'http'; +import { IServerWorkbenchConstructionOptions } from 'vs/workbench/workbench.web.api'; + +export function requestHandler(req: http.IncomingMessage, res: http.ServerResponse, webConfigJSON: IServerWorkbenchConstructionOptions): void; diff --git a/resources/web/code-web.js b/resources/web/code-web.js index 13ff160ceb6a7..46137601612a7 100644 --- a/resources/web/code-web.js +++ b/resources/web/code-web.js @@ -4,6 +4,7 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +Object.defineProperty(exports, '__esModule', { value: true }); // @ts-check @@ -31,6 +32,8 @@ const WEB_MAIN = path.join(APP_ROOT, 'src', 'vs', 'code', 'browser', 'workbench' // This is useful to simulate real world CORS const ALLOWED_CORS_ORIGINS = [ + 'http://localhost:8082', + 'http://127.0.0.1:8082', 'http://localhost:8081', 'http://127.0.0.1:8081', 'http://localhost:8080', @@ -41,15 +44,17 @@ const WEB_PLAYGROUND_VERSION = '0.0.12'; const args = minimist(process.argv, { boolean: [ - 'no-launch', + 'launch', 'help', 'verbose', 'wrap-iframe', 'enable-sync', ], string: [ + 'server', 'scheme', 'host', + 'remote-authority', 'port', 'local_port', 'extension', @@ -61,12 +66,13 @@ const args = minimist(process.argv, { if (args.help) { console.log( 'yarn web [options]\n' + - ' --no-launch Do not open VSCode web in the browser\n' + + ' --launch Open VSCode web in the browser\n' + ' --wrap-iframe Wrap the Web Worker Extension Host in an iframe\n' + ' --enable-sync Enable sync by default\n' + ' --scheme Protocol (https or http)\n' + ' --host Remote host\n' + ' --port Remote/Local port\n' + + ' --remote-authority Remote auth\n' + ' --local_port Local port override\n' + ' --secondary-port Secondary port\n' + ' --extension Path of an extension to include\n' + @@ -221,8 +227,10 @@ const mapCallbackUriToRequestId = new Map(); /** * @param req {http.IncomingMessage} * @param res {http.ServerResponse} + * @param webConfigJSON {import('../../src/vs/workbench/workbench.web.api').IServerWorkbenchConstructionOptions} + * @param webConfigJSON undefined */ -const requestHandler = (req, res) => { +const requestHandler = (req, res, webConfigJSON) => { const parsedUrl = url.parse(req.url, true); const pathname = parsedUrl.pathname; @@ -253,6 +261,9 @@ const requestHandler = (req, res) => { return handleExtension(req, res, parsedUrl); } if (pathname === '/') { + if (args.server) { + return handleRootFromServer(req, res, webConfigJSON); + } // main web return handleRoot(req, res); } else if (pathname === '/callback') { @@ -261,8 +272,11 @@ const requestHandler = (req, res) => { } else if (pathname === '/fetch-callback') { // callback fetch support return handleFetchCallback(req, res, parsedUrl); + } else if (pathname === '/vscode-remote-resource') { + // callback fetch support + return handleRemoteResource(req, res, parsedUrl); } else if (pathname === '/builtin') { - // builtin extnesions JSON + // builtin extensions JSON return handleBuiltInExtensions(req, res, parsedUrl); } @@ -274,26 +288,28 @@ const requestHandler = (req, res) => { } }; -const server = http.createServer(requestHandler); -server.listen(LOCAL_PORT, () => { - if (LOCAL_PORT !== PORT) { - console.log(`Operating location at http://0.0.0.0:${LOCAL_PORT}`); - } - console.log(`Web UI available at ${SCHEME}://${AUTHORITY}`); -}); -server.on('error', err => { - console.error(`Error occurred in server:`); - console.error(err); -}); +if (!args.server) { + const server = http.createServer(requestHandler); + server.listen(LOCAL_PORT, () => { + if (LOCAL_PORT !== PORT) { + console.log(`Operating location at http://0.0.0.0:${LOCAL_PORT}`); + } + console.log(`Web UI available at ${SCHEME}://${AUTHORITY}`); + }); + server.on('error', err => { + console.error(`Error occurred in server:`); + console.error(err); + }); -const secondaryServer = http.createServer(requestHandler); -secondaryServer.listen(SECONDARY_PORT, () => { - console.log(`Secondary server available at ${SCHEME}://${HOST}:${SECONDARY_PORT}`); -}); -secondaryServer.on('error', err => { - console.error(`Error occurred in server:`); - console.error(err); -}); + const secondaryServer = http.createServer(requestHandler); + secondaryServer.listen(SECONDARY_PORT, () => { + console.log(`Secondary server available at ${SCHEME}://${HOST}:${SECONDARY_PORT}`); + }); + secondaryServer.on('error', err => { + console.error(`Error occurred in server:`); + console.error(err); + }); +} /** * @param {import('http').IncomingMessage} req @@ -317,6 +333,20 @@ async function handleBuiltInExtensions(req, res, parsedUrl) { return res.end(JSON.stringify(extensions)); } +/** + * @param {import('http').IncomingMessage} req + * @param {import('http').ServerResponse} res + * @param {import('url').UrlWithParsedQuery} parsedUrl + */ +async function handleRemoteResource(req, res, parsedUrl) { + const { path } = parsedUrl.query; + + if (path) { + res.setHeader('Content-Type', getMediaMime(path)); + res.end(await readFile(path)); + } +} + /** * @param {import('http').IncomingMessage} req * @param {import('http').ServerResponse} res @@ -420,6 +450,7 @@ async function handleRoot(req, res) { ? req.headers['host'].replace(':' + PORT, ':' + SECONDARY_PORT) : `${HOST}:${SECONDARY_PORT}` ); + const webConfigJSON = { folderUri: folderUri, additionalBuiltinExtensions, @@ -454,6 +485,52 @@ async function handleRoot(req, res) { return res.end(data); } +/** + * @param {import('http').IncomingMessage} req + * @param {import('http').ServerResponse} res + * @param webConfigJSON {import('../../src/vs/workbench/workbench.web.api').IServerWorkbenchConstructionOptions} + */ +async function handleRootFromServer(req, res, webConfigJSON) { + // const { extensions: builtInExtensions } = await builtInExtensionsPromise; + // const { extensions: additionalBuiltinExtensions, locations: staticLocations } = await commandlineProvidedExtensionsPromise; + + // const dedupedBuiltInExtensions = []; + // for (const builtInExtension of builtInExtensions) { + // const extensionId = `${builtInExtension.packageJSON.publisher}.${builtInExtension.packageJSON.name}`; + // if (staticLocations[extensionId]) { + // fancyLog(`${ansiColors.magenta('BuiltIn extensions')}: Ignoring built-in ${extensionId} because it was overridden via --extension argument`); + // continue; + // } + + // dedupedBuiltInExtensions.push(builtInExtension); + // } + + if (args.verbose) { + fancyLog(`${ansiColors.magenta('BuiltIn extensions')}: ${dedupedBuiltInExtensions.map(e => path.basename(e.extensionPath)).join(', ')}`); + fancyLog(`${ansiColors.magenta('Additional extensions')}: ${additionalBuiltinExtensions.map(e => path.basename(e.extensionLocation.path)).join(', ') || 'None'}`); + } + + if (req.headers['x-forwarded-host']) { + // support for running in codespace => no iframe wrapping + delete webConfigJSON.webWorkerExtensionHostIframeSrc; + } + const authSessionInfo = undefined; + + const data = (await readFile(WEB_MAIN)).toString() + .replace('{{WORKBENCH_WEB_CONFIGURATION}}', () => escapeAttribute(JSON.stringify(webConfigJSON))) // use a replace function to avoid that regexp replace patterns ($&, $0, ...) are applied + // .replace('{{WORKBENCH_BUILTIN_EXTENSIONS}}', () => escapeAttribute(JSON.stringify(dedupedBuiltInExtensions))) + .replace('{{WORKBENCH_AUTH_SESSION}}', () => authSessionInfo ? escapeAttribute(JSON.stringify(authSessionInfo)) : '') + .replace('{{WEBVIEW_ENDPOINT}}', ''); + + const headers = { + 'Content-Type': 'text/html', + 'Content-Security-Policy': 'require-trusted-types-for \'script\';' + }; + + res.writeHead(200, headers); + return res.end(data); +} + /** * Handle HTTP requests for /callback * @param {import('http').IncomingMessage} req @@ -613,7 +690,7 @@ const mapExtToMediaMimes = { function getMediaMime(forPath) { const ext = path.extname(forPath); - return mapExtToMediaMimes[ext.toLowerCase()]; + return mapExtToMediaMimes[ext.toLowerCase()] || 'text/plain'; } /** @@ -655,3 +732,5 @@ async function serveFile(req, res, filePath, responseHeaders = Object.create(nul if (args.launch !== false) { opn(`${SCHEME}://${HOST}:${PORT}`); } + +exports.requestHandler = requestHandler; diff --git a/src/vs/base/common/ipc.d.ts b/src/vs/base/common/ipc.d.ts new file mode 100644 index 0000000000000..2c3dc0cbf401b --- /dev/null +++ b/src/vs/base/common/ipc.d.ts @@ -0,0 +1,15 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Coder Technologies. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +/** + * External interfaces for integration into code-server over IPC. + */ +export interface CodeServerConfiguration { + authed: boolean + base: string + csStaticBase: string + disableUpdateCheck: boolean + logLevel: number +} diff --git a/src/vs/base/common/network.ts b/src/vs/base/common/network.ts index 69351f7d9180b..5feb80d16a901 100644 --- a/src/vs/base/common/network.ts +++ b/src/vs/base/common/network.ts @@ -128,16 +128,17 @@ class RemoteAuthoritiesImpl { if (host && host.indexOf(':') !== -1) { host = `[${host}]`; } - const port = this._ports[authority]; + // const port = this._ports[authority]; const connectionToken = this._connectionTokens[authority]; let query = `path=${encodeURIComponent(uri.path)}`; if (typeof connectionToken === 'string') { query += `&tkn=${encodeURIComponent(connectionToken)}`; } + // NOTE@coder: Changed this to work against the current path. return URI.from({ scheme: platform.isWeb ? this._preferredWebSchema : Schemas.vscodeRemoteResource, - authority: `${host}:${port}`, - path: `/vscode-remote-resource`, + authority: window.location.host, + path: `${window.location.pathname.replace(/\/+$/, '')}/vscode-remote-resource`, query }); } diff --git a/src/vs/base/common/platform.ts b/src/vs/base/common/platform.ts index 2c281b62d9b99..dce7d7187ab12 100644 --- a/src/vs/base/common/platform.ts +++ b/src/vs/base/common/platform.ts @@ -107,6 +107,18 @@ if (typeof navigator === 'object' && !isElectronRenderer) { _isWeb = true; _locale = navigator.language; _language = _locale; + + // NOTE@coder: Make languages work. + const el = typeof document !== 'undefined' && document.getElementById('vscode-remote-nls-configuration'); + const rawNlsConfig = el && el.getAttribute('data-settings'); + if (rawNlsConfig) { + try { + const nlsConfig: NLSConfig = JSON.parse(rawNlsConfig); + _locale = nlsConfig.locale; + _translationsConfigFile = nlsConfig._translationsConfigFile; + _language = nlsConfig.availableLanguages['*'] || LANGUAGE_DEFAULT; + } catch (error) { /* Oh well. */ } + } } // Native environment diff --git a/src/vs/base/common/processes.ts b/src/vs/base/common/processes.ts index 6c52c3f9ce2bf..6b84dbedbf57a 100644 --- a/src/vs/base/common/processes.ts +++ b/src/vs/base/common/processes.ts @@ -111,6 +111,7 @@ export function sanitizeProcessEnvironment(env: IProcessEnvironment, ...preserve /^VSCODE_.+$/, /^SNAP(|_.*)$/, /^GDK_PIXBUF_.+$/, + /^CODE_SERVER_.+$/, ]; const envKeys = Object.keys(env); envKeys diff --git a/src/vs/base/common/product.ts b/src/vs/base/common/product.ts index d47fc57bf84e5..b1c0ffef17211 100644 --- a/src/vs/base/common/product.ts +++ b/src/vs/base/common/product.ts @@ -31,6 +31,11 @@ export type ExtensionVirtualWorkspaceSupport = { }; export interface IProductConfiguration { + /** @coder BEGIN */ + /** @deprecated Should be replaced with code-oss version. */ + readonly codeServerVersion?: string; + /** @coder END */ + readonly version: string; readonly date?: string; readonly quality?: string; diff --git a/src/vs/base/common/types.ts b/src/vs/base/common/types.ts index 3e431196b998f..7d17b82d6ec34 100644 --- a/src/vs/base/common/types.ts +++ b/src/vs/base/common/types.ts @@ -272,6 +272,13 @@ export type Dto = T extends { toJSON(): infer U } ? { [k in keyof T]: Dto; } : T; +/** + * Marks optional types as required. + */ +export type Complete = { + [P in keyof Required]: Pick extends Required> ? T[P] : (NonNullable); +}; + export function NotImplementedProxy(name: string): { new(): T } { return class { constructor() { diff --git a/src/vs/base/common/uriServer.ts b/src/vs/base/common/uriServer.ts new file mode 100644 index 0000000000000..a78b44fb574be --- /dev/null +++ b/src/vs/base/common/uriServer.ts @@ -0,0 +1,50 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Coder Technologies. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Schemas } from 'vs/base/common/network'; +import { IRawURITransformer, UriParts, URITransformer } from 'vs/base/common/uriIpc'; + +/** + * Transforms URIs such that incoming and outgoing requests are directed to their + * respective local and remote routes. + */ +class RawURITransformer implements IRawURITransformer { + constructor(private readonly authority: string) { } + + transformIncoming(uri: UriParts): UriParts { + switch (uri.scheme) { + case Schemas.vscodeRemote: + return { scheme: Schemas.file, path: uri.path }; + default: + return uri; + } + } + + transformOutgoing(uri: UriParts): UriParts { + switch (uri.scheme) { + case Schemas.file: + return { scheme: Schemas.vscodeRemote, authority: this.authority, path: uri.path }; + default: + return uri; + } + } + + transformOutgoingScheme(scheme: string): string { + switch (scheme) { + case 'file': + return Schemas.vscodeRemote; + default: + return scheme; + } + } +} + +/** + * Convenience function, given that a server's raw URI transformer is often wrapped + * by VSCode's `URITransformer`. + */ +export function createServerURITransformer(authority: string) { + return new URITransformer(new RawURITransformer(authority)); +} diff --git a/src/vs/base/common/util.ts b/src/vs/base/common/util.ts new file mode 100644 index 0000000000000..f83b86482d2a3 --- /dev/null +++ b/src/vs/base/common/util.ts @@ -0,0 +1,64 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Coder Technologies. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +/** + * Base options included on every page. + */ +export interface Options { + base: string + csStaticBase: string + logLevel: number +} + +/** + * Remove extra slashes in a URL. + */ +export const normalize = (url: string, keepTrailing = false): string => { + return url.replace(/\/\/+/g, '/').replace(/\/+$/, keepTrailing ? '/' : ''); +}; + +/** + * Resolve a relative base against the window location. This is used for + * anything that doesn't work with a relative path. + */ +export const resolveBase = (base?: string): string => { + // After resolving the base will either start with / or be an empty string. + if (!base || base.startsWith('/')) { + return base ?? ''; + } + const parts = location.pathname.split('/'); + parts[parts.length - 1] = base; + const url = new URL(location.origin + '/' + parts.join('/')); + return normalize(url.pathname); +}; + +/** + * Get options embedded in the HTML or query params. + */ +export const getOptions = (): T => { + let options: T; + try { + options = JSON.parse(document.getElementById('coder-options')!.getAttribute('data-settings')!); + } catch (error) { + options = {} as T; + } + + // You can also pass options in stringified form to the options query + // variable. Options provided here will override the ones in the options + // element. + const params = new URLSearchParams(location.search); + const queryOpts = params.get('options'); + if (queryOpts) { + options = { + ...options, + ...JSON.parse(queryOpts), + }; + } + + options.base = resolveBase(options.base); + options.csStaticBase = resolveBase(options.csStaticBase); + + return options; +}; diff --git a/src/vs/base/node/languagePacks.js b/src/vs/base/node/languagePacks.js index 5c14fade87895..0899cab4ed150 100644 --- a/src/vs/base/node/languagePacks.js +++ b/src/vs/base/node/languagePacks.js @@ -73,7 +73,10 @@ function getLanguagePackConfigurations(userDataPath) { const configFile = path.join(userDataPath, 'languagepacks.json'); try { - return nodeRequire(configFile); + // NOTE@coder: Swapped require with readFile since require is cached and + // we don't restart the server-side portion of code-server when the + // language changes. + return JSON.parse(fs.readFileSync(configFile, 'utf8')); } catch (err) { // Do nothing. If we can't read the file we have no // language pack config. diff --git a/src/vs/base/node/proxy_agent.ts b/src/vs/base/node/proxy_agent.ts new file mode 100644 index 0000000000000..495634bdc7652 --- /dev/null +++ b/src/vs/base/node/proxy_agent.ts @@ -0,0 +1,85 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Coder Technologies. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as http from "http" +import * as proxyAgent from "proxy-agent" +import * as proxyFromEnv from "proxy-from-env" + +/** + * This file has nothing to do with the code-server proxy. + * It is to support $HTTP_PROXY, $HTTPS_PROXY and $NO_PROXY. + * + * - https://github.com/cdr/code-server/issues/124 + * - https://www.npmjs.com/package/proxy-agent + * - https://www.npmjs.com/package/proxy-from-env + * + * This file exists in two locations: + * - src/node/proxy_agent.ts + * - lib/vscode/src/vs/base/node/proxy_agent.ts + * The second is a symlink to the first. + */ + +/** + * monkeyPatch patches the node http,https modules to route all requests through the + * agent we get from the proxy-agent package. + * + * This approach only works if there is no code specifying an explicit agent when making + * a request. + * + * None of our code ever passes in a explicit agent to the http,https modules. + * VS Code's does sometimes but only when a user sets the http.proxy configuration. + * See https://code.visualstudio.com/docs/setup/network#_legacy-proxy-server-support + * + * Even if they do, it's probably the same proxy so we should be fine! And those knobs + * are deprecated anyway. + */ +export function monkeyPatch(inVSCode: boolean): void { + if (shouldEnableProxy()) { + const http = require("http") + const https = require("https") + + // If we do not pass in a proxy URL, proxy-agent will get the URL from the environment. + // See https://www.npmjs.com/package/proxy-from-env. + // Also see shouldEnableProxy. + const pa = newProxyAgent(inVSCode) + http.globalAgent = pa + https.globalAgent = pa + } +} + +function newProxyAgent(inVSCode: boolean): http.Agent { + // The reasoning for this split is that VS Code's build process does not have + // esModuleInterop enabled but the code-server one does. As a result depending on where + // we execute, we either have a default attribute or we don't. + // + // I can't enable esModuleInterop in VS Code's build process as it breaks and spits out + // a huge number of errors. And we can't use require as otherwise the modules won't be + // included in the final product. + if (inVSCode) { + return new (proxyAgent as any)() + } else { + return new (proxyAgent as any).default() + } +} + +// If they have $NO_PROXY set to example.com then this check won't work! +// But that's drastically unlikely. +function shouldEnableProxy(): boolean { + let shouldEnable = false + + const httpProxy = proxyFromEnv.getProxyForUrl(`http://example.com`) + if (httpProxy) { + shouldEnable = true + console.debug(`using $HTTP_PROXY ${httpProxy}`) + } + + const httpsProxy = proxyFromEnv.getProxyForUrl(`https://example.com`) + if (httpsProxy) { + shouldEnable = true + console.debug(`using $HTTPS_PROXY ${httpsProxy}`) + } + + return shouldEnable +} diff --git a/src/vs/base/parts/ipc/common/ipc.net.ts b/src/vs/base/parts/ipc/common/ipc.net.ts index 5df1869678459..3f03b8b51c62c 100644 --- a/src/vs/base/parts/ipc/common/ipc.net.ts +++ b/src/vs/base/parts/ipc/common/ipc.net.ts @@ -175,6 +175,16 @@ const enum ProtocolMessageType { ReplayRequest = 6 } +const ProtocolMessageTypeToLabel: { [K in ProtocolMessageType]: string } = { + 0: 'None', + 1: 'Regular', + 2: 'Control', + 3: 'Ack', + 4: 'KeepAlive', + 5: 'Disconnect', + 6: 'ReplayRequest' +}; + export const enum ProtocolConstants { HeaderLength = 13, /** @@ -219,6 +229,10 @@ class ProtocolMessage { public get size(): number { return this.data.byteLength; } + + public get label(): string { + return ProtocolMessageTypeToLabel[this.type as ProtocolMessageType] || 'Unknown'; + } } class ProtocolReader extends Disposable { @@ -262,8 +276,6 @@ class ProtocolReader extends Disposable { const buff = this._incomingData.read(this._state.readLen); if (this._state.readHead) { - // buff is the header - // save new state => next time will read the body this._state.readHead = false; this._state.readLen = buff.readUInt32BE(9); @@ -703,7 +715,10 @@ export class PersistentProtocol implements IMessagePassingProtocol { this._socketReader = new ProtocolReader(this._socket); this._socketDisposables.push(this._socketReader); this._socketDisposables.push(this._socketReader.onMessage(msg => this._receiveMessage(msg))); - this._socketDisposables.push(this._socket.onClose((e) => this._onSocketClose.fire(e))); + this._socketDisposables.push(this._socket.onClose((e) => { + console.log('SOCKET DISPOSABLE CLOSE', (e as WebSocketCloseEvent).reason); + return this._onSocketClose.fire(e); + })); if (initialChunk) { this._socketReader.acceptChunk(initialChunk); } @@ -792,6 +807,11 @@ export class PersistentProtocol implements IMessagePassingProtocol { return this._socket; } + // NOTE@coder: add setSocket + public setSocket(socket: ISocket) { + this._socket = socket; + } + public getMillisSinceLastIncomingData(): number { return Date.now() - this._socketReader.lastReadTime; } diff --git a/src/vs/base/parts/ipc/node/ipc.cp.ts b/src/vs/base/parts/ipc/node/ipc.cp.ts index 84ef5cb0a0482..2d6d6164cab66 100644 --- a/src/vs/base/parts/ipc/node/ipc.cp.ts +++ b/src/vs/base/parts/ipc/node/ipc.cp.ts @@ -3,18 +3,20 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ChildProcess, fork, ForkOptions } from 'child_process'; +import { ChildProcess, fork, ForkOptions, SendHandle } from 'child_process'; import { IDisposable, toDisposable, dispose } from 'vs/base/common/lifecycle'; import { Delayer, createCancelablePromise } from 'vs/base/common/async'; import { deepClone } from 'vs/base/common/objects'; import { Emitter, Event } from 'vs/base/common/event'; import { createQueuedSender } from 'vs/base/node/processes'; import { IChannel, ChannelServer as IPCServer, ChannelClient as IPCClient, IChannelClient } from 'vs/base/parts/ipc/common/ipc'; -import { isRemoteConsoleLog, log } from 'vs/base/common/console'; +import { IRemoteConsoleLog, isRemoteConsoleLog, log } from 'vs/base/common/console'; import { CancellationToken } from 'vs/base/common/cancellation'; import * as errors from 'vs/base/common/errors'; import { VSBuffer } from 'vs/base/common/buffer'; import { isMacintosh } from 'vs/base/common/platform'; +// eslint-disable-next-line code-import-patterns +import { IExtHostMessage, IExtHostReadyMessage } from 'vs/workbench/services/extensions/common/extensionHostProtocol'; /** * This implementation doesn't perform well since it uses base64 encoding for buffers. @@ -290,3 +292,105 @@ export class Client implements IChannelClient, IDisposable { this.activeRequests.clear(); } } + +export type ExtensionHostMessage = IRemoteConsoleLog | IExtHostReadyMessage; +/** + * Creates an extension host child process with a standard disposable interface. + */ +export class ExtensionHost implements IDisposable { + private child: ChildProcess | null = null; + private readonly _onDidProcessExit = new Emitter<{ code: number, signal: string }>(); + public readonly onDidProcessExit = this._onDidProcessExit.event; + + private readonly _onReadyMessage = new Emitter(); + public readonly onReady = this._onReadyMessage.event; + + private disposeClient() { + if (this.child) { + this.child.kill(); + this.child = null; + } + } + + dispose() { + this._onDidProcessExit.dispose(); + + this.disposeClient(); + } + + sendIPCMessage(message: IExtHostMessage, sendHandle?: SendHandle): boolean { + if (this.child && this.child.connected) { + return this.child.send(message, sendHandle); + } + + return false; + } + + constructor(private modulePath: string, private options: IIPCOptions) { + const args = options && options.args ? this.options.args : []; + const forkOpts: ForkOptions = Object.create(null); + + forkOpts.silent = true; + forkOpts.env = { ...deepClone(process.env), 'VSCODE_PARENT_PID': String(process.pid) }; + + if (this.options && this.options.env) { + forkOpts.env = { ...forkOpts.env, ...this.options.env }; + } + + if (this.options && this.options.freshExecArgv) { + forkOpts.execArgv = []; + } + + if (this.options && typeof this.options.debug === 'number') { + forkOpts.execArgv = ['--nolazy', '--inspect=' + this.options.debug]; + } + + if (this.options && typeof this.options.debugBrk === 'number') { + forkOpts.execArgv = ['--nolazy', '--inspect-brk=' + this.options.debugBrk]; + } + + if (forkOpts.execArgv === undefined) { + // if not set, the forked process inherits the execArgv of the parent process + // --inspect and --inspect-brk can not be inherited as the port would conflict + forkOpts.execArgv = process.execArgv.filter(a => !/^--inspect(-brk)?=/.test(a)); // remove + } + + if (isMacintosh && forkOpts.env) { + // Unset `DYLD_LIBRARY_PATH`, as it leads to process crashes + // See https://github.com/microsoft/vscode/issues/105848 + delete forkOpts.env['DYLD_LIBRARY_PATH']; + } + + this.child = fork(this.modulePath, args, forkOpts); + + const onRawMessage = Event.fromNodeEventEmitter(this.child, 'message', msg => msg); + + onRawMessage(msg => { + // Handle remote console logs specially + if (isRemoteConsoleLog(msg)) { + log(msg, `IPC Library: ${this.options.serverName}`); + return; + } + + if (msg.type === 'VSCODE_EXTHOST_IPC_READY') { + this._onReadyMessage.fire(msg); + } + }); + + const onExit = () => this.disposeClient(); + process.once('exit', onExit); + + this.child.on('error', err => console.warn('IPC "' + this.options.serverName + '" errored with ' + err)); + + this.child.on('exit', (code: any, signal: any) => { + process.removeListener('exit' as 'loaded', onExit); // https://github.com/electron/electron/issues/21475 + + + if (code !== 0 && signal !== 'SIGTERM') { + console.warn('IPC "' + this.options.serverName + '" crashed with exit code ' + code + ' and signal ' + signal); + } + + this._onDidProcessExit.fire({ code, signal }); + }); + } +} diff --git a/src/vs/code/browser/workbench/workbench.ts b/src/vs/code/browser/workbench/workbench.ts index 9c222a0048ec3..e79458b885e4f 100644 --- a/src/vs/code/browser/workbench/workbench.ts +++ b/src/vs/code/browser/workbench/workbench.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IWorkbenchConstructionOptions, create, ICredentialsProvider, IURLCallbackProvider, IWorkspaceProvider, IWorkspace, IWindowIndicator, IHomeIndicator, IProductQualityChangeHandler, ISettingsSyncOptions } from 'vs/workbench/workbench.web.api'; +import { create, ICredentialsProvider, IURLCallbackProvider, IWorkspaceProvider, IWorkspace, IWindowIndicator, IProductQualityChangeHandler, ISettingsSyncOptions, IServerWorkbenchConstructionOptions } from 'vs/workbench/workbench.web.api'; import { URI, UriComponents } from 'vs/base/common/uri'; import { Event, Emitter } from 'vs/base/common/event'; import { generateUuid } from 'vs/base/common/uuid'; @@ -17,7 +17,6 @@ import { isStandalone } from 'vs/base/browser/browser'; import { localize } from 'vs/nls'; import { Schemas } from 'vs/base/common/network'; import product from 'vs/platform/product/common/product'; -import { parseLogLevel } from 'vs/platform/log/common/log'; function doCreateUri(path: string, queryValues: Map): URI { let query: string | undefined = undefined; @@ -37,6 +36,15 @@ function doCreateUri(path: string, queryValues: Map): URI { return URI.parse(window.location.href).with({ path, query }); } +/** + * NOTE@coder: Add this function. + * Encode a path for opening via the folder or workspace query parameter. This + * preserves slashes so it can be edited by hand more easily. + */ +export const encodePath = (path: string): string => { + return path.split('/').map((p) => encodeURIComponent(p)).join('/'); +}; + interface ICredential { service: string; account: string; @@ -316,12 +324,18 @@ class WorkspaceProvider implements IWorkspaceProvider { // Folder else if (isFolderToOpen(workspace)) { - targetHref = `${document.location.origin}${document.location.pathname}?${WorkspaceProvider.QUERY_PARAM_FOLDER}=${encodeURIComponent(workspace.folderUri.toString())}`; + const target = workspace.folderUri.scheme === Schemas.vscodeRemote + ? encodePath(workspace.folderUri.path) + : encodeURIComponent(workspace.folderUri.toString()); + targetHref = `${document.location.origin}${document.location.pathname}?${WorkspaceProvider.QUERY_PARAM_FOLDER}=${target}`; } // Workspace else if (isWorkspaceToOpen(workspace)) { - targetHref = `${document.location.origin}${document.location.pathname}?${WorkspaceProvider.QUERY_PARAM_WORKSPACE}=${encodeURIComponent(workspace.workspaceUri.toString())}`; + const target = workspace.workspaceUri.scheme === Schemas.vscodeRemote + ? encodePath(workspace.workspaceUri.path) + : encodeURIComponent(workspace.workspaceUri.toString()); + targetHref = `${document.location.origin}${document.location.pathname}?${WorkspaceProvider.QUERY_PARAM_WORKSPACE}=${target}`; } // Append payload if any @@ -403,7 +417,6 @@ class WindowIndicator implements IWindowIndicator { } (function () { - // Find config by checking for DOM const configElement = document.getElementById('vscode-workbench-web-configuration'); const configElementAttribute = configElement ? configElement.getAttribute('data-settings') : undefined; @@ -411,51 +424,28 @@ class WindowIndicator implements IWindowIndicator { throw new Error('Missing web configuration element'); } - const config: IWorkbenchConstructionOptions & { folderUri?: UriComponents, workspaceUri?: UriComponents } = JSON.parse(configElementAttribute); + const config: IServerWorkbenchConstructionOptions = JSON.parse(configElementAttribute); + // const config: IWorkbenchConstructionOptions & { folderUri?: UriComponents, workspaceUri?: UriComponents } = { + // webviewEndpoint: `${window.location.origin}${window.location.pathname.replace(/\/+$/, '')}/webview`, + // ...JSON.parse(configElementAttribute), + // }; + + // Strip the protocol from the authority if it exists. + const normalizeAuthority = (authority: string): string => authority.replace(/^https?:\/\//, ''); + if (config.remoteAuthority) { + (config as any).remoteAuthority = normalizeAuthority(config.remoteAuthority); + } + if (config.workspaceUri && config.workspaceUri.authority) { + config.workspaceUri.authority = normalizeAuthority(config.workspaceUri.authority); + } + if (config.folderUri && config.folderUri.authority) { + config.folderUri.authority = normalizeAuthority(config.folderUri.authority); + } // Find workspace to open and payload let foundWorkspace = false; let workspace: IWorkspace; - let payload = Object.create(null); - let logLevel: string | undefined = undefined; - - const query = new URL(document.location.href).searchParams; - query.forEach((value, key) => { - switch (key) { - - // Folder - case WorkspaceProvider.QUERY_PARAM_FOLDER: - workspace = { folderUri: URI.parse(value) }; - foundWorkspace = true; - break; - - // Workspace - case WorkspaceProvider.QUERY_PARAM_WORKSPACE: - workspace = { workspaceUri: URI.parse(value) }; - foundWorkspace = true; - break; - - // Empty - case WorkspaceProvider.QUERY_PARAM_EMPTY_WINDOW: - workspace = undefined; - foundWorkspace = true; - break; - - // Payload - case WorkspaceProvider.QUERY_PARAM_PAYLOAD: - try { - payload = JSON.parse(value); - } catch (error) { - console.error(error); // possible invalid JSON - } - break; - - // Log level - case 'logLevel': - logLevel = value; - break; - } - }); + let payload = config.workspaceProvider?.payload || Object.create(null); // If no workspace is provided through the URL, check for config attribute from server if (!foundWorkspace) { @@ -471,13 +461,6 @@ class WindowIndicator implements IWindowIndicator { // Workspace Provider const workspaceProvider = new WorkspaceProvider(workspace, payload); - // Home Indicator - const homeIndicator: IHomeIndicator = { - href: 'https://github.com/microsoft/vscode', - icon: 'code', - title: localize('home', "Home") - }; - // Window indicator (unless connected to a remote) let windowIndicator: WindowIndicator | undefined = undefined; if (!workspaceProvider.hasRemote()) { @@ -520,12 +503,7 @@ class WindowIndicator implements IWindowIndicator { // Finally create workbench create(document.body, { ...config, - developmentOptions: { - logLevel: logLevel ? parseLogLevel(logLevel) : undefined, - ...config.developmentOptions - }, settingsSyncOptions, - homeIndicator, windowIndicator, productQualityChangeHandler, workspaceProvider, diff --git a/src/vs/code/electron-main/main.ts b/src/vs/code/electron-main/main.ts index c3477d25bc751..791a55d86a9b4 100644 --- a/src/vs/code/electron-main/main.ts +++ b/src/vs/code/electron-main/main.ts @@ -8,11 +8,9 @@ import { app, dialog } from 'electron'; import { unlinkSync } from 'fs'; import { Promises as FSPromises } from 'vs/base/node/pfs'; import { localize } from 'vs/nls'; -import { isWindows, IProcessEnvironment, isMacintosh } from 'vs/base/common/platform'; +import { isWindows, IProcessEnvironment } from 'vs/base/common/platform'; import { mark } from 'vs/base/common/performance'; import product from 'vs/platform/product/common/product'; -import { parseMainProcessArgv, addArg } from 'vs/platform/environment/node/argvHelper'; -import { createWaitMarkerFile } from 'vs/platform/environment/node/wait'; import { LifecycleMainService, ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; import { ProxyChannel } from 'vs/base/parts/ipc/common/ipc'; import { Server as NodeIPCServer, serve as nodeIPCServe, connect as nodeIPCConnect, XDG_RUNTIME_DIR } from 'vs/base/parts/ipc/node/ipc.net'; @@ -25,7 +23,6 @@ import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { ILogService, ConsoleMainLogger, MultiplexLogService, getLogLevel, ILoggerService } from 'vs/platform/log/common/log'; import { StateMainService } from 'vs/platform/state/electron-main/stateMainService'; import { IStateMainService } from 'vs/platform/state/electron-main/state'; -import { NativeParsedArgs } from 'vs/platform/environment/common/argv'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ConfigurationService } from 'vs/platform/configuration/common/configurationService'; import { IRequestService } from 'vs/platform/request/common/request'; @@ -47,18 +44,17 @@ import { IFileService } from 'vs/platform/files/common/files'; import { ITunnelService } from 'vs/platform/remote/common/tunnel'; import { TunnelService } from 'vs/platform/remote/node/tunnelService'; import { IProductService } from 'vs/platform/product/common/productService'; -import { IPathWithLineAndColumn, isValidBasename, parseLineAndColumnAware, sanitizeFilePath } from 'vs/base/common/extpath'; -import { rtrim, trim } from 'vs/base/common/strings'; -import { basename, join, resolve } from 'vs/base/common/path'; -import { coalesce, distinct } from 'vs/base/common/arrays'; +import { join } from 'vs/base/common/path'; +import { coalesce } from 'vs/base/common/arrays'; import { EnvironmentMainService, IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; import { LoggerService } from 'vs/platform/log/node/loggerService'; -import { cwd } from 'vs/base/common/process'; import { IProtocolMainService } from 'vs/platform/protocol/electron-main/protocol'; import { ProtocolMainService } from 'vs/platform/protocol/electron-main/protocolMainService'; import { Promises } from 'vs/base/common/async'; +// eslint-disable-next-line code-import-patterns +import { ArgumentParser } from 'vs/platform/environment/argumentParser'; /** * The main VS Code entry point. @@ -67,9 +63,10 @@ import { Promises } from 'vs/base/common/async'; * running and a second instance is started from the command line. It will always * try to communicate with an existing instance to prevent that 2 VS Code instances * are running at the same time. + * + * @coder Moved argument parsing code to `ArgumentParser` for sharing with `CodeServer`. */ -class CodeMain { - +export class CodeMain extends ArgumentParser { main(): void { try { this.startup(); @@ -419,122 +416,7 @@ class CodeMain { lifecycleMainService.kill(exitCode); } - //#region Command line arguments utilities - - private resolveArgs(): NativeParsedArgs { - - // Parse arguments - const args = this.validatePaths(parseMainProcessArgv(process.argv)); - - // If we are started with --wait create a random temporary file - // and pass it over to the starting instance. We can use this file - // to wait for it to be deleted to monitor that the edited file - // is closed and then exit the waiting process. - // - // Note: we are not doing this if the wait marker has been already - // added as argument. This can happen if Code was started from CLI. - if (args.wait && !args.waitMarkerFilePath) { - const waitMarkerFilePath = createWaitMarkerFile(args.verbose); - if (waitMarkerFilePath) { - addArg(process.argv, '--waitMarkerFilePath', waitMarkerFilePath); - args.waitMarkerFilePath = waitMarkerFilePath; - } - } - - return args; - } - - private validatePaths(args: NativeParsedArgs): NativeParsedArgs { - - // Track URLs if they're going to be used - if (args['open-url']) { - args._urls = args._; - args._ = []; - } - - // Normalize paths and watch out for goto line mode - if (!args['remote']) { - const paths = this.doValidatePaths(args._, args.goto); - args._ = paths; - } - - return args; - } - - private doValidatePaths(args: string[], gotoLineMode?: boolean): string[] { - const currentWorkingDir = cwd(); - const result = args.map(arg => { - let pathCandidate = String(arg); - - let parsedPath: IPathWithLineAndColumn | undefined = undefined; - if (gotoLineMode) { - parsedPath = parseLineAndColumnAware(pathCandidate); - pathCandidate = parsedPath.path; - } - - if (pathCandidate) { - pathCandidate = this.preparePath(currentWorkingDir, pathCandidate); - } - - const sanitizedFilePath = sanitizeFilePath(pathCandidate, currentWorkingDir); - - const filePathBasename = basename(sanitizedFilePath); - if (filePathBasename /* can be empty if code is opened on root */ && !isValidBasename(filePathBasename)) { - return null; // do not allow invalid file names - } - - if (gotoLineMode && parsedPath) { - parsedPath.path = sanitizedFilePath; - - return this.toPath(parsedPath); - } - - return sanitizedFilePath; - }); - - const caseInsensitive = isWindows || isMacintosh; - const distinctPaths = distinct(result, path => path && caseInsensitive ? path.toLowerCase() : (path || '')); - - return coalesce(distinctPaths); - } - - private preparePath(cwd: string, path: string): string { - - // Trim trailing quotes - if (isWindows) { - path = rtrim(path, '"'); // https://github.com/microsoft/vscode/issues/1498 - } - - // Trim whitespaces - path = trim(trim(path, ' '), '\t'); - - if (isWindows) { - - // Resolve the path against cwd if it is relative - path = resolve(cwd, path); - - // Trim trailing '.' chars on Windows to prevent invalid file names - path = rtrim(path, '.'); - } - - return path; - } - - private toPath(pathWithLineAndCol: IPathWithLineAndColumn): string { - const segments = [pathWithLineAndCol.path]; - - if (typeof pathWithLineAndCol.line === 'number') { - segments.push(String(pathWithLineAndCol.line)); - } - - if (typeof pathWithLineAndCol.column === 'number') { - segments.push(String(pathWithLineAndCol.column)); - } - - return segments.join(':'); - } - //#endregion } // Main Startup diff --git a/src/vs/code/node/cli.ts b/src/vs/code/node/cli.ts index 9c7ad6268b6fb..642603996e2b2 100644 --- a/src/vs/code/node/cli.ts +++ b/src/vs/code/node/cli.ts @@ -54,6 +54,13 @@ export async function main(argv: string[]): Promise { console.log(buildVersionMessage(product.version, product.commit)); } + else if (args.server) { + const server = await new Promise((resolve, reject) => require(['vs/server/entry'], resolve, reject)); + await server.main(args); + + return; + } + // Extensions Management else if (shouldSpawnCliProcess(args)) { const cli = await new Promise((resolve, reject) => require(['vs/code/node/cliProcessMain'], resolve, reject)); diff --git a/src/vs/platform/environment/argumentParser.ts b/src/vs/platform/environment/argumentParser.ts new file mode 100644 index 0000000000000..0be59fa6e828f --- /dev/null +++ b/src/vs/platform/environment/argumentParser.ts @@ -0,0 +1,135 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + + +import { NativeParsedArgs } from 'vs/platform/environment/common/argv'; +import { addArg, parseMainProcessArgv } from 'vs/platform/environment/node/argvHelper'; +import { isWindows, isMacintosh } from 'vs/base/common/platform'; +import { createWaitMarkerFile } from 'vs/platform/environment/node/wait'; +import { IPathWithLineAndColumn, isValidBasename, parseLineAndColumnAware, sanitizeFilePath } from 'vs/base/common/extpath'; +import { rtrim, trim } from 'vs/base/common/strings'; +import { basename, resolve } from 'vs/base/common/path'; +import { cwd } from 'vs/base/common/process'; +import { coalesce, distinct } from 'vs/base/common/arrays'; + +/** + * Command line arg parser. + * + * @coder This is originally from `CodeMain` but has been separated for use in `CodeServer` + */ +export class ArgumentParser { + protected resolveArgs(): NativeParsedArgs { + + // Parse arguments + const args = this.validatePaths(parseMainProcessArgv(process.argv)); + + // If we are started with --wait create a random temporary file + // and pass it over to the starting instance. We can use this file + // to wait for it to be deleted to monitor that the edited file + // is closed and then exit the waiting process. + // + // Note: we are not doing this if the wait marker has been already + // added as argument. This can happen if Code was started from CLI. + if (args.wait && !args.waitMarkerFilePath) { + const waitMarkerFilePath = createWaitMarkerFile(args.verbose); + if (waitMarkerFilePath) { + addArg(process.argv, '--waitMarkerFilePath', waitMarkerFilePath); + args.waitMarkerFilePath = waitMarkerFilePath; + } + } + + return args; + } + + private validatePaths(args: NativeParsedArgs): NativeParsedArgs { + + // Track URLs if they're going to be used + if (args['open-url']) { + args._urls = args._; + args._ = []; + } + + // Normalize paths and watch out for goto line mode + if (!args['remote']) { + const paths = this.doValidatePaths(args._, args.goto); + args._ = paths; + } + + return args; + } + + private doValidatePaths(args: string[], gotoLineMode?: boolean): string[] { + const currentWorkingDir = cwd(); + const result = args.map(arg => { + let pathCandidate = String(arg); + + let parsedPath: IPathWithLineAndColumn | undefined = undefined; + if (gotoLineMode) { + parsedPath = parseLineAndColumnAware(pathCandidate); + pathCandidate = parsedPath.path; + } + + if (pathCandidate) { + pathCandidate = this.preparePath(currentWorkingDir, pathCandidate); + } + + const sanitizedFilePath = sanitizeFilePath(pathCandidate, currentWorkingDir); + + const filePathBasename = basename(sanitizedFilePath); + if (filePathBasename /* can be empty if code is opened on root */ && !isValidBasename(filePathBasename)) { + return null; // do not allow invalid file names + } + + if (gotoLineMode && parsedPath) { + parsedPath.path = sanitizedFilePath; + + return this.toPath(parsedPath); + } + + return sanitizedFilePath; + }); + + const caseInsensitive = isWindows || isMacintosh; + const distinctPaths = distinct(result, path => path && caseInsensitive ? path.toLowerCase() : (path || '')); + + return coalesce(distinctPaths); + } + + private preparePath(cwd: string, path: string): string { + + // Trim trailing quotes + if (isWindows) { + path = rtrim(path, '"'); // https://github.com/microsoft/vscode/issues/1498 + } + + // Trim whitespaces + path = trim(trim(path, ' '), '\t'); + + if (isWindows) { + + // Resolve the path against cwd if it is relative + path = resolve(cwd, path); + + // Trim trailing '.' chars on Windows to prevent invalid file names + path = rtrim(path, '.'); + } + + return path; + } + + private toPath(pathWithLineAndCol: IPathWithLineAndColumn): string { + const segments = [pathWithLineAndCol.path]; + + if (typeof pathWithLineAndCol.line === 'number') { + segments.push(String(pathWithLineAndCol.line)); + } + + if (typeof pathWithLineAndCol.column === 'number') { + segments.push(String(pathWithLineAndCol.column)); + } + + return segments.join(':'); + } +} diff --git a/src/vs/platform/environment/common/argv.ts b/src/vs/platform/environment/common/argv.ts index 70a7eb4b86e41..a09cd6a72e3ae 100644 --- a/src/vs/platform/environment/common/argv.ts +++ b/src/vs/platform/environment/common/argv.ts @@ -40,6 +40,12 @@ export interface NativeParsedArgs { 'extensions-dir'?: string; 'extensions-download-dir'?: string; 'builtin-extensions-dir'?: string; + /** @coder: BEGIN */ + 'extra-extensions-dir'?: string[]; + 'extra-builtin-extensions-dir'?: string[]; + 'ignore-last-opened'?: string, + 'server'?: string, + /** @coder: END */ extensionDevelopmentPath?: string[]; // undefined or array of 1 or more local paths or URIs extensionTestsPath?: string; // either a local path or a URI extensionDevelopmentKind?: string[]; diff --git a/src/vs/platform/environment/common/environment.ts b/src/vs/platform/environment/common/environment.ts index 77efaac3fbaff..f5d5fd956db88 100644 --- a/src/vs/platform/environment/common/environment.ts +++ b/src/vs/platform/environment/common/environment.ts @@ -124,6 +124,9 @@ export interface INativeEnvironmentService extends IEnvironmentService { extensionsPath: string; extensionsDownloadPath: string; builtinExtensionsPath: string; + // NOTE@coder: add extraExtensionPaths/extraBuiltinExtensionPaths + extraExtensionPaths: string[]; + extraBuiltinExtensionPaths: string[]; // --- smoke test support driverHandle?: string; diff --git a/src/vs/platform/environment/common/environmentService.ts b/src/vs/platform/environment/common/environmentService.ts index 70e06765c6970..03650810378f0 100644 --- a/src/vs/platform/environment/common/environmentService.ts +++ b/src/vs/platform/environment/common/environmentService.ts @@ -15,6 +15,7 @@ import { URI } from 'vs/base/common/uri'; import { ExtensionKind } from 'vs/platform/extensions/common/extensions'; import { env } from 'vs/base/common/process'; + export interface INativeEnvironmentPaths { /** @@ -157,6 +158,19 @@ export abstract class AbstractNativeEnvironmentService implements INativeEnviron return joinPath(this.userHome, this.productService.dataFolderName, 'extensions').fsPath; } + /** + * NOTE@coder: add extraExtensionPaths and extraBuiltinExtensionPaths + */ + @memoize + get extraExtensionPaths(): string[] { + return (this._args['extra-extensions-dir'] || []).map((p) => resolve(p)); + } + + @memoize + get extraBuiltinExtensionPaths(): string[] { + return (this._args['extra-builtin-extensions-dir'] || []).map((p) => resolve(p)); + } + @memoize get extensionDevelopmentLocationURI(): URI[] | undefined { const extensionDevelopmentPaths = this.args.extensionDevelopmentPath; diff --git a/src/vs/platform/environment/node/argv.ts b/src/vs/platform/environment/node/argv.ts index 2715617e2bc92..169e56652effa 100644 --- a/src/vs/platform/environment/node/argv.ts +++ b/src/vs/platform/environment/node/argv.ts @@ -51,7 +51,13 @@ export const OPTIONS: OptionDescriptions> = { 'extensions-dir': { type: 'string', deprecates: 'extensionHomePath', cat: 'e', args: 'dir', description: localize('extensionHomePath', "Set the root path for extensions.") }, 'extensions-download-dir': { type: 'string' }, + /** @coder: BEGIN */ 'builtin-extensions-dir': { type: 'string' }, + 'extra-extensions-dir': { type: 'string[]', cat: 'o', description: localize('extra-extensions-dir', 'Path to an extra user extension directory.') }, + 'extra-builtin-extensions-dir': { type: 'string[]', cat: 'o', description: localize('extra-builtin-extensions-dir', 'Path to an extra builtin extension directory.') }, + 'ignore-last-opened': { type: 'string', cat: 'o', description: localize('ignore-last-opened', "Ignore last opened.") }, + 'server': { type: 'string', cat: 'o', description: localize('server', "Run a remote server e.g. http://localhost:8080") }, + /** @coder: END */ 'list-extensions': { type: 'boolean', cat: 'e', description: localize('listExtensions', "List the installed extensions.") }, 'show-versions': { type: 'boolean', cat: 'e', description: localize('showVersions', "Show versions of installed extensions, when using --list-extensions.") }, 'category': { type: 'string', cat: 'e', description: localize('category', "Filters installed extensions by provided category, when using --list-extensions."), args: 'category' }, @@ -78,7 +84,7 @@ export const OPTIONS: OptionDescriptions> = { 'max-memory': { type: 'string', cat: 't', description: localize('maxMemory', "Max memory size for a window (in Mbytes)."), args: 'memory' }, 'telemetry': { type: 'boolean', cat: 't', description: localize('telemetry', "Shows all telemetry events which VS code collects.") }, - 'remote': { type: 'string' }, + 'remote': { type: 'string', cat: 'o', description: localize('remote', "Remote authority e.g. http://localhost:8080") }, 'folder-uri': { type: 'string[]', cat: 'o', args: 'uri' }, 'file-uri': { type: 'string[]', cat: 'o', args: 'uri' }, diff --git a/src/vs/platform/extensionManagement/node/extensionsScanner.ts b/src/vs/platform/extensionManagement/node/extensionsScanner.ts index 052919c84798a..120386e3e7302 100644 --- a/src/vs/platform/extensionManagement/node/extensionsScanner.ts +++ b/src/vs/platform/extensionManagement/node/extensionsScanner.ts @@ -97,7 +97,7 @@ export class ExtensionsScanner extends Disposable { } async scanAllUserExtensions(): Promise { - return this.scanExtensionsInDir(this.extensionsPath, ExtensionType.User); + return this.scanExtensionsInDirs([this.extensionsPath, ...this.environmentService.extraExtensionPaths], ExtensionType.User); } async extractUserExtension(identifierWithVersion: ExtensionIdentifierWithVersion, zipPath: string, token: CancellationToken): Promise { @@ -273,6 +273,7 @@ export class ExtensionsScanner extends Disposable { return [...systemExtensions, ...devSystemExtensions]; } + private async scanExtensionsInDir(dir: string, type: ExtensionType): Promise { const limiter = new Limiter(10); const stat = await this.fileService.resolve(URI.file(dir)); @@ -313,7 +314,7 @@ export class ExtensionsScanner extends Disposable { } private async scanDefaultSystemExtensions(): Promise { - const result = await this.scanExtensionsInDir(this.systemExtensionsPath, ExtensionType.System); + const result = await this.scanExtensionsInDirs([this.systemExtensionsPath, ...this.environmentService.extraBuiltinExtensionPaths], ExtensionType.System); this.logService.trace('Scanned system extensions:', result.length); return result; } @@ -417,4 +418,9 @@ export class ExtensionsScanner extends Disposable { } }); } + + private async scanExtensionsInDirs(dirs: string[], type: ExtensionType): Promise { + const results = await Promise.all(dirs.map((path) => this.scanExtensionsInDir(path, type))); + return results.reduce((flat, current) => flat.concat(current), []); + } } diff --git a/src/vs/platform/log/node/spdlogLog.ts b/src/vs/platform/log/node/spdlogLog.ts index a57db48bc31d4..9a6caa8e2f2e4 100644 --- a/src/vs/platform/log/node/spdlogLog.ts +++ b/src/vs/platform/log/node/spdlogLog.ts @@ -14,7 +14,9 @@ async function createSpdLogLogger(name: string, logfilePath: string, filesize: n _spdlog.setFlushOn(LogLevel.Trace); return _spdlog.createAsyncRotatingLogger(name, logfilePath, filesize, filecount); } catch (e) { - console.error(e); + // console.error(e); + // TODO@coder enable + console.error('generic log failure'); } return null; } diff --git a/src/vs/platform/product/common/product.ts b/src/vs/platform/product/common/product.ts index 1eafa991bd947..c41a9f0337ae3 100644 --- a/src/vs/platform/product/common/product.ts +++ b/src/vs/platform/product/common/product.ts @@ -73,6 +73,13 @@ else { ], }); } + + // NOTE@coder: Add the ability to inject settings from the server. + const el = document.getElementById('vscode-remote-product-configuration'); + const rawProductConfiguration = el && el.getAttribute('data-settings'); + if (rawProductConfiguration) { + Object.assign(product, JSON.parse(rawProductConfiguration)); + } } export default product; diff --git a/src/vs/platform/remote/browser/browserSocketFactory.ts b/src/vs/platform/remote/browser/browserSocketFactory.ts index abf7428a8ff60..d83dc184c1e95 100644 --- a/src/vs/platform/remote/browser/browserSocketFactory.ts +++ b/src/vs/platform/remote/browser/browserSocketFactory.ts @@ -241,6 +241,8 @@ export class BrowserSocketFactory implements ISocketFactory { connect(host: string, port: number, query: string, callback: IConnectCallback): void { const socket = this._webSocketFactory.create(`ws://${/:/.test(host) ? `[${host}]` : host}:${port}/?${query}&skipWebSocketFrames=false`); + // // NOTE@coder: Modified to work against the current path. + // const socket = this._webSocketFactory.create(`${window.location.protocol === 'https:' ? 'wss' : 'ws'}://${window.location.host}${window.location.pathname}?${query}&skipWebSocketFrames=false`); const errorListener = socket.onError((err) => callback(err, undefined)); socket.onOpen(() => { errorListener.dispose(); @@ -248,6 +250,3 @@ export class BrowserSocketFactory implements ISocketFactory { }); } } - - - diff --git a/src/vs/platform/remote/common/remoteAgentConnection.ts b/src/vs/platform/remote/common/remoteAgentConnection.ts index e6e9a607fbfe2..e261f9f546ad1 100644 --- a/src/vs/platform/remote/common/remoteAgentConnection.ts +++ b/src/vs/platform/remote/common/remoteAgentConnection.ts @@ -25,7 +25,10 @@ export const enum ConnectionType { Tunnel = 3, } -function connectionTypeToString(connectionType: ConnectionType): string { +/** + * @coder exported for server side use. + */ +export function connectionTypeToString(connectionType: ConnectionType): string { switch (connectionType) { case ConnectionType.Management: return 'Management'; @@ -63,7 +66,17 @@ export interface OKMessage { type: 'ok'; } -export type HandshakeMessage = AuthRequest | SignRequest | ConnectionTypeRequest | ErrorMessage | OKMessage; +/** + * Expected response from `CodeServer` connection. + * @coder Moved from inline type for use outside this module. + */ +export interface DebugMessage { + type: 'debug'; + debugPort?: NonNullable; +} + + +export type HandshakeMessage = AuthRequest | SignRequest | ConnectionTypeRequest | ErrorMessage | OKMessage | DebugMessage; interface ISimpleConnectionOptions { @@ -225,9 +238,10 @@ function raceWithTimeoutCancellation(promise: Promise, timeoutCancellation async function connectToRemoteExtensionHostAgent(options: ISimpleConnectionOptions, connectionType: ConnectionType, args: any | undefined, timeoutCancellationToken: CancellationToken): Promise<{ protocol: PersistentProtocol; ownsProtocol: boolean; }> { const logPrefix = connectLogPrefix(options, connectionType); - options.logService.trace(`${logPrefix} 1/6. invoking socketFactory.connect().`); + options.logService.info(`${logPrefix} 1/6. invoking socketFactory.connect().`); let socket: ISocket; + try { socket = await createSocket(options.logService, options.socketFactory, options.host, options.port, `reconnectionToken=${options.reconnectionToken}&reconnection=${options.reconnectionProtocol ? 'true' : 'false'}`, timeoutCancellationToken); } catch (error) { @@ -236,7 +250,7 @@ async function connectToRemoteExtensionHostAgent(options: ISimpleConnectionOptio throw error; } - options.logService.trace(`${logPrefix} 2/6. socketFactory.connect() was successful.`); + options.logService.info(`${logPrefix} 2/6. socketFactory.connect() was successful.`); let protocol: PersistentProtocol; let ownsProtocol: boolean; @@ -249,7 +263,7 @@ async function connectToRemoteExtensionHostAgent(options: ISimpleConnectionOptio ownsProtocol = true; } - options.logService.trace(`${logPrefix} 3/6. sending AuthRequest control message.`); + options.logService.info(`${logPrefix} 3/6. sending AuthRequest control message.`); const authRequest: AuthRequest = { type: 'auth', auth: options.connectionToken || '00000000000000000000' @@ -265,7 +279,7 @@ async function connectToRemoteExtensionHostAgent(options: ISimpleConnectionOptio throw error; } - options.logService.trace(`${logPrefix} 4/6. received SignRequest control message.`); + options.logService.info(`${logPrefix} 4/6. received SignRequest control message.`); const signed = await raceWithTimeoutCancellation(options.signService.sign(msg.data), timeoutCancellationToken); const connTypeRequest: ConnectionTypeRequest = { @@ -278,7 +292,7 @@ async function connectToRemoteExtensionHostAgent(options: ISimpleConnectionOptio connTypeRequest.args = args; } - options.logService.trace(`${logPrefix} 5/6. sending ConnectionTypeRequest control message.`); + options.logService.info(`${logPrefix} 5/6. sending ConnectionTypeRequest control message.`); protocol.sendControl(VSBuffer.fromString(JSON.stringify(connTypeRequest))); return { protocol, ownsProtocol }; @@ -322,7 +336,7 @@ async function connectToRemoteExtensionHostAgentAndReadOneMessage(options: IS if (options.reconnectionProtocol) { options.reconnectionProtocol.endAcceptReconnection(); } - options.logService.trace(`${logPrefix} 6/6. handshake finished, connection is up and running after ${logElapsed(startTime)}!`); + options.logService.info(`${logPrefix} 6/6. handshake finished, connection is up and running after ${logElapsed(startTime)}!`); result.resolve({ protocol, firstMessage: msg }); } })); @@ -348,7 +362,9 @@ interface IExtensionHostConnectionResult { } async function doConnectRemoteAgentExtensionHost(options: ISimpleConnectionOptions, startArguments: IRemoteExtensionHostStartParams, timeoutCancellationToken: CancellationToken): Promise { - const { protocol, firstMessage } = await connectToRemoteExtensionHostAgentAndReadOneMessage<{ debugPort?: number; }>(options, ConnectionType.ExtensionHost, startArguments, timeoutCancellationToken); + console.log('>>> READING ONE MESSAGE', options, startArguments); + const { protocol, firstMessage } = await connectToRemoteExtensionHostAgentAndReadOneMessage(options, ConnectionType.ExtensionHost, startArguments, timeoutCancellationToken); + console.log('>>> READ ONE MESSAGE', JSON.stringify(firstMessage)); const debugPort = firstMessage && firstMessage.debugPort; return { protocol, debugPort }; } @@ -361,7 +377,7 @@ async function doConnectRemoteAgentTunnel(options: ISimpleConnectionOptions, sta const startTime = Date.now(); const logPrefix = connectLogPrefix(options, ConnectionType.Tunnel); const { protocol } = await connectToRemoteExtensionHostAgent(options, ConnectionType.Tunnel, startParams, timeoutCancellationToken); - options.logService.trace(`${logPrefix} 6/6. handshake finished, connection is up and running after ${logElapsed(startTime)}!`); + options.logService.info(`${logPrefix} 6/6. handshake finished, connection is up and running after ${logElapsed(startTime)}!`); return protocol; } @@ -551,7 +567,7 @@ abstract class PersistentConnection extends Disposable { })); this._register(protocol.onSocketTimeout(() => { const logPrefix = commonLogPrefix(this._connectionType, this.reconnectionToken, true); - this._options.logService.trace(`${logPrefix} received socket timeout event.`); + this._options.logService.info(`${logPrefix} received socket timeout event.`); this._beginReconnecting(); })); @@ -630,19 +646,19 @@ abstract class PersistentConnection extends Disposable { } if (RemoteAuthorityResolverError.isTemporarilyNotAvailable(err)) { this._options.logService.info(`${logPrefix} A temporarily not available error occurred while trying to reconnect, will try again...`); - this._options.logService.trace(err); + this._options.logService.info(err); // try again! continue; } if ((err.code === 'ETIMEDOUT' || err.code === 'ENETUNREACH' || err.code === 'ECONNREFUSED' || err.code === 'ECONNRESET') && err.syscall === 'connect') { this._options.logService.info(`${logPrefix} A network error occurred while trying to reconnect, will try again...`); - this._options.logService.trace(err); + this._options.logService.info(err); // try again! continue; } if (isPromiseCanceledError(err)) { this._options.logService.info(`${logPrefix} A promise cancelation error occurred while trying to reconnect, will try again...`); - this._options.logService.trace(err); + this._options.logService.info(err); // try again! continue; } diff --git a/src/vs/platform/storage/common/storage.ts b/src/vs/platform/storage/common/storage.ts index 939b3a436439c..697af9adadfa4 100644 --- a/src/vs/platform/storage/common/storage.ts +++ b/src/vs/platform/storage/common/storage.ts @@ -105,8 +105,10 @@ export interface IStorageService { * * @param target allows to define the target of the storage operation * to either the current machine or user. + * + * NOTE@coder: Add a promise so extensions can await storage writes. */ - store(key: string, value: string | boolean | number | undefined | null, scope: StorageScope, target: StorageTarget): void; + store(key: string, value: string | boolean | number | undefined | null, scope: StorageScope, target: StorageTarget): Promise | void; /** * Delete an element stored under the provided key from storage. @@ -333,46 +335,49 @@ export abstract class AbstractStorageService extends Disposable implements IStor return this.getStorage(scope)?.getNumber(key, fallbackValue); } - store(key: string, value: string | boolean | number | undefined | null, scope: StorageScope, target: StorageTarget): void { + // NOTE@coder: Make a promise so extensions can await storage writes. + store(key: string, value: string | boolean | number | undefined | null, scope: StorageScope, target: StorageTarget): Promise | void { // We remove the key for undefined/null values if (isUndefinedOrNull(value)) { this.remove(key, scope); - return; + return Promise.resolve(); } // Update our datastructures but send events only after - this.withPausedEmitters(() => { + return this.withPausedEmitters(() => { // Update key-target map this.updateKeyTarget(key, scope, target); // Store actual value - this.getStorage(scope)?.set(key, value); + return this.getStorage(scope)?.set(key, value); }); } - remove(key: string, scope: StorageScope): void { + // NOTE@coder: Make a promise so extensions can await the storage write. + remove(key: string, scope: StorageScope): Promise | void { // Update our datastructures but send events only after - this.withPausedEmitters(() => { + return this.withPausedEmitters(() => { // Update key-target map this.updateKeyTarget(key, scope, undefined); // Remove actual key - this.getStorage(scope)?.delete(key); + return this.getStorage(scope)?.delete(key); }); } - private withPausedEmitters(fn: Function): void { + // NOTE@coder: Return the function's return so extensions can await the storage write. + private withPausedEmitters(fn: () => T): T { // Pause emitters this._onDidChangeValue.pause(); this._onDidChangeTarget.pause(); try { - fn(); + return fn(); } finally { // Resume emitters diff --git a/src/vs/server/channel.ts b/src/vs/server/channel.ts new file mode 100644 index 0000000000000..f9bb92ef8daff --- /dev/null +++ b/src/vs/server/channel.ts @@ -0,0 +1,564 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Coder Technologies. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as os from 'os'; +import * as path from 'path'; +import { VSBuffer } from 'vs/base/common/buffer'; +import { CancellationTokenSource } from 'vs/base/common/cancellation'; +import { Emitter, Event } from 'vs/base/common/event'; +import { IDisposable } from 'vs/base/common/lifecycle'; +import * as platform from 'vs/base/common/platform'; +import * as resources from 'vs/base/common/resources'; +import { ReadableStreamEventPayload } from 'vs/base/common/stream'; +import { URI, UriComponents } from 'vs/base/common/uri'; +import { transformOutgoingURIs } from 'vs/base/common/uriIpc'; +import { IServerChannel } from 'vs/base/parts/ipc/common/ipc'; +import { IDiagnosticInfo } from 'vs/platform/diagnostics/common/diagnostics'; +import { INativeEnvironmentService } from 'vs/platform/environment/common/environment'; +import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { FileDeleteOptions, FileOpenOptions, FileOverwriteOptions, FileReadStreamOptions, FileType, FileWriteOptions, IStat, IWatchOptions } from 'vs/platform/files/common/files'; +import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider'; +import { ConsoleLogger, ILogService } from 'vs/platform/log/common/log'; +import product from 'vs/platform/product/common/product'; +import { IRemoteAgentEnvironment, RemoteAgentConnectionContext } from 'vs/platform/remote/common/remoteAgentEnvironment'; +import { ITelemetryData, ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { IShellLaunchConfig, ITerminalEnvironment } from 'vs/platform/terminal/common/terminal'; +import { getTranslations } from 'vs/server/nls'; +import { IFileChangeDto } from 'vs/workbench/api/common/extHost.protocol'; +import { IEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariable'; +import { MergedEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariableCollection'; +import { deserializeEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariableShared'; +import * as terminal from 'vs/workbench/contrib/terminal/common/remoteTerminalChannel'; +import * as terminalEnvironment from 'vs/workbench/contrib/terminal/common/terminalEnvironment'; +import { AbstractVariableResolverService } from 'vs/workbench/services/configurationResolver/common/variableResolver'; +import { ExtensionScanner, ExtensionScannerInput } from 'vs/workbench/services/extensions/node/extensionPoints'; +import { PtyHostService } from 'vs/platform/terminal/node/ptyHostService'; +import { createServerURITransformer } from 'vs/base/common/uriServer'; + +const logger = new ConsoleLogger(); + +/** + * Extend the file provider to allow unwatching. + */ +class Watcher extends DiskFileSystemProvider { + public readonly watches = new Map(); + + public override dispose(): void { + this.watches.forEach((w) => w.dispose()); + this.watches.clear(); + super.dispose(); + } + + public _watch(req: number, resource: URI, opts: IWatchOptions): void { + this.watches.set(req, this.watch(resource, opts)); + } + + public unwatch(req: number): void { + this.watches.get(req)!.dispose(); + this.watches.delete(req); + } +} + +export class FileProviderChannel implements IServerChannel, IDisposable { + private readonly provider: DiskFileSystemProvider; + private readonly watchers = new Map(); + + public constructor( + private readonly environmentService: INativeEnvironmentService, + private readonly logService: ILogService, + ) { + this.provider = new DiskFileSystemProvider(this.logService); + } + + public listen(context: RemoteAgentConnectionContext, event: string, args?: any): Event { + switch (event) { + case 'filechange': return this.filechange(context, args[0]); + case 'readFileStream': return this.readFileStream(args[0], args[1]); + } + + throw new Error(`Invalid listen '${event}'`); + } + + private filechange(context: RemoteAgentConnectionContext, session: string): Event { + const emitter = new Emitter({ + onFirstListenerAdd: () => { + const provider = new Watcher(this.logService); + this.watchers.set(session, provider); + const transformer = createServerURITransformer(context.remoteAuthority); + provider.onDidChangeFile((events) => { + emitter.fire(events.map((event) => ({ + ...event, + resource: transformer.transformOutgoing(event.resource), + }))); + }); + provider.onDidErrorOccur((event) => this.logService.error(event)); + }, + onLastListenerRemove: () => { + this.watchers.get(session)!.dispose(); + this.watchers.delete(session); + }, + }); + + return emitter.event; + } + + private readFileStream(resource: UriComponents, opts: FileReadStreamOptions): Event> { + const cts = new CancellationTokenSource(); + const fileStream = this.provider.readFileStream(this.transform(resource), opts, cts.token); + const emitter = new Emitter>({ + onFirstListenerAdd: () => { + fileStream.on('data', (data) => emitter.fire(VSBuffer.wrap(data))); + fileStream.on('error', (error) => emitter.fire(error)); + fileStream.on('end', () => emitter.fire('end')); + }, + onLastListenerRemove: () => cts.cancel(), + }); + + return emitter.event; + } + + public call(_: unknown, command: string, args?: any): Promise { + switch (command) { + case 'stat': return this.stat(args[0]); + case 'open': return this.open(args[0], args[1]); + case 'close': return this.close(args[0]); + case 'read': return this.read(args[0], args[1], args[2]); + case 'readFile': return this.readFile(args[0]); + case 'write': return this.write(args[0], args[1], args[2], args[3], args[4]); + case 'writeFile': return this.writeFile(args[0], args[1], args[2]); + case 'delete': return this.delete(args[0], args[1]); + case 'mkdir': return this.mkdir(args[0]); + case 'readdir': return this.readdir(args[0]); + case 'rename': return this.rename(args[0], args[1], args[2]); + case 'copy': return this.copy(args[0], args[1], args[2]); + case 'watch': return this.watch(args[0], args[1], args[2], args[3]); + case 'unwatch': return this.unwatch(args[0], args[1]); + } + + throw new Error(`Invalid call '${command}'`); + } + + public dispose(): void { + this.watchers.forEach((w) => w.dispose()); + this.watchers.clear(); + } + + private async stat(resource: UriComponents): Promise { + return this.provider.stat(this.transform(resource)); + } + + private async open(resource: UriComponents, opts: FileOpenOptions): Promise { + return this.provider.open(this.transform(resource), opts); + } + + private async close(fd: number): Promise { + return this.provider.close(fd); + } + + private async read(fd: number, pos: number, length: number): Promise<[VSBuffer, number]> { + const buffer = VSBuffer.alloc(length); + const bytesRead = await this.provider.read(fd, pos, buffer.buffer, 0, length); + return [buffer, bytesRead]; + } + + private async readFile(resource: UriComponents): Promise { + return VSBuffer.wrap(await this.provider.readFile(this.transform(resource))); + } + + private write(fd: number, pos: number, buffer: VSBuffer, offset: number, length: number): Promise { + return this.provider.write(fd, pos, buffer.buffer, offset, length); + } + + private writeFile(resource: UriComponents, buffer: VSBuffer, opts: FileWriteOptions): Promise { + return this.provider.writeFile(this.transform(resource), buffer.buffer, opts); + } + + private async delete(resource: UriComponents, opts: FileDeleteOptions): Promise { + return this.provider.delete(this.transform(resource), opts); + } + + private async mkdir(resource: UriComponents): Promise { + return this.provider.mkdir(this.transform(resource)); + } + + private async readdir(resource: UriComponents): Promise<[string, FileType][]> { + return this.provider.readdir(this.transform(resource)); + } + + private async rename(resource: UriComponents, target: UriComponents, opts: FileOverwriteOptions): Promise { + return this.provider.rename(this.transform(resource), URI.from(target), opts); + } + + private copy(resource: UriComponents, target: UriComponents, opts: FileOverwriteOptions): Promise { + return this.provider.copy(this.transform(resource), URI.from(target), opts); + } + + private async watch(session: string, req: number, resource: UriComponents, opts: IWatchOptions): Promise { + this.watchers.get(session)!._watch(req, this.transform(resource), opts); + } + + private async unwatch(session: string, req: number): Promise { + this.watchers.get(session)!.unwatch(req); + } + + private transform(resource: UriComponents): URI { + // Used for walkthrough content. + if (/^\/static[^/]*\//.test(resource.path)) { + return URI.file(this.environmentService.appRoot + resource.path.replace(/^\/static[^/]*\//, '/')); + // Used by the webview service worker to load resources. + } else if (resource.path === '/vscode-resource' && resource.query) { + try { + const query = JSON.parse(resource.query); + if (query.requestResourcePath) { + return URI.file(query.requestResourcePath); + } + } catch (error) { /* Carry on. */ } + } + return URI.from(resource); + } +} + +// See ../../workbench/services/remote/common/remoteAgentEnvironmentChannel.ts +export class ExtensionEnvironmentChannel implements IServerChannel { + public constructor( + private readonly environment: INativeEnvironmentService, + private readonly log: ILogService, + private readonly telemetry: ITelemetryService, + private readonly connectionToken: string, + ) { } + + public listen(_: unknown, event: string): Event { + throw new Error(`Invalid listen '${event}'`); + } + + public async call(context: any, command: string, args: any): Promise { + switch (command) { + case 'getEnvironmentData': + return transformOutgoingURIs( + await this.getEnvironmentData(), + createServerURITransformer(context.remoteAuthority), + ); + case 'scanExtensions': + return transformOutgoingURIs( + await this.scanExtensions(args.language), + createServerURITransformer(context.remoteAuthority), + ); + case 'getDiagnosticInfo': return this.getDiagnosticInfo(); + case 'disableTelemetry': return this.disableTelemetry(); + case 'logTelemetry': return this.logTelemetry(args.eventName, args.data); + case 'flushTelemetry': return this.flushTelemetry(); + } + throw new Error(`Invalid call '${command}'`); + } + + private async getEnvironmentData(): Promise { + return { + pid: process.pid, + connectionToken: this.connectionToken, + appRoot: URI.file(this.environment.appRoot), + settingsPath: this.environment.settingsResource, + logsPath: URI.file(this.environment.logsPath), + extensionsPath: URI.file(this.environment.extensionsPath!), + extensionHostLogsPath: URI.file(path.join(this.environment.logsPath, 'extension-host')), + globalStorageHome: this.environment.globalStorageHome, + workspaceStorageHome: this.environment.workspaceStorageHome, + userHome: this.environment.userHome, + useHostProxy: false, + os: platform.OS, + marks: [] + }; + } + + private async scanExtensions(language: string): Promise { + const translations = await getTranslations(language, this.environment.userDataPath); + + const scanMultiple = (isBuiltin: boolean, isUnderDevelopment: boolean, paths: string[]): Promise => { + return Promise.all(paths.map((path) => { + return ExtensionScanner.scanExtensions(new ExtensionScannerInput( + product.version, + product.date, + product.commit, + language, + !!process.env.VSCODE_DEV, + path, + isBuiltin, + isUnderDevelopment, + translations, + ), this.log); + })); + }; + + const scanBuiltin = async (): Promise => { + return scanMultiple(true, false, [this.environment.builtinExtensionsPath, ...this.environment.extraBuiltinExtensionPaths]); + }; + + const scanInstalled = async (): Promise => { + return scanMultiple(false, true, [this.environment.extensionsPath!, ...this.environment.extraExtensionPaths]); + }; + + return Promise.all([scanBuiltin(), scanInstalled()]).then((allExtensions) => { + const uniqueExtensions = new Map(); + allExtensions.forEach((multipleExtensions) => { + multipleExtensions.forEach((extensions) => { + extensions.forEach((extension) => { + const id = ExtensionIdentifier.toKey(extension.identifier); + if (uniqueExtensions.has(id)) { + const oldPath = uniqueExtensions.get(id)!.extensionLocation.fsPath; + const newPath = extension.extensionLocation.fsPath; + this.log.warn(`${oldPath} has been overridden ${newPath}`); + } + uniqueExtensions.set(id, extension); + }); + }); + }); + return Array.from(uniqueExtensions.values()); + }); + } + + private getDiagnosticInfo(): Promise { + throw new Error('not implemented'); + } + + private async disableTelemetry(): Promise { + this.telemetry.setEnabled(false); + } + + private async logTelemetry(eventName: string, data: ITelemetryData): Promise { + this.telemetry.publicLog(eventName, data); + } + + private async flushTelemetry(): Promise { + // We always send immediately at the moment. + } +} + +// Reference: - ../../workbench/api/common/extHostDebugService.ts +class VariableResolverService extends AbstractVariableResolverService { + constructor( + remoteAuthority: string, + args: terminal.ICreateTerminalProcessArguments, + env: platform.IProcessEnvironment, + ) { + super({ + getFolderUri: (name: string): URI | undefined => { + const folder = args.workspaceFolders.find((f) => f.name === name); + return folder && URI.revive(folder.uri); + }, + getWorkspaceFolderCount: (): number => { + return args.workspaceFolders.length; + }, + // In ../../workbench/contrib/terminal/common/remoteTerminalChannel.ts it + // looks like there are `config:` entries which must be for this? Not sure + // how/if the URI comes into play though. + getConfigurationValue: (_: URI, section: string): string | undefined => { + return args.resolvedVariables[`config:${section}`]; + }, + getAppRoot: (): string | undefined => { + return (args.resolverEnv && args.resolverEnv['VSCODE_CWD']) || env['VSCODE_CWD'] || process.cwd(); + }, + getExecPath: (): string | undefined => { + // Assuming that resolverEnv is just for use in the resolver and not for + // the terminal itself. + return (args.resolverEnv && args.resolverEnv['VSCODE_EXEC_PATH']) || env['VSCODE_EXEC_PATH']; + }, + // This is just a guess; this is the only file-related thing we're sent + // and none of these resolver methods seem to get called so I don't know + // how to test. + getFilePath: (): string | undefined => { + const resource = transformIncoming(remoteAuthority, args.activeFileResource); + if (!resource) { + return undefined; + } + // See ../../editor/standalone/browser/simpleServices.ts; + // `BaseConfigurationResolverService` calls `getUriLabel` from there. + if (resource.scheme === 'file') { + return resource.fsPath; + } + return resource.path; + }, + // It looks like these are set here although they aren't on the types: + // ../../workbench/contrib/terminal/common/remoteTerminalChannel.ts + getSelectedText: (): string | undefined => { + return args.resolvedVariables.selectedText; + }, + getLineNumber: (): string | undefined => { + return args.resolvedVariables.selectedText; + }, + }, undefined, Promise.resolve(env)); + } +} + +export class TerminalProviderChannel implements IServerChannel, IDisposable { + public constructor( + private readonly logService: ILogService, + private readonly ptyService: PtyHostService, + ) { } + + public listen(_: RemoteAgentConnectionContext, event: string, args: any): Event { + logger.trace('TerminalProviderChannel:listen', event, args); + + switch (event) { + case '$onPtyHostExitEvent': return this.ptyService.onPtyHostExit || Event.None; + case '$onPtyHostStartEvent': return this.ptyService.onPtyHostStart || Event.None; + case '$onPtyHostUnresponsiveEvent': return this.ptyService.onPtyHostUnresponsive || Event.None; + case '$onPtyHostResponsiveEvent': return this.ptyService.onPtyHostResponsive || Event.None; + case '$onPtyHostRequestResolveVariablesEvent': return this.ptyService.onPtyHostRequestResolveVariables || Event.None; + case '$onProcessDataEvent': return this.ptyService.onProcessData; + case '$onProcessExitEvent': return this.ptyService.onProcessExit; + case '$onProcessReadyEvent': return this.ptyService.onProcessReady; + case '$onProcessReplayEvent': return this.ptyService.onProcessReplay; + case '$onProcessTitleChangedEvent': return this.ptyService.onProcessTitleChanged; + case '$onProcessShellTypeChangedEvent': return this.ptyService.onProcessShellTypeChanged; + case '$onProcessOverrideDimensionsEvent': return this.ptyService.onProcessOverrideDimensions; + case '$onProcessResolvedShellLaunchConfigEvent': return this.ptyService.onProcessResolvedShellLaunchConfig; + case '$onProcessOrphanQuestion': return this.ptyService.onProcessOrphanQuestion; + // NOTE@asher: I think this must have something to do with running + // commands on the terminal that will do things in VS Code but we + // already have that functionality via a socket so I'm not sure what + // this is for. + // NOTE: VSCODE_IPC_HOOK_CLI is now missing, perhaps this is meant to + // replace that in some way. + case '$onExecuteCommand': return Event.None; + } + + throw new Error(`Invalid listen '${event}'`); + } + + public call(context: RemoteAgentConnectionContext, command: string, args: any): Promise { + logger.trace('TerminalProviderChannel:call', command, args); + + switch (command) { + case '$restartPtyHost': return this.ptyService.restartPtyHost(); + case '$createProcess': return this.createProcess(context.remoteAuthority, args); + case '$attachToProcess': return this.ptyService.attachToProcess(args[0]); + case '$start': return this.ptyService.start(args[0]); + case '$input': return this.ptyService.input(args[0], args[1]); + case '$acknowledgeDataEvent': return this.ptyService.acknowledgeDataEvent(args[0], args[1]); + case '$shutdown': return this.ptyService.shutdown(args[0], args[1]); + case '$resize': return this.ptyService.resize(args[0], args[1], args[2]); + case '$getInitialCwd': return this.ptyService.getInitialCwd(args[0]); + case '$getCwd': return this.ptyService.getCwd(args[0]); + case '$sendCommandResult': return this.sendCommandResult(args[0], args[1], args[2], args[3]); + case '$orphanQuestionReply': return this.ptyService.orphanQuestionReply(args[0]); + case '$listProcesses': return this.ptyService.listProcesses(); + case '$setTerminalLayoutInfo': return this.ptyService.setTerminalLayoutInfo(args); + case '$getTerminalLayoutInfo': return this.ptyService.getTerminalLayoutInfo(args); + case '$getEnvironment': return this.ptyService.getEnvironment(); + case '$getDefaultSystemShell': return this.ptyService.getDefaultSystemShell(args[0]); + case '$reduceConnectionGraceTime': return this.ptyService.reduceConnectionGraceTime(); + case '$updateTitle': return this.ptyService.updateTitle(args[0], args[1], args[2]); + case '$getProfiles': return this.ptyService.getProfiles(args[0], args[1], args[2]); + case '$acceptPtyHostResolvedVariables': return this.ptyService.acceptPtyHostResolvedVariables(args[0], args[1]); + } + + throw new Error(`Invalid call '${command}'`); + } + + public async dispose(): Promise { + // Nothing at the moment. + } + + // References: - ../../workbench/api/node/extHostTerminalService.ts + // - ../../workbench/contrib/terminal/browser/terminalProcessManager.ts + private async createProcess(remoteAuthority: string, args: terminal.ICreateTerminalProcessArguments): Promise { + const shellLaunchConfig: IShellLaunchConfig = { + name: args.shellLaunchConfig.name, + executable: args.shellLaunchConfig.executable, + args: args.shellLaunchConfig.args, + // TODO: Should we transform if it's a string as well? The incoming + // transform only takes `UriComponents` so I suspect it's not necessary. + cwd: typeof args.shellLaunchConfig.cwd !== 'string' + ? transformIncoming(remoteAuthority, args.shellLaunchConfig.cwd) + : args.shellLaunchConfig.cwd, + env: args.shellLaunchConfig.env, + }; + + const activeWorkspaceUri = transformIncoming(remoteAuthority, args.activeWorkspaceFolder?.uri); + const activeWorkspace = activeWorkspaceUri && args.activeWorkspaceFolder ? { + ...args.activeWorkspaceFolder, + uri: activeWorkspaceUri, + toResource: (relativePath: string) => resources.joinPath(activeWorkspaceUri, relativePath), + } : undefined; + + const resolverService = new VariableResolverService(remoteAuthority, args, process.env); + const resolver = terminalEnvironment.createVariableResolver(activeWorkspace, process.env, resolverService); + + shellLaunchConfig.cwd = terminalEnvironment.getCwd( + shellLaunchConfig, + os.homedir(), + resolver, + activeWorkspaceUri, + args.configuration['terminal.integrated.cwd'], + this.logService, + ); + + // Use instead of `terminal.integrated.env.${platform}` to make types work. + const getEnvFromConfig = (): ITerminalEnvironment => { + if (platform.isWindows) { + return args.configuration['terminal.integrated.env.windows']; + } else if (platform.isMacintosh) { + return args.configuration['terminal.integrated.env.osx']; + } + return args.configuration['terminal.integrated.env.linux']; + }; + + // ptyHostService calls getEnvironment in the ptyHost process it creates, + // which uses that process's environment. The process spawned doesn't have + // VSCODE_IPC_HOOK_CLI in its env, so we add it here. + const getEnvironment = async (): Promise => { + const env = await this.ptyService.getEnvironment(); + env.VSCODE_IPC_HOOK_CLI = process.env['VSCODE_IPC_HOOK_CLI']!; + return env; + }; + + const env = terminalEnvironment.createTerminalEnvironment( + shellLaunchConfig, + getEnvFromConfig(), + resolver, + product.version, + args.configuration['terminal.integrated.detectLocale'], + await getEnvironment() + ); + + // Apply extension environment variable collections to the environment. + if (!shellLaunchConfig.strictEnv) { + // They come in an array and in serialized format. + const envVariableCollections = new Map(); + for (const [k, v] of args.envVariableCollections) { + envVariableCollections.set(k, { map: deserializeEnvironmentVariableCollection(v) }); + } + const mergedCollection = new MergedEnvironmentVariableCollection(envVariableCollections); + mergedCollection.applyToProcessEnvironment(env); + } + + const persistentTerminalId = await this.ptyService.createProcess( + shellLaunchConfig, + shellLaunchConfig.cwd, + args.cols, + args.rows, + env, + process.env as platform.IProcessEnvironment, // Environment used for findExecutable + false, // windowsEnableConpty + args.shouldPersistTerminal, + args.workspaceId, + args.workspaceName, + ); + + return { + persistentTerminalId, + resolvedShellLaunchConfig: shellLaunchConfig, + }; + } + + private async sendCommandResult(_id: number, _reqId: number, _isError: boolean, _payload: any): Promise { + // NOTE: Not required unless we implement the matching event, see above. + throw new Error('not implemented'); + } +} + +function transformIncoming(remoteAuthority: string, uri: UriComponents | undefined): URI | undefined { + const transformer = createServerURITransformer(remoteAuthority); + return uri ? URI.revive(transformer.transformIncoming(uri)) : uri; +} diff --git a/src/vs/server/connection/abstractConnection.ts b/src/vs/server/connection/abstractConnection.ts new file mode 100644 index 0000000000000..41a5e05e1d410 --- /dev/null +++ b/src/vs/server/connection/abstractConnection.ts @@ -0,0 +1,120 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Coder Technologies. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { onUnexpectedError } from 'vs/base/common/errors'; +import { Emitter } from 'vs/base/common/event'; +import { ConsoleLogger } from 'vs/platform/log/common/log'; +import { ServerProtocol } from 'vs/server/protocol'; + +export abstract class AbstractConnection { + private readonly _onClose = new Emitter(); + /** + * Fire when the connection is closed (not just disconnected). This should + * only happen when the connection is offline and old or has an error. + */ + public readonly onClose = this._onClose.event; + private disposed = false; + private _offline: number | undefined; + + protected get logPrefix() { + return `[${this.name}] ${this.protocol.logPrefix}`; + } + + public constructor( + protected readonly protocol: ServerProtocol, + protected readonly logService: ConsoleLogger, + public readonly name: string, + ) { + + this.logService.debug('Connecting...'); + this.onClose(() => this.logService.debug('Closed')); + } + + public get offline(): number | undefined { + return this._offline; + } + + public reconnect(protocol: ServerProtocol): void { + this.logService.debug(`${this.protocol.reconnectionToken} Reconnecting...`); + this._offline = undefined; + this.doReconnect(protocol); + } + + public dispose(reason?: string): void { + this.logService.debug(`${this.protocol.reconnectionToken} Disposing...`, reason); + if (!this.disposed) { + this.disposed = true; + this.doDispose(); + this._onClose.fire(); + } + } + + public safeDisposeProtocolAndSocket(): void { + try { + this.protocol.acceptDisconnect(); + const socket = this.protocol.getSocket(); + this.protocol.dispose(); + socket.dispose(); + } catch (err) { + onUnexpectedError(err); + } + } + + protected setOffline(): void { + this.logService.debug('Disconnected'); + if (!this._offline) { + this._offline = Date.now(); + } + } + + /** + * Set up the connection on a new socket. + */ + protected abstract doReconnect(protcol: ServerProtocol): void; + + /** + * Dispose/destroy everything permanently. + */ + protected abstract doDispose(): void; +} + +/** + * Connection options sent by the client via a remote agent connection. + */ +export interface ConnectionOptions { + /** The token is how we identify and connect to existing sessions. */ + readonly reconnectionToken: string, + /** Specifies that the client is trying to reconnect. */ + readonly reconnection: boolean, + /** If true assume this is not a web socket (always false for code-server). */ + readonly skipWebSocketFrames: boolean, +} + +/** + * Convenience function to convert a client's query params into a useful object. + */ +export function parseQueryConnectionOptions(query: URLSearchParams): ConnectionOptions { + const reconnectionToken = query.get('reconnectionToken'); + const reconnection = query.get('reconnection'); + const skipWebSocketFrames = query.get('skipWebSocketFrames'); + + if (typeof reconnectionToken !== 'string') { + throw new Error('`reconnectionToken` not present in query'); + } + + if (typeof reconnection !== 'string') { + throw new Error('`reconnection` not present in query'); + } + + if (typeof skipWebSocketFrames !== 'string') { + throw new Error('`skipWebSocketFrames` not present in query'); + } + + return { + reconnectionToken, + reconnection: reconnection === 'true', + skipWebSocketFrames: skipWebSocketFrames === 'true', + }; +} diff --git a/src/vs/server/connection/extensionHostConnection.ts b/src/vs/server/connection/extensionHostConnection.ts new file mode 100644 index 0000000000000..b16ebca756064 --- /dev/null +++ b/src/vs/server/connection/extensionHostConnection.ts @@ -0,0 +1,202 @@ +import { FileAccess } from 'vs/base/common/network'; +import { INativeEnvironmentService } from 'vs/platform/environment/common/environment'; +import { DebugMessage, IRemoteExtensionHostStartParams } from 'vs/platform/remote/common/remoteAgentConnection'; +import { AbstractConnection } from 'vs/server/connection/abstractConnection'; +import { getNlsConfiguration } from 'vs/server/nls'; +import { ServerProtocol } from 'vs/server/protocol'; +import { NLSConfiguration } from 'vs/base/node/languagePacks'; +import { parseExtensionDevOptions } from 'vs/workbench/services/extensions/common/extensionDevOptions'; +import { findFreePort } from 'vs/base/node/ports'; +import { ExtensionHost, IIPCOptions } from 'vs/base/parts/ipc/node/ipc.cp'; +import { VSBuffer } from 'vs/base/common/buffer'; +import { SendHandle } from 'child_process'; +import { ConsoleLogger } from 'vs/platform/log/common/log'; + +export interface ForkEnvironmentVariables { + VSCODE_AMD_ENTRYPOINT: string; + /** One or the other. */ + VSCODE_EXTHOST_WILL_SEND_SOCKET: true; + VSCODE_HANDLES_UNCAUGHT_ERRORS: boolean; + VSCODE_LOG_LEVEL?: string; + VSCODE_LOG_NATIVE: boolean; + VSCODE_LOG_STACK: boolean; + VSCODE_NLS_CONFIG: NLSConfiguration; + VSCODE_PIPE_LOGGING: boolean; + VSCODE_VERBOSE_LOGGING: boolean; + VSCODE_CODE_CACHE_PATH?: string; +} + +/** + * This complements the client-side `PersistantConnection` in `RemoteExtensionHost`. + */ +export class ExtensionHostConnection extends AbstractConnection { + private clientProcess?: ExtensionHost; + + /** @TODO Document usage. */ + public readonly _isExtensionDevHost: boolean; + public readonly _isExtensionDevDebug: boolean; + public readonly _isExtensionDevTestFromCli: boolean; + + public constructor( + protocol: ServerProtocol, + logService: ConsoleLogger, + private readonly startParams: IRemoteExtensionHostStartParams, + private readonly _environmentService: INativeEnvironmentService, + ) { + super(protocol, logService, 'ExtensionHost'); + + const devOpts = parseExtensionDevOptions(this._environmentService); + this._isExtensionDevHost = devOpts.isExtensionDevHost; + this._isExtensionDevDebug = devOpts.isExtensionDevDebug; + this._isExtensionDevTestFromCli = devOpts.isExtensionDevTestFromCli; + } + + private get debugMessage(): DebugMessage { + return { + type: 'debug', + debugPort: typeof this.startParams.port === 'number' ? this.startParams.port : undefined + }; + } + + /** + * Find a free port if extension host debugging is enabled. + */ + private async _tryFindDebugPort(): Promise { + if (typeof this._environmentService.debugExtensionHost.port !== 'number') { + return 0; + } + + const expected = this.startParams.port || this._environmentService.debugExtensionHost.port; + const port = await findFreePort(expected, 10 /* try 10 ports */, 5000 /* try up to 5 seconds */); + + if (!this._isExtensionDevTestFromCli) { + if (!port) { + console.warn('%c[Extension Host] %cCould not find a free port for debugging', 'color: blue', 'color:'); + } else { + if (port !== expected) { + console.warn(`%c[Extension Host] %cProvided debugging port ${expected} is not free, using ${port} instead.`, 'color: blue', 'color:'); + } + } + } + + return port || 0; + } + + protected doDispose(): void { + this.protocol.dispose(); + + this.clientProcess?.dispose(); + } + + protected doReconnect(reconnectionProtocol: ServerProtocol): void { + this.logService.info(this.logPrefix, '(Reconnect 1/4)', 'Sending new protocol debug message...'); + reconnectionProtocol.sendMessage(this.debugMessage); + + this.logService.info(this.logPrefix, '(Reconnect 2/4)', 'Swapping socket references...'); + + this.protocol.beginAcceptReconnection(reconnectionProtocol.getSocket(), reconnectionProtocol.readEntireBuffer()); + this.protocol.endAcceptReconnection(); + + this.logService.info(this.logPrefix, '(Reconnect 3/4)', 'Pausing socket until we have a chance to forward its data.'); + const { initialDataChunk, sendHandle } = this.protocol.suspend(); + + const messageSent = this.sendInitMessage(initialDataChunk, this.protocol.inflateBytes, sendHandle); + + if (!messageSent) { + new Error('Child process did not receive init message. Is their a backlog?'); + } + + this.logService.info(this.logPrefix, '(Reconnect 4/4)', 'Child process received init message!'); + } + + /** + * Sends IPC socket to client process. + * @remark This is the complement of `extensionHostProcessSetup.ts#_createExtHostProtocol` + */ + private sendInitMessage(initialDataChunk: VSBuffer, inflateBytes: VSBuffer, sendHandle: SendHandle): boolean { + if (!this.clientProcess) { + throw new Error(`${this.logPrefix} Client process is not set`); + } + + this.logService.info(this.logPrefix, 'Sending init message to client process...'); + + return this.clientProcess.sendIPCMessage({ + type: 'VSCODE_EXTHOST_IPC_SOCKET', + initialDataChunk: Buffer.from(initialDataChunk.buffer).toString('base64'), + skipWebSocketFrames: this.protocol.skipWebSocketFrames, + permessageDeflate: this.protocol.getSocket().permessageDeflate, + inflateBytes: inflateBytes ? Buffer.from(inflateBytes.buffer).toString('base64') : '', + }, sendHandle); + } + + private async generateClientOptions(): Promise { + this.logService.debug('Getting NLS configuration...'); + const config = await getNlsConfiguration(this.startParams.language, this._environmentService.userDataPath); + const portNumber = await this._tryFindDebugPort(); + + return { + serverName: 'Server Extension Host', + freshExecArgv: true, + debugBrk: this.startParams.break ? portNumber : undefined, + debug: this.startParams.break ? undefined : portNumber, + args: ['--type=extensionHost', '--skipWorkspaceStorageLock'], + env: { + VSCODE_AMD_ENTRYPOINT: 'vs/workbench/services/extensions/node/extensionHostProcess', + VSCODE_PIPE_LOGGING: true, + VSCODE_VERBOSE_LOGGING: true, + /** Extension child process will wait until socket is sent. */ + VSCODE_EXTHOST_WILL_SEND_SOCKET: true, + VSCODE_HANDLES_UNCAUGHT_ERRORS: true, + VSCODE_LOG_STACK: false, + VSCODE_LOG_LEVEL: this._environmentService.verbose ? 'trace' : (this._environmentService.logLevel || process.env.LOG_LEVEL), + VSCODE_NLS_CONFIG: config, + VSCODE_LOG_NATIVE: this._isExtensionDevHost, + // Unset `VSCODE_CODE_CACHE_PATH` when developing extensions because it might + // be that dependencies, that otherwise would be cached, get modified. + VSCODE_CODE_CACHE_PATH: this._isExtensionDevHost ? undefined : process.env['VSCODE_CODE_CACHE_PATH'] + } + }; + } + + /** + * Creates an extension host child process. + * @remark this is very similar to `LocalProcessExtensionHost` + */ + public spawn(): Promise { + return new Promise(async (resolve, reject) => { + this.logService.info(this.logPrefix, '(Spawn 1/7)', 'Sending client initial debug message.'); + this.protocol.sendMessage(this.debugMessage); + + this.logService.info(this.logPrefix, '(Spawn 2/7)', 'Pausing socket until we have a chance to forward its data.'); + + const { initialDataChunk, sendHandle } = this.protocol.suspend(); + + this.logService.info(this.logPrefix, '(Spawn 3/7)', 'Generating IPC client options...'); + const clientOptions = await this.generateClientOptions(); + + this.logService.info(this.logPrefix, '(Spawn 4/7)', 'Starting extension host child process...'); + this.clientProcess = new ExtensionHost(FileAccess.asFileUri('bootstrap-fork', require).fsPath, clientOptions); + + this.clientProcess.onDidProcessExit(({ code, signal }) => { + this.dispose(); + + if (code !== 0 && signal !== 'SIGTERM') { + this.logService.error(`${this.logPrefix} Extension host exited with code: ${code} and signal: ${signal}.`); + } + }); + + this.clientProcess.onReady(() => { + this.logService.info(this.logPrefix, '(Spawn 5/7)', 'Extension host is ready!'); + this.logService.info(this.logPrefix, '(Spawn 6/7)', 'Sending init message to child process...'); + const messageSent = this.sendInitMessage(initialDataChunk, this.protocol.inflateBytes, sendHandle); + + if (messageSent) { + this.logService.info(this.logPrefix, '(Spawn 7/7)', 'Child process received init message!'); + return resolve(); + } + + reject(new Error('Child process did not receive init message. Is their a backlog?')); + }); + }); + } +} diff --git a/src/vs/server/connection/managementConnection.ts b/src/vs/server/connection/managementConnection.ts new file mode 100644 index 0000000000000..26e44e79e48d0 --- /dev/null +++ b/src/vs/server/connection/managementConnection.ts @@ -0,0 +1,34 @@ +// /*--------------------------------------------------------------------------------------------- +// * Copyright (c) Coder Technologies. All rights reserved. +// * Licensed under the MIT License. See License.txt in the project root for license information. +// *--------------------------------------------------------------------------------------------*/ + +import { ConsoleLogger } from 'vs/platform/log/common/log'; +import { AbstractConnection } from 'vs/server/connection/abstractConnection'; +import { ServerProtocol } from 'vs/server/protocol'; + +/** + * Used for all the IPC channels. + */ +export class ManagementConnection extends AbstractConnection { + public constructor(protocol: ServerProtocol, logService: ConsoleLogger) { + super(protocol, logService, 'management'); + + protocol.onDidDispose(() => this.dispose('Explicitly closed')); + protocol.onSocketClose(() => this.setOffline()); // Might reconnect. + + protocol.sendMessage({ type: 'ok' }); + } + + protected doDispose(): void { + this.protocol.dispose(); + } + + protected doReconnect(reconnectionProtocol: ServerProtocol): void { + reconnectionProtocol.sendMessage({ type: 'ok' }); + + this.protocol.beginAcceptReconnection(reconnectionProtocol.getSocket(), reconnectionProtocol.readEntireBuffer()); + this.protocol.endAcceptReconnection(); + reconnectionProtocol.dispose(); + } +} diff --git a/src/vs/server/entry.ts b/src/vs/server/entry.ts new file mode 100644 index 0000000000000..7b673df1b4b7d --- /dev/null +++ b/src/vs/server/entry.ts @@ -0,0 +1,96 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Coder Technologies. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { setUnexpectedErrorHandler } from 'vs/base/common/errors'; +import * as proxyAgent from 'vs/base/node/proxy_agent'; +import { enableCustomMarketplace } from 'vs/server/marketplace'; +import { CodeServerMain, VscodeServerArgs as ServerArgs } from 'vs/server/server'; +import { createServer, IncomingHttpHeaders, IncomingMessage } from 'http'; +import * as net from 'net'; + +// eslint-disable-next-line code-import-patterns +import { requestHandler as defaultRequestHandler } from '../../../resources/web/code-web'; +import { createHash } from 'crypto'; +import { ConnectionOptions, parseQueryConnectionOptions } from 'vs/server/connection/abstractConnection'; + +const logger = console; + +setUnexpectedErrorHandler((error) => { + logger.warn('Uncaught error', error instanceof Error ? error.message : error); +}); + +enableCustomMarketplace(); +proxyAgent.monkeyPatch(true); + +type UpgradeHandler = (request: IncomingMessage, socket: net.Socket, upgradeHead: Buffer) => void; + +export async function main(args: ServerArgs) { + const serverUrl = new URL(`http://${args.server}`); + + const codeServer = new CodeServerMain(); + const workbenchConstructionOptions = await codeServer.createWorkbenchConstructionOptions(serverUrl); + + const httpServer = createServer((req, res) => defaultRequestHandler(req, res, workbenchConstructionOptions)); + + const upgrade: UpgradeHandler = (req, socket) => { + if (req.headers['upgrade'] !== 'websocket' || !req.url) { + logger.error(`failed to upgrade for header "${req.headers['upgrade']}" and url: "${req.url}".`); + socket.end('HTTP/1.1 400 Bad Request'); + return; + } + + const upgradeUrl = new URL(req.url, serverUrl.toString()); + logger.log('Upgrade from', upgradeUrl.toString()); + + let connectionOptions: ConnectionOptions; + + try { + connectionOptions = parseQueryConnectionOptions(upgradeUrl.searchParams); + } catch (error: unknown) { + logger.error(error); + socket.end('HTTP/1.1 400 Bad Request'); + return; + } + + socket.on('error', e => { + logger.error(`[${connectionOptions.reconnectionToken}] Socket failed for "${req.url}".`, e); + }); + + + const { responseHeaders, permessageDeflate } = createReponseHeaders(req.headers); + socket.write(responseHeaders); + + codeServer.handleWebSocket(socket, connectionOptions, permessageDeflate); + }; + + httpServer.on('upgrade', upgrade); + + return new Promise((resolve, reject) => { + httpServer.listen(parseInt(serverUrl.port, 10), serverUrl.hostname, () => { + logger.info('Code Server active listening at:', serverUrl.toString()); + }); + }); +} + +function createReponseHeaders(incomingHeaders: IncomingHttpHeaders) { + const acceptKey = incomingHeaders['sec-websocket-key']; + // WebSocket standard hash suffix. + const hash = createHash('sha1').update(acceptKey + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11').digest('base64'); + + const responseHeaders = ['HTTP/1.1 101 Web Socket Protocol Handshake', 'Upgrade: WebSocket', 'Connection: Upgrade', `Sec-WebSocket-Accept: ${hash}`]; + + let permessageDeflate = false; + + if (String(incomingHeaders['sec-websocket-extensions']).indexOf('permessage-deflate') !== -1) { + permessageDeflate = true; + responseHeaders.push('Sec-WebSocket-Extensions: permessage-deflate; server_max_window_bits=15'); + } + + return { + responseHeaders: responseHeaders.join('\r\n') + '\r\n\r\n', + permessageDeflate + }; +} + diff --git a/src/vs/server/insights.ts b/src/vs/server/insights.ts new file mode 100644 index 0000000000000..db9a65c7dfbbc --- /dev/null +++ b/src/vs/server/insights.ts @@ -0,0 +1,129 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Coder Technologies. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as appInsights from 'applicationinsights'; +import * as https from 'https'; +import * as http from 'http'; +import * as os from 'os'; + +class Channel { + public get _sender() { + throw new Error('unimplemented'); + } + public get _buffer() { + throw new Error('unimplemented'); + } + + public setUseDiskRetryCaching(): void { + throw new Error('unimplemented'); + } + public send(): void { + throw new Error('unimplemented'); + } + public triggerSend(): void { + throw new Error('unimplemented'); + } +} + +export class TelemetryClient { + public context: any = undefined; + public commonProperties: any = undefined; + public config: any = {}; + + public channel: any = new Channel(); + + public addTelemetryProcessor(): void { + throw new Error('unimplemented'); + } + + public clearTelemetryProcessors(): void { + throw new Error('unimplemented'); + } + + public runTelemetryProcessors(): void { + throw new Error('unimplemented'); + } + + public trackTrace(): void { + throw new Error('unimplemented'); + } + + public trackMetric(): void { + throw new Error('unimplemented'); + } + + public trackException(): void { + throw new Error('unimplemented'); + } + + public trackRequest(): void { + throw new Error('unimplemented'); + } + + public trackDependency(): void { + throw new Error('unimplemented'); + } + + public track(): void { + throw new Error('unimplemented'); + } + + public trackNodeHttpRequestSync(): void { + throw new Error('unimplemented'); + } + + public trackNodeHttpRequest(): void { + throw new Error('unimplemented'); + } + + public trackNodeHttpDependency(): void { + throw new Error('unimplemented'); + } + + public trackEvent(options: appInsights.Contracts.EventTelemetry): void { + if (!options.properties) { + options.properties = {}; + } + if (!options.measurements) { + options.measurements = {}; + } + + try { + const cpus = os.cpus(); + options.measurements.cores = cpus.length; + options.properties['common.cpuModel'] = cpus[0].model; + } catch (error) { } + + try { + options.measurements.memoryFree = os.freemem(); + options.measurements.memoryTotal = os.totalmem(); + } catch (error) { } + + try { + options.properties['common.shell'] = os.userInfo().shell; + options.properties['common.release'] = os.release(); + options.properties['common.arch'] = os.arch(); + } catch (error) { } + + try { + const url = process.env.TELEMETRY_URL || 'https://v1.telemetry.coder.com/track'; + const request = (/^http:/.test(url) ? http : https).request(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + }); + request.on('error', () => { /* We don't care. */ }); + request.write(JSON.stringify(options)); + request.end(); + } catch (error) { } + } + + public flush(options: { callback: (v: string) => void }): void { + if (options.callback) { + options.callback(''); + } + } +} diff --git a/src/vs/server/marketplace.ts b/src/vs/server/marketplace.ts new file mode 100644 index 0000000000000..da2ad9983efb3 --- /dev/null +++ b/src/vs/server/marketplace.ts @@ -0,0 +1,179 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Coder Technologies. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as fs from 'fs'; +import * as path from 'path'; +import * as tarStream from 'tar-stream'; +import * as util from 'util'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import * as vszip from 'vs/base/node/zip'; +import * as nls from 'vs/nls'; +import product from 'vs/platform/product/common/product'; +import { IProductConfiguration } from 'vs/workbench/workbench.web.api'; + +// We will be overriding these, so keep a reference to the original. +const vszipExtract = vszip.extract; +const vszipBuffer = vszip.buffer; + +export const tar = async (tarPath: string, files: vszip.IFile[]): Promise => { + const pack = tarStream.pack(); + const chunks: Buffer[] = []; + const ended = new Promise((resolve) => { + pack.on('end', () => resolve(Buffer.concat(chunks))); + }); + pack.on('data', (chunk: Buffer) => chunks.push(chunk)); + for (let i = 0; i < files.length; i++) { + const file = files[i]; + pack.entry({ name: file.path }, file.contents); + } + pack.finalize(); + await util.promisify(fs.writeFile)(tarPath, await ended); + return tarPath; +}; + +export const extract = async (archivePath: string, extractPath: string, options: vszip.IExtractOptions = {}, token: CancellationToken): Promise => { + try { + await extractTar(archivePath, extractPath, options, token); + } catch (error) { + if (error.toString().includes('Invalid tar header')) { + await vszipExtract(archivePath, extractPath, options, token); + } + } +}; + +export const buffer = (targetPath: string, filePath: string): Promise => { + return new Promise(async (resolve, reject) => { + try { + let done: boolean = false; + await extractAssets(targetPath, new RegExp(filePath), (assetPath: string, data: Buffer) => { + if (path.normalize(assetPath) === path.normalize(filePath)) { + done = true; + resolve(data); + } + }); + if (!done) { + throw new Error('couldn\'t find asset ' + filePath); + } + } catch (error) { + if (error.toString().includes('Invalid tar header')) { + vszipBuffer(targetPath, filePath).then(resolve).catch(reject); + } else { + reject(error); + } + } + }); +}; + +const extractAssets = async (tarPath: string, match: RegExp, callback: (path: string, data: Buffer) => void): Promise => { + return new Promise((resolve, reject): void => { + const extractor = tarStream.extract(); + const fail = (error: Error) => { + extractor.destroy(); + reject(error); + }; + extractor.once('error', fail); + extractor.on('entry', async (header, stream, next) => { + const name = header.name; + if (match.test(name)) { + extractData(stream).then((data) => { + callback(name, data); + next(); + }).catch(fail); + } else { + stream.on('end', () => next()); + stream.resume(); // Just drain it. + } + }); + extractor.on('finish', resolve); + fs.createReadStream(tarPath).pipe(extractor); + }); +}; + +const extractData = (stream: NodeJS.ReadableStream): Promise => { + return new Promise((resolve, reject): void => { + const fileData: Buffer[] = []; + stream.on('error', reject); + stream.on('end', () => resolve(Buffer.concat(fileData))); + stream.on('data', (data) => fileData.push(data)); + }); +}; + +const extractTar = async (tarPath: string, targetPath: string, options: vszip.IExtractOptions = {}, token: CancellationToken): Promise => { + return new Promise((resolve, reject): void => { + const sourcePathRegex = new RegExp(options.sourcePath ? `^${options.sourcePath}` : ''); + const extractor = tarStream.extract(); + const fail = (error: Error) => { + extractor.destroy(); + reject(error); + }; + extractor.once('error', fail); + extractor.on('entry', async (header, stream, next) => { + const nextEntry = (): void => { + stream.on('end', () => next()); + stream.resume(); + }; + + const rawName = path.normalize(header.name); + if (token.isCancellationRequested || !sourcePathRegex.test(rawName)) { + return nextEntry(); + } + + const fileName = rawName.replace(sourcePathRegex, ''); + const targetFileName = path.join(targetPath, fileName); + if (/\/$/.test(fileName)) { + /* + NOTE:@coder: they removed mkdirp in favor of fs.promises + See commit: https://github.com/microsoft/vscode/commit/a0d76bb9834b63a02fba8017a6306511fe1ab4fe#diff-2bf233effbb62ea789bb7c4739d222a43ccd97ed9f1219f75bb07e9dee91c1a7 + 3/11/21 @jsjoeio + */ + return fs.promises.mkdir(targetFileName, { recursive: true }).then(nextEntry); + } + + const dirName = path.dirname(fileName); + const targetDirName = path.join(targetPath, dirName); + if (targetDirName.indexOf(targetPath) !== 0) { + return fail(new Error(nls.localize('invalid file', 'Error extracting {0}. Invalid file.', fileName))); + } + + /* + NOTE:@coder: they removed mkdirp in favor of fs.promises + See commit: https://github.com/microsoft/vscode/commit/a0d76bb9834b63a02fba8017a6306511fe1ab4fe#diff-2bf233effbb62ea789bb7c4739d222a43ccd97ed9f1219f75bb07e9dee91c1a7 + 3/11/21 @jsjoeio + */ + await fs.promises.mkdir(targetDirName, { recursive: true }); + + const fstream = fs.createWriteStream(targetFileName, { mode: header.mode }); + fstream.once('close', () => next()); + fstream.once('error', fail); + stream.pipe(fstream); + }); + extractor.once('finish', resolve); + fs.createReadStream(tarPath).pipe(extractor); + }); +}; + +/** + * Override original functionality so we can use a custom marketplace with + * either tars or zips. + */ +export const enableCustomMarketplace = (): void => { + const extensionsGallery: IProductConfiguration['extensionsGallery'] = { + serviceUrl: process.env.SERVICE_URL || 'https://extensions.coder.com/api', + resourceUrlTemplate: '', + itemUrl: process.env.ITEM_URL || '', + controlUrl: '', + recommendationsUrl: '', + ...(product.extensionsGallery || {}), + }; + + // Workaround for readonly property. + Object.assign(product, { extensionsGallery }); + + const target = vszip as typeof vszip; + + target.zip = tar; + target.extract = extract; + target.buffer = buffer; +}; diff --git a/src/vs/server/nls.ts b/src/vs/server/nls.ts new file mode 100644 index 0000000000000..4b16cc37c31a4 --- /dev/null +++ b/src/vs/server/nls.ts @@ -0,0 +1,93 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Coder Technologies. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as fs from 'fs'; +import * as path from 'path'; +import * as util from 'util'; +import { FileAccess } from 'vs/base/common/network'; +import * as lp from 'vs/base/node/languagePacks'; +import product from 'vs/platform/product/common/product'; +import { Translations } from 'vs/workbench/services/extensions/common/extensionPoints'; + +const configurations = new Map>(); +const metadataPath = path.join(FileAccess.asFileUri('', require).fsPath, 'nls.metadata.json'); + +export const isInternalConfiguration = (config: lp.NLSConfiguration): config is lp.InternalNLSConfiguration => { + return config && !!(config)._languagePackId; +}; + +const DefaultConfiguration = { + locale: 'en', + availableLanguages: {}, +}; + +export const getNlsConfiguration = async (locale: string, userDataPath: string): Promise => { + const id = `${locale}: ${userDataPath}`; + if (!configurations.has(id)) { + configurations.set(id, new Promise(async (resolve) => { + const config = product.commit && await util.promisify(fs.exists)(metadataPath) + ? await lp.getNLSConfiguration(product.commit, userDataPath, metadataPath, locale) + : DefaultConfiguration; + if (isInternalConfiguration(config)) { + config._languagePackSupport = true; + } + // If the configuration has no results keep trying since code-server + // doesn't restart when a language is installed so this result would + // persist (the plugin might not be installed yet or something). + if (config.locale !== 'en' && config.locale !== 'en-us' && Object.keys(config.availableLanguages).length === 0) { + configurations.delete(id); + } + resolve(config); + })); + } + return configurations.get(id)!; +}; + +export const getTranslations = async (locale: string, userDataPath: string): Promise => { + const config = await getNlsConfiguration(locale, userDataPath); + if (isInternalConfiguration(config)) { + try { + return JSON.parse(await util.promisify(fs.readFile)(config._translationsConfigFile, 'utf8')); + } catch (error) { /* Nothing yet. */ } + } + return {}; +}; + +export const getLocaleFromConfig = async (userDataPath: string): Promise => { + const files = ['locale.json', 'argv.json']; + for (let i = 0; i < files.length; ++i) { + try { + const localeConfigUri = path.join(userDataPath, 'User', files[i]); + const content = stripComments(await util.promisify(fs.readFile)(localeConfigUri, 'utf8')); + return JSON.parse(content).locale; + } catch (error) { /* Ignore. */ } + } + return 'en'; +}; + +// Taken from src/main.js in the main VS Code source. +const stripComments = (content: string): string => { + const regexp = /('(?:[^\\']*(?:\\.)?)*')|('(?:[^\\']*(?:\\.)?)*')|(\/\*(?:\r?\n|.)*?\*\/)|(\/{2,}.*?(?:(?:\r?\n)|$))/g; + + return content.replace(regexp, (match, _m1, _m2, m3, m4) => { + // Only one of m1, m2, m3, m4 matches + if (m3) { + // A block comment. Replace with nothing + return ''; + } else if (m4) { + // A line comment. If it ends in \r?\n then keep it. + const length_1 = m4.length; + if (length_1 > 2 && m4[length_1 - 1] === '\n') { + return m4[length_1 - 2] === '\r' ? '\r\n' : '\n'; + } + else { + return ''; + } + } else { + // We match a string + return match; + } + }); +}; diff --git a/src/vs/server/protocol.ts b/src/vs/server/protocol.ts new file mode 100644 index 0000000000000..1ac3cb3ea0287 --- /dev/null +++ b/src/vs/server/protocol.ts @@ -0,0 +1,193 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Coder Technologies. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { SendHandle } from 'child_process'; +import * as net from 'net'; +import { VSBuffer } from 'vs/base/common/buffer'; +import { ISocket, PersistentProtocol } from 'vs/base/parts/ipc/common/ipc.net'; +import { WebSocketNodeSocket } from 'vs/base/parts/ipc/node/ipc.net'; +import { ConsoleLogger } from 'vs/platform/log/common/log'; +import { ConnectionTypeRequest, HandshakeMessage } from 'vs/platform/remote/common/remoteAgentConnection'; +import { ConnectionOptions } from 'vs/server/connection/abstractConnection'; + +/** + * Matches `remoteAgentConnection.ts#connectToRemoteExtensionHostAgent` + */ +const HANDSHAKE_TIMEOUT_DURATION = 10000; + + +/** + * Trims a long token for a cleaner logging. + */ +export function trimConnectionToken(token: string): string { + return `${token.slice(0, 3)}...${token.slice(-3)}`; +} + +/** + * This server-side protocol is the complement of the client-side `PersistentConnection`. + */ +export class ServerProtocol extends PersistentProtocol { + public readonly logPrefix: string; + + constructor( + socket: ISocket, + private readonly logger: ConsoleLogger, + private readonly options: ConnectionOptions, + // `initialChunk` is likely not used. + initialChunk?: VSBuffer | null, + ) { + super(socket, initialChunk); + + this.logPrefix = `[${trimConnectionToken(this.reconnectionToken)}]`; + } + + public override getSocket() { + return super.getSocket() as WebSocketNodeSocket; + } + + /** + * Used when passing the underlying socket to a child process. + * + * @remark this may be undefined if the socket or its parent container is disposed. + */ + private getSendHandle(): net.Socket | undefined { + return this.getSocket().socket.socket; + } + + public get reconnectionToken() { + return this.options.reconnectionToken; + } + + public get reconnection() { + return this.options.reconnection; + } + + public get skipWebSocketFrames() { + return this.options.skipWebSocketFrames; + } + + /** + * Perform a handshake to get a connection request. + */ + public handshake(): Promise { + this.logger.info(this.logPrefix, '(Handshake 1/4)', 'Waiting for client authentication...'); + + return new Promise((resolve, reject) => { + const cleanup = () => { + onControlMessageHandler.dispose(); + onClose.dispose(); + clearTimeout(handshakeTimeout); + }; + + const onClose = this.onSocketClose(() => { + cleanup(); + this.logger.error('Handshake failed'); + reject(new Error('Protocol socket closed unexpectedly')); + }); + + const handshakeTimeout = setTimeout(() => { + cleanup(); + this.logger.error('Handshake timed out'); + reject(new Error('Protocol handshake timed out')); + }, HANDSHAKE_TIMEOUT_DURATION); + + const onControlMessageHandler = this.onControlMessage((rawMessage) => { + try { + const raw = rawMessage.toString(); + const message: HandshakeMessage = JSON.parse(raw); + + switch (message.type) { + + case 'auth': + this.logger.info(this.logPrefix, '(Handshake 2/4)', 'Client auth received!'); + this.sendMessage({ type: 'sign', data: message.auth }); + this.logger.info(this.logPrefix, '(Handshake 3/4)', 'Sent client signed auth...'); + break; + case 'connectionType': + cleanup(); + this.logger.info(this.logPrefix, '(Handshake 4/4)', 'Client has requested a connection!'); + resolve(message); + default: + throw new Error('Unrecognized message type'); + } + } catch (error) { + cleanup(); + reject(error); + } + }); + }); + } + + /** + * TODO: implement. + */ + public tunnel(): void { + throw new Error('Tunnel is not implemented yet'); + } + + /** + * Send a handshake message as a VSBuffer. + * @remark In the case of ExtensionHost it should only send a debug port. + */ + public sendMessage(message: HandshakeMessage): void { + this.logger.debug(this.logPrefix, `Sending control message to client (${message.type})`); + this.sendControl(VSBuffer.fromString(JSON.stringify(message))); + } + + /** + * Disconnect and dispose protocol. + */ + public override dispose(errorReason?: string): void { + try { + if (errorReason) { + this.sendMessage({ type: 'error', reason: errorReason }); + } + + // If still connected try notifying the client. + this.sendDisconnect(); + } catch (error) { + // I think the write might fail if already disconnected. + this.logger.warn(error.message || error); + } + + // This disposes timers and socket event handlers. + super.dispose(); + } + + /** + * Suspends protocol in preparation for socket passing to a child process + * @remark This will partially dispose the protocol! + */ + public suspend(): { initialDataChunk: VSBuffer, sendHandle: SendHandle } { + const sendHandle = this.getSendHandle(); + + if (!sendHandle) { + throw new Error('Send handle is not present in protocol. Was it disposed?'); + } + const initialDataChunk = this.readEntireBuffer(); + + // This disposes timers and socket event handlers. + super.dispose(); + sendHandle.pause(); + this.getSocket().drain(); + + return { + initialDataChunk, + sendHandle + }; + } + + /** + * Get inflateBytes from the current socket. + * Seed zlib with these bytes (web socket only). If parts of inflating was + * done in a different zlib instance we need to pass all those bytes into zlib + * otherwise the inflate might hit an inflated portion referencing a distance + * too far back. + */ + public get inflateBytes() { + const socket = this.getSocket(); + return socket.recordedInflateBytes; + } +} diff --git a/src/vs/server/server.ts b/src/vs/server/server.ts new file mode 100644 index 0000000000000..1e027b01ffb9e --- /dev/null +++ b/src/vs/server/server.ts @@ -0,0 +1,407 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Coder Technologies. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { promises as fs } from 'fs'; +import * as net from 'net'; +import { hostname, release } from 'os'; +import * as path from 'path'; +import { Emitter } from 'vs/base/common/event'; +import { Schemas } from 'vs/base/common/network'; +import { Complete } from 'vs/base/common/types'; +import { URI } from 'vs/base/common/uri'; +import { createServerURITransformer } from 'vs/base/common/uriServer'; +import { getMachineId } from 'vs/base/node/id'; +import { ClientConnectionEvent, IPCServer, IServerChannel, ProxyChannel } from 'vs/base/parts/ipc/common/ipc'; +import { LogsDataCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/logsDataCleaner'; +import { main } from 'vs/code/node/cliProcessMain'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { ConfigurationService } from 'vs/platform/configuration/common/configurationService'; +import { ExtensionHostDebugBroadcastChannel } from 'vs/platform/debug/common/extensionHostDebugIpc'; +// eslint-disable-next-line code-import-patterns +import { ArgumentParser } from 'vs/platform/environment/argumentParser'; +import { NativeParsedArgs } from 'vs/platform/environment/common/argv'; +import { IEnvironmentService, INativeEnvironmentService } from 'vs/platform/environment/common/environment'; +import { NativeEnvironmentService } from 'vs/platform/environment/node/environmentService'; +import { ExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionGalleryService'; +import { IExtensionGalleryService, IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { ExtensionManagementChannel } from 'vs/platform/extensionManagement/common/extensionManagementIpc'; +import { ExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService'; +import { IFileService } from 'vs/platform/files/common/files'; +import { FileService } from 'vs/platform/files/common/fileService'; +import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider'; +import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; +import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService'; +import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; +import { ILocalizationsService } from 'vs/platform/localizations/common/localizations'; +import { LocalizationsService } from 'vs/platform/localizations/node/localizations'; +import { ConsoleMainLogger, getLogLevel, ILoggerService, ILogService, MultiplexLogService } from 'vs/platform/log/common/log'; +import { LogLevelChannel } from 'vs/platform/log/common/logIpc'; +import { LoggerService } from 'vs/platform/log/node/loggerService'; +import { SpdLogLogger } from 'vs/platform/log/node/spdlogLog'; +import productConfiguration from 'vs/platform/product/common/product'; +import { IProductService } from 'vs/platform/product/common/productService'; +import { ConnectionType, connectionTypeToString, IRemoteExtensionHostStartParams } from 'vs/platform/remote/common/remoteAgentConnection'; +import { RemoteAgentConnectionContext } from 'vs/platform/remote/common/remoteAgentEnvironment'; +import { IRequestService } from 'vs/platform/request/common/request'; +import { RequestChannel } from 'vs/platform/request/common/requestIpc'; +import { RequestService } from 'vs/platform/request/node/requestService'; +import { resolveCommonProperties } from 'vs/platform/telemetry/common/commonProperties'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { TelemetryLogAppender } from 'vs/platform/telemetry/common/telemetryLogAppender'; +import { TelemetryService } from 'vs/platform/telemetry/common/telemetryService'; +import { combinedAppender, NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; +import { AppInsightsAppender } from 'vs/platform/telemetry/node/appInsightsAppender'; +import ErrorTelemetry from 'vs/platform/telemetry/node/errorTelemetry'; +import { PtyHostService } from 'vs/platform/terminal/node/ptyHostService'; +import { toWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; +import { ExtensionEnvironmentChannel, FileProviderChannel, TerminalProviderChannel } from 'vs/server/channel'; +import { ConnectionOptions } from 'vs/server/connection/abstractConnection'; +import { TelemetryClient } from 'vs/server/insights'; +import { getLocaleFromConfig, getNlsConfiguration } from 'vs/server/nls'; +import { ServerProtocol } from 'vs/server/protocol'; +import { REMOTE_TERMINAL_CHANNEL_NAME } from 'vs/workbench/contrib/terminal/common/remoteTerminalChannel'; +import { REMOTE_FILE_SYSTEM_CHANNEL_NAME } from 'vs/workbench/services/remote/common/remoteAgentFileSystemChannel'; +import { RemoteExtensionLogFileName } from 'vs/workbench/services/remote/common/remoteAgentService'; +import { IServerWorkbenchConstructionOptions, IWorkspace } from 'vs/workbench/workbench.web.api'; +import { NodeSocket, WebSocketNodeSocket } from 'vs/base/parts/ipc/node/ipc.net'; +import { BufferLogService } from 'vs/platform/log/common/bufferLog'; +import { ExtensionHostConnection } from 'vs/server/connection/extensionHostConnection'; +import { ManagementConnection } from 'vs/server/connection/managementConnection'; + +const commit = productConfiguration.commit || 'development'; + +export type VscodeServerArgs = NativeParsedArgs & Complete>; +type Connection = ExtensionHostConnection | ManagementConnection; + +/** + * Handles client connections to a editor instance via IPC. + */ +export class CodeServerMain extends ArgumentParser { + public readonly _onDidClientConnect = new Emitter(); + public readonly onDidClientConnect = this._onDidClientConnect.event; + + private readonly ipc = new IPCServer(this.onDidClientConnect); + + private readonly maxExtraOfflineConnections = 0; + + private readonly services = new ServiceCollection(); + private servicesPromise?: Promise; + private authority: string = ''; + + private readonly connections = new Map>(); + + /** + * Initializes connection map for this type of connection. + */ + private getCachedConnectionMap(desiredConnectionType: T) { + let connectionMap = this.connections.get(desiredConnectionType); + + if (!connectionMap) { + connectionMap = new Map(); + this.connections.set(desiredConnectionType, connectionMap); + } + + return connectionMap; + } + public async cli(args: NativeParsedArgs): Promise { + return main(args); + } + + private createWorkbenchURIs(paths: string[]) { + return paths.map(path => toWorkspaceFolder(URI.from({ + scheme: Schemas.vscodeRemote, + authority: this.authority, + path, + }))); + } + + public async createWorkbenchConstructionOptions(serverUrl: URL): Promise { + const parsedArgs = this.resolveArgs(); + + if (!parsedArgs.server) { + throw new Error('Server argument not provided'); + } + + this.authority = parsedArgs.server; + + const transformer = createServerURITransformer(this.authority); + + if (!this.servicesPromise) { + this.servicesPromise = this.initializeServices(parsedArgs); + } + await this.servicesPromise; + + const environment = this.services.get(IEnvironmentService) as INativeEnvironmentService; + + /** + * A workspace to open in the workbench can either be: + * - a workspace file with 0-N folders (via `workspaceUri`) + * - a single folder (via `folderUri`) + * - empty (via `undefined`) + */ + const workbenchURIs = this.createWorkbenchURIs(parsedArgs._.slice(1)); + // const hasSingleEntry = workbenchURIs.length > 0; + // const isSingleEntry = workbenchURIs.length === 1; + + const workspace: IWorkspace = { + // workspaceUri: isSingleEntry ? undefined : fs.stat(path), + workspaceUri: undefined, + folderUri: workbenchURIs[0].uri, + }; + + const webEndpointUrl = new URL(serverUrl.toString()); + webEndpointUrl.pathname = '/static'; + + return { + ...workspace, + remoteAuthority: parsedArgs.remote || serverUrl.toJSON(), + logLevel: getLogLevel(environment), + workspaceProvider: { + workspace, + trusted: undefined, + payload: [ + ['userDataPath', environment.userDataPath], + ['enableProposedApi', JSON.stringify(parsedArgs['enable-proposed-api'] || [])] + ], + }, + remoteUserDataUri: transformer.transformOutgoing(URI.file(environment.userDataPath)), + productConfiguration: { + ...productConfiguration, + webEndpointUrl: webEndpointUrl.toJSON() + }, + nlsConfiguration: await getNlsConfiguration(environment.args.locale || await getLocaleFromConfig(environment.userDataPath), environment.userDataPath), + commit, + }; + } + + public async handleWebSocket(socket: net.Socket, connectionOptions: ConnectionOptions, permessageDeflate = false): Promise { + const logger = this.services.get(ILogService) as MultiplexLogService; + + const protocol = new ServerProtocol( + new WebSocketNodeSocket(new NodeSocket(socket), permessageDeflate, null, permessageDeflate), + logger, + connectionOptions + ); + + try { + await this.connect(protocol); + } catch (error) { + protocol.dispose(error.message); + } + return true; + } + + private async connect(protocol: ServerProtocol): Promise { + const logger = this.services.get(ILogService) as MultiplexLogService; + + const message = await protocol.handshake(); + + const clientVersion = message.commit; + const serverVersion = productConfiguration.commit; + if (serverVersion && clientVersion !== serverVersion) { + logger.warn(`Client version (${message.commit} does not match server version ${serverVersion})`); + } + + // `desiredConnectionType` is marked as optional, + // but it's a scenario we haven't yet seen. + if (!message.desiredConnectionType) { + throw new Error(`Expected desired connection type in protocol handshake: ${JSON.stringify(message)}`); + } + + const connections = this.getCachedConnectionMap(message.desiredConnectionType); + let connection = connections.get(protocol.reconnectionToken); + const logPrefix = connectLogPrefix(message.desiredConnectionType, protocol); + + if (protocol.reconnection && connection) { + logger.info(logPrefix, 'Client attempting to reconnect'); + return connection.reconnect(protocol); + } + + // This probably means the process restarted so the session was lost + // while the browser remained open. + if (protocol.reconnection) { + throw new Error(`Unable to reconnect; session no longer exists (${protocol.reconnectionToken})`); + } + + // This will probably never happen outside a chance collision. + if (connection) { + throw new Error('Unable to connect; token is already in use'); + } + + // Now that the initial exchange has completed we can create the actual + // connection on top of the protocol then send it to whatever uses it. + logger.info(logPrefix, 'Client requesting connection'); + + switch (message.desiredConnectionType) { + case ConnectionType.Management: + connection = new ManagementConnection(protocol, logger); + + // The management connection is used by firing onDidClientConnect + // which makes the IPC server become aware of the connection. + this._onDidClientConnect.fire({ + protocol, + onDidClientDisconnect: connection.onClose, + }); + break; + case ConnectionType.ExtensionHost: + // The extension host connection is used by spawning an extension host + // and then passing the socket into it. + + const startParams: IRemoteExtensionHostStartParams = { + language: 'en', + ...message.args, + }; + + connection = new ExtensionHostConnection( + protocol, + logger, + startParams, + // Partial fix for imprecise typing. + this.services.get(INativeEnvironmentService) as INativeEnvironmentService); + + await connection.spawn(); + break; + case ConnectionType.Tunnel: + return protocol.tunnel(); + default: + throw new Error(`Unknown desired connection type ${message.desiredConnectionType}`); + } + + connections.set(protocol.reconnectionToken, connection); + connection.onClose(() => connections.delete(protocol.reconnectionToken)); + + this.disposeOldOfflineConnections(connections); + logger.debug(`${connections.size} active ${connection.name} connection(s)`); + + } + + private disposeOldOfflineConnections(connections: Map): void { + const offline = Array.from(connections.values()) + .filter((connection) => typeof connection.offline !== 'undefined'); + for (let i = 0, max = offline.length - this.maxExtraOfflineConnections; i < max; ++i) { + offline[i].dispose('old'); + } + } + + // References: + // ../../electron-browser/sharedProcess/sharedProcessMain.ts#L148 + // ../../../code/electron-main/app.ts + private async initializeServices(args: NativeParsedArgs): Promise { + const productService: IProductService = { _serviceBrand: undefined, ...productConfiguration }; + const environmentService = new NativeEnvironmentService(args, productService); + + await Promise.all([ + environmentService.extensionsPath, + environmentService.logsPath, + environmentService.globalStorageHome.fsPath, + environmentService.workspaceStorageHome.fsPath, + ...environmentService.extraExtensionPaths, + ...environmentService.extraBuiltinExtensionPaths, + ].map((p) => fs.mkdir(p, { recursive: true }).catch((error) => { + console.warn(error.message || error); + }))); + + + // Loggers + // src/vs/code/electron-main/main.ts#142 + const bufferLogService = new BufferLogService(); + const logService = new MultiplexLogService([ + new ConsoleMainLogger(getLogLevel(environmentService)), + bufferLogService, + ]); + // registerErrorHandler(logService); + + const fileService = new FileService(logService); + fileService.registerProvider(Schemas.file, new DiskFileSystemProvider(logService)); + + const loggerService = new LoggerService(logService, fileService); + + const piiPaths = [ + path.join(environmentService.userDataPath, 'clp'), // Language packs. + environmentService.appRoot, + environmentService.extensionsPath, + environmentService.builtinExtensionsPath, + ...environmentService.extraExtensionPaths, + ...environmentService.extraBuiltinExtensionPaths, + ]; + + this.ipc.registerChannel('logger', new LogLevelChannel(logService)); + this.ipc.registerChannel(ExtensionHostDebugBroadcastChannel.ChannelName, new ExtensionHostDebugBroadcastChannel()); + + this.services.set(ILogService, logService); + this.services.set(IEnvironmentService, environmentService); + this.services.set(INativeEnvironmentService, environmentService); + this.services.set(ILoggerService, loggerService); + + const configurationService = new ConfigurationService(environmentService.settingsResource, fileService); + await configurationService.initialize(); + this.services.set(IConfigurationService, configurationService); + + this.services.set(IRequestService, new SyncDescriptor(RequestService)); + this.services.set(IFileService, fileService); + this.services.set(IProductService, productService); + + await configurationService.initialize(); + this.services.set(IConfigurationService, configurationService); + + const machineId = await getMachineId(); + + await new Promise((resolve) => { + const instantiationService = new InstantiationService(this.services); + + instantiationService.invokeFunction((accessor) => { + // Delay creation of spdlog for perf reasons (https://github.com/microsoft/vscode/issues/72906) + bufferLogService.logger = new SpdLogLogger('main', path.join(environmentService.logsPath, `${RemoteExtensionLogFileName}.log`), true, bufferLogService.getLevel()); + + instantiationService.createInstance(LogsDataCleaner); + + let telemetryService: ITelemetryService; + + if (!environmentService.isExtensionDevelopment && !environmentService.disableTelemetry && !!productService.enableTelemetry) { + telemetryService = new TelemetryService({ + appender: combinedAppender( + new AppInsightsAppender('code-server', null, () => new TelemetryClient() as any), + new TelemetryLogAppender(accessor.get(ILoggerService), environmentService) + ), + sendErrorTelemetry: true, + commonProperties: resolveCommonProperties( + fileService, release(), hostname(), process.arch, commit, productConfiguration.version, machineId, + undefined, environmentService.installSourcePath, 'code-server', + ), + piiPaths, + }, configurationService); + } else { + telemetryService = NullTelemetryService; + } + + this.services.set(ITelemetryService, telemetryService); + + this.services.set(IExtensionManagementService, new SyncDescriptor(ExtensionManagementService)); + this.services.set(IExtensionGalleryService, new SyncDescriptor(ExtensionGalleryService)); + this.services.set(ILocalizationsService, new SyncDescriptor(LocalizationsService)); + + this.ipc.registerChannel('extensions', new ExtensionManagementChannel( + accessor.get(IExtensionManagementService), + (context) => createServerURITransformer(context.remoteAuthority), + )); + this.ipc.registerChannel('remoteextensionsenvironment', new ExtensionEnvironmentChannel( + environmentService, logService, telemetryService, '', + )); + this.ipc.registerChannel('request', new RequestChannel(accessor.get(IRequestService))); + this.ipc.registerChannel('localizations', >ProxyChannel.fromService(accessor.get(ILocalizationsService))); + this.ipc.registerChannel(REMOTE_FILE_SYSTEM_CHANNEL_NAME, new FileProviderChannel(environmentService, logService)); + + const ptyHostService = new PtyHostService({ GraceTime: 60000, ShortGraceTime: 6000 }, configurationService, logService, telemetryService); + this.ipc.registerChannel(REMOTE_TERMINAL_CHANNEL_NAME, new TerminalProviderChannel(logService, ptyHostService)); + + resolve(new ErrorTelemetry(telemetryService)); + }); + }); + } +} + +function connectLogPrefix(connectionType: ConnectionType, protocol: ServerProtocol) { + return `[${connectionTypeToString(connectionType)}] ${protocol.logPrefix}`; +} diff --git a/src/vs/workbench/api/browser/mainThreadStorage.ts b/src/vs/workbench/api/browser/mainThreadStorage.ts index 5cb658e91d1c6..8fd15e86f9f8f 100644 --- a/src/vs/workbench/api/browser/mainThreadStorage.ts +++ b/src/vs/workbench/api/browser/mainThreadStorage.ts @@ -62,12 +62,13 @@ export class MainThreadStorage implements MainThreadStorageShape { return JSON.parse(jsonValue); } - $setValue(shared: boolean, key: string, value: object): Promise { + async $setValue(shared: boolean, key: string, value: object): Promise { let jsonValue: string; try { jsonValue = JSON.stringify(value); // Extension state is synced separately through extensions - this._storageService.store(key, jsonValue, shared ? StorageScope.GLOBAL : StorageScope.WORKSPACE, StorageTarget.MACHINE); + // NOTE@coder: Wait for the actual storage write. + await this._storageService.store(key, jsonValue, shared ? StorageScope.GLOBAL : StorageScope.WORKSPACE, StorageTarget.MACHINE); } catch (err) { return Promise.reject(err); } diff --git a/src/vs/workbench/api/node/extHostCLIServer.ts b/src/vs/workbench/api/node/extHostCLIServer.ts index 6177f03c80fd1..ee509b68f1899 100644 --- a/src/vs/workbench/api/node/extHostCLIServer.ts +++ b/src/vs/workbench/api/node/extHostCLIServer.ts @@ -11,6 +11,8 @@ import { IWindowOpenable, IOpenWindowOptions } from 'vs/platform/windows/common/ import { URI } from 'vs/base/common/uri'; import { hasWorkspaceFileExtension } from 'vs/platform/workspaces/common/workspaces'; import { ILogService } from 'vs/platform/log/common/log'; +import { tmpdir } from 'os'; +import { join } from 'vs/base/common/path'; export interface OpenCommandPipeArgs { type: 'open'; @@ -67,6 +69,11 @@ export class CLIServerBase { } private async setup(): Promise { + // NOTE@coder: Write this out so we can get the most recent path. + fs.promises.writeFile(join(tmpdir(), 'vscode-ipc'), this._ipcHandlePath).catch((error) => { + this.logService.error(error); + }); + try { this._server.listen(this.ipcHandlePath); this._server.on('error', err => this.logService.error(err)); diff --git a/src/vs/workbench/browser/client.ts b/src/vs/workbench/browser/client.ts new file mode 100644 index 0000000000000..b74bbc4115197 --- /dev/null +++ b/src/vs/workbench/browser/client.ts @@ -0,0 +1,172 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Coder Technologies. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as path from 'vs/base/common/path'; +import { CodeServerConfiguration } from 'vs/base/common/ipc'; +import { localize } from 'vs/nls'; +import { MenuId, MenuRegistry } from 'vs/platform/actions/common/actions'; +import { CommandsRegistry } from 'vs/platform/commands/common/commands'; +import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; +import { ILogService } from 'vs/platform/log/common/log'; +import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; +import { getOptions } from 'vs/base/common/util'; +import 'vs/workbench/contrib/localizations/browser/localizations.contribution'; // eslint-disable-line code-import-patterns +import 'vs/workbench/services/localizations/browser/localizationsService'; + +/** + * All client-side customization to VS Code should live in this file when + * possible. + */ + +const options = getOptions(); + +/** + * This is called by vs/workbench/browser/web.main.ts after the workbench has + * been initialized so we can initialize our own client-side code. + */ +export const initialize = async (services: ServiceCollection): Promise => { + const event = new CustomEvent('ide-ready'); + window.dispatchEvent(event); + + if (parent) { + // Tell the parent loading has completed. + parent.postMessage({ event: 'loaded' }, '*'); + + // Proxy or stop proxing events as requested by the parent. + const listeners = new Map void>(); + window.addEventListener('message', (parentEvent) => { + const eventName = parentEvent.data.bind || parentEvent.data.unbind; + if (eventName) { + const oldListener = listeners.get(eventName); + if (oldListener) { + document.removeEventListener(eventName, oldListener); + } + } + + if (parentEvent.data.bind && parentEvent.data.prop) { + const listener = (event: Event) => { + parent?.postMessage({ + event: parentEvent.data.event, + [parentEvent.data.prop]: event[parentEvent.data.prop as keyof Event] + }, window.location.origin); + }; + listeners.set(parentEvent.data.bind, listener); + document.addEventListener(parentEvent.data.bind, listener); + } + }); + } + + if (!window.isSecureContext) { + (services.get(INotificationService) as INotificationService).notify({ + severity: Severity.Warning, + message: 'code-server is being accessed over an insecure domain. Web views, the clipboard, and other functionality will not work as expected.', + actions: { + primary: [{ + id: 'understand', + label: 'I understand', + tooltip: '', + class: undefined, + enabled: true, + checked: true, + dispose: () => undefined, + run: () => { + return Promise.resolve(); + } + }], + } + }); + } + + const logService = (services.get(ILogService) as ILogService); + const storageService = (services.get(IStorageService) as IStorageService); + const updateCheckEndpoint = path.join(options.base, '/update/check'); + const getUpdate = async (): Promise => { + logService.debug('Checking for update...'); + + const response = await fetch(updateCheckEndpoint, { + headers: { 'Accept': 'application/json' }, + }); + if (!response.ok) { + throw new Error(response.statusText); + } + const json = await response.json(); + if (json.error) { + throw new Error(json.error); + } + if (json.isLatest) { + return; + } + + const lastNoti = storageService.getNumber('csLastUpdateNotification', StorageScope.GLOBAL); + if (lastNoti) { + // Only remind them again after 1 week. + const timeout = 1000 * 60 * 60 * 24 * 7; + const threshold = lastNoti + timeout; + if (Date.now() < threshold) { + return; + } + } + + storageService.store('csLastUpdateNotification', Date.now(), StorageScope.GLOBAL, StorageTarget.MACHINE); + (services.get(INotificationService) as INotificationService).notify({ + severity: Severity.Info, + message: `[code-server v${json.latest}](https://github.com/cdr/code-server/releases/tag/v${json.latest}) has been released!`, + }); + }; + + const updateLoop = (): void => { + getUpdate().catch((error) => { + logService.debug(`failed to check for update: ${error}`); + }).finally(() => { + // Check again every 6 hours. + setTimeout(updateLoop, 1000 * 60 * 60 * 6); + }); + }; + + // TODO@coder enable + // if (!options.disableUpdateCheck) { + // updateLoop(); + // } + + // This will be used to set the background color while VS Code loads. + const theme = storageService.get('colorThemeData', StorageScope.GLOBAL); + if (theme) { + localStorage.setItem('colorThemeData', theme); + } + + // Use to show or hide logout commands and menu options. + const contextKeyService = (services.get(IContextKeyService) as IContextKeyService); + contextKeyService.createKey('code-server.authed', options.authed); + + // Add a logout command. + const logoutEndpoint = path.join(options.base, '/logout') + `?base=${options.base}`; + const LOGOUT_COMMAND_ID = 'code-server.logout'; + CommandsRegistry.registerCommand( + LOGOUT_COMMAND_ID, + () => { + window.location.href = logoutEndpoint; + }, + ); + + // Add logout to command palette. + MenuRegistry.appendMenuItem(MenuId.CommandPalette, { + command: { + id: LOGOUT_COMMAND_ID, + title: localize('logout', "Log out") + }, + when: ContextKeyExpr.has('code-server.authed') + }); + + // Add logout to the (web-only) home menu. + MenuRegistry.appendMenuItem(MenuId.MenubarHomeMenu, { + command: { + id: LOGOUT_COMMAND_ID, + title: localize('logout', "Log out") + }, + when: ContextKeyExpr.has('code-server.authed') + }); +}; diff --git a/src/vs/workbench/browser/parts/dialogs/dialogHandler.ts b/src/vs/workbench/browser/parts/dialogs/dialogHandler.ts index 530ce5e5c287a..8e71a71ae1494 100644 --- a/src/vs/workbench/browser/parts/dialogs/dialogHandler.ts +++ b/src/vs/workbench/browser/parts/dialogs/dialogHandler.ts @@ -144,11 +144,12 @@ export class BrowserDialogHandler implements IDialogHandler { async about(): Promise { const detailString = (useAgo: boolean): string => { return localize('aboutDetail', - "Version: {0}\nCommit: {1}\nDate: {2}\nBrowser: {3}", + "code-server: v{4}\n VS Code: v{0}\nCommit: {1}\nDate: {2}\nBrowser: {3}", this.productService.version || 'Unknown', this.productService.commit || 'Unknown', this.productService.date ? `${this.productService.date}${useAgo ? ' (' + fromNow(new Date(this.productService.date), true) + ')' : ''}` : 'Unknown', - navigator.userAgent + navigator.userAgent, + this.productService.codeServerVersion || 'Unknown', ); }; diff --git a/src/vs/workbench/browser/web.main.ts b/src/vs/workbench/browser/web.main.ts index d985a9e1b06a5..7daac7d8c41c6 100644 --- a/src/vs/workbench/browser/web.main.ts +++ b/src/vs/workbench/browser/web.main.ts @@ -64,6 +64,7 @@ import { WorkspaceTrustManagementService } from 'vs/workbench/services/workspace import { IWorkspaceTrustManagementService } from 'vs/platform/workspace/common/workspaceTrust'; import { HTMLFileSystemProvider } from 'vs/platform/files/browser/htmlFileSystemProvider'; import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { initialize } from './client'; class BrowserMain extends Disposable { @@ -96,6 +97,9 @@ class BrowserMain extends Disposable { // Startup const instantiationService = workbench.startup(); + // NOTE@coder: initialize our additions + await initialize(services.serviceCollection); + // Window this._register(instantiationService.createInstance(BrowserWindow)); @@ -364,6 +368,9 @@ class BrowserMain extends Disposable { } } + /** + * @coder There seems to be only partial support for workspaces on the web. + */ private resolveWorkspaceInitializationPayload(): IWorkspaceInitializationPayload { let workspace: IWorkspace | undefined = undefined; if (this.configuration.workspaceProvider) { diff --git a/src/vs/workbench/common/resources.ts b/src/vs/workbench/common/resources.ts index f99661f7ea790..79eb43d845f5c 100644 --- a/src/vs/workbench/common/resources.ts +++ b/src/vs/workbench/common/resources.ts @@ -16,6 +16,7 @@ import { ParsedExpression, IExpression, parse } from 'vs/base/common/glob'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; import { withNullAsUndefined } from 'vs/base/common/types'; +import { Schemas } from 'vs/base/common/network'; export class ResourceContextKey extends Disposable implements IContextKey { @@ -75,7 +76,10 @@ export class ResourceContextKey extends Disposable implements IContextKey { if (!ResourceContextKey._uriEquals(this._resourceKey.get(), value)) { this._contextKeyService.bufferChangeEvents(() => { this._resourceKey.set(value); - this._schemeKey.set(value ? value.scheme : null); + // NOTE@coder: this is to get Git context actions to show up + // See issue #1140 / commit 7e4a73ce2d19eee08ceea25113debefeb8ac27e2 + // TODO@oxy: Codespaces has this working alright without this patch - investigate why we need this and remove it. + this._schemeKey.set(value ? (value.scheme === Schemas.vscodeRemote ? Schemas.file : value.scheme) : null); this._filenameKey.set(value ? basename(value) : null); this._dirnameKey.set(value ? dirname(value).fsPath : null); this._pathKey.set(value ? value.fsPath : null); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts index 088b128bd827e..21337a635d911 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts @@ -27,7 +27,7 @@ import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/ import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import Severity from 'vs/base/common/severity'; import { IActivityService, NumberBadge } from 'vs/workbench/services/activity/common/activity'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IViewsRegistry, IViewDescriptor, Extensions, ViewContainer, IViewDescriptorService, IAddedViewDescriptorRef } from 'vs/workbench/common/views'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; @@ -59,7 +59,7 @@ import { ICommandService } from 'vs/platform/commands/common/commands'; import { isWeb } from 'vs/base/common/platform'; import { installLocalInRemoteIcon } from 'vs/workbench/contrib/extensions/browser/extensionsIcons'; import { registerAction2, Action2, MenuId } from 'vs/platform/actions/common/actions'; - +import { textLinkForeground } from 'vs/platform/theme/common/colorRegistry'; const SearchMarketplaceExtensionsContext = new RawContextKey('searchMarketplaceExtensions', false); const SearchIntalledExtensionsContext = new RawContextKey('searchInstalledExtensions', false); const SearchOutdatedExtensionsContext = new RawContextKey('searchOutdatedExtensions', false); @@ -524,6 +524,46 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE overlay.style.backgroundColor = overlayBackgroundColor; hide(overlay); + // NOTE@coder this UI element helps users understand the extension marketplace divergence + const extensionHelperLocalStorageKey = 'coder.extension-help-message'; + + if (localStorage.getItem(extensionHelperLocalStorageKey) === null) { + const helperHeader = append(this.root, $('.header')); + helperHeader.id = 'codeServerMarketplaceHelper'; + helperHeader.style.height = 'auto'; + helperHeader.style.fontWeight = '600'; + helperHeader.style.padding = 'padding: 5px 16px'; + helperHeader.style.position = 'relative'; + + const helperText = append(helperHeader, $('div')); + + + // We call this function because it gives us access to the current theme + // Then we can apply the link color to the links in the helper header + registerThemingParticipant((theme) => { + const linkColor = theme.getColor(textLinkForeground); + + append(helperText, $('div', { style: 'margin-bottom: 8px;' }, + $('p', { style: 'margin-bottom: 0; display: flex; align-items: center' }, + $('span', { class: 'codicon codicon-warning', style: 'margin-right: 2px; color: #C4A103' }), + 'WARNING'), + $('p', { style: 'margin-top: 0; margin-bottom: 4px' }, + 'These extensions are not official. Find additional open-source extensions ', + $('a', { style: `color: ${linkColor}`, href: 'https://open-vsx.org/', target: '_blank' }, 'here'), + '. See ', + $('a', { style: `color: ${linkColor}`, href: 'https://github.com/cdr/code-server/blob/master/docs/FAQ.md#differences-compared-to-vs-code', target: '_blank' }, 'docs'), + '.' + ))); + }); + + const dismiss = append(helperHeader, $('span', { style: `display: 'block'; textAlign: 'right'; cursor: 'pointer';`, tabindex: '0' }, 'Dismiss')); + + dismiss.onclick = () => { + helperHeader.remove(); + localStorage.setItem(extensionHelperLocalStorageKey, 'viewed'); + }; + } + const header = append(this.root, $('.header')); const placeholder = localize('searchExtensions', "Search Extensions in Marketplace"); diff --git a/src/vs/workbench/contrib/files/browser/explorerViewlet.ts b/src/vs/workbench/contrib/files/browser/explorerViewlet.ts index ad72a5cc2c43c..5b7af077020a9 100644 --- a/src/vs/workbench/contrib/files/browser/explorerViewlet.ts +++ b/src/vs/workbench/contrib/files/browser/explorerViewlet.ts @@ -298,9 +298,17 @@ viewsRegistry.registerViewWelcomeContent(EmptyView.ID, { order: 1 }); +/** + * @coder + * We use `OpenFolderAction.ID` instead of `commandId` + * because for some reason, the command `openFileFolder` + * does not work as expected and causes the "Open Folder" + * command to not work + * See: https://github.com/cdr/code-server/issues/3457 + */ viewsRegistry.registerViewWelcomeContent(EmptyView.ID, { content: localize({ key: 'noFolderHelp', comment: ['Please do not translate the word "commmand", it is part of our internal syntax which must not change'] }, - "You have not yet opened a folder.\n[Open Folder](command:{0})", commandId), + "You have not yet opened a folder.\n[Open Folder](command:{0})", OpenFolderAction.ID), when: ContextKeyExpr.or(ContextKeyExpr.and(WorkbenchStateContext.notEqualsTo('workspace'), RemoteNameContext.isEqualTo('')), ContextKeyExpr.and(WorkbenchStateContext.notEqualsTo('workspace'), IsWebContext)), group: ViewContentGroups.Open, order: 1 diff --git a/src/vs/workbench/contrib/welcome/page/browser/vs_code_welcome_page.ts b/src/vs/workbench/contrib/welcome/page/browser/vs_code_welcome_page.ts index a405271ac1c1c..c32048e7df18c 100644 --- a/src/vs/workbench/contrib/welcome/page/browser/vs_code_welcome_page.ts +++ b/src/vs/workbench/contrib/welcome/page/browser/vs_code_welcome_page.ts @@ -5,13 +5,14 @@ import { escape } from 'vs/base/common/strings'; import { localize } from 'vs/nls'; +import product from 'vs/platform/product/common/product'; export default () => `

${escape(localize('welcomePage.vscode', "Visual Studio Code"))}

-

${escape(localize({ key: 'welcomePage.editingEvolved', comment: ['Shown as subtitle on the Welcome page.'] }, "Editing evolved"))}

+

VS Code v${product.version}

+
+

code-server ${escape(localize('welcomePage.help', "Help"))}

+ +

${escape(localize('welcomePage.help', "Help"))}

    diff --git a/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts b/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts index 2df033a4409b3..5b16a93ec88b8 100644 --- a/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts +++ b/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts @@ -357,7 +357,7 @@ class WelcomePage extends Disposable { const prodName = container.querySelector('.welcomePage .title .caption') as HTMLElement; if (prodName) { - prodName.textContent = this.productService.nameLong; + prodName.textContent = `code-server v${this.productService.codeServerVersion}`; } recentlyOpened.then(({ workspaces }) => { diff --git a/src/vs/workbench/services/environment/browser/environmentService.ts b/src/vs/workbench/services/environment/browser/environmentService.ts index b4f4bec0a93b6..724033aaa9099 100644 --- a/src/vs/workbench/services/environment/browser/environmentService.ts +++ b/src/vs/workbench/services/environment/browser/environmentService.ts @@ -120,8 +120,37 @@ export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironment @memoize get logFile(): URI { return joinPath(this.options.logsPath, 'window.log'); } + /** + * Path helper + * + * @coder modified to prefer local user data. + */ @memoize - get userRoamingDataHome(): URI { return URI.file('/User').with({ scheme: Schemas.userData }); } + get userRoamingDataHome(): URI { + if (this.userDataPath) { + return joinPath(URI.file(this.userDataPath).with({ scheme: Schemas.vscodeRemote }), 'User'); + } + + return URI.file('/User').with({ scheme: Schemas.userData }); + } + + /** + * Local and persistant user data directory. + * @coder The browser environment service payload should include a `userDataPath` + * matching the implementation used in `NativeEnvironmentService` + * This solves two problems: + * 1. Extensions running in the browser (like Vim) might use these paths + * directly instead of using the file service and most likely can't write + * to `/User` on disk. + * 2. Settings will be stored in the file system instead of in browser + * storage. Using browser storage makes sharing or seeding settings + * between browsers difficult. We may want to revisit this once/if we get + * settings sync. + */ + @memoize + get userDataPath(): string | undefined { + return this.payload?.get('userDataPath'); + } @memoize get settingsResource(): URI { return joinPath(this.userRoamingDataHome, 'settings.json'); } @@ -318,7 +347,12 @@ export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironment extensionHostDebugEnvironment.params.port = parseInt(value); break; case 'enableProposedApi': - extensionHostDebugEnvironment.extensionEnabledProposedApi = []; + try { + extensionHostDebugEnvironment.extensionEnabledProposedApi = JSON.parse(value); + } catch (error) { + console.error(error); + extensionHostDebugEnvironment.extensionEnabledProposedApi = []; + } break; } } diff --git a/src/vs/workbench/services/extensionManagement/browser/extensionEnablementService.ts b/src/vs/workbench/services/extensionManagement/browser/extensionEnablementService.ts index 627c10c1d70ea..1cc9d5444f570 100644 --- a/src/vs/workbench/services/extensionManagement/browser/extensionEnablementService.ts +++ b/src/vs/workbench/services/extensionManagement/browser/extensionEnablementService.ts @@ -321,7 +321,7 @@ export class ExtensionEnablementService extends Disposable implements IWorkbench } } } - return true; + return false; // NOTE@coder: Don't disable anything by extensionKind. } return false; } diff --git a/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts b/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts index 97c82ff1aecca..a67de12609160 100644 --- a/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts +++ b/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts @@ -26,6 +26,7 @@ import { IUserDataAutoSyncEnablementService, IUserDataSyncResourceEnablementServ import { Promises } from 'vs/base/common/async'; import { IWorkspaceTrustRequestService } from 'vs/platform/workspace/common/workspaceTrust'; import { IExtensionManifestPropertiesService } from 'vs/workbench/services/extensions/common/extensionManifestPropertiesService'; +import { isWeb } from 'vs/base/common/platform'; export class ExtensionManagementService extends Disposable implements IWorkbenchExtensionManagementService { @@ -316,6 +317,11 @@ export class ExtensionManagementService extends Disposable implements IWorkbench } } + // NOTE@coder: Fall back to installing on the remote server on web. + if (isWeb && this.extensionManagementServerService.remoteExtensionManagementServer) { + return this.extensionManagementServerService.remoteExtensionManagementServer; + } + // Local server can accept any extension. So return local server if not compatible server found. return this.extensionManagementServerService.localExtensionManagementServer; } diff --git a/src/vs/workbench/services/extensions/common/extensionHostProtocol.ts b/src/vs/workbench/services/extensions/common/extensionHostProtocol.ts index 7d3d4c30dc55c..be6d7f5c996dc 100644 --- a/src/vs/workbench/services/extensions/common/extensionHostProtocol.ts +++ b/src/vs/workbench/services/extensions/common/extensionHostProtocol.ts @@ -27,6 +27,8 @@ export interface IExtHostReduceGraceTimeMessage { type: 'VSCODE_EXTHOST_IPC_REDUCE_GRACE_TIME'; } +export type IExtHostMessage = IExtHostReadyMessage | IExtHostSocketMessage | IExtHostReduceGraceTimeMessage; + export const enum MessageType { Initialized, Ready, diff --git a/src/vs/workbench/services/extensions/common/remoteExtensionHost.ts b/src/vs/workbench/services/extensions/common/remoteExtensionHost.ts index d238526f526c7..6737308ee0859 100644 --- a/src/vs/workbench/services/extensions/common/remoteExtensionHost.ts +++ b/src/vs/workbench/services/extensions/common/remoteExtensionHost.ts @@ -88,6 +88,7 @@ export class RemoteExtensionHost extends Disposable implements IExtensionHost { } public start(): Promise { + this._logService.info('>>> STARTING REMOT EXT HOST'); const options: IConnectionOptions = { commit: this._productService.commit, socketFactory: this._socketFactory, @@ -101,6 +102,8 @@ export class RemoteExtensionHost extends Disposable implements IExtensionHost { logService: this._logService, ipcLogger: null }; + this._logService.info('>>> RESOLVING AUTH'); + return this.remoteAuthorityResolverService.resolveAuthority(this._initDataProvider.remoteAuthority).then((resolverResult) => { const startParams: IRemoteExtensionHostStartParams = { @@ -124,11 +127,13 @@ export class RemoteExtensionHost extends Disposable implements IExtensionHost { if (!debugOk) { startParams.break = false; } - + this._logService.info('>>> CONNECTING REMOTE AGENT EXT HOST'); return connectRemoteAgentExtensionHost(options, startParams).then(result => { let { protocol, debugPort } = result; const isExtensionDevelopmentDebug = typeof debugPort === 'number'; if (debugOk && this._environmentService.isExtensionDevelopment && this._environmentService.debugExtensionHost.debugId && debugPort) { + this._logService.info('>>> ATTACHING DEBUG'); + this._extensionHostDebugService.attachSession(this._environmentService.debugExtensionHost.debugId, debugPort, this._initDataProvider.remoteAuthority); } @@ -145,6 +150,7 @@ export class RemoteExtensionHost extends Disposable implements IExtensionHost { // 1) wait for the incoming `ready` event and send the initialization data. // 2) wait for the incoming `initialized` event. return new Promise((resolve, reject) => { + this._logService.info('>>> WAITING FOR MESSAGE'); let handle = setTimeout(() => { reject('timeout'); @@ -153,6 +159,7 @@ export class RemoteExtensionHost extends Disposable implements IExtensionHost { let logFile: URI; const disposable = protocol.onMessage(msg => { + this._logService.info('>>> PROTOCOL MESSAGE RECEIVED', msg.toString()); if (isMessageOfType(msg, MessageType.Ready)) { // 1) Extension Host is ready to receive messages, initialize it diff --git a/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts b/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts index e6d56c8ff7b75..24793921376fc 100644 --- a/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts +++ b/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts @@ -17,12 +17,14 @@ import { IInitData } from 'vs/workbench/api/common/extHost.protocol'; import { MessageType, createMessageOfType, isMessageOfType, IExtHostSocketMessage, IExtHostReadyMessage, IExtHostReduceGraceTimeMessage, ExtensionHostExitCode } from 'vs/workbench/services/extensions/common/extensionHostProtocol'; import { ExtensionHostMain, IExitFn } from 'vs/workbench/services/extensions/common/extensionHostMain'; import { VSBuffer } from 'vs/base/common/buffer'; -import { IURITransformer, URITransformer, IRawURITransformer } from 'vs/base/common/uriIpc'; +import { IRawURITransformer, IURITransformer, URITransformer } from 'vs/base/common/uriIpc'; import { Promises } from 'vs/base/node/pfs'; +import { createServerURITransformer } from 'vs/base/common/uriServer'; import { realpath } from 'vs/base/node/extpath'; import { IHostUtils } from 'vs/workbench/api/common/extHostExtensionService'; import { RunOnceScheduler } from 'vs/base/common/async'; import { boolean } from 'vs/editor/common/config/editorOptions'; +import * as proxyAgent from 'vs/base/node/proxy_agent'; import 'vs/workbench/api/common/extHost.common.services'; import 'vs/workbench/api/node/extHost.node.services'; @@ -122,6 +124,7 @@ function _createExtHostProtocol(): Promise { if (msg && msg.type === 'VSCODE_EXTHOST_IPC_SOCKET') { const initialDataChunk = VSBuffer.wrap(Buffer.from(msg.initialDataChunk, 'base64')); let socket: NodeSocket | WebSocketNodeSocket; + if (msg.skipWebSocketFrames) { socket = new NodeSocket(handle); } else { @@ -313,12 +316,16 @@ function connectToRenderer(protocol: IMessagePassingProtocol): Promise>> WANT TO SEND READY'); // Tell the outside that we are ready to receive messages protocol.send(createMessageOfType(MessageType.Ready)); }); } export async function startExtensionHostProcess(): Promise { + // NOTE@coder: add proxy agent patch + proxyAgent.monkeyPatch(true); + performance.mark(`code/extHost/willConnectToRenderer`); const protocol = await createExtHostProtocol(); performance.mark(`code/extHost/didConnectToRenderer`); @@ -340,13 +347,18 @@ export async function startExtensionHostProcess(): Promise { // Attempt to load uri transformer let uriTransformer: IURITransformer | null = null; - if (initData.remote.authority && args.uriTransformerPath) { - try { - const rawURITransformerFactory = require.__$__nodeRequire(args.uriTransformerPath); - const rawURITransformer = rawURITransformerFactory(initData.remote.authority); - uriTransformer = new URITransformer(rawURITransformer); - } catch (e) { - console.error(e); + + if (initData.remote.authority) { + if (args.uriTransformerPath) { + try { + const rawURITransformerFactory = require.__$__nodeRequire(args.uriTransformerPath); + const rawURITransformer = rawURITransformerFactory(initData.remote.authority); + uriTransformer = new URITransformer(rawURITransformer); + } catch (e) { + console.error(e); + } + } else { + uriTransformer = createServerURITransformer(initData.remote.authority); } } diff --git a/src/vs/workbench/services/host/browser/browserHostService.ts b/src/vs/workbench/services/host/browser/browserHostService.ts index 21ab06e1c6af7..1f013d6856b96 100644 --- a/src/vs/workbench/services/host/browser/browserHostService.ts +++ b/src/vs/workbench/services/host/browser/browserHostService.ts @@ -71,6 +71,14 @@ export interface IWorkspaceProvider { open(workspace: IWorkspace, options?: { reuse?: boolean, payload?: object }): Promise; } +/** + * @coder Similar to the workspace provider, without `open` helper. + * This allows for JSON serialization when passing options to a client. + */ +export interface IServerWorkspaceProvider extends Omit { + payload: [['userDataPath', string], ['enableProposedApi', string]]; +} + enum HostShutdownReason { /** diff --git a/src/vs/workbench/services/keybinding/browser/keyboardLayoutService.ts b/src/vs/workbench/services/keybinding/browser/keyboardLayoutService.ts index 6d71a818bbeaf..0fcefe306e617 100644 --- a/src/vs/workbench/services/keybinding/browser/keyboardLayoutService.ts +++ b/src/vs/workbench/services/keybinding/browser/keyboardLayoutService.ts @@ -406,6 +406,9 @@ export class BrowserKeyboardMapperFactoryBase { // } // return null; + }).catch(() => { + // NOTE@coder: It looks like the intention was to catch this error but + // a try/catch won't do it when using promises without `await`. }); } catch { // getLayoutMap can throw if invoked from a nested browsing context diff --git a/src/vs/workbench/services/localizations/browser/localizationsService.ts b/src/vs/workbench/services/localizations/browser/localizationsService.ts new file mode 100644 index 0000000000000..9a26f6b1a3d9a --- /dev/null +++ b/src/vs/workbench/services/localizations/browser/localizationsService.ts @@ -0,0 +1,29 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Coder Technologies. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +/** + * @file This appears to fix localization as of v1.58.2 + * However, upstream diverges from this behavior: + * https://github.com/microsoft/vscode/commit/3ef4aa861a38a1aac95e3f560e073fe98929ddda + */ + +import { ProxyChannel } from 'vs/base/parts/ipc/common/ipc'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { ILocalizationsService } from 'vs/platform/localizations/common/localizations'; +import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; + +// @ts-ignore: interface is implemented via proxy +export class LocalizationsService implements ILocalizationsService { + + declare readonly _serviceBrand: undefined; + + constructor( + @IRemoteAgentService remoteAgentService: IRemoteAgentService, + ) { + return ProxyChannel.toService(remoteAgentService.getConnection()!.getChannel('localizations')); + } +} + +registerSingleton(ILocalizationsService, LocalizationsService, true); diff --git a/src/vs/workbench/workbench.web.api.ts b/src/vs/workbench/workbench.web.api.ts index ae1618a515430..aef98e174f1bd 100644 --- a/src/vs/workbench/workbench.web.api.ts +++ b/src/vs/workbench/workbench.web.api.ts @@ -13,12 +13,14 @@ import { LogLevel } from 'vs/platform/log/common/log'; import { IUpdateProvider, IUpdate } from 'vs/workbench/services/update/browser/updateService'; import { Event, Emitter } from 'vs/base/common/event'; import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; -import { IWorkspaceProvider, IWorkspace } from 'vs/workbench/services/host/browser/browserHostService'; +import { IWorkspaceProvider, IWorkspace, IServerWorkspaceProvider } from 'vs/workbench/services/host/browser/browserHostService'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { IProductConfiguration } from 'vs/base/common/product'; import { mark } from 'vs/base/common/performance'; import { ICredentialsProvider } from 'vs/workbench/services/credentials/common/credentials'; import { TunnelProviderFeatures } from 'vs/platform/remote/common/tunnel'; +// eslint-disable-next-line code-import-patterns +import { NLSConfiguration } from 'vs/base/node/languagePacks'; interface IResourceUriProvider { (uri: URI): URI; @@ -441,6 +443,28 @@ interface IWorkbenchConstructionOptions { } +/** + * @coder This is commonly used when initializing a code server. + */ +interface IOptionalPathURIs { + folderUri?: UriComponents + workspaceUri?: UriComponents +} + +/** + * @coder Standard workbench constructor options with additional server paths. + * JSON serializable. + */ +interface IServerWorkbenchConstructionOptions extends Omit, IOptionalPathURIs { + readonly workspaceProvider: IServerWorkspaceProvider + /** @TODO still used? */ + readonly logLevel?: number + + readonly remoteUserDataUri: UriComponents + readonly nlsConfiguration: NLSConfiguration + readonly commit: string +} + interface IDevelopmentOptions { /** @@ -609,6 +633,8 @@ export { // Factory create, IWorkbenchConstructionOptions, + IServerWorkbenchConstructionOptions, + IOptionalPathURIs, IWorkbench, // Basic Types diff --git a/yarn.lock b/yarn.lock index 62c3a19de68dd..7918274e4be6a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -538,6 +538,13 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.37.tgz#a3dd8da4eb84a996c36e331df98d82abd76b516e" integrity sha512-XYmBiy+ohOR4Lh5jE379fV2IU+6Jn4g5qASinhitfyO71b/sCo6MKsMLF5tc7Zf2CE8hViVQyYSobJNke8OvUw== +"@types/proxy-from-env@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@types/proxy-from-env/-/proxy-from-env-1.0.1.tgz#b5f3e99230ca4518af196c18267055fc51f892b7" + integrity sha512-luG++TFHyS61eKcfkR1CVV6a1GMNXDjtqEQIIfaSHax75xp0HU3SlezjOi1yqubJwrG8e9DeW59n6wTblIDwFg== + dependencies: + "@types/node" "*" + "@types/q@^1.5.1": version "1.5.4" resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.4.tgz#15925414e0ad2cd765bfef58842f7e26a7accb24" @@ -572,6 +579,13 @@ resolved "https://registry.yarnpkg.com/@types/tapable/-/tapable-1.0.4.tgz#b4ffc7dc97b498c969b360a41eee247f82616370" integrity sha512-78AdXtlhpCHT0K3EytMpn4JNxaf5tbqbLcbIRoQIHzpTIyjpxLQKRoxU55ujBXAtg3Nl2h/XWvfDa9dsMOd0pQ== +"@types/tar-stream@^2.2.1": + version "2.2.1" + resolved "https://registry.yarnpkg.com/@types/tar-stream/-/tar-stream-2.2.1.tgz#7cb4516fe6d1a926a37b7733905c50885718e7ad" + integrity sha512-zhcfACZ4HavArMutfAB1/ApfSx44kNF2zyytU4mbO1dGCT/y9kL2IZwRDRyYYtBUxW6LRparZpLoX8i67b6IZw== + dependencies: + "@types/node" "*" + "@types/trusted-types@^1.0.6": version "1.0.6" resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-1.0.6.tgz#569b8a08121d3203398290d602d84d73c8dcf5da" @@ -643,6 +657,13 @@ resolved "https://registry.yarnpkg.com/@types/winreg/-/winreg-1.2.30.tgz#91d6710e536d345b9c9b017c574cf6a8da64c518" integrity sha1-kdZxDlNtNFucmwF8V0z2qNpkxRg= +"@types/ws@^7.4.7": + version "7.4.7" + resolved "https://registry.yarnpkg.com/@types/ws/-/ws-7.4.7.tgz#f7c390a36f7a0679aa69de2d501319f4f8d9b702" + integrity sha512-JQbbmxZTZehdc2iszGKs5oC3NFnjeay7mtAWrdt7qNtAVK0g19muApzAy4bm9byz79xa2ZnO/BOBC2R8RC5Lww== + dependencies: + "@types/node" "*" + "@types/yauzl@^2.9.1": version "2.9.1" resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.9.1.tgz#d10f69f9f522eef3cf98e30afb684a1e1ec923af" @@ -930,7 +951,7 @@ agent-base@^4.3.0: dependencies: es6-promisify "^5.0.0" -agent-base@^6.0.2: +agent-base@^6.0.0, agent-base@^6.0.2: version "6.0.2" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== @@ -1319,6 +1340,13 @@ assign-symbols@^1.0.0: resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c= +ast-types@^0.13.2: + version "0.13.4" + resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.13.4.tgz#ee0d77b343263965ecc3fb62da16e7222b2b6782" + integrity sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w== + dependencies: + tslib "^2.0.1" + astral-regex@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9" @@ -1716,7 +1744,7 @@ builtin-status-codes@^3.0.0: resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8" integrity sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug= -bytes@^3.0.0: +bytes@3.1.0, bytes@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6" integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg== @@ -2826,6 +2854,15 @@ define-property@^2.0.2: is-descriptor "^1.0.2" isobject "^3.0.1" +degenerator@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/degenerator/-/degenerator-2.2.0.tgz#49e98c11fa0293c5b26edfbb52f15729afcdb254" + integrity sha512-aiQcQowF01RxFI4ZLFMpzyotbQonhNpBao6dkI8JPk5a+hmSjR5ErHp2CQySmQe8os3VBqLCIh87nDBgZXvsmg== + dependencies: + ast-types "^0.13.2" + escodegen "^1.8.1" + esprima "^4.0.0" + delayed-stream@0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-0.0.6.tgz#a2646cb7ec3d5d7774614670a7a65de0c173edbc" @@ -2841,6 +2878,11 @@ delegates@^1.0.0: resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o= +depd@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" + integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= + deprecation@^2.0.0, deprecation@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/deprecation/-/deprecation-2.3.1.tgz#6368cbdb40abf3373b525ac87e4a260c3a700919" @@ -3264,6 +3306,18 @@ escape-string-regexp@^2.0.0: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== +escodegen@^1.8.1: + version "1.14.3" + resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.14.3.tgz#4e7b81fba61581dc97582ed78cab7f0e8d63f503" + integrity sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw== + dependencies: + esprima "^4.0.1" + estraverse "^4.2.0" + esutils "^2.0.2" + optionator "^0.8.1" + optionalDependencies: + source-map "~0.6.1" + eslint-plugin-jsdoc@^19.1.0: version "19.1.0" resolved "https://registry.yarnpkg.com/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-19.1.0.tgz#fcc17f0378fdd6ee1c847a79b7211745cb05d014" @@ -3416,7 +3470,7 @@ espree@^6.1.2: acorn-jsx "^5.2.0" eslint-visitor-keys "^1.1.0" -esprima@^4.0.0: +esprima@^4.0.0, esprima@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== @@ -3435,7 +3489,7 @@ esrecurse@^4.1.0, esrecurse@^4.3.0: dependencies: estraverse "^5.2.0" -estraverse@^4.1.1: +estraverse@^4.1.1, estraverse@^4.2.0: version "4.3.0" resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== @@ -4144,7 +4198,7 @@ get-stream@^5.1.0: dependencies: pump "^3.0.0" -get-uri@^3.0.2: +get-uri@3, get-uri@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/get-uri/-/get-uri-3.0.2.tgz#f0ef1356faabc70e1f9404fa3b66b2ba9bfc725c" integrity sha512-+5s0SJbGoyiJTZZ2JTpFPLMPSch72KEqGOTvQsBqg0RBWvwhWUSYZFAtz3TPW0GXJuLBJPts1E241iHg+VRfhg== @@ -4859,6 +4913,17 @@ http-cache-semantics@^4.0.0: resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390" integrity sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ== +http-errors@1.7.3: + version "1.7.3" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06" + integrity sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw== + dependencies: + depd "~1.1.2" + inherits "2.0.4" + setprototypeof "1.1.1" + statuses ">= 1.5.0 < 2" + toidentifier "1.0.0" + http-proxy-agent@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz#e4821beef5b2142a2026bd73926fe537631c5405" @@ -4867,7 +4932,7 @@ http-proxy-agent@^2.1.0: agent-base "4" debug "3.1.0" -http-proxy-agent@^4.0.1: +http-proxy-agent@^4.0.0, http-proxy-agent@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz#8a8c8ef7f5932ccf953c296ca8291b95aa74aa3a" integrity sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg== @@ -4890,6 +4955,14 @@ https-browserify@^1.0.0: resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" integrity sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM= +https-proxy-agent@5, https-proxy-agent@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz#e2a90542abb68a762e0a0850f6c9edadfd8506b2" + integrity sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA== + dependencies: + agent-base "6" + debug "4" + https-proxy-agent@^2.2.3: version "2.2.4" resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz#4ee7a737abd92678a293d9b34a1af4d0d08c787b" @@ -4906,14 +4979,6 @@ https-proxy-agent@^4.0.0: agent-base "5" debug "4" -https-proxy-agent@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz#e2a90542abb68a762e0a0850f6c9edadfd8506b2" - integrity sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA== - dependencies: - agent-base "6" - debug "4" - husky@^0.13.1: version "0.13.4" resolved "https://registry.yarnpkg.com/husky/-/husky-0.13.4.tgz#48785c5028de3452a51c48c12c4f94b2124a1407" @@ -4929,18 +4994,18 @@ iconv-lite-umd@0.6.8: resolved "https://registry.yarnpkg.com/iconv-lite-umd/-/iconv-lite-umd-0.6.8.tgz#5ad310ec126b260621471a2d586f7f37b9958ec0" integrity sha512-zvXJ5gSwMC9JD3wDzH8CoZGc1pbiJn12Tqjk8BXYCnYz3hYL5GRjHW8LEykjXhV9WgNGI4rgpgHcbIiBfrRq6A== -iconv-lite@^0.4.19: - version "0.4.19" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b" - integrity sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ== - -iconv-lite@^0.4.24: +iconv-lite@0.4.24, iconv-lite@^0.4.24: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== dependencies: safer-buffer ">= 2.1.2 < 3" +iconv-lite@^0.4.19: + version "0.4.19" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b" + integrity sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ== + icss-utils@^4.0.0, icss-utils@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-4.1.1.tgz#21170b53789ee27447c2f47dd683081403f9a467" @@ -5034,7 +5099,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3: +inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -6552,6 +6617,11 @@ neo-async@^2.5.0, neo-async@^2.6.1: resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== +netmask@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/netmask/-/netmask-2.0.2.tgz#8b01a07644065d536383835823bc52004ebac5e7" + integrity sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg== + next-tick@1, next-tick@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.1.0.tgz#1836ee30ad56d67ef281b22bd199f709449b35eb" @@ -6916,7 +6986,7 @@ optimist@0.3.5: dependencies: wordwrap "~0.0.2" -optionator@^0.8.2, optionator@^0.8.3: +optionator@^0.8.1, optionator@^0.8.2, optionator@^0.8.3: version "0.8.3" resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA== @@ -7061,6 +7131,30 @@ p-try@^2.0.0: resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== +pac-proxy-agent@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/pac-proxy-agent/-/pac-proxy-agent-4.1.0.tgz#66883eeabadc915fc5e95457324cb0f0ac78defb" + integrity sha512-ejNgYm2HTXSIYX9eFlkvqFp8hyJ374uDf0Zq5YUAifiSh1D6fo+iBivQZirGvVv8dCYUsLhmLBRhlAYvBKI5+Q== + dependencies: + "@tootallnate/once" "1" + agent-base "6" + debug "4" + get-uri "3" + http-proxy-agent "^4.0.1" + https-proxy-agent "5" + pac-resolver "^4.1.0" + raw-body "^2.2.0" + socks-proxy-agent "5" + +pac-resolver@^4.1.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/pac-resolver/-/pac-resolver-4.2.0.tgz#b82bcb9992d48166920bc83c7542abb454bd9bdd" + integrity sha512-rPACZdUyuxT5Io/gFKUeeZFfE5T7ve7cAkE5TUZRRfuKP0u5Hocwe48X7ZEm6mYB+bTB0Qf+xlVlA/RM/i6RCQ== + dependencies: + degenerator "^2.2.0" + ip "^1.1.5" + netmask "^2.0.1" + pako@~1.0.5: version "1.0.11" resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf" @@ -7800,7 +7894,21 @@ proto-list@~1.2.1: resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849" integrity sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk= -proxy-from-env@^1.1.0: +proxy-agent@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/proxy-agent/-/proxy-agent-4.0.1.tgz#326c3250776c7044cd19655ccbfadf2e065a045c" + integrity sha512-ODnQnW2jc/FUVwHHuaZEfN5otg/fMbvMxz9nMSUQfJ9JU7q2SZvSULSsjLloVgJOiv9yhc8GlNMKc4GkFmcVEA== + dependencies: + agent-base "^6.0.0" + debug "4" + http-proxy-agent "^4.0.0" + https-proxy-agent "^5.0.0" + lru-cache "^5.1.1" + pac-proxy-agent "^4.1.0" + proxy-from-env "^1.0.0" + socks-proxy-agent "^5.0.0" + +proxy-from-env@^1.0.0, proxy-from-env@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== @@ -7945,6 +8053,16 @@ randomfill@^1.0.3: randombytes "^2.0.5" safe-buffer "^5.1.0" +raw-body@^2.2.0: + version "2.4.1" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.1.tgz#30ac82f98bb5ae8c152e67149dac8d55153b168c" + integrity sha512-9WmIKF6mkvA0SLmA2Knm9+qj89e+j1zqgyn8aXGd7+nAduPoqgI9lO57SAZNn/Byzo5P7JhXTyg9PzaJbH73bA== + dependencies: + bytes "3.1.0" + http-errors "1.7.3" + iconv-lite "0.4.24" + unpipe "1.0.0" + rc@^1.2.7: version "1.2.8" resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" @@ -8551,6 +8669,11 @@ setimmediate@^1.0.4: resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" integrity sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU= +setprototypeof@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683" + integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw== + sha.js@^2.4.0, sha.js@^2.4.8: version "2.4.11" resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7" @@ -8678,6 +8801,15 @@ snapdragon@^0.8.1: source-map-resolve "^0.5.0" use "^3.1.0" +socks-proxy-agent@5: + version "5.0.1" + resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-5.0.1.tgz#032fb583048a29ebffec2e6a73fca0761f48177e" + integrity sha512-vZdmnjb9a2Tz6WEQVIurybSwElwPxMZaIc7PzqbJTrezcKNznv6giT7J7tZDZ1BojVaa1jvO/UiUdhDVB0ACoQ== + dependencies: + agent-base "^6.0.2" + debug "4" + socks "^2.3.3" + socks-proxy-agent@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-5.0.0.tgz#7c0f364e7b1cf4a7a437e71253bed72e9004be60" @@ -8891,6 +9023,11 @@ static-extend@^0.1.1: define-property "^0.2.5" object-copy "^0.1.0" +"statuses@>= 1.5.0 < 2": + version "1.5.0" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" + integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= + stream-browserify@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.2.tgz#87521d38a44aa7ee91ce1cd2a47df0cb49dd660b" @@ -9230,7 +9367,7 @@ tar-fs@^2.0.0: pump "^3.0.0" tar-stream "^2.1.4" -tar-stream@^2.1.4: +tar-stream@^2.1.4, tar-stream@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287" integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ== @@ -9478,6 +9615,11 @@ to-through@^2.0.0: dependencies: through2 "^2.0.3" +toidentifier@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" + integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw== + tough-cookie@~2.4.3: version "2.4.3" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.4.3.tgz#53f36da3f47783b0925afa06ff9f3b165280f781" @@ -9543,6 +9685,11 @@ tslib@^1.9.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== +tslib@^2.0.1: + version "2.3.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.0.tgz#803b8cdab3e12ba581a4ca41c8839bbb0dacb09e" + integrity sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg== + tsutils@^3.17.1: version "3.17.1" resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.17.1.tgz#ed719917f11ca0dee586272b2ac49e015a2dd759" @@ -9725,6 +9872,11 @@ universalify@^0.1.0: resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== +unpipe@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" + integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw= + unquote@~1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/unquote/-/unquote-1.1.1.tgz#8fded7324ec6e88a0ff8b905e7c098cdc086d544"