diff --git a/.eslintignore b/.eslintignore index 8b93a4199e5d7..f9c117426c757 100644 --- a/.eslintignore +++ b/.eslintignore @@ -16,3 +16,8 @@ **/extensions/markdown-language-features/notebook-out/** **/extensions/typescript-basics/test/colorize-fixtures/** **/extensions/**/dist/** +# These are code-server code symlinks. +src/vs/base/common/util.ts +src/vs/base/common/ipc.d.ts +src/vs/base/node/proxy_agent.ts +src/vs/server/uriTransformer.ts diff --git a/.eslintrc.json b/.eslintrc.json index a54964c17b673..97d31c1771295 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -802,10 +802,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..ed42d401d9cd8 100644 --- a/.gitignore +++ b/.gitignore @@ -7,7 +7,8 @@ node_modules/ extensions/**/dist/ /out*/ /extensions/**/out/ -src/vs/server +# NOTE@coder: remove to provide our own server +# 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/.yarnrc b/.yarnrc index ba290809667b9..0b7e220665004 100644 --- a/.yarnrc +++ b/.yarnrc @@ -1,3 +1,3 @@ disturl "https://electronjs.org/headers" -target "12.0.9" +target "12.0.7" runtime "electron" diff --git a/build/gulpfile.reh.js b/build/gulpfile.reh.js index 387a54cbbfea9..0704ab70e9d19 100644 --- a/build/gulpfile.reh.js +++ b/build/gulpfile.reh.js @@ -42,6 +42,8 @@ BUILD_TARGETS.forEach(({ platform, arch }) => { }); function getNodeVersion() { + // NOTE@coder: Fix version due to .yarnrc removal. + return process.versions.node; const yarnrc = fs.readFileSync(path.join(REPO_ROOT, 'remote', '.yarnrc'), 'utf8'); const target = /^target "(.*)"$/m.exec(yarnrc)[1]; return target; 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 6c55569b97313..9281bb19e20b1 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/node.js b/build/lib/node.js index e727aff832396..6e735f2cdb29e 100644 --- a/build/lib/node.js +++ b/build/lib/node.js @@ -8,8 +8,8 @@ const path = require("path"); const fs = require("fs"); const root = path.dirname(path.dirname(__dirname)); const yarnrcPath = path.join(root, 'remote', '.yarnrc'); -const yarnrc = fs.readFileSync(yarnrcPath, 'utf8'); -const version = /^target\s+"([^"]+)"$/m.exec(yarnrc)[1]; +// NOTE@coder: Fix version due to .yarnrc removal. +const version = process.versions.node; const platform = process.platform; const arch = platform === 'darwin' ? 'x64' : process.arch; const node = platform === 'win32' ? 'node.exe' : 'node'; diff --git a/build/lib/node.ts b/build/lib/node.ts index 6ac45ebb1f89c..96fa624ad30a2 100644 --- a/build/lib/node.ts +++ b/build/lib/node.ts @@ -4,13 +4,11 @@ *--------------------------------------------------------------------------------------------*/ import * as path from 'path'; -import * as fs from 'fs'; const root = path.dirname(path.dirname(__dirname)); -const yarnrcPath = path.join(root, 'remote', '.yarnrc'); -const yarnrc = fs.readFileSync(yarnrcPath, 'utf8'); -const version = /^target\s+"([^"]+)"$/m.exec(yarnrc)![1]; +// NOTE@coder: Fix version due to .yarnrc removal. +const version = process.versions.node; const platform = process.platform; const arch = platform === 'darwin' ? 'x64' : process.arch; 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/lib/util.ts b/build/lib/util.ts index c0a0d9619d736..2c64a4bfe64b9 100644 --- a/build/lib/util.ts +++ b/build/lib/util.ts @@ -336,7 +336,6 @@ export function streamToPromise(stream: NodeJS.ReadWriteStream): Promise { } export function getElectronVersion(): string { - const yarnrc = fs.readFileSync(path.join(root, '.yarnrc'), 'utf8'); - const target = /^target "(.*)"$/m.exec(yarnrc)![1]; - return target; + // NOTE@coder: Fix version due to .yarnrc removal. + return process.versions.node; } diff --git a/build/npm/update-localization-extension.js b/build/npm/update-localization-extension.js index 43bc792d9f849..7c6a73aa9d848 100644 --- a/build/npm/update-localization-extension.js +++ b/build/npm/update-localization-extension.js @@ -25,7 +25,7 @@ function update(options) { throw new Error(`${location} doesn't exist.`); } let locExtFolder = idOrPath; - if (/^\w{2}(-\w+)?$/.test(idOrPath)) { + if (/^\w{2,3}(-\w+)?$/.test(idOrPath)) { locExtFolder = path.join('..', 'vscode-loc', 'i18n', `vscode-language-pack-${idOrPath}`); } let locExtStat = fs.statSync(locExtFolder); diff --git a/build/package.json b/build/package.json index f683c3241dc13..69a210e5e57bb 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,11 +35,9 @@ "@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", - "electron-osx-sign": "^0.4.16", "esbuild": "^0.12.6", "fs-extra": "^9.1.0", "got": "11.8.1", @@ -50,10 +46,9 @@ "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.20210528", - "vsce": "1.48.0", + "vsce": "1.88.0", "vscode-universal": "deepak1556/universal#61454d96223b774c53cda10f72c2098c0ce02d58" }, "scripts": { diff --git a/build/yarn.lock b/build/yarn.lock index b0449db06eebd..b324d5bfa7b0d 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,15 +426,12 @@ 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== +ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== 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" + color-convert "^1.9.0" applicationinsights@1.0.8: version "1.0.8" @@ -602,77 +461,24 @@ 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" +azure-devops-node-api@^10.2.2: + version "10.2.2" + resolved "https://registry.yarnpkg.com/azure-devops-node-api/-/azure-devops-node-api-10.2.2.tgz#9f557e622dd07bbaa9bd5e7e84e17c761e2151b2" + integrity sha512-4TVv2X7oNStT0vLaEfExmy3J4/CzfuXolEcQl/BRUmvGySqKStTG2O55/hUQ0kM7UJlZBLgniM0SBq4d/WkKow== + dependencies: + tunnel "0.0.6" + typed-rest-client "^1.8.4" 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" - -bluebird@^3.5.0: - version "3.7.2" - resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" - integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== - boolbase@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" @@ -686,24 +492,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" - integrity sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg== - -buffer-alloc@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/buffer-alloc/-/buffer-alloc-1.2.0.tgz#890dd90d923a873e08e10e5fd51a57e5b7cce0ec" - integrity sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow== - dependencies: - buffer-alloc-unsafe "^1.1.0" - buffer-fill "^1.0.0" - buffer-crc32@~0.2.3: version "0.2.13" resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" @@ -714,11 +502,6 @@ buffer-equal@1.0.0: resolved "https://registry.yarnpkg.com/buffer-equal/-/buffer-equal-1.0.0.tgz#59616b498304d556abd466966b22eeda3eca5fbe" integrity sha1-WWFrSYME1Var1GaWayLu2j7KX74= -buffer-fill@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/buffer-fill/-/buffer-fill-1.0.0.tgz#f8f78b76789888ef39f205cd637f68e702122b2c" - integrity sha1-+PeLdniYiO858gXNY39o5wISKyw= - byline@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/byline/-/byline-5.0.0.tgz#741c5216468eadc457b03410118ad77de8c1ddb1" @@ -742,10 +525,22 @@ 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= +call-bind@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" + integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== + dependencies: + function-bind "^1.1.1" + get-intrinsic "^1.0.2" + +chalk@^2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" cheerio@^1.0.0-rc.1: version "1.0.0-rc.2" @@ -771,6 +566,18 @@ clone-response@^1.0.2: dependencies: mimic-response "^1.0.0" +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= + colors@1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/colors/-/colors-1.0.3.tgz#0433f44d809680fdeb60ed260f1b0c262e82a40b" @@ -781,13 +588,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" @@ -795,36 +595,26 @@ commander@2.9.0: dependencies: 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== - commander@^5.0.0: version "5.1.0" resolved "https://registry.yarnpkg.com/commander/-/commander-5.1.0.tgz#46abbd1652f8e059bddaef99bbdcb2ad9cf179ae" integrity sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg== +commander@^6.1.0: + version "6.2.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c" + integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA== + commander@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/commander/-/commander-7.0.0.tgz#3e2bbfd8bb6724760980988fb5b22b7ee6b71ab2" integrity sha512-ovx/7NkTrnPuIV8sqk/GjUIIM1+iUQeqA3ye2VNpq9sVoiZsooObWlQy+OPWGI17GDaEoybuAGJm6U8yC077BA== -compare-version@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/compare-version/-/compare-version-0.1.2.tgz#0162ec2d9351f5ddd59a9202cba935366a725080" - integrity sha1-AWLsLZNR9d3VmpICy6k1NmpyUIA= - concat-map@0.0.1: version "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,20 +639,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" - integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== - dependencies: - ms "2.0.0" - debug@^4.1.1, debug@^4.3.1: version "4.3.1" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" @@ -882,11 +658,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,26 +731,6 @@ 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== - dependencies: - bluebird "^3.5.0" - compare-version "^0.1.2" - debug "^2.6.8" - isbinaryfile "^3.0.2" - minimist "^1.2.0" - plist "^3.0.1" - end-of-stream@^1.1.0: version "1.4.4" resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" @@ -992,10 +743,20 @@ entities@^1.1.1, entities@~1.1.1: resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56" integrity sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w== +entities@~2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/entities/-/entities-2.0.3.tgz#5c487e5742ab93c15abb5da22759b8590ec03b7f" + integrity sha512-MyoZ0jgnLvB2X3Lg5HqpFmn1kybDiIfEQmKzTb5apr51Rb+T3KdmMiqa70T+bhGnyv7bQ6WMj2QMHpGMmlrUYQ== + 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== + +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= eslint-scope@^5.0.0: version "5.0.0" @@ -1029,36 +790,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 +797,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" @@ -1104,6 +812,20 @@ fs.realpath@^1.0.0: resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== + +get-intrinsic@^1.0.2: + version "1.1.1" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.1.tgz#15f59f376f855c446963948f0d24cd3637b4abc6" + integrity sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q== + dependencies: + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.1" + get-stream@^5.1.0: version "5.2.0" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" @@ -1111,13 +833,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,27 +884,22 @@ 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= +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= -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" +has-symbols@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.2.tgz#165d3070c00309752a1236a479331e3ac56f1423" + integrity sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw== -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== +has@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" + integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== dependencies: - inherits "^2.0.4" - readable-stream "^3.6.0" - safe-buffer "^5.2.0" + function-bind "^1.1.1" htmlparser2@^3.9.1: version "3.10.0" @@ -1208,15 +918,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 +939,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,70 +961,16 @@ 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" - integrity sha512-8cJBL5tTd2OS0dM4jz07wQd5g0dCCqIhUxPIGtZfa5L6hWlvV5MHTITy/DBAsF+Oe2LS1X3krBUhNwaGUWpWxw== - dependencies: - buffer-alloc "^1.2.0" - isexe@^2.0.0: version "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 +985,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" @@ -1360,6 +992,11 @@ keyv@^4.0.0: dependencies: json-buffer "3.0.1" +leven@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" + integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== + linkify-it@^2.0.0: version "2.0.3" resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-2.0.3.tgz#d94a4648f9b1c179d64fa97291268bdb6ce9434f" @@ -1372,7 +1009,7 @@ lodash.unescape@4.0.1: resolved "https://registry.yarnpkg.com/lodash.unescape/-/lodash.unescape-4.0.1.tgz#bf2249886ce514cda112fae9218cdc065211fc9c" integrity sha1-vyJJiGzlFM2hEvrpIYzcBlIR/Jw= -lodash@^4.15.0, lodash@^4.17.10, lodash@^4.17.15: +lodash@^4.15.0, lodash@^4.17.15: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -1389,42 +1026,22 @@ lru-cache@^6.0.0: dependencies: yallist "^4.0.0" -markdown-it@^8.3.1: - version "8.4.2" - resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-8.4.2.tgz#386f98998dc15a37722aa7722084f4020bdd9b54" - integrity sha512-GcRz3AWTqSUphY3vsUqQSFMbgR38a4Lh3GWlHRh/7MRwz8mcu9n2IO7HOh+bXHrR9kOPDl5RNCaEsrneb+xhHQ== +markdown-it@^10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-10.0.0.tgz#abfc64f141b1722d663402044e43927f1f50a8dc" + integrity sha512-YWOP1j7UbDNz+TumYP1kpwnP0aEa711cJjrAQrzd0UXlbJfc5aAq0F/PZHjiioqDC1NKgvIMX+o+9Bk7yuM2dg== dependencies: argparse "^1.0.7" - entities "~1.1.1" + entities "~2.0.0" linkify-it "^2.0.0" 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" @@ -1452,21 +1069,11 @@ minimatch@3.0.4, minimatch@^3.0.3, minimatch@^3.0.4: dependencies: 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== - mkdirp@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== -ms@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" - integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= - ms@2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" @@ -1477,16 +1084,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.0" resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.0.tgz#453354087e6ca96957bd8f5baf753f5982142129" @@ -1499,10 +1096,10 @@ 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== +object-inspect@^1.9.0: + version "1.11.0" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.11.0.tgz#9dceb146cedd4148a0d9e51ab88d34cf509922b1" + integrity sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg== once@^1.3.0, once@^1.3.1, once@^1.4.0: version "1.4.0" @@ -1570,40 +1167,6 @@ 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== - dependencies: - base64-js "^1.2.3" - 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== - pump@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" @@ -1612,20 +1175,12 @@ 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== +qs@^6.9.1: + version "6.10.1" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.10.1.tgz#4931482fa8d647a5aab799c5271d2133b981fb6a" + integrity sha512-M528Hph6wsSVOBiYUnGf+K/7w0hNshs/duGsNXPUCLH5XAqjEtiPGwNONLV0tBH8NoGb0mvD5JubnUTrujKDTg== + dependencies: + side-channel "^1.0.4" quick-lru@^5.1.1: version "5.1.1" @@ -1648,53 +1203,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 +1215,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" @@ -1766,6 +1249,15 @@ shebang-regex@^3.0.0: resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== +side-channel@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" + integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== + dependencies: + call-bind "^1.0.0" + get-intrinsic "^1.0.2" + object-inspect "^1.9.0" + source-map@0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" @@ -1776,21 +1268,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,10 +1275,12 @@ 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= +supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" tmp@0.0.29: version "0.0.29" @@ -1810,33 +1289,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,35 +1301,19 @@ 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: +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" - integrity sha1-92jMDcP06VDwbgSCXDaz54NKofI= +typed-rest-client@^1.8.4: + version "1.8.4" + resolved "https://registry.yarnpkg.com/typed-rest-client/-/typed-rest-client-1.8.4.tgz#ba3fb788e5b9322547406392533f12d660a5ced6" + integrity sha512-MyfKKYzk3I6/QQp6e1T50py4qg+c+9BzOEl2rBmQIpStwNUoqQ73An+Tkfy9YuV7O+o2mpVVJpe+fH//POZkbg== dependencies: - tunnel "0.0.4" - underscore "1.8.3" + qs "^6.9.1" + tunnel "0.0.6" + underscore "^1.12.1" typescript@^4.1.3: version "4.1.3" @@ -1889,83 +1330,40 @@ 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: - 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== +underscore@^1.12.1: + 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" - integrity sha512-1qJn6QLRTu26FIvvMbK/gzHLLdxJVTg9CUTSnCjJHObCCF5CQ0F3FUv7t+5cT7i0J5v5YljrsRY09u7dPBcEnA== +vsce@1.88.0: + version "1.88.0" + resolved "https://registry.yarnpkg.com/vsce/-/vsce-1.88.0.tgz#748dc9f75996d97a5953408848c56c4c1b4dca6b" + integrity sha512-FS5ou3G+WRnPPr/tWVs8b/jVzeDacgZHy/y7/QQW7maSPFEAmRt2bFGUJtJVEUDLBqtDm/3VGMJ7D31cF2U1tw== dependencies: + azure-devops-node-api "^10.2.2" + chalk "^2.4.2" cheerio "^1.0.0-rc.1" - commander "^2.8.1" + commander "^6.1.0" denodeify "^1.2.1" glob "^7.0.6" - lodash "^4.17.10" - markdown-it "^8.3.1" + leven "^3.1.0" + lodash "^4.17.15" + markdown-it "^10.0.0" mime "^1.3.4" minimatch "^3.0.3" osenv "^0.1.3" @@ -1973,8 +1371,8 @@ vsce@1.48.0: read "^1.0.7" semver "^5.1.0" tmp "0.0.29" + typed-rest-client "^1.8.4" url-join "^1.1.0" - vso-node-api "6.1.2-preview" yauzl "^2.3.1" yazl "^2.2.2" @@ -1992,16 +1390,6 @@ vscode-universal@deepak1556/universal#61454d96223b774c53cda10f72c2098c0ce02d58: fs-extra "^9.0.1" typescript "^4.1.3" -vso-node-api@6.1.2-preview: - version "6.1.2-preview" - resolved "https://registry.yarnpkg.com/vso-node-api/-/vso-node-api-6.1.2-preview.tgz#aab3546df2451ecd894e071bb99b5df19c5fa78f" - integrity sha1-qrNUbfJFHs2JTgcbuZtd8Zxfp48= - dependencies: - q "^1.0.1" - tunnel "0.0.4" - typed-rest-client "^0.9.0" - underscore "^1.8.3" - which@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" @@ -2014,41 +1402,11 @@ 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" integrity sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg== -xmlbuilder@^9.0.7: - version "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== - yallist@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" diff --git a/cgmanifest.json b/cgmanifest.json index 22cbc50562c5b..474d6191ba53b 100644 --- a/cgmanifest.json +++ b/cgmanifest.json @@ -60,12 +60,12 @@ "git": { "name": "electron", "repositoryUrl": "https://github.com/electron/electron", - "commitHash": "30f82dd1cb8140ccb5c6a4960eef8e3b8c15eeba" + "commitHash": "8d55658bfa8b5983e1a90ad079c2e2ac91ee7af0" } }, "isOnlyProductionDependency": true, "license": "MIT", - "version": "12.0.9" + "version": "12.0.7" }, { "component": { 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/configuration-editing/schemas/devContainer.schema.generated.json b/extensions/configuration-editing/schemas/devContainer.schema.generated.json index 095a0e355f133..112b4e4827953 100644 --- a/extensions/configuration-editing/schemas/devContainer.schema.generated.json +++ b/extensions/configuration-editing/schemas/devContainer.schema.generated.json @@ -357,6 +357,28 @@ "type": "object", "additionalProperties": true, "description": "Codespaces-specific configuration." + }, + "hostRequirements": { + "type": "object", + "description": "Host hardware requirements.", + "properties": { + "cpus": { + "type": "integer", + "minimum": 1, + "description": "Number of required CPUs." + }, + "memory": { + "type": "string", + "pattern": "^\\d+([tgmk]b)?$", + "description": "Amount of required RAM in bytes. Supports units tb, gb, mb and kb." + }, + "storage": { + "type": "string", + "pattern": "^\\d+([tgmk]b)?$", + "description": "Amount of required disk space in bytes. Supports units tb, gb, mb and kb." + } + }, + "additionalProperties": false } }, "required": [ @@ -714,6 +736,28 @@ "type": "object", "additionalProperties": true, "description": "Codespaces-specific configuration." + }, + "hostRequirements": { + "type": "object", + "description": "Host hardware requirements.", + "properties": { + "cpus": { + "type": "integer", + "minimum": 1, + "description": "Number of required CPUs." + }, + "memory": { + "type": "string", + "pattern": "^\\d+([tgmk]b)?$", + "description": "Amount of required RAM in bytes. Supports units tb, gb, mb and kb." + }, + "storage": { + "type": "string", + "pattern": "^\\d+([tgmk]b)?$", + "description": "Amount of required disk space in bytes. Supports units tb, gb, mb and kb." + } + }, + "additionalProperties": false } }, "required": [ @@ -1047,6 +1091,28 @@ "type": "object", "additionalProperties": true, "description": "Codespaces-specific configuration." + }, + "hostRequirements": { + "type": "object", + "description": "Host hardware requirements.", + "properties": { + "cpus": { + "type": "integer", + "minimum": 1, + "description": "Number of required CPUs." + }, + "memory": { + "type": "string", + "pattern": "^\\d+([tgmk]b)?$", + "description": "Amount of required RAM in bytes. Supports units tb, gb, mb and kb." + }, + "storage": { + "type": "string", + "pattern": "^\\d+([tgmk]b)?$", + "description": "Amount of required disk space in bytes. Supports units tb, gb, mb and kb." + } + }, + "additionalProperties": false } }, "required": [ @@ -1346,6 +1412,28 @@ "type": "object", "additionalProperties": true, "description": "Codespaces-specific configuration." + }, + "hostRequirements": { + "type": "object", + "description": "Host hardware requirements.", + "properties": { + "cpus": { + "type": "integer", + "minimum": 1, + "description": "Number of required CPUs." + }, + "memory": { + "type": "string", + "pattern": "^\\d+([tgmk]b)?$", + "description": "Amount of required RAM in bytes. Supports units tb, gb, mb and kb." + }, + "storage": { + "type": "string", + "pattern": "^\\d+([tgmk]b)?$", + "description": "Amount of required disk space in bytes. Supports units tb, gb, mb and kb." + } + }, + "additionalProperties": false } }, "required": [ @@ -1614,6 +1702,28 @@ "type": "object", "additionalProperties": true, "description": "Codespaces-specific configuration." + }, + "hostRequirements": { + "type": "object", + "description": "Host hardware requirements.", + "properties": { + "cpus": { + "type": "integer", + "minimum": 1, + "description": "Number of required CPUs." + }, + "memory": { + "type": "string", + "pattern": "^\\d+([tgmk]b)?$", + "description": "Amount of required RAM in bytes. Supports units tb, gb, mb and kb." + }, + "storage": { + "type": "string", + "pattern": "^\\d+([tgmk]b)?$", + "description": "Amount of required disk space in bytes. Supports units tb, gb, mb and kb." + } + }, + "additionalProperties": false } }, "additionalProperties": false diff --git a/extensions/configuration-editing/schemas/devContainer.schema.src.json b/extensions/configuration-editing/schemas/devContainer.schema.src.json index 3d1536fa7437b..14c13a958b3b2 100644 --- a/extensions/configuration-editing/schemas/devContainer.schema.src.json +++ b/extensions/configuration-editing/schemas/devContainer.schema.src.json @@ -256,6 +256,32 @@ "type": "object", "additionalProperties": true, "description": "Codespaces-specific configuration." + }, + "hostRequirements": { + "type": "object", + "description": "Host hardware requirements.", + "allOf": [ + { + "type": "object", + "properties": { + "cpus": { + "type": "integer", + "minimum": 1, + "description": "Number of required CPUs." + }, + "memory": { + "type": "string", + "pattern": "^\\d+([tgmk]b)?$", + "description": "Amount of required RAM in bytes. Supports units tb, gb, mb and kb." + }, + "storage": { + "type": "string", + "pattern": "^\\d+([tgmk]b)?$", + "description": "Amount of required disk space in bytes. Supports units tb, gb, mb and kb." + } + } + } + ] } } }, diff --git a/extensions/github-authentication/src/githubServer.ts b/extensions/github-authentication/src/githubServer.ts index f675507c4dd39..33ee47e63162e 100644 --- a/extensions/github-authentication/src/githubServer.ts +++ b/extensions/github-authentication/src/githubServer.ts @@ -15,7 +15,8 @@ import { AuthProviderType } from './github'; const localize = nls.loadMessageBundle(); export const NETWORK_ERROR = 'network error'; -const AUTH_RELAY_SERVER = 'vscode-auth.github.com'; +// NOTE@coder: use our own auth relay (the commented one is microsoft's, not ours) +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/markdown-language-features/src/features/preview.ts b/extensions/markdown-language-features/src/features/preview.ts index fb3fbeca1832a..0f16361338b5f 100644 --- a/extensions/markdown-language-features/src/features/preview.ts +++ b/extensions/markdown-language-features/src/features/preview.ts @@ -531,7 +531,7 @@ export class StaticMarkdownPreview extends Disposable implements ManagedMarkdown })); this._register(this.preview.onScroll((scrollInfo) => { - topmostLineMonitor.setPreviousEditorLine(scrollInfo); + topmostLineMonitor.setPreviousStaticEditorLine(scrollInfo); })); this._register(topmostLineMonitor.onDidChanged(event => { diff --git a/extensions/markdown-language-features/src/features/previewManager.ts b/extensions/markdown-language-features/src/features/previewManager.ts index 7f30abb1c84d6..4cf55fa9d39eb 100644 --- a/extensions/markdown-language-features/src/features/previewManager.ts +++ b/extensions/markdown-language-features/src/features/previewManager.ts @@ -81,7 +81,7 @@ export class MarkdownPreviewManager extends Disposable implements vscode.Webview // When at a markdown file, apply existing scroll settings if (textEditor && textEditor.document && isMarkdownFile(textEditor.document)) { - const line = this._topmostLineMonitor.getPreviousEditorLineByUri(textEditor.document.uri); + const line = this._topmostLineMonitor.getPreviousStaticEditorLineByUri(textEditor.document.uri); if (line) { scrollEditorToLine(line, textEditor); } @@ -172,7 +172,7 @@ export class MarkdownPreviewManager extends Disposable implements vscode.Webview document: vscode.TextDocument, webview: vscode.WebviewPanel ): Promise { - const lineNumber = this._topmostLineMonitor.getPreviousEditorLineByUri(document.uri); + const lineNumber = this._topmostLineMonitor.getPreviousTextEditorLineByUri(document.uri); const preview = StaticMarkdownPreview.revive( document.uri, webview, diff --git a/extensions/markdown-language-features/src/util/topmostLineMonitor.ts b/extensions/markdown-language-features/src/util/topmostLineMonitor.ts index 62f5b5c194b1b..f2e0e2061ca67 100644 --- a/extensions/markdown-language-features/src/util/topmostLineMonitor.ts +++ b/extensions/markdown-language-features/src/util/topmostLineMonitor.ts @@ -16,15 +16,15 @@ export class TopmostLineMonitor extends Disposable { private readonly pendingUpdates = new Map(); private readonly throttle = 50; - private previousEditorInfo = new Map(); - public isPrevEditorCustom = false; + private previousTextEditorInfo = new Map(); + private previousStaticEditorInfo = new Map(); constructor() { super(); if (vscode.window.activeTextEditor) { const line = getVisibleLine(vscode.window.activeTextEditor); - this.setPreviousEditorLine({ uri: vscode.window.activeTextEditor.document.uri, line: line ?? 0 }); + this.setPreviousTextEditorLine({ uri: vscode.window.activeTextEditor.document.uri, line: line ?? 0 }); } this._register(vscode.window.onDidChangeTextEditorVisibleRanges(event => { @@ -32,7 +32,7 @@ export class TopmostLineMonitor extends Disposable { const line = getVisibleLine(event.textEditor); if (typeof line === 'number') { this.updateLine(event.textEditor.document.uri, line); - this.setPreviousEditorLine({ uri: event.textEditor.document.uri, line: line }); + this.setPreviousTextEditorLine({ uri: event.textEditor.document.uri, line: line }); } } })); @@ -41,12 +41,24 @@ export class TopmostLineMonitor extends Disposable { private readonly _onChanged = this._register(new vscode.EventEmitter<{ readonly resource: vscode.Uri, readonly line: number }>()); public readonly onDidChanged = this._onChanged.event; - public setPreviousEditorLine(scrollLocation: LastScrollLocation): void { - this.previousEditorInfo.set(scrollLocation.uri.toString(), scrollLocation); + public setPreviousStaticEditorLine(scrollLocation: LastScrollLocation): void { + this.previousStaticEditorInfo.set(scrollLocation.uri.toString(), scrollLocation); } - public getPreviousEditorLineByUri(resource: vscode.Uri): number | undefined { - const scrollLoc = this.previousEditorInfo.get(resource.toString()); + public getPreviousStaticEditorLineByUri(resource: vscode.Uri): number | undefined { + const scrollLoc = this.previousStaticEditorInfo.get(resource.toString()); + this.previousStaticEditorInfo.delete(resource.toString()); + return scrollLoc?.line; + } + + + public setPreviousTextEditorLine(scrollLocation: LastScrollLocation): void { + this.previousTextEditorInfo.set(scrollLocation.uri.toString(), scrollLocation); + } + + public getPreviousTextEditorLineByUri(resource: vscode.Uri): number | undefined { + const scrollLoc = this.previousTextEditorInfo.get(resource.toString()); + this.previousTextEditorInfo.delete(resource.toString()); return scrollLoc?.line; } diff --git a/extensions/postinstall.js b/extensions/postinstall.js index da4fa3e9d0443..654f6dbfdeb4a 100644 --- a/extensions/postinstall.js +++ b/extensions/postinstall.js @@ -24,6 +24,9 @@ function processRoot() { rimraf.sync(filePath); } } + + // NOTE@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 3c2b3b89dc097..8b362dc39b79d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "code-oss-dev", - "version": "1.57.0", + "version": "1.57.1", "distro": "90b97e4e10fb8a1ab3cd4846f4dc56bfea8ea620", "author": { "name": "Microsoft Corporation" @@ -57,6 +57,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 +73,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.20", "vscode-oniguruma": "1.5.1", @@ -125,7 +130,7 @@ "cssnano": "^4.1.11", "debounce": "^1.0.0", "deemon": "^1.4.0", - "electron": "12.0.9", + "electron": "12.0.7", "eslint": "6.8.0", "eslint-plugin-jsdoc": "^19.1.0", "event-stream": "3.3.4", diff --git a/resources/linux/code-url-handler.desktop b/resources/linux/code-url-handler.desktop index 7106e0e0969be..b85525fbd042c 100644 --- a/resources/linux/code-url-handler.desktop +++ b/resources/linux/code-url-handler.desktop @@ -2,7 +2,7 @@ Name=@@NAME_LONG@@ - URL Handler Comment=Code Editing. Redefined. GenericName=Text Editor -Exec=@@EXEC@@ --open-url %U +Exec=@@EXEC@@ --no-sandbox --open-url %U Icon=@@ICON@@ Type=Application NoDisplay=true diff --git a/resources/linux/code.desktop b/resources/linux/code.desktop index ab3b79a011b77..62d6bfc47b469 100755 --- a/resources/linux/code.desktop +++ b/resources/linux/code.desktop @@ -2,7 +2,7 @@ Name=@@NAME_LONG@@ Comment=Code Editing. Redefined. GenericName=Text Editor -Exec=@@EXEC@@ --unity-launch %F +Exec=@@EXEC@@ --no-sandbox --unity-launch %F Icon=@@ICON@@ Type=Application StartupNotify=false @@ -14,5 +14,5 @@ Keywords=vscode; [Desktop Action new-empty-window] Name=New Empty Window -Exec=@@EXEC@@ --new-window %F +Exec=@@EXEC@@ --no-sandbox --new-window %F Icon=@@ICON@@ diff --git a/src/vs/base/common/ipc.d.ts b/src/vs/base/common/ipc.d.ts new file mode 100644 index 0000000000000..5bd7beca09cb8 --- /dev/null +++ b/src/vs/base/common/ipc.d.ts @@ -0,0 +1,138 @@ +/*--------------------------------------------------------------------------------------------- + * 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 +} + +export interface InitMessage { + type: 'init' + id: string + options: VscodeInitializationOptions +} + +export type Query = { [key: string]: string | string[] | undefined | Query | Query[] } + +export interface SocketMessage { + type: 'socket' + query: Query + permessageDeflate: boolean +} + +export interface CliMessage { + type: 'cli' + args: Args +} + +export interface OpenCommandPipeArgs { + type: 'open' + fileURIs?: string[] + folderURIs: string[] + forceNewWindow?: boolean + diffMode?: boolean + addMode?: boolean + gotoLineMode?: boolean + forceReuseWindow?: boolean + waitMarkerFilePath?: string +} + +export type CodeServerMessage = InitMessage | SocketMessage | CliMessage + +export interface ReadyMessage { + type: 'ready' +} + +export interface OptionsMessage { + id: string + type: 'options' + options: WorkbenchOptions +} + +export type VscodeMessage = ReadyMessage | OptionsMessage + +export interface StartPath { + url: string + workspace: boolean +} + +export interface Args { + 'user-data-dir'?: string + + 'enable-proposed-api'?: string[] + 'extensions-dir'?: string + 'builtin-extensions-dir'?: string + 'extra-extensions-dir'?: string[] + 'extra-builtin-extensions-dir'?: string[] + 'ignore-last-opened'?: boolean + + locale?: string + + log?: string + verbose?: boolean + + _: string[] +} + +export interface VscodeInitializationOptions { + readonly args: Args + readonly remoteAuthority: string + readonly startPath?: StartPath +} + +export interface VscodeInitializationOptionsMessage extends VscodeInitializationOptions { + readonly id: string +} + +export interface UriComponents { + readonly scheme: string + readonly authority: string + readonly path: string + readonly query: string + readonly fragment: string +} + +export interface NLSConfiguration { + locale: string + availableLanguages: { + [key: string]: string + } + pseudo?: boolean + _languagePackSupport?: boolean +} + +export interface WorkbenchOptions { + readonly workbenchWebConfiguration: { + readonly remoteAuthority?: string + readonly folderUri?: UriComponents + readonly workspaceUri?: UriComponents + readonly logLevel?: number + readonly workspaceProvider?: { + payload: [['userDataPath', string], ['enableProposedApi', string]] + } + } + readonly remoteUserDataUri: UriComponents + readonly productConfiguration: { + codeServerVersion?: string + readonly extensionsGallery?: { + readonly serviceUrl: string + readonly itemUrl: string + readonly controlUrl: string + readonly recommendationsUrl: string + } + } + readonly nlsConfiguration: NLSConfiguration + readonly commit: string +} + +export interface WorkbenchOptionsMessage { + id: string +} diff --git a/src/vs/base/common/network.ts b/src/vs/base/common/network.ts index d5bacc79d2d8c..c990c88e095fa 100644 --- a/src/vs/base/common/network.ts +++ b/src/vs/base/common/network.ts @@ -126,16 +126,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 512775eaddc69..2453c8fd24f45 100644 --- a/src/vs/base/common/platform.ts +++ b/src/vs/base/common/platform.ts @@ -101,6 +101,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 a301b50683c18..8ede7fbdfd6b9 100644 --- a/src/vs/base/common/product.ts +++ b/src/vs/base/common/product.ts @@ -31,6 +31,9 @@ export type ExtensionVirtualWorkspaceSupport = { }; export interface IProductConfiguration { + // NOTE@coder: add codeServerVersion + readonly codeServerVersion?: string; + readonly version: string; readonly date?: string; readonly quality?: string; diff --git a/src/vs/base/common/uriServer.ts b/src/vs/base/common/uriServer.ts new file mode 100644 index 0000000000000..7e59775e213e2 --- /dev/null +++ b/src/vs/base/common/uriServer.ts @@ -0,0 +1,45 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Coder Technologies. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IRawURITransformer, UriParts, URITransformer } from 'vs/base/common/uriIpc'; + +class RawURITransformer implements IRawURITransformer { + constructor(private readonly authority: string) { } + + transformIncoming(uri: UriParts): UriParts { + switch (uri.scheme) { + case 'vscode-remote': + return { scheme: 'file', path: uri.path }; + default: + return uri; + } + } + + transformOutgoing(uri: UriParts): UriParts { + switch (uri.scheme) { + case 'file': + return { scheme: 'vscode-remote', authority: this.authority, path: uri.path }; + default: + return uri; + } + } + + transformOutgoingScheme(scheme: string): string { + switch (scheme) { + case 'file': + return 'vscode-remote'; + 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..338db5a2fdea4 --- /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 aad4fb4e92a28..7d9459c1c2407 100644 --- a/src/vs/base/parts/ipc/common/ipc.net.ts +++ b/src/vs/base/parts/ipc/common/ipc.net.ts @@ -747,6 +747,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/code/browser/workbench/workbench.ts b/src/vs/code/browser/workbench/workbench.ts index 9c222a0048ec3..033924a7743a2 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 { IWorkbenchConstructionOptions, create, ICredentialsProvider, IURLCallbackProvider, IWorkspaceProvider, IWorkspace, IWindowIndicator, IProductQualityChangeHandler, ISettingsSyncOptions } 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,27 @@ class WindowIndicator implements IWindowIndicator { throw new Error('Missing web configuration element'); } - const config: IWorkbenchConstructionOptions & { folderUri?: UriComponents, workspaceUri?: UriComponents } = 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 +460,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 +502,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/node/cli.ts b/src/vs/code/node/cli.ts index 2e7b324ed1d1e..169b50ae77c29 100644 --- a/src/vs/code/node/cli.ts +++ b/src/vs/code/node/cli.ts @@ -15,7 +15,7 @@ import { isAbsolute, join } from 'vs/base/common/path'; import { whenDeleted, writeFileSync } from 'vs/base/node/pfs'; import { findFreePort } from 'vs/base/node/ports'; import { randomPort } from 'vs/base/common/ports'; -import { isWindows, IProcessEnvironment } from 'vs/base/common/platform'; +import { isLinux, isWindows, IProcessEnvironment } from 'vs/base/common/platform'; import type { ProfilingSession, Target } from 'v8-inspect-profiler'; import { isString } from 'vs/base/common/types'; import { hasStdinWithoutTty, stdinDataListener, getStdinFilePath, readFromStdin } from 'vs/platform/environment/node/stdin'; @@ -319,6 +319,10 @@ export async function main(argv: string[]): Promise { options['stdio'] = 'ignore'; } + if (isLinux) { + addArg(argv, '--no-sandbox'); // Electron 6 introduces a chrome-sandbox that requires root to run. This can fail. Disable sandbox via --no-sandbox + } + const child = spawn(process.execPath, argv.slice(2), options); if (args.wait && waitMarkerFilePath) { diff --git a/src/vs/editor/common/view/editorColorRegistry.ts b/src/vs/editor/common/view/editorColorRegistry.ts index b5acf0adac584..df5cdcf3e1f4e 100644 --- a/src/vs/editor/common/view/editorColorRegistry.ts +++ b/src/vs/editor/common/view/editorColorRegistry.ts @@ -44,7 +44,7 @@ export const editorUnnecessaryCodeBorder = registerColor('editorUnnecessaryCode. export const editorUnnecessaryCodeOpacity = registerColor('editorUnnecessaryCode.opacity', { dark: Color.fromHex('#000a'), light: Color.fromHex('#0007'), hc: null }, nls.localize('unnecessaryCodeOpacity', 'Opacity of unnecessary (unused) source code in the editor. For example, "#000000c0" will render the code with 75% opacity. For high contrast themes, use the \'editorUnnecessaryCode.border\' theme color to underline unnecessary code instead of fading it out.')); export const ghostTextBorder = registerColor('editorGhostText.border', { dark: null, light: null, hc: Color.fromHex('#fff').transparent(0.8) }, nls.localize('editorGhostTextBorder', 'Border color of ghost text in the editor.')); -export const ghostTextForeground = registerColor('editorGhostText.foreground', { dark: Color.fromHex('#FFFa'), light: Color.fromHex('#0007'), hc: null }, nls.localize('editorGhostTextForeground', 'Foreground color of the ghost text in the editor.')); +export const ghostTextForeground = registerColor('editorGhostText.foreground', { dark: Color.fromHex('#ffffff56'), light: Color.fromHex('#0007'), hc: null }, nls.localize('editorGhostTextForeground', 'Foreground color of the ghost text in the editor.')); const rulerRangeDefault = new Color(new RGBA(0, 122, 204, 0.6)); export const overviewRulerRangeHighlight = registerColor('editorOverviewRuler.rangeHighlightForeground', { dark: rulerRangeDefault, light: rulerRangeDefault, hc: rulerRangeDefault }, nls.localize('overviewRulerRangeHighlight', 'Overview ruler marker color for range highlights. The color must not be opaque so as not to hide underlying decorations.'), true); diff --git a/src/vs/editor/contrib/inlineCompletions/ghostTextController.ts b/src/vs/editor/contrib/inlineCompletions/ghostTextController.ts index f7de4aa6484ae..e226aca73b826 100644 --- a/src/vs/editor/contrib/inlineCompletions/ghostTextController.ts +++ b/src/vs/editor/contrib/inlineCompletions/ghostTextController.ts @@ -19,7 +19,7 @@ import { ContextKeyExpr, IContextKeyService, RawContextKey } from 'vs/platform/c import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; export class GhostTextController extends Disposable { - public static readonly inlineSuggestionVisible = new RawContextKey('inlineSuggestionVisible ', false, nls.localize('inlineSuggestionVisible', "Whether an inline suggestion is visible")); + public static readonly inlineSuggestionVisible = new RawContextKey('inlineSuggestionVisible', false, nls.localize('inlineSuggestionVisible', "Whether an inline suggestion is visible")); public static readonly inlineSuggestionHasIndentation = new RawContextKey('inlineSuggestionHasIndentation', false, nls.localize('inlineSuggestionHasIndentation', "Whether the inline suggestion starts with whitespace")); static ID = 'editor.contrib.ghostTextController'; @@ -34,7 +34,7 @@ export class GhostTextController extends Disposable { private triggeredExplicitly = false; constructor( - private readonly editor: ICodeEditor, + public readonly editor: ICodeEditor, @IInstantiationService private readonly instantiationService: IInstantiationService, @IContextKeyService contextKeyService: IContextKeyService, ) { @@ -50,6 +50,9 @@ export class GhostTextController extends Disposable { if (e.hasChanged(EditorOption.suggest)) { this.updateModelController(); } + if (e.hasChanged(EditorOption.inlineSuggest)) { + this.updateModelController(); + } })); this.updateModelController(); } @@ -159,6 +162,9 @@ export class ActiveGhostTextController extends Disposable { this._register(this.suggestWidgetAdapterModel.onDidChange(() => { this.updateModel(); + // When the suggest widget becomes inactive and an inline completion + // becomes visible, we need to update the context keys. + this.updateContextKeys(); })); this.updateModel(); @@ -251,6 +257,7 @@ export const commitInlineSuggestionAction = new GhostTextCommand({ }, handler(x) { x.commit(); + x.editor.focus(); } }); registerEditorCommand(commitInlineSuggestionAction); diff --git a/src/vs/editor/contrib/inlineCompletions/inlineCompletionsModel.ts b/src/vs/editor/contrib/inlineCompletions/inlineCompletionsModel.ts index 026b39f07c42c..612991e5b9398 100644 --- a/src/vs/editor/contrib/inlineCompletions/inlineCompletionsModel.ts +++ b/src/vs/editor/contrib/inlineCompletions/inlineCompletionsModel.ts @@ -294,6 +294,9 @@ class InlineCompletionsSession extends BaseGhostTextWidgetModel { } public scheduleAutomaticUpdate(): void { + // Since updateSoon debounces, starvation can happen. + // To prevent stale cache, we clear the current update operation. + this.updateOperation.clear(); this.updateSoon.schedule(); } @@ -366,6 +369,8 @@ class InlineCompletionsSession extends BaseGhostTextWidgetModel { cache?.dispose(); }) .then(undefined, onUnexpectedExternalError); + } else { + cache?.dispose(); } this.onDidChangeEmitter.fire(); @@ -475,15 +480,21 @@ export function inlineCompletionToGhostText(inlineCompletion: NormalizedInlineCo // "\t\tfoo" -> "\t\t\tfoobar" (+"\t", +"bar") // "\t\tfoo" -> "\tfoobar" (-"\t", +"\bar") + const firstNonWsCol = textModel.getLineFirstNonWhitespaceColumn(inlineCompletion.range.startLineNumber); + if (inlineCompletion.text.startsWith(valueToBeReplaced)) { remainingInsertText = inlineCompletion.text.substr(valueToBeReplaced.length); - } else { + } else if (firstNonWsCol === 0 || inlineCompletion.range.startColumn < firstNonWsCol) { + // Only allow ignoring leading whitespace in indentation. + // This prevents flickering when the user types whitespace that extends an empty range. const valueToBeReplacedTrimmed = leftTrim(valueToBeReplaced); const insertTextTrimmed = leftTrim(inlineCompletion.text); if (!insertTextTrimmed.startsWith(valueToBeReplacedTrimmed)) { return undefined; } remainingInsertText = insertTextTrimmed.substr(valueToBeReplacedTrimmed.length); + } else { + return undefined; } const position = inlineCompletion.range.getEndPosition(); diff --git a/src/vs/editor/contrib/inlineCompletions/suggestWidgetAdapterModel.ts b/src/vs/editor/contrib/inlineCompletions/suggestWidgetAdapterModel.ts index 765d200b5dab3..eac1ad30f5382 100644 --- a/src/vs/editor/contrib/inlineCompletions/suggestWidgetAdapterModel.ts +++ b/src/vs/editor/contrib/inlineCompletions/suggestWidgetAdapterModel.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { RunOnceScheduler } from 'vs/base/common/async'; import { Event } from 'vs/base/common/event'; import { toDisposable } from 'vs/base/common/lifecycle'; import { IActiveCodeEditor } from 'vs/editor/browser/editorBrowser'; @@ -20,10 +21,21 @@ import { ISelectedSuggestion } from 'vs/editor/contrib/suggest/suggestWidget'; export class SuggestWidgetAdapterModel extends BaseGhostTextWidgetModel { private isSuggestWidgetVisible: boolean = false; private currentGhostText: GhostText | undefined = undefined; + private _isActive: boolean = false; public override minReservedLineCount: number = 0; - public get isActive() { return this.isSuggestWidgetVisible; } + public get isActive() { return this._isActive; } + + // This delay fixes an suggest widget issue when typing "." immediately restarts the suggestion session. + private setInactiveDelayed = this._register(new RunOnceScheduler(() => { + if (!this.isSuggestWidgetVisible) { + if (this.isActive) { + this._isActive = false; + this.onDidChangeEmitter.fire(); + } + } + }, 100)); constructor( editor: IActiveCodeEditor @@ -41,15 +53,18 @@ export class SuggestWidgetAdapterModel extends BaseGhostTextWidgetModel { this._register(suggestController.widget.value.onDidShow(() => { this.isSuggestWidgetVisible = true; + this._isActive = true; this.updateFromSuggestion(); })); this._register(suggestController.widget.value.onDidHide(() => { this.isSuggestWidgetVisible = false; + this.setInactiveDelayed.schedule(); this.minReservedLineCount = 0; this.updateFromSuggestion(); })); this._register(suggestController.widget.value.onDidFocus(() => { this.isSuggestWidgetVisible = true; + this._isActive = true; this.updateFromSuggestion(); })); }; diff --git a/src/vs/editor/contrib/suggest/suggestModel.ts b/src/vs/editor/contrib/suggest/suggestModel.ts index 1880ef1bd9d1f..7089173d95471 100644 --- a/src/vs/editor/contrib/suggest/suggestModel.ts +++ b/src/vs/editor/contrib/suggest/suggestModel.ts @@ -24,6 +24,8 @@ import { isLowSurrogate, isHighSurrogate, getLeadingWhitespace } from 'vs/base/c import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { ILogService } from 'vs/platform/log/common/log'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; export interface ICancelEvent { readonly retrigger: boolean; @@ -95,6 +97,20 @@ export const enum State { Auto = 2 } +function shouldPreventQuickSuggest(contextKeyService: IContextKeyService, configurationService: IConfigurationService): boolean { + return ( + Boolean(contextKeyService.getContextKeyValue('inlineSuggestionVisible')) + && !Boolean(configurationService.getValue('editor.inlineSuggest.allowQuickSuggestions')) + ); +} + +function shouldPreventSuggestOnTriggerCharacters(contextKeyService: IContextKeyService, configurationService: IConfigurationService): boolean { + return ( + Boolean(contextKeyService.getContextKeyValue('inlineSuggestionVisible')) + && !Boolean(configurationService.getValue('editor.inlineSuggest.allowSuggestOnTriggerCharacters')) + ); +} + export class SuggestModel implements IDisposable { private readonly _toDispose = new DisposableStore(); @@ -123,6 +139,8 @@ export class SuggestModel implements IDisposable { @IClipboardService private readonly _clipboardService: IClipboardService, @ITelemetryService private readonly _telemetryService: ITelemetryService, @ILogService private readonly _logService: ILogService, + @IContextKeyService private readonly _contextKeyService: IContextKeyService, + @IConfigurationService private readonly _configurationService: IConfigurationService, ) { this._currentSelection = this._editor.getSelection() || new Selection(1, 1, 1, 1); @@ -213,6 +231,10 @@ export class SuggestModel implements IDisposable { const checkTriggerCharacter = (text?: string) => { + if (shouldPreventSuggestOnTriggerCharacters(this._contextKeyService, this._configurationService)) { + return; + } + if (!text) { // came here from the compositionEnd-event const position = this._editor.getPosition()!; @@ -351,6 +373,11 @@ export class SuggestModel implements IDisposable { } } + if (shouldPreventQuickSuggest(this._contextKeyService, this._configurationService)) { + // do not trigger quick suggestions if inline suggestions are shown + return; + } + // we made it till here -> trigger now this.trigger({ auto: true, shy: false }); diff --git a/src/vs/editor/contrib/suggest/test/suggestModel.test.ts b/src/vs/editor/contrib/suggest/test/suggestModel.test.ts index a17fa48850bf6..882e001e73526 100644 --- a/src/vs/editor/contrib/suggest/test/suggestModel.test.ts +++ b/src/vs/editor/contrib/suggest/test/suggestModel.test.ts @@ -31,11 +31,12 @@ import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerServ import { ISuggestMemoryService } from 'vs/editor/contrib/suggest/suggestMemory'; import { ITextModel } from 'vs/editor/common/model'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { MockKeybindingService } from 'vs/platform/keybinding/test/common/mockKeybindingService'; +import { MockKeybindingService, MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService'; import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { mock } from 'vs/base/test/common/mock'; import { NullLogService } from 'vs/platform/log/common/log'; +import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; function createMockEditor(model: TextModel): ITestCodeEditor { @@ -204,7 +205,9 @@ suite('SuggestModel - TriggerAndCancelOracle', function () { } }, NullTelemetryService, - new NullLogService() + new NullLogService(), + new MockContextKeyService(), + new TestConfigurationService() ); disposables.push(oracle, editor); diff --git a/src/vs/platform/environment/common/argv.ts b/src/vs/platform/environment/common/argv.ts index ded07ffcd9e5d..08a4afa55ba15 100644 --- a/src/vs/platform/environment/common/argv.ts +++ b/src/vs/platform/environment/common/argv.ts @@ -39,6 +39,8 @@ export interface NativeParsedArgs { 'extensions-dir'?: string; 'extensions-download-dir'?: string; 'builtin-extensions-dir'?: string; + 'extra-extensions-dir'?: string[]; // NOTE@coder: added extra extensions dir + 'extra-builtin-extensions-dir'?: string[]; // NOTE@coder: added extra builtin extensions dir 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 238098af3a062..6b65cf668fd99 100644 --- a/src/vs/platform/environment/node/argv.ts +++ b/src/vs/platform/environment/node/argv.ts @@ -52,6 +52,9 @@ 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' }, 'builtin-extensions-dir': { type: 'string' }, + // NOTE@coder: add extra-extensions-dir and extra-builtin-extensions-dir + 'extra-extensions-dir': { type: 'string[]', cat: 'o', description: 'Path to an extra user extension directory.' }, + 'extra-builtin-extensions-dir': { type: 'string[]', cat: 'o', description: 'Path to an extra builtin extension directory.' }, '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' }, diff --git a/src/vs/platform/extensionManagement/node/extensionsScanner.ts b/src/vs/platform/extensionManagement/node/extensionsScanner.ts index f489f82362ba5..bd49ee29bdb91 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/externalTerminal/common/externalTerminal.ts b/src/vs/platform/externalTerminal/common/externalTerminal.ts index 72d71f88520bb..df1a6e20a2eed 100644 --- a/src/vs/platform/externalTerminal/common/externalTerminal.ts +++ b/src/vs/platform/externalTerminal/common/externalTerminal.ts @@ -22,7 +22,7 @@ export interface ITerminalForPlatform { export interface IExternalTerminalService { readonly _serviceBrand: undefined; - openTerminal(path: string): Promise; + openTerminal(configuration: IExternalTerminalSettings, path: string): Promise; runInTerminal(title: string, cwd: string, args: string[], env: ITerminalEnvironment, settings: IExternalTerminalSettings): Promise; getDefaultTerminalForPlatforms(): Promise; } diff --git a/src/vs/platform/externalTerminal/electron-main/externalTerminalService.test.ts b/src/vs/platform/externalTerminal/electron-main/externalTerminalService.test.ts index a3acac5ce1352..86970909b5000 100644 --- a/src/vs/platform/externalTerminal/electron-main/externalTerminalService.test.ts +++ b/src/vs/platform/externalTerminal/electron-main/externalTerminalService.test.ts @@ -42,7 +42,7 @@ suite('ExternalTerminalService', () => { }; } }; - let testService = new WindowsExternalTerminalService(mockConfig); + let testService = new WindowsExternalTerminalService(); (testService).spawnTerminal( mockSpawner, mockConfig, @@ -67,7 +67,7 @@ suite('ExternalTerminalService', () => { } }; mockConfig.terminal.external.windowsExec = undefined; - let testService = new WindowsExternalTerminalService(mockConfig); + let testService = new WindowsExternalTerminalService(); (testService).spawnTerminal( mockSpawner, mockConfig, @@ -91,7 +91,7 @@ suite('ExternalTerminalService', () => { }; } }; - let testService = new WindowsExternalTerminalService(mockConfig); + let testService = new WindowsExternalTerminalService(); (testService).spawnTerminal( mockSpawner, mockConfig, @@ -115,7 +115,7 @@ suite('ExternalTerminalService', () => { return { on: (evt: any) => evt }; } }; - let testService = new WindowsExternalTerminalService(mockConfig); + let testService = new WindowsExternalTerminalService(); (testService).spawnTerminal( mockSpawner, mockConfig, @@ -137,7 +137,7 @@ suite('ExternalTerminalService', () => { return { on: (evt: any) => evt }; } }; - let testService = new WindowsExternalTerminalService(mockConfig); + let testService = new WindowsExternalTerminalService(); (testService).spawnTerminal( mockSpawner, mockConfig, @@ -160,7 +160,7 @@ suite('ExternalTerminalService', () => { }; } }; - let testService = new MacExternalTerminalService(mockConfig); + let testService = new MacExternalTerminalService(); (testService).spawnTerminal( mockSpawner, mockConfig, @@ -183,7 +183,7 @@ suite('ExternalTerminalService', () => { } }; mockConfig.terminal.external.osxExec = undefined; - let testService = new MacExternalTerminalService(mockConfig); + let testService = new MacExternalTerminalService(); (testService).spawnTerminal( mockSpawner, mockConfig, @@ -206,7 +206,7 @@ suite('ExternalTerminalService', () => { }; } }; - let testService = new LinuxExternalTerminalService(mockConfig); + let testService = new LinuxExternalTerminalService(); (testService).spawnTerminal( mockSpawner, mockConfig, @@ -230,7 +230,7 @@ suite('ExternalTerminalService', () => { } }; mockConfig.terminal.external.linuxExec = undefined; - let testService = new LinuxExternalTerminalService(mockConfig); + let testService = new LinuxExternalTerminalService(); (testService).spawnTerminal( mockSpawner, mockConfig, diff --git a/src/vs/platform/externalTerminal/node/externalTerminalService.ts b/src/vs/platform/externalTerminal/node/externalTerminalService.ts index a7ae827eee1c2..c90429f95092a 100644 --- a/src/vs/platform/externalTerminal/node/externalTerminalService.ts +++ b/src/vs/platform/externalTerminal/node/externalTerminalService.ts @@ -9,9 +9,7 @@ import * as processes from 'vs/base/node/processes'; import * as nls from 'vs/nls'; import * as pfs from 'vs/base/node/pfs'; import * as env from 'vs/base/common/platform'; -import { IExternalTerminalConfiguration, IExternalTerminalSettings, DEFAULT_TERMINAL_OSX, ITerminalForPlatform, IExternalTerminalMainService } from 'vs/platform/externalTerminal/common/externalTerminal'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { optional } from 'vs/platform/instantiation/common/instantiation'; +import { IExternalTerminalSettings, DEFAULT_TERMINAL_OSX, ITerminalForPlatform, IExternalTerminalMainService } from 'vs/platform/externalTerminal/common/externalTerminal'; import { FileAccess } from 'vs/base/common/network'; import { ITerminalEnvironment } from 'vs/platform/terminal/common/terminal'; import { sanitizeProcessEnvironment } from 'vs/base/common/processes'; @@ -31,20 +29,12 @@ export class WindowsExternalTerminalService extends ExternalTerminalService impl private static readonly CMD = 'cmd.exe'; private static _DEFAULT_TERMINAL_WINDOWS: string; - constructor( - @optional(IConfigurationService) private readonly _configurationService: IConfigurationService - ) { - super(); - } - - public openTerminal(cwd?: string): Promise { - const configuration = this._configurationService.getValue(); + public openTerminal(configuration: IExternalTerminalSettings, cwd?: string): Promise { return this.spawnTerminal(cp, configuration, processes.getWindowsShell(), cwd); } - public spawnTerminal(spawner: typeof cp, configuration: IExternalTerminalConfiguration, command: string, cwd?: string): Promise { - const terminalConfig = configuration.terminal.external; - const exec = terminalConfig?.windowsExec || WindowsExternalTerminalService.getDefaultTerminalWindows(); + public spawnTerminal(spawner: typeof cp, configuration: IExternalTerminalSettings, command: string, cwd?: string): Promise { + const exec = configuration.windowsExec || WindowsExternalTerminalService.getDefaultTerminalWindows(); // Make the drive letter uppercase on Windows (see #9448) if (cwd && cwd[1] === ':') { @@ -124,14 +114,7 @@ export class WindowsExternalTerminalService extends ExternalTerminalService impl export class MacExternalTerminalService extends ExternalTerminalService implements IExternalTerminalMainService { private static readonly OSASCRIPT = '/usr/bin/osascript'; // osascript is the AppleScript interpreter on OS X - constructor( - @optional(IConfigurationService) private readonly _configurationService: IConfigurationService - ) { - super(); - } - - public openTerminal(cwd?: string): Promise { - const configuration = this._configurationService.getValue(); + public openTerminal(configuration: IExternalTerminalSettings, cwd?: string): Promise { return this.spawnTerminal(cp, configuration, cwd); } @@ -199,9 +182,8 @@ export class MacExternalTerminalService extends ExternalTerminalService implemen }); } - spawnTerminal(spawner: typeof cp, configuration: IExternalTerminalConfiguration, cwd?: string): Promise { - const terminalConfig = configuration.terminal.external; - const terminalApp = terminalConfig?.osxExec || DEFAULT_TERMINAL_OSX; + spawnTerminal(spawner: typeof cp, configuration: IExternalTerminalSettings, cwd?: string): Promise { + const terminalApp = configuration.osxExec || DEFAULT_TERMINAL_OSX; return new Promise((c, e) => { const args = ['-a', terminalApp]; @@ -219,14 +201,7 @@ export class LinuxExternalTerminalService extends ExternalTerminalService implem private static readonly WAIT_MESSAGE = nls.localize('press.any.key', "Press any key to continue..."); - constructor( - @optional(IConfigurationService) private readonly _configurationService: IConfigurationService - ) { - super(); - } - - public openTerminal(cwd?: string): Promise { - const configuration = this._configurationService.getValue(); + public openTerminal(configuration: IExternalTerminalSettings, cwd?: string): Promise { return this.spawnTerminal(cp, configuration, cwd); } @@ -314,9 +289,8 @@ export class LinuxExternalTerminalService extends ExternalTerminalService implem return LinuxExternalTerminalService._DEFAULT_TERMINAL_LINUX_READY; } - spawnTerminal(spawner: typeof cp, configuration: IExternalTerminalConfiguration, cwd?: string): Promise { - const terminalConfig = configuration.terminal.external; - const execPromise = terminalConfig?.linuxExec ? Promise.resolve(terminalConfig.linuxExec) : LinuxExternalTerminalService.getDefaultTerminalLinuxReady(); + spawnTerminal(spawner: typeof cp, configuration: IExternalTerminalSettings, cwd?: string): Promise { + const execPromise = configuration.linuxExec ? Promise.resolve(configuration.linuxExec) : LinuxExternalTerminalService.getDefaultTerminalLinuxReady(); return new Promise((c, e) => { execPromise.then(exec => { diff --git a/src/vs/platform/product/common/product.ts b/src/vs/platform/product/common/product.ts index 4a0b94187d631..0735d29d9f00b 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 2f343e841ab99..60bf97572f8e4 100644 --- a/src/vs/platform/remote/browser/browserSocketFactory.ts +++ b/src/vs/platform/remote/browser/browserSocketFactory.ts @@ -208,7 +208,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(); diff --git a/src/vs/platform/remote/common/remoteAgentConnection.ts b/src/vs/platform/remote/common/remoteAgentConnection.ts index 41475982dd398..e36cc6be53691 100644 --- a/src/vs/platform/remote/common/remoteAgentConnection.ts +++ b/src/vs/platform/remote/common/remoteAgentConnection.ts @@ -229,7 +229,9 @@ async function connectToRemoteExtensionHostAgent(options: ISimpleConnectionOptio let socket: ISocket; try { - socket = await createSocket(options.logService, options.socketFactory, options.host, options.port, `reconnectionToken=${options.reconnectionToken}&reconnection=${options.reconnectionProtocol ? 'true' : 'false'}`, timeoutCancellationToken); + // NOTE@coder: Add connection type to the socket. This is so they can be + // distinguished by the backend. + socket = await createSocket(options.logService, options.socketFactory, options.host, options.port, `type=${connectionTypeToString(connectionType)}&reconnectionToken=${options.reconnectionToken}&reconnection=${options.reconnectionProtocol ? 'true' : 'false'}`, timeoutCancellationToken); } catch (error) { options.logService.error(`${logPrefix} socketFactory.connect() failed or timed out. Error:`); options.logService.error(error); diff --git a/src/vs/platform/storage/common/storage.ts b/src/vs/platform/storage/common/storage.ts index faecb2618ea52..4dc61721d815a 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/platform/windows/electron-main/windowsStateHandler.ts b/src/vs/platform/windows/electron-main/windowsStateHandler.ts index b24443601c2e5..1bdadb1261073 100644 --- a/src/vs/platform/windows/electron-main/windowsStateHandler.ts +++ b/src/vs/platform/windows/electron-main/windowsStateHandler.ts @@ -232,11 +232,9 @@ export class WindowsStateHandler extends Disposable { const windowConfig = this.configurationService.getValue('window'); // Window state is not from a previous session: only allow fullscreen if we inherit it or user wants fullscreen - // or to address a Electron issue on macOS (https://github.com/microsoft/vscode/issues/125122) let allowFullscreen: boolean; if (state.hasDefaultState) { - const configAllowsFullScreen = !!(windowConfig?.newWindowDimensions && ['fullscreen', 'inherit', 'offset'].indexOf(windowConfig.newWindowDimensions) >= 0); - allowFullscreen = configAllowsFullScreen || (isMacintosh && windowConfig?.nativeFullScreen !== false); + allowFullscreen = !!(windowConfig?.newWindowDimensions && ['fullscreen', 'inherit', 'offset'].indexOf(windowConfig.newWindowDimensions) >= 0); } // Window state is from a previous session: only allow fullscreen when we got updated or user wants to restore @@ -339,22 +337,14 @@ export class WindowsStateHandler extends Disposable { // Compute x/y based on display bounds // Note: important to use Math.round() because Electron does not seem to be too happy about // display coordinates that are not absolute numbers. - let state: INewWindowState = defaultWindowState(); + let state = defaultWindowState(); state.x = Math.round(displayToUse.bounds.x + (displayToUse.bounds.width / 2) - (state.width! / 2)); state.y = Math.round(displayToUse.bounds.y + (displayToUse.bounds.height / 2) - (state.height! / 2)); + // Check for newWindowDimensions setting and adjust accordingly const windowConfig = this.configurationService.getValue('window'); let ensureNoOverlap = true; - - // TODO@electron macOS: if the current window is fullscreen and native fullscreen - // is not disabled, always open a new window in fullscreen. This is a workaround - // for regression https://github.com/microsoft/vscode/issues/125122 - if (isMacintosh && windowConfig?.nativeFullScreen !== false && lastActive?.isFullScreen) { - state.mode = WindowMode.Fullscreen; - } - - // Adjust according to `newWindowDimensions` user setting - else if (windowConfig?.newWindowDimensions) { + if (windowConfig?.newWindowDimensions) { if (windowConfig.newWindowDimensions === 'maximized') { state.mode = WindowMode.Maximized; ensureNoOverlap = false; @@ -377,7 +367,7 @@ export class WindowsStateHandler extends Disposable { state = this.ensureNoOverlap(state); } - state.hasDefaultState = true; // flag as default state + (state as INewWindowState).hasDefaultState = true; // flag as default state return state; } 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.ts b/src/vs/server/connection.ts new file mode 100644 index 0000000000000..4d2fb53755a15 --- /dev/null +++ b/src/vs/server/connection.ts @@ -0,0 +1,244 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Coder Technologies. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as cp from 'child_process'; +import { VSBuffer } from 'vs/base/common/buffer'; +import { Emitter } from 'vs/base/common/event'; +import { FileAccess } from 'vs/base/common/network'; +import { INativeEnvironmentService } from 'vs/platform/environment/common/environment'; +import { ConsoleLogger } from 'vs/platform/log/common/log'; +import { IRemoteExtensionHostStartParams } from 'vs/platform/remote/common/remoteAgentConnection'; +import { getNlsConfiguration } from 'vs/server/nls'; +import { Protocol } from 'vs/server/protocol'; +import { IExtHostReadyMessage } from 'vs/workbench/services/extensions/common/extensionHostProtocol'; + +export abstract class Connection { + 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 readonly logger: ConsoleLogger; + + public constructor( + protected readonly protocol: Protocol, + public readonly name: string, + ) { + this.logger = new ConsoleLogger(); + + this.logger.debug('Connecting...'); + this.onClose(() => this.logger.debug('Closed')); + } + + public get offline(): number | undefined { + return this._offline; + } + + public reconnect(protocol: Protocol): void { + this.logger.debug(`${this.protocol.options.reconnectionToken} Reconnecting...`); + this._offline = undefined; + this.doReconnect(protocol); + } + + public dispose(reason?: string): void { + this.logger.debug(`${this.protocol.options.reconnectionToken} Disposing...`, reason); + if (!this.disposed) { + this.disposed = true; + this.doDispose(); + this._onClose.fire(); + } + } + + protected setOffline(): void { + this.logger.debug('Disconnected'); + if (!this._offline) { + this._offline = Date.now(); + } + } + + /** + * Set up the connection on a new socket. + */ + protected abstract doReconnect(protcol: Protocol): void; + + /** + * Dispose/destroy everything permanently. + */ + protected abstract doDispose(): void; +} + +/** + * Used for all the IPC channels. + */ +export class ManagementConnection extends Connection { + public constructor(protocol: Protocol) { + super(protocol, 'management'); + protocol.onDidDispose(() => this.dispose()); // Explicit close. + protocol.onSocketClose(() => this.setOffline()); // Might reconnect. + protocol.sendMessage({ type: 'ok' }); + } + + protected doDispose(): void { + this.protocol.destroy(); + } + + protected doReconnect(protocol: Protocol): void { + protocol.sendMessage({ type: 'ok' }); + this.protocol.beginAcceptReconnection(protocol.getSocket(), protocol.readEntireBuffer()); + this.protocol.endAcceptReconnection(); + protocol.dispose(); + } +} + +interface DisconnectedMessage { + type: 'VSCODE_EXTHOST_DISCONNECTED'; +} + +interface ConsoleMessage { + type: '__$console'; + // See bootstrap-fork.js#L135. + severity: 'log' | 'warn' | 'error'; + arguments: any[]; +} + +type ExtHostMessage = DisconnectedMessage | ConsoleMessage | IExtHostReadyMessage; + +export class ExtensionHostConnection extends Connection { + private process?: cp.ChildProcess; + + public constructor( + protocol: Protocol, + private readonly params: IRemoteExtensionHostStartParams, + private readonly environment: INativeEnvironmentService, + ) { + super(protocol, 'exthost'); + + protocol.sendMessage({ debugPort: this.params.port }); + const buffer = protocol.readEntireBuffer(); + const inflateBytes = protocol.inflateBytes; + protocol.dispose(); + protocol.getUnderlyingSocket().pause(); + + this.spawn(buffer, inflateBytes).then((p) => this.process = p); + } + + protected doDispose(): void { + this.protocol.destroy(); + if (this.process) { + this.process.kill(); + } + } + + protected doReconnect(protocol: Protocol): void { + protocol.sendMessage({ debugPort: this.params.port }); + const buffer = protocol.readEntireBuffer(); + const inflateBytes = protocol.inflateBytes; + protocol.dispose(); + protocol.getUnderlyingSocket().pause(); + this.protocol.setSocket(protocol.getSocket()); + + this.sendInitMessage(buffer, inflateBytes); + } + + private sendInitMessage(buffer: VSBuffer, inflateBytes: Uint8Array | undefined): void { + if (!this.process) { + throw new Error('Tried to initialize VS Code before spawning'); + } + + this.logger.debug('Sending socket'); + + // TODO: Do something with the debug port. + this.process.send({ + type: 'VSCODE_EXTHOST_IPC_SOCKET', + initialDataChunk: Buffer.from(buffer.buffer).toString('base64'), + skipWebSocketFrames: this.protocol.options.skipWebSocketFrames, + permessageDeflate: this.protocol.options.permessageDeflate, + inflateBytes: inflateBytes ? Buffer.from(inflateBytes).toString('base64') : undefined, + }, this.protocol.getUnderlyingSocket()); + } + + private async spawn(buffer: VSBuffer, inflateBytes: Uint8Array | undefined): Promise { + this.logger.debug('Getting NLS configuration...'); + const config = await getNlsConfiguration(this.params.language, this.environment.userDataPath); + this.logger.debug('Spawning extension host...'); + const proc = cp.fork( + FileAccess.asFileUri('bootstrap-fork', require).fsPath, + [ + // While not technically necessary, adding --type makes it easier to + // tell which process bootstrap-fork is executing. Can also do `pkill -f + // extensionHost`. Other spawns in the VS Code codebase behave + // similarly. + '--type=extensionHost', + // We can't use the symlinked uriTransformer in this same directory + // because it gets compiled into AMD syntax and this path is imported + // using Node's native require. + `--uriTransformerPath=${FileAccess.asFileUri('vs/server/uriTransformer.js', require).fsPath}` + ], + { + env: { + ...process.env, + VSCODE_AMD_ENTRYPOINT: 'vs/workbench/services/extensions/node/extensionHostProcess', + VSCODE_PIPE_LOGGING: 'true', + VSCODE_VERBOSE_LOGGING: 'true', + VSCODE_EXTHOST_WILL_SEND_SOCKET: 'true', + VSCODE_HANDLES_UNCAUGHT_ERRORS: 'true', + VSCODE_LOG_STACK: 'false', + VSCODE_LOG_LEVEL: process.env.LOG_LEVEL, + VSCODE_NLS_CONFIG: JSON.stringify(config), + VSCODE_PARENT_PID: String(process.pid), + }, + silent: true, + }, + ); + + proc.on('error', (error) => { + this.logger.error(`${this.protocol.options.reconnectionToken} Exited unexpectedly`, error); + this.dispose(); + }); + proc.on('exit', (code) => { + this.logger.debug(`${this.protocol.options.reconnectionToken} Exited`, code); + this.dispose(); + }); + if (proc.stdout && proc.stderr) { + proc.stdout.setEncoding('utf8').on('data', (d) => this.logger.info(d)); + proc.stderr.setEncoding('utf8').on('data', (d) => this.logger.error(d)); + } + + proc.on('message', (event: ExtHostMessage) => { + switch (event.type) { + case '__$console': + switch (event.severity) { + case 'log': + this.logger.info('console', event.arguments); + break; + case 'warn': + this.logger.warn('console', event.arguments); + break; + default: + this.logger.error('console', event.arguments); + } + break; + case 'VSCODE_EXTHOST_DISCONNECTED': + this.logger.debug('Got disconnected message'); + this.setOffline(); + break; + case 'VSCODE_EXTHOST_IPC_READY': + this.logger.debug('Handshake completed'); + this.sendInitMessage(buffer, inflateBytes); + break; + default: + this.logger.error('Unexpected message', event); + break; + } + }); + + this.logger.debug('Waiting for handshake...'); + return proc; + } +} diff --git a/src/vs/server/entry.ts b/src/vs/server/entry.ts new file mode 100644 index 0000000000000..a510f41c02ba4 --- /dev/null +++ b/src/vs/server/entry.ts @@ -0,0 +1,90 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { CodeServerMessage, VscodeMessage } from 'vs/base/common/ipc'; +import { enableCustomMarketplace } from 'vs/server/marketplace'; +import { VscodeServer } from 'vs/server/server'; +import { ConsoleMainLogger } from 'vs/platform/log/common/log'; + +const logger = new ConsoleMainLogger(); + +setUnexpectedErrorHandler((error) => { + logger.warn('Uncaught error', error instanceof Error ? error.message : error); +}); +enableCustomMarketplace(); +proxyAgent.monkeyPatch(true); + +/** + * Ensure we control when the process exits. + */ +const exit = process.exit; +process.exit = function (code?: number) { + logger.warn(`process.exit() was prevented: ${code || 'unknown code'}.`); +} as (code?: number) => never; + +// Kill VS Code if the parent process dies. +if (typeof process.env.CODE_SERVER_PARENT_PID !== 'undefined') { + const parentPid = parseInt(process.env.CODE_SERVER_PARENT_PID, 10); + setInterval(() => { + try { + process.kill(parentPid, 0); // Throws an exception if the process doesn't exist anymore. + } catch (e) { + exit(); + } + }, 5000); +} else { + logger.error('no parent process'); + exit(1); +} + +const vscode = new VscodeServer(); +const send = (message: VscodeMessage): void => { + if (!process.send) { + throw new Error('not spawned with IPC'); + } + process.send(message); +}; + +// Wait for the init message then start up VS Code. Subsequent messages will +// return new workbench options without starting a new instance. +process.on('message', async (message: CodeServerMessage, socket) => { + logger.debug('got message from code-server', message.type); + logger.trace('code-server message content', message); + switch (message.type) { + case 'init': + try { + const workbenchOptions = await vscode.initialize(message.options); + + send({ type: 'options', id: message.id, options: workbenchOptions }); + } catch (error) { + logger.error(error.message); + logger.error(error.stack); + exit(1); + } + break; + case 'cli': + try { + await vscode.cli(message.args); + exit(0); + } catch (error) { + logger.error(error.message); + logger.error(error.stack); + exit(1); + } + break; + case 'socket': + vscode.handleWebSocket(socket, message.query, message.permessageDeflate); + break; + } +}); +if (!process.send) { + logger.error('not spawned with IPC'); + exit(1); +} else { + // This lets the parent know the child is ready to receive messages. + send({ type: 'ready' }); +} diff --git a/src/vs/server/fork.js b/src/vs/server/fork.js new file mode 100644 index 0000000000000..915d9d8313572 --- /dev/null +++ b/src/vs/server/fork.js @@ -0,0 +1,8 @@ +/*--------------------------------------------------------------------------------------------- + * 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 a JS file otherwise when it gets compiled it turns into AMD +// syntax which will not work without the right loader. +require('../../bootstrap-amd').load('vs/server/entry'); 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/ipc.ts b/src/vs/server/ipc.ts new file mode 100644 index 0000000000000..3a56d24edfb78 --- /dev/null +++ b/src/vs/server/ipc.ts @@ -0,0 +1,66 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Coder Technologies. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as cp from 'child_process'; +import { Emitter } from 'vs/base/common/event'; + +enum ControlMessage { + okToChild = 'ok>', + okFromChild = 'ok<', +} + +interface RelaunchMessage { + type: 'relaunch'; + version: string; +} + +export type Message = RelaunchMessage; + +class IpcMain { + protected readonly _onMessage = new Emitter(); + public readonly onMessage = this._onMessage.event; + + public handshake(child?: cp.ChildProcess): Promise { + return new Promise((resolve, reject) => { + const target = child || process; + if (!target.send) { + throw new Error('Not spawned with IPC enabled'); + } + target.on('message', (message) => { + if (message === child ? ControlMessage.okFromChild : ControlMessage.okToChild) { + target.removeAllListeners(); + target.on('message', (msg) => this._onMessage.fire(msg)); + if (child) { + target.send!(ControlMessage.okToChild); + } + resolve(); + } + }); + if (child) { + child.once('error', reject); + child.once('exit', (code) => { + const error = new Error(`Unexpected exit with code ${code}`); + (error as any).code = code; + reject(error); + }); + } else { + target.send(ControlMessage.okFromChild); + } + }); + } + + public relaunch(version: string): void { + this.send({ type: 'relaunch', version }); + } + + private send(message: Message): void { + if (!process.send) { + throw new Error('Not a child process with IPC enabled'); + } + process.send(message); + } +} + +export const ipcMain = new IpcMain(); diff --git a/src/vs/server/marketplace.ts b/src/vs/server/marketplace.ts new file mode 100644 index 0000000000000..355e443d01382 --- /dev/null +++ b/src/vs/server/marketplace.ts @@ -0,0 +1,178 @@ +/*--------------------------------------------------------------------------------------------- + * 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', + 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..89b860307351d --- /dev/null +++ b/src/vs/server/protocol.ts @@ -0,0 +1,158 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Coder Technologies. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as net from 'net'; +import { VSBuffer } from 'vs/base/common/buffer'; +import { PersistentProtocol } from 'vs/base/parts/ipc/common/ipc.net'; +import { NodeSocket, WebSocketNodeSocket } from 'vs/base/parts/ipc/node/ipc.net'; +import { ConsoleLogger, LogLevel } from 'vs/platform/log/common/log'; +import { AuthRequest, ConnectionTypeRequest, HandshakeMessage } from 'vs/platform/remote/common/remoteAgentConnection'; + +export interface SocketOptions { + /** 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; + /** Whether to support compression (web socket only). */ + readonly permessageDeflate?: boolean; + /** + * 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. + */ + readonly inflateBytes?: VSBuffer; +} + +export class Protocol extends PersistentProtocol { + private readonly logger: ConsoleLogger; + + public constructor(socket: net.Socket, public readonly options: SocketOptions) { + super( + options.skipWebSocketFrames + ? new NodeSocket(socket) + : new WebSocketNodeSocket( + new NodeSocket(socket), + options.permessageDeflate || false, + options.inflateBytes || null, + // Always record inflate bytes if using permessage-deflate. + options.permessageDeflate || false, + ), + ); + + this.logger = new ConsoleLogger(LogLevel.Info); + } + + public getUnderlyingSocket(): net.Socket { + const socket = this.getSocket(); + return socket instanceof NodeSocket + ? socket.socket + : (socket as WebSocketNodeSocket).socket.socket; + } + + /** + * Perform a handshake to get a connection request. + */ + public handshake(): Promise { + this.logger.debug('Initiating handshake...'); + + return new Promise((resolve, reject) => { + const cleanup = () => { + handler.dispose(); + onClose.dispose(); + clearTimeout(timeout); + }; + + const onClose = this.onSocketClose(() => { + cleanup(); + this.logger.debug('Handshake failed'); + reject(new Error('Protocol socket closed unexpectedly')); + }); + + const timeout = setTimeout(() => { + cleanup(); + this.logger.debug('Handshake timed out'); + reject(new Error('Protocol handshake timed out')); + }, 10000); // Matches the client timeout. + + const handler = this.onControlMessage((rawMessage) => { + try { + const raw = rawMessage.toString(); + this.logger.trace('Got message', raw); + const message = JSON.parse(raw); + switch (message.type) { + case 'auth': + return this.authenticate(message); + case 'connectionType': + cleanup(); + this.logger.debug('Handshake completed'); + return resolve(message); + default: + throw new Error('Unrecognized message type'); + } + } catch (error) { + cleanup(); + reject(error); + } + }); + + // Kick off the handshake in case we missed the client's opening shot. + // TODO: Investigate why that message seems to get lost. + this.authenticate(); + }); + } + + /** + * TODO: This ignores the authentication process entirely for now. + */ + private authenticate(_?: AuthRequest): void { + this.sendMessage({ type: 'sign', data: '' }); + } + + /** + * TODO: implement. + */ + public tunnel(): void { + throw new Error('Tunnel is not implemented yet'); + } + + /** + * Send a handshake message. In the case of the extension host it should just + * send a debug port. + */ + public sendMessage(message: HandshakeMessage | { debugPort?: number | null }): void { + this.sendControl(VSBuffer.fromString(JSON.stringify(message))); + } + + /** + * Disconnect and dispose everything including the underlying socket. + */ + public destroy(reason?: string): void { + try { + if (reason) { + this.sendMessage({ type: 'error', reason }); + } + // 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.dispose(); // This disposes timers and socket event handlers. + this.getSocket().dispose(); // This will destroy() the socket. + } + + /** + * Get inflateBytes from the current socket. + */ + public get inflateBytes(): Uint8Array | undefined { + const socket = this.getSocket(); + return socket instanceof WebSocketNodeSocket + ? socket.recordedInflateBytes.buffer + : undefined; + } +} diff --git a/src/vs/server/server.ts b/src/vs/server/server.ts new file mode 100644 index 0000000000000..de9c4eb5d3a04 --- /dev/null +++ b/src/vs/server/server.ts @@ -0,0 +1,321 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { URI } from 'vs/base/common/uri'; +import { getMachineId } from 'vs/base/node/id'; +import { ClientConnectionEvent, IPCServer, IServerChannel, ProxyChannel } from 'vs/base/parts/ipc/common/ipc'; +import { main } from 'vs/code/node/cliProcessMain'; +import { Query, VscodeInitializationOptions, WorkbenchOptions } from 'vs/base/common/ipc'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { ConfigurationService } from 'vs/platform/configuration/common/configurationService'; +import { ExtensionHostDebugBroadcastChannel } from 'vs/platform/debug/common/extensionHostDebugIpc'; +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 { ConsoleLogger, 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 product from 'vs/platform/product/common/product'; +import { IProductService } from 'vs/platform/product/common/productService'; +import { ConnectionType, ConnectionTypeRequest } 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 ErrorTelemetry from 'vs/platform/telemetry/node/errorTelemetry'; +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 { ExtensionEnvironmentChannel, FileProviderChannel, TerminalProviderChannel } from 'vs/server/channel'; +import { Connection, ExtensionHostConnection, ManagementConnection } from 'vs/server/connection'; +import { TelemetryClient } from 'vs/server/insights'; +import { getLocaleFromConfig, getNlsConfiguration } from 'vs/server/nls'; +import { Protocol } 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 { PtyHostService } from 'vs/platform/terminal/node/ptyHostService'; +import { LogsDataCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/logsDataCleaner'; +import { createServerURITransformer } from 'vs/base/common/uriServer'; + +const commit = product.commit || 'development'; +const logger = new ConsoleMainLogger(); + +/** + * Handles client connections to a VSCode instance via IPC. + */ +export class VscodeServer { + public readonly _onDidClientConnect = new Emitter(); + public readonly onDidClientConnect = this._onDidClientConnect.event; + private readonly ipc = new IPCServer(this.onDidClientConnect); + + private readonly maxExtraOfflineConnections = 0; + private readonly connections = new Map>(); + + private readonly services = new ServiceCollection(); + private servicesPromise?: Promise; + + public async cli(args: NativeParsedArgs): Promise { + return main(args); + } + + public async initialize(options: VscodeInitializationOptions): Promise { + const transformer = createServerURITransformer(options.remoteAuthority); + if (!this.servicesPromise) { + this.servicesPromise = this.initializeServices(options.args); + } + await this.servicesPromise; + const environment = this.services.get(IEnvironmentService) as INativeEnvironmentService; + const startPath = options.startPath; + const parseUrl = (url: string): URI => { + // This might be a fully-specified URL or just a path. + try { + return URI.parse(url, true); + } catch (error) { + return URI.from({ + scheme: Schemas.vscodeRemote, + authority: options.remoteAuthority, + path: url, + }); + } + }; + return { + workbenchWebConfiguration: { + workspaceUri: startPath && startPath.workspace ? parseUrl(startPath.url) : undefined, + folderUri: startPath && !startPath.workspace ? parseUrl(startPath.url) : undefined, + remoteAuthority: options.remoteAuthority, + logLevel: getLogLevel(environment), + workspaceProvider: { + payload: [ + ['userDataPath', environment.userDataPath], + ['enableProposedApi', JSON.stringify(options.args['enable-proposed-api'] || [])] + ], + }, + }, + remoteUserDataUri: transformer.transformOutgoing(URI.file(environment.userDataPath)), + productConfiguration: product, + nlsConfiguration: await getNlsConfiguration(environment.args.locale || await getLocaleFromConfig(environment.userDataPath), environment.userDataPath), + commit, + }; + } + + public async handleWebSocket(socket: net.Socket, query: Query, permessageDeflate: boolean): Promise { + if (!query.reconnectionToken) { + throw new Error('Reconnection token is missing from query parameters'); + } + const protocol = new Protocol(socket, { + reconnectionToken: query.reconnectionToken, + reconnection: query.reconnection === 'true', + skipWebSocketFrames: query.skipWebSocketFrames === 'true', + permessageDeflate, + }); + try { + await this.connect(await protocol.handshake(), protocol); + } catch (error) { + protocol.destroy(error.message); + } + return true; + } + + private async connect(message: ConnectionTypeRequest, protocol: Protocol): Promise { + if (product.commit && message.commit !== product.commit) { + logger.warn(`Version mismatch (${message.commit} instead of ${product.commit})`); + } + + switch (message.desiredConnectionType) { + case ConnectionType.ExtensionHost: + case ConnectionType.Management: + // Initialize connection map for this type of connection. + if (!this.connections.has(message.desiredConnectionType)) { + this.connections.set(message.desiredConnectionType, new Map()); + } + const connections = this.connections.get(message.desiredConnectionType)!; + + const token = protocol.options.reconnectionToken; + let connection = connections.get(token); + if (protocol.options.reconnection && connection) { + return connection.reconnect(protocol); + } + + // This probably means the process restarted so the session was lost + // while the browser remained open. + if (protocol.options.reconnection) { + throw new Error(`Unable to reconnect; session no longer exists (${token})`); + } + + // 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. + if (message.desiredConnectionType === ConnectionType.Management) { + // The management connection is used by firing onDidClientConnect + // which makes the IPC server become aware of the connection. + connection = new ManagementConnection(protocol); + this._onDidClientConnect.fire({ + protocol, + onDidClientDisconnect: connection.onClose, + }); + } else { + // The extension host connection is used by spawning an extension host + // and passing the socket into it. + connection = new ExtensionHostConnection( + protocol, + { + language: 'en', + ...message.args, + }, + this.services.get(IEnvironmentService) as INativeEnvironmentService, + ); + } + connections.set(token, connection); + connection.onClose(() => connections.delete(token)); + + this.disposeOldOfflineConnections(connections); + logger.debug(`${connections.size} active ${connection.name} connection(s)`); + break; + case ConnectionType.Tunnel: + return protocol.tunnel(); + default: + throw new Error(`Unrecognized connection type ${message.desiredConnectionType}`); + } + } + + 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 = { _serviceBrand: undefined, ...product }; + 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) => { + logger.warn(error.message || error); + }))); + + const logService = new MultiplexLogService([ + new ConsoleLogger(getLogLevel(environmentService)), + new SpdLogLogger(RemoteExtensionLogFileName, path.join(environmentService.logsPath, `${RemoteExtensionLogFileName}.log`), false, getLogLevel(environmentService)) + ]); + 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); + + const machineId = await getMachineId(); + + await new Promise((resolve) => { + const instantiationService = new InstantiationService(this.services); + + instantiationService.invokeFunction((accessor) => { + 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, product.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)); + }); + }); + } +} 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/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 9dc02f2235abc..c5b64e74910aa 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -1460,7 +1460,7 @@ export namespace NotebookData { export function from(data: vscode.NotebookData): notebooks.NotebookDataDto { const res: notebooks.NotebookDataDto = { - metadata: Object.create(null), + metadata: data.metadata ?? Object.create(null), cells: [], }; for (let cell of data.cells) { 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..5cf3ddf6be5fa --- /dev/null +++ b/src/vs/workbench/browser/client.ts @@ -0,0 +1,171 @@ +/*--------------------------------------------------------------------------------------------- + * 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); + }); + }; + + 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 84c51a948ad9a..2defb9ee22cac 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/parts/titlebar/menubarControl.ts b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts index 144ec97e83099..87dbc5d320483 100644 --- a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts +++ b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts @@ -7,7 +7,7 @@ import { localize } from 'vs/nls'; import { IMenuService, MenuId, IMenu, SubmenuItemAction, registerAction2, Action2, MenuItemAction, MenuRegistry } from 'vs/platform/actions/common/actions'; import { registerThemingParticipant, IThemeService } from 'vs/platform/theme/common/themeService'; import { MenuBarVisibility, getTitleBarStyle, IWindowOpenable, getMenuBarVisibility } from 'vs/platform/windows/common/windows'; -import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IAction, Action, SubmenuAction, Separator } from 'vs/base/common/actions'; import { addDisposableListener, Dimension, EventType } from 'vs/base/browser/dom'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; @@ -98,7 +98,8 @@ MenuRegistry.appendMenuItem(MenuId.MenubarMainMenu, { original: 'Terminal', mnemonicTitle: localize({ key: 'mTerminal', comment: ['&& denotes a mnemonic'] }, "&&Terminal") }, - order: 7 + order: 7, + when: ContextKeyExpr.has('terminalProcessSupported') }); MenuRegistry.appendMenuItem(MenuId.MenubarMainMenu, { diff --git a/src/vs/workbench/browser/web.main.ts b/src/vs/workbench/browser/web.main.ts index ef1a454bf1c27..5e03179dd48a2 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)); 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/codeEditor/browser/saveParticipants.ts b/src/vs/workbench/contrib/codeEditor/browser/saveParticipants.ts index 5f4725e8f5631..825a93c9fc29d 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/saveParticipants.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/saveParticipants.ts @@ -233,9 +233,10 @@ class FormatOnSaveParticipant implements ITextFileSaveParticipant { const nestedProgress = new Progress<{ displayName?: string, extensionId?: ExtensionIdentifier }>(provider => { progress.report({ message: localize( - 'formatting', - "Running '{0}' Formatter ([configure](command:workbench.action.openSettings?%5B%22editor.formatOnSave%22%5D)).", - provider.displayName || provider.extensionId && provider.extensionId.value || '???' + { key: 'formatting2', comment: ['[configure]({1}) is a link. Only translate `configure`. Do not change brackets and parentheses or {1}'] }, + "Running '{0}' Formatter ([configure]({1})).", + provider.displayName || provider.extensionId && provider.extensionId.value || '???', + 'command:workbench.action.openSettings?%5B%22editor.formatOnSave%22%5D' ) }); }); @@ -336,9 +337,10 @@ class CodeActionOnSaveParticipant implements ITextFileSaveParticipant { private _report(): void { progress.report({ message: localize( - 'codeaction.get', - "Getting code actions from '{0}' ([configure](command:workbench.action.openSettings?%5B%22editor.codeActionsOnSave%22%5D)).", - [...this._names].map(name => `'${name}'`).join(', ') + { key: 'codeaction.get2', comment: ['[configure]({1}) is a link. Only translate `configure`. Do not change brackets and parentheses or {1}'] }, + "Getting code actions from '{0}' ([configure]({1})).", + [...this._names].map(name => `'${name}'`).join(', '), + 'command:workbench.action.openSettings?%5B%22editor.codeActionsOnSave%22%5D' ) }); } diff --git a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts index b5a853fa432c8..b8ac11b437924 100644 --- a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts @@ -50,7 +50,6 @@ import { registerColors } from 'vs/workbench/contrib/debug/browser/debugColors'; import { DebugEditorContribution } from 'vs/workbench/contrib/debug/browser/debugEditorContribution'; import { FileAccess } from 'vs/base/common/network'; import * as icons from 'vs/workbench/contrib/debug/browser/debugIcons'; -import { IsWebContext } from 'vs/platform/contextkey/common/contextkeys'; const debugCategory = nls.localize('debugCategory', "Debug"); registerColors(); @@ -186,7 +185,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarMainMenu, { original: 'Run', mnemonicTitle: nls.localize({ key: 'mRun', comment: ['&& denotes a mnemonic'] }, "&&Run") }, - when: ContextKeyExpr.or(CONTEXT_DEBUGGERS_AVAILABLE, IsWebContext.toNegated()), + when: ContextKeyExpr.or(CONTEXT_DEBUGGERS_AVAILABLE), order: 6 }); diff --git a/src/vs/workbench/contrib/debug/browser/debugAdapterManager.ts b/src/vs/workbench/contrib/debug/browser/debugAdapterManager.ts index 46760eab9bd5b..f3e32f051f075 100644 --- a/src/vs/workbench/contrib/debug/browser/debugAdapterManager.ts +++ b/src/vs/workbench/contrib/debug/browser/debugAdapterManager.ts @@ -28,8 +28,11 @@ import { IModeService } from 'vs/editor/common/services/modeService'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import Severity from 'vs/base/common/severity'; import { TaskDefinitionRegistry } from 'vs/workbench/contrib/tasks/common/taskDefinitionRegistry'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; const jsonRegistry = Registry.as(JSONExtensions.JSONContribution); +const DEBUGGERS_AVAILABLE_KEY = 'debug.debuggersavailable'; + export class AdapterManager implements IAdapterManager { private debuggers: Debugger[]; @@ -49,12 +52,15 @@ export class AdapterManager implements IAdapterManager { @IExtensionService private readonly extensionService: IExtensionService, @IContextKeyService contextKeyService: IContextKeyService, @IModeService private readonly modeService: IModeService, - @IDialogService private readonly dialogService: IDialogService + @IDialogService private readonly dialogService: IDialogService, + @IStorageService private readonly storageService: IStorageService ) { this.adapterDescriptorFactories = []; this.debuggers = []; this.registerListeners(); + const debuggersAvailable = this.storageService.getBoolean(DEBUGGERS_AVAILABLE_KEY, StorageScope.WORKSPACE, false); this.debuggersAvailable = CONTEXT_DEBUGGERS_AVAILABLE.bindTo(contextKeyService); + this.debuggersAvailable.set(debuggersAvailable); } private registerListeners(): void { @@ -158,6 +164,7 @@ export class AdapterManager implements IAdapterManager { registerDebugAdapterFactory(debugTypes: string[], debugAdapterLauncher: IDebugAdapterFactory): IDisposable { debugTypes.forEach(debugType => this.debugAdapterFactories.set(debugType, debugAdapterLauncher)); this.debuggersAvailable.set(this.debugAdapterFactories.size > 0); + this.storageService.store(DEBUGGERS_AVAILABLE_KEY, this.debugAdapterFactories.size > 0, StorageScope.WORKSPACE, StorageTarget.MACHINE); this._onDidRegisterDebugger.fire(); return { diff --git a/src/vs/workbench/contrib/debug/node/terminals.ts b/src/vs/workbench/contrib/debug/node/terminals.ts index a4ef4b4c84f47..4155cf3bfdd18 100644 --- a/src/vs/workbench/contrib/debug/node/terminals.ts +++ b/src/vs/workbench/contrib/debug/node/terminals.ts @@ -8,7 +8,6 @@ import * as platform from 'vs/base/common/platform'; import { getDriveLetter } from 'vs/base/common/extpath'; import { LinuxExternalTerminalService, MacExternalTerminalService, WindowsExternalTerminalService } from 'vs/platform/externalTerminal/node/externalTerminalService'; import { IExternalTerminalService } from 'vs/platform/externalTerminal/common/externalTerminal'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ExtHostConfigProvider } from 'vs/workbench/api/common/extHostConfiguration'; @@ -36,11 +35,11 @@ let externalTerminalService: IExternalTerminalService | undefined = undefined; export function runInExternalTerminal(args: DebugProtocol.RunInTerminalRequestArguments, configProvider: ExtHostConfigProvider): Promise { if (!externalTerminalService) { if (platform.isWindows) { - externalTerminalService = new WindowsExternalTerminalService(undefined); + externalTerminalService = new WindowsExternalTerminalService(); } else if (platform.isMacintosh) { - externalTerminalService = new MacExternalTerminalService(undefined); + externalTerminalService = new MacExternalTerminalService(); } else if (platform.isLinux) { - externalTerminalService = new LinuxExternalTerminalService(undefined); + externalTerminalService = new LinuxExternalTerminalService(); } else { throw new Error('external terminals not supported on this platform'); } diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts index 9cba26e468397..82e67722ad471 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'; @@ -60,6 +60,7 @@ import { isIOS, 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 { WorkspaceTrustContext } from 'vs/workbench/services/workspaces/common/workspaceTrust'; +import { textLinkForeground } from 'vs/platform/theme/common/colorRegistry'; const SearchMarketplaceExtensionsContext = new RawContextKey('searchMarketplaceExtensions', false); const SearchIntalledExtensionsContext = new RawContextKey('searchInstalledExtensions', false); @@ -525,6 +526,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/externalTerminal/browser/externalTerminal.contribution.ts b/src/vs/workbench/contrib/externalTerminal/browser/externalTerminal.contribution.ts index d817ac6417005..daa8d8ec03a2c 100644 --- a/src/vs/workbench/contrib/externalTerminal/browser/externalTerminal.contribution.ts +++ b/src/vs/workbench/contrib/externalTerminal/browser/externalTerminal.contribution.ts @@ -42,7 +42,8 @@ CommandsRegistry.registerCommand({ return fileService.resolveAll(resources.map(r => ({ resource: r }))).then(async stats => { const targets = distinct(stats.filter(data => data.success)); // Always use integrated terminal when using a remote - const useIntegratedTerminal = remoteAgentService.getConnection() || configurationService.getValue().terminal.explorerKind === 'integrated'; + const config = configurationService.getValue(); + const useIntegratedTerminal = remoteAgentService.getConnection() || config.terminal.explorerKind === 'integrated'; if (useIntegratedTerminal) { // TODO: Use uri for cwd in createterminal const opened: { [path: string]: boolean } = {}; @@ -71,7 +72,7 @@ CommandsRegistry.registerCommand({ }); } else { distinct(targets.map(({ stat }) => stat!.isDirectory ? stat!.resource.fsPath : dirname(stat!.resource.fsPath))).forEach(cwd => { - terminalService!.openTerminal(cwd); + terminalService!.openTerminal(config.terminal.external, cwd); }); } }); diff --git a/src/vs/workbench/contrib/externalTerminal/electron-sandbox/externalTerminal.contribution.ts b/src/vs/workbench/contrib/externalTerminal/electron-sandbox/externalTerminal.contribution.ts index e63a96b7c60c4..b7acdba1aec5f 100644 --- a/src/vs/workbench/contrib/externalTerminal/electron-sandbox/externalTerminal.contribution.ts +++ b/src/vs/workbench/contrib/externalTerminal/electron-sandbox/externalTerminal.contribution.ts @@ -5,7 +5,7 @@ import * as nls from 'vs/nls'; import * as paths from 'vs/base/common/path'; -import { DEFAULT_TERMINAL_OSX, IExternalTerminalService } from 'vs/platform/externalTerminal/common/externalTerminal'; +import { DEFAULT_TERMINAL_OSX, IExternalTerminalService, IExternalTerminalSettings } from 'vs/platform/externalTerminal/common/externalTerminal'; import { MenuId, MenuRegistry } from 'vs/platform/actions/common/actions'; import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import { KEYBINDING_CONTEXT_TERMINAL_NOT_FOCUSED } from 'vs/workbench/contrib/terminal/common/terminal'; @@ -17,6 +17,7 @@ import { IConfigurationRegistry, Extensions, ConfigurationScope } from 'vs/platf import { Registry } from 'vs/platform/registry/common/platform'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IExternalTerminalMainService } from 'vs/platform/externalTerminal/electron-sandbox/externalTerminalMainService'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; const OPEN_NATIVE_CONSOLE_COMMAND_ID = 'workbench.action.terminal.openNativeConsole'; KeybindingsRegistry.registerCommandAndKeybindingRule({ @@ -28,18 +29,20 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ const historyService = accessor.get(IHistoryService); // Open external terminal in local workspaces const terminalService = accessor.get(IExternalTerminalService); + const configurationService = accessor.get(IConfigurationService); const root = historyService.getLastActiveWorkspaceRoot(Schemas.file); + const config = configurationService.getValue('terminal.external'); if (root) { - terminalService.openTerminal(root.fsPath); + terminalService.openTerminal(config, root.fsPath); } else { // Opens current file's folder, if no folder is open in editor const activeFile = historyService.getLastActiveFile(Schemas.file); if (activeFile) { - terminalService.openTerminal(paths.dirname(activeFile.fsPath)); + terminalService.openTerminal(config, paths.dirname(activeFile.fsPath)); } else { const pathService = accessor.get(IPathService); const userHome = await pathService.userHome(); - terminalService.openTerminal(userHome.fsPath); + terminalService.openTerminal(config, userHome.fsPath); } } } diff --git a/src/vs/workbench/contrib/files/browser/explorerViewlet.ts b/src/vs/workbench/contrib/files/browser/explorerViewlet.ts index f752b8790e5a2..3e91e275941c4 100644 --- a/src/vs/workbench/contrib/files/browser/explorerViewlet.ts +++ b/src/vs/workbench/contrib/files/browser/explorerViewlet.ts @@ -303,9 +303,15 @@ viewsRegistry.registerViewWelcomeContent(EmptyView.ID, { order: 1 }); +// NOTE@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/notebook/browser/contrib/coreActions.ts b/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts index df23ae81bf6b4..dfb33a2cc08e5 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts @@ -1874,14 +1874,18 @@ registerAction2(class NotebookConfigureLayoutAction extends Action2 { group: 'notebookLayout', when: ContextKeyExpr.and( NOTEBOOK_IS_ACTIVE_EDITOR, - ContextKeyExpr.notEquals('config.notebook.globalToolbar', true) + ContextKeyExpr.notEquals('config.notebook.globalToolbar', true), + ContextKeyExpr.equals('config.notebook.experimental.openGettingStarted', true) ), order: 0 }, { id: MenuId.NotebookToolbar, group: 'notebookLayout', - when: ContextKeyExpr.equals('config.notebook.globalToolbar', true), + when: ContextKeyExpr.and( + ContextKeyExpr.equals('config.notebook.globalToolbar', true), + ContextKeyExpr.equals('config.notebook.experimental.openGettingStarted', true) + ), order: 0 } ] diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/statusBar/executionStatusBarItemController.ts b/src/vs/workbench/contrib/notebook/browser/contrib/statusBar/executionStatusBarItemController.ts index e32ba0518d534..5b5a85e9e03b0 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/statusBar/executionStatusBarItemController.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/statusBar/executionStatusBarItemController.ts @@ -13,7 +13,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { themeColorFromId } from 'vs/platform/theme/common/themeService'; import { ICellVisibilityChangeEvent, NotebookVisibleCellObserver } from 'vs/workbench/contrib/notebook/browser/contrib/statusBar/notebookVisibleCellObserver'; -import { EXECUTE_CELL_COMMAND_ID, ICellViewModel, INotebookEditor, INotebookEditorContribution, NOTEBOOK_CELL_EXECUTION_STATE, QUIT_EDIT_CELL_COMMAND_ID } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { EXECUTE_CELL_COMMAND_ID, ICellViewModel, INotebookEditor, INotebookEditorContribution, NOTEBOOK_CELL_EXECUTION_STATE, NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_CELL_TYPE, NOTEBOOK_EDITOR_FOCUSED, QUIT_EDIT_CELL_COMMAND_ID } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { registerNotebookContribution } from 'vs/workbench/contrib/notebook/browser/notebookEditorExtensions'; import { cellStatusIconError, cellStatusIconSuccess } from 'vs/workbench/contrib/notebook/browser/notebookEditorWidget'; import { NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; @@ -232,7 +232,8 @@ class TimerCellStatusBarHelper extends Disposable { */ class KeybindingPlaceholderStatusBarHelper extends Disposable { private _currentItemIds: string[] = []; - private readonly _contextKeyService: IContextKeyService; + private readonly _codeContextKeyService: IContextKeyService; + private readonly _markupContextKeyService: IContextKeyService; constructor( private readonly _notebookViewModel: NotebookViewModel, @@ -243,12 +244,20 @@ class KeybindingPlaceholderStatusBarHelper extends Disposable { super(); // Create a fake ContextKeyService, and look up the keybindings within this context. - this._contextKeyService = this._register(_contextKeyService.createScoped(document.createElement('div'))); - InputFocusedContext.bindTo(this._contextKeyService).set(true); - EditorContextKeys.editorTextFocus.bindTo(this._contextKeyService).set(true); - EditorContextKeys.focus.bindTo(this._contextKeyService).set(true); - EditorContextKeys.textInputFocus.bindTo(this._contextKeyService).set(true); - NOTEBOOK_CELL_EXECUTION_STATE.bindTo(this._contextKeyService).set('idle'); + const commonContextKeyService = this._register(_contextKeyService.createScoped(document.createElement('div'))); + InputFocusedContext.bindTo(commonContextKeyService).set(true); + EditorContextKeys.editorTextFocus.bindTo(commonContextKeyService).set(true); + EditorContextKeys.focus.bindTo(commonContextKeyService).set(true); + EditorContextKeys.textInputFocus.bindTo(commonContextKeyService).set(true); + NOTEBOOK_CELL_EXECUTION_STATE.bindTo(commonContextKeyService).set('idle'); + NOTEBOOK_CELL_LIST_FOCUSED.bindTo(commonContextKeyService).set(true); + NOTEBOOK_EDITOR_FOCUSED.bindTo(commonContextKeyService).set(true); + + this._codeContextKeyService = this._register(commonContextKeyService.createScoped(document.createElement('div'))); + NOTEBOOK_CELL_TYPE.bindTo(this._codeContextKeyService).set('code'); + + this._markupContextKeyService = this._register(commonContextKeyService.createScoped(document.createElement('div'))); + NOTEBOOK_CELL_TYPE.bindTo(this._markupContextKeyService).set('markup'); this._update(); this._register(this._cell.model.onDidChangeInternalMetadata(() => this._update())); @@ -268,14 +277,14 @@ class KeybindingPlaceholderStatusBarHelper extends Disposable { let text: string; if (cell.cellKind === CellKind.Code) { - const keybinding = this._keybindingService.lookupKeybinding(EXECUTE_CELL_COMMAND_ID, this._contextKeyService)?.getLabel(); + const keybinding = this._keybindingService.lookupKeybinding(EXECUTE_CELL_COMMAND_ID, this._codeContextKeyService)?.getLabel(); if (!keybinding) { return []; } text = localize('notebook.cell.status.codeExecuteTip', "Press {0} to execute cell", keybinding); } else { - const keybinding = this._keybindingService.lookupKeybinding(QUIT_EDIT_CELL_COMMAND_ID, this._contextKeyService)?.getLabel(); + const keybinding = this._keybindingService.lookupKeybinding(QUIT_EDIT_CELL_COMMAND_ID, this._markupContextKeyService)?.getLabel(); if (!keybinding) { return []; } diff --git a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts index 5c000e4c0800a..4bacef840b79a 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts @@ -698,7 +698,7 @@ configurationRegistry.registerConfiguration({ [ConsolidatedRunButton]: { description: nls.localize('notebook.consolidatedRunButton.description', "Control whether extra actions are shown in a dropdown next to the run button."), type: 'boolean', - default: true, + default: false, tags: ['notebookLayout'] }, [NotebookCellEditorOptionsCustomizations]: editorOptionsCustomizationSchema diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts index e68eac4a35cac..ec91e32c8f430 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts @@ -1071,7 +1071,7 @@ async function webviewPreloads(style: PreloadStyles, options: PreloadOptions, re } } - async function createMarkdownPreview(cellId: string, content: string, top: number): Promise { + async function createMarkdownPreview(cellId: string, content: string, top: number, visible: boolean): Promise { const container = document.getElementById('container')!; const cellContainer = document.createElement('div'); @@ -1084,6 +1084,7 @@ async function webviewPreloads(style: PreloadStyles, options: PreloadOptions, re cellContainer.id = cellId; cellContainer.classList.add('preview'); + cellContainer.style.visibility = visible ? 'visible' : 'hidden'; cellContainer.style.position = 'absolute'; cellContainer.style.top = top + 'px'; container.appendChild(cellContainer); @@ -1157,12 +1158,11 @@ async function webviewPreloads(style: PreloadStyles, options: PreloadOptions, re await Promise.all(update.map(async cell => { let container = document.getElementById(cell.cellId); if (container) { + container.style.visibility = cell.visible ? 'visible' : 'hidden'; await updateMarkdownPreview(container, cell.cellId, cell.content); } else { - container = await createMarkdownPreview(cell.cellId, cell.content, cell.offset); + container = await createMarkdownPreview(cell.cellId, cell.content, cell.offset, cell.visible); } - - container.style.visibility = cell.visible ? 'visible' : 'hidden'; })); } diff --git a/src/vs/workbench/contrib/notebook/common/notebookEditorModelResolverServiceImpl.ts b/src/vs/workbench/contrib/notebook/common/notebookEditorModelResolverServiceImpl.ts index 17babc5a8f15e..0b5f0490b45a5 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookEditorModelResolverServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookEditorModelResolverServiceImpl.ts @@ -181,12 +181,15 @@ export class NotebookModelResolverServiceImpl implements INotebookEditorModelRes } const reference = this._data.acquire(resource.toString(), viewType); - const model = await reference.object; - return { - object: model, - dispose() { - reference.dispose(); - } - }; + try { + const model = await reference.object; + return { + object: model, + dispose() { reference.dispose(); } + }; + } catch (err) { + reference.dispose(); + throw err; + } } } diff --git a/src/vs/workbench/contrib/remote/browser/remoteExplorer.ts b/src/vs/workbench/contrib/remote/browser/remoteExplorer.ts index 33a5e367bc0b8..0ba9d54a78e57 100644 --- a/src/vs/workbench/contrib/remote/browser/remoteExplorer.ts +++ b/src/vs/workbench/contrib/remote/browser/remoteExplorer.ts @@ -314,7 +314,9 @@ class OnAutoForwardedAction extends Disposable { } private linkMessage() { - return nls.localize('remote.tunnelsView.notificationLink', "[See all forwarded ports](command:{0}.focus)", TunnelPanel.ID); + return nls.localize( + { key: 'remote.tunnelsView.notificationLink2', comment: ['[See all forwarded ports]({0}) is a link. Only translate `See all forwarded ports`. Do not change brackets and parentheses or {0}'] }, + "[See all forwarded ports]({0})", `command:${TunnelPanel.ID}.focus`); } private async showNotification(tunnel: RemoteTunnel) { @@ -486,7 +488,15 @@ class ProcAutomaticPortForwarding extends Disposable { ) { super(); this.notifier = new OnAutoForwardedAction(notificationService, remoteExplorerService, openerService, externalOpenerService, tunnelService, hostService, logService); - this._register(configurationService.onDidChangeConfiguration(async (e) => { + this.initialize(); + } + + private async initialize() { + if (!this.remoteExplorerService.tunnelModel.environmentTunnelsSet) { + await new Promise(resolve => this.remoteExplorerService.tunnelModel.onEnvironmentTunnelsSet(() => resolve())); + } + + this._register(this.configurationService.onDidChangeConfiguration(async (e) => { if (e.affectsConfiguration(PORT_AUTO_FORWARD_SETTING)) { await this.startStopCandidateListener(); } @@ -522,14 +532,13 @@ class ProcAutomaticPortForwarding extends Disposable { this.portsFeatures.dispose(); } - if (!this.remoteExplorerService.tunnelModel.environmentTunnelsSet) { - await new Promise(resolve => this.remoteExplorerService.tunnelModel.onEnvironmentTunnelsSet(() => resolve())); - } - // Capture list of starting candidates so we don't auto forward them later. await this.setInitialCandidates(); - this.candidateListener = this._register(this.remoteExplorerService.tunnelModel.onCandidatesChanged(this.handleCandidateUpdate, this)); + // Need to check the setting again, since it may have changed while we waited for the initial candidates to be set. + if (this.configurationService.getValue(PORT_AUTO_FORWARD_SETTING)) { + this.candidateListener = this._register(this.remoteExplorerService.tunnelModel.onCandidatesChanged(this.handleCandidateUpdate, this)); + } } private async setInitialCandidates() { diff --git a/src/vs/workbench/contrib/remote/browser/remoteIndicator.ts b/src/vs/workbench/contrib/remote/browser/remoteIndicator.ts index cd9414e6fa577..a1f3501b511fa 100644 --- a/src/vs/workbench/contrib/remote/browser/remoteIndicator.ts +++ b/src/vs/workbench/contrib/remote/browser/remoteIndicator.ts @@ -301,7 +301,11 @@ export class RemoteStatusIndicator extends Disposable implements IWorkbenchContr const workspaceLabel = this.getWorkspaceLabel(); if (workspaceLabel) { const toolTip: IMarkdownString = { - value: nls.localize('workspace.tooltip', "Virtual workspace on {0}\n\n[Some features](command:{1}) are not available for resources located on a virtual file system.", workspaceLabel, LIST_WORKSPACE_UNSUPPORTED_EXTENSIONS_COMMAND_ID), + value: nls.localize( + { key: 'workspace.tooltip2', comment: ['{0} is a remote location name, e.g. GitHub', '[Some features]({1}) is a link. Only translate `Some features`. Do not change brackets and parentheses or {1}'] }, + "Virtual workspace on {0}\n\n[Some features]({1}) are not available for resources located on a virtual file system.", + workspaceLabel, `command:${LIST_WORKSPACE_UNSUPPORTED_EXTENSIONS_COMMAND_ID}` + ), isTrusted: true }; this.renderRemoteStatusIndicator(`$(remote) ${truncate(workspaceLabel, RemoteStatusIndicator.REMOTE_STATUS_LABEL_MAX_LENGTH)}`, toolTip); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 2377fc9bbd048..611101f004621 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -68,6 +68,8 @@ const NUMBER_OF_FRAMES_TO_MEASURE = 20; const SHOULD_PROMPT_FOR_PROFILE_MIGRATION_KEY = 'terminals.integrated.profile-migration'; +let migrationMessageShown = false; + const enum Constants { /** * The maximum amount of milliseconds to wait for a container before starting to create the @@ -143,8 +145,6 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { private _hasHadInput: boolean; - messageShown: boolean = false; - readonly statusList: ITerminalStatusList = new TerminalStatusList(); disableLayout: boolean = false; get instanceId(): number { return this._instanceId; } @@ -281,6 +281,12 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { this._terminalProfileResolverService.resolveIcon(this._shellLaunchConfig, OS); } + // When a custom pty is used set the name immediately so it gets passed over to the exthost + // and is available when Pseudoterminal.open fires. + if (this.shellLaunchConfig.customPtyImplementation) { + this.setTitle(this._shellLaunchConfig.name, TitleEventSource.Api); + } + this._initDimensions(); this._createProcessManager(); @@ -362,7 +368,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { const shouldMigrateToProfile = (!!this._configurationService.getValue(TerminalSettingPrefix.Shell + platform) || !!this._configurationService.inspect(TerminalSettingPrefix.ShellArgs + platform).userValue) && !!this._configurationService.getValue(TerminalSettingPrefix.DefaultProfile + platform); - if (shouldMigrateToProfile && this._storageService.getBoolean(SHOULD_PROMPT_FOR_PROFILE_MIGRATION_KEY, StorageScope.WORKSPACE, true) && !this.messageShown) { + if (shouldMigrateToProfile && this._storageService.getBoolean(SHOULD_PROMPT_FOR_PROFILE_MIGRATION_KEY, StorageScope.WORKSPACE, true) && !migrationMessageShown) { this._notificationService.prompt( Severity.Info, nls.localize('terminalProfileMigration', "The terminal is using deprecated shell/shellArgs settings, do you want to migrate it to a profile?"), @@ -377,7 +383,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { await this._configurationService.updateValue(TerminalSettingPrefix.DefaultProfile + platform, profile); this._logService.trace(`migrated from shell/shellArgs, using existing profile ${profile}`); } else { - const profiles = this._configurationService.inspect<{ [key: string]: ITerminalProfileObject }>(TerminalSettingPrefix.Profiles + platform).userValue || {}; + const profiles = { ...this._configurationService.inspect>(TerminalSettingPrefix.Profiles + platform).userValue } || {}; const profileConfig: ITerminalProfileObject = { path: profile.path }; if (profile.args) { profileConfig.args = profile.args; @@ -396,7 +402,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { neverShowAgain: { id: SHOULD_PROMPT_FOR_PROFILE_MIGRATION_KEY, scope: NeverShowAgainScope.WORKSPACE } } ); - this.messageShown = true; + migrationMessageShown = true; } } diff --git a/src/vs/workbench/contrib/webview/browser/baseWebviewElement.ts b/src/vs/workbench/contrib/webview/browser/baseWebviewElement.ts index f1b6fb31cc1f6..05c9310208f71 100644 --- a/src/vs/workbench/contrib/webview/browser/baseWebviewElement.ts +++ b/src/vs/workbench/contrib/webview/browser/baseWebviewElement.ts @@ -247,7 +247,7 @@ export abstract class BaseWebview extends Disposable { scheme: entry.scheme, authority: entry.authority, path: decodeURIComponent(entry.path), // This gets re-encoded - query: entry.query, + query: entry.query ? decodeURIComponent(entry.query) : entry.query, }); this.loadResource(entry.id, uri, entry.ifNoneMatch); } catch (e) { diff --git a/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.ts b/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.ts index b38ca2cfec45c..94175cb868a30 100644 --- a/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.ts +++ b/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.ts @@ -5,7 +5,7 @@ import 'vs/css!./gettingStarted'; import { localize } from 'vs/nls'; -import { IInstantiationService, optional } from 'vs/platform/instantiation/common/instantiation'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IEditorInputSerializer, IEditorOpenContext } from 'vs/workbench/common/editor'; import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { assertIsDefined } from 'vs/base/common/types'; @@ -17,7 +17,7 @@ import { IThemeService, registerThemingParticipant, ThemeIcon } from 'vs/platfor import { welcomePageBackground, welcomePageProgressBackground, welcomePageProgressForeground, welcomePageTileBackground, welcomePageTileHoverBackground, welcomePageTileShadow } from 'vs/workbench/contrib/welcome/page/browser/welcomePageColors'; import { activeContrastBorder, buttonBackground, buttonForeground, buttonHoverBackground, contrastBorder, descriptionForeground, focusBorder, foreground, textLinkActiveForeground, textLinkForeground } from 'vs/platform/theme/common/colorRegistry'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { ITelemetryService, lastSessionDateStorageKey } from 'vs/platform/telemetry/common/telemetry'; +import { firstSessionDateStorageKey, ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; import { gettingStartedCheckedCodicon, gettingStartedUncheckedCodicon } from 'vs/workbench/contrib/welcome/gettingStarted/browser/gettingStartedIcons'; import { IOpenerService, matchesScheme } from 'vs/platform/opener/common/opener'; @@ -27,7 +27,6 @@ import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storag import { CancellationToken } from 'vs/base/common/cancellation'; import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ContextKeyExpr, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; -import { ITASExperimentService } from 'vs/workbench/services/experiment/common/experimentService'; import { IRecentFolder, IRecentlyOpened, IRecentWorkspace, isRecentFolder, isRecentWorkspace, IWorkspacesService } from 'vs/platform/workspaces/common/workspaces'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { onUnexpectedError } from 'vs/base/common/errors'; @@ -103,7 +102,6 @@ export class GettingStartedPage extends EditorPane { private container: HTMLElement; private contextService: IContextKeyService; - private tasExperimentService?: ITASExperimentService; private previousSelection?: string; private recentlyOpened: Promise; private selectedStepElement?: HTMLDivElement; @@ -144,7 +142,6 @@ export class GettingStartedPage extends EditorPane { @IHostService private readonly hostService: IHostService, @IWebviewService private readonly webviewService: IWebviewService, @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, - @optional(ITASExperimentService) tasExperimentService: ITASExperimentService, ) { super(GettingStartedPage.ID, telemetryService, themeService, storageService); @@ -158,9 +155,6 @@ export class GettingStartedPage extends EditorPane { this.stepMediaComponent = $('.getting-started-media'); this.stepMediaComponent.id = generateUuid(); - - this.tasExperimentService = tasExperimentService; - this.contextService = this._register(contextService.createScoped(this.container)); inGettingStartedContext.bindTo(this.contextService).set(true); @@ -797,28 +791,19 @@ export class GettingStartedPage extends EditorPane { const someStepsComplete = this.gettingStartedCategories.some(categry => categry.content.type === 'steps' && categry.content.stepsComplete); if (!someStepsComplete && !this.hasScrolledToFirstCategory) { - const fistContentBehaviour = - !this.storageService.get(lastSessionDateStorageKey, StorageScope.GLOBAL) // isNewUser ? - ? 'openToFirstCategory' - : await Promise.race([ - this.tasExperimentService?.getTreatment<'index' | 'openToFirstCategory'>('GettingStartedFirstContent'), - new Promise<'index'>(resolve => setTimeout(() => resolve('index'), 1000)), - ]); - - if (this.gettingStartedCategories.some(category => category.content.type === 'steps' && category.content.stepsComplete)) { - this.setSlide('categories'); - return; - } else { - if (fistContentBehaviour === 'openToFirstCategory') { - const first = this.gettingStartedCategories.find(category => category.content.type === 'steps'); - this.hasScrolledToFirstCategory = true; - if (first) { - this.currentCategory = first; - this.editorInput.selectedCategory = this.currentCategory?.id; - this.buildCategorySlide(this.editorInput.selectedCategory); - this.setSlide('details'); - return; - } + const firstSessionDateString = this.storageService.get(firstSessionDateStorageKey, StorageScope.GLOBAL) || new Date().toUTCString(); + const daysSinceFirstSession = ((+new Date()) - (+new Date(firstSessionDateString))) / 1000 / 60 / 60 / 24; + const fistContentBehaviour = daysSinceFirstSession < 1 ? 'openToFirstCategory' : 'index'; + + if (fistContentBehaviour === 'openToFirstCategory') { + const first = this.gettingStartedCategories.find(category => category.content.type === 'steps'); + this.hasScrolledToFirstCategory = true; + if (first) { + this.currentCategory = first; + this.editorInput.selectedCategory = this.currentCategory?.id; + this.buildCategorySlide(this.editorInput.selectedCategory); + this.setSlide('details'); + return; } } } diff --git a/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStartedExtensionPoint.ts b/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStartedExtensionPoint.ts index d3cc402e9055a..c02fe90975990 100644 --- a/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStartedExtensionPoint.ts +++ b/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStartedExtensionPoint.ts @@ -7,6 +7,8 @@ import { localize } from 'vs/nls'; import { IStartEntry, IWalkthrough } from 'vs/platform/extensions/common/extensions'; import { ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry'; +const titleTranslated = localize('title', "Title"); + export const walkthroughsExtensionPoint = ExtensionsRegistry.registerExtensionPoint({ extensionPoint: 'walkthroughs', jsonSchema: { @@ -64,10 +66,10 @@ export const walkthroughsExtensionPoint = ExtensionsRegistry.registerExtensionPo }, description: { type: 'string', - description: localize('walkthroughs.steps.description', "Description of step. Supports ``preformatted``, __italic__, and **bold** text. Use markdown-style links for commands or external links: [Title](command:myext.command), [Title](command:toSide:myext.command), or [Title](https://aka.ms). Links on their own line will be rendered as buttons.") + description: localize('walkthroughs.steps.description.interpolated', "Description of step. Supports ``preformatted``, __italic__, and **bold** text. Use markdown-style links for commands or external links: {0}, {1}, or {2}. Links on their own line will be rendered as buttons.", `[${titleTranslated}](command:myext.command)`, `[${titleTranslated}](command:toSide:myext.command)`, `[${titleTranslated}](https://aka.ms)`) }, button: { - deprecationMessage: localize('walkthroughs.steps.button.deprecated', "Deprecated. Use markdown links in the description instead, i.e. [Title](command:myext.command), [Title](command:toSide:myext.command), or [Title](https://aka.ms), "), + deprecationMessage: localize('walkthroughs.steps.button.deprecated.interpolated', "Deprecated. Use markdown links in the description instead, i.e. {0}, {1}, or {2}", `[${titleTranslated}](command:myext.command)`, `[${titleTranslated}](command:toSide:myext.command)`, `[${titleTranslated}](https://aka.ms)`), }, media: { type: 'object', diff --git a/src/vs/workbench/contrib/welcome/gettingStarted/common/gettingStartedContent.ts b/src/vs/workbench/contrib/welcome/gettingStarted/common/gettingStartedContent.ts index 5188d25939cf1..aa5fdc543fc01 100644 --- a/src/vs/workbench/contrib/welcome/gettingStarted/common/gettingStartedContent.ts +++ b/src/vs/workbench/contrib/welcome/gettingStarted/common/gettingStartedContent.ts @@ -118,6 +118,8 @@ export const startEntries: GettingStartedStartEntryContent = [ }, ]; +const Button = (title: string, href: string) => `[${title}](${href})`; + export const walkthroughs: GettingStartedWalkthroughContent = [ { id: 'Setup', @@ -131,14 +133,14 @@ export const walkthroughs: GettingStartedWalkthroughContent = [ { id: 'pickColorTheme', title: localize('gettingStarted.pickColor.title', "Choose the look you want"), - description: localize('gettingStarted.pickColor.description', "The right color palette helps you focus on your code, is easy on your eyes, and is simply more fun to use.\n[Browse Color Themes](command:workbench.action.selectTheme)"), + description: localize('gettingStarted.pickColor.description.interpolated', "The right color palette helps you focus on your code, is easy on your eyes, and is simply more fun to use.\n{0}", Button(localize('titleID', "Browse Color Themes"), 'command:workbench.action.selectTheme')), completionEvents: ['onSettingChanged:workbench.colorTheme'], media: { type: 'markdown', path: 'example_markdown_media', } }, { id: 'findLanguageExtensions', title: localize('gettingStarted.findLanguageExts.title', "Rich support for all your languages"), - description: localize('gettingStarted.findLanguageExts.description', "Code smarter with syntax highlighting, code completion, linting and debugging. While many languages are built-in, many more can be added as extensions.\n[Browse Language Extensions](command:workbench.extensions.action.showLanguageExtensions)"), + description: localize('gettingStarted.findLanguageExts.description.interpolated', "Code smarter with syntax highlighting, code completion, linting and debugging. While many languages are built-in, many more can be added as extensions.\n{0}", Button(localize('browseLangExts', "Browse Language Extensions"), 'command:workbench.extensions.action.showLanguageExtensions')), media: { type: 'image', altText: 'Language extensions', path: { dark: 'dark/languageExtensions.png', @@ -150,7 +152,7 @@ export const walkthroughs: GettingStartedWalkthroughContent = [ { id: 'commandPaletteTask', title: localize('gettingStarted.commandPalette.title', "One shortcut to access everything"), - description: localize('gettingStarted.commandPalette.description', "Commands Palette is the keyboard way to accomplish any task in VS Code. **Practice** by looking up your frequently used commands to save time and keep in the flow.\n[Open Command Palette](command:workbench.action.showCommands)\n__Try searching for 'view toggle'.__"), + description: localize('gettingStarted.commandPalette.description.interpolated', "Commands Palette is the keyboard way to accomplish any task in VS Code. **Practice** by looking up your frequently used commands to save time and keep in the flow.\n{0}\n__Try searching for 'view toggle'.__", Button(localize('commandPalette', "Open Command Palette"), 'command:workbench.action.showCommands')), media: { type: 'image', altText: 'Command Palette overlay for searching and executing commands.', path: { dark: 'dark/commandPalette.png', @@ -162,7 +164,7 @@ export const walkthroughs: GettingStartedWalkthroughContent = [ { id: 'workspaceTrust', title: localize('gettingStarted.workspaceTrust.title', "Safely browse and edit code"), - description: localize('gettingStarted.workspaceTrust.description', "[Workspace Trust](https://github.com/microsoft/vscode-docs/blob/workspaceTrust/docs/editor/workspace-trust.md) lets you decide whether your project folders should **allow or restrict** automatic code execution __(required for extensions, debugging, etc)__.\nOpening a file/folder will prompt to grant trust. You can always [enable trust](command:toSide:workbench.action.manageTrustedDomain) later."), + description: localize('gettingStarted.workspaceTrust.description.interpolated', "{0} lets you decide whether your project folders should **allow or restrict** automatic code execution __(required for extensions, debugging, etc)__.\nOpening a file/folder will prompt to grant trust. You can always {1} later.", Button(localize('workspaceTrust', "Workspace Trust"), 'https://github.com/microsoft/vscode-docs/blob/workspaceTrust/docs/editor/workspace-trust.md'), Button(localize('enableTrust', "enable trust"), 'command:toSide:workbench.action.manageTrustedDomain')), when: '!isWorkspaceTrusted && workspaceFolderCount == 0', media: { type: 'image', altText: 'Workspace Trust editor in Restricted mode and a primary button for switching to Trusted mode.', path: { @@ -175,7 +177,7 @@ export const walkthroughs: GettingStartedWalkthroughContent = [ { id: 'pickAFolderTask-Mac', title: localize('gettingStarted.setup.OpenFolder.title', "Open up your code"), - description: localize('gettingStarted.setup.OpenFolder.description', "You're all set to start coding. Open a project folder to get your files into VS Code.\n[Pick a Folder](command:workbench.action.files.openFileFolder)"), + description: localize('gettingStarted.setup.OpenFolder.description.interpolated', "You're all set to start coding. Open a project folder to get your files into VS Code.\n{0}", Button(localize('pickFolder', "Pick a Folder"), 'command:workbench.action.files.openFileFolder')), when: 'isMac && workspaceFolderCount == 0', media: { type: 'image', altText: 'Explorer view showing buttons for opening folder and cloning repository.', path: { @@ -188,7 +190,7 @@ export const walkthroughs: GettingStartedWalkthroughContent = [ { id: 'pickAFolderTask-Other', title: localize('gettingStarted.setup.OpenFolder.title', "Open up your code"), - description: localize('gettingStarted.setup.OpenFolder.description2', "You're all set to start coding. Open a project folder to get your files into VS Code.\n[Pick a Folder](command:workbench.action.files.openFolder)"), + description: localize('gettingStarted.setup.OpenFolder.description.interpolated', "You're all set to start coding. Open a project folder to get your files into VS Code.\n{0}", Button(localize('pickFolder', "Pick a Folder"), 'command:workbench.action.files.openFolder')), when: '!isMac && workspaceFolderCount == 0', media: { type: 'image', altText: 'Explorer view showing buttons for opening folder and cloning repository.', path: { @@ -201,7 +203,7 @@ export const walkthroughs: GettingStartedWalkthroughContent = [ { id: 'quickOpen', title: localize('gettingStarted.quickOpen.title', "Quickly navigate between your files"), - description: localize('gettingStarted.quickOpen.description', "Navigate between files in an instant with one keystroke. Tip: Open multiple files by pressing the right arrow key.\n[Quick Open a File](command:toSide:workbench.action.quickOpen)"), + description: localize('gettingStarted.quickOpen.description.interpolated', "Navigate between files in an instant with one keystroke. Tip: Open multiple files by pressing the right arrow key.\n{0}", Button(localize('quickOpen', "Quick Open a File"), 'command:toSide:workbench.action.quickOpen')), when: 'workspaceFolderCount != 0', media: { type: 'image', altText: 'Go to file in quick search.', path: { @@ -227,7 +229,7 @@ export const walkthroughs: GettingStartedWalkthroughContent = [ { id: 'playground', title: localize('gettingStarted.playground.title', "Redefine your editing skills"), - description: localize('gettingStarted.playground.description', "Want to code faster and smarter? Practice powerful code editing features in the interactive playground.\n[Open Interactive Playground](command:toSide:workbench.action.showInteractivePlayground)"), + description: localize('gettingStarted.playground.description.interpolated', "Want to code faster and smarter? Practice powerful code editing features in the interactive playground.\n{0}", Button(localize('openInteractivePlayground', "Open Interactive Playground"), 'command:toSide:workbench.action.showInteractivePlayground')), media: { type: 'image', altText: 'Interactive Playground.', path: { dark: 'dark/playground.png', @@ -239,7 +241,7 @@ export const walkthroughs: GettingStartedWalkthroughContent = [ { id: 'terminal', title: localize('gettingStarted.terminal.title', "Convenient built-in terminal"), - description: localize('gettingStarted.terminal.description', "Quickly run shell commands and monitor build output, right next to your code.\n[Show Terminal Panel](command:workbench.action.terminal.toggleTerminal)"), + description: localize('gettingStarted.terminal.description.interpolated', "Quickly run shell commands and monitor build output, right next to your code.\n{0}", Button(localize('showTerminal', "Show Terminal Panel"), 'command:workbench.action.terminal.toggleTerminal')), when: 'remoteName != codespaces && !terminalIsOpen', media: { type: 'image', altText: 'Integrated terminal running a few npm commands', path: { @@ -252,7 +254,7 @@ export const walkthroughs: GettingStartedWalkthroughContent = [ { id: 'extensions', title: localize('gettingStarted.extensions.title', "Limitless extensibility"), - description: localize('gettingStarted.extensions.description', "Extensions are VS Code's power-ups. They range from handy productivity hacks, expanding out-of-the-box features, to adding completely new capabilities.\n[Browse Recommended Extensions](command:workbench.extensions.action.showRecommendedExtensions)"), + description: localize('gettingStarted.extensions.description.interpolated', "Extensions are VS Code's power-ups. They range from handy productivity hacks, expanding out-of-the-box features, to adding completely new capabilities.\n{0}", Button(localize('browseRecommended', "Browse Recommended Extensions"), 'command:workbench.extensions.action.showRecommendedExtensions')), media: { type: 'image', altText: 'VS Code extension marketplace with featured language extensions', path: { dark: 'dark/extensions.png', @@ -264,7 +266,7 @@ export const walkthroughs: GettingStartedWalkthroughContent = [ { id: 'settings', title: localize('gettingStarted.settings.title', "Tune your settings"), - description: localize('gettingStarted.settings.description', "Tweak every aspect of VS Code and your extensions to your liking. Commonly used settings are listed first to get you started.\n[Tweak my Settings](command:toSide:workbench.action.openSettings)"), + description: localize('gettingStarted.settings.description.interpolated', "Tweak every aspect of VS Code and your extensions to your liking. Commonly used settings are listed first to get you started.\n{0}", Button(localize('tweakSettings', "Tweak my Settings"), 'command:toSide:workbench.action.openSettings')), media: { type: 'image', altText: 'VS Code Settings', path: { dark: 'dark/settings.png', @@ -276,7 +278,7 @@ export const walkthroughs: GettingStartedWalkthroughContent = [ { id: 'settingsSync', title: localize('gettingStarted.settingsSync.title', "Sync your stuff across devices"), - description: localize('gettingStarted.settingsSync.description', "Never lose the perfect VS Code setup! Settings Sync will back up and share settings, keybindings & extensions across several installations.\n[Enable Settings Sync](command:workbench.userDataSync.actions.turnOn)"), + description: localize('gettingStarted.settingsSync.description.interpolated', "Never lose the perfect VS Code setup! Settings Sync will back up and share settings, keybindings & extensions across several installations.\n{0}", Button(localize('enableSync', "Enable Settings Sync"), 'command:workbench.userDataSync.actions.turnOn')), when: 'syncStatus != uninitialized', completionEvents: ['onEvent:sync-enabled'], media: { @@ -290,7 +292,7 @@ export const walkthroughs: GettingStartedWalkthroughContent = [ { id: 'videoTutorial', title: localize('gettingStarted.videoTutorial.title', "Lean back and learn"), - description: localize('gettingStarted.videoTutorial.description', "Watch the first in a series of short & practical video tutorials for VS Code's key features.\n[Watch Tutorial](https://aka.ms/vscode-getting-started-video)"), + description: localize('gettingStarted.videoTutorial.description.interpolated', "Watch the first in a series of short & practical video tutorials for VS Code's key features.\n{0}", Button(localize('watch', "Watch Tutorial"), 'https://aka.ms/vscode-getting-started-video')), media: { type: 'image', altText: 'VS Code Settings', path: 'tutorialVideo.png' }, } ] @@ -308,7 +310,7 @@ export const walkthroughs: GettingStartedWalkthroughContent = [ { id: 'splitview', title: localize('gettingStarted.splitview.title', "Side by side editing"), - description: localize('gettingStarted.splitview.description', "Make the most of your screen estate by opening files side by side, vertically and horizontally.\n[Split Editor](command:workbench.action.splitEditor)"), + description: localize('gettingStarted.splitview.description.interpolated', "Make the most of your screen estate by opening files side by side, vertically and horizontally.\n{0}", Button(localize('splitEditor', "Split Editor"), 'command:workbench.action.splitEditor')), media: { type: 'image', altText: 'Multiple editors in split view.', path: { dark: 'dark/splitview.png', @@ -320,7 +322,7 @@ export const walkthroughs: GettingStartedWalkthroughContent = [ { id: 'debugging', title: localize('gettingStarted.debug.title', "Watch your code in action"), - description: localize('gettingStarted.debug.description', "Accelerate your edit, build, test, and debug loop by setting up a launch configuration.\n[Run your Project](command:workbench.action.debug.selectandstart)"), + description: localize('gettingStarted.debug.description.interpolated', "Accelerate your edit, build, test, and debug loop by setting up a launch configuration.\n{0}", Button(localize('runProject', "Run your Project"), 'command:workbench.action.debug.selectandstart')), when: 'workspaceFolderCount != 0', media: { type: 'image', altText: 'Run and debug view.', path: { @@ -333,7 +335,7 @@ export const walkthroughs: GettingStartedWalkthroughContent = [ { id: 'scmClone', title: localize('gettingStarted.scm.title', "Track your code with Git"), - description: localize('gettingStarted.scmClone.description', "Set up the built-in version control for your project to track your changes and collaborate with others.\n[Clone Repository](command:git.clone)"), + description: localize('gettingStarted.scmClone.description.interpolated', "Set up the built-in version control for your project to track your changes and collaborate with others.\n{0}", Button(localize('cloneRepo', "Clone Repository"), 'command:git.clone')), when: 'config.git.enabled && !git.missing && workspaceFolderCount == 0', media: { type: 'image', altText: 'Source Control view.', path: { @@ -346,7 +348,7 @@ export const walkthroughs: GettingStartedWalkthroughContent = [ { id: 'scmSetup', title: localize('gettingStarted.scm.title', "Track your code with Git"), - description: localize('gettingStarted.scmSetup.description', "Set up the built-in version control for your project to track your changes and collaborate with others.\n[Initialize Git Repository](command:git.init)"), + description: localize('gettingStarted.scmSetup.description.interpolated', "Set up the built-in version control for your project to track your changes and collaborate with others.\n{0}", Button(localize('initRepo', "Initialize Git Repository"), 'command:git.init')), when: 'config.git.enabled && !git.missing && workspaceFolderCount != 0 && gitOpenRepositoryCount == 0', media: { type: 'image', altText: 'Source Control view.', path: { @@ -359,7 +361,7 @@ export const walkthroughs: GettingStartedWalkthroughContent = [ { id: 'scm', title: localize('gettingStarted.scm.title', "Track your code with Git"), - description: localize('gettingStarted.scm.description', "No more looking up Git commands! Git and GitHub workflows are seamlessly integrated.\n[Open Source Control](command:workbench.view.scm)"), + description: localize('gettingStarted.scm.description.interpolated', "No more looking up Git commands! Git and GitHub workflows are seamlessly integrated.\n{0}", Button(localize('openSCM', "Open Source Control"), 'command:workbench.view.scm')), when: 'config.git.enabled && !git.missing && workspaceFolderCount != 0 && gitOpenRepositoryCount != 0 && activeViewlet != \'workbench.view.scm\'', media: { type: 'image', altText: 'Source Control view.', path: { @@ -373,7 +375,7 @@ export const walkthroughs: GettingStartedWalkthroughContent = [ id: 'tasks', title: localize('gettingStarted.tasks.title', "Automate your project tasks"), when: 'workspaceFolderCount != 0', - description: localize('gettingStarted.tasks.description', "Create tasks for your common workflows and enjoy the integrated experience of running scripts and automatically checking results.\n[Run Auto-detected Tasks](command:workbench.action.tasks.runTask)"), + description: localize('gettingStarted.tasks.description.interpolated', "Create tasks for your common workflows and enjoy the integrated experience of running scripts and automatically checking results.\n{0}", Button(localize('runTasks', "Run Auto-detected Tasks"), 'command:workbench.action.tasks.runTask')), media: { type: 'image', altText: 'Task runner.', path: { dark: 'dark/tasks.png', @@ -385,7 +387,7 @@ export const walkthroughs: GettingStartedWalkthroughContent = [ { id: 'shortcuts', title: localize('gettingStarted.shortcuts.title', "Customize your shortcuts"), - description: localize('gettingStarted.shortcuts.description', "Once you have discovered your favorite commands, create custom keyboard shortcuts for instant access.\n[Keyboard Shortcuts](command:toSide:workbench.action.openGlobalKeybindings)"), + description: localize('gettingStarted.shortcuts.description.interpolated', "Once you have discovered your favorite commands, create custom keyboard shortcuts for instant access.\n{0}", Button(localize('keyboardShortcuts', "Keyboard Shortcuts"), 'command:toSide:workbench.action.openGlobalKeybindings')), media: { type: 'image', altText: 'Interactive shortcuts.', path: { dark: 'dark/shortcuts.png', 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 0824a9edbbb3e..a8f5d76bda394 100644 --- a/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts +++ b/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts @@ -365,7 +365,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/contrib/workspace/browser/workspace.contribution.ts b/src/vs/workbench/contrib/workspace/browser/workspace.contribution.ts index acc89ff6576c8..2ea100b6ac31f 100644 --- a/src/vs/workbench/contrib/workspace/browser/workspace.contribution.ts +++ b/src/vs/workbench/contrib/workspace/browser/workspace.contribution.ts @@ -374,7 +374,12 @@ export class WorkspaceTrustUXHandler extends Disposable implements IWorkbenchCon ariaLabel = trusted ? localize('status.ariaTrustedWindow', "This window is trusted.") : localize('status.ariaUntrustedWindow', "Restricted Mode: Some features are disabled because this window is not trusted."); toolTip = trusted ? ariaLabel : { - value: localize('status.tooltipUntrustedWindow', "Running in Restricted Mode\n\n\Some [features are disabled](command:{0}) because this [window is not trusted](command:{1}).", LIST_WORKSPACE_UNSUPPORTED_EXTENSIONS_COMMAND_ID, MANAGE_TRUST_COMMAND_ID), + value: localize( + { key: 'status.tooltipUntrustedWindow2', comment: ['[abc]({n}) are links. Only translate `features are disabled` and `window is not trusted`. Do not change brackets and parentheses or {n}'] }, + "Running in Restricted Mode\n\nSome [features are disabled]({0}) because this [window is not trusted]({1}).", + `command:${LIST_WORKSPACE_UNSUPPORTED_EXTENSIONS_COMMAND_ID}`, + `command:${MANAGE_TRUST_COMMAND_ID}` + ), isTrusted: true, supportThemeIcons: true }; @@ -384,7 +389,12 @@ export class WorkspaceTrustUXHandler extends Disposable implements IWorkbenchCon ariaLabel = trusted ? localize('status.ariaTrustedFolder', "This folder is trusted.") : localize('status.ariaUntrustedFolder', "Restricted Mode: Some features are disabled because this folder is not trusted."); toolTip = trusted ? ariaLabel : { - value: localize('status.tooltipUntrustedFolder', "Running in Restricted Mode\n\n\Some [features are disabled](command:{0}) because this [folder is not trusted](command:{1}).", LIST_WORKSPACE_UNSUPPORTED_EXTENSIONS_COMMAND_ID, MANAGE_TRUST_COMMAND_ID), + value: localize( + { key: 'status.tooltipUntrustedFolder2', comment: ['[abc]({n}) are links. Only translate `features are disabled` and `folder is not trusted`. Do not change brackets and parentheses or {n}'] }, + "Running in Restricted Mode\n\nSome [features are disabled]({0}) because this [folder is not trusted]({1}).", + `command:${LIST_WORKSPACE_UNSUPPORTED_EXTENSIONS_COMMAND_ID}`, + `command:${MANAGE_TRUST_COMMAND_ID}` + ), isTrusted: true, supportThemeIcons: true }; @@ -394,7 +404,12 @@ export class WorkspaceTrustUXHandler extends Disposable implements IWorkbenchCon ariaLabel = trusted ? localize('status.ariaTrustedWorkspace', "This workspace is trusted.") : localize('status.ariaUntrustedWorkspace', "Restricted Mode: Some features are disabled because this workspace is not trusted."); toolTip = trusted ? ariaLabel : { - value: localize('status.tooltipUntrustedWorkspace', "Running in Restricted Mode\n\n\Some [features are disabled](command:{0}) because this [workspace is not trusted](command:{1}).", LIST_WORKSPACE_UNSUPPORTED_EXTENSIONS_COMMAND_ID, MANAGE_TRUST_COMMAND_ID), + value: localize( + { key: 'status.tooltipUntrustedWorkspace2', comment: ['[abc]({n}) are links. Only translate `features are disabled` and `workspace is not trusted`. Do not change brackets and parentheses or {n}'] }, + "Running in Restricted Mode\n\nSome [features are disabled]({0}) because this [workspace is not trusted]({1}).", + `command:${LIST_WORKSPACE_UNSUPPORTED_EXTENSIONS_COMMAND_ID}`, + `command:${MANAGE_TRUST_COMMAND_ID}` + ), isTrusted: true, supportThemeIcons: true }; @@ -442,7 +457,9 @@ export class WorkspaceTrustUXHandler extends Disposable implements IWorkbenchCon })); // TODO: Consider moving the check into setWorkspaceTrust() if (this.workspaceTrustManagementService.canSetWorkspaceTrust()) { - this.workspaceTrustManagementService.setWorkspaceTrust(this.configurationService.getValue(WORKSPACE_TRUST_EMPTY_WINDOW) ?? false); + if (this.configurationService.getValue(WORKSPACE_TRUST_EMPTY_WINDOW)) { + this.workspaceTrustManagementService.setWorkspaceTrust(true); + } } } } @@ -635,7 +652,7 @@ Registry.as(ConfigurationExtensions.Configuration) }, [WORKSPACE_TRUST_EMPTY_WINDOW]: { type: 'boolean', - default: false, + default: true, included: !isWeb, markdownDescription: localize('workspace.trust.emptyWindow.description', "Controls whether or not the empty window is trusted by default within VS Code. When used with `#{0}#`, you can enable the full functionality of VS Code without prompting in an empty window.", WORKSPACE_TRUST_UNTRUSTED_FILES), scope: ConfigurationScope.APPLICATION diff --git a/src/vs/workbench/contrib/workspace/browser/workspaceTrustEditor.ts b/src/vs/workbench/contrib/workspace/browser/workspaceTrustEditor.ts index 230f468978cd9..4f95167c1f1ca 100644 --- a/src/vs/workbench/contrib/workspace/browser/workspaceTrustEditor.ts +++ b/src/vs/workbench/contrib/workspace/browser/workspaceTrustEditor.ts @@ -823,13 +823,13 @@ export class WorkspaceTrustEditor extends EditorPane { [ localize('untrustedTasks', "Tasks are disabled"), localize('untrustedDebugging', "Debugging is disabled"), - localize('untrustedExtensions', "[{0} extensions](command:{1}) are disabled or have limited functionality", numExtensions, LIST_WORKSPACE_UNSUPPORTED_EXTENSIONS_COMMAND_ID) + localize('untrustedExtensions', "[{0} extensions]({1}) are disabled or have limited functionality", numExtensions, `command:${LIST_WORKSPACE_UNSUPPORTED_EXTENSIONS_COMMAND_ID}`) ] : [ localize('untrustedTasks', "Tasks are disabled"), localize('untrustedDebugging', "Debugging is disabled"), - numSettings ? localize('untrustedSettings', "[{0} workspace settings](command:{1}) are not applied", numSettings, 'settings.filterUntrusted') : localize('no untrustedSettings', "Workspace settings requiring trust are not applied"), - localize('untrustedExtensions', "[{0} extensions](command:{1}) are disabled or have limited functionality", numExtensions, LIST_WORKSPACE_UNSUPPORTED_EXTENSIONS_COMMAND_ID) + numSettings ? localize('untrustedSettings', "[{0} workspace settings]({1}) are not applied", numSettings, 'command:settings.filterUntrusted') : localize('no untrustedSettings', "Workspace settings requiring trust are not applied"), + localize('untrustedExtensions', "[{0} extensions]({1}) are disabled or have limited functionality", numExtensions, `command:${LIST_WORKSPACE_UNSUPPORTED_EXTENSIONS_COMMAND_ID}`) ]; this.renderLimitationsListElement(untrustedContainer, untrustedContainerItems, xListIcon.classNamesArray); diff --git a/src/vs/workbench/electron-sandbox/actions/windowActions.ts b/src/vs/workbench/electron-sandbox/actions/windowActions.ts index e6dbac49746c5..67fd94f32dea7 100644 --- a/src/vs/workbench/electron-sandbox/actions/windowActions.ts +++ b/src/vs/workbench/electron-sandbox/actions/windowActions.ts @@ -27,9 +27,11 @@ import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegis export class CloseWindowAction extends Action2 { + static readonly ID = 'workbench.action.closeWindow'; + constructor() { super({ - id: 'workbench.action.closeWindow', + id: CloseWindowAction.ID, title: { value: localize('closeWindow', "Close Window"), mnemonicTitle: localize({ key: 'miCloseWindow', comment: ['&& denotes a mnemonic'] }, "Clos&&e Window"), diff --git a/src/vs/workbench/electron-sandbox/desktop.contribution.ts b/src/vs/workbench/electron-sandbox/desktop.contribution.ts index 01564b51c732b..59b1afc670988 100644 --- a/src/vs/workbench/electron-sandbox/desktop.contribution.ts +++ b/src/vs/workbench/electron-sandbox/desktop.contribution.ts @@ -23,6 +23,7 @@ import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } fr import { PartsSplash } from 'vs/workbench/electron-sandbox/splash'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { InstallShellScriptAction, UninstallShellScriptAction } from 'vs/workbench/electron-sandbox/actions/installActions'; +import { EditorsVisibleContext, SingleEditorGroupsContext } from 'vs/workbench/common/editor'; // Actions (function registerActions(): void { @@ -37,6 +38,19 @@ import { InstallShellScriptAction, UninstallShellScriptAction } from 'vs/workben registerAction2(QuickSwitchWindowAction); registerAction2(CloseWindowAction); + if (isMacintosh) { + // macOS: behave like other native apps that have documents + // but can run without a document opened and allow to close + // the window when the last document is closed + // (https://github.com/microsoft/vscode/issues/126042) + KeybindingsRegistry.registerKeybindingRule({ + id: CloseWindowAction.ID, + weight: KeybindingWeight.WorkbenchContrib, + when: ContextKeyExpr.and(EditorsVisibleContext.toNegated(), SingleEditorGroupsContext), + primary: KeyMod.CtrlCmd | KeyCode.KEY_W + }); + } + // Actions: Install Shell Script (macOS only) if (isMacintosh) { registerAction2(InstallShellScriptAction); diff --git a/src/vs/workbench/services/editor/browser/editorOverrideService.ts b/src/vs/workbench/services/editor/browser/editorOverrideService.ts index bd54a0ec39d35..5d87f892e2a6e 100644 --- a/src/vs/workbench/services/editor/browser/editorOverrideService.ts +++ b/src/vs/workbench/services/editor/browser/editorOverrideService.ts @@ -10,7 +10,7 @@ import { basename, extname, isEqual } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { EditorActivation, EditorOverride, IEditorOptions } from 'vs/platform/editor/common/editor'; -import { EditorResourceAccessor, IEditorInput, IEditorInputWithOptions, IEditorInputWithOptionsAndGroup } from 'vs/workbench/common/editor'; +import { EditorResourceAccessor, IEditorInput, IEditorInputWithOptions, IEditorInputWithOptionsAndGroup, SideBySideEditor } from 'vs/workbench/common/editor'; import { IEditorGroup, IEditorGroupsService, preferredSideBySideGroupDirection } from 'vs/workbench/services/editor/common/editorGroupsService'; import { Schemas } from 'vs/base/common/network'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; @@ -22,6 +22,8 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; +import { ILogService } from 'vs/platform/log/common/log'; interface IContributedEditorInput extends IEditorInput { viewType?: string; @@ -56,7 +58,9 @@ export class EditorOverrideService extends Disposable implements IEditorOverride @INotificationService private readonly notificationService: INotificationService, @ITelemetryService private readonly telemetryService: ITelemetryService, @IStorageService private readonly storageService: IStorageService, - @IExtensionService private readonly extensionService: IExtensionService + @IExtensionService private readonly extensionService: IExtensionService, + @IHostService private readonly hostService: IHostService, + @ILogService private readonly logService: ILogService, ) { super(); // Read in the cache on statup @@ -75,8 +79,10 @@ export class EditorOverrideService extends Disposable implements IEditorOverride }); // When the setting changes we want to ensure that it is properly converted - this._register(this.configurationService.onDidChangeConfiguration(() => { - this.convertOldAssociationFormat(); + this._register(this.configurationService.onDidChangeConfiguration((e) => { + if (e.affectsConfiguration(editorsAssociationsSettingId)) { + this.convertOldAssociationFormat(); + } })); } @@ -91,11 +97,8 @@ export class EditorOverrideService extends Disposable implements IEditorOverride } // Always ensure inputs have populated resource fields - if (editor instanceof DiffEditorInput) { - if ((!editor.modifiedInput.resource || !editor.originalInput.resource)) { - return { editor, options, group }; - } - } else if (!editor.resource) { + const resource = EditorResourceAccessor.getCanonicalUri(editor, { supportSideBySide: SideBySideEditor.PRIMARY }); + if (!resource) { return { editor, options, group }; } @@ -116,7 +119,7 @@ export class EditorOverrideService extends Disposable implements IEditorOverride } // Resolved the override as much as possible, now find a given contribution - const { contributionPoint, conflictingDefault } = this.getContributionPoint(editor instanceof DiffEditorInput ? editor.modifiedInput.resource! : editor.resource!, override); + const { contributionPoint, conflictingDefault } = this.getContributionPoint(resource, override); const selectedContribution = contributionPoint; if (!selectedContribution) { return { editor, options, group }; @@ -131,10 +134,10 @@ export class EditorOverrideService extends Disposable implements IEditorOverride if (selectedContribution.editorInfo.describes(editor)) { return; } - const input = await this.doOverrideEditorInput(editor, options, group, selectedContribution); + const input = await this.doOverrideEditorInput(resource, editor, options, group, selectedContribution); if (conflictingDefault && input) { // Show the conflicting default dialog - await this.doHandleConflictingDefaults(selectedContribution.editorInfo.label, input.editor, input.options ?? options, group); + await this.doHandleConflictingDefaults(resource, selectedContribution.editorInfo.label, input.editor, input.options ?? options, group); } // Add the group as we might've changed it with the quickpick @@ -152,10 +155,12 @@ export class EditorOverrideService extends Disposable implements IEditorOverride createEditorInput: EditorInputFactoryFunction, createDiffEditorInput?: DiffEditorInputFactoryFunction ): IDisposable { - if (this._contributionPoints.get(globPattern) === undefined) { - this._contributionPoints.set(globPattern, []); + let contributionPoint = this._contributionPoints.get(globPattern); + if (contributionPoint === undefined) { + contributionPoint = []; + this._contributionPoints.set(globPattern, contributionPoint); } - const remove = insert(this._contributionPoints.get(globPattern)!, { + const remove = insert(contributionPoint, { globPattern, editorInfo, options, @@ -178,23 +183,42 @@ export class EditorOverrideService extends Disposable implements IEditorOverride } private convertOldAssociationFormat(): void { - const rawAssociations = this.configurationService.getValue(editorsAssociationsSettingId) || []; - // If it's not an array, then it's the new format - if (!Array.isArray(rawAssociations)) { - return; - } - let newSettingObject = Object.create(null); - // Make the correctly formatted object from the array and then set that object - for (const association of rawAssociations) { - if (association.filenamePattern) { - newSettingObject[association.filenamePattern] = association.viewType; + this.hostService.hadLastFocus().then(hadLastFocus => { + if (!hadLastFocus) { + return; } - } - this.configurationService.updateValue(editorsAssociationsSettingId, newSettingObject); + const rawAssociations = this.configurationService.getValue(editorsAssociationsSettingId) || {}; + // If it's not an array, then it's the new format + if (!Array.isArray(rawAssociations)) { + return; + } + let newSettingObject = Object.create(null); + // Make the correctly formatted object from the array and then set that object + for (const association of rawAssociations) { + if (association.filenamePattern) { + newSettingObject[association.filenamePattern] = association.viewType; + } + } + this.logService.info(`Migrating ${editorsAssociationsSettingId}`); + this.configurationService.updateValue(editorsAssociationsSettingId, newSettingObject); + }); } private getAllUserAssociations(): EditorAssociations { - const rawAssociations = this.configurationService.getValue<{ [fileNamePattern: string]: string }>(editorsAssociationsSettingId) || []; + let rawAssociations = this.configurationService.getValue(editorsAssociationsSettingId) || {}; + + // If it's an array then it is old format + if (Array.isArray(rawAssociations)) { + // Make the correctly formatted object + const newValue = Object.create(null); + for (const association of rawAssociations) { + if (association.filenamePattern) { + newValue[association.filenamePattern] = association.viewType; + } + } + rawAssociations = newValue; + } + let associations = []; for (const [key, value] of Object.entries(rawAssociations)) { const association: EditorAssociation = { @@ -231,8 +255,7 @@ export class EditorOverrideService extends Disposable implements IEditorOverride const userSettings = this.getAssociationsForResource(resource); let contributions: ContributionPoint[] = []; // Then all glob patterns - for (const key of this._contributionPoints.keys()) { - const contributionPoints = this._contributionPoints.get(key)!; + for (const [key, contributionPoints] of this._contributionPoints) { for (const contributionPoint of contributionPoints) { const foundInSettings = userSettings.find(setting => setting.viewType === contributionPoint.editorInfo.id); if (foundInSettings || globMatchesResource(key, resource)) { @@ -292,7 +315,7 @@ export class EditorOverrideService extends Disposable implements IEditorOverride }; } - private async doOverrideEditorInput(editor: IEditorInput, options: IEditorOptions | undefined, group: IEditorGroup, selectedContribution: ContributionPoint): Promise { + private async doOverrideEditorInput(resource: URI, editor: IEditorInput, options: IEditorOptions | undefined, group: IEditorGroup, selectedContribution: ContributionPoint): Promise { // If no activation option is provided, populate it. if (options && typeof options.activation === 'undefined') { @@ -308,9 +331,6 @@ export class EditorOverrideService extends Disposable implements IEditorOverride return inputWithOptions; } - // We only call this function from one place and there we do the check to ensure editor.resource is not undefined - const resource = editor.resource!; - // Respect options passed back const inputWithOptions = selectedContribution.createEditorInput(resource, options, group); options = inputWithOptions.options ?? options; @@ -375,13 +395,13 @@ export class EditorOverrideService extends Disposable implements IEditorOverride return out; } - private async doHandleConflictingDefaults(editorName: string, currentEditor: IContributedEditorInput, options: IEditorOptions | undefined, group: IEditorGroup) { + private async doHandleConflictingDefaults(resource: URI, editorName: string, currentEditor: IContributedEditorInput, options: IEditorOptions | undefined, group: IEditorGroup) { type StoredChoice = { [key: string]: string[]; }; - const contributionPoints = this.findMatchingContributions(currentEditor.resource!); + const contributionPoints = this.findMatchingContributions(resource); const storedChoices: StoredChoice = JSON.parse(this.storageService.get(EditorOverrideService.conflictingDefaultsStorageID, StorageScope.GLOBAL, '{}')); - const globForResource = `*${extname(currentEditor.resource!)}`; + const globForResource = `*${extname(resource)}`; // Writes to the storage service that a choice has been made for the currently installed editors const writeCurrentEditorsToStorage = () => { storedChoices[globForResource] = []; @@ -599,8 +619,7 @@ export class EditorOverrideService extends Disposable implements IEditorOverride const cacheStorage: Set = new Set(); // Store just the relative pattern pieces without any path info - for (const globPattern of this._contributionPoints.keys()) { - const contribPoint = this._contributionPoints.get(globPattern)!; + for (const [globPattern, contribPoint] of this._contributionPoints) { const nonOptional = !!contribPoint.find(c => c.editorInfo.priority !== ContributedEditorPriority.option && c.editorInfo.id !== DEFAULT_EDITOR_ASSOCIATION.id); // Don't keep a cache of the optional ones as those wouldn't be opened on start anyways if (!nonOptional) { diff --git a/src/vs/workbench/services/environment/browser/environmentService.ts b/src/vs/workbench/services/environment/browser/environmentService.ts index 00b781462ea29..536a4e8f372c6 100644 --- a/src/vs/workbench/services/environment/browser/environmentService.ts +++ b/src/vs/workbench/services/environment/browser/environmentService.ts @@ -120,8 +120,26 @@ export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironment @memoize get logFile(): URI { return joinPath(this.options.logsPath, 'window.log'); } + // NOTE@coder: Use the same path in // ../../../../platform/environment/node/environmentService.ts + // and don't use the user data scheme. 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 userRoamingDataHome(): URI { return URI.file('/User').with({ scheme: Schemas.userData }); } + get userRoamingDataHome(): URI { return joinPath(URI.file(this.userDataPath).with({ scheme: Schemas.vscodeRemote }), 'User'); } + + @memoize + get userDataPath(): string { + const dataPath = this.payload?.get('userDataPath'); + if (!dataPath) { + throw new Error('userDataPath was not provided to environment service'); + } + return dataPath; + } @memoize get settingsResource(): URI { return joinPath(this.userRoamingDataHome, 'settings.json'); } @@ -317,7 +335,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 521ab664f825d..06a8083eeb00d 100644 --- a/src/vs/workbench/services/extensionManagement/browser/extensionEnablementService.ts +++ b/src/vs/workbench/services/extensionManagement/browser/extensionEnablementService.ts @@ -263,7 +263,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 e4921e4e0a3a6..9921920689924 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 { @@ -307,6 +308,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/node/extensionHostProcessSetup.ts b/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts index a0baef2503403..ca8ea8106e2ad 100644 --- a/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts +++ b/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts @@ -17,11 +17,13 @@ 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 { IURITransformer } from 'vs/base/common/uriIpc'; +import { createServerURITransformer } from 'vs/base/common/uriServer'; import { exists } from 'vs/base/node/pfs'; import { realpath } from 'vs/base/node/extpath'; import { IHostUtils } from 'vs/workbench/api/common/extHostExtensionService'; import { RunOnceScheduler } from 'vs/base/common/async'; +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'; @@ -137,8 +139,11 @@ function _createExtHostProtocol(): Promise { // Wait for rich client to reconnect protocol.onSocketClose(() => { - // The socket has closed, let's give the renderer a certain amount of time to reconnect - disconnectRunner1.schedule(); + // NOTE@coder: Inform the server so we can manage offline + // connections there instead. Our goal is to persist connections + // forever (to a reasonable point) to account for things like + // hibernating overnight. + process.send!({ type: 'VSCODE_EXTHOST_DISCONNECTED' }); }); } } @@ -312,6 +317,9 @@ function connectToRenderer(protocol: IMessagePassingProtocol): Promise { + // NOTE@coder: add proxy agent patch + proxyAgent.monkeyPatch(true); + performance.mark(`code/extHost/willConnectToRenderer`); const protocol = await createExtHostProtocol(); performance.mark(`code/extHost/didConnectToRenderer`); @@ -332,11 +340,10 @@ 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); + uriTransformer = createServerURITransformer(initData.remote.authority); } catch (e) { console.error(e); } 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..a47d340aaa0a0 --- /dev/null +++ b/src/vs/workbench/services/localizations/browser/localizationsService.ts @@ -0,0 +1,27 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Coder Technologies. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// NOTE@coder: 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/services/workspaces/common/workspaceTrust.ts b/src/vs/workbench/services/workspaces/common/workspaceTrust.ts index 802aeff50fd50..f3a1ae0150480 100644 --- a/src/vs/workbench/services/workspaces/common/workspaceTrust.ts +++ b/src/vs/workbench/services/workspaces/common/workspaceTrust.ts @@ -126,15 +126,17 @@ export class WorkspaceTrustManagementService extends Disposable implements IWork private registerListeners(): void { // Resolve the workspace uris and resolve the initialization promise - this.resolveCanonicalWorkspaceUris().then(async () => { - this._initialized = true; - await this.updateWorkspaceTrust(); - - this._workspaceResolvedPromiseResolve(); - if (!this.environmentService.remoteAuthority) { - this._workspaceTrustInitializedPromiseResolve(); - } - }); + this.resolveCanonicalWorkspaceUris() + .then(async () => { + this._initialized = true; + await this.updateWorkspaceTrust(); + }) + .finally(() => { + this._workspaceResolvedPromiseResolve(); + if (!this.environmentService.remoteAuthority) { + this._workspaceTrustInitializedPromiseResolve(); + } + }); // Remote - resolve remote authority if (this.environmentService.remoteAuthority) { @@ -142,7 +144,8 @@ export class WorkspaceTrustManagementService extends Disposable implements IWork .then(async result => { this._remoteAuthority = result; await this.updateWorkspaceTrust(); - + }) + .finally(() => { this._workspaceTrustInitializedPromiseResolve(); }); } @@ -235,8 +238,8 @@ export class WorkspaceTrustManagementService extends Disposable implements IWork return true; } - if (!this._initialized) { - return false; + if (this.environmentService.extensionTestsLocationURI) { + return true; // trust running tests with vscode-test } // Remote - remote authority explicitly sets workspace trust @@ -244,16 +247,16 @@ export class WorkspaceTrustManagementService extends Disposable implements IWork return this._remoteAuthority.options.isTrusted; } - if (this.environmentService.extensionTestsLocationURI) { - return true; // trust running tests with vscode-test - } - if (this.workspaceService.getWorkbenchState() === WorkbenchState.EMPTY) { // Use memento if present, otherwise default to restricted mode // Workspace may transition to trusted based on the opened editors return this._trustState.isTrusted ?? false; } + if (!this._initialized) { + return false; + } + return this.getUrisTrust(this.getWorkspaceUris()); } @@ -272,6 +275,11 @@ export class WorkspaceTrustManagementService extends Disposable implements IWork // Update workspace trust this._trustState.isTrusted = trusted; + // Reset acceptsOutOfWorkspaceFiles + if (!trusted) { + this._trustState.acceptsOutOfWorkspaceFiles = false; + } + // Run workspace trust transition participants await this._trustTransitionManager.participate(trusted); @@ -742,10 +750,6 @@ class WorkspaceTrustState { set isTrusted(value: boolean | undefined) { this._mementoObject[this._isTrustedKey] = value; - if (!value) { - this._mementoObject[this._acceptsOutOfWorkspaceFilesKey] = value; - } - this._memento.saveMemento(); } } diff --git a/src/vs/workbench/test/browser/api/extHostTypeConverter.test.ts b/src/vs/workbench/test/browser/api/extHostTypeConverter.test.ts index 48cdcba4d1385..a635a390c670f 100644 --- a/src/vs/workbench/test/browser/api/extHostTypeConverter.test.ts +++ b/src/vs/workbench/test/browser/api/extHostTypeConverter.test.ts @@ -6,7 +6,7 @@ import * as assert from 'assert'; import * as extHostTypes from 'vs/workbench/api/common/extHostTypes'; -import { MarkdownString, NotebookCellOutputItem } from 'vs/workbench/api/common/extHostTypeConverters'; +import { MarkdownString, NotebookCellOutputItem, NotebookData } from 'vs/workbench/api/common/extHostTypeConverters'; import { isEmptyObject } from 'vs/base/common/types'; import { forEach } from 'vs/base/common/collections'; import { LogLevel as _MainLogLevel } from 'vs/platform/log/common/log'; @@ -83,6 +83,20 @@ suite('ExtHostTypeConverter', function () { }); }); + test('Notebook metadata is ignored when using Notebook Serializer #125716', function () { + + const d = new extHostTypes.NotebookData([]); + d.cells.push(new extHostTypes.NotebookCellData(extHostTypes.NotebookCellKind.Code, 'hello', 'fooLang')); + d.metadata = { custom: { foo: 'bar', bar: 123 } }; + + const dto = NotebookData.from(d); + + assert.strictEqual(dto.cells.length, 1); + assert.strictEqual(dto.cells[0].language, 'fooLang'); + assert.strictEqual(dto.cells[0].source, 'hello'); + assert.deepStrictEqual(dto.metadata, d.metadata); + }); + test('NotebookCellOutputItem', function () { const item = extHostTypes.NotebookCellOutputItem.text('Hello', 'foo/bar'); diff --git a/yarn.lock b/yarn.lock index 2d5cec7e66e5a..55e1b578ab4dd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -509,6 +509,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" @@ -534,6 +541,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" @@ -892,7 +906,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== @@ -1281,6 +1295,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" @@ -1683,7 +1704,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== @@ -2793,6 +2814,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" @@ -2808,6 +2838,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" @@ -3001,10 +3036,10 @@ electron-to-chromium@^1.3.723: resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.737.tgz#196f2e9656f4f3c31930750e1899c091b72d36b5" integrity sha512-P/B84AgUSQXaum7a8m11HUsYL8tj9h/Pt5f7Hg7Ty6bm5DxlFq+e5+ouHUoNQMsKDJ7u4yGfI8mOErCmSH9wyg== -electron@12.0.9: - version "12.0.9" - resolved "https://registry.yarnpkg.com/electron/-/electron-12.0.9.tgz#d582afa8f6fc0c429606f0961a4c89b376994823" - integrity sha512-p5aEt1tIh/PYjwN+6MHTc5HtW529XR9r4Qlj9PPcSb5ubkotSsS0BtWJoRPhDenSAN8sgHk3sbZLxXPJtdnRYA== +electron@12.0.7: + version "12.0.7" + resolved "https://registry.yarnpkg.com/electron/-/electron-12.0.7.tgz#e0fca2c8be34cb7da48c4d15cfb1d2ad666d2718" + integrity sha512-722TZNKDuLpEmj96AzTYFKHaJEH98xgOBH0aldStaPXI1xDFfb9SJQQuirvwFlkwG5OqQdz6Ne3OwwJ7Dbs5nQ== dependencies: "@electron/get" "^1.0.1" "@types/node" "^14.6.2" @@ -3221,6 +3256,18 @@ escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.3, escape-string-regexp@^ resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= +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" @@ -3373,7 +3420,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== @@ -3392,7 +3439,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== @@ -4108,7 +4155,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== @@ -4811,6 +4858,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" @@ -4819,7 +4877,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== @@ -4842,6 +4900,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" @@ -4858,14 +4924,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" @@ -4881,18 +4939,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" @@ -4986,7 +5044,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== @@ -6499,6 +6557,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" @@ -6852,7 +6915,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== @@ -6997,6 +7060,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" @@ -7722,7 +7809,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== @@ -7867,6 +7968,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" @@ -8483,6 +8594,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" @@ -8603,6 +8719,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" @@ -8809,6 +8934,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" @@ -9148,7 +9278,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== @@ -9396,6 +9526,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" @@ -9461,6 +9596,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" @@ -9643,6 +9783,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"