From 1c8eede1aa3f8110f9f63695bac462236f46f781 Mon Sep 17 00:00:00 2001 From: Asher Date: Tue, 28 Jul 2020 15:06:10 -0500 Subject: [PATCH 01/10] Add missing types to release code-server exports its types but they weren't complete since it imports ipc.d.ts and that wasn't being included. --- ci/build/build-release.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ci/build/build-release.sh b/ci/build/build-release.sh index 6ef0d4d5..d1b99908 100755 --- a/ci/build/build-release.sh +++ b/ci/build/build-release.sh @@ -21,6 +21,10 @@ main() { rsync README.md "$RELEASE_PATH" rsync LICENSE.txt "$RELEASE_PATH" rsync ./lib/vscode/ThirdPartyNotices.txt "$RELEASE_PATH" + + # Keep these types since code-server's exported types use them. + mkdir -p "$RELEASE_PATH/lib/vscode/src/vs/server" + rsync ./lib/vscode/src/vs/server/ipc.d.ts "$RELEASE_PATH/lib/vscode/src/vs/server" } bundle_code_server() { From bac948ea6f8232fec6d3afac9c600289056fb751 Mon Sep 17 00:00:00 2001 From: Asher Date: Tue, 28 Jul 2020 15:06:15 -0500 Subject: [PATCH 02/10] Add plugin system --- .gitignore | 1 + ci/dev/vscode.patch | 5 ++-- src/node/app/vscode.ts | 2 -- src/node/entry.ts | 5 +++- src/node/http.ts | 24 +++++++------------ src/node/plugin.ts | 52 ++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 67 insertions(+), 22 deletions(-) create mode 100644 src/node/plugin.ts diff --git a/.gitignore b/.gitignore index 424cb9e7..616f9b01 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ release-gcp/ release-images/ node_modules node-* +/plugins diff --git a/ci/dev/vscode.patch b/ci/dev/vscode.patch index 03b9c5f6..9e46e2ec 100644 --- a/ci/dev/vscode.patch +++ b/ci/dev/vscode.patch @@ -1306,17 +1306,16 @@ index 0000000000..56331ff1fc +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 0000000000..0a9c95d50e +index 0000000000..5cc3e1f0f4 --- /dev/null +++ b/src/vs/server/ipc.d.ts -@@ -0,0 +1,117 @@ +@@ -0,0 +1,116 @@ +/** + * External interfaces for integration into code-server over IPC. No vs imports + * should be made in this file. + */ +export interface Options { + base: string -+ commit: string + disableTelemetry: boolean +} + diff --git a/src/node/app/vscode.ts b/src/node/app/vscode.ts index 0de9cb3e..ed4f714e 100644 --- a/src/node/app/vscode.ts +++ b/src/node/app/vscode.ts @@ -200,8 +200,6 @@ export class VscodeHttpProvider extends HttpProvider { .replace(`"{{WORKBENCH_WEB_CONFIGURATION}}"`, `'${JSON.stringify(options.workbenchWebConfiguration)}'`) .replace(`"{{NLS_CONFIGURATION}}"`, `'${JSON.stringify(options.nlsConfiguration)}'`) return this.replaceTemplates(route, response, { - base: this.base(route), - commit: this.options.commit, disableTelemetry: !!this.args["disable-telemetry"], }) } diff --git a/src/node/entry.ts b/src/node/entry.ts index a030cb49..4514b589 100644 --- a/src/node/entry.ts +++ b/src/node/entry.ts @@ -9,7 +9,8 @@ import { UpdateHttpProvider } from "./app/update" import { VscodeHttpProvider } from "./app/vscode" import { Args, bindAddrFromAllSources, optionDescriptions, parse, readConfigFile, setDefaults } from "./cli" import { AuthType, HttpServer, HttpServerOptions } from "./http" -import { generateCertificate, hash, open, humanPath } from "./util" +import { loadPlugins } from "./plugin" +import { generateCertificate, hash, humanPath, open } from "./util" import { ipcMain, wrap } from "./wrapper" process.on("uncaughtException", (error) => { @@ -77,6 +78,8 @@ const main = async (args: Args, cliArgs: Args, configArgs: Args): Promise httpServer.registerHttpProvider("/login", LoginHttpProvider, args.config!, envPassword) httpServer.registerHttpProvider("/static", StaticHttpProvider) + await loadPlugins(httpServer, args) + ipcMain().onDispose(() => { httpServer.dispose().then((errors) => { errors.forEach((error) => logger.error(error.message)) diff --git a/src/node/http.ts b/src/node/http.ts index 216ff5a2..d3e8e44a 100644 --- a/src/node/http.ts +++ b/src/node/http.ts @@ -235,30 +235,22 @@ export abstract class HttpProvider { /** * Replace common templates strings. */ - protected replaceTemplates(route: Route, response: HttpStringFileResponse, sessionId?: string): HttpStringFileResponse protected replaceTemplates( route: Route, response: HttpStringFileResponse, - options: T, - ): HttpStringFileResponse - protected replaceTemplates( - route: Route, - response: HttpStringFileResponse, - sessionIdOrOptions?: string | object, + extraOptions?: Omit, ): HttpStringFileResponse { - if (typeof sessionIdOrOptions === "undefined" || typeof sessionIdOrOptions === "string") { - sessionIdOrOptions = { - base: this.base(route), - commit: this.options.commit, - logLevel: logger.level, - sessionID: sessionIdOrOptions, - } as Options + const options: Options = { + base: this.base(route), + commit: this.options.commit, + logLevel: logger.level, + ...extraOptions, } response.content = response.content .replace(/{{COMMIT}}/g, this.options.commit) .replace(/{{TO}}/g, Array.isArray(route.query.to) ? route.query.to[0] : route.query.to || "/dashboard") .replace(/{{BASE}}/g, this.base(route)) - .replace(/"{{OPTIONS}}"/, `'${JSON.stringify(sessionIdOrOptions)}'`) + .replace(/"{{OPTIONS}}"/, `'${JSON.stringify(options)}'`) return response } @@ -664,7 +656,7 @@ export class HttpServer { e = new HttpError("Not found", HttpCode.NotFound) } const code = typeof e.code === "number" ? e.code : HttpCode.ServerError - logger.debug("Request error", field("url", request.url), field("code", code)) + logger.debug("Request error", field("url", request.url), field("code", code), field("error", error)) if (code >= HttpCode.ServerError) { logger.error(error.stack) } diff --git a/src/node/plugin.ts b/src/node/plugin.ts new file mode 100644 index 00000000..0e024be0 --- /dev/null +++ b/src/node/plugin.ts @@ -0,0 +1,52 @@ +import { field, logger } from "@coder/logger" +import * as fs from "fs" +import * as path from "path" +import * as util from "util" +import { Args } from "./cli" +import { HttpServer } from "./http" + +/* eslint-disable @typescript-eslint/no-var-requires */ + +export type Activate = (httpServer: HttpServer, args: Args) => void + +export interface Plugin { + activate: Activate +} + +const originalLoad = require("module")._load +// eslint-disable-next-line @typescript-eslint/no-explicit-any +require("module")._load = function (request: string, parent: object, isMain: boolean): any { + return originalLoad.apply(this, [request.replace(/^code-server/, path.resolve(__dirname, "../..")), parent, isMain]) +} + +const loadPlugin = async (pluginPath: string, httpServer: HttpServer, args: Args): Promise => { + try { + const plugin: Plugin = require(pluginPath) + plugin.activate(httpServer, args) + logger.debug("Loaded plugin", field("name", path.basename(pluginPath))) + } catch (error) { + if (error.code !== "MODULE_NOT_FOUND") { + logger.warn(error.message) + } else { + logger.debug(error.message) + } + } +} + +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 => { + try { + await _loadPlugins(httpServer, args) + } catch (error) { + if (error.code !== "ENOENT") { + logger.warn(error.message) + } + } +} From 361e7103ea3c6ffa8881c3a166ce391f129b9e04 Mon Sep 17 00:00:00 2001 From: Asher Date: Wed, 29 Jul 2020 15:02:14 -0500 Subject: [PATCH 03/10] Enable loading external plugins --- ci/build/build-code-server.sh | 2 +- ci/dev/vscode.patch | 32 +++++----------------- ci/dev/watch.ts | 2 +- src/browser/media/manifest.json | 12 ++++----- src/browser/pages/error.html | 14 ++++------ src/browser/pages/login.html | 14 ++++------ src/browser/pages/vscode.html | 48 ++++++++++++++------------------- src/browser/register.ts | 2 +- src/common/util.ts | 41 ++++++++++++++-------------- src/node/app/static.ts | 23 ++++++++++------ src/node/http.ts | 19 ++++++------- src/node/plugin.ts | 4 +++ 12 files changed, 95 insertions(+), 118 deletions(-) diff --git a/ci/build/build-code-server.sh b/ci/build/build-code-server.sh index df528874..f0e4ddc3 100755 --- a/ci/build/build-code-server.sh +++ b/ci/build/build-code-server.sh @@ -18,7 +18,7 @@ main() { fi parcel build \ - --public-url "/static/$(git rev-parse HEAD)/dist" \ + --public-url "." \ --out-dir dist \ $([[ $MINIFY ]] || echo --no-minify) \ src/browser/register.ts \ diff --git a/ci/dev/vscode.patch b/ci/dev/vscode.patch index 9e46e2ec..cb7ee829 100644 --- a/ci/dev/vscode.patch +++ b/ci/dev/vscode.patch @@ -722,10 +722,10 @@ index eab8591492..26668701f7 100644 options.logService.error(`${logPrefix} socketFactory.connect() failed. Error:`); diff --git a/src/vs/server/browser/client.ts b/src/vs/server/browser/client.ts new file mode 100644 -index 0000000000..8fb2a87303 +index 0000000000..3c0703b717 --- /dev/null +++ b/src/vs/server/browser/client.ts -@@ -0,0 +1,208 @@ +@@ -0,0 +1,189 @@ +import { Emitter } from 'vs/base/common/event'; +import { URI } from 'vs/base/common/uri'; +import { localize } from 'vs/nls'; @@ -761,31 +761,12 @@ index 0000000000..8fb2a87303 +}; + +/** -+ * Get options embedded in the HTML from the server. ++ * Get options embedded in the HTML. + */ +export const getOptions = (): T => { -+ if (typeof document === "undefined") { -+ return {} as T; -+ } -+ const el = document.getElementById("coder-options"); + try { -+ if (!el) { -+ throw new Error("no options element"); -+ } -+ const value = el.getAttribute("data-settings"); -+ if (!value) { -+ throw new Error("no options value"); -+ } -+ const options = JSON.parse(value); -+ const parts = window.location.pathname.replace(/^\//g, "").split("/"); -+ parts[parts.length - 1] = options.base; -+ const url = new URL(window.location.origin + "/" + parts.join("/")); -+ return { -+ ...options, -+ base: normalize(url.pathname, true), -+ }; ++ return JSON.parse(document.getElementById("coder-options")!.getAttribute("data-settings")!); + } catch (error) { -+ console.warn(error); + return {} as T; + } +}; @@ -1306,16 +1287,15 @@ index 0000000000..56331ff1fc +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 0000000000..5cc3e1f0f4 +index 0000000000..7e1cd270c8 --- /dev/null +++ b/src/vs/server/ipc.d.ts -@@ -0,0 +1,116 @@ +@@ -0,0 +1,115 @@ +/** + * External interfaces for integration into code-server over IPC. No vs imports + * should be made in this file. + */ +export interface Options { -+ base: string + disableTelemetry: boolean +} + diff --git a/ci/dev/watch.ts b/ci/dev/watch.ts index fd144653..8a7372d0 100644 --- a/ci/dev/watch.ts +++ b/ci/dev/watch.ts @@ -150,7 +150,7 @@ class Watcher { cacheDir: path.join(this.rootPath, ".cache"), minify: !!process.env.MINIFY, logLevel: 1, - publicUrl: "/static/development/dist", + publicUrl: ".", }, ) } diff --git a/src/browser/media/manifest.json b/src/browser/media/manifest.json index 76ec15ce..b33be207 100644 --- a/src/browser/media/manifest.json +++ b/src/browser/media/manifest.json @@ -7,32 +7,32 @@ "description": "Run editors on a remote server.", "icons": [ { - "src": "{{BASE}}/static/{{COMMIT}}/src/browser/media/pwa-icon-96.png", + "src": "{{CS_STATIC_BASE}}/src/browser/media/pwa-icon-96.png", "type": "image/png", "sizes": "96x96" }, { - "src": "{{BASE}}/static/{{COMMIT}}/src/browser/media/pwa-icon-128.png", + "src": "{{CS_STATIC_BASE}}/src/browser/media/pwa-icon-128.png", "type": "image/png", "sizes": "128x128" }, { - "src": "{{BASE}}/static/{{COMMIT}}/src/browser/media/pwa-icon-192.png", + "src": "{{CS_STATIC_BASE}}/src/browser/media/pwa-icon-192.png", "type": "image/png", "sizes": "192x192" }, { - "src": "{{BASE}}/static/{{COMMIT}}/src/browser/media/pwa-icon-256.png", + "src": "{{CS_STATIC_BASE}}/src/browser/media/pwa-icon-256.png", "type": "image/png", "sizes": "256x256" }, { - "src": "{{BASE}}/static/{{COMMIT}}/src/browser/media/pwa-icon-384.png", + "src": "{{CS_STATIC_BASE}}/src/browser/media/pwa-icon-384.png", "type": "image/png", "sizes": "384x384" }, { - "src": "{{BASE}}/static/{{COMMIT}}/src/browser/media/pwa-icon-512.png", + "src": "{{CS_STATIC_BASE}}/src/browser/media/pwa-icon-512.png", "type": "image/png", "sizes": "512x512" } diff --git a/src/browser/pages/error.html b/src/browser/pages/error.html index 12d6efe2..9e6dd0b9 100644 --- a/src/browser/pages/error.html +++ b/src/browser/pages/error.html @@ -11,14 +11,10 @@ content="style-src 'self'; manifest-src 'self'; img-src 'self' data:; font-src 'self' data:;" /> {{ERROR_TITLE}} - code-server - - - - + + + + @@ -33,6 +29,6 @@ - + diff --git a/src/browser/pages/login.html b/src/browser/pages/login.html index 788055d6..586c9c66 100644 --- a/src/browser/pages/login.html +++ b/src/browser/pages/login.html @@ -11,14 +11,10 @@ content="style-src 'self'; script-src 'self' 'unsafe-inline'; manifest-src 'self'; img-src 'self' data:; font-src 'self' data:;" /> code-server login - - - - + + + + @@ -50,7 +46,7 @@ - + - - + +