diff --git a/patches/integration.diff b/patches/integration.diff index b50651269..ae514489b 100644 --- a/patches/integration.diff +++ b/patches/integration.diff @@ -272,3 +272,18 @@ Index: code-server/lib/vscode/src/vs/server/node/webClientServer.ts embedderIdentifier: 'server-distro', extensionsGallery: this._webExtensionResourceUrlTemplate && this._productService.extensionsGallery ? { ...this._productService.extensionsGallery, +Index: code-server/lib/vscode/src/server-main.js +=================================================================== +--- code-server.orig/lib/vscode/src/server-main.js ++++ code-server/lib/vscode/src/server-main.js +@@ -336,4 +336,9 @@ function prompt(question) { + }); + } + +-start(); ++async function loadCodeWithNls() { ++ const nlsConfiguration = await resolveNLSConfiguration({ userLocale: 'en', osLocale: 'en', commit: product.commit, userDataPath: '', nlsMetadataPath: __dirname }); ++ return loadCode(nlsConfiguration); ++} ++ ++module.exports.loadCodeWithNls = loadCodeWithNls; diff --git a/src/node/cli.ts b/src/node/cli.ts index fb24949f2..69890279e 100644 --- a/src/node/cli.ts +++ b/src/node/cli.ts @@ -837,11 +837,6 @@ export interface CodeArgs extends UserProvidedCodeArgs { log?: string[] } -/** - * Types for ../../lib/vscode/src/vs/server/node/server.main.ts:65. - */ -export type SpawnCodeCli = (args: CodeArgs) => Promise - /** * Convert our arguments to equivalent VS Code server arguments. * Does not add any extra arguments. diff --git a/src/node/main.ts b/src/node/main.ts index 5a8b1a0af..0458f66ad 100644 --- a/src/node/main.ts +++ b/src/node/main.ts @@ -1,12 +1,14 @@ import { field, logger } from "@coder/logger" import http from "http" +import * as path from "path" import { Disposable } from "../common/emitter" import { plural } from "../common/util" import { createApp, ensureAddress } from "./app" -import { AuthType, DefaultedArgs, Feature, SpawnCodeCli, toCodeArgs, UserProvidedArgs } from "./cli" -import { commit, version } from "./constants" +import { AuthType, DefaultedArgs, Feature, toCodeArgs, UserProvidedArgs } from "./cli" +import { commit, version, vsRootPath } from "./constants" import { register } from "./routes" -import { isDirectory, loadAMDModule, open } from "./util" +import { VSCodeModule } from "./routes/vscode" +import { isDirectory, open } from "./util" /** * Return true if the user passed an extension-related VS Code flag. @@ -46,12 +48,10 @@ export interface OpenCommandPipeArgs { */ export const runCodeCli = async (args: DefaultedArgs): Promise => { logger.debug("Running Code CLI") - - // See ../../lib/vscode/src/vs/server/node/server.main.ts:65. - const spawnCli = await loadAMDModule("vs/server/node/server.main", "spawnCli") - try { - await spawnCli(await toCodeArgs(args)) + const mod = require(path.join(vsRootPath, "out/server-main")) as VSCodeModule + const serverModule = await mod.loadCodeWithNls() + await serverModule.spawnCli(await toCodeArgs(args)) // Rather than have the caller handle errors and exit, spawnCli will exit // itself. Additionally, it does this on a timeout set to 0. So, try // waiting for VS Code to exit before giving up and doing it ourselves. diff --git a/src/node/routes/vscode.ts b/src/node/routes/vscode.ts index 8893dc80e..b3f04fd6c 100644 --- a/src/node/routes/vscode.ts +++ b/src/node/routes/vscode.ts @@ -8,10 +8,10 @@ import * as path from "path" import { WebsocketRequest } from "../../../typings/pluginapi" import { logError } from "../../common/util" import { CodeArgs, toCodeArgs } from "../cli" -import { isDevMode } from "../constants" +import { isDevMode, vsRootPath } from "../constants" import { authenticated, ensureAuthenticated, ensureOrigin, redirect, replaceTemplates, self } from "../http" import { SocketProxyProvider } from "../socket" -import { isFile, loadAMDModule } from "../util" +import { isFile } from "../util" import { Router as WsRouter } from "../wsRouter" export const router = express.Router() @@ -31,11 +31,46 @@ export interface IVSCodeServerAPI { dispose(): void } -// See ../../../lib/vscode/src/vs/server/node/server.main.ts:72. -export type CreateServer = (address: string | net.AddressInfo | null, args: CodeArgs) => Promise +/** + * VS Code's CLI entrypoint (../../../lib/vscode/src/server-main.js). + * + * Normally VS Code will run `node server-main.js` which starts either the web + * server or the CLI (for installing extensions, etc) but we patch it so we can + * `require` it and call its functions directly in order to integrate with our + * web server. + */ +export type VSCodeModule = { + // See ../../../lib/vscode/src/server-main.js:339. + loadCodeWithNls(): { + // See ../../../lib/vscode/src/vs/server/node/server.main.ts:72. + createServer(address: string | net.AddressInfo | null, args: CodeArgs): Promise + // See ../../../lib/vscode/src/vs/server/node/server.main.ts:65. + spawnCli(args: CodeArgs): Promise + } +} -// The VS Code server is dynamically loaded in when a request is made to this -// router by `ensureCodeServerLoaded`. +/** + * Load then create the VS Code server. + */ +async function loadVSCode(req: express.Request): Promise { + const mod = require(path.join(vsRootPath, "out/server-main")) as VSCodeModule + const serverModule = await mod.loadCodeWithNls() + return serverModule.createServer(null, { + ...(await toCodeArgs(req.args)), + "accept-server-license-terms": true, + // This seems to be used to make the connection token flags optional (when + // set to 1.63) but we have always included them. + compatibility: "1.64", + "without-connection-token": true, + }) +} + +// To prevent loading the module more than once at a time. We also have the +// resolved value so you do not need to `await` everywhere. +let vscodeServerPromise: Promise | undefined + +// The resolved value from the dynamically loaded VS Code server. Do not use +// without first calling and awaiting `ensureCodeServerLoaded`. let vscodeServer: IVSCodeServerAPI | undefined /** @@ -49,21 +84,21 @@ export const ensureVSCodeLoaded = async ( if (vscodeServer) { return next() } - // See ../../../lib/vscode/src/vs/server/node/server.main.ts:72. - const createVSServer = await loadAMDModule("vs/server/node/server.main", "createServer") + if (!vscodeServerPromise) { + vscodeServerPromise = loadVSCode(req) + } try { - vscodeServer = await createVSServer(null, { - ...(await toCodeArgs(req.args)), - "accept-server-license-terms": true, - // This seems to be used to make the connection token flags optional (when - // set to 1.63) but we have always included them. - compatibility: "1.64", - "without-connection-token": true, - }) + vscodeServer = await vscodeServerPromise } catch (error) { + vscodeServerPromise = undefined // Unset so we can try again. logError(logger, "CodeServerRouteWrapper", error) if (isDevMode) { - return next(new Error((error instanceof Error ? error.message : error) + " (VS Code may still be compiling)")) + return next( + new Error( + (error instanceof Error ? error.message : error) + + " (Have you applied the patches? If so, VS Code may still be compiling)", + ), + ) } return next(error) } diff --git a/src/node/util.ts b/src/node/util.ts index 6d2993e2c..b7c0c3fa5 100644 --- a/src/node/util.ts +++ b/src/node/util.ts @@ -9,7 +9,6 @@ import * as path from "path" import safeCompare from "safe-compare" import * as util from "util" import xdgBasedir from "xdg-basedir" -import { vsRootPath } from "./constants" export interface Paths { data: string @@ -503,31 +502,6 @@ export function isNodeJSErrnoException(error: unknown): error is NodeJS.ErrnoExc // TODO: Replace with proper templating system. export const escapeJSON = (value: cp.Serializable) => JSON.stringify(value).replace(/"/g, """) -type AMDModule = { [exportName: string]: T } - -/** - * Loads AMD module, typically from a compiled VSCode bundle. - * - * @deprecated This should be gradually phased out as code-server migrates to lib/vscode - * @param amdPath Path to module relative to lib/vscode - * @param exportName Given name of export in the file - */ -export const loadAMDModule = async (amdPath: string, exportName: string): Promise => { - // Set default remote native node modules path, if unset - process.env["VSCODE_INJECT_NODE_MODULE_LOOKUP_PATH"] = - process.env["VSCODE_INJECT_NODE_MODULE_LOOKUP_PATH"] || path.join(vsRootPath, "remote", "node_modules") - - require(path.join(vsRootPath, "out/bootstrap-node")).injectNodeModuleLookupPath( - process.env["VSCODE_INJECT_NODE_MODULE_LOOKUP_PATH"], - ) - - const module = await new Promise>((resolve, reject) => { - require(path.join(vsRootPath, "out/bootstrap-amd")).load(amdPath, resolve, reject) - }) - - return module[exportName] as T -} - /** * Split a string on the first equals. The result will always be an array with * two items regardless of how many equals there are. The second item will be