From e52ebe027b7cd431ef80a4e4965f8a9e463d0144 Mon Sep 17 00:00:00 2001 From: Asher Date: Tue, 15 Mar 2022 23:43:04 +0000 Subject: [PATCH 01/18] Move integration types into code-server This will be easier to maintain than to have it as a patch. --- src/node/cli.ts | 57 ++++++++++++++++++++++++++------------ src/node/entry.ts | 4 +-- src/node/main.ts | 34 +++++++++++++++-------- src/node/routes/vscode.ts | 19 ++++++++++--- test/unit/node/cli.test.ts | 8 +++--- 5 files changed, 82 insertions(+), 40 deletions(-) diff --git a/src/node/cli.ts b/src/node/cli.ts index 2e638c8cf2de..949f1472247a 100644 --- a/src/node/cli.ts +++ b/src/node/cli.ts @@ -31,13 +31,33 @@ export enum LogLevel { export class OptionalString extends Optional {} +/** + * Code flags provided by the user. + */ +export interface UserProvidedCodeArgs { + "disable-telemetry"?: boolean + force?: boolean + "user-data-dir"?: string + "enable-proposed-api"?: string[] + "extensions-dir"?: string + "builtin-extensions-dir"?: string + "install-extension"?: string[] + "uninstall-extension"?: string[] + "list-extensions"?: boolean + "locate-extension"?: string[] + "show-versions"?: boolean + category?: string + "github-auth"?: string + "disable-update-check"?: boolean +} + /** * Arguments that the user explicitly provided on the command line. All * arguments must be optional. * * For arguments with defaults see DefaultedArgs. */ -export interface UserProvidedArgs { +export interface UserProvidedArgs extends UserProvidedCodeArgs { config?: string auth?: AuthType password?: string @@ -45,7 +65,6 @@ export interface UserProvidedArgs { cert?: OptionalString "cert-host"?: string "cert-key"?: string - "disable-update-check"?: boolean enable?: string[] help?: boolean host?: string @@ -66,21 +85,6 @@ export interface UserProvidedArgs { verbose?: boolean /* Positional arguments. */ _?: string[] - - // VS Code flags. - "disable-telemetry"?: boolean - force?: boolean - "user-data-dir"?: string - "enable-proposed-api"?: string[] - "extensions-dir"?: string - "builtin-extensions-dir"?: string - "install-extension"?: string[] - "uninstall-extension"?: string[] - "list-extensions"?: boolean - "locate-extension"?: string[] - "show-versions"?: boolean - category?: string - "github-auth"?: string } interface Option { @@ -761,10 +765,27 @@ export const shouldOpenInExistingInstance = async (args: UserProvidedArgs): Prom return undefined } +/** + * Arguments for running Code's server. + */ +export interface CodeArgs extends UserProvidedCodeArgs { + "accept-server-license-terms"?: boolean + "connection-token"?: string + help: boolean + port: string + version: boolean + "without-browser-env-var"?: boolean +} + +/** + * Spawn the Code CLI. + */ +export type SpawnCli = (args: CodeArgs) => Promise + /** * Convert our arguments to VS Code server arguments. */ -export const toVsCodeArgs = async (args: DefaultedArgs): Promise => { +export const toCodeArgs = async (args: DefaultedArgs): Promise => { return { "connection-token": "0000", ...args, diff --git a/src/node/entry.ts b/src/node/entry.ts index 010f18b76fd0..2a62ac77a1da 100644 --- a/src/node/entry.ts +++ b/src/node/entry.ts @@ -1,7 +1,7 @@ import { logger } from "@coder/logger" import { optionDescriptions, parse, readConfigFile, setDefaults, shouldOpenInExistingInstance } from "./cli" import { getVersionString, getVersionJsonString } from "./constants" -import { openInExistingInstance, runCodeServer, runVsCodeCli, shouldSpawnCliProcess } from "./main" +import { openInExistingInstance, runCodeServer, runCodeCli, shouldSpawnCliProcess } from "./main" import { isChild, wrapper } from "./wrapper" async function entry(): Promise { @@ -48,7 +48,7 @@ async function entry(): Promise { if (shouldSpawnCliProcess(args)) { logger.debug("Found VS Code arguments; spawning VS Code CLI") - return runVsCodeCli(args) + return runCodeCli(args) } const socketPath = await shouldOpenInExistingInstance(cliArgs) diff --git a/src/node/main.ts b/src/node/main.ts index fc4bfe83f832..3521b70f9733 100644 --- a/src/node/main.ts +++ b/src/node/main.ts @@ -5,7 +5,7 @@ import path from "path" import { Disposable } from "../common/emitter" import { plural } from "../common/util" import { createApp, ensureAddress } from "./app" -import { AuthType, DefaultedArgs, Feature, toVsCodeArgs, UserProvidedArgs } from "./cli" +import { AuthType, DefaultedArgs, Feature, SpawnCli, toCodeArgs, UserProvidedArgs } from "./cli" import { coderCloudBind } from "./coder_cloud" import { commit, version } from "./constants" import { register } from "./routes" @@ -23,28 +23,38 @@ export const shouldSpawnCliProcess = (args: UserProvidedArgs): boolean => { ) } +export interface OpenCommandPipeArgs { + type: "open" + fileURIs?: string[] + folderURIs: string[] + forceNewWindow?: boolean + diffMode?: boolean + addMode?: boolean + gotoLineMode?: boolean + forceReuseWindow?: boolean + waitMarkerFilePath?: string +} + /** - * This is useful when an CLI arg should be passed to VS Code directly, - * such as when managing extensions. - * @deprecated This should be removed when code-server merges with lib/vscode. + * Run Code's CLI for things like managing extensions. */ -export const runVsCodeCli = async (args: DefaultedArgs): Promise => { - logger.debug("Running VS Code CLI") +export const runCodeCli = async (args: DefaultedArgs): Promise => { + logger.debug("Running Code CLI") // See ../../lib/vscode/src/vs/server/node/server.main.js. - const spawnCli = await loadAMDModule("vs/server/node/server.main", "spawnCli") + const spawnCli = await loadAMDModule("vs/server/node/server.main", "spawnCli") try { - await spawnCli(await toVsCodeArgs(args)) + await spawnCli(await toCodeArgs(args)) } catch (error: any) { - logger.error("Got error from VS Code", error) + logger.error("Got error from Code", error) } process.exit(0) } export const openInExistingInstance = async (args: DefaultedArgs, socketPath: string): Promise => { - const pipeArgs: CodeServerLib.OpenCommandPipeArgs & { fileURIs: string[] } = { + const pipeArgs: OpenCommandPipeArgs & { fileURIs: string[] } = { type: "open", folderURIs: [], fileURIs: [], @@ -76,12 +86,12 @@ export const openInExistingInstance = async (args: DefaultedArgs, socketPath: st }, (response) => { response.on("data", (message) => { - logger.debug("got message from VS Code", field("message", message.toString())) + logger.debug("got message from Code", field("message", message.toString())) }) }, ) vscode.on("error", (error: unknown) => { - logger.error("got error from VS Code", field("error", error)) + logger.error("got error from Code", field("error", error)) }) vscode.write(JSON.stringify(pipeArgs)) vscode.end() diff --git a/src/node/routes/vscode.ts b/src/node/routes/vscode.ts index ba67426ecaef..711c23c398f4 100644 --- a/src/node/routes/vscode.ts +++ b/src/node/routes/vscode.ts @@ -1,9 +1,11 @@ import { logger } from "@coder/logger" import * as express from "express" +import * as http from "http" +import * as net from "net" import * as path from "path" import { WebsocketRequest } from "../../../typings/pluginapi" import { logError } from "../../common/util" -import { toVsCodeArgs } from "../cli" +import { CodeArgs, toCodeArgs } from "../cli" import { isDevMode } from "../constants" import { authenticated, ensureAuthenticated, redirect, self } from "../http" import { SocketProxyProvider } from "../socket" @@ -11,9 +13,18 @@ import { isFile, loadAMDModule } from "../util" import { Router as WsRouter } from "../wsRouter" import { errorHandler } from "./errors" +export interface IServerAPI { + handleRequest(req: http.IncomingMessage, res: http.ServerResponse): Promise + handleUpgrade(req: http.IncomingMessage, socket: net.Socket): void + handleServerError(err: Error): void + dispose(): void +} + +export type CreateServer = (address: string | net.AddressInfo | null, args: CodeArgs) => Promise + export class CodeServerRouteWrapper { /** Assigned in `ensureCodeServerLoaded` */ - private _codeServerMain!: CodeServerLib.IServerAPI + private _codeServerMain!: IServerAPI private _wsRouterWrapper = WsRouter() private _socketProxyProvider = new SocketProxyProvider() public router = express.Router() @@ -120,11 +131,11 @@ export class CodeServerRouteWrapper { /** * @file ../../../lib/vscode/src/vs/server/node/server.main.js */ - const createVSServer = await loadAMDModule("vs/server/node/server.main", "createServer") + const createVSServer = await loadAMDModule("vs/server/node/server.main", "createServer") try { this._codeServerMain = await createVSServer(null, { - ...(await toVsCodeArgs(args)), + ...(await toCodeArgs(args)), // TODO: Make the browser helper script work. "without-browser-env-var": true, }) diff --git a/test/unit/node/cli.test.ts b/test/unit/node/cli.test.ts index 73cbea17d4b8..d733ee2ed9da 100644 --- a/test/unit/node/cli.test.ts +++ b/test/unit/node/cli.test.ts @@ -12,7 +12,7 @@ import { setDefaults, shouldOpenInExistingInstance, splitOnFirstEquals, - toVsCodeArgs, + toCodeArgs, optionDescriptions, options, Options, @@ -727,7 +727,7 @@ describe("readSocketPath", () => { }) }) -describe("toVsCodeArgs", () => { +describe("toCodeArgs", () => { const vscodeDefaults = { ...defaults, "connection-token": "0000", @@ -744,7 +744,7 @@ describe("toVsCodeArgs", () => { }) it("should convert empty args", async () => { - expect(await toVsCodeArgs(await setDefaults(parse([])))).toStrictEqual({ + expect(await toCodeArgs(await setDefaults(parse([])))).toStrictEqual({ ...vscodeDefaults, }) }) @@ -752,7 +752,7 @@ describe("toVsCodeArgs", () => { it("should ignore regular file", async () => { const file = path.join(await tmpdir(testName), "file") await fs.writeFile(file, "foobar") - expect(await toVsCodeArgs(await setDefaults(parse([file])))).toStrictEqual({ + expect(await toCodeArgs(await setDefaults(parse([file])))).toStrictEqual({ ...vscodeDefaults, _: [file], }) From 2d4f1b195ab106b806236d007df67ba06819df44 Mon Sep 17 00:00:00 2001 From: Asher Date: Wed, 16 Mar 2022 00:16:30 +0000 Subject: [PATCH 02/18] Disable connection token Using a flag means we will not need to patch it out. I think this is new from 1.64? --- src/node/cli.ts | 2 +- src/node/routes/vscode.ts | 1 + test/unit/node/cli.test.ts | 1 - 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/node/cli.ts b/src/node/cli.ts index 949f1472247a..73ae14120c49 100644 --- a/src/node/cli.ts +++ b/src/node/cli.ts @@ -774,6 +774,7 @@ export interface CodeArgs extends UserProvidedCodeArgs { help: boolean port: string version: boolean + "without-connection-token"?: boolean "without-browser-env-var"?: boolean } @@ -787,7 +788,6 @@ export type SpawnCli = (args: CodeArgs) => Promise */ export const toCodeArgs = async (args: DefaultedArgs): Promise => { return { - "connection-token": "0000", ...args, "accept-server-license-terms": true, /** Type casting. */ diff --git a/src/node/routes/vscode.ts b/src/node/routes/vscode.ts index 711c23c398f4..255fd7835f38 100644 --- a/src/node/routes/vscode.ts +++ b/src/node/routes/vscode.ts @@ -137,6 +137,7 @@ export class CodeServerRouteWrapper { this._codeServerMain = await createVSServer(null, { ...(await toCodeArgs(args)), // TODO: Make the browser helper script work. + "without-connection-token": true, "without-browser-env-var": true, }) } catch (error) { diff --git a/test/unit/node/cli.test.ts b/test/unit/node/cli.test.ts index d733ee2ed9da..edf214947aa2 100644 --- a/test/unit/node/cli.test.ts +++ b/test/unit/node/cli.test.ts @@ -730,7 +730,6 @@ describe("readSocketPath", () => { describe("toCodeArgs", () => { const vscodeDefaults = { ...defaults, - "connection-token": "0000", "accept-server-license-terms": true, help: false, port: "8080", From 52ca4502392f53a0e59af910e2a642797f0d862d Mon Sep 17 00:00:00 2001 From: Asher Date: Wed, 16 Mar 2022 15:47:44 +0000 Subject: [PATCH 03/18] Add product.json to build process This way we do not have to patch it. --- ci/build/build-release.sh | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/ci/build/build-release.sh b/ci/build/build-release.sh index a720b2804df6..9aeb5c0bf80c 100755 --- a/ci/build/build-release.sh +++ b/ci/build/build-release.sh @@ -89,8 +89,8 @@ bundle_vscode() { rsync "$VSCODE_SRC_PATH/resources/server/bin/helpers/" "$VSCODE_OUT_PATH/bin/helpers" chmod +x "$VSCODE_OUT_PATH/bin/helpers/browser.sh" - # Add the commit and date and enable telemetry. This just makes telemetry - # available; telemetry can still be disabled by flag or setting. + # Add the commit, date, our name, links, and enable telemetry. This just makes + # telemetry available; telemetry can still be disabled by flag or setting. jq --slurp '.[0] * .[1]' "$VSCODE_SRC_PATH/product.json" <( cat << EOF { @@ -98,7 +98,30 @@ bundle_vscode() { "commit": "$(cd "$VSCODE_SRC_PATH" && git rev-parse HEAD)", "quality": "stable", "date": $(jq -n 'now | todate'), - "codeServerVersion": "$VERSION" + "codeServerVersion": "$VERSION", + "nameShort": "code-server", + "nameLong": "code-server", + "applicationName": "code-server", + "dataFolderName": ".code-server", + "win32MutexName": "codeserver", + "licenseUrl": "https://github.com/cdr/code-server/blob/main/LICENSE.txt", + "win32DirName": "code-server", + "win32NameVersion": "code-server", + "win32AppUserModelId": "coder.code-server", + "win32ShellNameShort": "c&ode-server", + "darwinBundleIdentifier": "com.coder.code.server", + "linuxIconName": "com.coder.code.server", + "reportIssueUrl": "https://github.com/cdr/code-server/issues/new", + "documentationUrl": "https://go.microsoft.com/fwlink/?LinkID=533484#vscode", + "keyboardShortcutsUrlMac": "https://go.microsoft.com/fwlink/?linkid=832143", + "keyboardShortcutsUrlLinux": "https://go.microsoft.com/fwlink/?linkid=832144", + "keyboardShortcutsUrlWin": "https://go.microsoft.com/fwlink/?linkid=832145", + "introductoryVideosUrl": "https://go.microsoft.com/fwlink/?linkid=832146", + "tipsAndTricksUrl": "https://go.microsoft.com/fwlink/?linkid=852118", + "newsletterSignupUrl": "https://www.research.net/r/vsc-newsletter", + "linkProtectionTrustedDomains": [ + "https://open-vsx.org" + ] } EOF ) > "$VSCODE_OUT_PATH/product.json" From 175846867641c709e82c6fe84d4d5bb96b05774f Mon Sep 17 00:00:00 2001 From: Asher Date: Wed, 16 Mar 2022 16:07:45 +0000 Subject: [PATCH 04/18] Ship with remote agent package.json Instead of the root one. This contains fewer dependencies. --- ci/build/build-release.sh | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/ci/build/build-release.sh b/ci/build/build-release.sh index 9aeb5c0bf80c..f2df4b3cdca4 100755 --- a/ci/build/build-release.sh +++ b/ci/build/build-release.sh @@ -66,7 +66,6 @@ EOF bundle_vscode() { mkdir -p "$VSCODE_OUT_PATH" - rsync "$VSCODE_SRC_PATH/yarn.lock" "$VSCODE_OUT_PATH" rsync "$VSCODE_SRC_PATH/out-vscode-reh-web${MINIFY:+-min}/" "$VSCODE_OUT_PATH/out" rsync "$VSCODE_SRC_PATH/.build/extensions/" "$VSCODE_OUT_PATH/extensions" @@ -126,10 +125,15 @@ bundle_vscode() { EOF ) > "$VSCODE_OUT_PATH/product.json" - # We remove the scripts field so that later on we can run - # yarn to fetch node_modules if necessary without build scripts running. - # We cannot use --no-scripts because we still want dependent package scripts to run. - jq 'del(.scripts)' < "$VSCODE_SRC_PATH/package.json" > "$VSCODE_OUT_PATH/package.json" + # Use the package.json for the web/remote server. It does not have the right + # version though so pull that from the main package.json. Also remove keytar + # since the web does not rely on it and that removes the dependency on + # libsecret. + jq --slurp '.[0] * {version: .[1].version} | del(.dependencies.keytar)' \ + "$VSCODE_SRC_PATH/remote/package.json" \ + "$VSCODE_SRC_PATH/package.json" > "$VSCODE_OUT_PATH/package.json" + + rsync "$VSCODE_SRC_PATH/remote/yarn.lock" "$VSCODE_OUT_PATH/yarn.lock" pushd "$VSCODE_OUT_PATH" symlink_asar From 430448b06d774aac2587e75f80f0295396cbb8e9 Mon Sep 17 00:00:00 2001 From: Asher Date: Wed, 16 Mar 2022 20:53:50 +0000 Subject: [PATCH 05/18] Let Code handle errors This way we will not have to patch Code to make this work and I think it makes sense to let Code handle the request. If we do want to handle errors we can do it cleanly by patching their error handler to throw instead. --- src/node/routes/vscode.ts | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/node/routes/vscode.ts b/src/node/routes/vscode.ts index 255fd7835f38..06d7ba71339c 100644 --- a/src/node/routes/vscode.ts +++ b/src/node/routes/vscode.ts @@ -11,7 +11,6 @@ import { authenticated, ensureAuthenticated, redirect, self } from "../http" import { SocketProxyProvider } from "../socket" import { isFile, loadAMDModule } from "../util" import { Router as WsRouter } from "../wsRouter" -import { errorHandler } from "./errors" export interface IServerAPI { handleRequest(req: http.IncomingMessage, res: http.ServerResponse): Promise @@ -92,17 +91,6 @@ export class CodeServerRouteWrapper { } private $proxyRequest: express.Handler = async (req, res, next) => { - // We allow certain errors to propagate so that other routers may handle requests - // outside VS Code - const requestErrorHandler = (error: any) => { - if (error instanceof Error && ["EntryNotFound", "FileNotFound", "HttpError"].includes(error.message)) { - next() - } - errorHandler(error, req, res, next) - } - - req.once("error", requestErrorHandler) - this._codeServerMain.handleRequest(req, res) } From 8f1d5a6c3b00863b87a521195d05490559081260 Mon Sep 17 00:00:00 2001 From: Asher Date: Thu, 17 Mar 2022 23:34:57 +0000 Subject: [PATCH 06/18] Move manifest override into code-server This way we will not have to patch it. --- src/node/routes/vscode.ts | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/src/node/routes/vscode.ts b/src/node/routes/vscode.ts index 06d7ba71339c..f08aaa59cf1a 100644 --- a/src/node/routes/vscode.ts +++ b/src/node/routes/vscode.ts @@ -7,7 +7,7 @@ import { WebsocketRequest } from "../../../typings/pluginapi" import { logError } from "../../common/util" import { CodeArgs, toCodeArgs } from "../cli" import { isDevMode } from "../constants" -import { authenticated, ensureAuthenticated, redirect, self } from "../http" +import { authenticated, ensureAuthenticated, redirect, replaceTemplates, self } from "../http" import { SocketProxyProvider } from "../socket" import { isFile, loadAMDModule } from "../util" import { Router as WsRouter } from "../wsRouter" @@ -34,6 +34,32 @@ export class CodeServerRouteWrapper { //#region Route Handlers + private manifest: express.Handler = async (req, res, next) => { + res.writeHead(200, { "Content-Type": "application/manifest+json" }) + + return res.end( + replaceTemplates( + req, + JSON.stringify( + { + name: "code-server", + short_name: "code-server", + start_url: ".", + display: "fullscreen", + description: "Run Code on a remote server.", + icons: [192, 512].map((size) => ({ + src: `{{BASE}}/_static/src/browser/media/pwa-icon-${size}.png`, + type: "image/png", + sizes: `${size}x${size}`, + })), + }, + null, + 2, + ), + ), + ) + } + private $root: express.Handler = async (req, res, next) => { const isAuthenticated = await authenticated(req) const NO_FOLDER_OR_WORKSPACE_QUERY = !req.query.folder && !req.query.workspace @@ -141,6 +167,7 @@ export class CodeServerRouteWrapper { constructor() { this.router.get("/", this.ensureCodeServerLoaded, this.$root) + this.router.get(/manifest.json$/, this.manifest) this.router.all("*", ensureAuthenticated, this.ensureCodeServerLoaded, this.$proxyRequest) this._wsRouterWrapper.ws("/", ensureAuthenticated, this.ensureCodeServerLoaded, this.$proxyWebsocket) } From 1bba7e3819cf583e82b361e29f0170e0eacb6c90 Mon Sep 17 00:00:00 2001 From: Asher Date: Thu, 17 Mar 2022 20:06:08 +0000 Subject: [PATCH 07/18] Move to patches - Switch submodule to track upstream - Add quilt to the process - Add patches The node-* ignore was ignoring one of the diffs so I removed it. This was added when we were curling Node as node-v{version}-darwin-x64 for the macOS build but this no longer happens (we use the Node action to install a specific version now so we just use the system-wide Node). --- .github/workflows/ci.yaml | 13 +- .gitignore | 9 +- .gitmodules | 2 +- ci/build/build-packages.sh | 5 + ci/build/build-standalone-release.sh | 5 - docs/CONTRIBUTING.md | 160 ++++++-------- lib/vscode | 2 +- patches/base-path.diff | 305 +++++++++++++++++++++++++++ patches/connection-type.diff | 19 ++ patches/display-language.diff | 265 +++++++++++++++++++++++ patches/github-auth.diff | 118 +++++++++++ patches/insecure-notification.diff | 57 +++++ patches/integration.diff | 269 +++++++++++++++++++++++ patches/last-opened.diff | 32 +++ patches/local-storage.diff | 66 ++++++ patches/log-level.diff | 21 ++ patches/logout.diff | 107 ++++++++++ patches/marketplace.diff | 53 +++++ patches/node-version.diff | 106 ++++++++++ patches/post-install.diff | 26 +++ patches/proposed-api.diff | 36 ++++ patches/proxy-uri.diff | 104 +++++++++ patches/series | 20 ++ patches/service-worker.diff | 67 ++++++ patches/store-socket.diff | 31 +++ patches/unique-db.diff | 63 ++++++ patches/update-check.diff | 132 ++++++++++++ patches/webview.diff | 46 ++++ src/browser/serviceWorker.ts | 14 ++ src/node/routes/index.ts | 5 + 30 files changed, 2050 insertions(+), 108 deletions(-) create mode 100644 patches/base-path.diff create mode 100644 patches/connection-type.diff create mode 100644 patches/display-language.diff create mode 100644 patches/github-auth.diff create mode 100644 patches/insecure-notification.diff create mode 100644 patches/integration.diff create mode 100644 patches/last-opened.diff create mode 100644 patches/local-storage.diff create mode 100644 patches/log-level.diff create mode 100644 patches/logout.diff create mode 100644 patches/marketplace.diff create mode 100644 patches/node-version.diff create mode 100644 patches/post-install.diff create mode 100644 patches/proposed-api.diff create mode 100644 patches/proxy-uri.diff create mode 100644 patches/series create mode 100644 patches/service-worker.diff create mode 100644 patches/store-socket.diff create mode 100644 patches/unique-db.diff create mode 100644 patches/update-check.diff create mode 100644 patches/webview.diff create mode 100644 src/browser/serviceWorker.ts diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 433132b37b27..e2a9b99a0d15 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -110,6 +110,12 @@ jobs: fetch-depth: 0 submodules: true + - name: Install quilt + run: sudo apt update && sudo apt install quilt + + - name: Patch Code + run: quilt push -a + - name: Install Node.js v14 uses: actions/setup-node@v3 with: @@ -132,12 +138,13 @@ jobs: run: yarn build # Get Code's git hash. When this changes it means the content is - # different and we need to rebuild. Use VSCODE_CACHE_VERSION to force a - # rebuild. + # different and we need to rebuild. - name: Get latest lib/vscode rev id: vscode-rev run: echo "::set-output name=rev::$(git rev-parse HEAD:./lib/vscode)" + # We need to rebuild when we have a new version of Code or when any of + # the patches changed. Use VSCODE_CACHE_VERSION to force a rebuild. - name: Fetch Code build from cache id: cache-vscode-2 uses: actions/cache@v3 @@ -147,7 +154,7 @@ jobs: lib/vscode/out-build lib/vscode/out-vscode-reh-web lib/vscode/out-vscode-reh-web-min - key: vscode-reh-build-${{ secrets.VSCODE_CACHE_VERSION }}-${{ steps.vscode-rev.outputs.rev }} + key: vscode-reh-build-${{ secrets.VSCODE_CACHE_VERSION }}-${{ steps.vscode-rev.outputs.rev }}-${{ hashFiles('patches/*.diff') }} - name: Build vscode if: steps.cache-vscode-2.outputs.cache-hit != 'true' diff --git a/.gitignore b/.gitignore index 3cc6e31d7af3..8fd09359d0b9 100644 --- a/.gitignore +++ b/.gitignore @@ -8,12 +8,17 @@ release-packages/ release-gcp/ release-images/ node_modules -vendor/modules -node-* /plugins /lib/coder-cloud-agent .home coverage **/.DS_Store + +# Code packages itself here. +/lib/vscode-reh-web-linux-x64/ + # Failed e2e test videos are saved here test/test-results + +# Quilt's internal data. +/.pc diff --git a/.gitmodules b/.gitmodules index a185a80e1fd7..9854a1b1dd30 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "lib/vscode"] path = lib/vscode - url = https://github.com/coder/vscode + url = https://github.com/microsoft/vscode diff --git a/ci/build/build-packages.sh b/ci/build/build-packages.sh index 8da6aec38332..6c85ccd33cde 100755 --- a/ci/build/build-packages.sh +++ b/ci/build/build-packages.sh @@ -50,6 +50,11 @@ release_nfpm() { export NFPM_ARCH + # Code deletes some files from the extension node_modules directory which + # leaves broken symlinks in the corresponding .bin directory. nfpm will fail + # on these broken symlinks so clean them up. + rm -fr "./release-standalone/lib/vscode/extensions/node_modules/.bin" + PKG_FORMAT="deb" NFPM_ARCH="$(get_nfpm_arch $PKG_FORMAT "$ARCH")" nfpm_config="$(envsubst < ./ci/build/nfpm.yaml)" diff --git a/ci/build/build-standalone-release.sh b/ci/build/build-standalone-release.sh index cba139947163..2bc553a619ce 100755 --- a/ci/build/build-standalone-release.sh +++ b/ci/build/build-standalone-release.sh @@ -29,11 +29,6 @@ main() { cd "$RELEASE_PATH" yarn --production --frozen-lockfile - - # HACK: the version of Typescript vscode 1.57 uses in extensions/ - # leaves a few stray symlinks. Clean them up so nfpm does not fail. - # Remove this line when its no longer needed. - rm -fr "$RELEASE_PATH/lib/vscode/extensions/node_modules/.bin" } main "$@" diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index c047fa6b960c..9a59aee07b81 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -7,7 +7,8 @@ - [Creating pull requests](#creating-pull-requests) - [Commits and commit history](#commits-and-commit-history) - [Development workflow](#development-workflow) - - [Updates to VS Code](#updates-to-vs-code) + - [Version updates to Code](#version-updates-to-code) + - [Patching Code](#patching-code) - [Build](#build) - [Help](#help) - [Test](#test) @@ -16,7 +17,7 @@ - [Integration tests](#integration-tests) - [End-to-end tests](#end-to-end-tests) - [Structure](#structure) - - [Modifications to VS Code](#modifications-to-vs-code) + - [Modifications to Code](#modifications-to-code) - [Currently Known Issues](#currently-known-issues) @@ -44,6 +45,8 @@ Here is what is needed: signature verification](https://docs.github.com/en/github/authenticating-to-github/managing-commit-signature-verification) or follow [this tutorial](https://joeprevite.com/verify-commits-on-github) +- `quilt` + - Used to manage patches to Code - `rsync` and `unzip` - Used for code-server releases - `bats` @@ -57,7 +60,7 @@ If you're developing code-server on Linux, make sure you have installed or insta sudo apt-get install build-essential g++ libx11-dev libxkbfile-dev libsecret-1-dev python-is-python3 ``` -These are required by VS Code. See [their Wiki](https://github.com/microsoft/vscode/wiki/How-to-Contribute#prerequisites) for more information. +These are required by Code. See [their Wiki](https://github.com/microsoft/vscode/wiki/How-to-Contribute#prerequisites) for more information. ## Creating pull requests @@ -78,26 +81,44 @@ we'll guide you. ## Development workflow -The current development workflow is a bit tricky because we have this repo and we use our `coder/vscode` fork inside it with [`yarn link`](https://classic.yarnpkg.com/lang/en/docs/cli/link/). - -Here are these steps you should follow to get your dev environment setup: - 1. `git clone https://github.com/coder/code-server.git` - Clone `code-server` 2. `git submodule update --init` - Clone `vscode` submodule 3. `yarn` - Install dependencies -4. `yarn watch` - This will spin up code-server on localhost:8080 which you can start developing. It will live reload changes to the source. +4. `quilt patch -a` - Apply patches to the `vscode` submodule. +5. `yarn watch` - Launch code-server localhost:8080. code-server will be live + reloaded when changes are made; the browser needs to be refreshed manually. -### Updates to VS Code +When pulling down changes that include modifications to the patches you will +need to apply them with `quilt`. If you pull down changes that update the +`vscode` submodule you will need to run `git submodule update --init` and +re-apply the patches. -If changes are made and merged into `main` in the [`coder/vscode`](https://github.com/coder/vscode) repo, then you'll need to update the version in the `code-server` repo by following these steps: +### Version updates to Code -1. Update the `lib/vscode` submodule to the latest `main`. +1. Update the `lib/vscode` submodule to the desired upstream version branch. 2. From the code-server **project root**, run `yarn install`. -3. Test code-server locally to make sure everything works. -4. Check the Node.js version that's used by Electron (which is shipped with VS +3. Apply the patches (`quilt push -a`) or restore your stashed changes. At this + stage you may need to resolve conflicts. For example use `quilt push -f`, + manually apply the rejected portions, then `quilt refresh`. +4. Test code-server locally to make sure everything works. +5. Check the Node.js version that's used by Electron (which is shipped with VS Code. If necessary, update your version of Node.js to match. -5. Commit the updated submodule to `code-server`. -6. Open a PR. +6. Commit the updated submodule and patches to `code-server`. +7. Open a PR. + +### Patching Code + +0. You can go through the patch stack with `quilt push` and `quilt pop`. +1. Create a new patch (`quilt new {name}.diff`) or use an existing patch. +2. Add the file(s) you are patching (`quilt add [-P patch] {file}`). A file + **must** be added before you make changes to it. +3. Make your changes. Patches do not need to be independent of each other but + each patch must result in a working code-server without any broken in-between + states otherwise they are difficult to test and modify. +4. Add your changes to the patch (`quilt refresh`) +5. Add a comment in the patch about the reason for the patch and how to + reproduce the behavior it fixes or adds. Every patch should have an e2e test + as well. ### Build @@ -193,99 +214,46 @@ code-server running locally. In CI, this is taken care of for you. ## Structure -The `code-server` script serves as an HTTP API for login and starting a remote VS +The `code-server` script serves as an HTTP API for login and starting a remote Code process. The CLI code is in [src/node](../src/node) and the HTTP routes are implemented in [src/node/routes](../src/node/routes). -Most of the meaty parts are in the VS Code portion of the codebase under +Most of the meaty parts are in the Code portion of the codebase under [lib/vscode](../lib/vscode), which we describe next. -### Modifications to VS Code - -In v1 of code-server, we had a patch of VS Code that split the codebase into a -front-end and a server. The front-end consisted of the UI code, while the server -ran the extensions and exposed an API to the front-end for file access and all -UI needs. - -Over time, Microsoft added support to VS Code to run it on the web. They have -made the front-end open source, but not the server. As such, code-server v2 (and -later) uses the VS Code front-end and implements the server. We do this by using -a Git subtree to fork and modify VS Code. This code lives under -[lib/vscode](../lib/vscode). - -Some noteworthy changes in our version of VS Code include: - -- Adding our build file, [`lib/vscode/coder.js`](../lib/vscode/coder.js), which includes build steps specific to code-server -- Node.js version detection changes in [`build/lib/node.ts`](../lib/vscode/build/lib/node.ts) and [`build/lib/util.ts`](../lib/vscode/build/lib/util.ts) -- Allowing extra extension directories - - Added extra arguments to [`src/vs/platform/environment/common/argv.ts`](../lib/vscode/src/vs/platform/environment/common/argv.ts) and to [`src/vs/platform/environment/node/argv.ts`](../lib/vscode/src/vs/platform/environment/node/argv.ts) - - Added extra environment state to [`src/vs/platform/environment/common/environment.ts`](../lib/vscode/src/vs/platform/environment/common/environment.ts); - - Added extra getters to [`src/vs/platform/environment/common/environmentService.ts`](../lib/vscode/src/vs/platform/environment/common/environmentService.ts) - - Added extra scanning paths to [`src/vs/platform/extensionManagement/node/extensionsScanner.ts`](../lib/vscode/src/vs/platform/extensionManagement/node/extensionsScanner.ts) -- Additions/removals from [`package.json`](../lib/vscode/package.json): - - Removing `electron`, `keytar` and `native-keymap` to avoid pulling in desktop dependencies during build on Linux - - Removing `gulp-azure-storage` and `gulp-tar` (unsued in our build process, may pull in outdated dependencies) - - Adding `proxy-agent`, `proxy-from-env` (for proxying) and `rimraf` (used during build/install steps) -- Adding our branding/custom URLs/version: - - [`product.json`](../lib/vscode/product.json) - - [`src/vs/base/common/product.ts`](../lib/vscode/src/vs/base/common/product.ts) - - [`src/vs/workbench/browser/parts/dialogs/dialogHandler.ts`](../lib/vscode/src/vs/workbench/browser/parts/dialogs/dialogHandler.ts) - - [`src/vs/workbench/contrib/welcome/page/browser/vs_code_welcome_page.ts`](../lib/vscode/src/vs/workbench/contrib/welcome/page/browser/vs_code_welcome_page.ts) - - [`src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts`](../lib/vscode/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts) -- Removing azure/macOS signing related dependencies from [`build/package.json`](../lib/vscode/build/package.json) -- Modifying `.gitignore` to allow us to add files to `src/vs/server` and modifying `.eslintignore` to ignore lint on the shared files below (we use different formatter settings than VS Code). -- Sharing some files with our codebase via symlinks: - - [`src/vs/base/common/ipc.d.ts`](../lib/vscode/src/vs/base/common/ipc.d.ts) points to [`typings/ipc.d.ts`](../typings/ipc.d.ts) - - [`src/vs/base/common/util.ts`](../lib/vscode/src/vs/base/common/util.ts) points to [`src/common/util.ts`](../src/common/util.ts) - - [`src/vs/base/node/proxy_agent.ts`](../lib/vscode/src/vs/base/node/proxy_agent.ts) points to [`src/node/proxy_agent.ts`](../src/node/proxy_agent.ts) -- Allowing socket changes by adding `setSocket` in [`src/vs/base/parts/ipc/common/ipc.net.ts`](../lib/vscode/src/vs/base/parts/ipc/common/ipc.net.ts) - - We use this for connection persistence in our server-side code. -- Added our server-side Node.JS code to `src/vs/server`. - - This code includes the logic to spawn the various services (extension host, terminal, etc.) and some glue -- Added [`src/vs/workbench/browser/client.ts`](../lib/vscode/src/vs/workbench/browser/client.ts) to hold some server customizations. - - Includes the functionality for the Log Out command and menu item - - Also, imported and called `initialize` from the main web file, [`src/vs/workbench/browser/web.main.ts`](../lib/vscode/src/vs/workbench/browser/web.main.ts) -- Added a (hopefully temporary) hotfix to [`src/vs/workbench/common/resources.ts`](../lib/vscode/src/vs/workbench/common/resources.ts) to get context menu actions working for the Git integration. -- Added connection type to WebSocket query parameters in [`src/vs/platform/remote/common/remoteAgentConnection.ts`](../lib/vscode/src/vs/platform/remote/common/remoteAgentConnection.ts) -- Added `CODE_SERVER*` variables to the sanitization list in [`src/vs/base/common/processes.ts`](../lib/vscode/src/vs/base/common/processes.ts) -- Fix localization support: - - Added file [`src/vs/workbench/services/localizations/browser/localizationsService.ts`](../lib/vscode/src/vs/workbench/services/localizations/browser/localizationsService.ts). - - Modified file [`src/vs/base/common/platform.ts`](../lib/vscode/src/vs/base/common/platform.ts) - - Modified file [`src/vs/base/node/languagePacks.js`](../lib/vscode/src/vs/base/node/languagePacks.js) -- Added code to allow server to inject settings to [`src/vs/platform/product/common/product.ts`](../lib/vscode/src/vs/platform/product/common/product.ts) -- Extension fixes: - - Avoid disabling extensions by extensionKind in [`src/vs/workbench/services/extensionManagement/browser/extensionEnablementService.ts`](../lib/vscode/src/vs/workbench/services/extensionManagement/browser/extensionEnablementService.ts) (Needed for vscode-icons) - - Remove broken symlinks in [`extensions/postinstall.js`](../lib/vscode/extensions/postinstall.js) - - Add tip about extension gallery in [`src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts`](../lib/vscode/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts) - - Use our own server for GitHub authentication in [`extensions/github-authentication/src/githubServer.ts`](../lib/vscode/extensions/github-authentication/src/githubServer.ts) - - Settings persistence on the server in [`src/vs/workbench/services/environment/browser/environmentService.ts`](../lib/vscode/src/vs/workbench/services/environment/browser/environmentService.ts) - - Add extension install fallback in [`src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts`](../lib/vscode/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts) - - Add proxy-agent monkeypatch and keep extension host indefinitely running in [`src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts`](../lib/vscode/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts) - - Patch build system to avoid removing extension dependencies for `yarn global add` users in [`build/lib/extensions.ts`](../lib/vscode/build/lib/extensions.ts) - - Allow all extensions to use proposed APIs in [`src/vs/workbench/services/environment/browser/environmentService.ts`](../lib/vscode/src/vs/workbench/services/environment/browser/environmentService.ts) - - Make storage writes async to allow extensions to wait for them to complete in [`src/vs/platform/storage/common/storage.ts`](../lib/vscode/src/vs/platform/storage/common/storage.ts) -- Specify webview path in [`src/vs/code/browser/workbench/workbench.ts`](../lib/vscode/src/vs/code/browser/workbench/workbench.ts) -- URL readability improvements for folder/workspace in [`src/vs/code/browser/workbench/workbench.ts`](../lib/vscode/src/vs/code/browser/workbench/workbench.ts) -- Socket/Authority-related fixes (for remote proxying etc.): - - [`src/vs/code/browser/workbench/workbench.ts`](../lib/vscode/src/vs/code/browser/workbench/workbench.ts) - - [`src/vs/platform/remote/browser/browserSocketFactory.ts`](../lib/vscode/src/vs/platform/remote/browser/browserSocketFactory.ts) - - [`src/vs/base/common/network.ts`](../lib/vscode/src/vs/base/common/network.ts) -- Added code to write out IPC path in [`src/vs/workbench/api/node/extHostCLIServer.ts`](../lib/vscode/src/vs/workbench/api/node/extHostCLIServer.ts) - -As the web portion of VS Code matures, we'll be able to shrink and possibly -eliminate our modifications. In the meantime, upgrading the VS Code version requires -us to ensure that our changes are still applied and work as intended. In the future, -we'd like to run VS Code unit tests against our builds to ensure that features -work as expected. +### Modifications to Code + +Our modifications to Code can be found in the [patches](../patches) directory. +We pull in Code as a submodule pointing to an upstream release branch. + +In v1 of code-server, we had Code as a submodule and used a single massive patch +that split the codebase into a front-end and a server. The front-end consisted +of the UI code, while the server ran the extensions and exposed an API to the +front-end for file access and all UI needs. + +Over time, Microsoft added support to Code to run it on the web. They had made +the front-end open source, but not the server. As such, code-server v2 (and +later) uses the Code front-end and implements the server. We did this by using a +Git subtree to fork and modify Code. + +Microsoft eventually made the server open source and we were able to reduce our +changes significantly. Some time later we moved back to a submodule and patches +(managed by `quilt` this time instead of the mega-patch). + +As the web portion of Code continues to mature, we'll be able to shrink and +possibly eliminate our patches. In the meantime, upgrading the Code version +requires us to ensure that our changes are still applied correctly and work as +intended. In the future, we'd like to run Code unit tests against our builds to +ensure that features work as expected. > We have [extension docs](../ci/README.md) on the CI and build system. -If the functionality you're working on does NOT depend on code from VS Code, please +If the functionality you're working on does NOT depend on code from Code, please move it out and into code-server. ### Currently Known Issues -- Creating custom VS Code extensions and debugging them doesn't work +- Creating custom Code extensions and debugging them doesn't work - Extension profiling and tips are currently disabled diff --git a/lib/vscode b/lib/vscode index a13f6e1434ad..f80445acd5a3 160000 --- a/lib/vscode +++ b/lib/vscode @@ -1 +1 @@ -Subproject commit a13f6e1434ad6ab820eef0ecca5b923b3e275667 +Subproject commit f80445acd5a3dadef24aa209168452a3d97cc326 diff --git a/patches/base-path.diff b/patches/base-path.diff new file mode 100644 index 000000000000..715f8a2150a9 --- /dev/null +++ b/patches/base-path.diff @@ -0,0 +1,305 @@ +Add base path support + +Some users will host code-server behind a path-rewriting reverse proxy, for +example domain.tld/my/base/path. This patch adds support for that since Code +assumes everything is on / by default. + +To test this serve code-server behind a reverse proxy with a path like /code. + +Index: code-server/lib/vscode/src/vs/base/common/network.ts +=================================================================== +--- code-server.orig/lib/vscode/src/vs/base/common/network.ts ++++ code-server/lib/vscode/src/vs/base/common/network.ts +@@ -151,8 +151,10 @@ class RemoteAuthoritiesImpl { + } + return URI.from({ + scheme: platform.isWeb ? this._preferredWebSchema : Schemas.vscodeRemoteResource, +- authority: `${host}:${port}`, +- path: `/vscode-remote-resource`, ++ authority: platform.isWeb ? window.location.host : `${host}:${port}`, ++ path: platform.isWeb ++ ? URI.joinPath(URI.parse(window.location.href), `/vscode-remote-resource`).path ++ : `/vscode-remote-resource`, + query + }); + } +Index: code-server/lib/vscode/src/vs/code/browser/workbench/workbench-dev.html +=================================================================== +--- code-server.orig/lib/vscode/src/vs/code/browser/workbench/workbench-dev.html ++++ code-server/lib/vscode/src/vs/code/browser/workbench/workbench-dev.html +@@ -11,8 +11,8 @@ + + + +- +- ++ ++ + + + +@@ -27,23 +27,26 @@ + + + +- +- +- ++ ++ ++ + + + + + + +- +- ++ ++ + +- ++ ++ + +- +- +- ++ ++ ++ + +Index: code-server/lib/vscode/src/vs/platform/remote/browser/browserSocketFactory.ts +=================================================================== +--- code-server.orig/lib/vscode/src/vs/platform/remote/browser/browserSocketFactory.ts ++++ code-server/lib/vscode/src/vs/platform/remote/browser/browserSocketFactory.ts +@@ -274,7 +274,7 @@ export class BrowserSocketFactory implem + + connect(host: string, port: number, query: string, debugLabel: string, callback: IConnectCallback): void { + const webSocketSchema = (/^https:/.test(window.location.href) ? 'wss' : 'ws'); +- const socket = this._webSocketFactory.create(`${webSocketSchema}://${/:/.test(host) ? `[${host}]` : host}:${port}/?${query}&skipWebSocketFrames=false`, debugLabel); ++ const socket = this._webSocketFactory.create(`${webSocketSchema}://${window.location.host}${window.location.pathname}?${query}&skipWebSocketFrames=false`, debugLabel); + const errorListener = socket.onError((err) => callback(err, undefined)); + socket.onOpen(() => { + errorListener.dispose(); +@@ -282,6 +282,3 @@ export class BrowserSocketFactory implem + }); + } + } +- +- +- +Index: code-server/lib/vscode/src/vs/server/node/webClientServer.ts +=================================================================== +--- code-server.orig/lib/vscode/src/vs/server/node/webClientServer.ts ++++ code-server/lib/vscode/src/vs/server/node/webClientServer.ts +@@ -252,7 +252,10 @@ export class WebClientServer { + return res.end(); + } + +- const remoteAuthority = req.headers.host; ++ // It is not possible to reliably detect the remote authority on the server ++ // in all cases. Set this to something invalid to make sure we catch code ++ // that is using this when it should not. ++ const remoteAuthority = 'remote'; + + function escapeAttribute(value: string): string { + return value.replace(/"/g, '"'); +@@ -272,6 +275,8 @@ export class WebClientServer { + accessToken: this._environmentService.args['github-auth'], + scopes: [['user:email'], ['repo']] + } : undefined; ++ const base = relativeRoot(getOriginalUrl(req)) ++ const vscodeBase = relativePath(getOriginalUrl(req)) + const data = (await util.promisify(fs.readFile)(filePath)).toString() + .replace('{{WORKBENCH_WEB_CONFIGURATION}}', escapeAttribute(JSON.stringify({ + remoteAuthority, +@@ -279,6 +284,7 @@ export class WebClientServer { + developmentOptions: { enableSmokeTestDriver: this._environmentService.driverHandle === 'web' ? true : undefined }, + settingsSyncOptions: !this._environmentService.isBuilt && this._environmentService.args['enable-sync'] ? { enabled: true } : undefined, + productConfiguration: >{ ++ rootEndpoint: base, + extensionsGallery: this._webExtensionResourceUrlTemplate ? { + ...this._productService.extensionsGallery, + 'resourceUrlTemplate': this._webExtensionResourceUrlTemplate.with({ +@@ -289,7 +295,9 @@ export class WebClientServer { + } : undefined + } + }))) +- .replace('{{WORKBENCH_AUTH_SESSION}}', () => authSessionInfo ? escapeAttribute(JSON.stringify(authSessionInfo)) : ''); ++ .replace('{{WORKBENCH_AUTH_SESSION}}', () => authSessionInfo ? escapeAttribute(JSON.stringify(authSessionInfo)) : '') ++ .replace(/{{BASE}}/g, base) ++ .replace(/{{VS_BASE}}/g, vscodeBase); + + const cspDirectives = [ + 'default-src \'self\';', +@@ -368,3 +376,70 @@ export class WebClientServer { + return res.end(data); + } + } ++ ++/** ++ * Remove extra slashes in a URL. ++ * ++ * This is meant to fill the job of `path.join` so you can concatenate paths and ++ * then normalize out any extra slashes. ++ * ++ * If you are using `path.join` you do not need this but note that `path` is for ++ * file system paths, not URLs. ++ */ ++export const normalizeUrlPath = (url: string, keepTrailing = false): string => { ++ return url.replace(/\/\/+/g, "/").replace(/\/+$/, keepTrailing ? "/" : "") ++} ++ ++/** ++ * Get the relative path that will get us to the root of the page. For each ++ * slash we need to go up a directory. Will not have a trailing slash. ++ * ++ * For example: ++ * ++ * / => . ++ * /foo => . ++ * /foo/ => ./.. ++ * /foo/bar => ./.. ++ * /foo/bar/ => ./../.. ++ * ++ * All paths must be relative in order to work behind a reverse proxy since we ++ * we do not know the base path. Anything that needs to be absolute (for ++ * example cookies) must get the base path from the frontend. ++ * ++ * All relative paths must be prefixed with the relative root to ensure they ++ * work no matter the depth at which they happen to appear. ++ * ++ * For Express `req.originalUrl` should be used as they remove the base from the ++ * standard `url` property making it impossible to get the true depth. ++ */ ++export const relativeRoot = (originalUrl: string): string => { ++ const depth = (originalUrl.split("?", 1)[0].match(/\//g) || []).length ++ return normalizeUrlPath("./" + (depth > 1 ? "../".repeat(depth - 1) : "")) ++} ++ ++/** ++ * Get the relative path to the current resource. ++ * ++ * For example: ++ * ++ * / => . ++ * /foo => ./foo ++ * /foo/ => . ++ * /foo/bar => ./bar ++ * /foo/bar/ => . ++ */ ++export const relativePath = (originalUrl: string): string => { ++ const parts = originalUrl.split("?", 1)[0].split("/") ++ return normalizeUrlPath("./" + parts[parts.length - 1]) ++} ++ ++/** ++ * code-server serves Code using Express. Express removes the base from the url ++ * and puts the original in `originalUrl` so we must use this to get the correct ++ * depth. Code is not aware it is behind Express so the types do not match. We ++ * may want to continue moving code into Code and eventually remove the Express ++ * wrapper or move the web server back into code-server. ++ */ ++export const getOriginalUrl = (req: http.IncomingMessage): string => { ++ return (req as any).originalUrl || req.url ++} +Index: code-server/lib/vscode/src/vs/base/common/product.ts +=================================================================== +--- code-server.orig/lib/vscode/src/vs/base/common/product.ts ++++ code-server/lib/vscode/src/vs/base/common/product.ts +@@ -32,6 +32,7 @@ export type ExtensionVirtualWorkspaceSup + + export interface IProductConfiguration { + readonly codeServerVersion?: string ++ readonly rootEndpoint?: string + + readonly version: string; + readonly date?: string; +Index: code-server/lib/vscode/src/vs/code/browser/workbench/workbench.ts +=================================================================== +--- code-server.orig/lib/vscode/src/vs/code/browser/workbench/workbench.ts ++++ code-server/lib/vscode/src/vs/code/browser/workbench/workbench.ts +@@ -504,6 +504,7 @@ function doCreateUri(path: string, query + }); + } + ++ path = (window.location.pathname + "/" + path).replace(/\/\/+/g, "/") + return URI.parse(window.location.href).with({ path, query }); + } + +@@ -515,7 +516,7 @@ function doCreateUri(path: string, query + if (!configElement || !configElementAttribute) { + throw new Error('Missing web configuration element'); + } +- const config: IWorkbenchConstructionOptions & { folderUri?: UriComponents, workspaceUri?: UriComponents } = JSON.parse(configElementAttribute); ++ const config: IWorkbenchConstructionOptions & { folderUri?: UriComponents, workspaceUri?: UriComponents } = { ...JSON.parse(configElementAttribute), remoteAuthority: location.host } + + // Create workbench + create(document.body, { diff --git a/patches/connection-type.diff b/patches/connection-type.diff new file mode 100644 index 000000000000..9f04bc29918d --- /dev/null +++ b/patches/connection-type.diff @@ -0,0 +1,19 @@ +Add connection type to web sockets + +This allows the backend to distinguish them. In our case we use them to count a +single "open" of Code so we need to be able to distinguish between web sockets +from two instances and two web sockets used in a single instance. + +Index: code-server/lib/vscode/src/vs/platform/remote/common/remoteAgentConnection.ts +=================================================================== +--- code-server.orig/lib/vscode/src/vs/platform/remote/common/remoteAgentConnection.ts ++++ code-server/lib/vscode/src/vs/platform/remote/common/remoteAgentConnection.ts +@@ -231,7 +231,7 @@ async function connectToRemoteExtensionH + + let socket: ISocket; + try { +- socket = await createSocket(options.logService, options.socketFactory, options.host, options.port, `reconnectionToken=${options.reconnectionToken}&reconnection=${options.reconnectionProtocol ? 'true' : 'false'}`, `renderer-${connectionTypeToString(connectionType)}-${options.reconnectionToken}`, timeoutCancellationToken); ++ socket = await createSocket(options.logService, options.socketFactory, options.host, options.port, `type=${connectionTypeToString(connectionType)}&reconnectionToken=${options.reconnectionToken}&reconnection=${options.reconnectionProtocol ? 'true' : 'false'}`, `renderer-${connectionTypeToString(connectionType)}-${options.reconnectionToken}`, timeoutCancellationToken); + } catch (error) { + options.logService.error(`${logPrefix} socketFactory.connect() failed or timed out. Error:`); + options.logService.error(error); diff --git a/patches/display-language.diff b/patches/display-language.diff new file mode 100644 index 000000000000..d5ddfa785a52 --- /dev/null +++ b/patches/display-language.diff @@ -0,0 +1,265 @@ +Add display language support + +This likely needs tweaking if we want to upstream. + +Index: code-server/lib/vscode/src/vs/server/node/serverServices.ts +=================================================================== +--- code-server.orig/lib/vscode/src/vs/server/node/serverServices.ts ++++ code-server/lib/vscode/src/vs/server/node/serverServices.ts +@@ -198,6 +198,9 @@ export async function setupServerService + const channel = new ExtensionManagementChannel(extensionManagementService, (ctx: RemoteAgentConnectionContext) => getUriTransformer(ctx.remoteAuthority)); + socketServer.registerChannel('extensions', channel); + ++ const localizationsChannel = ProxyChannel.fromService(accessor.get(ILocalizationsService)); ++ socketServer.registerChannel('localizations', localizationsChannel); ++ + const encryptionChannel = ProxyChannel.fromService(accessor.get(IEncryptionMainService)); + socketServer.registerChannel('encryption', encryptionChannel); + +Index: code-server/lib/vscode/src/vs/base/common/platform.ts +=================================================================== +--- code-server.orig/lib/vscode/src/vs/base/common/platform.ts ++++ code-server/lib/vscode/src/vs/base/common/platform.ts +@@ -83,6 +83,17 @@ if (typeof navigator === 'object' && !is + _isWeb = true; + _locale = navigator.language; + _language = _locale; ++ ++ const el = typeof document !== 'undefined' && document.getElementById('vscode-remote-nls-configuration'); ++ const rawNlsConfig = el && el.getAttribute('data-settings'); ++ if (rawNlsConfig) { ++ try { ++ const nlsConfig: NLSConfig = JSON.parse(rawNlsConfig); ++ _locale = nlsConfig.locale; ++ _translationsConfigFile = nlsConfig._translationsConfigFile; ++ _language = nlsConfig.availableLanguages['*'] || LANGUAGE_DEFAULT; ++ } catch (error) { /* Oh well. */ } ++ } + } + + // Native environment +Index: code-server/lib/vscode/src/vs/code/browser/workbench/workbench.html +=================================================================== +--- code-server.orig/lib/vscode/src/vs/code/browser/workbench/workbench.html ++++ code-server/lib/vscode/src/vs/code/browser/workbench/workbench.html +@@ -23,6 +23,9 @@ + + + ++ ++ ++ + + + +@@ -38,6 +41,27 @@ + + + +