From 39a57700bcc11784df5b5bb484877223ce13c249 Mon Sep 17 00:00:00 2001 From: Asher Date: Tue, 18 Feb 2020 12:24:12 -0600 Subject: [PATCH 1/2] Enable access to vscode cli --- scripts/vscode.patch | 43 +++++++++++++++++++++++++++++++------------ src/node/app/app.ts | 2 +- src/node/cli.ts | 13 +++++++++++++ src/node/entry.ts | 30 ++++++++++++++++++++++++++++-- 4 files changed, 73 insertions(+), 15 deletions(-) diff --git a/scripts/vscode.patch b/scripts/vscode.patch index 77fe4367..8590fae5 100644 --- a/scripts/vscode.patch +++ b/scripts/vscode.patch @@ -12,7 +12,7 @@ index 160c42ed74..0d544c495c 100644 coverage/ diff --git a/coder.js b/coder.js new file mode 100644 -index 0000000000..fc18355f89 +index 0000000000..6aee0e46bc --- /dev/null +++ b/coder.js @@ -0,0 +1,70 @@ @@ -32,9 +32,9 @@ index 0000000000..fc18355f89 + buildfile.workbenchWeb, + buildfile.workerExtensionHost, + 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"]), -+ buildfile.entrypoint('vs/workbench/services/extensions/node/extensionHostProcess', ["vs/css", "vs/nls"]), ++ 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"]), ++ buildfile.entrypoint("vs/workbench/services/extensions/node/extensionHostProcess", ["vs/css", "vs/nls"]), +]); + +const vscodeResources = [ @@ -894,10 +894,10 @@ index 0000000000..eb62b87798 +} diff --git a/src/vs/server/entry.ts b/src/vs/server/entry.ts new file mode 100644 -index 0000000000..9995e9f7fc +index 0000000000..0d7feaa24e --- /dev/null +++ b/src/vs/server/entry.ts -@@ -0,0 +1,67 @@ +@@ -0,0 +1,76 @@ +import { field } from '@coder/logger'; +import { setUnexpectedErrorHandler } from 'vs/base/common/errors'; +import { CodeServerMessage, VscodeMessage } from 'vs/server/ipc'; @@ -953,6 +953,15 @@ index 0000000000..9995e9f7fc + exit(1); + } + break; ++ case 'cli': ++ try { ++ await vscode.cli(message.args); ++ exit(0); ++ } catch (error) { ++ logger.error(error.message); ++ exit(1); ++ } ++ break; + case 'socket': + vscode.handleWebSocket(socket, message.query); + break; @@ -976,10 +985,10 @@ 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..a1047fff86 +index 0000000000..82566066ff --- /dev/null +++ b/src/vs/server/ipc.d.ts -@@ -0,0 +1,101 @@ +@@ -0,0 +1,106 @@ +/** + * External interfaces for integration into code-server over IPC. No vs imports + * should be made in this file. @@ -998,7 +1007,12 @@ index 0000000000..a1047fff86 + query: Query; +} + -+export type CodeServerMessage = InitMessage | SocketMessage; ++export interface CliMessage { ++ type: 'cli'; ++ args: Args; ++} ++ ++export type CodeServerMessage = InitMessage | SocketMessage | CliMessage; + +export interface ReadyMessage { + type: 'ready'; @@ -1032,8 +1046,8 @@ index 0000000000..a1047fff86 +} + +export interface VscodeOptions { -+ readonly remoteAuthority: string; + readonly args: Args; ++ readonly remoteAuthority: string; + readonly startPath?: StartPath; +} + @@ -2152,10 +2166,10 @@ index 0000000000..3c74512192 +} diff --git a/src/vs/server/node/server.ts b/src/vs/server/node/server.ts new file mode 100644 -index 0000000000..13d71949ce +index 0000000000..20dbca69b2 --- /dev/null +++ b/src/vs/server/node/server.ts -@@ -0,0 +1,252 @@ +@@ -0,0 +1,257 @@ +import * as net from 'net'; +import * as path from 'path'; +import { Emitter } from 'vs/base/common/event'; @@ -2165,6 +2179,7 @@ index 0000000000..13d71949ce +import { ClientConnectionEvent, IPCServer, IServerChannel } from 'vs/base/parts/ipc/common/ipc'; +import { createChannelReceiver } from 'vs/base/parts/ipc/node/ipc'; +import { LogsDataCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/logsDataCleaner'; ++import { main } from "vs/code/node/cliProcessMain"; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { ConfigurationService } from 'vs/platform/configuration/node/configurationService'; +import { ExtensionHostDebugBroadcastChannel } from 'vs/platform/debug/common/extensionHostDebugIpc'; @@ -2222,6 +2237,10 @@ index 0000000000..13d71949ce + private readonly services = new ServiceCollection(); + private servicesPromise?: Promise; + ++ public async cli(args: ParsedArgs): Promise { ++ return main(args); ++ } ++ + public async initialize(options: VscodeOptions): Promise { + const transformer = getUriTransformer(options.remoteAuthority); + if (!this.servicesPromise) { diff --git a/src/node/app/app.ts b/src/node/app/app.ts index 136c4cc6..c80fb0b7 100644 --- a/src/node/app/app.ts +++ b/src/node/app/app.ts @@ -136,7 +136,7 @@ export class MainHttpProvider extends HttpProvider { private async getUpdate(): Promise { if (!this.update.enabled) { - return "Updates are disabled" + return `
Updates are disabled
` } const humanize = (time: number): string => { diff --git a/src/node/cli.ts b/src/node/cli.ts index 34409ac4..a87b0666 100644 --- a/src/node/cli.ts +++ b/src/node/cli.ts @@ -15,6 +15,7 @@ export interface Args extends VsArgs { readonly cert?: OptionalString readonly "cert-key"?: string readonly "disable-updates"?: boolean + readonly "disable-telemetry"?: boolean readonly help?: boolean readonly host?: string readonly json?: boolean @@ -22,6 +23,9 @@ export interface Args extends VsArgs { readonly port?: number readonly socket?: string readonly version?: boolean + readonly "list-extensions"?: boolean + readonly "install-extension"?: string[] + readonly "uninstall-extension"?: string[] readonly _: string[] } @@ -68,6 +72,7 @@ const options: Options> = { }, "cert-key": { type: "string", path: true, description: "Path to certificate key when using non-generated cert." }, "disable-updates": { type: "boolean", description: "Disable automatic updates." }, + "disable-telemetry": { type: "boolean", description: "Disable telemetry." }, host: { type: "string", description: "Host for the HTTP server." }, help: { type: "boolean", short: "h", description: "Show this output." }, json: { type: "boolean" }, @@ -82,6 +87,9 @@ const options: Options> = { "builtin-extensions-dir": { type: "string", path: true }, "extra-extensions-dir": { type: "string[]", path: true }, "extra-builtin-extensions-dir": { type: "string[]", path: true }, + "list-extensions": { type: "boolean" }, + "install-extension": { type: "string[]" }, + "uninstall-extension": { type: "string[]" }, log: { type: "string" }, verbose: { type: "boolean", short: "vvv", description: "Enable verbose logging." }, @@ -193,8 +201,13 @@ export const parse = (argv: string[]): Args => { if (process.env.LOG_LEVEL === "trace" || args.verbose) { args.verbose = true args.log = "trace" + } else if (!args.log) { + args.log = process.env.LOG_LEVEL } + // Ensure this passes down to forked processes. + process.env.LOG_LEVEL = args.log + switch (args.log) { case "trace": logger.level = Level.Trace diff --git a/src/node/entry.ts b/src/node/entry.ts index b9a03a93..bfa8bc4c 100644 --- a/src/node/entry.ts +++ b/src/node/entry.ts @@ -1,10 +1,13 @@ -import { logger } from "@coder/logger" -import { Args, optionDescriptions, parse } from "./cli" +import { field, logger } from "@coder/logger" +import * as cp from "child_process" +import * as path from "path" +import { CliMessage } from "../../lib/vscode/src/vs/server/ipc" import { ApiHttpProvider } from "./app/api" import { MainHttpProvider } from "./app/app" import { LoginHttpProvider } from "./app/login" import { UpdateHttpProvider } from "./app/update" import { VscodeHttpProvider } from "./app/vscode" +import { Args, optionDescriptions, parse } from "./cli" import { AuthType, HttpServer } from "./http" import { generateCertificate, generatePassword, hash, open } from "./util" import { ipcMain, wrap } from "./wrapper" @@ -105,6 +108,29 @@ if (args.help) { console.log(version) } process.exit(0) +} else if (args["list-extensions"] || args["install-extension"] || args["uninstall-extension"]) { + process.env.NBIN_BYPASS = "true" + 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) => { + 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)) } From d47591e253bfbc30d3c70b6eb080984aa0e882a3 Mon Sep 17 00:00:00 2001 From: Asher Date: Tue, 18 Feb 2020 12:57:45 -0600 Subject: [PATCH 2/2] Inject base path into manifest Might fix #1181, although not for the reasons I initially thought (because the URLs are resolved from the manifest path, not the path of the current page). This should ensure that the URLs used by the manifest are always correct regardless of the manifest's path. --- src/browser/media/manifest.json | 4 ++-- src/node/app/app.ts | 16 +++++++++++++++- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/browser/media/manifest.json b/src/browser/media/manifest.json index 513304e1..0b9a7e1d 100644 --- a/src/browser/media/manifest.json +++ b/src/browser/media/manifest.json @@ -1,12 +1,12 @@ { "name": "code-server", "short_name": "code-server", - "start_url": "../../../..", + "start_url": "{{BASE}}", "display": "fullscreen", "background-color": "#fff", "description": "Run editors on a remote server.", "icons": [{ - "src": "./code-server.png", + "src": "{{BASE}}/static-{{COMMIT}}/src/browser/media/code-server.png", "sizes": "384x384", "type": "image/png" }] diff --git a/src/node/app/app.ts b/src/node/app/app.ts index c80fb0b7..8c65e0f7 100644 --- a/src/node/app/app.ts +++ b/src/node/app/app.ts @@ -24,7 +24,7 @@ export class MainHttpProvider extends HttpProvider { switch (route.base) { case "/static": { this.ensureMethod(request) - const response = await this.getResource(this.rootPath, route.requestPath) + const response = await this.getReplacedResource(route) if (!this.isDev) { response.cache = true } @@ -75,6 +75,20 @@ export class MainHttpProvider extends HttpProvider { return this.getErrorRoot(route, "404", "404", "Application not found") } + /** + * Return a resource with variables replaced where necessary. + */ + protected async getReplacedResource(route: Route): Promise { + if (route.requestPath.endsWith("/manifest.json")) { + const response = await this.getUtf8Resource(this.rootPath, route.requestPath) + response.content = response.content + .replace(/{{BASE}}/g, this.base(route)) + .replace(/{{COMMIT}}/g, this.options.commit) + return response + } + return this.getResource(this.rootPath, route.requestPath) + } + public async getRoot(route: Route): Promise { const recent = await this.api.recent() const apps = await this.api.installedApplications()