Use server-main.js to load VS Code

It looks like the bootstrap files are now bundled so we can no longer
require them.  We could make them included again, but maybe it is better
to go through the main entrypoint anyway because it includes some nls
stuff which is maybe necessary.

This also fixes what looks like a bug where we could create two servers
if two requests came in.  I am not sure what the practical consequences
of that would be, but it will no longer do that.
This commit is contained in:
Asher 2024-08-12 12:21:35 -08:00
parent 3140312fe1
commit a78c0db5d5
No known key found for this signature in database
GPG Key ID: D63C1EF81242354A
5 changed files with 75 additions and 56 deletions

View File

@ -272,3 +272,18 @@ Index: code-server/lib/vscode/src/vs/server/node/webClientServer.ts
embedderIdentifier: 'server-distro', embedderIdentifier: 'server-distro',
extensionsGallery: this._webExtensionResourceUrlTemplate && this._productService.extensionsGallery ? { extensionsGallery: this._webExtensionResourceUrlTemplate && this._productService.extensionsGallery ? {
...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;

View File

@ -837,11 +837,6 @@ export interface CodeArgs extends UserProvidedCodeArgs {
log?: string[] log?: string[]
} }
/**
* Types for ../../lib/vscode/src/vs/server/node/server.main.ts:65.
*/
export type SpawnCodeCli = (args: CodeArgs) => Promise<void>
/** /**
* Convert our arguments to equivalent VS Code server arguments. * Convert our arguments to equivalent VS Code server arguments.
* Does not add any extra arguments. * Does not add any extra arguments.

View File

@ -1,12 +1,14 @@
import { field, logger } from "@coder/logger" import { field, logger } from "@coder/logger"
import http from "http" import http from "http"
import * as path from "path"
import { Disposable } from "../common/emitter" import { Disposable } from "../common/emitter"
import { plural } from "../common/util" import { plural } from "../common/util"
import { createApp, ensureAddress } from "./app" import { createApp, ensureAddress } from "./app"
import { AuthType, DefaultedArgs, Feature, SpawnCodeCli, toCodeArgs, UserProvidedArgs } from "./cli" import { AuthType, DefaultedArgs, Feature, toCodeArgs, UserProvidedArgs } from "./cli"
import { commit, version } from "./constants" import { commit, version, vsRootPath } from "./constants"
import { register } from "./routes" 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. * 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<void> => { export const runCodeCli = async (args: DefaultedArgs): Promise<void> => {
logger.debug("Running Code CLI") logger.debug("Running Code CLI")
// See ../../lib/vscode/src/vs/server/node/server.main.ts:65.
const spawnCli = await loadAMDModule<SpawnCodeCli>("vs/server/node/server.main", "spawnCli")
try { 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 // 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 // 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. // waiting for VS Code to exit before giving up and doing it ourselves.

View File

@ -8,10 +8,10 @@ import * as path from "path"
import { WebsocketRequest } from "../../../typings/pluginapi" import { WebsocketRequest } from "../../../typings/pluginapi"
import { logError } from "../../common/util" import { logError } from "../../common/util"
import { CodeArgs, toCodeArgs } from "../cli" import { CodeArgs, toCodeArgs } from "../cli"
import { isDevMode } from "../constants" import { isDevMode, vsRootPath } from "../constants"
import { authenticated, ensureAuthenticated, ensureOrigin, redirect, replaceTemplates, self } from "../http" import { authenticated, ensureAuthenticated, ensureOrigin, redirect, replaceTemplates, self } from "../http"
import { SocketProxyProvider } from "../socket" import { SocketProxyProvider } from "../socket"
import { isFile, loadAMDModule } from "../util" import { isFile } from "../util"
import { Router as WsRouter } from "../wsRouter" import { Router as WsRouter } from "../wsRouter"
export const router = express.Router() export const router = express.Router()
@ -31,11 +31,46 @@ export interface IVSCodeServerAPI {
dispose(): void dispose(): void
} }
// See ../../../lib/vscode/src/vs/server/node/server.main.ts:72. /**
export type CreateServer = (address: string | net.AddressInfo | null, args: CodeArgs) => Promise<IVSCodeServerAPI> * 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<IVSCodeServerAPI>
// See ../../../lib/vscode/src/vs/server/node/server.main.ts:65.
spawnCli(args: CodeArgs): Promise<void>
}
}
// 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<IVSCodeServerAPI> {
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<IVSCodeServerAPI> | undefined
// The resolved value from the dynamically loaded VS Code server. Do not use
// without first calling and awaiting `ensureCodeServerLoaded`.
let vscodeServer: IVSCodeServerAPI | undefined let vscodeServer: IVSCodeServerAPI | undefined
/** /**
@ -49,21 +84,21 @@ export const ensureVSCodeLoaded = async (
if (vscodeServer) { if (vscodeServer) {
return next() return next()
} }
// See ../../../lib/vscode/src/vs/server/node/server.main.ts:72. if (!vscodeServerPromise) {
const createVSServer = await loadAMDModule<CreateServer>("vs/server/node/server.main", "createServer") vscodeServerPromise = loadVSCode(req)
}
try { try {
vscodeServer = await createVSServer(null, { vscodeServer = await vscodeServerPromise
...(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,
})
} catch (error) { } catch (error) {
vscodeServerPromise = undefined // Unset so we can try again.
logError(logger, "CodeServerRouteWrapper", error) logError(logger, "CodeServerRouteWrapper", error)
if (isDevMode) { 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) return next(error)
} }

View File

@ -9,7 +9,6 @@ import * as path from "path"
import safeCompare from "safe-compare" import safeCompare from "safe-compare"
import * as util from "util" import * as util from "util"
import xdgBasedir from "xdg-basedir" import xdgBasedir from "xdg-basedir"
import { vsRootPath } from "./constants"
export interface Paths { export interface Paths {
data: string data: string
@ -503,31 +502,6 @@ export function isNodeJSErrnoException(error: unknown): error is NodeJS.ErrnoExc
// TODO: Replace with proper templating system. // TODO: Replace with proper templating system.
export const escapeJSON = (value: cp.Serializable) => JSON.stringify(value).replace(/"/g, "&quot;") export const escapeJSON = (value: cp.Serializable) => JSON.stringify(value).replace(/"/g, "&quot;")
type AMDModule<T> = { [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 <T>(amdPath: string, exportName: string): Promise<T> => {
// 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<AMDModule<T>>((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 * 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 * two items regardless of how many equals there are. The second item will be