From ceceef1dae81e0a396d196ed9023d6f5ea453ffa Mon Sep 17 00:00:00 2001 From: Anmol Sethi Date: Thu, 3 Sep 2020 14:56:12 -0400 Subject: [PATCH 01/75] Add documentation issue template --- .github/ISSUE_TEMPLATE/doc.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/doc.md diff --git a/.github/ISSUE_TEMPLATE/doc.md b/.github/ISSUE_TEMPLATE/doc.md new file mode 100644 index 000000000..ba63b11bd --- /dev/null +++ b/.github/ISSUE_TEMPLATE/doc.md @@ -0,0 +1,7 @@ +--- +name: Documentation improvement +about: Suggest a documentation improvement +title: "" +labels: "docs" +assignees: "" +--- From 3761f7bd519181e412f2add37d073f5b1218d766 Mon Sep 17 00:00:00 2001 From: Asher Date: Thu, 3 Sep 2020 13:57:46 -0500 Subject: [PATCH 02/75] Patch VS Code to wait for storage write (#2049) VS Code has a short delay before writing storage (probably to queue up rapid changes). In the web version of VS Code this happens on the client which means if the page is reloaded before the delay expires the write never happens. Storage updates are already promises so this simply returns the promise returned by the delayer so it won't resolve until the write actually happens. Fixes #2021. --- ci/dev/vscode.patch | 61 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/ci/dev/vscode.patch b/ci/dev/vscode.patch index 14bdce6b1..b783063c3 100644 --- a/ci/dev/vscode.patch +++ b/ci/dev/vscode.patch @@ -696,6 +696,49 @@ index 2185bb5228c..35463ca6520 100644 (err: any, socket: ISocket | undefined) => { if (err || !socket) { options.logService.error(`${logPrefix} socketFactory.connect() failed. Error:`); +diff --git a/src/vs/platform/storage/browser/storageService.ts b/src/vs/platform/storage/browser/storageService.ts +index 59b1baf912..cf9805554b 100644 +--- a/src/vs/platform/storage/browser/storageService.ts ++++ b/src/vs/platform/storage/browser/storageService.ts +@@ -116,8 +116,8 @@ export class BrowserStorageService extends Disposable implements IStorageService + return this.getStorage(scope).getNumber(key, fallbackValue); + } + +- store(key: string, value: string | boolean | number | undefined | null, scope: StorageScope): void { +- this.getStorage(scope).set(key, value); ++ store(key: string, value: string | boolean | number | undefined | null, scope: StorageScope): Promise { ++ return this.getStorage(scope).set(key, value); + } + + remove(key: string, scope: StorageScope): void { +diff --git a/src/vs/platform/storage/common/storage.ts b/src/vs/platform/storage/common/storage.ts +index 1623957cb1..d366438d54 100644 +--- a/src/vs/platform/storage/common/storage.ts ++++ b/src/vs/platform/storage/common/storage.ts +@@ -83,7 +83,7 @@ export interface IStorageService { + * The scope argument allows to define the scope of the storage + * operation to either the current workspace only or all workspaces. + */ +- store(key: string, value: string | boolean | number | undefined | null, scope: StorageScope): void; ++ store(key: string, value: string | boolean | number | undefined | null, scope: StorageScope): Promise | void; + + /** + * Delete an element stored under the provided key from storage. +diff --git a/src/vs/platform/storage/node/storageService.ts b/src/vs/platform/storage/node/storageService.ts +index 75514fe5a4..62d97c6048 100644 +--- a/src/vs/platform/storage/node/storageService.ts ++++ b/src/vs/platform/storage/node/storageService.ts +@@ -204,8 +204,8 @@ export class NativeStorageService extends Disposable implements IStorageService + return this.getStorage(scope).getNumber(key, fallbackValue); + } + +- store(key: string, value: string | boolean | number | undefined | null, scope: StorageScope): void { +- this.getStorage(scope).set(key, value); ++ store(key: string, value: string | boolean | number | undefined | null, scope: StorageScope): Promise { ++ return this.getStorage(scope).set(key, value); + } + + remove(key: string, scope: StorageScope): void { diff --git a/src/vs/server/browser/client.ts b/src/vs/server/browser/client.ts new file mode 100644 index 00000000000..3c0703b7174 @@ -2811,6 +2854,24 @@ index 3d77009b908..11deb1b99ac 100644 import './mainThreadTunnelService'; import './mainThreadAuthentication'; import './mainThreadTimeline'; +diff --git a/src/vs/workbench/api/browser/mainThreadStorage.ts b/src/vs/workbench/api/browser/mainThreadStorage.ts +index 7bc3904963..c6db2368ae 100644 +--- a/src/vs/workbench/api/browser/mainThreadStorage.ts ++++ b/src/vs/workbench/api/browser/mainThreadStorage.ts +@@ -58,11 +58,11 @@ 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); +- this._storageService.store(key, jsonValue, shared ? StorageScope.GLOBAL : StorageScope.WORKSPACE); ++ await this._storageService.store(key, jsonValue, shared ? StorageScope.GLOBAL : StorageScope.WORKSPACE); + } catch (err) { + return Promise.reject(err); + } diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 97793666ad8..13cd137db1e 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts From 4a250be79afb1ffb1b1be0c035830543a725a370 Mon Sep 17 00:00:00 2001 From: Asher Date: Thu, 3 Sep 2020 14:32:29 -0500 Subject: [PATCH 03/75] Use --full-index for patch This should eliminate potential noise in the diffs for the patch since different versions seem to default to different hash lengths. --- ci/dev/diff-vscode.sh | 2 +- ci/dev/vscode.patch | 144 +++++++++++++++++++++--------------------- 2 files changed, 73 insertions(+), 73 deletions(-) diff --git a/ci/dev/diff-vscode.sh b/ci/dev/diff-vscode.sh index 98c955dff..38f7cb563 100755 --- a/ci/dev/diff-vscode.sh +++ b/ci/dev/diff-vscode.sh @@ -6,7 +6,7 @@ main() { cd ./lib/vscode git add -A - git diff HEAD > ../../ci/dev/vscode.patch + git diff HEAD --full-index > ../../ci/dev/vscode.patch } main "$@" diff --git a/ci/dev/vscode.patch b/ci/dev/vscode.patch index b783063c3..107a22f9a 100644 --- a/ci/dev/vscode.patch +++ b/ci/dev/vscode.patch @@ -1,5 +1,5 @@ diff --git a/.gitignore b/.gitignore -index 0fe46b6eadc..e545e004cef 100644 +index 0fe46b6eadc4ccc819fbf342ee1071bb657792b3..e545e004cef31fa5f40ba8df6a2317ea5b69ddb5 100644 --- a/.gitignore +++ b/.gitignore @@ -25,7 +25,6 @@ out-vscode-reh-web-pkg/ @@ -12,7 +12,7 @@ index 0fe46b6eadc..e545e004cef 100644 coverage/ diff --git a/.yarnrc b/.yarnrc deleted file mode 100644 -index 135e10442a7..00000000000 +index 135e10442a7e5184cf8c47615322bb7d622855d9..0000000000000000000000000000000000000000 --- a/.yarnrc +++ /dev/null @@ -1,3 +0,0 @@ @@ -20,7 +20,7 @@ index 135e10442a7..00000000000 -target "7.3.2" -runtime "electron" diff --git a/build/gulpfile.reh.js b/build/gulpfile.reh.js -index f2ea1bd3701..3f660f99819 100644 +index f2ea1bd37010b1eb8a43ce9beaae4a88810f6e2d..3f660f9981921ec465d2b8809a1a5ea5663f4c1f 100644 --- a/build/gulpfile.reh.js +++ b/build/gulpfile.reh.js @@ -52,6 +52,7 @@ gulp.task('vscode-reh-web-linux-x64-min', noop); @@ -32,7 +32,7 @@ index f2ea1bd3701..3f660f99819 100644 const target = /^target "(.*)"$/m.exec(yarnrc)[1]; return target; diff --git a/build/lib/node.js b/build/lib/node.js -index 403ae3d9657..738ee8cee0e 100644 +index 403ae3d9657f823019542e739fc39292db20e4fe..738ee8cee0e79aa239af10e1abefc9e836b8ce33 100644 --- a/build/lib/node.js +++ b/build/lib/node.js @@ -5,11 +5,8 @@ @@ -49,7 +49,7 @@ index 403ae3d9657..738ee8cee0e 100644 const nodePath = path.join(root, '.build', 'node', `v${version}`, `${process.platform}-${process.arch}`, node); console.log(nodePath); diff --git a/build/lib/node.ts b/build/lib/node.ts -index 64397034461..c53dccf4dc0 100644 +index 64397034461b1661f82007c141cbf4c039a3b722..c53dccf4dc0a99122ed96cf10c2eb632bb25059e 100644 --- a/build/lib/node.ts +++ b/build/lib/node.ts @@ -4,13 +4,10 @@ @@ -70,7 +70,7 @@ index 64397034461..c53dccf4dc0 100644 \ No newline at end of file +console.log(nodePath); diff --git a/build/lib/util.js b/build/lib/util.js -index e552a036f89..169e8614b9f 100644 +index e552a036f89bd581644459fd5c27fe4ae1379f62..169e8614b9f6a2bd68446144ab7e1ce5c6d49b64 100644 --- a/build/lib/util.js +++ b/build/lib/util.js @@ -257,6 +257,7 @@ function streamToPromise(stream) { @@ -82,7 +82,7 @@ index e552a036f89..169e8614b9f 100644 const target = /^target "(.*)"$/m.exec(yarnrc)[1]; return target; diff --git a/build/lib/util.ts b/build/lib/util.ts -index 035c7e95ea3..4ff8dcfe6b2 100644 +index 035c7e95ea3006bb3dabd68bbf54db80de4aaaf2..4ff8dcfe6b21a0ec8064ebc7bb05506b8f1faa91 100644 --- a/build/lib/util.ts +++ b/build/lib/util.ts @@ -322,6 +322,7 @@ export function streamToPromise(stream: NodeJS.ReadWriteStream): Promise { @@ -94,7 +94,7 @@ index 035c7e95ea3..4ff8dcfe6b2 100644 const target = /^target "(.*)"$/m.exec(yarnrc)![1]; return target; diff --git a/build/npm/postinstall.js b/build/npm/postinstall.js -index 8f8b0019a77..ea054c725be 100644 +index 8f8b0019a7792a993fbd6bf95b013b596aa2935a..ea054c725bea2eec342e12b07314241aa18a4951 100644 --- a/build/npm/postinstall.js +++ b/build/npm/postinstall.js @@ -33,10 +33,11 @@ function yarnInstall(location, opts) { @@ -127,7 +127,7 @@ index 8f8b0019a77..ea054c725be 100644 cp.execSync('git config pull.rebase true'); diff --git a/build/npm/preinstall.js b/build/npm/preinstall.js -index cb88d37adef..6b3253af0a3 100644 +index cb88d37adefd4882f61a2711fdd7f72b89e1a6e3..6b3253af0a3a0aa4d75456379ef1c00f4cb98d13 100644 --- a/build/npm/preinstall.js +++ b/build/npm/preinstall.js @@ -8,8 +8,9 @@ let err = false; @@ -144,7 +144,7 @@ index cb88d37adef..6b3253af0a3 100644 const cp = require('child_process'); diff --git a/coder.js b/coder.js new file mode 100644 -index 00000000000..9cb693af63b +index 0000000000000000000000000000000000000000..9cb693af63b86b4a6b35c442e6ea501a1076d18a --- /dev/null +++ b/coder.js @@ -0,0 +1,63 @@ @@ -212,7 +212,7 @@ index 00000000000..9cb693af63b + common.minifyTask("out-vscode") +)); diff --git a/extensions/postinstall.js b/extensions/postinstall.js -index da4fa3e9d04..50f3e1144f8 100644 +index da4fa3e9d0443d679dfbab1000b434af2ae01afd..50f3e1144f8057883dea8b91ec2f7073458dbd94 100644 --- a/extensions/postinstall.js +++ b/extensions/postinstall.js @@ -24,6 +24,9 @@ function processRoot() { @@ -226,7 +226,7 @@ index da4fa3e9d04..50f3e1144f8 100644 function processLib() { diff --git a/package.json b/package.json -index 226f51a1ec5..5c4e5af5f69 100644 +index 226f51a1ec55fc31ae90e175bc1ab62d74eb6294..5c4e5af5f69c0fd994f4b54cb7a9dfb20f868bfa 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,11 @@ @@ -267,7 +267,7 @@ index 226f51a1ec5..5c4e5af5f69 100644 } } diff --git a/product.json b/product.json -index 2b884d18f30..518b935b837 100644 +index 2b884d18f301b86bf29e3a8f1343cfb651f8727c..518b935b837dd21251089bdd317d6c3c03756d7b 100644 --- a/product.json +++ b/product.json @@ -20,7 +20,7 @@ @@ -281,7 +281,7 @@ index 2b884d18f30..518b935b837 100644 "ms-vscode.vscode-js-profile-flame", diff --git a/remote/.yarnrc b/remote/.yarnrc deleted file mode 100644 -index 1e16cde724c..00000000000 +index 1e16cde724c7703d2836b3641de48c99f7f47e68..0000000000000000000000000000000000000000 --- a/remote/.yarnrc +++ /dev/null @@ -1,3 +0,0 @@ @@ -289,7 +289,7 @@ index 1e16cde724c..00000000000 -target "12.4.0" -runtime "node" diff --git a/src/vs/base/common/network.ts b/src/vs/base/common/network.ts -index 1286c5117a4..e60dd11d039 100644 +index 1286c5117a4cae9d6075ed36f32f6414897d705b..e60dd11d03992800853e76d4d68b8ff211da7627 100644 --- a/src/vs/base/common/network.ts +++ b/src/vs/base/common/network.ts @@ -111,16 +111,17 @@ class RemoteAuthoritiesImpl { @@ -314,7 +314,7 @@ index 1286c5117a4..e60dd11d039 100644 }); } diff --git a/src/vs/base/common/platform.ts b/src/vs/base/common/platform.ts -index 0bbc5d6ef91..61f139b9c55 100644 +index 0bbc5d6ef911b1e98d26ad796873a9b6b7fb04ec..61f139b9c557b9c46e5a9640ab0e37a6fb7692ee 100644 --- a/src/vs/base/common/platform.ts +++ b/src/vs/base/common/platform.ts @@ -59,6 +59,17 @@ if (typeof navigator === 'object' && !isElectronRenderer) { @@ -336,7 +336,7 @@ index 0bbc5d6ef91..61f139b9c55 100644 _isWindows = (process.platform === 'win32'); _isMacintosh = (process.platform === 'darwin'); diff --git a/src/vs/base/common/processes.ts b/src/vs/base/common/processes.ts -index c52f7b3774f..08a87fa970f 100644 +index c52f7b3774f399d3fa161682316b20d807072806..08a87fa970f159f84691c5068cf5e38f0926015c 100644 --- a/src/vs/base/common/processes.ts +++ b/src/vs/base/common/processes.ts @@ -110,7 +110,8 @@ export function sanitizeProcessEnvironment(env: IProcessEnvironment, ...preserve @@ -350,7 +350,7 @@ index c52f7b3774f..08a87fa970f 100644 const envKeys = Object.keys(env); envKeys diff --git a/src/vs/base/common/uriIpc.ts b/src/vs/base/common/uriIpc.ts -index ef2291d49b1..29b2f9dfc2b 100644 +index ef2291d49b13c9c995afc90eab9c92afabc2b3b4..29b2f9dfc2b7fa998ac1188db06dee95419fcd5b 100644 --- a/src/vs/base/common/uriIpc.ts +++ b/src/vs/base/common/uriIpc.ts @@ -5,6 +5,7 @@ @@ -416,7 +416,7 @@ index ef2291d49b1..29b2f9dfc2b 100644 \ No newline at end of file +} diff --git a/src/vs/base/node/languagePacks.js b/src/vs/base/node/languagePacks.js -index 2c64061da7b..c0ef8faedd4 100644 +index 2c64061da7b01aef0bfe3cec851da232ca9461c8..c0ef8faedd406c38bf9c55bbbdbbb060046492d9 100644 --- a/src/vs/base/node/languagePacks.js +++ b/src/vs/base/node/languagePacks.js @@ -128,7 +128,10 @@ function factory(nodeRequire, path, fs, perf) { @@ -432,7 +432,7 @@ index 2c64061da7b..c0ef8faedd4 100644 // Do nothing. If we can't read the file we have no // language pack config. diff --git a/src/vs/code/browser/workbench/workbench.ts b/src/vs/code/browser/workbench/workbench.ts -index c629f7fffa1..c266e1fb06f 100644 +index c629f7fffa1faad78e5d9907fd38aec289db0428..c266e1fb06ffc44f1ec4230ac59fd094a53b257b 100644 --- a/src/vs/code/browser/workbench/workbench.ts +++ b/src/vs/code/browser/workbench/workbench.ts @@ -13,6 +13,8 @@ import { isFolderToOpen, isWorkspaceToOpen } from 'vs/platform/windows/common/wi @@ -532,7 +532,7 @@ index c629f7fffa1..c266e1fb06f 100644 // If no workspace is provided through the URL, check for config attribute from server if (!foundWorkspace) { diff --git a/src/vs/platform/environment/node/argv.ts b/src/vs/platform/environment/node/argv.ts -index 2379b626c81..28f8971cf39 100644 +index 2379b626c81321afe18267c69c6903efbfa354f4..28f8971cf398b048c7d8f56df2e9dc4e32aadb6d 100644 --- a/src/vs/platform/environment/node/argv.ts +++ b/src/vs/platform/environment/node/argv.ts @@ -8,6 +8,8 @@ import { localize } from 'vs/nls'; @@ -559,7 +559,7 @@ index 2379b626c81..28f8971cf39 100644 } - diff --git a/src/vs/platform/environment/node/environmentService.ts b/src/vs/platform/environment/node/environmentService.ts -index 5c0dc4ad4ae..38b8c7573a8 100644 +index 5c0dc4ad4ae79a172bed4bc3d6440cdf6dd22386..38b8c7573a872d587c5f3f6c5e0521d2bd918daa 100644 --- a/src/vs/platform/environment/node/environmentService.ts +++ b/src/vs/platform/environment/node/environmentService.ts @@ -38,6 +38,8 @@ export interface INativeEnvironmentService extends IEnvironmentService { @@ -586,7 +586,7 @@ index 5c0dc4ad4ae..38b8c7573a8 100644 get extensionDevelopmentLocationURI(): URI[] | undefined { const s = this._args.extensionDevelopmentPath; diff --git a/src/vs/platform/extensionManagement/node/extensionsScanner.ts b/src/vs/platform/extensionManagement/node/extensionsScanner.ts -index 575b2aafc38..873181f9678 100644 +index 575b2aafc3802cd6f5f943930e30de9f2c2690de..873181f967856759e3dc001e5bbe06e2f9eb676a 100644 --- a/src/vs/platform/extensionManagement/node/extensionsScanner.ts +++ b/src/vs/platform/extensionManagement/node/extensionsScanner.ts @@ -85,7 +85,7 @@ export class ExtensionsScanner extends Disposable { @@ -633,7 +633,7 @@ index 575b2aafc38..873181f9678 100644 + } } diff --git a/src/vs/platform/product/common/product.ts b/src/vs/platform/product/common/product.ts -index 3370a608b4b..37b3592d39d 100644 +index 3370a608b4b54c238a6ea69a92cdfcd4817cab57..37b3592d39d8ee3aed5455d599612485ea621323 100644 --- a/src/vs/platform/product/common/product.ts +++ b/src/vs/platform/product/common/product.ts @@ -30,6 +30,12 @@ if (isWeb) { @@ -650,7 +650,7 @@ index 3370a608b4b..37b3592d39d 100644 // Node: AMD loader diff --git a/src/vs/platform/product/common/productService.ts b/src/vs/platform/product/common/productService.ts -index 040c869d94c..bf16defcf7b 100644 +index 040c869d94ceb278350c1d752f55712feedda379..bf16defcf7bc4229dedbbe9eae8a965e996c69d9 100644 --- a/src/vs/platform/product/common/productService.ts +++ b/src/vs/platform/product/common/productService.ts @@ -30,6 +30,8 @@ export type ConfigurationSyncStore = { @@ -663,7 +663,7 @@ index 040c869d94c..bf16defcf7b 100644 readonly date?: string; readonly quality?: string; diff --git a/src/vs/platform/remote/browser/browserSocketFactory.ts b/src/vs/platform/remote/browser/browserSocketFactory.ts -index 3715cbb8e6e..c65de8ad37e 100644 +index 3715cbb8e6ee41c3d9b5090918d243b723ae2d00..c65de8ad37e727d66da97a8f8b170cbcef87181b 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 { @@ -684,7 +684,7 @@ index 3715cbb8e6e..c65de8ad37e 100644 - - diff --git a/src/vs/platform/remote/common/remoteAgentConnection.ts b/src/vs/platform/remote/common/remoteAgentConnection.ts -index 2185bb5228c..35463ca6520 100644 +index 2185bb5228c3f9603f307237e7f146fe386708d8..35463ca6520a7da2308d01a51ef2d3e544f10a10 100644 --- a/src/vs/platform/remote/common/remoteAgentConnection.ts +++ b/src/vs/platform/remote/common/remoteAgentConnection.ts @@ -89,7 +89,7 @@ async function connectToRemoteExtensionHostAgent(options: ISimpleConnectionOptio @@ -697,7 +697,7 @@ index 2185bb5228c..35463ca6520 100644 if (err || !socket) { options.logService.error(`${logPrefix} socketFactory.connect() failed. Error:`); diff --git a/src/vs/platform/storage/browser/storageService.ts b/src/vs/platform/storage/browser/storageService.ts -index 59b1baf912..cf9805554b 100644 +index 59b1baf9120cb0ccf1ebc425ed708224b5513d41..cf9805554b91176ac2521963a7775711e287e4cc 100644 --- a/src/vs/platform/storage/browser/storageService.ts +++ b/src/vs/platform/storage/browser/storageService.ts @@ -116,8 +116,8 @@ export class BrowserStorageService extends Disposable implements IStorageService @@ -712,7 +712,7 @@ index 59b1baf912..cf9805554b 100644 remove(key: string, scope: StorageScope): void { diff --git a/src/vs/platform/storage/common/storage.ts b/src/vs/platform/storage/common/storage.ts -index 1623957cb1..d366438d54 100644 +index 1623957cb18eedbf968cca6231d226b587f51935..d366438d54d36a86bd416aeb9f802ad9015f601c 100644 --- a/src/vs/platform/storage/common/storage.ts +++ b/src/vs/platform/storage/common/storage.ts @@ -83,7 +83,7 @@ export interface IStorageService { @@ -725,7 +725,7 @@ index 1623957cb1..d366438d54 100644 /** * Delete an element stored under the provided key from storage. diff --git a/src/vs/platform/storage/node/storageService.ts b/src/vs/platform/storage/node/storageService.ts -index 75514fe5a4..62d97c6048 100644 +index 75514fe5a4fabdc885556311954ab016c593bac3..62d97c60488856dfde0bb64fea85032b2e49bb94 100644 --- a/src/vs/platform/storage/node/storageService.ts +++ b/src/vs/platform/storage/node/storageService.ts @@ -204,8 +204,8 @@ export class NativeStorageService extends Disposable implements IStorageService @@ -741,7 +741,7 @@ index 75514fe5a4..62d97c6048 100644 remove(key: string, scope: StorageScope): void { diff --git a/src/vs/server/browser/client.ts b/src/vs/server/browser/client.ts new file mode 100644 -index 00000000000..3c0703b7174 +index 0000000000000000000000000000000000000000..3c0703b7174ad792a4b42841e96ee93765d71601 --- /dev/null +++ b/src/vs/server/browser/client.ts @@ -0,0 +1,189 @@ @@ -936,7 +936,7 @@ index 00000000000..3c0703b7174 +}; diff --git a/src/vs/server/browser/extHostNodeProxy.ts b/src/vs/server/browser/extHostNodeProxy.ts new file mode 100644 -index 00000000000..ed7c078077b +index 0000000000000000000000000000000000000000..ed7c078077b0c375758529959b280e091436113a --- /dev/null +++ b/src/vs/server/browser/extHostNodeProxy.ts @@ -0,0 +1,46 @@ @@ -988,7 +988,7 @@ index 00000000000..ed7c078077b +export const IExtHostNodeProxy = createDecorator('IExtHostNodeProxy'); diff --git a/src/vs/server/browser/mainThreadNodeProxy.ts b/src/vs/server/browser/mainThreadNodeProxy.ts new file mode 100644 -index 00000000000..0d2e93edae2 +index 0000000000000000000000000000000000000000..0d2e93edae2baf34d27b7b52be0bf4960f244531 --- /dev/null +++ b/src/vs/server/browser/mainThreadNodeProxy.ts @@ -0,0 +1,37 @@ @@ -1031,7 +1031,7 @@ index 00000000000..0d2e93edae2 +} diff --git a/src/vs/server/browser/worker.ts b/src/vs/server/browser/worker.ts new file mode 100644 -index 00000000000..5ae44cdc856 +index 0000000000000000000000000000000000000000..5ae44cdc856bf81326a4c516b8be9afb2c746a67 --- /dev/null +++ b/src/vs/server/browser/worker.ts @@ -0,0 +1,56 @@ @@ -1093,7 +1093,7 @@ index 00000000000..5ae44cdc856 +}; diff --git a/src/vs/server/common/nodeProxy.ts b/src/vs/server/common/nodeProxy.ts new file mode 100644 -index 00000000000..14b9de879ce +index 0000000000000000000000000000000000000000..14b9de879ceab4c1976770fa7810d276c5aa3e36 --- /dev/null +++ b/src/vs/server/common/nodeProxy.ts @@ -0,0 +1,47 @@ @@ -1146,7 +1146,7 @@ index 00000000000..14b9de879ce +} diff --git a/src/vs/server/common/telemetry.ts b/src/vs/server/common/telemetry.ts new file mode 100644 -index 00000000000..4ea6d95d36a +index 0000000000000000000000000000000000000000..4ea6d95d36aaac07dbd4d0e16ab3c1bba255f683 --- /dev/null +++ b/src/vs/server/common/telemetry.ts @@ -0,0 +1,65 @@ @@ -1217,7 +1217,7 @@ index 00000000000..4ea6d95d36a +} diff --git a/src/vs/server/entry.ts b/src/vs/server/entry.ts new file mode 100644 -index 00000000000..ab020fbb4e4 +index 0000000000000000000000000000000000000000..ab020fbb4e4ab3748cc807765ff9c362389faafa --- /dev/null +++ b/src/vs/server/entry.ts @@ -0,0 +1,78 @@ @@ -1301,7 +1301,7 @@ index 00000000000..ab020fbb4e4 +} diff --git a/src/vs/server/fork.js b/src/vs/server/fork.js new file mode 100644 -index 00000000000..56331ff1fc3 +index 0000000000000000000000000000000000000000..56331ff1fc32bbd82e769aaecb551e427f798ec3 --- /dev/null +++ b/src/vs/server/fork.js @@ -0,0 +1,3 @@ @@ -1310,7 +1310,7 @@ index 00000000000..56331ff1fc3 +require('../../bootstrap-amd').load('vs/server/entry'); diff --git a/src/vs/server/ipc.d.ts b/src/vs/server/ipc.d.ts new file mode 100644 -index 00000000000..33b28cf2d53 +index 0000000000000000000000000000000000000000..33b28cf2d53746ee9c50c056ac2e087dcee0a4e2 --- /dev/null +++ b/src/vs/server/ipc.d.ts @@ -0,0 +1,131 @@ @@ -1447,7 +1447,7 @@ index 00000000000..33b28cf2d53 +} diff --git a/src/vs/server/node/channel.ts b/src/vs/server/node/channel.ts new file mode 100644 -index 00000000000..e10cc9c218b +index 0000000000000000000000000000000000000000..e10cc9c218b27d859a523be3db5b8a30ef90d953 --- /dev/null +++ b/src/vs/server/node/channel.ts @@ -0,0 +1,360 @@ @@ -1813,7 +1813,7 @@ index 00000000000..e10cc9c218b +} diff --git a/src/vs/server/node/connection.ts b/src/vs/server/node/connection.ts new file mode 100644 -index 00000000000..36e80fb6966 +index 0000000000000000000000000000000000000000..36e80fb6966ae2cb53c98f3d31e2193d00c509c3 --- /dev/null +++ b/src/vs/server/node/connection.ts @@ -0,0 +1,157 @@ @@ -1976,7 +1976,7 @@ index 00000000000..36e80fb6966 +} diff --git a/src/vs/server/node/insights.ts b/src/vs/server/node/insights.ts new file mode 100644 -index 00000000000..a0ece345f28 +index 0000000000000000000000000000000000000000..a0ece345f28f06afb2af12fe4901ad228b2475a4 --- /dev/null +++ b/src/vs/server/node/insights.ts @@ -0,0 +1,124 @@ @@ -2106,7 +2106,7 @@ index 00000000000..a0ece345f28 +} diff --git a/src/vs/server/node/ipc.ts b/src/vs/server/node/ipc.ts new file mode 100644 -index 00000000000..5e560eb46e6 +index 0000000000000000000000000000000000000000..5e560eb46e6a0a18c91e440c655ac0d44b09b6dd --- /dev/null +++ b/src/vs/server/node/ipc.ts @@ -0,0 +1,61 @@ @@ -2173,7 +2173,7 @@ index 00000000000..5e560eb46e6 +export const ipcMain = new IpcMain(); diff --git a/src/vs/server/node/logger.ts b/src/vs/server/node/logger.ts new file mode 100644 -index 00000000000..2a39c524aaa +index 0000000000000000000000000000000000000000..2a39c524aaa1b4031e04a631842f30b6fec3d98a --- /dev/null +++ b/src/vs/server/node/logger.ts @@ -0,0 +1,2 @@ @@ -2181,7 +2181,7 @@ index 00000000000..2a39c524aaa +export const logger = baseLogger.named('vscode'); diff --git a/src/vs/server/node/marketplace.ts b/src/vs/server/node/marketplace.ts new file mode 100644 -index 00000000000..8956fc40d48 +index 0000000000000000000000000000000000000000..8956fc40d48448b9932036c4c286464881807338 --- /dev/null +++ b/src/vs/server/node/marketplace.ts @@ -0,0 +1,174 @@ @@ -2361,7 +2361,7 @@ index 00000000000..8956fc40d48 +}; diff --git a/src/vs/server/node/nls.ts b/src/vs/server/node/nls.ts new file mode 100644 -index 00000000000..3d428a57d31 +index 0000000000000000000000000000000000000000..3d428a57d31f29c40f9c3ce45f715b443badf4e9 --- /dev/null +++ b/src/vs/server/node/nls.ts @@ -0,0 +1,88 @@ @@ -2455,7 +2455,7 @@ index 00000000000..3d428a57d31 +}; diff --git a/src/vs/server/node/protocol.ts b/src/vs/server/node/protocol.ts new file mode 100644 -index 00000000000..3c74512192a +index 0000000000000000000000000000000000000000..3c74512192aec6220216bc8563b3127b9cfd5fbf --- /dev/null +++ b/src/vs/server/node/protocol.ts @@ -0,0 +1,73 @@ @@ -2534,7 +2534,7 @@ index 00000000000..3c74512192a +} diff --git a/src/vs/server/node/server.ts b/src/vs/server/node/server.ts new file mode 100644 -index 00000000000..4b88fedb2f0 +index 0000000000000000000000000000000000000000..4b88fedb2f05d300fb50978e63721d4d04b7fb5f --- /dev/null +++ b/src/vs/server/node/server.ts @@ -0,0 +1,285 @@ @@ -2825,7 +2825,7 @@ index 00000000000..4b88fedb2f0 +} diff --git a/src/vs/server/node/util.ts b/src/vs/server/node/util.ts new file mode 100644 -index 00000000000..fa47e993b46 +index 0000000000000000000000000000000000000000..fa47e993b46802f1a26457649e9e8bc467a73bf2 --- /dev/null +++ b/src/vs/server/node/util.ts @@ -0,0 +1,13 @@ @@ -2843,7 +2843,7 @@ index 00000000000..fa47e993b46 + return path.split("/").map((p) => encodeURIComponent(p)).join("/"); +}; diff --git a/src/vs/workbench/api/browser/extensionHost.contribution.ts b/src/vs/workbench/api/browser/extensionHost.contribution.ts -index 3d77009b908..11deb1b99ac 100644 +index 3d77009b908f61690a56dc589360627f6f5a3924..11deb1b99ac9d3baa4aa583d711a5e020b4379ec 100644 --- a/src/vs/workbench/api/browser/extensionHost.contribution.ts +++ b/src/vs/workbench/api/browser/extensionHost.contribution.ts @@ -60,6 +60,7 @@ import './mainThreadComments'; @@ -2855,7 +2855,7 @@ index 3d77009b908..11deb1b99ac 100644 import './mainThreadAuthentication'; import './mainThreadTimeline'; diff --git a/src/vs/workbench/api/browser/mainThreadStorage.ts b/src/vs/workbench/api/browser/mainThreadStorage.ts -index 7bc3904963..c6db2368ae 100644 +index 7bc3904963bed2925f3640b6bd929347159dd3cf..c6db2368ae9eaca61889efcf3c49763c01ff7459 100644 --- a/src/vs/workbench/api/browser/mainThreadStorage.ts +++ b/src/vs/workbench/api/browser/mainThreadStorage.ts @@ -58,11 +58,11 @@ export class MainThreadStorage implements MainThreadStorageShape { @@ -2873,7 +2873,7 @@ index 7bc3904963..c6db2368ae 100644 return Promise.reject(err); } diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts -index 97793666ad8..13cd137db1e 100644 +index 97793666ad8abf7d052ba96a88565042b21ebcec..13cd137db1e9435ef66ade3220774b1ddb608d91 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -68,6 +68,7 @@ import { IURITransformerService } from 'vs/workbench/api/common/extHostUriTransf @@ -2901,7 +2901,7 @@ index 97793666ad8..13cd137db1e 100644 rpcProtocol.set(ExtHostContext.ExtHostWindow, extHostWindow); diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts -index eb5d8ea8455..da9eb521ca4 100644 +index eb5d8ea84551030a9d72918813fca7adb49a5cd8..da9eb521ca4c660de1bb23df52c18c0efe6f5979 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -769,6 +769,16 @@ export interface MainThreadLabelServiceShape extends IDisposable { @@ -2938,7 +2938,7 @@ index eb5d8ea8455..da9eb521ca4 100644 ExtHostTunnelService: createMainId('ExtHostTunnelService'), ExtHostAuthentication: createMainId('ExtHostAuthentication'), diff --git a/src/vs/workbench/api/common/extHostExtensionService.ts b/src/vs/workbench/api/common/extHostExtensionService.ts -index 34639e18b6f..9c22fe6f090 100644 +index 34639e18b6fb567feaf19cf20bd312b6b578723f..9c22fe6f090f3cfceea5c3f41695a1ab1d797a19 100644 --- a/src/vs/workbench/api/common/extHostExtensionService.ts +++ b/src/vs/workbench/api/common/extHostExtensionService.ts @@ -32,6 +32,7 @@ import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitData @@ -2992,7 +2992,7 @@ index 34639e18b6f..9c22fe6f090 100644 } diff --git a/src/vs/workbench/api/node/extHost.node.services.ts b/src/vs/workbench/api/node/extHost.node.services.ts -index b3c89e51cfc..e21abe4e13b 100644 +index b3c89e51cfc25a53293a352a2a8ad50d5f26d595..e21abe4e13bc25a5b72f556bbfb61085842faeb7 100644 --- a/src/vs/workbench/api/node/extHost.node.services.ts +++ b/src/vs/workbench/api/node/extHost.node.services.ts @@ -3,6 +3,8 @@ @@ -3010,7 +3010,7 @@ index b3c89e51cfc..e21abe4e13b 100644 registerSingleton(IExtHostTunnelService, ExtHostTunnelService); +registerSingleton(IExtHostNodeProxy, class extends NotImplementedProxy(String(IExtHostNodeProxy)) { whenReady = Promise.resolve(); }); diff --git a/src/vs/workbench/api/worker/extHost.worker.services.ts b/src/vs/workbench/api/worker/extHost.worker.services.ts -index 3843fdec386..8aac4df5278 100644 +index 3843fdec386edc09a1d361b63de892a04e0070ed..8aac4df527857e964798362a69f5591bef07c165 100644 --- a/src/vs/workbench/api/worker/extHost.worker.services.ts +++ b/src/vs/workbench/api/worker/extHost.worker.services.ts @@ -8,6 +8,7 @@ import { ILogService } from 'vs/platform/log/common/log'; @@ -3027,7 +3027,7 @@ index 3843fdec386..8aac4df5278 100644 registerSingleton(ILogService, ExtHostLogService); +registerSingleton(IExtHostNodeProxy, ExtHostNodeProxy); diff --git a/src/vs/workbench/api/worker/extHostExtensionService.ts b/src/vs/workbench/api/worker/extHostExtensionService.ts -index c71ab1c7da4..572b07ff251 100644 +index c71ab1c7da462da8f4a12146d45e6cde7f06ad81..572b07ff2516154f49ab9e02bfcab2b4d8b3009f 100644 --- a/src/vs/workbench/api/worker/extHostExtensionService.ts +++ b/src/vs/workbench/api/worker/extHostExtensionService.ts @@ -9,6 +9,7 @@ import { AbstractExtHostExtensionService } from 'vs/workbench/api/common/extHost @@ -3057,7 +3057,7 @@ index c71ab1c7da4..572b07ff251 100644 module = module.with({ path: ensureSuffix(module.path, '.js') }); const response = await fetch(module.toString(true)); diff --git a/src/vs/workbench/browser/parts/activitybar/media/activitybarpart.css b/src/vs/workbench/browser/parts/activitybar/media/activitybarpart.css -index ced2d815834..dfcae73e8a0 100644 +index ced2d815834e40a1543e80516472799075980733..dfcae73e8a042307600c67f163aa00ba9e0762f4 100644 --- a/src/vs/workbench/browser/parts/activitybar/media/activitybarpart.css +++ b/src/vs/workbench/browser/parts/activitybar/media/activitybarpart.css @@ -55,6 +55,10 @@ @@ -3072,7 +3072,7 @@ index ced2d815834..dfcae73e8a0 100644 .monaco-workbench .activitybar > .content > .home-bar > .home-bar-icon-badge { diff --git a/src/vs/workbench/browser/web.main.ts b/src/vs/workbench/browser/web.main.ts -index 0462617196b..11434d27af9 100644 +index 0462617196b39111cb22e5abbf4b096406496bf8..11434d27af9ce3262c57918e0f5a44d4eebcf8ac 100644 --- a/src/vs/workbench/browser/web.main.ts +++ b/src/vs/workbench/browser/web.main.ts @@ -45,6 +45,7 @@ import { FileLogService } from 'vs/platform/log/common/fileLogService'; @@ -3093,7 +3093,7 @@ index 0462617196b..11434d27af9 100644 return instantiationService.invokeFunction(accessor => { const commandService = accessor.get(ICommandService); diff --git a/src/vs/workbench/common/resources.ts b/src/vs/workbench/common/resources.ts -index 18ea0bfedb4..d59a17c17f4 100644 +index 18ea0bfedb4492327429a38237b05915b29f6dd0..d59a17c17f4fffa23d786ce36b4ff624d5688a58 100644 --- a/src/vs/workbench/common/resources.ts +++ b/src/vs/workbench/common/resources.ts @@ -15,6 +15,7 @@ import { ParsedExpression, IExpression, parse } from 'vs/base/common/glob'; @@ -3115,7 +3115,7 @@ index 18ea0bfedb4..d59a17c17f4 100644 this._langIdKey.set(value ? this._modeService.getModeIdByFilepathOrFirstLine(value) : null); this._extensionKey.set(value ? extname(value) : null); diff --git a/src/vs/workbench/contrib/scm/browser/media/scm.css b/src/vs/workbench/contrib/scm/browser/media/scm.css -index 9947f240bf2..bdba0a2fc64 100644 +index 9947f240bf20b42069bd3d50a96d7a783615f54b..bdba0a2fc64a2e6c2cd2644bcc6afc0d131501a7 100644 --- a/src/vs/workbench/contrib/scm/browser/media/scm.css +++ b/src/vs/workbench/contrib/scm/browser/media/scm.css @@ -138,9 +138,11 @@ @@ -3134,7 +3134,7 @@ index 9947f240bf2..bdba0a2fc64 100644 .scm-view .monaco-list .monaco-list-row .resource-group > .actions, .scm-view .monaco-list .monaco-list-row .resource > .name > .monaco-icon-label > .actions { diff --git a/src/vs/workbench/services/dialogs/browser/dialogService.ts b/src/vs/workbench/services/dialogs/browser/dialogService.ts -index 6e3182a696d..7df85da165a 100644 +index 6e3182a696dd3443e68ad9e92d029b6ec2d01677..7df85da165a3ba157629c6b9e92f08dd18c7511a 100644 --- a/src/vs/workbench/services/dialogs/browser/dialogService.ts +++ b/src/vs/workbench/services/dialogs/browser/dialogService.ts @@ -124,11 +124,12 @@ export class DialogService implements IDialogService { @@ -3153,7 +3153,7 @@ index 6e3182a696d..7df85da165a 100644 }; diff --git a/src/vs/workbench/services/environment/browser/environmentService.ts b/src/vs/workbench/services/environment/browser/environmentService.ts -index ba2701ec54d..4d4aaa6958b 100644 +index ba2701ec54d1a70eaf66afacce585fe906644319..4d4aaa6958b636480178470570e856e62ab922ee 100644 --- a/src/vs/workbench/services/environment/browser/environmentService.ts +++ b/src/vs/workbench/services/environment/browser/environmentService.ts @@ -121,8 +121,18 @@ export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironment @@ -3191,7 +3191,7 @@ index ba2701ec54d..4d4aaa6958b 100644 } } diff --git a/src/vs/workbench/services/extensionManagement/common/extensionEnablementService.ts b/src/vs/workbench/services/extensionManagement/common/extensionEnablementService.ts -index c28b1477400..6090200d9c3 100644 +index c28b14774005509f58dddd2dec25547bac85e09f..6090200d9c3671fc1239880dbd060a01a84db1fb 100644 --- a/src/vs/workbench/services/extensionManagement/common/extensionEnablementService.ts +++ b/src/vs/workbench/services/extensionManagement/common/extensionEnablementService.ts @@ -163,7 +163,7 @@ export class ExtensionEnablementService extends Disposable implements IWorkbench @@ -3204,7 +3204,7 @@ index c28b1477400..6090200d9c3 100644 return false; } diff --git a/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts b/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts -index 33eb56db3c2..e5167794c3f 100644 +index 33eb56db3c25a0dc028b0d54dfa102e5584441cf..e5167794c3f761b06c9745e12d49b4a5257b48ef 100644 --- a/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts +++ b/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts @@ -236,6 +236,11 @@ export class ExtensionManagementService extends Disposable implements IExtension @@ -3220,7 +3220,7 @@ index 33eb56db3c2..e5167794c3f 100644 const error = new Error(localize('cannot be installed', "Cannot install '{0}' because this extension has defined that it cannot run on the remote server.", gallery.displayName || gallery.name)); error.name = INSTALL_ERROR_NOT_SUPPORTED; diff --git a/src/vs/workbench/services/extensions/browser/extensionService.ts b/src/vs/workbench/services/extensions/browser/extensionService.ts -index d0710e77fa2..ceb27174aee 100644 +index d0710e77fa28aacf5b4dfe85efbf67a6a9ae78ab..ceb27174aee3c78ca5a086f05a6b1d3188888034 100644 --- a/src/vs/workbench/services/extensions/browser/extensionService.ts +++ b/src/vs/workbench/services/extensions/browser/extensionService.ts @@ -116,8 +116,10 @@ export class ExtensionService extends AbstractExtensionService implements IExten @@ -3236,7 +3236,7 @@ index d0710e77fa2..ceb27174aee 100644 const remoteAgentConnection = this._remoteAgentService.getConnection(); this._runningLocation = _determineRunningLocation(this._productService, this._configService, localExtensions, remoteExtensions, Boolean(remoteEnv && remoteAgentConnection)); diff --git a/src/vs/workbench/services/extensions/common/extensionsUtil.ts b/src/vs/workbench/services/extensions/common/extensionsUtil.ts -index 65e532ee58d..0b6282fde7a 100644 +index 65e532ee58dfc06ed944846d01b885cb8f260ebc..0b6282fde7ad03c7ea9872a777cbf487253abed1 100644 --- a/src/vs/workbench/services/extensions/common/extensionsUtil.ts +++ b/src/vs/workbench/services/extensions/common/extensionsUtil.ts @@ -37,7 +37,8 @@ export function canExecuteOnWorkspace(manifest: IExtensionManifest, productServi @@ -3250,7 +3250,7 @@ index 65e532ee58d..0b6282fde7a 100644 export function getExtensionKind(manifest: IExtensionManifest, productService: IProductService, configurationService: IConfigurationService): ExtensionKind[] { diff --git a/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts b/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts -index 49542eda74c..de0e2da0a4c 100644 +index 49542eda74c65e485272cd37d586911886aa3ad7..de0e2da0a4c2dca91dc7e0e48c28a8a75ca3e7d4 100644 --- a/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts +++ b/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts @@ -16,7 +16,7 @@ import { IInitData } from 'vs/workbench/api/common/extHost.protocol'; @@ -3307,7 +3307,7 @@ index 49542eda74c..de0e2da0a4c 100644 console.error(e); } diff --git a/src/vs/workbench/services/extensions/worker/extensionHostWorkerMain.ts b/src/vs/workbench/services/extensions/worker/extensionHostWorkerMain.ts -index 79455414c06..a407593b4dc 100644 +index 79455414c06b95612a0dce2cad01f2bb2f40ef49..a407593b4dc6053309ed560898918cf67470e836 100644 --- a/src/vs/workbench/services/extensions/worker/extensionHostWorkerMain.ts +++ b/src/vs/workbench/services/extensions/worker/extensionHostWorkerMain.ts @@ -14,7 +14,11 @@ @@ -3324,7 +3324,7 @@ index 79455414c06..a407593b4dc 100644 require(['vs/workbench/services/extensions/worker/extensionHostWorker'], () => { }, err => console.error(err)); diff --git a/src/vs/workbench/services/localizations/electron-browser/localizationsService.ts b/src/vs/workbench/services/localizations/electron-browser/localizationsService.ts -index 44999bd842e..601b1c54088 100644 +index 44999bd842eae12b752b2e7e8c4904272b111dc1..601b1c5408835c743fe07e34da4d4534873bf832 100644 --- a/src/vs/workbench/services/localizations/electron-browser/localizationsService.ts +++ b/src/vs/workbench/services/localizations/electron-browser/localizationsService.ts @@ -5,17 +5,17 @@ @@ -3349,7 +3349,7 @@ index 44999bd842e..601b1c54088 100644 } diff --git a/src/vs/workbench/workbench.web.main.ts b/src/vs/workbench/workbench.web.main.ts -index 0669178db4c..28fafeb2de2 100644 +index 0669178db4cf5efe28ffd2a8fe3301de47bcc545..28fafeb2de2efea5c6412853044ce84775f1e038 100644 --- a/src/vs/workbench/workbench.web.main.ts +++ b/src/vs/workbench/workbench.web.main.ts @@ -35,7 +35,8 @@ import 'vs/workbench/services/textfile/browser/browserTextFileService'; @@ -3363,7 +3363,7 @@ index 0669178db4c..28fafeb2de2 100644 import 'vs/workbench/services/credentials/browser/credentialsService'; import 'vs/workbench/services/url/browser/urlService'; diff --git a/yarn.lock b/yarn.lock -index b2fbf543af3..f10dddd6594 100644 +index b2fbf543af319fcc3973248b4ed4981db1ca213a..f10dddd6594bed959e2caa69911f70a6ae8e0554 100644 --- a/yarn.lock +++ b/yarn.lock @@ -140,6 +140,23 @@ From 9fb318cf15191f2766c39e5d1eb549ff11ca1487 Mon Sep 17 00:00:00 2001 From: Anmol Sethi Date: Thu, 3 Sep 2020 18:38:40 -0400 Subject: [PATCH 04/75] docker: Fix $DOCKER_USER (#2057) We do not try renaming $HOME anymore as there is no good way to do it. We also only try to convert if the user hasn't been changed. Finally I added usage to the docker docs in install.md Closes #2056 --- ci/release-image/Dockerfile | 5 ++++- ci/release-image/entrypoint.sh | 28 +++++++++++++++------------- doc/install.md | 3 ++- 3 files changed, 21 insertions(+), 15 deletions(-) diff --git a/ci/release-image/Dockerfile b/ci/release-image/Dockerfile index 4dcd2bfb4..5c31ecbe7 100644 --- a/ci/release-image/Dockerfile +++ b/ci/release-image/Dockerfile @@ -39,6 +39,9 @@ COPY ci/release-image/entrypoint.sh /usr/bin/entrypoint.sh RUN dpkg -i /tmp/code-server*$(dpkg --print-architecture).deb && rm /tmp/code-server*.deb EXPOSE 8080 -USER coder +# This way, if someone sets $DOCKER_USER, docker-exec will still work as +# the uid will remain the same. note: only relevant if -u isn't passed to +# docker-run. +USER 1000 WORKDIR /home/coder ENTRYPOINT ["/usr/bin/entrypoint.sh", "--bind-addr", "0.0.0.0:8080", "."] diff --git a/ci/release-image/entrypoint.sh b/ci/release-image/entrypoint.sh index 6e7525ce6..ee58d07a6 100755 --- a/ci/release-image/entrypoint.sh +++ b/ci/release-image/entrypoint.sh @@ -1,18 +1,20 @@ -#!/usr/bin/env sh +#!/bin/sh set -eu -if [ "${DOCKER_USER-}" ]; then - echo "$DOCKER_USER ALL=(ALL) NOPASSWD:ALL" | sudo tee -a /etc/sudoers.d/nopasswd > /dev/null - sudo usermod --login "$DOCKER_USER" \ - --move-home --home "/home/$DOCKER_USER" \ - coder - sudo groupmod -n "$DOCKER_USER" coder - - sudo sed -i "/coder/d" /etc/sudoers.d/nopasswd - sudo sed -i "s/coder/$DOCKER_USER/g" /etc/fixuid/config.yml - export HOME="/home/$DOCKER_USER" -fi - # This isn't set by default. export USER="$(whoami)" + +if [ "${DOCKER_USER-}" != "$USER" ]; then + echo "$DOCKER_USER ALL=(ALL) NOPASSWD:ALL" | sudo tee -a /etc/sudoers.d/nopasswd > /dev/null + # Unfortunately we cannot change $HOME as we cannot move any bind mounts + # nor can we bind mount $HOME into a new home as that requires a privileged container. + sudo usermod --login "$DOCKER_USER" coder + sudo groupmod -n "$DOCKER_USER" coder + + export USER="$(whoami)" + + sudo sed -i "/coder/d" /etc/sudoers.d/nopasswd + sudo sed -i "s/coder/$DOCKER_USER/g" /etc/fixuid/config.yml +fi + dumb-init fixuid -q /usr/bin/code-server "$@" diff --git a/doc/install.md b/doc/install.md index 5938cff07..8d7e98ba8 100644 --- a/doc/install.md +++ b/doc/install.md @@ -179,10 +179,11 @@ code-server # easily access/modify your code-server config in $HOME/.config/code-server/config.json # outside the container. mkdir -p ~/.config -docker run -it -p 127.0.0.1:8080:8080 \ +docker run -it --name code-server -p 127.0.0.1:8080:8080 \ -v "$HOME/.config:/home/coder/.config" \ -v "$PWD:/home/coder/project" \ -u "$(id -u):$(id -g)" \ + -e "DOCKER_USER=$USER" \ codercom/code-server:latest ``` From 7991e09bbc6bbbbb461c11281b45585b32bbcb6b Mon Sep 17 00:00:00 2001 From: Anmol Sethi Date: Fri, 4 Sep 2020 06:30:15 -0400 Subject: [PATCH 05/75] Skip update tests (#2059) We don't use auto updating anymore and the tests are randomly failing so just disabling for now. --- test/update.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/test/update.test.ts b/test/update.test.ts index 15719bfac..7e4b80f21 100644 --- a/test/update.test.ts +++ b/test/update.test.ts @@ -8,6 +8,7 @@ import { SettingsProvider, UpdateSettings } from "../src/node/settings" import { tmpdir } from "../src/node/util" describe("update", () => { + return let version = "1.0.0" let spy: string[] = [] const server = http.createServer((request: http.IncomingMessage, response: http.ServerResponse) => { From e44e574ce106b75907acbbe6125713826f0110ec Mon Sep 17 00:00:00 2001 From: Asher Date: Fri, 4 Sep 2020 10:10:40 -0500 Subject: [PATCH 06/75] Fix language packs (#2058) * Fix incorrect nls.json fetch When moving this out of the HTML I didn't remove {{BASE}}. * Fix language package installation Updates #2046. --- ci/dev/vscode.patch | 17 +++++++++++++++-- src/browser/pages/vscode.ts | 2 +- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/ci/dev/vscode.patch b/ci/dev/vscode.patch index 107a22f9a..5fe34a20f 100644 --- a/ci/dev/vscode.patch +++ b/ci/dev/vscode.patch @@ -3204,10 +3204,23 @@ index c28b14774005509f58dddd2dec25547bac85e09f..6090200d9c3671fc1239880dbd060a01 return false; } diff --git a/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts b/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts -index 33eb56db3c25a0dc028b0d54dfa102e5584441cf..e5167794c3f761b06c9745e12d49b4a5257b48ef 100644 +index 33eb56db3c25a0dc028b0d54dfa102e5584441cf..de70af33529e40a56969d8f241c82906cda72e1e 100644 --- a/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts +++ b/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts -@@ -236,6 +236,11 @@ export class ExtensionManagementService extends Disposable implements IExtension +@@ -202,8 +202,11 @@ export class ExtensionManagementService extends Disposable implements IExtension + } + + // Install Language pack on all servers ++ // NOTE@coder: It does not appear language packs can be installed on the web ++ // extension management server at this time. Filter out the web to fix this. + if (isLanguagePackExtension(manifest)) { +- return Promise.all(this.servers.map(server => server.extensionManagementService.installFromGallery(gallery))).then(([local]) => local); ++ const servers = this.servers.filter(s => s !== this.extensionManagementServerService.webExtensionManagementServer); ++ return Promise.all(servers.map(server => server.extensionManagementService.installFromGallery(gallery))).then(([local]) => local); + } + + // 1. Install on preferred location +@@ -236,6 +239,11 @@ export class ExtensionManagementService extends Disposable implements IExtension return this.extensionManagementServerService.webExtensionManagementServer.extensionManagementService.installFromGallery(gallery); } diff --git a/src/browser/pages/vscode.ts b/src/browser/pages/vscode.ts index feb38a289..5810c253a 100644 --- a/src/browser/pages/vscode.ts +++ b/src/browser/pages/vscode.ts @@ -17,7 +17,7 @@ try { } // FIXME: Only works if path separators are /. const path = nlsConfig._resolvedLanguagePackCoreLocation + "/" + bundle.replace(/\//g, "!") + ".nls.json" - fetch(`{{BASE}}/resource/?path=${encodeURIComponent(path)}`) + fetch(`${options.base}/resource/?path=${encodeURIComponent(path)}`) .then((response) => response.json()) .then((json) => { bundles[bundle] = json From 0a2328c1f6bc047186cd71575d16d7ed523f3446 Mon Sep 17 00:00:00 2001 From: Asher Date: Tue, 8 Sep 2020 13:59:01 -0500 Subject: [PATCH 07/75] Don't require auth for healthz (#2055) * Don't require authentication for healthz endpoint * Add FAQ entry for /healthz --- doc/FAQ.md | 15 +++++++++++++++ src/node/app/health.ts | 17 +++-------------- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/doc/FAQ.md b/doc/FAQ.md index 129498e4f..370dd6660 100644 --- a/doc/FAQ.md +++ b/doc/FAQ.md @@ -19,6 +19,7 @@ - [How does code-server decide what workspace or folder to open?](#how-does-code-server-decide-what-workspace-or-folder-to-open) - [How do I debug issues with code-server?](#how-do-i-debug-issues-with-code-server) - [Heartbeat File](#heartbeat-file) +- [Healthz endpoint](#healthz-endpoint) - [How does the config file work?](#how-does-the-config-file-work) - [Blank screen on iPad?](#blank-screen-on-ipad) - [Isn't an install script piped into sh insecure?](#isnt-an-install-script-piped-into-sh-insecure) @@ -242,6 +243,20 @@ older than X minutes, kill `code-server`. [#1636](https://github.com/cdr/code-server/issues/1636) will make the experience here better. +## Healthz endpoint + +`code-server` exposes an endpoint at `/healthz` which can be used to check +whether `code-server` is up without triggering a heartbeat. The response will +include a status (`alive` or `expired`) and a timestamp for the last heartbeat +(defaults to `0`). This endpoint does not require authentication. + +```json +{ + "status": "alive", + "lastHeartbeat": 1599166210566 +} +``` + ## How does the config file work? When `code-server` starts up, it creates a default config file in `~/.config/code-server/config.yaml` that looks diff --git a/src/node/app/health.ts b/src/node/app/health.ts index 6a3aae94c..48d6897cf 100644 --- a/src/node/app/health.ts +++ b/src/node/app/health.ts @@ -1,6 +1,4 @@ -import * as http from "http" -import { HttpCode, HttpError } from "../../common/http" -import { HttpProvider, HttpResponse, Route, Heart, HttpProviderOptions } from "../http" +import { HttpProvider, HttpResponse, Heart, HttpProviderOptions } from "../http" /** * Check the heartbeat. @@ -10,15 +8,8 @@ export class HealthHttpProvider extends HttpProvider { super(options) } - public async handleRequest(route: Route, request: http.IncomingMessage): Promise { - if (!this.authenticated(request)) { - if (this.isRoot(route)) { - return { redirect: "/login", query: { to: route.fullPath } } - } - throw new HttpError("Unauthorized", HttpCode.Unauthorized) - } - - const result = { + public async handleRequest(): Promise { + return { cache: false, mime: "application/json", content: { @@ -26,7 +17,5 @@ export class HealthHttpProvider extends HttpProvider { lastHeartbeat: this.heart.lastHeartbeat, }, } - - return result } } From fef619aef8dc0479fb36f98fe434915082b0be46 Mon Sep 17 00:00:00 2001 From: Asher Date: Tue, 8 Sep 2020 14:06:41 -0500 Subject: [PATCH 08/75] Fix incorrect login script src path --- src/browser/pages/login.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/browser/pages/login.html b/src/browser/pages/login.html index f2b262991..fc772f392 100644 --- a/src/browser/pages/login.html +++ b/src/browser/pages/login.html @@ -47,5 +47,5 @@ - + From 938b4606855c48bf36a6bd0c6b8b99c222c7e879 Mon Sep 17 00:00:00 2001 From: Asher Date: Wed, 9 Sep 2020 12:05:04 -0500 Subject: [PATCH 09/75] Add trailing slash to service worker scope This will ensure it always matches or is underneath the allowed service worker scope. Fixes #2076. --- src/browser/register.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/browser/register.ts b/src/browser/register.ts index 60f054bc3..4f8345808 100644 --- a/src/browser/register.ts +++ b/src/browser/register.ts @@ -10,7 +10,7 @@ if ("serviceWorker" in navigator) { const path = normalize(`${options.csStaticBase}/dist/serviceWorker.js`) navigator.serviceWorker .register(path, { - scope: options.base || "/", + scope: (options.base ?? "") + "/", }) .then(() => { console.log("[Service Worker] registered") From ffe6a663aa1c3e634e97cd200d1a7a6abab500bf Mon Sep 17 00:00:00 2001 From: Asher Date: Wed, 9 Sep 2020 12:05:44 -0500 Subject: [PATCH 10/75] Add /vscode to nls fetch A plugin may modify the root endpoint which will make /resource no longer work so always use /vscode/resource instead. --- src/browser/pages/vscode.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/browser/pages/vscode.ts b/src/browser/pages/vscode.ts index 5810c253a..3ca1db83e 100644 --- a/src/browser/pages/vscode.ts +++ b/src/browser/pages/vscode.ts @@ -17,7 +17,7 @@ try { } // FIXME: Only works if path separators are /. const path = nlsConfig._resolvedLanguagePackCoreLocation + "/" + bundle.replace(/\//g, "!") + ".nls.json" - fetch(`${options.base}/resource/?path=${encodeURIComponent(path)}`) + fetch(`${options.base}/vscode/resource/?path=${encodeURIComponent(path)}`) .then((response) => response.json()) .then((json) => { bundles[bundle] = json From e998dc1e82209631c8b17e794a3f743060f5feb4 Mon Sep 17 00:00:00 2001 From: David Harkness Date: Thu, 10 Sep 2020 16:01:39 -0700 Subject: [PATCH 11/75] Minor readme grammar fixes (#2074) --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 3c94712fa..f395968ce 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Run [VS Code](https://github.com/Microsoft/vscode) on any machine anywhere and a - Develop on a Linux machine and pick up from any device with a web browser. - **Server-powered** - Take advantage of large cloud servers to speed up tests, compilations, downloads, and more. - - Preserve battery life when you're on the go as all intensive tasks runs on your server. + - Preserve battery life when you're on the go as all intensive tasks run on your server. - Make use of a spare computer you have lying around and turn it into a full development environment. ## Getting Started @@ -52,7 +52,7 @@ See [./doc/CONTRIBUTING.md](./doc/CONTRIBUTING.md). ## Hiring -We ([@cdr](https://github.com/cdr)) are looking for a engineers to help maintain +We ([@cdr](https://github.com/cdr)) are looking for engineers to help maintain code-server, innovate on open source and streamline dev workflows. Our main office is in Austin, Texas. Remote is ok as long as From cc5ed1eb5768e481f5c7a42c908b45bc03afced1 Mon Sep 17 00:00:00 2001 From: Anmol Sethi Date: Tue, 8 Sep 2020 14:54:23 -0400 Subject: [PATCH 12/75] Allow installing extensions from the CLI while $VSCODE_IPC_HOOK_CLI Closes #2083 --- src/node/entry.ts | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/src/node/entry.ts b/src/node/entry.ts index 692559663..a416ae993 100644 --- a/src/node/entry.ts +++ b/src/node/entry.ts @@ -166,6 +166,28 @@ async function entry(): Promise { console.log(version, commit) } process.exit(0) + } else if (args["list-extensions"] || args["install-extension"] || args["uninstall-extension"]) { + logger.debug("forking vs code cli...") + const vscode = cp.fork(path.resolve(__dirname, "../../lib/vscode/out/vs/server/fork"), [], { + env: { + ...process.env, + CODE_SERVER_PARENT_PID: process.pid.toString(), + }, + }) + vscode.once("message", (message: any) => { + logger.debug("Got message from VS Code", field("message", message)) + if (message.type !== "ready") { + logger.error("Unexpected response waiting for ready response") + process.exit(1) + } + const send: CliMessage = { type: "cli", args } + vscode.send(send) + }) + vscode.once("error", (error) => { + logger.error(error.message) + process.exit(1) + }) + vscode.on("exit", (code) => process.exit(code || 0)) } else if (process.env.VSCODE_IPC_HOOK_CLI) { const pipeArgs: OpenCommandPipeArgs = { type: "open", @@ -217,28 +239,6 @@ async function entry(): Promise { }) vscode.write(JSON.stringify(pipeArgs)) vscode.end() - } else if (args["list-extensions"] || args["install-extension"] || args["uninstall-extension"]) { - logger.debug("forking vs code cli...") - const vscode = cp.fork(path.resolve(__dirname, "../../lib/vscode/out/vs/server/fork"), [], { - env: { - ...process.env, - CODE_SERVER_PARENT_PID: process.pid.toString(), - }, - }) - vscode.once("message", (message: any) => { - logger.debug("Got message from VS Code", field("message", message)) - if (message.type !== "ready") { - logger.error("Unexpected response waiting for ready response") - process.exit(1) - } - const send: CliMessage = { type: "cli", args } - vscode.send(send) - }) - vscode.once("error", (error) => { - logger.error(error.message) - process.exit(1) - }) - vscode.on("exit", (code) => process.exit(code || 0)) } else { wrap(() => main(args, cliArgs, configArgs)) } From 9d87c5328cbf3d23c5f4926e4eca5eb4aeb5f34e Mon Sep 17 00:00:00 2001 From: Asher Date: Mon, 14 Sep 2020 17:34:48 -0500 Subject: [PATCH 13/75] Add robots.txt (#2080) Closes #1886. --- ci/build/build-release.sh | 1 + src/browser/robots.txt | 2 ++ src/node/http.ts | 30 ++++++++++++++++++++---------- 3 files changed, 23 insertions(+), 10 deletions(-) create mode 100644 src/browser/robots.txt diff --git a/ci/build/build-release.sh b/ci/build/build-release.sh index 1cb00ad01..74d991ac9 100755 --- a/ci/build/build-release.sh +++ b/ci/build/build-release.sh @@ -37,6 +37,7 @@ bundle_code_server() { rsync src/browser/media/ "$RELEASE_PATH/src/browser/media" mkdir -p "$RELEASE_PATH/src/browser/pages" rsync src/browser/pages/*.html "$RELEASE_PATH/src/browser/pages" + rsync src/browser/robots.txt "$RELEASE_PATH/src/browser" # Adds the commit to package.json jq --slurp '.[0] * .[1]' package.json <( diff --git a/src/browser/robots.txt b/src/browser/robots.txt new file mode 100644 index 000000000..1f53798bb --- /dev/null +++ b/src/browser/robots.txt @@ -0,0 +1,2 @@ +User-agent: * +Disallow: / diff --git a/src/node/http.ts b/src/node/http.ts index 37bbcfbdd..a8abb94b0 100644 --- a/src/node/http.ts +++ b/src/node/http.ts @@ -289,7 +289,7 @@ export abstract class HttpProvider { /** * Helper to error if not authorized. */ - protected ensureAuthenticated(request: http.IncomingMessage): void { + public ensureAuthenticated(request: http.IncomingMessage): void { if (!this.authenticated(request)) { throw new HttpError("Unauthorized", HttpCode.Unauthorized) } @@ -647,10 +647,7 @@ export class HttpServer { } try { - const payload = - this.maybeRedirect(request, route) || - (route.provider.authenticated(request) && this.maybeProxy(request)) || - (await route.provider.handleRequest(route, request)) + const payload = (await this.handleRequest(route, request)) || (await route.provider.handleRequest(route, request)) if (payload.proxy) { this.doProxy(route, request, response, payload.proxy) } else { @@ -685,15 +682,23 @@ export class HttpServer { } /** - * Return any necessary redirection before delegating to a provider. + * Handle requests that are always in effect no matter what provider is + * registered at the route. */ - private maybeRedirect(request: http.IncomingMessage, route: ProviderRoute): RedirectResponse | undefined { + private async handleRequest(route: ProviderRoute, request: http.IncomingMessage): Promise { // If we're handling TLS ensure all requests are redirected to HTTPS. if (this.options.cert && !(request.connection as tls.TLSSocket).encrypted) { return { redirect: route.fullPath } } - return undefined + // Return robots.txt. + if (route.fullPath === "/robots.txt") { + const filePath = path.resolve(__dirname, "../../src/browser/robots.txt") + return { content: await fs.readFile(filePath), filePath } + } + + // Handle proxy domains. + return this.maybeProxy(route, request) } /** @@ -744,7 +749,7 @@ export class HttpServer { // can't be transferred so we need an in-between). const socketProxy = await this.socketProvider.createProxy(socket) const payload = - this.maybeProxy(request) || (await route.provider.handleWebSocket(route, request, socketProxy, head)) + this.maybeProxy(route, request) || (await route.provider.handleWebSocket(route, request, socketProxy, head)) if (payload && payload.proxy) { this.doProxy(route, request, { socket: socketProxy, head }, payload.proxy) } @@ -894,8 +899,10 @@ export class HttpServer { * * For example if `coder.com` is specified `8080.coder.com` will be proxied * but `8080.test.coder.com` and `test.8080.coder.com` will not. + * + * Throw an error if proxying but the user isn't authenticated. */ - public maybeProxy(request: http.IncomingMessage): HttpResponse | undefined { + public maybeProxy(route: ProviderRoute, request: http.IncomingMessage): HttpResponse | undefined { // Split into parts. const host = request.headers.host || "" const idx = host.indexOf(":") @@ -909,6 +916,9 @@ export class HttpServer { return undefined } + // Must be authenticated to use the proxy. + route.provider.ensureAuthenticated(request) + return { proxy: { port, From 8b5deac92b339b621b5a6b6219180003d1c38196 Mon Sep 17 00:00:00 2001 From: Asher Date: Wed, 30 Sep 2020 11:56:49 -0500 Subject: [PATCH 14/75] Fix 80 getting dropped from bind-addr --- src/node/cli.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/node/cli.ts b/src/node/cli.ts index e5d069551..d3afe203d 100644 --- a/src/node/cli.ts +++ b/src/node/cli.ts @@ -401,7 +401,10 @@ export async function readConfigFile(configPath?: string): Promise { function parseBindAddr(bindAddr: string): [string, number] { const u = new URL(`http://${bindAddr}`) - return [u.hostname, parseInt(u.port, 10)] + // With the http scheme 80 will be dropped so assume it's 80 if missing. This + // means --bind-addr without a port will default to 80 as well and not + // the code-server default. + return [u.hostname, u.port ? parseInt(u.port, 10) : 80] } interface Addr { From 11eaf0b470c922693851c8a613e2cc2eb2fa6503 Mon Sep 17 00:00:00 2001 From: Asher Date: Wed, 30 Sep 2020 12:02:31 -0500 Subject: [PATCH 15/75] Fix being unable to use [::] for the host Fixes #1582. --- src/node/http.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/node/http.ts b/src/node/http.ts index a8abb94b0..297dda0cc 100644 --- a/src/node/http.ts +++ b/src/node/http.ts @@ -584,8 +584,11 @@ export class HttpServer { const onListen = (): void => resolve(this.address()) if (this.options.socket) { this.server.listen(this.options.socket, onListen) + } else if (this.options.host) { + // [] is the correct format when using :: but Node errors with them. + this.server.listen(this.options.port, this.options.host.replace(/^\[|\]$/g, ""), onListen) } else { - this.server.listen(this.options.port, this.options.host, onListen) + this.server.listen(this.options.port, onListen) } }) } From e64b186527a7a0a2585fe5cd3aefb5de5f1b90dc Mon Sep 17 00:00:00 2001 From: Asher Date: Wed, 30 Sep 2020 15:22:54 -0500 Subject: [PATCH 16/75] Add variables to better customize plugin directories --- src/node/plugin.ts | 53 ++++++++++++++++++++++++++++++++++------------ 1 file changed, 39 insertions(+), 14 deletions(-) diff --git a/src/node/plugin.ts b/src/node/plugin.ts index 54f7f2b76..d1d71518e 100644 --- a/src/node/plugin.ts +++ b/src/node/plugin.ts @@ -4,11 +4,15 @@ import * as path from "path" import * as util from "util" import { Args } from "./cli" import { HttpServer } from "./http" +import { paths } from "./util" /* eslint-disable @typescript-eslint/no-var-requires */ export type Activate = (httpServer: HttpServer, args: Args) => void +/** + * Plugins must implement this interface. + */ export interface Plugin { activate: Activate } @@ -23,6 +27,9 @@ require("module")._load = function (request: string, parent: object, isMain: boo return originalLoad.apply(this, [request.replace(/^code-server/, path.resolve(__dirname, "../..")), parent, isMain]) } +/** + * Load a plugin and run its activation function. + */ const loadPlugin = async (pluginPath: string, httpServer: HttpServer, args: Args): Promise => { try { const plugin: Plugin = require(pluginPath) @@ -37,24 +44,42 @@ const loadPlugin = async (pluginPath: string, httpServer: HttpServer, args: Args } } -const _loadPlugins = async (httpServer: HttpServer, args: Args): Promise => { - const pluginPath = path.resolve(__dirname, "../../plugins") - const files = await util.promisify(fs.readdir)(pluginPath, { - withFileTypes: true, - }) - await Promise.all(files.map((file) => loadPlugin(path.join(pluginPath, file.name), httpServer, args))) -} - -export const loadPlugins = async (httpServer: HttpServer, args: Args): Promise => { +/** + * Load all plugins in the specified directory. + */ +const _loadPlugins = async (pluginDir: string, httpServer: HttpServer, args: Args): Promise => { try { - await _loadPlugins(httpServer, args) + const files = await util.promisify(fs.readdir)(pluginDir, { + withFileTypes: true, + }) + await Promise.all(files.map((file) => loadPlugin(path.join(pluginDir, file.name), httpServer, args))) } catch (error) { if (error.code !== "ENOENT") { logger.warn(error.message) } } - - if (process.env.PLUGIN_DIR) { - await loadPlugin(process.env.PLUGIN_DIR, httpServer, args) - } +} + +/** + * Load all plugins from the `plugins` directory and the directory specified by + * `PLUGIN_DIR`. + + * Also load any individual plugins found in `PLUGIN_DIRS` (colon-separated). + * This allows you to test and develop plugins without having to move or symlink + * them into one directory. + */ +export const loadPlugins = async (httpServer: HttpServer, args: Args): Promise => { + await Promise.all([ + // Built-in plugins. + _loadPlugins(path.resolve(__dirname, "../../plugins"), httpServer, args), + // User-added plugins. + _loadPlugins( + path.resolve(process.env.PLUGIN_DIR || path.join(paths.data, "code-server-extensions")), + httpServer, + args, + ), + // For development so you don't have to use symlinks. + process.env.PLUGIN_DIRS && + (await Promise.all(process.env.PLUGIN_DIRS.split(":").map((dir) => loadPlugin(dir, httpServer, args)))), + ]) } From 7a982555a837f8eab064c89414c24b13ef33d1b3 Mon Sep 17 00:00:00 2001 From: Asher Date: Wed, 30 Sep 2020 15:43:43 -0500 Subject: [PATCH 17/75] Add version to plugin load log --- src/node/plugin.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/node/plugin.ts b/src/node/plugin.ts index d1d71518e..299862191 100644 --- a/src/node/plugin.ts +++ b/src/node/plugin.ts @@ -34,7 +34,12 @@ const loadPlugin = async (pluginPath: string, httpServer: HttpServer, args: Args try { const plugin: Plugin = require(pluginPath) plugin.activate(httpServer, args) - logger.debug("Loaded plugin", field("name", path.basename(pluginPath))) + + logger.debug( + "Loaded plugin", + field("name", path.basename(pluginPath)), + field("version", require(path.join(pluginPath, "package.json")).version || "n/a"), + ) } catch (error) { if (error.code !== "MODULE_NOT_FOUND") { logger.warn(error.message) From b415b7524f6d3d1ed30553e0fb2b3e075dd3f475 Mon Sep 17 00:00:00 2001 From: Ben Potter Date: Tue, 6 Oct 2020 17:29:53 -0400 Subject: [PATCH 18/75] Add social badges (#2142) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f395968ce..f126580b8 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# code-server +# code-server · [!["GitHub Discussions"](https://img.shields.io/badge/%20GitHub-%20Discussions-gray.svg?longCache=true&logo=github&colorB=purple)](https://github.com/cdr/code-server/discussions) [!["Join us on Slack"](https://img.shields.io/badge/join-us%20on%20slack-gray.svg?longCache=true&logo=slack&colorB=brightgreen)](https://cdr.co/join-community) [![Twitter Follow](https://img.shields.io/twitter/follow/CoderHQ?label=%40CoderHQ&style=social)](https://twitter.com/coderhq) Run [VS Code](https://github.com/Microsoft/vscode) on any machine anywhere and access it in the browser. From ddda280df4ed90519a675b85e2854dc7d6c6d8e6 Mon Sep 17 00:00:00 2001 From: Asher Date: Wed, 7 Oct 2020 12:13:12 -0500 Subject: [PATCH 19/75] Rename plugin vars and make both colon-separated Only one was colon separated but now they both are. --- src/node/plugin.ts | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/src/node/plugin.ts b/src/node/plugin.ts index 299862191..5a8d777cd 100644 --- a/src/node/plugin.ts +++ b/src/node/plugin.ts @@ -66,25 +66,23 @@ const _loadPlugins = async (pluginDir: string, httpServer: HttpServer, args: Arg } /** - * Load all plugins from the `plugins` directory and the directory specified by - * `PLUGIN_DIR`. - - * Also load any individual plugins found in `PLUGIN_DIRS` (colon-separated). - * This allows you to test and develop plugins without having to move or symlink - * them into one directory. + * Load all plugins from the `plugins` directory, directories specified by + * `CS_PLUGIN_PATH` (colon-separated), and individual plugins specified by + * `CS_PLUGIN` (also colon-separated). */ export const loadPlugins = async (httpServer: HttpServer, args: Args): Promise => { + const pluginPath = process.env.CS_PLUGIN_PATH || `${path.join(paths.data, "plugins")}:/etc/code-server/plugins` + const plugin = process.env.CS_PLUGIN || "" await Promise.all([ // Built-in plugins. _loadPlugins(path.resolve(__dirname, "../../plugins"), httpServer, args), // User-added plugins. - _loadPlugins( - path.resolve(process.env.PLUGIN_DIR || path.join(paths.data, "code-server-extensions")), - httpServer, - args, - ), - // For development so you don't have to use symlinks. - process.env.PLUGIN_DIRS && - (await Promise.all(process.env.PLUGIN_DIRS.split(":").map((dir) => loadPlugin(dir, httpServer, args)))), + ...pluginPath.split(":").map((dir) => _loadPlugins(path.resolve(dir), httpServer, args)), + // Individual plugins so you don't have to symlink or move them into a + // directory specifically for plugins. This lets you load plugins that are + // on the same level as other directories that are not plugins (if you tried + // to use CS_PLUGIN_PATH code-server would try to load those other + // directories as plugins). Intended for development. + ...plugin.split(":").map((dir) => loadPlugin(path.resolve(dir), httpServer, args)), ]) } From b3811a67e0deda7d2e0279fbf402eb561ad9ddcb Mon Sep 17 00:00:00 2001 From: Anmol Sethi Date: Wed, 7 Oct 2020 13:24:31 -0400 Subject: [PATCH 20/75] Add $KEEP_MODULES argument to build-release.sh (#2167) --- ci/build/build-release.sh | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/ci/build/build-release.sh b/ci/build/build-release.sh index 74d991ac9..8d8d1c903 100755 --- a/ci/build/build-release.sh +++ b/ci/build/build-release.sh @@ -6,6 +6,10 @@ set -euo pipefail # MINIFY controls whether minified vscode is bundled. MINIFY="${MINIFY-true}" +# KEEP_MODULES controls whether the script cleans all node_modules requiring a yarn install +# to run first. +KEEP_MODULES="${KEEP_MODULES-0}" + main() { cd "$(dirname "${0}")/../.." source ./ci/lib.sh @@ -52,6 +56,11 @@ EOF ) > "$RELEASE_PATH/package.json" rsync yarn.lock "$RELEASE_PATH" rsync ci/build/npm-postinstall.sh "$RELEASE_PATH/postinstall.sh" + + + if [ "$KEEP_MODULES" = 1 ]; then + rsync node_modules/ "$RELEASE_PATH/node_modules" + fi } bundle_vscode() { @@ -60,7 +69,11 @@ bundle_vscode() { rsync "$VSCODE_SRC_PATH/out-vscode${MINIFY+-min}/" "$VSCODE_OUT_PATH/out" rsync "$VSCODE_SRC_PATH/.build/extensions/" "$VSCODE_OUT_PATH/extensions" - rm -Rf "$VSCODE_OUT_PATH/extensions/node_modules" + if [ "$KEEP_MODULES" = 0 ]; then + rm -Rf "$VSCODE_OUT_PATH/extensions/node_modules" + else + rsync "$VSCODE_SRC_PATH/node_modules/" "$VSCODE_OUT_PATH/node_modules" + fi rsync "$VSCODE_SRC_PATH/extensions/package.json" "$VSCODE_OUT_PATH/extensions" rsync "$VSCODE_SRC_PATH/extensions/yarn.lock" "$VSCODE_OUT_PATH/extensions" rsync "$VSCODE_SRC_PATH/extensions/postinstall.js" "$VSCODE_OUT_PATH/extensions" From c2ac126a501622947d8c644a6a2bb1e8c751a017 Mon Sep 17 00:00:00 2001 From: Asher Date: Wed, 7 Oct 2020 12:25:23 -0500 Subject: [PATCH 21/75] Log all plugin errors as errors --- src/node/plugin.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/node/plugin.ts b/src/node/plugin.ts index 5a8d777cd..2860763ad 100644 --- a/src/node/plugin.ts +++ b/src/node/plugin.ts @@ -41,11 +41,7 @@ const loadPlugin = async (pluginPath: string, httpServer: HttpServer, args: Args field("version", require(path.join(pluginPath, "package.json")).version || "n/a"), ) } catch (error) { - if (error.code !== "MODULE_NOT_FOUND") { - logger.warn(error.message) - } else { - logger.error(error.message) - } + logger.error(error.message) } } From 402f5ebd77df43d68fd53fcf09a4e5510b83be77 Mon Sep 17 00:00:00 2001 From: Asher Date: Wed, 7 Oct 2020 12:37:37 -0500 Subject: [PATCH 22/75] Update VS code to 1.49.3 (#2081) --- ci/dev/vscode.patch | 140 ++++++++++++++++++++++---------------------- lib/vscode | 2 +- 2 files changed, 71 insertions(+), 71 deletions(-) diff --git a/ci/dev/vscode.patch b/ci/dev/vscode.patch index 5fe34a20f..1632dcd42 100644 --- a/ci/dev/vscode.patch +++ b/ci/dev/vscode.patch @@ -12,12 +12,12 @@ index 0fe46b6eadc4ccc819fbf342ee1071bb657792b3..e545e004cef31fa5f40ba8df6a2317ea coverage/ diff --git a/.yarnrc b/.yarnrc deleted file mode 100644 -index 135e10442a7e5184cf8c47615322bb7d622855d9..0000000000000000000000000000000000000000 +index 3c6eccfb102f2084d16395d70d65f05a91b6d47b..0000000000000000000000000000000000000000 --- a/.yarnrc +++ /dev/null @@ -1,3 +0,0 @@ -disturl "https://atom.io/download/electron" --target "7.3.2" +-target "9.2.1" -runtime "electron" diff --git a/build/gulpfile.reh.js b/build/gulpfile.reh.js index f2ea1bd37010b1eb8a43ce9beaae4a88810f6e2d..3f660f9981921ec465d2b8809a1a5ea5663f4c1f 100644 @@ -144,10 +144,10 @@ index cb88d37adefd4882f61a2711fdd7f72b89e1a6e3..6b3253af0a3a0aa4d75456379ef1c00f const cp = require('child_process'); diff --git a/coder.js b/coder.js new file mode 100644 -index 0000000000000000000000000000000000000000..9cb693af63b86b4a6b35c442e6ea501a1076d18a +index 0000000000000000000000000000000000000000..df5b42cba463b6c0043aebbc835f852f1284aa36 --- /dev/null +++ b/coder.js -@@ -0,0 +1,63 @@ +@@ -0,0 +1,64 @@ +// This must be ran from VS Code's root. +const gulp = require("gulp"); +const path = require("path"); @@ -163,6 +163,7 @@ index 0000000000000000000000000000000000000000..9cb693af63b86b4a6b35c442e6ea501a + buildfile.base, + buildfile.workbenchWeb, + buildfile.workerExtensionHost, ++ buildfile.workerNotebook, + buildfile.keyboardMaps, + buildfile.entrypoint("vs/platform/files/node/watcher/unix/watcherApp", ["vs/css", "vs/nls"]), + buildfile.entrypoint("vs/platform/files/node/watcher/nsfw/watcherApp", ["vs/css", "vs/nls"]), @@ -226,10 +227,10 @@ index da4fa3e9d0443d679dfbab1000b434af2ae01afd..50f3e1144f8057883dea8b91ec2f7073 function processLib() { diff --git a/package.json b/package.json -index 226f51a1ec55fc31ae90e175bc1ab62d74eb6294..5c4e5af5f69c0fd994f4b54cb7a9dfb20f868bfa 100644 +index 9b5ee0f876303283eb766fd2bb3ed818c50b1d3e..30ef9fa81b1cd844138388d794d4d6d9db5c7fba 100644 --- a/package.json +++ b/package.json -@@ -45,7 +45,11 @@ +@@ -46,7 +46,11 @@ "watch-web": "gulp watch-web --max_old_space_size=4095", "eslint": "eslint -c .eslintrc.json --rulesdir ./build/lib/eslint --ext .ts --ext .js ./src/vs ./extensions" }, @@ -241,15 +242,15 @@ index 226f51a1ec55fc31ae90e175bc1ab62d74eb6294..5c4e5af5f69c0fd994f4b54cb7a9dfb2 "applicationinsights": "1.0.8", "chokidar": "3.2.3", "graceful-fs": "4.2.3", -@@ -59,6 +63,7 @@ - "native-keymap": "2.1.2", +@@ -60,6 +64,7 @@ + "native-keymap": "2.2.0", "native-watchdog": "1.3.0", "node-pty": "0.10.0-beta8", + "rimraf": "^2.2.8", "semver-umd": "^5.5.7", "spdlog": "^0.11.1", "sudo-prompt": "9.1.1", -@@ -159,7 +164,6 @@ +@@ -160,7 +165,6 @@ "pump": "^1.0.1", "queue": "3.0.6", "rcedit": "^1.1.0", @@ -257,7 +258,7 @@ index 226f51a1ec55fc31ae90e175bc1ab62d74eb6294..5c4e5af5f69c0fd994f4b54cb7a9dfb2 "sinon": "^1.17.2", "source-map": "^0.4.4", "style-loader": "^1.0.0", -@@ -190,5 +194,8 @@ +@@ -192,5 +196,8 @@ "windows-foreground-love": "0.2.0", "windows-mutex": "0.3.0", "windows-process-tree": "0.2.4" @@ -267,7 +268,7 @@ index 226f51a1ec55fc31ae90e175bc1ab62d74eb6294..5c4e5af5f69c0fd994f4b54cb7a9dfb2 } } diff --git a/product.json b/product.json -index 2b884d18f301b86bf29e3a8f1343cfb651f8727c..518b935b837dd21251089bdd317d6c3c03756d7b 100644 +index b9349015e3475bff07104ca2fa859954a37f962a..4c32260abc42efe17ee7d717e4dcebf182044e8c 100644 --- a/product.json +++ b/product.json @@ -20,7 +20,7 @@ @@ -281,18 +282,18 @@ index 2b884d18f301b86bf29e3a8f1343cfb651f8727c..518b935b837dd21251089bdd317d6c3c "ms-vscode.vscode-js-profile-flame", diff --git a/remote/.yarnrc b/remote/.yarnrc deleted file mode 100644 -index 1e16cde724c7703d2836b3641de48c99f7f47e68..0000000000000000000000000000000000000000 +index c1a32ce532afa501fb19bdbcf6bcb0ec151ecd99..0000000000000000000000000000000000000000 --- a/remote/.yarnrc +++ /dev/null @@ -1,3 +0,0 @@ -disturl "http://nodejs.org/dist" --target "12.4.0" +-target "12.14.1" -runtime "node" diff --git a/src/vs/base/common/network.ts b/src/vs/base/common/network.ts -index 1286c5117a4cae9d6075ed36f32f6414897d705b..e60dd11d03992800853e76d4d68b8ff211da7627 100644 +index 4b6aebc16466dff58a9dfab4a680d230fa1f71a5..dd72e179ec0fa9a0b3e16e497225cb6da6218af3 100644 --- a/src/vs/base/common/network.ts +++ b/src/vs/base/common/network.ts -@@ -111,16 +111,17 @@ class RemoteAuthoritiesImpl { +@@ -113,16 +113,17 @@ class RemoteAuthoritiesImpl { if (host && host.indexOf(':') !== -1) { host = `[${host}]`; } @@ -432,19 +433,18 @@ index 2c64061da7b01aef0bfe3cec851da232ca9461c8..c0ef8faedd406c38bf9c55bbbdbbb060 // Do nothing. If we can't read the file we have no // language pack config. diff --git a/src/vs/code/browser/workbench/workbench.ts b/src/vs/code/browser/workbench/workbench.ts -index c629f7fffa1faad78e5d9907fd38aec289db0428..c266e1fb06ffc44f1ec4230ac59fd094a53b257b 100644 +index ad5272b22320a361cec0eed40d57629b06147c01..c9280b14472507ebb9a277f554485f08b090cb69 100644 --- a/src/vs/code/browser/workbench/workbench.ts +++ b/src/vs/code/browser/workbench/workbench.ts -@@ -13,6 +13,8 @@ import { isFolderToOpen, isWorkspaceToOpen } from 'vs/platform/windows/common/wi - import { isEqual } from 'vs/base/common/resources'; +@@ -16,6 +16,7 @@ import { isEqual } from 'vs/base/common/resources'; import { isStandalone } from 'vs/base/browser/browser'; import { localize } from 'vs/nls'; -+import { Schemas } from 'vs/base/common/network'; + import { Schemas } from 'vs/base/common/network'; +import { encodePath } from 'vs/server/node/util'; interface ICredential { service: string; -@@ -243,12 +245,18 @@ class WorkspaceProvider implements IWorkspaceProvider { +@@ -253,12 +254,18 @@ class WorkspaceProvider implements IWorkspaceProvider { // Folder else if (isFolderToOpen(workspace)) { @@ -465,7 +465,7 @@ index c629f7fffa1faad78e5d9907fd38aec289db0428..c266e1fb06ffc44f1ec4230ac59fd094 } // Append payload if any -@@ -285,7 +293,22 @@ class WorkspaceProvider implements IWorkspaceProvider { +@@ -348,7 +355,22 @@ class WindowIndicator implements IWindowIndicator { throw new Error('Missing web configuration element'); } @@ -489,7 +489,7 @@ index c629f7fffa1faad78e5d9907fd38aec289db0428..c266e1fb06ffc44f1ec4230ac59fd094 // Revive static extension locations if (Array.isArray(config.staticExtensions)) { -@@ -297,40 +320,7 @@ class WorkspaceProvider implements IWorkspaceProvider { +@@ -360,40 +382,7 @@ class WindowIndicator implements IWindowIndicator { // Find workspace to open and payload let foundWorkspace = false; let workspace: IWorkspace; @@ -532,7 +532,7 @@ index c629f7fffa1faad78e5d9907fd38aec289db0428..c266e1fb06ffc44f1ec4230ac59fd094 // If no workspace is provided through the URL, check for config attribute from server if (!foundWorkspace) { diff --git a/src/vs/platform/environment/node/argv.ts b/src/vs/platform/environment/node/argv.ts -index 2379b626c81321afe18267c69c6903efbfa354f4..28f8971cf398b048c7d8f56df2e9dc4e32aadb6d 100644 +index 92dd2bcf87dba5e5f07f2707a91b1a364ab1b05f..047522bd1a2c1edfda05c3739838fecbd70db6c5 100644 --- a/src/vs/platform/environment/node/argv.ts +++ b/src/vs/platform/environment/node/argv.ts @@ -8,6 +8,8 @@ import { localize } from 'vs/nls'; @@ -544,7 +544,7 @@ index 2379b626c81321afe18267c69c6903efbfa354f4..28f8971cf398b048c7d8f56df2e9dc4e _: string[]; 'folder-uri'?: string[]; // undefined or array of 1 or more 'file-uri'?: string[]; // undefined or array of 1 or more -@@ -141,6 +143,8 @@ export const OPTIONS: OptionDescriptions> = { +@@ -142,6 +144,8 @@ 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' }, @@ -553,13 +553,13 @@ index 2379b626c81321afe18267c69c6903efbfa354f4..28f8971cf398b048c7d8f56df2e9dc4e '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-extension.") }, 'category': { type: 'string', cat: 'e', description: localize('category', "Filters installed extensions by provided category, when using --list-extension.") }, -@@ -403,4 +407,3 @@ export function buildHelpMessage(productName: string, executableName: string, ve +@@ -405,4 +409,3 @@ export function buildHelpMessage(productName: string, executableName: string, ve export function buildVersionMessage(version: string | undefined, commit: string | undefined): string { return `${version || localize('unknownVersion', "Unknown version")}\n${commit || localize('unknownCommit', "Unknown commit")}\n${process.arch}`; } - diff --git a/src/vs/platform/environment/node/environmentService.ts b/src/vs/platform/environment/node/environmentService.ts -index 5c0dc4ad4ae79a172bed4bc3d6440cdf6dd22386..38b8c7573a872d587c5f3f6c5e0521d2bd918daa 100644 +index 45d5ec2cc02707d91f19a66d408ae46a1201a9e8..4ed498c63ceb55d15bd104a92b701ead3dfa81f2 100644 --- a/src/vs/platform/environment/node/environmentService.ts +++ b/src/vs/platform/environment/node/environmentService.ts @@ -38,6 +38,8 @@ export interface INativeEnvironmentService extends IEnvironmentService { @@ -633,7 +633,7 @@ index 575b2aafc3802cd6f5f943930e30de9f2c2690de..873181f967856759e3dc001e5bbe06e2 + } } diff --git a/src/vs/platform/product/common/product.ts b/src/vs/platform/product/common/product.ts -index 3370a608b4b54c238a6ea69a92cdfcd4817cab57..37b3592d39d8ee3aed5455d599612485ea621323 100644 +index bb33203d1727b1c076efac9113afc3b2580cdbd9..c53cea338cdaa0f0ac15542c129e1572b3f13b80 100644 --- a/src/vs/platform/product/common/product.ts +++ b/src/vs/platform/product/common/product.ts @@ -30,6 +30,12 @@ if (isWeb) { @@ -650,7 +650,7 @@ index 3370a608b4b54c238a6ea69a92cdfcd4817cab57..37b3592d39d8ee3aed5455d599612485 // Node: AMD loader diff --git a/src/vs/platform/product/common/productService.ts b/src/vs/platform/product/common/productService.ts -index 040c869d94ceb278350c1d752f55712feedda379..bf16defcf7bc4229dedbbe9eae8a965e996c69d9 100644 +index d1cb00a6d63621a4873a6a5e815220d084ceac2a..1a69d6f63a7406d364aa3e2b32fb75309f212e98 100644 --- a/src/vs/platform/product/common/productService.ts +++ b/src/vs/platform/product/common/productService.ts @@ -30,6 +30,8 @@ export type ConfigurationSyncStore = { @@ -684,10 +684,10 @@ index 3715cbb8e6ee41c3d9b5090918d243b723ae2d00..c65de8ad37e727d66da97a8f8b170cbc - - diff --git a/src/vs/platform/remote/common/remoteAgentConnection.ts b/src/vs/platform/remote/common/remoteAgentConnection.ts -index 2185bb5228c3f9603f307237e7f146fe386708d8..35463ca6520a7da2308d01a51ef2d3e544f10a10 100644 +index 18d3d04fd20335975293e37b3b641120dd92da20..4e49f9d63623da6c84624144765f76ec127ea526 100644 --- a/src/vs/platform/remote/common/remoteAgentConnection.ts +++ b/src/vs/platform/remote/common/remoteAgentConnection.ts -@@ -89,7 +89,7 @@ async function connectToRemoteExtensionHostAgent(options: ISimpleConnectionOptio +@@ -92,7 +92,7 @@ async function connectToRemoteExtensionHostAgent(options: ISimpleConnectionOptio options.socketFactory.connect( options.host, options.port, @@ -697,10 +697,10 @@ index 2185bb5228c3f9603f307237e7f146fe386708d8..35463ca6520a7da2308d01a51ef2d3e5 if (err || !socket) { options.logService.error(`${logPrefix} socketFactory.connect() failed. Error:`); diff --git a/src/vs/platform/storage/browser/storageService.ts b/src/vs/platform/storage/browser/storageService.ts -index 59b1baf9120cb0ccf1ebc425ed708224b5513d41..cf9805554b91176ac2521963a7775711e287e4cc 100644 +index ab3fd347b69f8a3d9b96e706cd87c911b8ffed6b..9d351037b577f9f1edfd18ae9b3c48a211f4467f 100644 --- a/src/vs/platform/storage/browser/storageService.ts +++ b/src/vs/platform/storage/browser/storageService.ts -@@ -116,8 +116,8 @@ export class BrowserStorageService extends Disposable implements IStorageService +@@ -122,8 +122,8 @@ export class BrowserStorageService extends Disposable implements IStorageService return this.getStorage(scope).getNumber(key, fallbackValue); } @@ -712,10 +712,10 @@ index 59b1baf9120cb0ccf1ebc425ed708224b5513d41..cf9805554b91176ac2521963a7775711 remove(key: string, scope: StorageScope): void { diff --git a/src/vs/platform/storage/common/storage.ts b/src/vs/platform/storage/common/storage.ts -index 1623957cb18eedbf968cca6231d226b587f51935..d366438d54d36a86bd416aeb9f802ad9015f601c 100644 +index 6611f1dae42055f69a55c1c154d9475f11cd4d0a..d598d4909d5ff6d1614e4a038b1865e1f9a4e963 100644 --- a/src/vs/platform/storage/common/storage.ts +++ b/src/vs/platform/storage/common/storage.ts -@@ -83,7 +83,7 @@ export interface IStorageService { +@@ -85,7 +85,7 @@ export interface IStorageService { * The scope argument allows to define the scope of the storage * operation to either the current workspace only or all workspaces. */ @@ -725,10 +725,10 @@ index 1623957cb18eedbf968cca6231d226b587f51935..d366438d54d36a86bd416aeb9f802ad9 /** * Delete an element stored under the provided key from storage. diff --git a/src/vs/platform/storage/node/storageService.ts b/src/vs/platform/storage/node/storageService.ts -index 75514fe5a4fabdc885556311954ab016c593bac3..62d97c60488856dfde0bb64fea85032b2e49bb94 100644 +index ac657056aa68549f0053cfb1ec68835ba4ce20f9..143f9b5681eb867c5e5c5437946ab785eb34e4b4 100644 --- a/src/vs/platform/storage/node/storageService.ts +++ b/src/vs/platform/storage/node/storageService.ts -@@ -204,8 +204,8 @@ export class NativeStorageService extends Disposable implements IStorageService +@@ -202,8 +202,8 @@ export class NativeStorageService extends Disposable implements IStorageService return this.getStorage(scope).getNumber(key, fallbackValue); } @@ -2843,7 +2843,7 @@ index 0000000000000000000000000000000000000000..fa47e993b46802f1a26457649e9e8bc4 + return path.split("/").map((p) => encodeURIComponent(p)).join("/"); +}; diff --git a/src/vs/workbench/api/browser/extensionHost.contribution.ts b/src/vs/workbench/api/browser/extensionHost.contribution.ts -index 3d77009b908f61690a56dc589360627f6f5a3924..11deb1b99ac9d3baa4aa583d711a5e020b4379ec 100644 +index bfabf0008910c87146df53a2e10fe63bae517a86..32b3b1cf84c8d280fd7f03d541b867691d51c2fb 100644 --- a/src/vs/workbench/api/browser/extensionHost.contribution.ts +++ b/src/vs/workbench/api/browser/extensionHost.contribution.ts @@ -60,6 +60,7 @@ import './mainThreadComments'; @@ -2873,7 +2873,7 @@ index 7bc3904963bed2925f3640b6bd929347159dd3cf..c6db2368ae9eaca61889efcf3c49763c return Promise.reject(err); } diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts -index 97793666ad8abf7d052ba96a88565042b21ebcec..13cd137db1e9435ef66ade3220774b1ddb608d91 100644 +index 3595cd3e38136222044a13050b15105bbe539068..989caefff7c4b8203c03cec8fa451f5e70ea8964 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -68,6 +68,7 @@ import { IURITransformerService } from 'vs/workbench/api/common/extHostUriTransf @@ -2884,7 +2884,7 @@ index 97793666ad8abf7d052ba96a88565042b21ebcec..13cd137db1e9435ef66ade3220774b1d import { ExtHostTheming } from 'vs/workbench/api/common/extHostTheming'; import { IExtHostTunnelService } from 'vs/workbench/api/common/extHostTunnelService'; import { IExtHostApiDeprecationService } from 'vs/workbench/api/common/extHostApiDeprecationService'; -@@ -97,6 +98,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I +@@ -100,6 +101,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I const extHostStorage = accessor.get(IExtHostStorage); const extensionStoragePaths = accessor.get(IExtensionStoragePaths); const extHostLogService = accessor.get(ILogService); @@ -2892,7 +2892,7 @@ index 97793666ad8abf7d052ba96a88565042b21ebcec..13cd137db1e9435ef66ade3220774b1d const extHostTunnelService = accessor.get(IExtHostTunnelService); const extHostApiDeprecation = accessor.get(IExtHostApiDeprecationService); const extHostWindow = accessor.get(IExtHostWindow); -@@ -107,6 +109,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I +@@ -110,6 +112,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I rpcProtocol.set(ExtHostContext.ExtHostConfiguration, extHostConfiguration); rpcProtocol.set(ExtHostContext.ExtHostExtensionService, extensionService); rpcProtocol.set(ExtHostContext.ExtHostStorage, extHostStorage); @@ -2901,10 +2901,10 @@ index 97793666ad8abf7d052ba96a88565042b21ebcec..13cd137db1e9435ef66ade3220774b1d rpcProtocol.set(ExtHostContext.ExtHostWindow, extHostWindow); diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts -index eb5d8ea84551030a9d72918813fca7adb49a5cd8..da9eb521ca4c660de1bb23df52c18c0efe6f5979 100644 +index 4b7946662950f18179a5b6e3552abd39e68ca80e..ca1352d311a94b42e18d0d9e4859b18ec2bb271d 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts -@@ -769,6 +769,16 @@ export interface MainThreadLabelServiceShape extends IDisposable { +@@ -795,6 +795,16 @@ export interface MainThreadLabelServiceShape extends IDisposable { $unregisterResourceLabelFormatter(handle: number): void; } @@ -2921,7 +2921,7 @@ index eb5d8ea84551030a9d72918813fca7adb49a5cd8..da9eb521ca4c660de1bb23df52c18c0e export interface MainThreadSearchShape extends IDisposable { $registerFileSearchProvider(handle: number, scheme: string): void; $registerTextSearchProvider(handle: number, scheme: string): void; -@@ -1707,6 +1717,7 @@ export const MainContext = { +@@ -1765,6 +1775,7 @@ export const MainContext = { MainThreadWindow: createMainId('MainThreadWindow'), MainThreadLabelService: createMainId('MainThreadLabelService'), MainThreadNotebook: createMainId('MainThreadNotebook'), @@ -2929,7 +2929,7 @@ index eb5d8ea84551030a9d72918813fca7adb49a5cd8..da9eb521ca4c660de1bb23df52c18c0e MainThreadTheming: createMainId('MainThreadTheming'), MainThreadTunnelService: createMainId('MainThreadTunnelService'), MainThreadTimeline: createMainId('MainThreadTimeline') -@@ -1745,6 +1756,7 @@ export const ExtHostContext = { +@@ -1806,6 +1817,7 @@ export const ExtHostContext = { ExtHostOutputService: createMainId('ExtHostOutputService'), ExtHosLabelService: createMainId('ExtHostLabelService'), ExtHostNotebook: createMainId('ExtHostNotebook'), @@ -2938,10 +2938,10 @@ index eb5d8ea84551030a9d72918813fca7adb49a5cd8..da9eb521ca4c660de1bb23df52c18c0e ExtHostTunnelService: createMainId('ExtHostTunnelService'), ExtHostAuthentication: createMainId('ExtHostAuthentication'), diff --git a/src/vs/workbench/api/common/extHostExtensionService.ts b/src/vs/workbench/api/common/extHostExtensionService.ts -index 34639e18b6fb567feaf19cf20bd312b6b578723f..9c22fe6f090f3cfceea5c3f41695a1ab1d797a19 100644 +index 0bb5188614bcbf98b85c9208edc2b173f70b1670..38ff3e2e05645be8df619ed2b47fa2984b918741 100644 --- a/src/vs/workbench/api/common/extHostExtensionService.ts +++ b/src/vs/workbench/api/common/extHostExtensionService.ts -@@ -32,6 +32,7 @@ import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitData +@@ -31,6 +31,7 @@ import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitData import { IExtensionStoragePaths } from 'vs/workbench/api/common/extHostStoragePaths'; import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; @@ -2982,7 +2982,7 @@ index 34639e18b6fb567feaf19cf20bd312b6b578723f..9c22fe6f090f3cfceea5c3f41695a1ab this._loadExtensionContext(extensionDescription) ]).then(values => { return AbstractExtHostExtensionService._callActivate(this._logService, extensionDescription.identifier, values[0], values[1], activationTimesBuilder); -@@ -754,7 +758,7 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme +@@ -746,7 +750,7 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme protected abstract _beforeAlmostReadyToRunExtensions(): Promise; protected abstract _getEntryPoint(extensionDescription: IExtensionDescription): string | undefined; @@ -3027,18 +3027,18 @@ index 3843fdec386edc09a1d361b63de892a04e0070ed..8aac4df527857e964798362a69f5591b registerSingleton(ILogService, ExtHostLogService); +registerSingleton(IExtHostNodeProxy, ExtHostNodeProxy); diff --git a/src/vs/workbench/api/worker/extHostExtensionService.ts b/src/vs/workbench/api/worker/extHostExtensionService.ts -index c71ab1c7da462da8f4a12146d45e6cde7f06ad81..572b07ff2516154f49ab9e02bfcab2b4d8b3009f 100644 +index a6a149083719d7479268e24eb5339f6cbf93e655..360888dc7dff9437f6c85f7a2043ad9e7c4daf21 100644 --- a/src/vs/workbench/api/worker/extHostExtensionService.ts +++ b/src/vs/workbench/api/worker/extHostExtensionService.ts -@@ -9,6 +9,7 @@ import { AbstractExtHostExtensionService } from 'vs/workbench/api/common/extHost - import { URI } from 'vs/base/common/uri'; +@@ -10,6 +10,7 @@ import { URI } from 'vs/base/common/uri'; import { RequireInterceptor } from 'vs/workbench/api/common/extHostRequireInterceptor'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; + import { ExtensionRuntime } from 'vs/workbench/api/common/extHostTypes'; +import { loadCommonJSModule } from 'vs/server/browser/worker'; class WorkerRequireInterceptor extends RequireInterceptor { -@@ -42,10 +43,15 @@ export class ExtHostExtensionService extends AbstractExtHostExtensionService { +@@ -44,10 +45,15 @@ export class ExtHostExtensionService extends AbstractExtHostExtensionService { } protected _getEntryPoint(extensionDescription: IExtensionDescription): string | undefined { @@ -3072,7 +3072,7 @@ index ced2d815834e40a1543e80516472799075980733..dfcae73e8a042307600c67f163aa00ba .monaco-workbench .activitybar > .content > .home-bar > .home-bar-icon-badge { diff --git a/src/vs/workbench/browser/web.main.ts b/src/vs/workbench/browser/web.main.ts -index 0462617196b39111cb22e5abbf4b096406496bf8..11434d27af9ce3262c57918e0f5a44d4eebcf8ac 100644 +index 511d7376a2bfebde59b4c67fed54c39e9dd534c9..c7c45f8e4e4ffe56a8782f58af75c6a7835142cf 100644 --- a/src/vs/workbench/browser/web.main.ts +++ b/src/vs/workbench/browser/web.main.ts @@ -45,6 +45,7 @@ import { FileLogService } from 'vs/platform/log/common/fileLogService'; @@ -3083,7 +3083,7 @@ index 0462617196b39111cb22e5abbf4b096406496bf8..11434d27af9ce3262c57918e0f5a44d4 import { coalesce } from 'vs/base/common/arrays'; import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFilesystemProvider'; import { WebResourceIdentityService, IResourceIdentityService } from 'vs/platform/resource/common/resourceIdentityService'; -@@ -84,6 +85,8 @@ class BrowserMain extends Disposable { +@@ -87,6 +88,8 @@ class BrowserMain extends Disposable { // Startup const instantiationService = workbench.startup(); @@ -3115,7 +3115,7 @@ index 18ea0bfedb4492327429a38237b05915b29f6dd0..d59a17c17f4fffa23d786ce36b4ff624 this._langIdKey.set(value ? this._modeService.getModeIdByFilepathOrFirstLine(value) : null); this._extensionKey.set(value ? extname(value) : null); diff --git a/src/vs/workbench/contrib/scm/browser/media/scm.css b/src/vs/workbench/contrib/scm/browser/media/scm.css -index 9947f240bf20b42069bd3d50a96d7a783615f54b..bdba0a2fc64a2e6c2cd2644bcc6afc0d131501a7 100644 +index b1838de8f21c60141d01cc424a5e000a32f1c828..0a480032e0cc8d5219cd240f8807aa317718659d 100644 --- a/src/vs/workbench/contrib/scm/browser/media/scm.css +++ b/src/vs/workbench/contrib/scm/browser/media/scm.css @@ -138,9 +138,11 @@ @@ -3134,7 +3134,7 @@ index 9947f240bf20b42069bd3d50a96d7a783615f54b..bdba0a2fc64a2e6c2cd2644bcc6afc0d .scm-view .monaco-list .monaco-list-row .resource-group > .actions, .scm-view .monaco-list .monaco-list-row .resource > .name > .monaco-icon-label > .actions { diff --git a/src/vs/workbench/services/dialogs/browser/dialogService.ts b/src/vs/workbench/services/dialogs/browser/dialogService.ts -index 6e3182a696dd3443e68ad9e92d029b6ec2d01677..7df85da165a3ba157629c6b9e92f08dd18c7511a 100644 +index 1360c248eb7ff937c92d08bbf30d2b76ea606dc0..adccf8b88d62381c3ec484df40c6d63142ec9ef5 100644 --- a/src/vs/workbench/services/dialogs/browser/dialogService.ts +++ b/src/vs/workbench/services/dialogs/browser/dialogService.ts @@ -124,11 +124,12 @@ export class DialogService implements IDialogService { @@ -3153,10 +3153,10 @@ index 6e3182a696dd3443e68ad9e92d029b6ec2d01677..7df85da165a3ba157629c6b9e92f08dd }; diff --git a/src/vs/workbench/services/environment/browser/environmentService.ts b/src/vs/workbench/services/environment/browser/environmentService.ts -index ba2701ec54d1a70eaf66afacce585fe906644319..4d4aaa6958b636480178470570e856e62ab922ee 100644 +index 819607be0c13fed28eb7fbe6d4a62c0b860b1aa9..b046943311b713a579cc3a94983ea1b7fca7b9b1 100644 --- a/src/vs/workbench/services/environment/browser/environmentService.ts +++ b/src/vs/workbench/services/environment/browser/environmentService.ts -@@ -121,8 +121,18 @@ export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironment +@@ -116,8 +116,18 @@ export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironment @memoize get logFile(): URI { return joinPath(this.options.logsPath, 'window.log'); } @@ -3176,7 +3176,7 @@ index ba2701ec54d1a70eaf66afacce585fe906644319..4d4aaa6958b636480178470570e856e6 @memoize get settingsResource(): URI { return joinPath(this.userRoamingDataHome, 'settings.json'); } -@@ -284,7 +294,12 @@ export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironment +@@ -279,7 +289,12 @@ export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironment extensionHostDebugEnvironment.params.port = parseInt(value); break; case 'enableProposedApi': @@ -3191,10 +3191,10 @@ index ba2701ec54d1a70eaf66afacce585fe906644319..4d4aaa6958b636480178470570e856e6 } } diff --git a/src/vs/workbench/services/extensionManagement/common/extensionEnablementService.ts b/src/vs/workbench/services/extensionManagement/common/extensionEnablementService.ts -index c28b14774005509f58dddd2dec25547bac85e09f..6090200d9c3671fc1239880dbd060a01a84db1fb 100644 +index 32f3dc52c1ff645df6471a03542d6ec3eb73a277..c2f4497d2eba13a771b2665ad58f12ecdfa7606a 100644 --- a/src/vs/workbench/services/extensionManagement/common/extensionEnablementService.ts +++ b/src/vs/workbench/services/extensionManagement/common/extensionEnablementService.ts -@@ -163,7 +163,7 @@ export class ExtensionEnablementService extends Disposable implements IWorkbench +@@ -205,7 +205,7 @@ export class ExtensionEnablementService extends Disposable implements IWorkbench } } } @@ -3204,10 +3204,10 @@ index c28b14774005509f58dddd2dec25547bac85e09f..6090200d9c3671fc1239880dbd060a01 return false; } diff --git a/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts b/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts -index 33eb56db3c25a0dc028b0d54dfa102e5584441cf..de70af33529e40a56969d8f241c82906cda72e1e 100644 +index a982b3ecc58c5a2f3a92be7b8cca3a1cacbb7d47..97f9bfcf0e679be683b1b09cd569149e7962f5ad 100644 --- a/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts +++ b/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts -@@ -202,8 +202,11 @@ export class ExtensionManagementService extends Disposable implements IExtension +@@ -211,8 +211,11 @@ export class ExtensionManagementService extends Disposable implements IExtension } // Install Language pack on all servers @@ -3220,7 +3220,7 @@ index 33eb56db3c25a0dc028b0d54dfa102e5584441cf..de70af33529e40a56969d8f241c82906 } // 1. Install on preferred location -@@ -236,6 +239,11 @@ export class ExtensionManagementService extends Disposable implements IExtension +@@ -245,6 +248,11 @@ export class ExtensionManagementService extends Disposable implements IExtension return this.extensionManagementServerService.webExtensionManagementServer.extensionManagementService.installFromGallery(gallery); } @@ -3233,10 +3233,10 @@ index 33eb56db3c25a0dc028b0d54dfa102e5584441cf..de70af33529e40a56969d8f241c82906 const error = new Error(localize('cannot be installed', "Cannot install '{0}' because this extension has defined that it cannot run on the remote server.", gallery.displayName || gallery.name)); error.name = INSTALL_ERROR_NOT_SUPPORTED; diff --git a/src/vs/workbench/services/extensions/browser/extensionService.ts b/src/vs/workbench/services/extensions/browser/extensionService.ts -index d0710e77fa28aacf5b4dfe85efbf67a6a9ae78ab..ceb27174aee3c78ca5a086f05a6b1d3188888034 100644 +index 9e979d28691d0b0b26fde5e46b606731e31f3da5..dd31879c7dd899c73c4a1371996912f4513bfd0d 100644 --- a/src/vs/workbench/services/extensions/browser/extensionService.ts +++ b/src/vs/workbench/services/extensions/browser/extensionService.ts -@@ -116,8 +116,10 @@ export class ExtensionService extends AbstractExtensionService implements IExten +@@ -125,8 +125,10 @@ export class ExtensionService extends AbstractExtensionService implements IExten this._remoteAgentService.getEnvironment(), this._remoteAgentService.scanExtensions() ]); @@ -3362,7 +3362,7 @@ index 44999bd842eae12b752b2e7e8c4904272b111dc1..601b1c5408835c743fe07e34da4d4534 } diff --git a/src/vs/workbench/workbench.web.main.ts b/src/vs/workbench/workbench.web.main.ts -index 0669178db4cf5efe28ffd2a8fe3301de47bcc545..28fafeb2de2efea5c6412853044ce84775f1e038 100644 +index f02bbbf874b5b18ac8d077ad56a8a4a57e77a4a6..86271940724aaf28e4eda93e59920820a7d93987 100644 --- a/src/vs/workbench/workbench.web.main.ts +++ b/src/vs/workbench/workbench.web.main.ts @@ -35,7 +35,8 @@ import 'vs/workbench/services/textfile/browser/browserTextFileService'; @@ -3376,7 +3376,7 @@ index 0669178db4cf5efe28ffd2a8fe3301de47bcc545..28fafeb2de2efea5c6412853044ce847 import 'vs/workbench/services/credentials/browser/credentialsService'; import 'vs/workbench/services/url/browser/urlService'; diff --git a/yarn.lock b/yarn.lock -index b2fbf543af319fcc3973248b4ed4981db1ca213a..f10dddd6594bed959e2caa69911f70a6ae8e0554 100644 +index 140ed883c1a92ebcd7a284b98ca71261fa9cb631..b363d7de5000fd370bb4221f48e193382648a185 100644 --- a/yarn.lock +++ b/yarn.lock @@ -140,6 +140,23 @@ @@ -3403,7 +3403,7 @@ index b2fbf543af319fcc3973248b4ed4981db1ca213a..f10dddd6594bed959e2caa69911f70a6 "@electron/get@^1.0.1": version "1.7.2" resolved "https://registry.yarnpkg.com/@electron/get/-/get-1.7.2.tgz#286436a9fb56ff1a1fcdf0e80131fd65f4d1e0fd" -@@ -5421,6 +5438,13 @@ jsprim@^1.2.2: +@@ -5375,6 +5392,13 @@ jsprim@^1.2.2: json-schema "0.2.3" verror "1.10.0" @@ -3417,7 +3417,7 @@ index b2fbf543af319fcc3973248b4ed4981db1ca213a..f10dddd6594bed959e2caa69911f70a6 just-debounce@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/just-debounce/-/just-debounce-1.0.0.tgz#87fccfaeffc0b68cd19d55f6722943f929ea35ea" -@@ -6008,26 +6032,11 @@ minimatch@0.3: +@@ -5955,26 +5979,11 @@ minimatch@0.3: dependencies: brace-expansion "^1.1.7" @@ -3445,7 +3445,7 @@ index b2fbf543af319fcc3973248b4ed4981db1ca213a..f10dddd6594bed959e2caa69911f70a6 minipass@^2.2.1, minipass@^2.3.3: version "2.3.3" resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.3.3.tgz#a7dcc8b7b833f5d368759cce544dccb55f50f233" -@@ -6797,6 +6806,11 @@ p-try@^2.0.0: +@@ -6716,6 +6725,11 @@ p-try@^2.0.0: resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.0.0.tgz#85080bb87c64688fa47996fe8f7dfbe8211760b1" integrity sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ== diff --git a/lib/vscode b/lib/vscode index a0479759d..2af051012 160000 --- a/lib/vscode +++ b/lib/vscode @@ -1 +1 @@ -Subproject commit a0479759d6e9ea56afa657e454193f72aef85bd0 +Subproject commit 2af051012b66169dde0c4dfae3f5ef48f787ff69 From a44b4455f5f633bfaa66882d1e71777105c38134 Mon Sep 17 00:00:00 2001 From: Asher Date: Wed, 7 Oct 2020 12:54:40 -0500 Subject: [PATCH 23/75] Read plugin name from package.json --- src/node/plugin.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/node/plugin.ts b/src/node/plugin.ts index 2860763ad..bd3765b6a 100644 --- a/src/node/plugin.ts +++ b/src/node/plugin.ts @@ -35,10 +35,12 @@ const loadPlugin = async (pluginPath: string, httpServer: HttpServer, args: Args const plugin: Plugin = require(pluginPath) plugin.activate(httpServer, args) + const packageJson = require(path.join(pluginPath, "package.json")) logger.debug( "Loaded plugin", - field("name", path.basename(pluginPath)), - field("version", require(path.join(pluginPath, "package.json")).version || "n/a"), + field("name", packageJson.name || path.basename(pluginPath)), + field("path", pluginPath), + field("version", packageJson.version || "n/a"), ) } catch (error) { logger.error(error.message) From 579bb94a6c702b331aa333d2e0f3e8ef598939dc Mon Sep 17 00:00:00 2001 From: Anmol Sethi Date: Tue, 8 Sep 2020 19:39:17 -0400 Subject: [PATCH 24/75] Add coder cloud expose command --- .gitignore | 1 + package.json | 2 ++ src/node/cli.ts | 5 +++++ src/node/coder-cloud.ts | 30 ++++++++++++++++++++++++++++++ src/node/entry.ts | 15 +++++++++++++++ yarn.lock | 16 +++++++++++++++- 6 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 src/node/coder-cloud.ts diff --git a/.gitignore b/.gitignore index 616f9b01b..0b810b296 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ release-images/ node_modules node-* /plugins +/lib/coder-cloud-agent diff --git a/package.json b/package.json index 4d75331e9..bf5977c8c 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "@types/pem": "^1.9.5", "@types/safe-compare": "^1.1.0", "@types/semver": "^7.1.0", + "@types/split2": "^2.1.6", "@types/tar-fs": "^2.0.0", "@types/tar-stream": "^2.1.0", "@types/ws": "^7.2.6", @@ -76,6 +77,7 @@ "safe-buffer": "^5.1.1", "safe-compare": "^1.1.4", "semver": "^7.1.3", + "split2": "^3.2.2", "tar": "^6.0.1", "tar-fs": "^2.0.0", "ws": "^7.2.0", diff --git a/src/node/cli.ts b/src/node/cli.ts index d3afe203d..b8272aa51 100644 --- a/src/node/cli.ts +++ b/src/node/cli.ts @@ -47,6 +47,8 @@ export interface Args extends VsArgs { readonly _: string[] readonly "reuse-window"?: boolean readonly "new-window"?: boolean + + readonly "expose"?: OptionalString } interface Option { @@ -155,6 +157,9 @@ const options: Options> = { locale: { type: "string" }, log: { type: LogLevel }, verbose: { type: "boolean", short: "vvv", description: "Enable verbose logging." }, + + "expose": { type: OptionalString, description: "Expose via Coder Cloud with the passed name. You'll get a URL" + + "like https://myname.coder-cloud.com at which you can easily access your code-server instance. Authorization is done via GitHub." }, } export const optionDescriptions = (): string[] => { diff --git a/src/node/coder-cloud.ts b/src/node/coder-cloud.ts new file mode 100644 index 000000000..082e2d823 --- /dev/null +++ b/src/node/coder-cloud.ts @@ -0,0 +1,30 @@ +import { spawn } from "child_process" +import path from "path" +import { logger } from "@coder/logger" +import split2 from "split2" + +export async function coderCloudExpose(serverName: string): Promise { + const coderCloudAgent = path.resolve(__dirname, "../../lib/coder-cloud-agent") + const agent = spawn(coderCloudAgent, ["link", serverName], { + stdio: ["inherit", "inherit", "pipe"], + }) + + agent.stderr.pipe(split2()).on("data", line => { + line = line.replace(/^[0-9-]+ [0-9:]+ [^ ]+\t/, "") + logger.info(line) + }) + + return new Promise((res, rej) => { + agent.on("error", rej) + + agent.on("close", code => { + if (code !== 0) { + rej({ + message: `coder cloud agent exited with ${code}`, + }) + return + } + res() + }) + }) +} diff --git a/src/node/entry.ts b/src/node/entry.ts index a416ae993..860d8de7a 100644 --- a/src/node/entry.ts +++ b/src/node/entry.ts @@ -16,6 +16,7 @@ import { AuthType, HttpServer, HttpServerOptions } from "./http" import { loadPlugins } from "./plugin" import { generateCertificate, hash, humanPath, open } from "./util" import { ipcMain, wrap } from "./wrapper" +import { coderCloudExpose } from "./coder-cloud" process.on("uncaughtException", (error) => { logger.error(`Uncaught exception: ${error.message}`) @@ -188,6 +189,20 @@ async function entry(): Promise { process.exit(1) }) vscode.on("exit", (code) => process.exit(code || 0)) + } else if (args["expose"]) { + logger.debug("exposing code-server via the coder-cloud agent") + + if (!args["expose"].value) { + logger.error("You must pass a name to expose with coder cloud. See --help") + process.exit(1) + } + + try { + await coderCloudExpose(args["expose"].value) + } catch (err) { + logger.error(err.message) + process.exit(1) + } } else if (process.env.VSCODE_IPC_HOOK_CLI) { const pipeArgs: OpenCommandPipeArgs = { type: "open", diff --git a/yarn.lock b/yarn.lock index 68221a85d..6f388626b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1107,6 +1107,13 @@ resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.3.tgz#3ad6ed949e7487e7bda6f886b4a2434a2c3d7b1a" integrity sha512-jQxClWFzv9IXdLdhSaTf16XI3NYe6zrEbckSpb5xhKfPbWgIyAY0AFyWWWfaiDcBuj3UHmMkCIwSRqpKMTZL2Q== +"@types/split2@^2.1.6": + version "2.1.6" + resolved "https://registry.yarnpkg.com/@types/split2/-/split2-2.1.6.tgz#b095c9e064853824b22c67993d99b066777402b1" + integrity sha512-ddaFSOMuy2Rp97l6q/LEteQygvTQJuEZ+SRhxFKR0uXGsdbFDqX/QF2xoGcOqLQ8XV91v01SnAv2vpgihNgW/Q== + dependencies: + "@types/node" "*" + "@types/tar-fs@^2.0.0": version "2.0.0" resolved "https://registry.yarnpkg.com/@types/tar-fs/-/tar-fs-2.0.0.tgz#db94cb4ea1cccecafe3d1a53812807efb4bbdbc1" @@ -5996,7 +6003,7 @@ readable-stream@^2.0.2, readable-stream@^2.2.2, readable-stream@^2.3.3, readable string_decoder "~1.1.1" util-deprecate "~1.0.1" -readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0: +readable-stream@^3.0.0, readable-stream@^3.1.1, readable-stream@^3.4.0, 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== @@ -6621,6 +6628,13 @@ split-string@^3.0.1, split-string@^3.0.2: dependencies: extend-shallow "^3.0.0" +split2@^3.2.2: + version "3.2.2" + resolved "https://registry.yarnpkg.com/split2/-/split2-3.2.2.tgz#bf2cf2a37d838312c249c89206fd7a17dd12365f" + integrity sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg== + dependencies: + readable-stream "^3.0.0" + sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" From c7c62daa67ef926c57c2f38271d5b9f2018e07a7 Mon Sep 17 00:00:00 2001 From: Anmol Sethi Date: Tue, 8 Sep 2020 19:53:14 -0400 Subject: [PATCH 25/75] Remove unused code in optionDescriptions --- src/node/cli.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/node/cli.ts b/src/node/cli.ts index b8272aa51..8830531fc 100644 --- a/src/node/cli.ts +++ b/src/node/cli.ts @@ -175,7 +175,7 @@ export const optionDescriptions = (): string[] => { ([k, v]) => `${" ".repeat(widths.short - (v.short ? v.short.length : 0))}${v.short ? `-${v.short}` : " "} --${k}${" ".repeat( widths.long - k.length, - )} ${v.description}${typeof v.type === "object" ? ` [${Object.values(v.type).join(", ")}]` : ""}`, + )} ${v.description}`, ) } From 916e24e1098d21893e4df6b0ec5a1d3723d71987 Mon Sep 17 00:00:00 2001 From: Anmol Sethi Date: Tue, 8 Sep 2020 20:30:31 -0400 Subject: [PATCH 26/75] Add support for multiline descriptions --- src/node/cli.ts | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/src/node/cli.ts b/src/node/cli.ts index 8830531fc..318b85935 100644 --- a/src/node/cli.ts +++ b/src/node/cli.ts @@ -131,8 +131,8 @@ const options: Options> = { force: { type: "boolean", description: "Avoid prompts when installing VS Code extensions." }, "install-extension": { type: "string[]", - description: - "Install or update a VS Code extension by id or vsix. The identifier of an extension is `${publisher}.${name}`. To install a specific version provide `@${version}`. For example: 'vscode.csharp@1.2.3'.", + description: "Install or update a VS Code extension by id or vsix. The identifier of an extension is `${publisher}.${name}`.\n" + + "To install a specific version provide `@${version}`. For example: 'vscode.csharp@1.2.3'.", }, "enable-proposed-api": { type: "string[]", @@ -158,8 +158,14 @@ const options: Options> = { log: { type: LogLevel }, verbose: { type: "boolean", short: "vvv", description: "Enable verbose logging." }, - "expose": { type: OptionalString, description: "Expose via Coder Cloud with the passed name. You'll get a URL" + - "like https://myname.coder-cloud.com at which you can easily access your code-server instance. Authorization is done via GitHub." }, + "expose": { + type: OptionalString, + description: ` + Securely expose code-server via Coder Cloud with the passed name. You'll get a URL like + https://myname.coder-cloud.com at which you can easily access your code-server instance. + Authorization is done via GitHub. Only the first code-server spawned with the current + configuration will be accessible.` + }, } export const optionDescriptions = (): string[] => { @@ -172,10 +178,16 @@ export const optionDescriptions = (): string[] => { { short: 0, long: 0 }, ) return entries.map( - ([k, v]) => - `${" ".repeat(widths.short - (v.short ? v.short.length : 0))}${v.short ? `-${v.short}` : " "} --${k}${" ".repeat( - widths.long - k.length, - )} ${v.description}`, + ([k, v]) => { + let help = `${" ".repeat(widths.short - (v.short ? v.short.length : 0))}${v.short ? `-${v.short}` : " "} --${k} ` + return help + v.description?.trim().split(/\n/).map((line, i) => { + line = line.trim() + if (i == 0) { + return " ".repeat(widths.long - k.length) + line + } + return " ".repeat(widths.long + widths.short + 6) + line + }).join("\n") + }, ) } From 55a7e8b56fa9d5d89d5a7df9509d38fb421d292a Mon Sep 17 00:00:00 2001 From: Anmol Sethi Date: Wed, 9 Sep 2020 00:03:01 -0400 Subject: [PATCH 27/75] Implement automatic cloud proxying --- package.json | 3 +- src/node/coder-cloud.ts | 129 +++++++++++++++++++++++++++++++++++++++- src/node/entry.ts | 4 +- yarn.lock | 5 ++ 4 files changed, 138 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index bf5977c8c..5bf72f641 100644 --- a/package.json +++ b/package.json @@ -82,7 +82,8 @@ "tar-fs": "^2.0.0", "ws": "^7.2.0", "xdg-basedir": "^4.0.0", - "yarn": "^1.22.4" + "yarn": "^1.22.4", + "delay": "^4.4.0" }, "bin": { "code-server": "out/node/entry.js" diff --git a/src/node/coder-cloud.ts b/src/node/coder-cloud.ts index 082e2d823..167b80c3f 100644 --- a/src/node/coder-cloud.ts +++ b/src/node/coder-cloud.ts @@ -2,9 +2,14 @@ import { spawn } from "child_process" import path from "path" import { logger } from "@coder/logger" import split2 from "split2" +import delay from "delay" +import fs from "fs" +import { promisify } from "util" +import xdgBasedir from "xdg-basedir" + +const coderCloudAgent = path.resolve(__dirname, "../../lib/coder-cloud-agent") export async function coderCloudExpose(serverName: string): Promise { - const coderCloudAgent = path.resolve(__dirname, "../../lib/coder-cloud-agent") const agent = spawn(coderCloudAgent, ["link", serverName], { stdio: ["inherit", "inherit", "pipe"], }) @@ -28,3 +33,125 @@ export async function coderCloudExpose(serverName: string): Promise { }) }) } + +export function coderCloudProxy(addr: string) { + // addr needs to be in host:port format. + // So we trim the protocol. + addr = addr.replace(/^https?:\/\//, "") + + if (!xdgBasedir.config) { + return + } + + const sessionTokenPath = path.join(xdgBasedir.config, "coder-cloud", "session") + + const _proxy = async () => { + await waitForPath(sessionTokenPath) + + logger.info("exposing coder-server with coder-cloud") + + const agent = spawn(coderCloudAgent, ["proxy", "--code-server-addr", addr], { + stdio: ["inherit", "inherit", "pipe"], + }) + + agent.stderr.pipe(split2()).on("data", line => { + line = line.replace(/^[0-9-]+ [0-9:]+ [^ ]+\t/, "") + logger.info(line) + }) + + return new Promise((res, rej) => { + agent.on("error", rej) + + agent.on("close", code => { + if (code !== 0) { + rej({ + message: `coder cloud agent exited with ${code}`, + }) + return + } + res() + }) + }) + } + + const proxy = async () => { + try { + await _proxy() + } catch(err) { + logger.error(err.message) + } + setTimeout(proxy, 3000) + } + proxy() +} + +/** + * waitForPath efficiently implements waiting for the existence of a path. + * + * We intentionally do not use fs.watchFile as it is very slow from testing. + * I believe it polls instead of watching. + * + * The way this works is for each level of the path it will check if it exists + * and if not, it will wait for it. e.g. if the path is /home/nhooyr/.config/coder-cloud/session + * then first it will check if /home exists, then /home/nhooyr and so on. + * + * The wait works by first creating a watch promise for the p segment. + * We call fs.watch on the dirname of the p segment. When the dirname has a change, + * we check if the p segment exists and if it does, we resolve the watch promise. + * On any error or the watcher being closed, we reject the watch promise. + * + * Once that promise is setup, we check if the p segment exists with fs.exists + * and if it does, we close the watcher and return. + * + * Now we race the watch promise and a 2000ms delay promise. Once the race + * is complete, we close the watcher. + * + * If the watch promise was the one to resolve, we return. + * Otherwise we setup the watch promise again and retry. + * + * This combination of polling and watching is very reliable and efficient. + */ +async function waitForPath(p: string): Promise { + const segs = p.split(path.sep) + for (let i = 0; i < segs.length; i++) { + const s = path.join("/", ...segs.slice(0, i + 1)) + // We need to wait for each segment to exist. + await _waitForPath(s) + } +} + +async function _waitForPath(p: string): Promise { + const watchDir = path.dirname(p) + + logger.debug(`waiting for ${p}`) + + for (;;) { + const w = fs.watch(watchDir) + const watchPromise = new Promise((res, rej) => { + w.on("change", async () => { + if (await promisify(fs.exists)(p)) { + res() + } + }) + w.on("close", () => rej(new Error("watcher closed"))) + w.on("error", rej) + }) + + // We want to ignore any errors from this promise being rejected if the file + // already exists below. + watchPromise.catch(() => {}) + + if (await promisify(fs.exists)(p)) { + // The path exists! + w.close() + return + } + + // Now we wait for either the watch promise to resolve/reject or 2000ms. + const s = await Promise.race([watchPromise.then(() => "exists"), delay(2000)]) + w.close() + if (s === "exists") { + return + } + } +} diff --git a/src/node/entry.ts b/src/node/entry.ts index 860d8de7a..539b3bccd 100644 --- a/src/node/entry.ts +++ b/src/node/entry.ts @@ -12,11 +12,11 @@ import { StaticHttpProvider } from "./app/static" import { UpdateHttpProvider } from "./app/update" import { VscodeHttpProvider } from "./app/vscode" import { Args, bindAddrFromAllSources, optionDescriptions, parse, readConfigFile, setDefaults } from "./cli" +import { coderCloudExpose, coderCloudProxy } from "./coder-cloud" import { AuthType, HttpServer, HttpServerOptions } from "./http" import { loadPlugins } from "./plugin" import { generateCertificate, hash, humanPath, open } from "./util" import { ipcMain, wrap } from "./wrapper" -import { coderCloudExpose } from "./coder-cloud" process.on("uncaughtException", (error) => { logger.error(`Uncaught exception: ${error.message}`) @@ -123,6 +123,8 @@ const main = async (args: Args, cliArgs: Args, configArgs: Args): Promise httpServer.proxyDomains.forEach((domain) => logger.info(` - *.${domain}`)) } + coderCloudProxy(serverAddress!) + if (serverAddress && !options.socket && args.open) { // The web socket doesn't seem to work if browsing with 0.0.0.0. const openAddress = serverAddress.replace(/:\/\/0.0.0.0/, "://localhost") diff --git a/yarn.lock b/yarn.lock index 6f388626b..8e13b6f35 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2525,6 +2525,11 @@ define-property@^2.0.2: is-descriptor "^1.0.2" isobject "^3.0.1" +delay@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/delay/-/delay-4.4.0.tgz#71abc745f3ce043fe7f450491236541edec4ad0c" + integrity sha512-txgOrJu3OdtOfTiEOT2e76dJVfG/1dz2NZ4F0Pyt4UGZJryssMRp5vdM5wQoLwSOBNdrJv3F9PAhp/heqd7vrA== + delayed-stream@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" From 0aa98279d679c97ded60e71a94267b4d2284f0cd Mon Sep 17 00:00:00 2001 From: Anmol Sethi Date: Wed, 9 Sep 2020 00:06:28 -0400 Subject: [PATCH 28/75] Fixes for CI --- src/node/cli.ts | 40 +++++++++++++++++++++++----------------- src/node/coder-cloud.ts | 16 ++++++++-------- 2 files changed, 31 insertions(+), 25 deletions(-) diff --git a/src/node/cli.ts b/src/node/cli.ts index 318b85935..c4d0d9dd4 100644 --- a/src/node/cli.ts +++ b/src/node/cli.ts @@ -48,7 +48,7 @@ export interface Args extends VsArgs { readonly "reuse-window"?: boolean readonly "new-window"?: boolean - readonly "expose"?: OptionalString + readonly expose?: OptionalString } interface Option { @@ -131,8 +131,9 @@ const options: Options> = { force: { type: "boolean", description: "Avoid prompts when installing VS Code extensions." }, "install-extension": { type: "string[]", - description: "Install or update a VS Code extension by id or vsix. The identifier of an extension is `${publisher}.${name}`.\n" + - "To install a specific version provide `@${version}`. For example: 'vscode.csharp@1.2.3'.", + description: + "Install or update a VS Code extension by id or vsix. The identifier of an extension is `${publisher}.${name}`.\n" + + "To install a specific version provide `@${version}`. For example: 'vscode.csharp@1.2.3'.", }, "enable-proposed-api": { type: "string[]", @@ -158,13 +159,13 @@ const options: Options> = { log: { type: LogLevel }, verbose: { type: "boolean", short: "vvv", description: "Enable verbose logging." }, - "expose": { + expose: { type: OptionalString, description: ` Securely expose code-server via Coder Cloud with the passed name. You'll get a URL like https://myname.coder-cloud.com at which you can easily access your code-server instance. Authorization is done via GitHub. Only the first code-server spawned with the current - configuration will be accessible.` + configuration will be accessible.`, }, } @@ -177,18 +178,23 @@ export const optionDescriptions = (): string[] => { }), { short: 0, long: 0 }, ) - return entries.map( - ([k, v]) => { - let help = `${" ".repeat(widths.short - (v.short ? v.short.length : 0))}${v.short ? `-${v.short}` : " "} --${k} ` - return help + v.description?.trim().split(/\n/).map((line, i) => { - line = line.trim() - if (i == 0) { - return " ".repeat(widths.long - k.length) + line - } - return " ".repeat(widths.long + widths.short + 6) + line - }).join("\n") - }, - ) + return entries.map(([k, v]) => { + const help = `${" ".repeat(widths.short - (v.short ? v.short.length : 0))}${v.short ? `-${v.short}` : " "} --${k} ` + return ( + help + + v.description + ?.trim() + .split(/\n/) + .map((line, i) => { + line = line.trim() + if (i === 0) { + return " ".repeat(widths.long - k.length) + line + } + return " ".repeat(widths.long + widths.short + 6) + line + }) + .join("\n") + ) + }) } export const parse = ( diff --git a/src/node/coder-cloud.ts b/src/node/coder-cloud.ts index 167b80c3f..b621b08cd 100644 --- a/src/node/coder-cloud.ts +++ b/src/node/coder-cloud.ts @@ -1,9 +1,9 @@ -import { spawn } from "child_process" -import path from "path" import { logger } from "@coder/logger" -import split2 from "split2" +import { spawn } from "child_process" import delay from "delay" import fs from "fs" +import path from "path" +import split2 from "split2" import { promisify } from "util" import xdgBasedir from "xdg-basedir" @@ -14,7 +14,7 @@ export async function coderCloudExpose(serverName: string): Promise { stdio: ["inherit", "inherit", "pipe"], }) - agent.stderr.pipe(split2()).on("data", line => { + agent.stderr.pipe(split2()).on("data", (line) => { line = line.replace(/^[0-9-]+ [0-9:]+ [^ ]+\t/, "") logger.info(line) }) @@ -22,7 +22,7 @@ export async function coderCloudExpose(serverName: string): Promise { return new Promise((res, rej) => { agent.on("error", rej) - agent.on("close", code => { + agent.on("close", (code) => { if (code !== 0) { rej({ message: `coder cloud agent exited with ${code}`, @@ -54,7 +54,7 @@ export function coderCloudProxy(addr: string) { stdio: ["inherit", "inherit", "pipe"], }) - agent.stderr.pipe(split2()).on("data", line => { + agent.stderr.pipe(split2()).on("data", (line) => { line = line.replace(/^[0-9-]+ [0-9:]+ [^ ]+\t/, "") logger.info(line) }) @@ -62,7 +62,7 @@ export function coderCloudProxy(addr: string) { return new Promise((res, rej) => { agent.on("error", rej) - agent.on("close", code => { + agent.on("close", (code) => { if (code !== 0) { rej({ message: `coder cloud agent exited with ${code}`, @@ -77,7 +77,7 @@ export function coderCloudProxy(addr: string) { const proxy = async () => { try { await _proxy() - } catch(err) { + } catch (err) { logger.error(err.message) } setTimeout(proxy, 3000) From eacca7d6929cc1a40f8ad7053a09db118d9733a3 Mon Sep 17 00:00:00 2001 From: Anmol Sethi Date: Wed, 9 Sep 2020 00:07:04 -0400 Subject: [PATCH 29/75] Unrelated fixes for CI --- ci/release-image/entrypoint.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ci/release-image/entrypoint.sh b/ci/release-image/entrypoint.sh index ee58d07a6..7842a3564 100755 --- a/ci/release-image/entrypoint.sh +++ b/ci/release-image/entrypoint.sh @@ -2,7 +2,8 @@ set -eu # This isn't set by default. -export USER="$(whoami)" +USER="$(whoami)" +export USER if [ "${DOCKER_USER-}" != "$USER" ]; then echo "$DOCKER_USER ALL=(ALL) NOPASSWD:ALL" | sudo tee -a /etc/sudoers.d/nopasswd > /dev/null @@ -11,7 +12,7 @@ if [ "${DOCKER_USER-}" != "$USER" ]; then sudo usermod --login "$DOCKER_USER" coder sudo groupmod -n "$DOCKER_USER" coder - export USER="$(whoami)" + USER="$DOCKER_USER" sudo sed -i "/coder/d" /etc/sudoers.d/nopasswd sudo sed -i "s/coder/$DOCKER_USER/g" /etc/fixuid/config.yml From b22f3cb72f4e95dd0b432dc1b0f98295969c3d12 Mon Sep 17 00:00:00 2001 From: Anmol Sethi Date: Tue, 15 Sep 2020 10:15:51 -0400 Subject: [PATCH 30/75] Add $HOME to ./ci/dev/image/run.sh --- .gitignore | 1 + ci/build/clean.sh | 3 ++- ci/dev/image/run.sh | 2 ++ 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 0b810b296..4929c46fb 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ node_modules node-* /plugins /lib/coder-cloud-agent +.home diff --git a/ci/build/clean.sh b/ci/build/clean.sh index 0e0425a4b..7a83a2845 100755 --- a/ci/build/clean.sh +++ b/ci/build/clean.sh @@ -14,7 +14,8 @@ main() { release-images \ dist \ .cache \ - node-* + node-* \ + .home pushd lib/vscode git clean -xffd diff --git a/ci/dev/image/run.sh b/ci/dev/image/run.sh index 70ab67e1d..0f17d4c27 100755 --- a/ci/dev/image/run.sh +++ b/ci/dev/image/run.sh @@ -4,11 +4,13 @@ set -euo pipefail main() { cd "$(dirname "$0")/../../.." source ./ci/lib.sh + mkdir -p .home docker run \ -it \ --rm \ -v "$PWD:/src" \ + -e HOME="/src/.home" \ -w /src \ -p 127.0.0.1:8080:8080 \ -u "$(id -u):$(id -g)" \ From 607444c695aefe0a29503ee0434024a90109b8f7 Mon Sep 17 00:00:00 2001 From: Anmol Sethi Date: Tue, 15 Sep 2020 10:41:47 -0400 Subject: [PATCH 31/75] Switch off debian:8 to debian:10 for the typescript build image We only want to use an old version for glibc which the centos:7 image takes care of. The old version of git used in debian:8 was causing problems with the uid/gid passthrough with no user in passwd. --- .github/workflows/ci.yaml | 10 +++++----- .github/workflows/publish.yaml | 4 ++-- ci/dev/image/run.sh | 3 ++- ci/dev/lint.sh | 5 +---- ci/images/centos7/Dockerfile | 2 +- ci/images/{debian8 => debian10}/Dockerfile | 22 ++++++---------------- doc/CONTRIBUTING.md | 4 ++-- 7 files changed, 19 insertions(+), 31 deletions(-) rename ci/images/{debian8 => debian10}/Dockerfile (57%) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index dcf917841..a265c98ef 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -8,7 +8,7 @@ jobs: steps: - uses: actions/checkout@v1 - name: Run ./ci/steps/fmt.sh - uses: ./ci/images/debian8 + uses: ./ci/images/debian10 with: args: ./ci/steps/fmt.sh @@ -17,7 +17,7 @@ jobs: steps: - uses: actions/checkout@v1 - name: Run ./ci/steps/lint.sh - uses: ./ci/images/debian8 + uses: ./ci/images/debian10 with: args: ./ci/steps/lint.sh @@ -26,7 +26,7 @@ jobs: steps: - uses: actions/checkout@v1 - name: Run ./ci/steps/test.sh - uses: ./ci/images/debian8 + uses: ./ci/images/debian10 with: args: ./ci/steps/test.sh @@ -35,7 +35,7 @@ jobs: steps: - uses: actions/checkout@v1 - name: Run ./ci/steps/release.sh - uses: ./ci/images/debian8 + uses: ./ci/images/debian10 with: args: ./ci/steps/release.sh - name: Upload npm package artifact @@ -116,7 +116,7 @@ jobs: name: release-packages path: ./release-packages - name: Run ./ci/steps/build-docker-image.sh - uses: ./ci/images/debian8 + uses: ./ci/images/debian10 with: args: ./ci/steps/build-docker-image.sh - name: Upload release image diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index c2fe429b9..74540651f 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -10,7 +10,7 @@ jobs: steps: - uses: actions/checkout@v1 - name: Run ./ci/steps/publish-npm.sh - uses: ./ci/images/debian8 + uses: ./ci/images/debian10 with: args: ./ci/steps/publish-npm.sh env: @@ -22,7 +22,7 @@ jobs: steps: - uses: actions/checkout@v1 - name: Run ./ci/steps/push-docker-manifest.sh - uses: ./ci/images/debian8 + uses: ./ci/images/debian10 with: args: ./ci/steps/push-docker-manifest.sh env: diff --git a/ci/dev/image/run.sh b/ci/dev/image/run.sh index 0f17d4c27..08391581b 100755 --- a/ci/dev/image/run.sh +++ b/ci/dev/image/run.sh @@ -11,11 +11,12 @@ main() { --rm \ -v "$PWD:/src" \ -e HOME="/src/.home" \ + -e USER="coder" \ -w /src \ -p 127.0.0.1:8080:8080 \ -u "$(id -u):$(id -g)" \ -e CI \ - "$(docker_build ./ci/images/debian8)" \ + "$(docker_build ./ci/images/"${IMAGE-debian10}")" \ "$@" } diff --git a/ci/dev/lint.sh b/ci/dev/lint.sh index 219c3793b..5f7c549bc 100755 --- a/ci/dev/lint.sh +++ b/ci/dev/lint.sh @@ -7,10 +7,7 @@ main() { eslint --max-warnings=0 --fix $(git ls-files "*.ts" "*.tsx" "*.js") stylelint $(git ls-files "*.css") tsc --noEmit - # See comment in ./ci/image/debian8 - if [[ ! ${CI-} ]]; then - shellcheck -e SC2046,SC2164,SC2154,SC1091,SC1090,SC2002 $(git ls-files "*.sh") - fi + shellcheck -e SC2046,SC2164,SC2154,SC1091,SC1090,SC2002 $(git ls-files "*.sh") } main "$@" diff --git a/ci/images/centos7/Dockerfile b/ci/images/centos7/Dockerfile index 92c212024..2c0c71ecd 100644 --- a/ci/images/centos7/Dockerfile +++ b/ci/images/centos7/Dockerfile @@ -15,7 +15,7 @@ RUN npm config set python python2 RUN yum install -y epel-release && yum install -y jq RUN yum install -y rsync -# Copied from ../debian8/Dockerfile +# Copied from ../debian10/Dockerfile # Install Go dependencies RUN ARCH="$(uname -m | sed 's/x86_64/amd64/; s/aarch64/arm64/')" && \ curl -fsSL "https://dl.google.com/go/go1.14.3.linux-$ARCH.tar.gz" | tar -C /usr/local -xz diff --git a/ci/images/debian8/Dockerfile b/ci/images/debian10/Dockerfile similarity index 57% rename from ci/images/debian8/Dockerfile rename to ci/images/debian10/Dockerfile index 4c62a398b..a13a25a03 100644 --- a/ci/images/debian8/Dockerfile +++ b/ci/images/debian10/Dockerfile @@ -1,4 +1,4 @@ -FROM debian:8 +FROM debian:10 RUN apt-get update @@ -24,23 +24,13 @@ RUN apt-get install -y build-essential \ RUN apt-get install -y gettext-base # Misc build dependencies. -RUN apt-get install -y git rsync unzip - -# We need latest jq from debian buster for date support. -RUN ARCH="$(dpkg --print-architecture)" && \ - curl -fsSOL http://http.us.debian.org/debian/pool/main/libo/libonig/libonig5_6.9.1-1_$ARCH.deb && \ - dpkg -i libonig*.deb && \ - curl -fsSOL http://http.us.debian.org/debian/pool/main/j/jq/libjq1_1.5+dfsg-2+b1_$ARCH.deb && \ - dpkg -i libjq*.deb && \ - curl -fsSOL http://http.us.debian.org/debian/pool/main/j/jq/jq_1.5+dfsg-2+b1_$ARCH.deb && \ - dpkg -i jq*.deb && rm *.deb +RUN apt-get install -y git rsync unzip jq # Installs shellcheck. -# Unfortunately coredumps on debian:8 so disabled for now. -#RUN curl -fsSL https://github.com/koalaman/shellcheck/releases/download/v0.7.1/shellcheck-v0.7.1.linux.$(uname -m).tar.xz | \ -# tar -xJ && \ -# mv shellcheck*/shellcheck /usr/local/bin && \ -# rm -R shellcheck* +RUN curl -fsSL https://github.com/koalaman/shellcheck/releases/download/v0.7.1/shellcheck-v0.7.1.linux.$(uname -m).tar.xz | \ + tar -xJ && \ + mv shellcheck*/shellcheck /usr/local/bin && \ + rm -R shellcheck* # Install Go dependencies RUN ARCH="$(uname -m | sed 's/x86_64/amd64/; s/aarch64/arm64/')" && \ diff --git a/doc/CONTRIBUTING.md b/doc/CONTRIBUTING.md index 80348848d..a15e25d20 100644 --- a/doc/CONTRIBUTING.md +++ b/doc/CONTRIBUTING.md @@ -32,7 +32,7 @@ Differences: - We require a minimum of node v12 but later versions should work. - We use [nfpm](https://github.com/goreleaser/nfpm) to build `.deb` and `.rpm` packages. - We use [jq](https://stedolan.github.io/jq/) to build code-server releases. -- The [CI container](../ci/images/debian8/Dockerfile) is a useful reference for all our dependencies. +- The [CI container](../ci/images/debian10/Dockerfile) is a useful reference for all our dependencies. ## Development Workflow @@ -76,7 +76,7 @@ node . Build release packages (make sure you run `./ci/steps/release.sh` first): ``` -./ci/dev/image/run.sh ./ci/steps/release-packages.sh +IMAGE=centos7 ./ci/dev/image/run.sh ./ci/steps/release-packages.sh # The standalone release is in ./release-standalone # .deb, .rpm and the standalone archive are in ./release-packages ``` From 22c4a7e10f8cfd4a7a6f752952221f80f37bc5f1 Mon Sep 17 00:00:00 2001 From: Anmol Sethi Date: Mon, 28 Sep 2020 15:43:26 -0400 Subject: [PATCH 32/75] Make linking and starting code-server to the cloud a single command --- src/node/cli.ts | 6 +++--- src/node/coder-cloud.ts | 2 +- src/node/entry.ts | 41 ++++++++++++++++++++++++++--------------- 3 files changed, 30 insertions(+), 19 deletions(-) diff --git a/src/node/cli.ts b/src/node/cli.ts index c4d0d9dd4..854686685 100644 --- a/src/node/cli.ts +++ b/src/node/cli.ts @@ -48,7 +48,7 @@ export interface Args extends VsArgs { readonly "reuse-window"?: boolean readonly "new-window"?: boolean - readonly expose?: OptionalString + readonly "coder-link"?: OptionalString } interface Option { @@ -159,10 +159,10 @@ const options: Options> = { log: { type: LogLevel }, verbose: { type: "boolean", short: "vvv", description: "Enable verbose logging." }, - expose: { + "coder-link": { type: OptionalString, description: ` - Securely expose code-server via Coder Cloud with the passed name. You'll get a URL like + Securely link code-server via Coder Cloud with the passed name. You'll get a URL like https://myname.coder-cloud.com at which you can easily access your code-server instance. Authorization is done via GitHub. Only the first code-server spawned with the current configuration will be accessible.`, diff --git a/src/node/coder-cloud.ts b/src/node/coder-cloud.ts index b621b08cd..c8782812b 100644 --- a/src/node/coder-cloud.ts +++ b/src/node/coder-cloud.ts @@ -9,7 +9,7 @@ import xdgBasedir from "xdg-basedir" const coderCloudAgent = path.resolve(__dirname, "../../lib/coder-cloud-agent") -export async function coderCloudExpose(serverName: string): Promise { +export async function coderCloudLink(serverName: string): Promise { const agent = spawn(coderCloudAgent, ["link", serverName], { stdio: ["inherit", "inherit", "pipe"], }) diff --git a/src/node/entry.ts b/src/node/entry.ts index 539b3bccd..42abbc2de 100644 --- a/src/node/entry.ts +++ b/src/node/entry.ts @@ -12,7 +12,7 @@ import { StaticHttpProvider } from "./app/static" import { UpdateHttpProvider } from "./app/update" import { VscodeHttpProvider } from "./app/vscode" import { Args, bindAddrFromAllSources, optionDescriptions, parse, readConfigFile, setDefaults } from "./cli" -import { coderCloudExpose, coderCloudProxy } from "./coder-cloud" +import { coderCloudLink, coderCloudProxy } from "./coder-cloud" import { AuthType, HttpServer, HttpServerOptions } from "./http" import { loadPlugins } from "./plugin" import { generateCertificate, hash, humanPath, open } from "./util" @@ -36,6 +36,15 @@ const version = pkg.version || "development" const commit = pkg.commit || "development" const main = async (args: Args, cliArgs: Args, configArgs: Args): Promise => { + if (args["coder-link"]) { + // If we're being exposed to the cloud, we listen on a random address. + args = { + ...args, + host: "localhost", + port: 0, + } + } + if (!args.auth) { args = { ...args, @@ -131,6 +140,22 @@ const main = async (args: Args, cliArgs: Args, configArgs: Args): Promise await open(openAddress).catch(console.error) logger.info(`Opened ${openAddress}`) } + + if (args["coder-link"]) { + if (!args["coder-link"].value) { + logger.error("You must pass a name to link with coder cloud. See --help") + process.exit(1) + } + + logger.info(`linking code-server to the cloud with name ${args["coder-link"].value}`) + + try { + await coderCloudLink(args["coder-link"].value) + } catch (err) { + logger.error(err.message) + process.exit(1) + } + } } async function entry(): Promise { @@ -191,20 +216,6 @@ async function entry(): Promise { process.exit(1) }) vscode.on("exit", (code) => process.exit(code || 0)) - } else if (args["expose"]) { - logger.debug("exposing code-server via the coder-cloud agent") - - if (!args["expose"].value) { - logger.error("You must pass a name to expose with coder cloud. See --help") - process.exit(1) - } - - try { - await coderCloudExpose(args["expose"].value) - } catch (err) { - logger.error(err.message) - process.exit(1) - } } else if (process.env.VSCODE_IPC_HOOK_CLI) { const pipeArgs: OpenCommandPipeArgs = { type: "open", From 9035bfa871669a5ff8ab9e3ab4ae485d2d57c26f Mon Sep 17 00:00:00 2001 From: Anmol Sethi Date: Tue, 6 Oct 2020 14:19:04 -0400 Subject: [PATCH 33/75] Add coder cloud agent binary to build process --- ci/build/build-code-server.sh | 6 ++++++ ci/build/build-release.sh | 1 + ci/build/clean.sh | 3 ++- ci/dev/image/run.sh | 1 + 4 files changed, 10 insertions(+), 1 deletion(-) diff --git a/ci/build/build-code-server.sh b/ci/build/build-code-server.sh index df0852804..0aff035af 100755 --- a/ci/build/build-code-server.sh +++ b/ci/build/build-code-server.sh @@ -18,6 +18,12 @@ main() { chmod +x out/node/entry.js fi + if ! [ -f ./lib/coder-cloud-agent ]; then + OS="$(uname | tr '[:upper:]' '[:lower:]')" + curl -fsSL "https://storage.googleapis.com/coder-cloud-releases/agent/latest/$OS/cloud-agent" -o ./lib/coder-cloud-agent + chmod +x ./lib/coder-cloud-agent + fi + parcel build \ --public-url "." \ --out-dir dist \ diff --git a/ci/build/build-release.sh b/ci/build/build-release.sh index 8d8d1c903..88d3fe613 100755 --- a/ci/build/build-release.sh +++ b/ci/build/build-release.sh @@ -25,6 +25,7 @@ main() { rsync README.md "$RELEASE_PATH" rsync LICENSE.txt "$RELEASE_PATH" rsync ./lib/vscode/ThirdPartyNotices.txt "$RELEASE_PATH" + rsync ./lib/coder-cloud-agent "$RELEASE_PATH/lib" # code-server exports types which can be imported and used by plugins. Those # types import ipc.d.ts but it isn't included in the final vscode build so diff --git a/ci/build/clean.sh b/ci/build/clean.sh index 7a83a2845..52d123c9a 100755 --- a/ci/build/clean.sh +++ b/ci/build/clean.sh @@ -15,7 +15,8 @@ main() { dist \ .cache \ node-* \ - .home + .home \ + lib/coder-cloud-agent pushd lib/vscode git clean -xffd diff --git a/ci/dev/image/run.sh b/ci/dev/image/run.sh index 08391581b..0557f1b2a 100755 --- a/ci/dev/image/run.sh +++ b/ci/dev/image/run.sh @@ -12,6 +12,7 @@ main() { -v "$PWD:/src" \ -e HOME="/src/.home" \ -e USER="coder" \ + -e GITHUB_TOKEN \ -w /src \ -p 127.0.0.1:8080:8080 \ -u "$(id -u):$(id -g)" \ From c308ae0eddb71bbeea3da025c1fdb676e4485ac3 Mon Sep 17 00:00:00 2001 From: Anmol Sethi Date: Tue, 6 Oct 2020 14:21:08 -0400 Subject: [PATCH 34/75] Ignore dirty lib/vscode --- .gitmodules | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitmodules b/.gitmodules index 9854a1b1d..f2cdafc7a 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,4 @@ [submodule "lib/vscode"] path = lib/vscode url = https://github.com/microsoft/vscode + ignore = dirty From fae07e14fbd2ed4101d25c8c5bf715efcd041253 Mon Sep 17 00:00:00 2001 From: Anmol Sethi Date: Tue, 6 Oct 2020 14:36:55 -0400 Subject: [PATCH 35/75] Fix Go inside dev image --- ci/images/centos7/Dockerfile | 9 +++++++-- ci/images/debian10/Dockerfile | 9 +++++++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/ci/images/centos7/Dockerfile b/ci/images/centos7/Dockerfile index 2c0c71ecd..52a101778 100644 --- a/ci/images/centos7/Dockerfile +++ b/ci/images/centos7/Dockerfile @@ -16,10 +16,15 @@ RUN yum install -y epel-release && yum install -y jq RUN yum install -y rsync # Copied from ../debian10/Dockerfile -# Install Go dependencies +# Install Go. RUN ARCH="$(uname -m | sed 's/x86_64/amd64/; s/aarch64/arm64/')" && \ curl -fsSL "https://dl.google.com/go/go1.14.3.linux-$ARCH.tar.gz" | tar -C /usr/local -xz -ENV PATH=/usr/local/go/bin:/root/go/bin:$PATH +ENV GOPATH=/gopath +# Ensures running this image as another user works. +RUN mkdir -p $GOPATH && chmod -R 777 $GOPATH +ENV PATH=/usr/local/go/bin:$GOPATH/bin:$PATH + +# Install Go dependencies ENV GO111MODULE=on RUN go get mvdan.cc/sh/v3/cmd/shfmt RUN go get github.com/goreleaser/nfpm/cmd/nfpm diff --git a/ci/images/debian10/Dockerfile b/ci/images/debian10/Dockerfile index a13a25a03..108348b65 100644 --- a/ci/images/debian10/Dockerfile +++ b/ci/images/debian10/Dockerfile @@ -32,10 +32,15 @@ RUN curl -fsSL https://github.com/koalaman/shellcheck/releases/download/v0.7.1/s mv shellcheck*/shellcheck /usr/local/bin && \ rm -R shellcheck* -# Install Go dependencies +# Install Go. RUN ARCH="$(uname -m | sed 's/x86_64/amd64/; s/aarch64/arm64/')" && \ curl -fsSL "https://dl.google.com/go/go1.14.3.linux-$ARCH.tar.gz" | tar -C /usr/local -xz -ENV PATH=/usr/local/go/bin:/root/go/bin:$PATH +ENV GOPATH=/gopath +# Ensures running this image as another user works. +RUN mkdir -p $GOPATH && chmod -R 777 $GOPATH +ENV PATH=/usr/local/go/bin:$GOPATH/bin:$PATH + +# Install Go dependencies ENV GO111MODULE=on RUN go get mvdan.cc/sh/v3/cmd/shfmt RUN go get github.com/goreleaser/nfpm/cmd/nfpm From dd996d8f6051741baff7ef1a37cfd8e21e2bd61b Mon Sep 17 00:00:00 2001 From: Anmol Sethi Date: Tue, 6 Oct 2020 14:45:27 -0400 Subject: [PATCH 36/75] v3.6.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5bf72f641..878eebf18 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-server", "license": "MIT", - "version": "3.5.0", + "version": "3.6.0", "description": "Run VS Code on a remote server.", "homepage": "https://github.com/cdr/code-server", "bugs": { From 6e8248cf0cf16383af7f6d732862a5d021649ea4 Mon Sep 17 00:00:00 2001 From: Anmol Sethi Date: Tue, 6 Oct 2020 17:19:36 -0400 Subject: [PATCH 37/75] Fix zip release creation --- ci/build/build-packages.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/build/build-packages.sh b/ci/build/build-packages.sh index 058a54781..c3ad6577a 100755 --- a/ci/build/build-packages.sh +++ b/ci/build/build-packages.sh @@ -33,7 +33,7 @@ release_archive() { elif [[ $OS == "darwin" && $ARCH == "x86_64" ]]; then # Just exists to make autoupdating from 3.2.0 work again. mv ./release-standalone "./$release_name" - zip -r "release-packages/$release_name.zip" "./$release_name" + zip -yr "release-packages/$release_name.zip" "./$release_name" mv "./$release_name" ./release-standalone return else From c3c24fe4d2935ae553ddd5a6be192f449b3f4512 Mon Sep 17 00:00:00 2001 From: Anmol Sethi Date: Tue, 6 Oct 2020 21:05:32 -0400 Subject: [PATCH 38/75] Fixes for @ammarb --- src/node/cli.ts | 8 ++++---- src/node/coder-cloud.ts | 2 +- src/node/entry.ts | 22 +++++++++------------- 3 files changed, 14 insertions(+), 18 deletions(-) diff --git a/src/node/cli.ts b/src/node/cli.ts index 854686685..61e5d9d78 100644 --- a/src/node/cli.ts +++ b/src/node/cli.ts @@ -48,7 +48,7 @@ export interface Args extends VsArgs { readonly "reuse-window"?: boolean readonly "new-window"?: boolean - readonly "coder-link"?: OptionalString + readonly "coder-bind"?: string } interface Option { @@ -159,10 +159,10 @@ const options: Options> = { log: { type: LogLevel }, verbose: { type: "boolean", short: "vvv", description: "Enable verbose logging." }, - "coder-link": { - type: OptionalString, + "coder-bind": { + type: "string", description: ` - Securely link code-server via Coder Cloud with the passed name. You'll get a URL like + Securely bind code-server via Coder Cloud with the passed name. You'll get a URL like https://myname.coder-cloud.com at which you can easily access your code-server instance. Authorization is done via GitHub. Only the first code-server spawned with the current configuration will be accessible.`, diff --git a/src/node/coder-cloud.ts b/src/node/coder-cloud.ts index c8782812b..5f7b7c0a1 100644 --- a/src/node/coder-cloud.ts +++ b/src/node/coder-cloud.ts @@ -9,7 +9,7 @@ import xdgBasedir from "xdg-basedir" const coderCloudAgent = path.resolve(__dirname, "../../lib/coder-cloud-agent") -export async function coderCloudLink(serverName: string): Promise { +export async function coderCloudBind(serverName: string): Promise { const agent = spawn(coderCloudAgent, ["link", serverName], { stdio: ["inherit", "inherit", "pipe"], }) diff --git a/src/node/entry.ts b/src/node/entry.ts index 42abbc2de..1be886e03 100644 --- a/src/node/entry.ts +++ b/src/node/entry.ts @@ -12,7 +12,7 @@ import { StaticHttpProvider } from "./app/static" import { UpdateHttpProvider } from "./app/update" import { VscodeHttpProvider } from "./app/vscode" import { Args, bindAddrFromAllSources, optionDescriptions, parse, readConfigFile, setDefaults } from "./cli" -import { coderCloudLink, coderCloudProxy } from "./coder-cloud" +import { coderCloudBind, coderCloudProxy } from "./coder-cloud" import { AuthType, HttpServer, HttpServerOptions } from "./http" import { loadPlugins } from "./plugin" import { generateCertificate, hash, humanPath, open } from "./util" @@ -36,13 +36,15 @@ const version = pkg.version || "development" const commit = pkg.commit || "development" const main = async (args: Args, cliArgs: Args, configArgs: Args): Promise => { - if (args["coder-link"]) { - // If we're being exposed to the cloud, we listen on a random address. + if (args["coder-bind"]) { + // If we're being exposed to the cloud, we listen on a random address and disable auth. args = { ...args, host: "localhost", port: 0, + auth: AuthType.None, } + logger.info("coder-bind: disabling auth and listening on random localhost port") } if (!args.auth) { @@ -132,8 +134,6 @@ const main = async (args: Args, cliArgs: Args, configArgs: Args): Promise httpServer.proxyDomains.forEach((domain) => logger.info(` - *.${domain}`)) } - coderCloudProxy(serverAddress!) - if (serverAddress && !options.socket && args.open) { // The web socket doesn't seem to work if browsing with 0.0.0.0. const openAddress = serverAddress.replace(/:\/\/0.0.0.0/, "://localhost") @@ -141,16 +141,12 @@ const main = async (args: Args, cliArgs: Args, configArgs: Args): Promise logger.info(`Opened ${openAddress}`) } - if (args["coder-link"]) { - if (!args["coder-link"].value) { - logger.error("You must pass a name to link with coder cloud. See --help") - process.exit(1) - } - - logger.info(`linking code-server to the cloud with name ${args["coder-link"].value}`) + if (args["coder-bind"]) { + logger.info(`linking code-server to the cloud with name ${args["coder-bind"]}`) try { - await coderCloudLink(args["coder-link"].value) + await coderCloudBind(args["coder-bind"]) + coderCloudProxy(serverAddress!) } catch (err) { logger.error(err.message) process.exit(1) From 1c16814a89e8c057591101763e4d39b54bc6d8f8 Mon Sep 17 00:00:00 2001 From: Anmol Sethi Date: Tue, 6 Oct 2020 21:10:46 -0400 Subject: [PATCH 39/75] Update coder-bind docs --- src/node/cli.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/node/cli.ts b/src/node/cli.ts index 61e5d9d78..d7ae7fc23 100644 --- a/src/node/cli.ts +++ b/src/node/cli.ts @@ -164,8 +164,8 @@ const options: Options> = { description: ` Securely bind code-server via Coder Cloud with the passed name. You'll get a URL like https://myname.coder-cloud.com at which you can easily access your code-server instance. - Authorization is done via GitHub. Only the first code-server spawned with the current - configuration will be accessible.`, + Authorization is done via GitHub. + `, }, } From 4b3c089630aa0ad495e3177df824f0bb0c7d60fa Mon Sep 17 00:00:00 2001 From: Anmol Sethi Date: Wed, 7 Oct 2020 02:03:27 -0400 Subject: [PATCH 40/75] Remove dead code --- package.json | 3 +- src/node/coder-cloud.ts | 85 ----------------------------------------- src/node/entry.ts | 4 +- 3 files changed, 3 insertions(+), 89 deletions(-) diff --git a/package.json b/package.json index 878eebf18..cc3edd30a 100644 --- a/package.json +++ b/package.json @@ -82,8 +82,7 @@ "tar-fs": "^2.0.0", "ws": "^7.2.0", "xdg-basedir": "^4.0.0", - "yarn": "^1.22.4", - "delay": "^4.4.0" + "yarn": "^1.22.4" }, "bin": { "code-server": "out/node/entry.js" diff --git a/src/node/coder-cloud.ts b/src/node/coder-cloud.ts index 5f7b7c0a1..a3c3c5906 100644 --- a/src/node/coder-cloud.ts +++ b/src/node/coder-cloud.ts @@ -1,11 +1,7 @@ import { logger } from "@coder/logger" import { spawn } from "child_process" -import delay from "delay" -import fs from "fs" import path from "path" import split2 from "split2" -import { promisify } from "util" -import xdgBasedir from "xdg-basedir" const coderCloudAgent = path.resolve(__dirname, "../../lib/coder-cloud-agent") @@ -39,17 +35,7 @@ export function coderCloudProxy(addr: string) { // So we trim the protocol. addr = addr.replace(/^https?:\/\//, "") - if (!xdgBasedir.config) { - return - } - - const sessionTokenPath = path.join(xdgBasedir.config, "coder-cloud", "session") - const _proxy = async () => { - await waitForPath(sessionTokenPath) - - logger.info("exposing coder-server with coder-cloud") - const agent = spawn(coderCloudAgent, ["proxy", "--code-server-addr", addr], { stdio: ["inherit", "inherit", "pipe"], }) @@ -84,74 +70,3 @@ export function coderCloudProxy(addr: string) { } proxy() } - -/** - * waitForPath efficiently implements waiting for the existence of a path. - * - * We intentionally do not use fs.watchFile as it is very slow from testing. - * I believe it polls instead of watching. - * - * The way this works is for each level of the path it will check if it exists - * and if not, it will wait for it. e.g. if the path is /home/nhooyr/.config/coder-cloud/session - * then first it will check if /home exists, then /home/nhooyr and so on. - * - * The wait works by first creating a watch promise for the p segment. - * We call fs.watch on the dirname of the p segment. When the dirname has a change, - * we check if the p segment exists and if it does, we resolve the watch promise. - * On any error or the watcher being closed, we reject the watch promise. - * - * Once that promise is setup, we check if the p segment exists with fs.exists - * and if it does, we close the watcher and return. - * - * Now we race the watch promise and a 2000ms delay promise. Once the race - * is complete, we close the watcher. - * - * If the watch promise was the one to resolve, we return. - * Otherwise we setup the watch promise again and retry. - * - * This combination of polling and watching is very reliable and efficient. - */ -async function waitForPath(p: string): Promise { - const segs = p.split(path.sep) - for (let i = 0; i < segs.length; i++) { - const s = path.join("/", ...segs.slice(0, i + 1)) - // We need to wait for each segment to exist. - await _waitForPath(s) - } -} - -async function _waitForPath(p: string): Promise { - const watchDir = path.dirname(p) - - logger.debug(`waiting for ${p}`) - - for (;;) { - const w = fs.watch(watchDir) - const watchPromise = new Promise((res, rej) => { - w.on("change", async () => { - if (await promisify(fs.exists)(p)) { - res() - } - }) - w.on("close", () => rej(new Error("watcher closed"))) - w.on("error", rej) - }) - - // We want to ignore any errors from this promise being rejected if the file - // already exists below. - watchPromise.catch(() => {}) - - if (await promisify(fs.exists)(p)) { - // The path exists! - w.close() - return - } - - // Now we wait for either the watch promise to resolve/reject or 2000ms. - const s = await Promise.race([watchPromise.then(() => "exists"), delay(2000)]) - w.close() - if (s === "exists") { - return - } - } -} diff --git a/src/node/entry.ts b/src/node/entry.ts index 1be886e03..901c732f7 100644 --- a/src/node/entry.ts +++ b/src/node/entry.ts @@ -142,14 +142,14 @@ const main = async (args: Args, cliArgs: Args, configArgs: Args): Promise } if (args["coder-bind"]) { - logger.info(`linking code-server to the cloud with name ${args["coder-bind"]}`) try { + logger.info(`binding code-server to the cloud with name ${args["coder-bind"]}`) await coderCloudBind(args["coder-bind"]) coderCloudProxy(serverAddress!) } catch (err) { logger.error(err.message) - process.exit(1) + ipcMain().exit(1) } } } From c4f1c053bf51536cc3884d6a61b256c457e17dd4 Mon Sep 17 00:00:00 2001 From: Anmol Sethi Date: Wed, 7 Oct 2020 03:52:37 -0400 Subject: [PATCH 41/75] Show valid values for --auth in --help See https://github.com/nhooyr/code-server/pull/1/files#r485847134 --- src/node/cli.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/node/cli.ts b/src/node/cli.ts index d7ae7fc23..deedf8309 100644 --- a/src/node/cli.ts +++ b/src/node/cli.ts @@ -192,7 +192,8 @@ export const optionDescriptions = (): string[] => { } return " ".repeat(widths.long + widths.short + 6) + line }) - .join("\n") + .join("\n") + + (typeof v.type === "object" ? ` [${Object.values(v.type).join(", ")}]` : "") ) }) } From bfe731f4f30782528a054f7d1e103ef9e95b9b2b Mon Sep 17 00:00:00 2001 From: Anmol Sethi Date: Wed, 7 Oct 2020 12:16:27 -0400 Subject: [PATCH 42/75] Ensure socket is undefined with --coder-bind --- src/node/entry.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/node/entry.ts b/src/node/entry.ts index 901c732f7..16118a1ef 100644 --- a/src/node/entry.ts +++ b/src/node/entry.ts @@ -38,11 +38,12 @@ const commit = pkg.commit || "development" const main = async (args: Args, cliArgs: Args, configArgs: Args): Promise => { if (args["coder-bind"]) { // If we're being exposed to the cloud, we listen on a random address and disable auth. - args = { - ...args, + cliArgs = { + ...cliArgs, host: "localhost", port: 0, auth: AuthType.None, + socket: undefined, } logger.info("coder-bind: disabling auth and listening on random localhost port") } From 7cc16ceb3aee37b8fb9a3afc17c21b1cb6f7a2e4 Mon Sep 17 00:00:00 2001 From: Anmol Sethi Date: Wed, 7 Oct 2020 13:26:57 -0400 Subject: [PATCH 43/75] Document KEEP_MODULES --- ci/dev/image/run.sh | 2 ++ doc/CONTRIBUTING.md | 7 +++++++ 2 files changed, 9 insertions(+) diff --git a/ci/dev/image/run.sh b/ci/dev/image/run.sh index 0557f1b2a..3d5e15dd6 100755 --- a/ci/dev/image/run.sh +++ b/ci/dev/image/run.sh @@ -13,6 +13,8 @@ main() { -e HOME="/src/.home" \ -e USER="coder" \ -e GITHUB_TOKEN \ + -e KEEP_MODULES \ + -e MINIFY \ -w /src \ -p 127.0.0.1:8080:8080 \ -u "$(id -u):$(id -g)" \ diff --git a/doc/CONTRIBUTING.md b/doc/CONTRIBUTING.md index a15e25d20..62c20f915 100644 --- a/doc/CONTRIBUTING.md +++ b/doc/CONTRIBUTING.md @@ -99,6 +99,13 @@ yarn test:standalone-release yarn package ``` +For a faster release build you can also run: + +``` +KEEP_MODULES=1 ./ci/steps/release.sh +node ./release +``` + ## Structure The `code-server` script serves an HTTP API to login and start a remote VS Code process. From df3089f3ad4b8231407e365819ef88aac42fcd76 Mon Sep 17 00:00:00 2001 From: Anmol Sethi Date: Wed, 7 Oct 2020 15:54:41 -0400 Subject: [PATCH 44/75] coder-cloud: Use consolidated bind command --- src/node/cli.ts | 4 ++-- src/node/coder-cloud.ts | 44 +++++------------------------------------ src/node/entry.ts | 7 ++----- 3 files changed, 9 insertions(+), 46 deletions(-) diff --git a/src/node/cli.ts b/src/node/cli.ts index deedf8309..ed188f281 100644 --- a/src/node/cli.ts +++ b/src/node/cli.ts @@ -48,7 +48,7 @@ export interface Args extends VsArgs { readonly "reuse-window"?: boolean readonly "new-window"?: boolean - readonly "coder-bind"?: string + readonly "coder-bind"?: OptionalString } interface Option { @@ -160,7 +160,7 @@ const options: Options> = { verbose: { type: "boolean", short: "vvv", description: "Enable verbose logging." }, "coder-bind": { - type: "string", + type: OptionalString, description: ` Securely bind code-server via Coder Cloud with the passed name. You'll get a URL like https://myname.coder-cloud.com at which you can easily access your code-server instance. diff --git a/src/node/coder-cloud.ts b/src/node/coder-cloud.ts index a3c3c5906..b57cf36df 100644 --- a/src/node/coder-cloud.ts +++ b/src/node/coder-cloud.ts @@ -5,8 +5,8 @@ import split2 from "split2" const coderCloudAgent = path.resolve(__dirname, "../../lib/coder-cloud-agent") -export async function coderCloudBind(serverName: string): Promise { - const agent = spawn(coderCloudAgent, ["link", serverName], { +function runAgent(...args: string[]): Promise { + const agent = spawn(coderCloudAgent, args, { stdio: ["inherit", "inherit", "pipe"], }) @@ -30,43 +30,9 @@ export async function coderCloudBind(serverName: string): Promise { }) } -export function coderCloudProxy(addr: string) { +export function coderCloudBind(csAddr: string, serverName = ""): Promise { // addr needs to be in host:port format. // So we trim the protocol. - addr = addr.replace(/^https?:\/\//, "") - - const _proxy = async () => { - const agent = spawn(coderCloudAgent, ["proxy", "--code-server-addr", addr], { - stdio: ["inherit", "inherit", "pipe"], - }) - - agent.stderr.pipe(split2()).on("data", (line) => { - line = line.replace(/^[0-9-]+ [0-9:]+ [^ ]+\t/, "") - logger.info(line) - }) - - return new Promise((res, rej) => { - agent.on("error", rej) - - agent.on("close", (code) => { - if (code !== 0) { - rej({ - message: `coder cloud agent exited with ${code}`, - }) - return - } - res() - }) - }) - } - - const proxy = async () => { - try { - await _proxy() - } catch (err) { - logger.error(err.message) - } - setTimeout(proxy, 3000) - } - proxy() + csAddr = csAddr.replace(/^https?:\/\//, "") + return runAgent("bind", `--code-server-addr=${csAddr}`, serverName) } diff --git a/src/node/entry.ts b/src/node/entry.ts index 16118a1ef..a39db7efb 100644 --- a/src/node/entry.ts +++ b/src/node/entry.ts @@ -12,7 +12,7 @@ import { StaticHttpProvider } from "./app/static" import { UpdateHttpProvider } from "./app/update" import { VscodeHttpProvider } from "./app/vscode" import { Args, bindAddrFromAllSources, optionDescriptions, parse, readConfigFile, setDefaults } from "./cli" -import { coderCloudBind, coderCloudProxy } from "./coder-cloud" +import { coderCloudBind } from "./coder-cloud" import { AuthType, HttpServer, HttpServerOptions } from "./http" import { loadPlugins } from "./plugin" import { generateCertificate, hash, humanPath, open } from "./util" @@ -143,11 +143,8 @@ const main = async (args: Args, cliArgs: Args, configArgs: Args): Promise } if (args["coder-bind"]) { - try { - logger.info(`binding code-server to the cloud with name ${args["coder-bind"]}`) - await coderCloudBind(args["coder-bind"]) - coderCloudProxy(serverAddress!) + await coderCloudBind(serverAddress!, args["coder-bind"].value) } catch (err) { logger.error(err.message) ipcMain().exit(1) From ebbcb8d6a7e2fc0acbe02c2534e11b46650a6a81 Mon Sep 17 00:00:00 2001 From: Anmol Sethi Date: Wed, 7 Oct 2020 16:09:40 -0400 Subject: [PATCH 45/75] Update yarn.lock --- yarn.lock | 5 ----- 1 file changed, 5 deletions(-) diff --git a/yarn.lock b/yarn.lock index 8e13b6f35..6f388626b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2525,11 +2525,6 @@ define-property@^2.0.2: is-descriptor "^1.0.2" isobject "^3.0.1" -delay@^4.4.0: - version "4.4.0" - resolved "https://registry.yarnpkg.com/delay/-/delay-4.4.0.tgz#71abc745f3ce043fe7f450491236541edec4ad0c" - integrity sha512-txgOrJu3OdtOfTiEOT2e76dJVfG/1dz2NZ4F0Pyt4UGZJryssMRp5vdM5wQoLwSOBNdrJv3F9PAhp/heqd7vrA== - delayed-stream@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" From 85b0804be5174940851ebdccc7d58b3eba581a32 Mon Sep 17 00:00:00 2001 From: Anmol Sethi Date: Wed, 7 Oct 2020 16:21:10 -0400 Subject: [PATCH 46/75] Remove cliArgs from main No purpose when all the args are in the args parameter. We only need configArgs for bindAddrFromAllSources. --- ci/build/build-release.sh | 1 - src/node/entry.ts | 16 ++++++++-------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/ci/build/build-release.sh b/ci/build/build-release.sh index 88d3fe613..eedda98e2 100755 --- a/ci/build/build-release.sh +++ b/ci/build/build-release.sh @@ -58,7 +58,6 @@ EOF rsync yarn.lock "$RELEASE_PATH" rsync ci/build/npm-postinstall.sh "$RELEASE_PATH/postinstall.sh" - if [ "$KEEP_MODULES" = 1 ]; then rsync node_modules/ "$RELEASE_PATH/node_modules" fi diff --git a/src/node/entry.ts b/src/node/entry.ts index a39db7efb..664134197 100644 --- a/src/node/entry.ts +++ b/src/node/entry.ts @@ -35,11 +35,11 @@ try { const version = pkg.version || "development" const commit = pkg.commit || "development" -const main = async (args: Args, cliArgs: Args, configArgs: Args): Promise => { +const main = async (args: Args, configArgs: Args): Promise => { if (args["coder-bind"]) { // If we're being exposed to the cloud, we listen on a random address and disable auth. - cliArgs = { - ...cliArgs, + args = { + ...args, host: "localhost", port: 0, auth: AuthType.None, @@ -64,7 +64,7 @@ const main = async (args: Args, cliArgs: Args, configArgs: Args): Promise if (args.auth === AuthType.Password && !password) { throw new Error("Please pass in a password via the config file or $PASSWORD") } - const [host, port] = bindAddrFromAllSources(cliArgs, configArgs) + const [host, port] = bindAddrFromAllSources(args, configArgs) // Spawn the main HTTP server. const options: HttpServerOptions = { @@ -153,21 +153,21 @@ const main = async (args: Args, cliArgs: Args, configArgs: Args): Promise } async function entry(): Promise { - const tryParse = async (): Promise<[Args, Args, Args]> => { + const tryParse = async (): Promise<[Args, Args]> => { try { const cliArgs = parse(process.argv.slice(2)) const configArgs = await readConfigFile(cliArgs.config) // This prioritizes the flags set in args over the ones in the config file. let args = Object.assign(configArgs, cliArgs) args = await setDefaults(args) - return [args, cliArgs, configArgs] + return [args, configArgs] } catch (error) { console.error(error.message) process.exit(1) } } - const [args, cliArgs, configArgs] = await tryParse() + const [args, configArgs] = await tryParse() if (args.help) { console.log("code-server", version, commit) console.log("") @@ -262,7 +262,7 @@ async function entry(): Promise { vscode.write(JSON.stringify(pipeArgs)) vscode.end() } else { - wrap(() => main(args, cliArgs, configArgs)) + wrap(() => main(args, configArgs)) } } From 3e28ab85a072e8df7cecd295fbd3e01b4d08d40d Mon Sep 17 00:00:00 2001 From: Anmol Sethi Date: Wed, 7 Oct 2020 17:16:20 -0400 Subject: [PATCH 47/75] Add debug log for options passed to the agent --- src/node/coder-cloud.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/node/coder-cloud.ts b/src/node/coder-cloud.ts index b57cf36df..3b36a2f57 100644 --- a/src/node/coder-cloud.ts +++ b/src/node/coder-cloud.ts @@ -6,6 +6,8 @@ import split2 from "split2" const coderCloudAgent = path.resolve(__dirname, "../../lib/coder-cloud-agent") function runAgent(...args: string[]): Promise { + logger.debug(`running agent with ${args}`) + const agent = spawn(coderCloudAgent, args, { stdio: ["inherit", "inherit", "pipe"], }) From febf4ead9631a252695c25690f61c416b49c60ec Mon Sep 17 00:00:00 2001 From: Anmol Sethi Date: Wed, 7 Oct 2020 17:28:13 -0400 Subject: [PATCH 48/75] Fix the clean script :facepalm: --- ci/build/clean.sh | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/ci/build/clean.sh b/ci/build/clean.sh index 52d123c9a..b80632278 100755 --- a/ci/build/clean.sh +++ b/ci/build/clean.sh @@ -5,18 +5,7 @@ main() { cd "$(dirname "${0}")/../.." source ./ci/lib.sh - rm -rf \ - out \ - release \ - release-standalone \ - release-packages \ - release-gcp \ - release-images \ - dist \ - .cache \ - node-* \ - .home \ - lib/coder-cloud-agent + git clean -Xffd pushd lib/vscode git clean -xffd From 8063c79e4436a1fc4ab65c186d1ff9fd60fe3ea9 Mon Sep 17 00:00:00 2001 From: Anmol Sethi Date: Thu, 8 Oct 2020 16:55:13 -0400 Subject: [PATCH 49/75] Patch VS Code to avoid deleting extension dependencies (#2170) Closes #1961 --- ci/dev/vscode.patch | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/ci/dev/vscode.patch b/ci/dev/vscode.patch index 1632dcd42..b3a7289df 100644 --- a/ci/dev/vscode.patch +++ b/ci/dev/vscode.patch @@ -31,6 +31,32 @@ index f2ea1bd37010b1eb8a43ce9beaae4a88810f6e2d..3f660f9981921ec465d2b8809a1a5ea5 const yarnrc = fs.readFileSync(path.join(REPO_ROOT, 'remote', '.yarnrc'), 'utf8'); const target = /^target "(.*)"$/m.exec(yarnrc)[1]; return target; +diff --git a/build/lib/extensions.js b/build/lib/extensions.js +index 9cc40c4e1befd38886dc5880581d6f462a38dd3a..34e1fc89a8ac1c273a5cb41f19a088a8ec759d24 100644 +--- a/build/lib/extensions.js ++++ b/build/lib/extensions.js +@@ -66,7 +66,7 @@ function fromLocal(extensionPath, forWeb) { + if (isWebPacked) { + input = updateExtensionPackageJSON(input, (data) => { + 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/extensions.ts b/build/lib/extensions.ts +index 7e529f17cb84d28d84de4ff64fa9fb8fc48135a9..462d699dc485369c74a4d9fdfefa48ba6124ac3a 100644 +--- a/build/lib/extensions.ts ++++ b/build/lib/extensions.ts +@@ -70,7 +70,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 403ae3d9657f823019542e739fc39292db20e4fe..738ee8cee0e79aa239af10e1abefc9e836b8ce33 100644 --- a/build/lib/node.js From 9f963c7e66771bebae6e4cea634c597788d6c503 Mon Sep 17 00:00:00 2001 From: Asher Date: Thu, 8 Oct 2020 16:15:05 -0500 Subject: [PATCH 50/75] Update Node to 12.18.4 (#2175) --- ci/images/centos7/Dockerfile | 2 +- ci/steps/release-packages.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ci/images/centos7/Dockerfile b/ci/images/centos7/Dockerfile index 92c212024..038e6dc73 100644 --- a/ci/images/centos7/Dockerfile +++ b/ci/images/centos7/Dockerfile @@ -1,6 +1,6 @@ FROM centos:7 -ARG NODE_VERSION=v12.18.3 +ARG NODE_VERSION=v12.18.4 RUN ARCH="$(uname -m | sed 's/86_64/64/; s/aarch64/arm64/')" && \ curl -fsSL "https://nodejs.org/dist/$NODE_VERSION/node-$NODE_VERSION-linux-$ARCH.tar.xz" | tar -C /usr/local -xJ && \ mv "/usr/local/node-$NODE_VERSION-linux-$ARCH" "/usr/local/node-$NODE_VERSION" diff --git a/ci/steps/release-packages.sh b/ci/steps/release-packages.sh index cc6cd2a06..ba8d61d5c 100755 --- a/ci/steps/release-packages.sh +++ b/ci/steps/release-packages.sh @@ -4,7 +4,7 @@ set -euo pipefail main() { cd "$(dirname "$0")/../.." - NODE_VERSION=v12.18.3 + NODE_VERSION=v12.18.4 NODE_OS="$(uname | tr '[:upper:]' '[:lower:]')" NODE_ARCH="$(uname -m | sed 's/86_64/64/; s/aarch64/arm64/')" curl -L "https://nodejs.org/dist/$NODE_VERSION/node-$NODE_VERSION-$NODE_OS-$NODE_ARCH.tar.gz" | tar -xz From c86d7398abdace6a593853e9accdbf66aab04ec2 Mon Sep 17 00:00:00 2001 From: Asher Date: Thu, 8 Oct 2020 16:18:00 -0500 Subject: [PATCH 51/75] Use system data directory for plugins --- src/node/plugin.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/node/plugin.ts b/src/node/plugin.ts index bd3765b6a..e17a9909c 100644 --- a/src/node/plugin.ts +++ b/src/node/plugin.ts @@ -69,7 +69,7 @@ const _loadPlugins = async (pluginDir: string, httpServer: HttpServer, args: Arg * `CS_PLUGIN` (also colon-separated). */ export const loadPlugins = async (httpServer: HttpServer, args: Args): Promise => { - const pluginPath = process.env.CS_PLUGIN_PATH || `${path.join(paths.data, "plugins")}:/etc/code-server/plugins` + const pluginPath = process.env.CS_PLUGIN_PATH || `${path.join(paths.data, "plugins")}:/usr/share/code-server/plugins` const plugin = process.env.CS_PLUGIN || "" await Promise.all([ // Built-in plugins. From f5489cd3a0c119ddf4ee01eaa02b5051fc042413 Mon Sep 17 00:00:00 2001 From: Anmol Sethi Date: Fri, 9 Oct 2020 07:38:38 -0400 Subject: [PATCH 52/75] Hide -coder-bind for now --- src/node/cli.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/node/cli.ts b/src/node/cli.ts index ed188f281..80c1e7402 100644 --- a/src/node/cli.ts +++ b/src/node/cli.ts @@ -65,6 +65,11 @@ interface Option { * Description of the option. Leave blank to hide the option. */ description?: string + + /** + * Whether to print this option in --help output + */ + hidden?: boolean } type OptionType = T extends boolean @@ -166,6 +171,7 @@ const options: Options> = { https://myname.coder-cloud.com at which you can easily access your code-server instance. Authorization is done via GitHub. `, + hidden: true, }, } @@ -178,7 +184,7 @@ export const optionDescriptions = (): string[] => { }), { short: 0, long: 0 }, ) - return entries.map(([k, v]) => { + return entries.filter(([_, v]) => !v.hidden).map(([k, v]) => { const help = `${" ".repeat(widths.short - (v.short ? v.short.length : 0))}${v.short ? `-${v.short}` : " "} --${k} ` return ( help + From 9ff37977a8169fcde8a770d1f08b4961841d2b77 Mon Sep 17 00:00:00 2001 From: Anmol Sethi Date: Fri, 9 Oct 2020 07:38:58 -0400 Subject: [PATCH 53/75] Make --coder-bind disable HTTPS --- src/node/entry.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/node/entry.ts b/src/node/entry.ts index 664134197..d05d5d552 100644 --- a/src/node/entry.ts +++ b/src/node/entry.ts @@ -44,6 +44,7 @@ const main = async (args: Args, configArgs: Args): Promise => { port: 0, auth: AuthType.None, socket: undefined, + cert: undefined, } logger.info("coder-bind: disabling auth and listening on random localhost port") } From a5b6d080bd425280f0c3d2a9c139f835c17f817d Mon Sep 17 00:00:00 2001 From: Anmol Sethi Date: Fri, 9 Oct 2020 07:45:20 -0400 Subject: [PATCH 54/75] Add CS_BETA and note --coder-bind is in beta --- src/node/cli.ts | 52 ++++++++++++++++++++++++----------------- src/node/coder-cloud.ts | 2 ++ 2 files changed, 33 insertions(+), 21 deletions(-) diff --git a/src/node/cli.ts b/src/node/cli.ts index 80c1e7402..5e9e71534 100644 --- a/src/node/cli.ts +++ b/src/node/cli.ts @@ -67,9 +67,9 @@ interface Option { description?: string /** - * Whether to print this option in --help output + * If marked as beta, the option is not printed unless $CS_BETA is set. */ - hidden?: boolean + beta?: boolean } type OptionType = T extends boolean @@ -170,8 +170,10 @@ const options: Options> = { Securely bind code-server via Coder Cloud with the passed name. You'll get a URL like https://myname.coder-cloud.com at which you can easily access your code-server instance. Authorization is done via GitHub. + This is presently beta and requires being accepted for testing. + See https://github.com/cdr/code-server/discussions/2137 `, - hidden: true, + beta: true, }, } @@ -184,24 +186,32 @@ export const optionDescriptions = (): string[] => { }), { short: 0, long: 0 }, ) - return entries.filter(([_, v]) => !v.hidden).map(([k, v]) => { - const help = `${" ".repeat(widths.short - (v.short ? v.short.length : 0))}${v.short ? `-${v.short}` : " "} --${k} ` - return ( - help + - v.description - ?.trim() - .split(/\n/) - .map((line, i) => { - line = line.trim() - if (i === 0) { - return " ".repeat(widths.long - k.length) + line - } - return " ".repeat(widths.long + widths.short + 6) + line - }) - .join("\n") + - (typeof v.type === "object" ? ` [${Object.values(v.type).join(", ")}]` : "") - ) - }) + return entries + .filter(([, v]) => { + // If CS_BETA is set, we show beta options but if not, then we do not want + // to show beta options. + return process.env.CS_BETA || !v.beta + }) + .map(([k, v]) => { + const help = `${" ".repeat(widths.short - (v.short ? v.short.length : 0))}${ + v.short ? `-${v.short}` : " " + } --${k} ` + return ( + help + + v.description + ?.trim() + .split(/\n/) + .map((line, i) => { + line = line.trim() + if (i === 0) { + return " ".repeat(widths.long - k.length) + line + } + return " ".repeat(widths.long + widths.short + 6) + line + }) + .join("\n") + + (typeof v.type === "object" ? ` [${Object.values(v.type).join(", ")}]` : "") + ) + }) } export const parse = ( diff --git a/src/node/coder-cloud.ts b/src/node/coder-cloud.ts index 3b36a2f57..f8038cbe9 100644 --- a/src/node/coder-cloud.ts +++ b/src/node/coder-cloud.ts @@ -33,6 +33,8 @@ function runAgent(...args: string[]): Promise { } export function coderCloudBind(csAddr: string, serverName = ""): Promise { + logger.info("Remember --coder-bind is a beta feature and requires being accepted for testing") + logger.info("See https://github.com/cdr/code-server/discussions/2137") // addr needs to be in host:port format. // So we trim the protocol. csAddr = csAddr.replace(/^https?:\/\//, "") From 9002f118c3358a228bd561353cb764a2bfaff7b5 Mon Sep 17 00:00:00 2001 From: Anmol Sethi Date: Fri, 9 Oct 2020 07:50:58 -0400 Subject: [PATCH 55/75] Remove the extra releases for autoupdating purposes --- ci/build/build-packages.sh | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/ci/build/build-packages.sh b/ci/build/build-packages.sh index c3ad6577a..a5ef794e5 100755 --- a/ci/build/build-packages.sh +++ b/ci/build/build-packages.sh @@ -11,15 +11,6 @@ main() { mkdir -p release-packages release_archive - # Will stop the auto update issues and allow people to upgrade their scripts - # for the new release structure. - if [[ $ARCH == "amd64" ]]; then - if [[ $OS == "linux" ]]; then - ARCH=x86_64 release_archive - elif [[ $OS == "macos" ]]; then - OS=darwin ARCH=x86_64 release_archive - fi - fi if [[ $OS == "linux" ]]; then release_nfpm @@ -30,12 +21,6 @@ release_archive() { local release_name="code-server-$VERSION-$OS-$ARCH" if [[ $OS == "linux" ]]; then tar -czf "release-packages/$release_name.tar.gz" --transform "s/^\.\/release-standalone/$release_name/" ./release-standalone - elif [[ $OS == "darwin" && $ARCH == "x86_64" ]]; then - # Just exists to make autoupdating from 3.2.0 work again. - mv ./release-standalone "./$release_name" - zip -yr "release-packages/$release_name.zip" "./$release_name" - mv "./$release_name" ./release-standalone - return else tar -czf "release-packages/$release_name.tar.gz" -s "/^release-standalone/$release_name/" release-standalone fi From 2d1de749f46d3f961420d754d050d05339f3dc8f Mon Sep 17 00:00:00 2001 From: Anmol Sethi Date: Fri, 9 Oct 2020 12:34:52 -0400 Subject: [PATCH 56/75] Unlink socket before using (#2181) See https://stackoverflow.com/a/34881585/4283659 Closes #1538 --- .eslintrc.yaml | 1 + src/node/http.ts | 9 ++++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/.eslintrc.yaml b/.eslintrc.yaml index 306dd2c22..92657d629 100644 --- a/.eslintrc.yaml +++ b/.eslintrc.yaml @@ -30,6 +30,7 @@ rules: eqeqeq: error import/order: [error, { alphabetize: { order: "asc" }, groups: [["builtin", "external", "internal"], "parent", "sibling"] }] + no-async-promise-executor: off settings: # Does not work with CommonJS unfortunately. diff --git a/src/node/http.ts b/src/node/http.ts index 297dda0cc..c616c8837 100644 --- a/src/node/http.ts +++ b/src/node/http.ts @@ -578,11 +578,18 @@ export class HttpServer { */ public listen(): Promise { if (!this.listenPromise) { - this.listenPromise = new Promise((resolve, reject) => { + this.listenPromise = new Promise(async (resolve, reject) => { this.server.on("error", reject) this.server.on("upgrade", this.onUpgrade) const onListen = (): void => resolve(this.address()) if (this.options.socket) { + try { + await fs.unlink(this.options.socket) + } catch (err) { + if (err.code !== "ENOENT") { + logger.warn(err.message) + } + } this.server.listen(this.options.socket, onListen) } else if (this.options.host) { // [] is the correct format when using :: but Node errors with them. From d67bd3f60479c4f425a7a04875e517d45a76e86f Mon Sep 17 00:00:00 2001 From: Anmol Sethi Date: Fri, 9 Oct 2020 12:57:20 -0400 Subject: [PATCH 57/75] cloud: Rename --coder-bind to --link --- src/node/cli.ts | 4 ++-- src/node/coder-cloud.ts | 2 +- src/node/entry.ts | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/node/cli.ts b/src/node/cli.ts index 5e9e71534..b723417d1 100644 --- a/src/node/cli.ts +++ b/src/node/cli.ts @@ -48,7 +48,7 @@ export interface Args extends VsArgs { readonly "reuse-window"?: boolean readonly "new-window"?: boolean - readonly "coder-bind"?: OptionalString + readonly link?: OptionalString } interface Option { @@ -164,7 +164,7 @@ const options: Options> = { log: { type: LogLevel }, verbose: { type: "boolean", short: "vvv", description: "Enable verbose logging." }, - "coder-bind": { + link: { type: OptionalString, description: ` Securely bind code-server via Coder Cloud with the passed name. You'll get a URL like diff --git a/src/node/coder-cloud.ts b/src/node/coder-cloud.ts index f8038cbe9..570d9cc68 100644 --- a/src/node/coder-cloud.ts +++ b/src/node/coder-cloud.ts @@ -33,7 +33,7 @@ function runAgent(...args: string[]): Promise { } export function coderCloudBind(csAddr: string, serverName = ""): Promise { - logger.info("Remember --coder-bind is a beta feature and requires being accepted for testing") + logger.info("Remember --link is a beta feature and requires being accepted for testing") logger.info("See https://github.com/cdr/code-server/discussions/2137") // addr needs to be in host:port format. // So we trim the protocol. diff --git a/src/node/entry.ts b/src/node/entry.ts index d05d5d552..5869ae461 100644 --- a/src/node/entry.ts +++ b/src/node/entry.ts @@ -36,7 +36,7 @@ const version = pkg.version || "development" const commit = pkg.commit || "development" const main = async (args: Args, configArgs: Args): Promise => { - if (args["coder-bind"]) { + if (args.link) { // If we're being exposed to the cloud, we listen on a random address and disable auth. args = { ...args, @@ -46,7 +46,7 @@ const main = async (args: Args, configArgs: Args): Promise => { socket: undefined, cert: undefined, } - logger.info("coder-bind: disabling auth and listening on random localhost port") + logger.info("link: disabling auth and listening on random localhost port for cloud agent") } if (!args.auth) { @@ -143,9 +143,9 @@ const main = async (args: Args, configArgs: Args): Promise => { logger.info(`Opened ${openAddress}`) } - if (args["coder-bind"]) { + if (args.link) { try { - await coderCloudBind(serverAddress!, args["coder-bind"].value) + await coderCloudBind(serverAddress!, args.link.value) } catch (err) { logger.error(err.message) ipcMain().exit(1) From fcfb03382acb9ac307c04a3647b076038d2ba06d Mon Sep 17 00:00:00 2001 From: Anmol Sethi Date: Fri, 9 Oct 2020 12:57:48 -0400 Subject: [PATCH 58/75] cloud: Add mention of cloud repo --- src/node/coder-cloud.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/node/coder-cloud.ts b/src/node/coder-cloud.ts index 570d9cc68..1241bc90b 100644 --- a/src/node/coder-cloud.ts +++ b/src/node/coder-cloud.ts @@ -3,6 +3,7 @@ import { spawn } from "child_process" import path from "path" import split2 from "split2" +// https://github.com/cdr/coder-cloud const coderCloudAgent = path.resolve(__dirname, "../../lib/coder-cloud-agent") function runAgent(...args: string[]): Promise { From 64a6a460c8d961df1538b02910a4c0b04b1158b1 Mon Sep 17 00:00:00 2001 From: Anmol Sethi Date: Fri, 9 Oct 2020 15:00:49 -0400 Subject: [PATCH 59/75] Adjust npm package postinstall to install extension dependencies (#2180) Closes #1961 --- ci/build/npm-postinstall.sh | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/ci/build/npm-postinstall.sh b/ci/build/npm-postinstall.sh index 127d6408a..74b904783 100755 --- a/ci/build/npm-postinstall.sh +++ b/ci/build/npm-postinstall.sh @@ -36,6 +36,13 @@ vscode_yarn() { yarn --production --frozen-lockfile cd extensions yarn --production --frozen-lockfile + for ext in */; do + ext="${ext%/}" + echo "extensions/$ext: installing dependencies" + cd "$ext" + yarn --production --frozen-lockfile + cd "$OLDPWD" + done } main "$@" From 811cf3364af86eecbf4578a1eab6ed53e5135c5b Mon Sep 17 00:00:00 2001 From: Anmol Sethi Date: Fri, 9 Oct 2020 15:33:58 -0400 Subject: [PATCH 60/75] install.sh: Allow installing directly onto a remote host (#2183) Updates #1729 To fully close that issue see the various TODOs. --- install.sh | 52 ++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 48 insertions(+), 4 deletions(-) diff --git a/install.sh b/install.sh index 0b768def3..08e3a51e8 100755 --- a/install.sh +++ b/install.sh @@ -17,21 +17,28 @@ usage() { Installs code-server for Linux, macOS and FreeBSD. It tries to use the system package manager if possible. After successful installation it explains how to start using code-server. + +Pass in user@host to install code-server on user@host over ssh. +The remote host must have internet access. ${not_curl_usage-} Usage: - $arg0 [--dry-run] [--version X.X.X] [--method detect] [--prefix ~/.local] + $arg0 [--dry-run] [--version X.X.X] [--method detect] \ + [--prefix ~/.local] [user@host] --dry-run Echo the commands for the install process without running them. + --version X.X.X Install a specific version instead of the latest. + --method [detect | standalone] Choose the installation method. Defaults to detect. - detect detects the system package manager and tries to use it. Full reference on the process is further below. - standalone installs a standalone release archive into ~/.local Add ~/.local/bin to your \$PATH to use it. + --prefix Sets the prefix used by standalone release archives. Defaults to ~/.local The release is unarchived into ~/.local/lib/code-server-X.X.X @@ -100,9 +107,18 @@ main() { METHOD \ STANDALONE_INSTALL_PREFIX \ VERSION \ - OPTIONAL + OPTIONAL \ + ALL_FLAGS \ + SSH_ARGS + ALL_FLAGS="" while [ "$#" -gt 0 ]; do + case "$1" in + -*) + ALL_FLAGS="${ALL_FLAGS} $1" + ;; + esac + case "$1" in --dry-run) DRY_RUN=1 @@ -132,16 +148,33 @@ main() { usage exit 0 ;; - *) + --) + shift + # We remove the -- added above. + ALL_FLAGS="${ALL_FLAGS% --}" + SSH_ARGS="$*" + break + ;; + -*) echoerr "Unknown flag $1" echoerr "Run with --help to see usage." exit 1 ;; + *) + SSH_ARGS="$*" + break + ;; esac shift done + if [ "${SSH_ARGS-}" ]; then + echoh "Installing remotely with ssh $SSH_ARGS" + curl -fsSL https://code-server.dev/install.sh | prefix "$SSH_ARGS" ssh "$SSH_ARGS" sh -s -- "$ALL_FLAGS" + return + fi + VERSION="${VERSION-$(echo_latest_version)}" METHOD="${METHOD-detect}" if [ "$METHOD" != detect ] && [ "$METHOD" != standalone ]; then @@ -446,7 +479,7 @@ arch() { } command_exists() { - command -v "$@" > /dev/null 2>&1 + command -v "$@" > /dev/null } sh_c() { @@ -500,4 +533,15 @@ humanpath() { sed "s# $HOME# ~#g; s#\"$HOME#\"\$HOME#g" } +# We need to make sure we exit with a non zero exit if the command fails. +# /bin/sh does not support -o pipefail unfortunately. +prefix() { + PREFIX="$1" + shift + fifo="$(mktemp -d)/fifo" + mkfifo "$fifo" + sed -e "s#^#$PREFIX: #" "$fifo" & + "$@" > "$fifo" 2>&1 +} + main "$@" From 6bdaada689dc9e78c461e4158310d99faa54347f Mon Sep 17 00:00:00 2001 From: Asher Date: Mon, 14 Sep 2020 15:56:08 -0500 Subject: [PATCH 61/75] Move uncaught exception handler to wrapper Feels more appropriate there to me. --- src/node/entry.ts | 7 ------- src/node/wrapper.ts | 8 ++++++++ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/node/entry.ts b/src/node/entry.ts index 5869ae461..dfd78cda7 100644 --- a/src/node/entry.ts +++ b/src/node/entry.ts @@ -18,13 +18,6 @@ import { loadPlugins } from "./plugin" import { generateCertificate, hash, humanPath, open } from "./util" import { ipcMain, wrap } from "./wrapper" -process.on("uncaughtException", (error) => { - logger.error(`Uncaught exception: ${error.message}`) - if (typeof error.stack !== "undefined") { - logger.error(error.stack) - } -}) - let pkg: { version?: string; commit?: string } = {} try { pkg = require("../../package.json") diff --git a/src/node/wrapper.ts b/src/node/wrapper.ts index ba459efd1..2f1eb037f 100644 --- a/src/node/wrapper.ts +++ b/src/node/wrapper.ts @@ -254,6 +254,14 @@ if (!process.stdout.isTTY) { process.stdout.on("error", () => ipcMain().exit()) } +// Don't let uncaught exceptions crash the process. +process.on("uncaughtException", (error) => { + logger.error(`Uncaught exception: ${error.message}`) + if (typeof error.stack !== "undefined") { + logger.error(error.stack) + } +}) + export const wrap = (fn: () => Promise): void => { if (ipcMain().parentPid) { ipcMain() From 0a8e71c6474ed59f3979c68005288f75c29bcd10 Mon Sep 17 00:00:00 2001 From: Asher Date: Mon, 14 Sep 2020 15:57:58 -0500 Subject: [PATCH 62/75] Refactor wrapper - Immediately create ipcMain so it doesn't have to be a function which I think feels cleaner. - Move exit handling to a separate function to compensate (otherwise the VS Code CLI for example won't be able to exit on its own). - New isChild prop that is clearer than checking for parentPid (IMO). - Skip all the checks that aren't necessary for the child process (like --help, --version, etc). - Since we check if we're the child in entry go ahead and move the wrap code into entry as well since that's basically what it does. - Use a single catch at the end of the entry. - Split out the VS Code CLI and existing instance code into separate functions. --- src/node/cli.ts | 20 ++++ src/node/entry.ts | 231 ++++++++++++++++++++++++++------------------ src/node/wrapper.ts | 102 +++++++++---------- 3 files changed, 199 insertions(+), 154 deletions(-) diff --git a/src/node/cli.ts b/src/node/cli.ts index b723417d1..9683945d4 100644 --- a/src/node/cli.ts +++ b/src/node/cli.ts @@ -496,3 +496,23 @@ async function copyOldMacOSDataDir(): Promise { await fs.copy(oldDataDir, paths.data) } } + +export const shouldRunVsCodeCli = (args: Args): boolean => { + return !!args["list-extensions"] || !!args["install-extension"] || !!args["uninstall-extension"] +} + +/** + * Determine if it looks like the user is trying to open a file or folder in an + * existing instance. The arguments here should be the arguments the user + * explicitly passed on the command line, not defaults or the configuration. + */ +export const shouldOpenInExistingInstance = async (args: Args): Promise => { + // Always use the existing instance if we're running from VS Code's terminal. + if (process.env.VSCODE_IPC_HOOK_CLI) { + return process.env.VSCODE_IPC_HOOK_CLI + } + + // TODO: implement + + return undefined +} diff --git a/src/node/entry.ts b/src/node/entry.ts index dfd78cda7..0d16c250d 100644 --- a/src/node/entry.ts +++ b/src/node/entry.ts @@ -11,12 +11,21 @@ import { ProxyHttpProvider } from "./app/proxy" import { StaticHttpProvider } from "./app/static" import { UpdateHttpProvider } from "./app/update" import { VscodeHttpProvider } from "./app/vscode" -import { Args, bindAddrFromAllSources, optionDescriptions, parse, readConfigFile, setDefaults } from "./cli" +import { + Args, + bindAddrFromAllSources, + optionDescriptions, + parse, + readConfigFile, + setDefaults, + shouldOpenInExistingInstance, + shouldRunVsCodeCli, +} from "./cli" import { coderCloudBind } from "./coder-cloud" import { AuthType, HttpServer, HttpServerOptions } from "./http" import { loadPlugins } from "./plugin" import { generateCertificate, hash, humanPath, open } from "./util" -import { ipcMain, wrap } from "./wrapper" +import { ipcMain, WrapperProcess } from "./wrapper" let pkg: { version?: string; commit?: string } = {} try { @@ -28,6 +37,86 @@ try { const version = pkg.version || "development" const commit = pkg.commit || "development" +export const runVsCodeCli = (args: Args): void => { + logger.debug("forking vs code cli...") + const vscode = cp.fork(path.resolve(__dirname, "../../lib/vscode/out/vs/server/fork"), [], { + env: { + ...process.env, + CODE_SERVER_PARENT_PID: process.pid.toString(), + }, + }) + vscode.once("message", (message: any) => { + logger.debug("got message from VS Code", field("message", message)) + if (message.type !== "ready") { + logger.error("Unexpected response waiting for ready response", field("type", message.type)) + process.exit(1) + } + const send: CliMessage = { type: "cli", args } + vscode.send(send) + }) + vscode.once("error", (error) => { + logger.error("Got error from VS Code", field("error", error)) + process.exit(1) + }) + vscode.on("exit", (code) => process.exit(code || 0)) +} + +export const openInExistingInstance = async (args: Args, socketPath: string): Promise => { + const pipeArgs: OpenCommandPipeArgs & { fileURIs: string[] } = { + type: "open", + folderURIs: [], + fileURIs: [], + forceReuseWindow: args["reuse-window"], + forceNewWindow: args["new-window"], + } + + const isDir = async (path: string): Promise => { + try { + const st = await fs.stat(path) + return st.isDirectory() + } catch (error) { + return false + } + } + + for (let i = 0; i < args._.length; i++) { + const fp = path.resolve(args._[i]) + if (await isDir(fp)) { + pipeArgs.folderURIs.push(fp) + } else { + pipeArgs.fileURIs.push(fp) + } + } + + if (pipeArgs.forceNewWindow && pipeArgs.fileURIs.length > 0) { + logger.error("--new-window can only be used with folder paths") + process.exit(1) + } + + if (pipeArgs.folderURIs.length === 0 && pipeArgs.fileURIs.length === 0) { + logger.error("Please specify at least one file or folder") + process.exit(1) + } + + const vscode = http.request( + { + path: "/", + method: "POST", + socketPath, + }, + (response) => { + response.on("data", (message) => { + logger.debug("got message from VS Code", field("message", message.toString())) + }) + }, + ) + vscode.on("error", (error: unknown) => { + logger.error("got error from VS Code", field("error", error)) + }) + vscode.write(JSON.stringify(pipeArgs)) + vscode.end() +} + const main = async (args: Args, configArgs: Args): Promise => { if (args.link) { // If we're being exposed to the cloud, we listen on a random address and disable auth. @@ -92,7 +181,7 @@ const main = async (args: Args, configArgs: Args): Promise => { await loadPlugins(httpServer, args) - ipcMain().onDispose(() => { + ipcMain.onDispose(() => { httpServer.dispose().then((errors) => { errors.forEach((error) => logger.error(error.message)) }) @@ -132,7 +221,9 @@ const main = async (args: Args, configArgs: Args): Promise => { if (serverAddress && !options.socket && args.open) { // The web socket doesn't seem to work if browsing with 0.0.0.0. const openAddress = serverAddress.replace(/:\/\/0.0.0.0/, "://localhost") - await open(openAddress).catch(console.error) + await open(openAddress).catch((error: Error) => { + logger.error("Failed to open", field("address", openAddress), field("error", error)) + }) logger.info(`Opened ${openAddress}`) } @@ -141,27 +232,32 @@ const main = async (args: Args, configArgs: Args): Promise => { await coderCloudBind(serverAddress!, args.link.value) } catch (err) { logger.error(err.message) - ipcMain().exit(1) + ipcMain.exit(1) } } } async function entry(): Promise { - const tryParse = async (): Promise<[Args, Args]> => { - try { - const cliArgs = parse(process.argv.slice(2)) - const configArgs = await readConfigFile(cliArgs.config) - // This prioritizes the flags set in args over the ones in the config file. - let args = Object.assign(configArgs, cliArgs) - args = await setDefaults(args) - return [args, configArgs] - } catch (error) { - console.error(error.message) - process.exit(1) - } + const tryParse = async (): Promise<[Args, Args, Args]> => { + const cliArgs = parse(process.argv.slice(2)) + const configArgs = await readConfigFile(cliArgs.config) + // This prioritizes the flags set in args over the ones in the config file. + let args = Object.assign(configArgs, cliArgs) + args = await setDefaults(args) + return [args, cliArgs, configArgs] + } + + const [args, cliArgs, configArgs] = await tryParse() + + // There's no need to check flags like --help or to spawn in an existing + // instance for the child process because these would have already happened in + // the parent and the child wouldn't have been spawned. + if (ipcMain.isChild) { + await ipcMain.handshake() + ipcMain.preventExit() + return main(args, configArgs) } - const [args, configArgs] = await tryParse() if (args.help) { console.log("code-server", version, commit) console.log("") @@ -171,7 +267,10 @@ async function entry(): Promise { optionDescriptions().forEach((description) => { console.log("", description) }) - } else if (args.version) { + return + } + + if (args.version) { if (args.json) { console.log({ codeServer: version, @@ -181,83 +280,23 @@ async function entry(): Promise { } else { console.log(version, commit) } - process.exit(0) - } else if (args["list-extensions"] || args["install-extension"] || args["uninstall-extension"]) { - logger.debug("forking vs code cli...") - const vscode = cp.fork(path.resolve(__dirname, "../../lib/vscode/out/vs/server/fork"), [], { - env: { - ...process.env, - CODE_SERVER_PARENT_PID: process.pid.toString(), - }, - }) - vscode.once("message", (message: any) => { - logger.debug("Got message from VS Code", field("message", message)) - if (message.type !== "ready") { - logger.error("Unexpected response waiting for ready response") - process.exit(1) - } - const send: CliMessage = { type: "cli", args } - vscode.send(send) - }) - vscode.once("error", (error) => { - logger.error(error.message) - process.exit(1) - }) - vscode.on("exit", (code) => process.exit(code || 0)) - } else if (process.env.VSCODE_IPC_HOOK_CLI) { - const pipeArgs: OpenCommandPipeArgs = { - type: "open", - folderURIs: [], - forceReuseWindow: args["reuse-window"], - forceNewWindow: args["new-window"], - } - const isDir = async (path: string): Promise => { - try { - const st = await fs.stat(path) - return st.isDirectory() - } catch (error) { - return false - } - } - for (let i = 0; i < args._.length; i++) { - const fp = path.resolve(args._[i]) - if (await isDir(fp)) { - pipeArgs.folderURIs.push(fp) - } else { - if (!pipeArgs.fileURIs) { - pipeArgs.fileURIs = [] - } - pipeArgs.fileURIs.push(fp) - } - } - if (pipeArgs.forceNewWindow && pipeArgs.fileURIs && pipeArgs.fileURIs.length > 0) { - logger.error("new-window can only be used with folder paths") - process.exit(1) - } - if (pipeArgs.folderURIs.length === 0 && (!pipeArgs.fileURIs || pipeArgs.fileURIs.length === 0)) { - logger.error("Please specify at least one file or folder argument") - process.exit(1) - } - const vscode = http.request( - { - path: "/", - method: "POST", - socketPath: process.env["VSCODE_IPC_HOOK_CLI"], - }, - (res) => { - res.on("data", (message) => { - logger.debug("Got message from VS Code", field("message", message.toString())) - }) - }, - ) - vscode.on("error", (err) => { - logger.debug("Got error from VS Code", field("error", err)) - }) - vscode.write(JSON.stringify(pipeArgs)) - vscode.end() - } else { - wrap(() => main(args, configArgs)) + return } + + if (shouldRunVsCodeCli(args)) { + return runVsCodeCli(args) + } + + const socketPath = await shouldOpenInExistingInstance(cliArgs) + if (socketPath) { + return openInExistingInstance(args, socketPath) + } + + const wrapper = new WrapperProcess(require("../../package.json").version) + return wrapper.start() } -entry() +entry().catch((error) => { + logger.error(error.message) + ipcMain.exit(error) +}) diff --git a/src/node/wrapper.ts b/src/node/wrapper.ts index 2f1eb037f..ea29eb28a 100644 --- a/src/node/wrapper.ts +++ b/src/node/wrapper.ts @@ -32,19 +32,13 @@ export class IpcMain { public readonly onMessage = this._onMessage.event private readonly _onDispose = new Emitter() public readonly onDispose = this._onDispose.event - public readonly processExit: (code?: number) => never + public readonly processExit: (code?: number) => never = process.exit - public constructor(public readonly parentPid?: number) { + public constructor(private readonly parentPid?: number) { process.on("SIGINT", () => this._onDispose.emit("SIGINT")) process.on("SIGTERM", () => this._onDispose.emit("SIGTERM")) process.on("exit", () => this._onDispose.emit(undefined)) - // Ensure we control when the process exits. - this.processExit = process.exit - process.exit = function (code?: number) { - logger.warn(`process.exit() was prevented: ${code || "unknown code"}.`) - } as (code?: number) => never - this.onDispose((signal) => { // Remove listeners to avoid possibly triggering disposal again. process.removeAllListeners() @@ -71,6 +65,19 @@ export class IpcMain { } } + /** + * Ensure we control when the process exits. + */ + public preventExit(): void { + process.exit = function (code?: number) { + logger.warn(`process.exit() was prevented: ${code || "unknown code"}.`) + } as (code?: number) => never + } + + public get isChild(): boolean { + return typeof this.parentPid !== "undefined" + } + public exit(error?: number | ProcessError): never { if (error && typeof error !== "number") { this.processExit(typeof error.code === "number" ? error.code : 1) @@ -127,17 +134,12 @@ export class IpcMain { } } -let _ipcMain: IpcMain -export const ipcMain = (): IpcMain => { - if (!_ipcMain) { - _ipcMain = new IpcMain( - typeof process.env.CODE_SERVER_PARENT_PID !== "undefined" - ? parseInt(process.env.CODE_SERVER_PARENT_PID) - : undefined, - ) - } - return _ipcMain -} +/** + * Channel for communication between the child and parent processes. + */ +export const ipcMain = new IpcMain( + typeof process.env.CODE_SERVER_PARENT_PID !== "undefined" ? parseInt(process.env.CODE_SERVER_PARENT_PID) : undefined, +) export interface WrapperOptions { maxMemory?: number @@ -162,14 +164,11 @@ export class WrapperProcess { this.logStdoutStream = rfs.createStream(path.join(paths.data, "coder-logs", "code-server-stdout.log"), opts) this.logStderrStream = rfs.createStream(path.join(paths.data, "coder-logs", "code-server-stderr.log"), opts) - ipcMain().onDispose(() => { - if (this.process) { - this.process.removeAllListeners() - this.process.kill() - } + ipcMain.onDispose(() => { + this.disposeChild() }) - ipcMain().onMessage((message) => { + ipcMain.onMessage((message) => { switch (message.type) { case "relaunch": logger.info(`Relaunching: ${this.currentVersion} -> ${message.version}`) @@ -181,28 +180,35 @@ export class WrapperProcess { break } }) - - process.on("SIGUSR1", async () => { - logger.info("Received SIGUSR1; hotswapping") - this.relaunch() - }) } - private async relaunch(): Promise { + private disposeChild(): void { this.started = undefined if (this.process) { this.process.removeAllListeners() this.process.kill() } + } + + private async relaunch(): Promise { + this.disposeChild() try { await this.start() } catch (error) { logger.error(error.message) - ipcMain().exit(typeof error.code === "number" ? error.code : 1) + ipcMain.exit(typeof error.code === "number" ? error.code : 1) } } public start(): Promise { + // If we have a process then we've already bound this. + if (!this.process) { + process.on("SIGUSR1", async () => { + logger.info("Received SIGUSR1; hotswapping") + this.relaunch() + }) + } + if (!this.started) { this.started = this.spawn().then((child) => { // Log both to stdout and to the log directory. @@ -215,14 +221,12 @@ export class WrapperProcess { child.stderr.pipe(process.stderr) } logger.debug(`spawned inner process ${child.pid}`) - ipcMain() - .handshake(child) - .then(() => { - child.once("exit", (code) => { - logger.debug(`inner process ${child.pid} exited unexpectedly`) - ipcMain().exit(code || 0) - }) + ipcMain.handshake(child).then(() => { + child.once("exit", (code) => { + logger.debug(`inner process ${child.pid} exited unexpectedly`) + ipcMain.exit(code || 0) }) + }) this.process = child }) } @@ -251,7 +255,7 @@ export class WrapperProcess { // It's possible that the pipe has closed (for example if you run code-server // --version | head -1). Assume that means we're done. if (!process.stdout.isTTY) { - process.stdout.on("error", () => ipcMain().exit()) + process.stdout.on("error", () => ipcMain.exit()) } // Don't let uncaught exceptions crash the process. @@ -261,21 +265,3 @@ process.on("uncaughtException", (error) => { logger.error(error.stack) } }) - -export const wrap = (fn: () => Promise): void => { - if (ipcMain().parentPid) { - ipcMain() - .handshake() - .then(() => fn()) - .catch((error: ProcessError): void => { - logger.error(error.message) - ipcMain().exit(error) - }) - } else { - const wrapper = new WrapperProcess(require("../../package.json").version) - wrapper.start().catch((error) => { - logger.error(error.message) - ipcMain().exit(error) - }) - } -} From bb1bf88439738eeacca246703fcf05f4a3182037 Mon Sep 17 00:00:00 2001 From: Asher Date: Mon, 14 Sep 2020 17:22:24 -0500 Subject: [PATCH 63/75] Fix wrapper.start not actually waiting for anything --- src/node/wrapper.ts | 47 +++++++++++++++++++++++++-------------------- 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/src/node/wrapper.ts b/src/node/wrapper.ts index ea29eb28a..cce841901 100644 --- a/src/node/wrapper.ts +++ b/src/node/wrapper.ts @@ -208,32 +208,37 @@ export class WrapperProcess { this.relaunch() }) } - if (!this.started) { - this.started = this.spawn().then((child) => { - // Log both to stdout and to the log directory. - if (child.stdout) { - child.stdout.pipe(this.logStdoutStream) - child.stdout.pipe(process.stdout) - } - if (child.stderr) { - child.stderr.pipe(this.logStderrStream) - child.stderr.pipe(process.stderr) - } - logger.debug(`spawned inner process ${child.pid}`) - ipcMain.handshake(child).then(() => { - child.once("exit", (code) => { - logger.debug(`inner process ${child.pid} exited unexpectedly`) - ipcMain.exit(code || 0) - }) - }) - this.process = child - }) + this.started = this._start() } return this.started } - private async spawn(): Promise { + private async _start(): Promise { + const child = this.spawn() + this.process = child + + // Log both to stdout and to the log directory. + if (child.stdout) { + child.stdout.pipe(this.logStdoutStream) + child.stdout.pipe(process.stdout) + } + if (child.stderr) { + child.stderr.pipe(this.logStderrStream) + child.stderr.pipe(process.stderr) + } + + logger.debug(`spawned inner process ${child.pid}`) + + await ipcMain.handshake(child) + + child.once("exit", (code) => { + logger.debug(`inner process ${child.pid} exited unexpectedly`) + ipcMain.exit(code || 0) + }) + } + + private spawn(): cp.ChildProcess { // Flags to pass along to the Node binary. let nodeOptions = `${process.env.NODE_OPTIONS || ""} ${(this.options && this.options.nodeOptions) || ""}` if (!/max_old_space_size=(\d+)/g.exec(nodeOptions)) { From 19022967024ac690cc5e0e7ea11ade0f05792318 Mon Sep 17 00:00:00 2001 From: Asher Date: Tue, 15 Sep 2020 12:47:33 -0500 Subject: [PATCH 64/75] Remove references to --open-in flag --- src/node/cli.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/node/cli.ts b/src/node/cli.ts index 9683945d4..3d4d0659c 100644 --- a/src/node/cli.ts +++ b/src/node/cli.ts @@ -152,12 +152,12 @@ const options: Options> = { "new-window": { type: "boolean", short: "n", - description: "Force to open a new window. (use with open-in)", + description: "Force to open a new window.", }, "reuse-window": { type: "boolean", short: "r", - description: "Force to open a file or folder in an already opened window. (use with open-in)", + description: "Force to open a file or folder in an already opened window.", }, locale: { type: "string" }, From 021c084e4315f6fbc40ca0b9feed1cf0930d43d7 Mon Sep 17 00:00:00 2001 From: Asher Date: Tue, 15 Sep 2020 13:43:07 -0500 Subject: [PATCH 65/75] Move log level defaults into setDefaults This will allow cliArgs to be only the actual arguments the user passed which will be used for some logic around opening in existing instances. --- src/node/cli.ts | 30 ++++++++++----------- test/cli.test.ts | 69 ++++++++++++++++++++++++++++++++---------------- 2 files changed, 61 insertions(+), 38 deletions(-) diff --git a/src/node/cli.ts b/src/node/cli.ts index 3d4d0659c..a52796a14 100644 --- a/src/node/cli.ts +++ b/src/node/cli.ts @@ -327,6 +327,21 @@ export const parse = ( logger.debug("parsed command line", field("args", args)) + return args +} + +export async function setDefaults(args: Args): Promise { + args = { ...args } + + if (!args["user-data-dir"]) { + await copyOldMacOSDataDir() + args["user-data-dir"] = paths.data + } + + if (!args["extensions-dir"]) { + args["extensions-dir"] = path.join(args["user-data-dir"], "extensions") + } + // --verbose takes priority over --log and --log takes priority over the // environment variable. if (args.verbose) { @@ -369,21 +384,6 @@ export const parse = ( return args } -export async function setDefaults(args: Args): Promise { - args = { ...args } - - if (!args["user-data-dir"]) { - await copyOldMacOSDataDir() - args["user-data-dir"] = paths.data - } - - if (!args["extensions-dir"]) { - args["extensions-dir"] = path.join(args["user-data-dir"], "extensions") - } - - return args -} - async function defaultConfigFile(): Promise { return `bind-addr: 127.0.0.1:8080 auth: password diff --git a/test/cli.test.ts b/test/cli.test.ts index f4f6c8849..fe78659d8 100644 --- a/test/cli.test.ts +++ b/test/cli.test.ts @@ -1,20 +1,23 @@ -import { logger, Level } from "@coder/logger" +import { Level, logger } from "@coder/logger" import * as assert from "assert" import * as path from "path" -import { parse } from "../src/node/cli" +import { parse, setDefaults } from "../src/node/cli" +import { paths } from "../src/node/util" describe("cli", () => { beforeEach(() => { delete process.env.LOG_LEVEL }) - // The parser will always fill these out. + // The parser should not set any defaults so the caller can determine what + // values the user actually set. These are set after calling `setDefaults`. const defaults = { - _: [], + "extensions-dir": path.join(paths.data, "extensions"), + "user-data-dir": paths.data, } it("should set defaults", () => { - assert.deepEqual(parse([]), defaults) + assert.deepEqual(parse([]), { _: [] }) }) it("should parse all available options", () => { @@ -69,7 +72,7 @@ describe("cli", () => { help: true, host: "0.0.0.0", json: true, - log: "trace", + log: "error", open: true, port: 8081, socket: path.resolve("mumble"), @@ -83,19 +86,20 @@ describe("cli", () => { it("should work with short options", () => { assert.deepEqual(parse(["-vvv", "-v"]), { - ...defaults, - log: "trace", + _: [], verbose: true, version: true, }) - assert.equal(process.env.LOG_LEVEL, "trace") - assert.equal(logger.level, Level.Trace) }) - it("should use log level env var", () => { + it("should use log level env var", async () => { + const args = parse([]) + assert.deepEqual(args, { _: [] }) + process.env.LOG_LEVEL = "debug" - assert.deepEqual(parse([]), { + assert.deepEqual(await setDefaults(args), { ...defaults, + _: [], log: "debug", verbose: false, }) @@ -103,8 +107,9 @@ describe("cli", () => { assert.equal(logger.level, Level.Debug) process.env.LOG_LEVEL = "trace" - assert.deepEqual(parse([]), { + assert.deepEqual(await setDefaults(args), { ...defaults, + _: [], log: "trace", verbose: true, }) @@ -113,9 +118,16 @@ describe("cli", () => { }) it("should prefer --log to env var and --verbose to --log", async () => { + let args = parse(["--log", "info"]) + assert.deepEqual(args, { + _: [], + log: "info", + }) + process.env.LOG_LEVEL = "debug" - assert.deepEqual(parse(["--log", "info"]), { + assert.deepEqual(await setDefaults(args), { ...defaults, + _: [], log: "info", verbose: false, }) @@ -123,17 +135,26 @@ describe("cli", () => { assert.equal(logger.level, Level.Info) process.env.LOG_LEVEL = "trace" - assert.deepEqual(parse(["--log", "info"]), { + assert.deepEqual(await setDefaults(args), { ...defaults, + _: [], log: "info", verbose: false, }) assert.equal(process.env.LOG_LEVEL, "info") assert.equal(logger.level, Level.Info) + args = parse(["--log", "info", "--verbose"]) + assert.deepEqual(args, { + _: [], + log: "info", + verbose: true, + }) + process.env.LOG_LEVEL = "warn" - assert.deepEqual(parse(["--log", "info", "--verbose"]), { + assert.deepEqual(await setDefaults(args), { ...defaults, + _: [], log: "trace", verbose: true, }) @@ -141,9 +162,12 @@ describe("cli", () => { assert.equal(logger.level, Level.Trace) }) - it("should ignore invalid log level env var", () => { + it("should ignore invalid log level env var", async () => { process.env.LOG_LEVEL = "bogus" - assert.deepEqual(parse([]), defaults) + assert.deepEqual(await setDefaults(parse([])), { + _: [], + ...defaults, + }) }) it("should error if value isn't provided", () => { @@ -166,7 +190,7 @@ describe("cli", () => { it("should not error if the value is optional", () => { assert.deepEqual(parse(["--cert"]), { - ...defaults, + _: [], cert: { value: undefined, }, @@ -177,7 +201,7 @@ describe("cli", () => { assert.throws(() => parse(["--socket", "--socket-path-value"]), /--socket requires a value/) // If you actually had a path like this you would do this instead: assert.deepEqual(parse(["--socket", "./--socket-path-value"]), { - ...defaults, + _: [], socket: path.resolve("--socket-path-value"), }) assert.throws(() => parse(["--cert", "--socket-path-value"]), /Unknown option --socket-path-value/) @@ -185,7 +209,6 @@ describe("cli", () => { it("should allow positional arguments before options", () => { assert.deepEqual(parse(["foo", "test", "--auth", "none"]), { - ...defaults, _: ["foo", "test"], auth: "none", }) @@ -193,11 +216,11 @@ describe("cli", () => { it("should support repeatable flags", () => { assert.deepEqual(parse(["--proxy-domain", "*.coder.com"]), { - ...defaults, + _: [], "proxy-domain": ["*.coder.com"], }) assert.deepEqual(parse(["--proxy-domain", "*.coder.com", "--proxy-domain", "test.com"]), { - ...defaults, + _: [], "proxy-domain": ["*.coder.com", "test.com"], }) }) From fe19391c036bf234e84b2e30f321de73f3b689e2 Mon Sep 17 00:00:00 2001 From: Asher Date: Tue, 15 Sep 2020 16:51:43 -0500 Subject: [PATCH 66/75] Read most recent socket path from file --- ci/dev/vscode.patch | 25 +++++++++++++ src/node/cli.ts | 32 +++++++++++++++- src/node/socket.ts | 13 +------ src/node/util.ts | 15 ++++++++ test/cli.test.ts | 89 +++++++++++++++++++++++++++++++++++++++++++-- 5 files changed, 156 insertions(+), 18 deletions(-) diff --git a/ci/dev/vscode.patch b/ci/dev/vscode.patch index b3a7289df..f15c7d7a7 100644 --- a/ci/dev/vscode.patch +++ b/ci/dev/vscode.patch @@ -3035,6 +3035,31 @@ index b3c89e51cfc25a53293a352a2a8ad50d5f26d595..e21abe4e13bc25a5b72f556bbfb61085 registerSingleton(IExtHostTerminalService, ExtHostTerminalService); registerSingleton(IExtHostTunnelService, ExtHostTunnelService); +registerSingleton(IExtHostNodeProxy, class extends NotImplementedProxy(String(IExtHostNodeProxy)) { whenReady = Promise.resolve(); }); +diff --git a/src/vs/workbench/api/node/extHostCLIServer.ts b/src/vs/workbench/api/node/extHostCLIServer.ts +index 7cae126cc0f804273850933468690e0f9f10a5b8..08c2aa5cdae3f3d06bb08b7055dc7e7def260132 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 { join } from 'vs/base/common/path'; ++import { tmpdir } from 'os'; + + export interface OpenCommandPipeArgs { + type: 'open'; +@@ -54,6 +56,11 @@ export class CLIServer { + private async setup(): Promise { + this._ipcHandlePath = generateRandomPipeName(); + ++ // 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/api/worker/extHost.worker.services.ts b/src/vs/workbench/api/worker/extHost.worker.services.ts index 3843fdec386edc09a1d361b63de892a04e0070ed..8aac4df527857e964798362a69f5591bef07c165 100644 --- a/src/vs/workbench/api/worker/extHost.worker.services.ts diff --git a/src/node/cli.ts b/src/node/cli.ts index a52796a14..028ea0d45 100644 --- a/src/node/cli.ts +++ b/src/node/cli.ts @@ -5,7 +5,7 @@ import * as os from "os" import * as path from "path" import { Args as VsArgs } from "../../lib/vscode/src/vs/server/ipc" import { AuthType } from "./http" -import { generatePassword, humanPath, paths } from "./util" +import { canConnect, generatePassword, humanPath, paths } from "./util" export class Optional { public constructor(public readonly value?: T) {} @@ -512,7 +512,35 @@ export const shouldOpenInExistingInstance = async (args: Args): Promise => { + try { + return await fs.readFile(path.join(os.tmpdir(), "vscode-ipc"), "utf8") + } catch (error) { + if (error.code !== "ENOENT") { + throw error + } + } + return undefined + } + + // If these flags are set then assume the user is trying to open in an + // existing instance since these flags have no effect otherwise. + const openInFlagCount = ["reuse-window", "new-window"].reduce((prev, cur) => { + return args[cur as keyof Args] ? prev + 1 : prev + }, 0) + if (openInFlagCount > 0) { + return readSocketPath() + } + + // It's possible the user is trying to spawn another instance of code-server. + // Check if any unrelated flags are set (add one for `_` which always exists), + // that a file or directory was passed, and that the socket is active. + if (Object.keys(args).length === openInFlagCount + 1 && args._.length > 0) { + const socketPath = await readSocketPath() + if (socketPath && (await canConnect(socketPath))) { + return socketPath + } + } return undefined } diff --git a/src/node/socket.ts b/src/node/socket.ts index e5fe66778..ada024831 100644 --- a/src/node/socket.ts +++ b/src/node/socket.ts @@ -4,7 +4,7 @@ import * as path from "path" import * as tls from "tls" import { Emitter } from "../common/emitter" import { generateUuid } from "../common/util" -import { tmpdir } from "./util" +import { canConnect, tmpdir } from "./util" /** * Provides a way to proxy a TLS socket. Can be used when you need to pass a @@ -89,17 +89,6 @@ export class SocketProxyProvider { } public async findFreeSocketPath(basePath: string, maxTries = 100): Promise { - const canConnect = (path: string): Promise => { - return new Promise((resolve) => { - const socket = net.connect(path) - socket.once("error", () => resolve(false)) - socket.once("connect", () => { - socket.destroy() - resolve(true) - }) - }) - } - let i = 0 let path = basePath while ((await canConnect(path)) && i < maxTries) { diff --git a/src/node/util.ts b/src/node/util.ts index c0f37f74b..75122fe76 100644 --- a/src/node/util.ts +++ b/src/node/util.ts @@ -2,6 +2,7 @@ import * as cp from "child_process" import * as crypto from "crypto" import envPaths from "env-paths" import * as fs from "fs-extra" +import * as net from "net" import * as os from "os" import * as path from "path" import * as util from "util" @@ -246,3 +247,17 @@ export function pathToFsPath(path: string, keepDriveLetterCasing = false): strin } return value } + +/** + * Return a promise that resolves with whether the socket path is active. + */ +export function canConnect(path: string): Promise { + return new Promise((resolve) => { + const socket = net.connect(path) + socket.once("error", () => resolve(false)) + socket.once("connect", () => { + socket.destroy() + resolve(true) + }) + }) +} diff --git a/test/cli.test.ts b/test/cli.test.ts index fe78659d8..ae5256142 100644 --- a/test/cli.test.ts +++ b/test/cli.test.ts @@ -1,16 +1,24 @@ import { Level, logger } from "@coder/logger" import * as assert from "assert" +import * as fs from "fs-extra" +import * as net from "net" +import * as os from "os" import * as path from "path" -import { parse, setDefaults } from "../src/node/cli" -import { paths } from "../src/node/util" +import { Args, parse, setDefaults, shouldOpenInExistingInstance } from "../src/node/cli" +import { paths, tmpdir } from "../src/node/util" -describe("cli", () => { +type Mutable = { + -readonly [P in keyof T]: T[P] +} + +describe("parser", () => { beforeEach(() => { delete process.env.LOG_LEVEL }) // The parser should not set any defaults so the caller can determine what - // values the user actually set. These are set after calling `setDefaults`. + // values the user actually set. These are only set after explicitly calling + // `setDefaults`. const defaults = { "extensions-dir": path.join(paths.data, "extensions"), "user-data-dir": paths.data, @@ -225,3 +233,76 @@ describe("cli", () => { }) }) }) + +describe("cli", () => { + let args: Mutable = { _: [] } + const testDir = path.join(tmpdir, "tests/cli") + const vscodeIpcPath = path.join(os.tmpdir(), "vscode-ipc") + + before(async () => { + await fs.remove(testDir) + await fs.mkdirp(testDir) + }) + + beforeEach(async () => { + delete process.env.VSCODE_IPC_HOOK_CLI + args = { _: [] } + await fs.remove(vscodeIpcPath) + }) + + it("should use existing if inside code-server", async () => { + process.env.VSCODE_IPC_HOOK_CLI = "test" + assert.strictEqual(await shouldOpenInExistingInstance(args), "test") + + args.port = 8081 + args._.push("./file") + assert.strictEqual(await shouldOpenInExistingInstance(args), "test") + }) + + it("should use existing if --reuse-window is set", async () => { + args["reuse-window"] = true + assert.strictEqual(await shouldOpenInExistingInstance(args), undefined) + + await fs.writeFile(vscodeIpcPath, "test") + assert.strictEqual(await shouldOpenInExistingInstance(args), "test") + + args.port = 8081 + assert.strictEqual(await shouldOpenInExistingInstance(args), "test") + }) + + it("should use existing if --new-window is set", async () => { + args["new-window"] = true + assert.strictEqual(await shouldOpenInExistingInstance(args), undefined) + + await fs.writeFile(vscodeIpcPath, "test") + assert.strictEqual(await shouldOpenInExistingInstance(args), "test") + + args.port = 8081 + assert.strictEqual(await shouldOpenInExistingInstance(args), "test") + }) + + it("should use existing if no unrelated flags are set, has positional, and socket is active", async () => { + assert.strictEqual(await shouldOpenInExistingInstance(args), undefined) + + args._.push("./file") + assert.strictEqual(await shouldOpenInExistingInstance(args), undefined) + + const socketPath = path.join(testDir, "socket") + await fs.writeFile(vscodeIpcPath, socketPath) + assert.strictEqual(await shouldOpenInExistingInstance(args), undefined) + + await new Promise((resolve) => { + const server = net.createServer(() => { + // Close after getting the first connection. + server.close() + }) + server.once("listening", () => resolve(server)) + server.listen(socketPath) + }) + + assert.strictEqual(await shouldOpenInExistingInstance(args), socketPath) + + args.port = 8081 + assert.strictEqual(await shouldOpenInExistingInstance(args), undefined) + }) +}) From e0769dc13a83653a10125eda19aec6b33c6f8601 Mon Sep 17 00:00:00 2001 From: Asher Date: Tue, 15 Sep 2020 17:29:53 -0500 Subject: [PATCH 67/75] Move config file info log Otherwise it outputs when trying to open a file in an existing instance externally. Externally there isn't an environment variable to branch on to skip this line so instead output it with the other info lines in the child process. --- src/node/cli.ts | 4 ---- src/node/entry.ts | 2 ++ 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/node/cli.ts b/src/node/cli.ts index 028ea0d45..92bf39cb8 100644 --- a/src/node/cli.ts +++ b/src/node/cli.ts @@ -410,10 +410,6 @@ export async function readConfigFile(configPath?: string): Promise { logger.info(`Wrote default config file to ${humanPath(configPath)}`) } - if (!process.env.CODE_SERVER_PARENT_PID && !process.env.VSCODE_IPC_HOOK_CLI) { - logger.info(`Using config file ${humanPath(configPath)}`) - } - const configFile = await fs.readFile(configPath) const config = yaml.safeLoad(configFile.toString(), { filename: configPath, diff --git a/src/node/entry.ts b/src/node/entry.ts index 0d16c250d..cec2a13cf 100644 --- a/src/node/entry.ts +++ b/src/node/entry.ts @@ -188,6 +188,8 @@ const main = async (args: Args, configArgs: Args): Promise => { }) logger.info(`code-server ${version} ${commit}`) + logger.info(`Using config file ${humanPath(args.config)}`) + const serverAddress = await httpServer.listen() logger.info(`HTTP server listening on ${serverAddress}`) From 466a04f8741ccada3f5e0e2c2a38c60ed57ee4a6 Mon Sep 17 00:00:00 2001 From: Asher Date: Wed, 7 Oct 2020 13:03:39 -0500 Subject: [PATCH 68/75] Remove pointless use of openInFlagCount It'll always be zero here. --- src/node/cli.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/node/cli.ts b/src/node/cli.ts index 92bf39cb8..1403d8920 100644 --- a/src/node/cli.ts +++ b/src/node/cli.ts @@ -529,9 +529,10 @@ export const shouldOpenInExistingInstance = async (args: Args): Promise 0) { + // Check if any unrelated flags are set (check against one because `_` always + // exists), that a file or directory was passed, and that the socket is + // active. + if (Object.keys(args).length === 1 && args._.length > 0) { const socketPath = await readSocketPath() if (socketPath && (await canConnect(socketPath))) { return socketPath From 26c735b4342674efbfd4003a17f99f3ff4c6ac3a Mon Sep 17 00:00:00 2001 From: Asher Date: Fri, 9 Oct 2020 17:05:21 -0500 Subject: [PATCH 69/75] Remove tryParse Now that the exception handling happens further up there doesn't seem to be an advantage in having this in a separate method. --- src/node/entry.ts | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/src/node/entry.ts b/src/node/entry.ts index cec2a13cf..96db046e2 100644 --- a/src/node/entry.ts +++ b/src/node/entry.ts @@ -240,16 +240,11 @@ const main = async (args: Args, configArgs: Args): Promise => { } async function entry(): Promise { - const tryParse = async (): Promise<[Args, Args, Args]> => { - const cliArgs = parse(process.argv.slice(2)) - const configArgs = await readConfigFile(cliArgs.config) - // This prioritizes the flags set in args over the ones in the config file. - let args = Object.assign(configArgs, cliArgs) - args = await setDefaults(args) - return [args, cliArgs, configArgs] - } - - const [args, cliArgs, configArgs] = await tryParse() + const cliArgs = parse(process.argv.slice(2)) + const configArgs = await readConfigFile(cliArgs.config) + // This prioritizes the flags set in args over the ones in the config file. + let args = Object.assign(configArgs, cliArgs) + args = await setDefaults(args) // There's no need to check flags like --help or to spawn in an existing // instance for the child process because these would have already happened in From d7e31126253afa0fd4b6b913318a57e8506e6aab Mon Sep 17 00:00:00 2001 From: Asher Date: Fri, 9 Oct 2020 18:01:43 -0500 Subject: [PATCH 70/75] Update standalone test --- ci/build/test-standalone-release.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ci/build/test-standalone-release.sh b/ci/build/test-standalone-release.sh index 92b58e8c0..0344ea39f 100755 --- a/ci/build/test-standalone-release.sh +++ b/ci/build/test-standalone-release.sh @@ -15,8 +15,7 @@ main() { ./release-standalone/bin/code-server --extensions-dir "$EXTENSIONS_DIR" --install-extension ms-python.python local installed_extensions installed_extensions="$(./release-standalone/bin/code-server --extensions-dir "$EXTENSIONS_DIR" --list-extensions 2>&1)" - if [[ $installed_extensions != *"info Using config file ~/.config/code-server/config.yaml -ms-python.python" ]]; then + if [[ $installed_extensions != "ms-python.python" ]]; then echo "Unexpected output from listing extensions:" echo "$installed_extensions" exit 1 From c6ba12942c584d3e4cefea0f9c84ed3092dde81c Mon Sep 17 00:00:00 2001 From: Asher Date: Sun, 11 Oct 2020 01:14:43 -0500 Subject: [PATCH 71/75] Filter blank plugin directories (#2187) I neglected to realize that "".split(":") is an array with "" in it. --- src/node/plugin.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/node/plugin.ts b/src/node/plugin.ts index e17a9909c..7469f317d 100644 --- a/src/node/plugin.ts +++ b/src/node/plugin.ts @@ -75,12 +75,18 @@ export const loadPlugins = async (httpServer: HttpServer, args: Args): Promise _loadPlugins(path.resolve(dir), httpServer, args)), + ...pluginPath + .split(":") + .filter((p) => !!p) + .map((dir) => _loadPlugins(path.resolve(dir), httpServer, args)), // Individual plugins so you don't have to symlink or move them into a // directory specifically for plugins. This lets you load plugins that are // on the same level as other directories that are not plugins (if you tried // to use CS_PLUGIN_PATH code-server would try to load those other // directories as plugins). Intended for development. - ...plugin.split(":").map((dir) => loadPlugin(path.resolve(dir), httpServer, args)), + ...plugin + .split(":") + .filter((p) => !!p) + .map((dir) => loadPlugin(path.resolve(dir), httpServer, args)), ]) } From d7ba9ae63345b2ea96f40641055d91b811bc2d17 Mon Sep 17 00:00:00 2001 From: Anmol Sethi Date: Mon, 12 Oct 2020 01:18:55 -0400 Subject: [PATCH 72/75] v3.6.0 --- doc/install.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/doc/install.md b/doc/install.md index 8d7e98ba8..b53a60675 100644 --- a/doc/install.md +++ b/doc/install.md @@ -79,8 +79,8 @@ commands presented in the rest of this document. ## Debian, Ubuntu ```bash -curl -fOL https://github.com/cdr/code-server/releases/download/v3.5.0/code-server_3.5.0_amd64.deb -sudo dpkg -i code-server_3.5.0_amd64.deb +curl -fOL https://github.com/cdr/code-server/releases/download/v3.6.0/code-server_3.6.0_amd64.deb +sudo dpkg -i code-server_3.6.0_amd64.deb sudo systemctl enable --now code-server@$USER # Now visit http://127.0.0.1:8080. Your password is in ~/.config/code-server/config.yaml ``` @@ -88,8 +88,8 @@ sudo systemctl enable --now code-server@$USER ## Fedora, CentOS, RHEL, SUSE ```bash -curl -fOL https://github.com/cdr/code-server/releases/download/v3.5.0/code-server-3.5.0-amd64.rpm -sudo rpm -i code-server-3.5.0-amd64.rpm +curl -fOL https://github.com/cdr/code-server/releases/download/v3.6.0/code-server-3.6.0-amd64.rpm +sudo rpm -i code-server-3.6.0-amd64.rpm sudo systemctl enable --now code-server@$USER # Now visit http://127.0.0.1:8080. Your password is in ~/.config/code-server/config.yaml ``` @@ -158,10 +158,10 @@ Here is an example script for installing and using a standalone `code-server` re ```bash mkdir -p ~/.local/lib ~/.local/bin -curl -fL https://github.com/cdr/code-server/releases/download/v3.5.0/code-server-3.5.0-linux-amd64.tar.gz \ +curl -fL https://github.com/cdr/code-server/releases/download/v3.6.0/code-server-3.6.0-linux-amd64.tar.gz \ | tar -C ~/.local/lib -xz -mv ~/.local/lib/code-server-3.5.0-linux-amd64 ~/.local/lib/code-server-3.5.0 -ln -s ~/.local/lib/code-server-3.5.0/bin/code-server ~/.local/bin/code-server +mv ~/.local/lib/code-server-3.6.0-linux-amd64 ~/.local/lib/code-server-3.6.0 +ln -s ~/.local/lib/code-server-3.6.0/bin/code-server ~/.local/bin/code-server PATH="~/.local/bin:$PATH" code-server # Now visit http://127.0.0.1:8080. Your password is in ~/.config/code-server/config.yaml From a4a03c14922ccaec2a9ff8d1b7b2af8522a4214d Mon Sep 17 00:00:00 2001 From: Anmol Sethi Date: Mon, 12 Oct 2020 02:09:48 -0400 Subject: [PATCH 73/75] Fix CI --- ci/build/build-release.sh | 2 +- ci/build/npm-postinstall.sh | 4 ++++ ci/build/release-github-assets.sh | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/ci/build/build-release.sh b/ci/build/build-release.sh index eedda98e2..8f71dc339 100755 --- a/ci/build/build-release.sh +++ b/ci/build/build-release.sh @@ -25,7 +25,6 @@ main() { rsync README.md "$RELEASE_PATH" rsync LICENSE.txt "$RELEASE_PATH" rsync ./lib/vscode/ThirdPartyNotices.txt "$RELEASE_PATH" - rsync ./lib/coder-cloud-agent "$RELEASE_PATH/lib" # code-server exports types which can be imported and used by plugins. Those # types import ipc.d.ts but it isn't included in the final vscode build so @@ -60,6 +59,7 @@ EOF if [ "$KEEP_MODULES" = 1 ]; then rsync node_modules/ "$RELEASE_PATH/node_modules" + rsync ./lib/coder-cloud-agent "$RELEASE_PATH/lib" fi } diff --git a/ci/build/npm-postinstall.sh b/ci/build/npm-postinstall.sh index 74b904783..bd7922d5c 100755 --- a/ci/build/npm-postinstall.sh +++ b/ci/build/npm-postinstall.sh @@ -24,6 +24,10 @@ main() { ;; esac + OS="$(uname | tr '[:upper:]' '[:lower:]')" + curl -fsSL "https://storage.googleapis.com/coder-cloud-releases/agent/latest/$OS/cloud-agent" -o ./lib/coder-cloud-agent + chmod +x ./lib/coder-cloud-agent + if ! vscode_yarn; then echo "You may not have the required dependencies to build the native modules." echo "Please see https://github.com/cdr/code-server/blob/master/doc/npm.md" diff --git a/ci/build/release-github-assets.sh b/ci/build/release-github-assets.sh index f2d9ff8c3..7fba67703 100755 --- a/ci/build/release-github-assets.sh +++ b/ci/build/release-github-assets.sh @@ -11,7 +11,7 @@ main() { source ./ci/lib.sh download_artifact release-packages ./release-packages - local assets=(./release-packages/code-server*"$VERSION"*{.tar.gz,.zip,.deb,.rpm}) + local assets=(./release-packages/code-server*"$VERSION"*{.tar.gz,.deb,.rpm}) for i in "${!assets[@]}"; do assets[$i]="--attach=${assets[$i]}" done From ea105a9290579040e1ab396c7c0d0d2d825aad3a Mon Sep 17 00:00:00 2001 From: Anmol Sethi Date: Mon, 12 Oct 2020 04:26:23 -0400 Subject: [PATCH 74/75] Fix release image entrypoint.sh --- ci/release-image/entrypoint.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/release-image/entrypoint.sh b/ci/release-image/entrypoint.sh index 7842a3564..b4343e7ed 100755 --- a/ci/release-image/entrypoint.sh +++ b/ci/release-image/entrypoint.sh @@ -5,7 +5,7 @@ set -eu USER="$(whoami)" export USER -if [ "${DOCKER_USER-}" != "$USER" ]; then +if [ "${DOCKER_USER-}" ] && [ "$DOCKER_USER" != "$USER" ]; then echo "$DOCKER_USER ALL=(ALL) NOPASSWD:ALL" | sudo tee -a /etc/sudoers.d/nopasswd > /dev/null # Unfortunately we cannot change $HOME as we cannot move any bind mounts # nor can we bind mount $HOME into a new home as that requires a privileged container. From ec564091f183987333806bab432bc49062561b4a Mon Sep 17 00:00:00 2001 From: Asher Date: Mon, 12 Oct 2020 17:26:50 -0500 Subject: [PATCH 75/75] Fix agent copy during release If there isn't a lib dir yet it'll copy as lib instead of getting put inside the directory. --- ci/build/build-release.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/ci/build/build-release.sh b/ci/build/build-release.sh index 8f71dc339..95579eb82 100755 --- a/ci/build/build-release.sh +++ b/ci/build/build-release.sh @@ -59,6 +59,7 @@ EOF if [ "$KEEP_MODULES" = 1 ]; then rsync node_modules/ "$RELEASE_PATH/node_modules" + mkdir -p "$RELEASE_PATH/lib" rsync ./lib/coder-cloud-agent "$RELEASE_PATH/lib" fi }