Quality check
This commit is contained in:
parent
e8cb6ffaa0
commit
9fdfacb314
|
@ -1,18 +1,14 @@
|
|||
/* global require, global, process, __dirname */
|
||||
if (!global.NBIN_LOADED) {
|
||||
try {
|
||||
const nbin = require("nbin");
|
||||
nbin.shimNativeFs("{{ROOT_PATH}}");
|
||||
global.NBIN_LOADED = true;
|
||||
|
||||
const path = require("path");
|
||||
const rg = require("vscode-ripgrep");
|
||||
rg.binaryRgPath = rg.rgPath;
|
||||
rg.rgPath = path.join(
|
||||
require("os").tmpdir(),
|
||||
`code-server/${path.basename(rg.binaryRgPath)}`,
|
||||
`code-server/${path.basename(rg.binaryRgPath)}`
|
||||
);
|
||||
} catch (error) {
|
||||
// Not in the binary.
|
||||
}
|
||||
} catch (error) { /* Not in the binary. */ }
|
||||
}
|
||||
|
|
42
src/api.ts
42
src/api.ts
|
@ -60,7 +60,6 @@ export const vscodeApi = (serviceCollection: ServiceCollection): typeof vscode =
|
|||
FileSystemError: extHostTypes.FileSystemError,
|
||||
FileType: FileType,
|
||||
Uri: URI,
|
||||
|
||||
commands: {
|
||||
executeCommand: (commandId: string, ...args: any[]): any => {
|
||||
return commandService.executeCommand(commandId, ...args);
|
||||
|
@ -69,7 +68,6 @@ export const vscodeApi = (serviceCollection: ServiceCollection): typeof vscode =
|
|||
return CommandsRegistry.registerCommand(id, command);
|
||||
},
|
||||
},
|
||||
|
||||
window: {
|
||||
registerTreeDataProvider: (id: string, dataProvider: ITreeViewDataProvider): void => {
|
||||
const view = viewsRegistry.getView(id);
|
||||
|
@ -81,7 +79,6 @@ export const vscodeApi = (serviceCollection: ServiceCollection): typeof vscode =
|
|||
notificationService.error(message);
|
||||
},
|
||||
},
|
||||
|
||||
workspace: {
|
||||
registerFileSystemProvider: (scheme: string, provider: vscode.FileSystemProvider): IDisposable => {
|
||||
return fileService.registerProvider(scheme, new FileSystemProvider(provider));
|
||||
|
@ -95,7 +92,6 @@ export const vscodeApi = (serviceCollection: ServiceCollection): typeof vscode =
|
|||
*/
|
||||
export const coderApi = (serviceCollection: ServiceCollection): typeof coder => {
|
||||
const getService = <T>(id: ServiceIdentifier<T>): T => serviceCollection.get<T>(id) as T;
|
||||
|
||||
return {
|
||||
workbench: {
|
||||
action: Action,
|
||||
|
@ -103,13 +99,8 @@ export const coderApi = (serviceCollection: ServiceCollection): typeof coder =>
|
|||
commandRegistry: CommandsRegistry,
|
||||
actionsRegistry: Registry.as<IWorkbenchActionRegistry>(ActionExtensions.WorkbenchActions),
|
||||
registerView: (viewId, viewName, containerId, containerName, icon): void => {
|
||||
const viewContainersRegistry = Registry.as<IViewContainersRegistry>(ViewsExtensions.ViewContainersRegistry);
|
||||
const viewsRegistry = Registry.as<IViewsRegistry>(ViewsExtensions.ViewsRegistry);
|
||||
const container = viewContainersRegistry.registerViewContainer(containerId);
|
||||
|
||||
const cssClass = `extensionViewlet-${containerId}`;
|
||||
const id = `workbench.view.extension.${containerId}`;
|
||||
|
||||
class CustomViewlet extends ViewContainerViewlet {
|
||||
public constructor(
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
|
@ -127,44 +118,32 @@ export const coderApi = (serviceCollection: ServiceCollection): typeof coder =>
|
|||
}
|
||||
}
|
||||
|
||||
const viewletDescriptor = new ViewletDescriptor(
|
||||
CustomViewlet as any,
|
||||
id,
|
||||
containerName,
|
||||
cssClass,
|
||||
undefined,
|
||||
URI.parse(icon),
|
||||
Registry.as<ViewletRegistry>(ViewletExtensions.Viewlets).registerViewlet(
|
||||
new ViewletDescriptor(CustomViewlet as any, id, containerName, cssClass, undefined, URI.parse(icon)),
|
||||
);
|
||||
|
||||
Registry.as<ViewletRegistry>(ViewletExtensions.Viewlets).registerViewlet(viewletDescriptor);
|
||||
|
||||
const registry = Registry.as<IWorkbenchActionRegistry>(ActionExtensions.WorkbenchActions);
|
||||
registry.registerWorkbenchAction(
|
||||
Registry.as<IWorkbenchActionRegistry>(ActionExtensions.WorkbenchActions).registerWorkbenchAction(
|
||||
new SyncActionDescriptor(OpenCustomViewletAction as any, id, localize("showViewlet", "Show {0}", containerName)),
|
||||
"View: Show {0}",
|
||||
localize("view", "View"),
|
||||
);
|
||||
|
||||
// Generate CSS to show the icon in the activity bar
|
||||
// Generate CSS to show the icon in the activity bar.
|
||||
const iconClass = `.monaco-workbench .activitybar .monaco-action-bar .action-label.${cssClass}`;
|
||||
createCSSRule(iconClass, `-webkit-mask: url('${icon}') no-repeat 50% 50%`);
|
||||
|
||||
const views = [{
|
||||
const container = Registry.as<IViewContainersRegistry>(ViewsExtensions.ViewContainersRegistry).registerViewContainer(containerId);
|
||||
Registry.as<IViewsRegistry>(ViewsExtensions.ViewsRegistry).registerViews([{
|
||||
id: viewId,
|
||||
name: viewName,
|
||||
ctorDescriptor: { ctor: CustomTreeViewPanel },
|
||||
treeView: getService(IInstantiationService).createInstance(CustomTreeView as any, viewId, container),
|
||||
}] as ITreeViewDescriptor[];
|
||||
viewsRegistry.registerViews(views, container);
|
||||
}] as ITreeViewDescriptor[], container);
|
||||
},
|
||||
// Even though the enums are exactly the same, Typescript says they are
|
||||
// not assignable to each other, so use `any`. I don't know if there is a
|
||||
// way around this.
|
||||
menuRegistry: MenuRegistry as any,
|
||||
statusbarService: getService(IStatusbarService) as any,
|
||||
notificationService: getService(INotificationService),
|
||||
terminalService: getService(ITerminalService),
|
||||
|
||||
onFileCreate: (cb): void => {
|
||||
getService<IFileService>(IFileService).onAfterOperation((e) => {
|
||||
if (e.operation === FileOperation.CREATE) {
|
||||
|
@ -198,7 +177,6 @@ export const coderApi = (serviceCollection: ServiceCollection): typeof coder =>
|
|||
}
|
||||
});
|
||||
},
|
||||
|
||||
onModelAdded: (cb): void => {
|
||||
getService<IModelService>(IModelService).onModelAdded((e) => {
|
||||
cb(e.uri.path, e.getLanguageIdentifier().language);
|
||||
|
@ -214,7 +192,6 @@ export const coderApi = (serviceCollection: ServiceCollection): typeof coder =>
|
|||
cb(e.model.uri.path, e.model.getLanguageIdentifier().language, e.oldModeId);
|
||||
});
|
||||
},
|
||||
|
||||
onTerminalAdded: (cb): void => {
|
||||
getService<ITerminalService>(ITerminalService).onInstanceCreated(() => cb());
|
||||
},
|
||||
|
@ -222,7 +199,6 @@ export const coderApi = (serviceCollection: ServiceCollection): typeof coder =>
|
|||
getService<ITerminalService>(ITerminalService).onInstanceDisposed(() => cb());
|
||||
},
|
||||
},
|
||||
|
||||
// @ts-ignore
|
||||
MenuId: MenuId,
|
||||
Severity: Severity,
|
||||
|
@ -250,9 +226,7 @@ class FileSystemProvider implements IFileSystemProvider {
|
|||
public readonly capabilities: FileSystemProviderCapabilities;
|
||||
public readonly onDidChangeCapabilities: Event<void> = Event.None;
|
||||
|
||||
public constructor(
|
||||
private readonly provider: vscode.FileSystemProvider,
|
||||
) {
|
||||
public constructor(private readonly provider: vscode.FileSystemProvider) {
|
||||
this.capabilities = FileSystemProviderCapabilities.Readonly;
|
||||
}
|
||||
|
||||
|
|
|
@ -43,9 +43,6 @@ class Watcher extends DiskFileSystemProvider {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* See: src/vs/platform/remote/common/remoteAgentFileSystemChannel.ts.
|
||||
*/
|
||||
export class FileProviderChannel implements IServerChannel, IDisposable {
|
||||
private readonly provider: DiskFileSystemProvider;
|
||||
private readonly watchers = new Map<string, Watcher>();
|
||||
|
@ -175,9 +172,6 @@ export class FileProviderChannel implements IServerChannel, IDisposable {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* See: src/vs/workbench/services/remote/common/remoteAgentEnvironmentChannel.ts.
|
||||
*/
|
||||
export class ExtensionEnvironmentChannel implements IServerChannel {
|
||||
public constructor(
|
||||
private readonly environment: IEnvironmentService,
|
||||
|
@ -245,7 +239,6 @@ export class ExtensionEnvironmentChannel implements IServerChannel {
|
|||
};
|
||||
|
||||
return Promise.all([scanBuiltin(), scanInstalled()]).then((allExtensions) => {
|
||||
// It's possible to get duplicates.
|
||||
const uniqueExtensions = new Map<string, IExtensionDescription>();
|
||||
allExtensions.forEach((multipleExtensions) => {
|
||||
multipleExtensions.forEach((extensions) => {
|
||||
|
@ -254,18 +247,13 @@ export class ExtensionEnvironmentChannel implements IServerChannel {
|
|||
if (uniqueExtensions.has(id)) {
|
||||
const oldPath = uniqueExtensions.get(id)!.extensionLocation.fsPath;
|
||||
const newPath = extension.extensionLocation.fsPath;
|
||||
this.log.warn(
|
||||
`Extension ${id} in ${oldPath} has been overridden ${newPath}`,
|
||||
);
|
||||
this.log.warn(`${oldPath} has been overridden ${newPath}`);
|
||||
}
|
||||
uniqueExtensions.set(id, extension);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const finalExtensions = <IExtensionDescription[]>[];
|
||||
uniqueExtensions.forEach((e) => finalExtensions.push(e));
|
||||
return finalExtensions;
|
||||
return Array.from(uniqueExtensions.values());
|
||||
});
|
||||
}
|
||||
|
||||
|
|
33
src/cli.ts
33
src/cli.ts
|
@ -92,12 +92,7 @@ const main = async (): Promise<void> => {
|
|||
const version = `${(pkg as any).codeServerVersion || "development"}-vsc${pkg.version}`;
|
||||
if (args.help) {
|
||||
const executable = `${product.applicationName}${os.platform() === "win32" ? ".exe" : ""}`;
|
||||
return console.log(buildHelpMessage(
|
||||
product.nameLong, executable,
|
||||
version,
|
||||
undefined,
|
||||
false,
|
||||
));
|
||||
return console.log(buildHelpMessage(product.nameLong, executable, version, undefined, false));
|
||||
}
|
||||
|
||||
if (args.version) {
|
||||
|
@ -116,26 +111,22 @@ const main = async (): Promise<void> => {
|
|||
if (shouldSpawnCliProcess()) {
|
||||
const cli = await new Promise<IMainCli>((c, e) => require(["vs/code/node/cliProcessMain"], c, e));
|
||||
await cli.main(args);
|
||||
// There is some WriteStream instance keeping it open so force an exit.
|
||||
return process.exit(0);
|
||||
return process.exit(0); // There is a WriteStream instance keeping it open.
|
||||
}
|
||||
|
||||
const extra = args["_"] || [];
|
||||
const options = {
|
||||
host: args.host,
|
||||
allowHttp: args["allow-http"],
|
||||
auth: typeof args.auth !== "undefined" ? args.auth : true,
|
||||
cert: args.cert,
|
||||
certKey: args["cert-key"],
|
||||
auth: typeof args.auth !== "undefined" ? args.auth : true,
|
||||
folderUri: extra.length > 1 ? extra[extra.length - 1] : undefined,
|
||||
host: args.host,
|
||||
password: process.env.PASSWORD,
|
||||
folderUri: args["_"] && args["_"].length > 1
|
||||
? args["_"][args["_"].length - 1]
|
||||
: undefined,
|
||||
};
|
||||
|
||||
if (!options.host) {
|
||||
options.host = !options.auth || options.allowHttp
|
||||
? "localhost"
|
||||
: "0.0.0.0";
|
||||
options.host = !options.auth || options.allowHttp ? "localhost" : "0.0.0.0";
|
||||
}
|
||||
|
||||
let usingGeneratedCert = false;
|
||||
|
@ -152,18 +143,16 @@ const main = async (): Promise<void> => {
|
|||
usingGeneratedPassword = true;
|
||||
}
|
||||
|
||||
const webviewPort = typeof args["webview-port"] !== "undefined"
|
||||
&& parseInt(args["webview-port"], 10) || 8444;
|
||||
const webviewPort = args["webview-port"];
|
||||
const webviewServer = new WebviewServer({
|
||||
...options,
|
||||
port: webviewPort,
|
||||
port: typeof webviewPort !== "undefined" && parseInt(webviewPort, 10) || 8444,
|
||||
socket: args["webview-socket"],
|
||||
});
|
||||
|
||||
const port = typeof args.port !== "undefined" && parseInt(args.port, 10) || 8443;
|
||||
const server = new MainServer({
|
||||
...options,
|
||||
port,
|
||||
port: typeof args.port !== "undefined" && parseInt(args.port, 10) || 8443,
|
||||
socket: args.socket,
|
||||
}, webviewServer, args);
|
||||
|
||||
|
@ -196,7 +185,7 @@ const main = async (): Promise<void> => {
|
|||
|
||||
if (!args.socket && args.open) {
|
||||
// The web socket doesn't seem to work if using 0.0.0.0.
|
||||
const openAddress = `http://localhost:${port}`;
|
||||
const openAddress = `http://localhost:${server.options.port}`;
|
||||
await open(openAddress).catch(console.error);
|
||||
console.log(` - Opened ${openAddress}`);
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ import { Emitter } from "vs/base/common/event";
|
|||
import { ISocket } from "vs/base/parts/ipc/common/ipc.net";
|
||||
import { NodeSocket } from "vs/base/parts/ipc/node/ipc.net";
|
||||
import { ILogService } from "vs/platform/log/common/log";
|
||||
import { IExtHostReadyMessage, IExtHostSocketMessage } from "vs/workbench/services/extensions/common/extensionHostProtocol";
|
||||
import { IExtHostReadyMessage } from "vs/workbench/services/extensions/common/extensionHostProtocol";
|
||||
|
||||
import { Protocol } from "vs/server/src/protocol";
|
||||
import { uriTransformerPath } from "vs/server/src/util";
|
||||
|
@ -15,17 +15,11 @@ export abstract class Connection {
|
|||
protected readonly _onClose = new Emitter<void>();
|
||||
public readonly onClose = this._onClose.event;
|
||||
protected disposed: boolean = false;
|
||||
|
||||
public constructor(protected protocol: Protocol) {}
|
||||
|
||||
/**
|
||||
* Set up the connection on a new socket.
|
||||
*/
|
||||
public abstract reconnect(socket: ISocket, buffer: VSBuffer): void;
|
||||
|
||||
/**
|
||||
* Clean up the connection.
|
||||
*/
|
||||
protected abstract dispose(): void;
|
||||
}
|
||||
|
||||
|
@ -62,16 +56,10 @@ export class ManagementConnection extends Connection {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Manage the extension host process.
|
||||
*/
|
||||
export class ExtensionHostConnection extends Connection {
|
||||
private process: cp.ChildProcess;
|
||||
|
||||
public constructor(
|
||||
protocol: Protocol, buffer: VSBuffer,
|
||||
private readonly log: ILogService,
|
||||
) {
|
||||
public constructor(protocol: Protocol, buffer: VSBuffer, private readonly log: ILogService) {
|
||||
super(protocol);
|
||||
protocol.dispose();
|
||||
this.process = this.spawn(buffer);
|
||||
|
@ -96,23 +84,17 @@ export class ExtensionHostConnection extends Connection {
|
|||
private sendInitMessage(buffer: VSBuffer): void {
|
||||
const socket = this.protocol.getUnderlyingSocket();
|
||||
socket.pause();
|
||||
|
||||
const initMessage: IExtHostSocketMessage = {
|
||||
this.process.send({
|
||||
type: "VSCODE_EXTHOST_IPC_SOCKET",
|
||||
initialDataChunk: (buffer.buffer as Buffer).toString("base64"),
|
||||
skipWebSocketFrames: this.protocol.getSocket() instanceof NodeSocket,
|
||||
};
|
||||
|
||||
this.process.send(initMessage, socket);
|
||||
}, socket);
|
||||
}
|
||||
|
||||
private spawn(buffer: VSBuffer): cp.ChildProcess {
|
||||
const proc = cp.fork(
|
||||
getPathFromAmdModule(require, "bootstrap-fork"),
|
||||
[
|
||||
"--type=extensionHost",
|
||||
`--uriTransformerPath=${uriTransformerPath()}`
|
||||
],
|
||||
[ "--type=extensionHost", `--uriTransformerPath=${uriTransformerPath()}` ],
|
||||
{
|
||||
env: {
|
||||
...process.env,
|
||||
|
@ -129,13 +111,8 @@ export class ExtensionHostConnection extends Connection {
|
|||
|
||||
proc.on("error", () => this.dispose());
|
||||
proc.on("exit", () => this.dispose());
|
||||
|
||||
proc.stdout.setEncoding("utf8");
|
||||
proc.stderr.setEncoding("utf8");
|
||||
|
||||
proc.stdout.on("data", (d) => this.log.info("Extension host stdout", d));
|
||||
proc.stderr.on("data", (d) => this.log.error("Extension host stderr", d));
|
||||
|
||||
proc.stdout.setEncoding("utf8").on("data", (d) => this.log.info("Extension host stdout", d));
|
||||
proc.stderr.setEncoding("utf8").on("data", (d) => this.log.error("Extension host stderr", d));
|
||||
proc.on("message", (event) => {
|
||||
if (event && event.type === "__$console") {
|
||||
const severity = this.log[event.severity] ? event.severity : "info";
|
||||
|
@ -149,8 +126,7 @@ export class ExtensionHostConnection extends Connection {
|
|||
this.sendInitMessage(buffer);
|
||||
}
|
||||
};
|
||||
proc.on("message", listen);
|
||||
|
||||
return proc;
|
||||
return proc.on("message", listen);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -73,10 +73,7 @@ export class Protocol extends PersistentProtocol {
|
|||
* TODO: This ignores the authentication process entirely for now.
|
||||
*/
|
||||
private authenticate(_message: AuthRequest): void {
|
||||
this.sendMessage({
|
||||
type: "sign",
|
||||
data: "",
|
||||
});
|
||||
this.sendMessage({ type: "sign", data: "" });
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
158
src/server.ts
158
src/server.ts
|
@ -108,14 +108,11 @@ export interface ServerOptions {
|
|||
}
|
||||
|
||||
export abstract class Server {
|
||||
// The underlying web server.
|
||||
protected readonly server: http.Server | https.Server;
|
||||
|
||||
protected rootPath = path.resolve(__dirname, "../../../..");
|
||||
|
||||
private listenPromise: Promise<string> | undefined;
|
||||
|
||||
public constructor(protected readonly options: ServerOptions) {
|
||||
public constructor(public readonly options: ServerOptions) {
|
||||
if (this.options.cert && this.options.certKey) {
|
||||
useHttpsTransformer();
|
||||
const httpolyglot = require.__$__nodeRequire(path.resolve(__dirname, "../node_modules/httpolyglot/lib/index")) as typeof import("httpolyglot");
|
||||
|
@ -167,8 +164,7 @@ export abstract class Server {
|
|||
): Promise<Response>;
|
||||
|
||||
protected async getResource(filePath: string): Promise<Response> {
|
||||
const content = await util.promisify(fs.readFile)(filePath);
|
||||
return { content, filePath };
|
||||
return { content: await util.promisify(fs.readFile)(filePath), filePath };
|
||||
}
|
||||
|
||||
private onRequest = async (request: http.IncomingMessage, response: http.ServerResponse): Promise<void> => {
|
||||
|
@ -208,12 +204,10 @@ export abstract class Server {
|
|||
} else if (base === "") { // Happens if it's a plain `domain.com`.
|
||||
base = "/";
|
||||
}
|
||||
if (requestPath === "/") { // Trailing slash, like `domain.com/login/`.
|
||||
requestPath = "";
|
||||
} else if (requestPath !== "") { // "" will become "." with normalize.
|
||||
base = path.normalize(base);
|
||||
if (requestPath !== "") { // "" will become "." with normalize.
|
||||
requestPath = path.normalize(requestPath);
|
||||
}
|
||||
base = path.normalize(base);
|
||||
|
||||
switch (base) {
|
||||
case "/":
|
||||
|
@ -227,8 +221,7 @@ export abstract class Server {
|
|||
case "/login":
|
||||
if (!this.options.auth) {
|
||||
throw new HttpError("Not found", HttpCode.NotFound);
|
||||
}
|
||||
if (requestPath === "") {
|
||||
} else if (requestPath === "") {
|
||||
return this.tryLogin(request);
|
||||
}
|
||||
this.ensureGet(request);
|
||||
|
@ -249,27 +242,19 @@ export abstract class Server {
|
|||
this.ensureGet(request);
|
||||
return { redirect: "https://" + request.headers.host + "/" };
|
||||
}
|
||||
|
||||
if (request.method === "POST") {
|
||||
const data = await this.getData<LoginPayload>(request);
|
||||
if (this.authenticate(request, data)) {
|
||||
return {
|
||||
redirect: "https://" + request.headers.host + "/",
|
||||
headers: {
|
||||
"Set-Cookie": `password=${data.password}`,
|
||||
}
|
||||
headers: {"Set-Cookie": `password=${data.password}` }
|
||||
};
|
||||
}
|
||||
let userAgent = request.headers["user-agent"];
|
||||
const timestamp = Math.floor(new Date().getTime() / 1000);
|
||||
if (Array.isArray(userAgent)) {
|
||||
userAgent = userAgent.join(", ");
|
||||
}
|
||||
console.error("Failed login attempt", JSON.stringify({
|
||||
xForwardedFor: request.headers["x-forwarded-for"],
|
||||
remoteAddress: request.connection.remoteAddress,
|
||||
userAgent,
|
||||
timestamp,
|
||||
userAgent: request.headers["user-agent"],
|
||||
timestamp: Math.floor(new Date().getTime() / 1000),
|
||||
}));
|
||||
return this.getLogin("Invalid password", data);
|
||||
}
|
||||
|
@ -279,23 +264,16 @@ export abstract class Server {
|
|||
|
||||
private async getLogin(error: string = "", payload?: LoginPayload): Promise<Response> {
|
||||
const filePath = path.join(this.rootPath, "out/vs/server/src/login/login.html");
|
||||
let content = await util.promisify(fs.readFile)(filePath, "utf8");
|
||||
if (error) {
|
||||
content = content.replace("{{ERROR}}", error)
|
||||
.replace("display:none", "display:block");
|
||||
}
|
||||
if (payload && payload.password) {
|
||||
content = content.replace('value=""', `value="${payload.password}"`);
|
||||
}
|
||||
const content = (await util.promisify(fs.readFile)(filePath, "utf8"))
|
||||
.replace("{{ERROR}}", error)
|
||||
.replace("display:none", error ? "display:block" : "display:none")
|
||||
.replace('value=""', `value="${payload && payload.password || ""}"`);
|
||||
return { content, filePath };
|
||||
}
|
||||
|
||||
private ensureGet(request: http.IncomingMessage): void {
|
||||
if (request.method !== "GET") {
|
||||
throw new HttpError(
|
||||
`Unsupported method ${request.method}`,
|
||||
HttpCode.BadRequest,
|
||||
);
|
||||
throw new HttpError(`Unsupported method ${request.method}`, HttpCode.BadRequest);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -357,15 +335,10 @@ export abstract class Server {
|
|||
}
|
||||
|
||||
export class MainServer extends Server {
|
||||
// Used to notify the IPC server that there is a new client.
|
||||
public readonly _onDidClientConnect = new Emitter<ClientConnectionEvent>();
|
||||
public readonly onDidClientConnect = this._onDidClientConnect.event;
|
||||
|
||||
// This is separate instead of just extending this class since we can't
|
||||
// use properties in the super call. This manages channels.
|
||||
private readonly ipc = new IPCServer(this.onDidClientConnect);
|
||||
|
||||
// Persistent connections. These can reconnect within a timeout.
|
||||
private readonly connections = new Map<ConnectionType, Map<string, Connection>>();
|
||||
|
||||
private readonly services = new ServiceCollection();
|
||||
|
@ -377,7 +350,6 @@ export class MainServer extends Server {
|
|||
args: ParsedArgs,
|
||||
) {
|
||||
super(options);
|
||||
|
||||
this.server.on("upgrade", async (request, socket) => {
|
||||
const protocol = this.createProtocol(request, socket);
|
||||
try {
|
||||
|
@ -393,12 +365,10 @@ export class MainServer extends Server {
|
|||
|
||||
public async listen(): Promise<string> {
|
||||
const environment = (this.services.get(IEnvironmentService) as EnvironmentService);
|
||||
const mkdirs = Promise.all([
|
||||
const [address] = await Promise.all<string>([
|
||||
super.listen(), ...[
|
||||
environment.extensionsPath,
|
||||
].map((p) => mkdirp(p)));
|
||||
const [address] = await Promise.all([
|
||||
super.listen(),
|
||||
mkdirs,
|
||||
].map((p) => mkdirp(p).then(() => p)),
|
||||
]);
|
||||
return address;
|
||||
}
|
||||
|
@ -426,22 +396,18 @@ export class MainServer extends Server {
|
|||
|
||||
private async getRoot(request: http.IncomingMessage, parsedUrl: url.UrlWithParsedQuery): Promise<Response> {
|
||||
const filePath = path.join(this.rootPath, "out/vs/code/browser/workbench/workbench.html");
|
||||
let content = await util.promisify(fs.readFile)(filePath, "utf8");
|
||||
|
||||
const remoteAuthority = request.headers.host as string;
|
||||
const transformer = getUriTransformer(remoteAuthority);
|
||||
|
||||
await Promise.all([
|
||||
let [content] = await Promise.all([
|
||||
util.promisify(fs.readFile)(filePath, "utf8"),
|
||||
this.webviewServer.listen(),
|
||||
this.servicesPromise,
|
||||
]);
|
||||
|
||||
const webviewEndpoint = this.webviewServer.address(request);
|
||||
|
||||
const cwd = process.env.VSCODE_CWD || process.cwd();
|
||||
const workspacePath = parsedUrl.query.workspace as string | undefined;
|
||||
const folderPath = !workspacePath ? parsedUrl.query.folder as string | undefined || this.options.folderUri || cwd: undefined;
|
||||
|
||||
const remoteAuthority = request.headers.host as string;
|
||||
const transformer = getUriTransformer(remoteAuthority);
|
||||
const options: Options = {
|
||||
WORKBENCH_WEB_CONGIGURATION: {
|
||||
workspaceUri: workspacePath
|
||||
|
@ -463,7 +429,6 @@ export class MainServer extends Server {
|
|||
Object.keys(options).forEach((key) => {
|
||||
content = content.replace(`"{{${key}}}"`, `'${JSON.stringify(options[key])}'`);
|
||||
});
|
||||
|
||||
content = content.replace('{{WEBVIEW_ENDPOINT}}', webviewEndpoint);
|
||||
|
||||
return { content, filePath };
|
||||
|
@ -473,83 +438,58 @@ export class MainServer extends Server {
|
|||
if (request.headers.upgrade !== "websocket") {
|
||||
throw new Error("HTTP/1.1 400 Bad Request");
|
||||
}
|
||||
|
||||
const options = {
|
||||
reconnectionToken: "",
|
||||
reconnection: false,
|
||||
skipWebSocketFrames: false,
|
||||
};
|
||||
|
||||
if (request.url) {
|
||||
const query = url.parse(request.url, true).query;
|
||||
if (query.reconnectionToken) {
|
||||
options.reconnectionToken = query.reconnectionToken as string;
|
||||
}
|
||||
if (query.reconnection === "true") {
|
||||
options.reconnection = true;
|
||||
}
|
||||
if (query.skipWebSocketFrames === "true") {
|
||||
options.skipWebSocketFrames = true;
|
||||
}
|
||||
}
|
||||
|
||||
return new Protocol(
|
||||
request.headers["sec-websocket-key"] as string,
|
||||
socket,
|
||||
options,
|
||||
);
|
||||
const query = request.url ? url.parse(request.url, true).query : {};
|
||||
return new Protocol(<string>request.headers["sec-websocket-key"], socket, {
|
||||
reconnectionToken: <string>query.reconnectionToken || "",
|
||||
reconnection: query.reconnection === "true",
|
||||
skipWebSocketFrames: query.skipWebSocketFrames === "true",
|
||||
});
|
||||
}
|
||||
|
||||
private async connect(message: ConnectionTypeRequest, protocol: Protocol): Promise<void> {
|
||||
switch (message.desiredConnectionType) {
|
||||
case ConnectionType.ExtensionHost:
|
||||
case ConnectionType.Management:
|
||||
const debugPort = await this.getDebugPort();
|
||||
const ok = message.desiredConnectionType === ConnectionType.ExtensionHost
|
||||
? (debugPort ? { debugPort } : {})
|
||||
: { type: "ok" };
|
||||
|
||||
if (!this.connections.has(message.desiredConnectionType)) {
|
||||
this.connections.set(message.desiredConnectionType, new Map());
|
||||
}
|
||||
|
||||
const connections = this.connections.get(message.desiredConnectionType)!;
|
||||
const token = protocol.options.reconnectionToken;
|
||||
|
||||
const ok = async () => {
|
||||
return message.desiredConnectionType === ConnectionType.ExtensionHost
|
||||
? { debugPort: await this.getDebugPort() }
|
||||
: { type: "ok" };
|
||||
};
|
||||
|
||||
const token = protocol.options.reconnectionToken;
|
||||
if (protocol.options.reconnection && connections.has(token)) {
|
||||
protocol.sendMessage(ok);
|
||||
protocol.sendMessage(await ok());
|
||||
const buffer = protocol.readEntireBuffer();
|
||||
protocol.dispose();
|
||||
return connections.get(token)!.reconnect(protocol.getSocket(), buffer);
|
||||
}
|
||||
|
||||
if (protocol.options.reconnection || connections.has(token)) {
|
||||
} else if (protocol.options.reconnection || connections.has(token)) {
|
||||
throw new Error(protocol.options.reconnection
|
||||
? "Unrecognized reconnection token"
|
||||
: "Duplicate reconnection token"
|
||||
);
|
||||
}
|
||||
|
||||
protocol.sendMessage(ok);
|
||||
protocol.sendMessage(await ok());
|
||||
|
||||
let connection: Connection;
|
||||
if (message.desiredConnectionType === ConnectionType.Management) {
|
||||
connection = new ManagementConnection(protocol);
|
||||
this._onDidClientConnect.fire({
|
||||
protocol,
|
||||
onDidClientDisconnect: connection.onClose,
|
||||
protocol, onDidClientDisconnect: connection.onClose,
|
||||
});
|
||||
} else {
|
||||
const buffer = protocol.readEntireBuffer();
|
||||
connection = new ExtensionHostConnection(
|
||||
protocol, buffer,
|
||||
this.services.get(ILogService) as ILogService,
|
||||
protocol, buffer, this.services.get(ILogService) as ILogService,
|
||||
);
|
||||
}
|
||||
connections.set(protocol.options.reconnectionToken, connection);
|
||||
connection.onClose(() => {
|
||||
connections.delete(protocol.options.reconnectionToken);
|
||||
});
|
||||
connections.set(token, connection);
|
||||
connection.onClose(() => connections.delete(token));
|
||||
break;
|
||||
case ConnectionType.Tunnel: return protocol.tunnel();
|
||||
default: throw new Error("Unrecognized connection type");
|
||||
|
@ -557,14 +497,11 @@ export class MainServer extends Server {
|
|||
}
|
||||
|
||||
private async initializeServices(args: ParsedArgs): Promise<void> {
|
||||
const router = new StaticRouter((ctx: any) => ctx.clientId === "renderer");
|
||||
const environmentService = new EnvironmentService(args, process.execPath);
|
||||
const logService = new SpdLogService(RemoteExtensionLogFileName, environmentService.logsPath, getLogLevel(environmentService));
|
||||
this.ipc.registerChannel("loglevel", new LogLevelSetterChannel(logService));
|
||||
|
||||
const router = new StaticRouter((context: any) => {
|
||||
return context.clientId === "renderer";
|
||||
});
|
||||
|
||||
this.services.set(ILogService, logService);
|
||||
this.services.set(IEnvironmentService, environmentService);
|
||||
this.services.set(IConfigurationService, new SyncDescriptor(ConfigurationService, [environmentService.machineSettingsResource]));
|
||||
|
@ -594,11 +531,9 @@ export class MainServer extends Server {
|
|||
this.services.set(IDialogService, new DialogChannelClient(this.ipc.getChannel("dialog", router)));
|
||||
this.services.set(IExtensionManagementService, new SyncDescriptor(ExtensionManagementService));
|
||||
|
||||
await new Promise((resolve) => {
|
||||
const instantiationService = new InstantiationService(this.services);
|
||||
|
||||
this.services.set(ILocalizationsService, instantiationService.createInstance(LocalizationsService));
|
||||
|
||||
return new Promise((resolve) => {
|
||||
instantiationService.invokeFunction(() => {
|
||||
instantiationService.createInstance(LogsDataCleaner);
|
||||
this.ipc.registerChannel(REMOTE_FILE_SYSTEM_CHANNEL_NAME, new FileProviderChannel(environmentService, logService));
|
||||
|
@ -612,9 +547,7 @@ export class MainServer extends Server {
|
|||
this.ipc.registerChannel("gallery", galleryChannel);
|
||||
const telemetryChannel = new TelemetryChannel(telemetryService);
|
||||
this.ipc.registerChannel("telemetry", telemetryChannel);
|
||||
// tslint:disable-next-line no-unused-expression
|
||||
new ErrorTelemetry(telemetryService);
|
||||
resolve();
|
||||
resolve(new ErrorTelemetry(telemetryService));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -633,9 +566,6 @@ export class WebviewServer extends Server {
|
|||
requestPath: string,
|
||||
): Promise<Response> {
|
||||
const webviewPath = path.join(this.rootPath, "out/vs/workbench/contrib/webview/browser/pre");
|
||||
if (requestPath === "") {
|
||||
requestPath = "/index.html";
|
||||
}
|
||||
return this.getResource(path.join(webviewPath, base, requestPath));
|
||||
return this.getResource(path.join(webviewPath, base, requestPath || "/index.html"));
|
||||
}
|
||||
}
|
||||
|
|
168
src/tar.ts
168
src/tar.ts
|
@ -1,7 +1,7 @@
|
|||
import * as fs from "fs";
|
||||
import * as path from "path";
|
||||
import * as tarStream from "tar-stream";
|
||||
import { promisify } from "util";
|
||||
import * as util from "util";
|
||||
|
||||
import * as nls from "vs/nls";
|
||||
import * as vszip from "vs/base/node/zip";
|
||||
|
@ -14,7 +14,6 @@ const vszipBuffer = vszip.buffer;
|
|||
|
||||
export interface IExtractOptions {
|
||||
overwrite?: boolean;
|
||||
|
||||
/**
|
||||
* Source path within the TAR/ZIP archive. Only the files
|
||||
* contained in this path will be extracted.
|
||||
|
@ -28,142 +27,95 @@ export interface IFile {
|
|||
localPath?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Override the standard VS Code behavior for zipping extensions to use the TAR
|
||||
* format instead of ZIP.
|
||||
*/
|
||||
export const zip = (tarPath: string, files: IFile[]): Promise<string> => {
|
||||
return new Promise<string>((c, e): void => {
|
||||
export const tar = async (tarPath: string, files: IFile[]): Promise<string> => {
|
||||
const pack = tarStream.pack();
|
||||
const chunks: Buffer[] = [];
|
||||
const ended = new Promise<Buffer>((res): void => {
|
||||
pack.on("end", () => {
|
||||
res(Buffer.concat(chunks));
|
||||
});
|
||||
});
|
||||
pack.on("data", (chunk) => {
|
||||
chunks.push(chunk as Buffer);
|
||||
const ended = new Promise<Buffer>((resolve) => {
|
||||
pack.on("end", () => resolve(Buffer.concat(chunks)));
|
||||
});
|
||||
pack.on("data", (chunk: Buffer) => chunks.push(chunk));
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
const file = files[i];
|
||||
pack.entry({
|
||||
name: file.path,
|
||||
}, file.contents);
|
||||
pack.entry({ name: file.path }, file.contents);
|
||||
}
|
||||
pack.finalize();
|
||||
|
||||
ended.then((buffer) => {
|
||||
return promisify(fs.writeFile)(tarPath, buffer);
|
||||
}).then(() => {
|
||||
c(tarPath);
|
||||
}).catch((ex) => {
|
||||
e(ex);
|
||||
});
|
||||
});
|
||||
await util.promisify(fs.writeFile)(tarPath, await ended);
|
||||
return tarPath;
|
||||
};
|
||||
|
||||
/**
|
||||
* Override the standard VS Code behavior for extracting archives to first
|
||||
* attempt to process the archive as a TAR and then fall back to the original
|
||||
* implementation for processing ZIPs.
|
||||
*/
|
||||
export const extract = (archivePath: string, extractPath: string, options: IExtractOptions = {}, token: CancellationToken): Promise<void> => {
|
||||
return new Promise<void>((c, e): void => {
|
||||
extractTar(archivePath, extractPath, options, token).then(c).catch((ex) => {
|
||||
if (!ex.toString().includes("Invalid tar header")) {
|
||||
e(ex);
|
||||
|
||||
return;
|
||||
export const extract = async (archivePath: string, extractPath: string, options: IExtractOptions = {}, token: CancellationToken): Promise<void> => {
|
||||
try {
|
||||
await extractTar(archivePath, extractPath, options, token);
|
||||
} catch (error) {
|
||||
if (error.toString().includes("Invalid tar header")) {
|
||||
await vszipExtract(archivePath, extractPath, options, token);
|
||||
}
|
||||
}
|
||||
vszipExtract(archivePath, extractPath, options, token).then(c).catch(e);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Override the standard VS Code behavior for buffering archives to first
|
||||
* process the Buffer as a TAR and then fall back to the original
|
||||
* implementation for processing ZIPs.
|
||||
*/
|
||||
export const buffer = (targetPath: string, filePath: string): Promise<Buffer> => {
|
||||
return new Promise<Buffer>((c, e): void => {
|
||||
return new Promise<Buffer>(async (resolve, reject) => {
|
||||
try {
|
||||
let done: boolean = false;
|
||||
extractAssets(targetPath, new RegExp(filePath), (assetPath: string, data: Buffer) => {
|
||||
await extractAssets(targetPath, new RegExp(filePath), (assetPath: string, data: Buffer) => {
|
||||
if (path.normalize(assetPath) === path.normalize(filePath)) {
|
||||
done = true;
|
||||
c(data);
|
||||
resolve(data);
|
||||
}
|
||||
}).then(() => {
|
||||
if (!done) {
|
||||
e("couldn't find asset " + filePath);
|
||||
}
|
||||
}).catch((ex) => {
|
||||
if (!ex.toString().includes("Invalid tar header")) {
|
||||
e(ex);
|
||||
|
||||
return;
|
||||
}
|
||||
vszipBuffer(targetPath, filePath).then(c).catch(e);
|
||||
});
|
||||
if (!done) {
|
||||
throw new Error("couldn't find asset " + filePath);
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.toString().includes("Invalid tar header")) {
|
||||
vszipBuffer(targetPath, filePath).then(resolve).catch(reject);
|
||||
} else {
|
||||
reject(error);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Override the standard VS Code behavior for extracting assets from archive
|
||||
* Buffers to use the TAR format instead of ZIP.
|
||||
*/
|
||||
const extractAssets = (tarPath: string, match: RegExp, callback: (path: string, data: Buffer) => void): Promise<void> => {
|
||||
return new Promise<void>(async (c, e): Promise<void> => {
|
||||
try {
|
||||
const buffer = await promisify(fs.readFile)(tarPath);
|
||||
const extractAssets = async (tarPath: string, match: RegExp, callback: (path: string, data: Buffer) => void): Promise<void> => {
|
||||
const buffer = await util.promisify(fs.readFile)(tarPath);
|
||||
return new Promise<void>(async (resolve, reject): Promise<void> => {
|
||||
const extractor = tarStream.extract();
|
||||
extractor.once("error", e);
|
||||
extractor.on("entry", (header, stream, next) => {
|
||||
extractor.once("error", reject);
|
||||
extractor.on("entry", async (header, stream, next) => {
|
||||
const name = header.name;
|
||||
if (match.test(name)) {
|
||||
extractData(stream).then((data) => {
|
||||
callback(name, data);
|
||||
next();
|
||||
}).catch(e);
|
||||
}).catch(reject);
|
||||
stream.resume();
|
||||
} else {
|
||||
stream.on("end", () => {
|
||||
next();
|
||||
});
|
||||
stream.on("end", () => next());
|
||||
stream.resume();
|
||||
}
|
||||
});
|
||||
extractor.on("finish", () => {
|
||||
c();
|
||||
});
|
||||
extractor.on("finish", resolve);
|
||||
extractor.write(buffer);
|
||||
extractor.end();
|
||||
} catch (ex) {
|
||||
e(ex);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const extractData = (stream: NodeJS.ReadableStream): Promise<Buffer> => {
|
||||
return new Promise<Buffer>((c, e): void => {
|
||||
return new Promise((resolve, reject): void => {
|
||||
const fileData: Buffer[] = [];
|
||||
stream.on("data", (data) => fileData.push(data));
|
||||
stream.on("end", () => {
|
||||
const fd = Buffer.concat(fileData);
|
||||
c(fd);
|
||||
});
|
||||
stream.on("error", e);
|
||||
stream.on("end", () => resolve(Buffer.concat(fileData)));
|
||||
stream.on("error", reject);
|
||||
});
|
||||
};
|
||||
|
||||
const extractTar = (tarPath: string, targetPath: string, options: IExtractOptions = {}, token: CancellationToken): Promise<void> => {
|
||||
return new Promise<void>(async (c, e): Promise<void> => {
|
||||
try {
|
||||
const extractTar = async (tarPath: string, targetPath: string, options: IExtractOptions = {}, token: CancellationToken): Promise<void> => {
|
||||
const buffer = await util.promisify(fs.readFile)(tarPath);
|
||||
return new Promise<void>(async (resolve, reject): Promise<void> => {
|
||||
const sourcePathRegex = new RegExp(options.sourcePath ? `^${options.sourcePath}` : "");
|
||||
const buffer = await promisify(fs.readFile)(tarPath);
|
||||
const extractor = tarStream.extract();
|
||||
extractor.once("error", e);
|
||||
extractor.on("entry", (header, stream, next) => {
|
||||
extractor.once("error", reject);
|
||||
extractor.on("entry", async (header, stream, next) => {
|
||||
const rawName = path.normalize(header.name);
|
||||
|
||||
const nextEntry = (): void => {
|
||||
|
@ -171,54 +123,38 @@ const extractTar = (tarPath: string, targetPath: string, options: IExtractOption
|
|||
next();
|
||||
};
|
||||
|
||||
if (token.isCancellationRequested) {
|
||||
return nextEntry();
|
||||
}
|
||||
|
||||
if (!sourcePathRegex.test(rawName)) {
|
||||
if (token.isCancellationRequested || !sourcePathRegex.test(rawName)) {
|
||||
return nextEntry();
|
||||
}
|
||||
|
||||
const fileName = rawName.replace(sourcePathRegex, "");
|
||||
const targetFileName = path.join(targetPath, fileName);
|
||||
if (/\/$/.test(fileName)) {
|
||||
stream.resume();
|
||||
mkdirp(targetFileName).then(() => {
|
||||
next();
|
||||
}, e);
|
||||
|
||||
return;
|
||||
return mkdirp(targetFileName).then(nextEntry);
|
||||
}
|
||||
|
||||
const dirName = path.dirname(fileName);
|
||||
const targetDirName = path.join(targetPath, dirName);
|
||||
if (targetDirName.indexOf(targetPath) !== 0) {
|
||||
e(nls.localize("invalid file", "Error extracting {0}. Invalid file.", fileName));
|
||||
|
||||
return nextEntry();
|
||||
return reject(nls.localize("invalid file", "Error extracting {0}. Invalid file.", fileName));
|
||||
}
|
||||
|
||||
return mkdirp(targetDirName, undefined, token).then(() => {
|
||||
const fstream = fs.createWriteStream(targetFileName, { mode: header.mode });
|
||||
fstream.once("close", () => {
|
||||
next();
|
||||
});
|
||||
fstream.once("error", e);
|
||||
fstream.once("close", () => next());
|
||||
fstream.once("error", reject);
|
||||
stream.pipe(fstream);
|
||||
stream.resume();
|
||||
});
|
||||
});
|
||||
extractor.once("finish", c);
|
||||
extractor.once("finish", resolve);
|
||||
extractor.write(buffer);
|
||||
extractor.end();
|
||||
} catch (ex) {
|
||||
e(ex);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// Override original functionality so we can use tar instead of zip.
|
||||
const target = vszip as typeof vszip;
|
||||
target.zip = zip;
|
||||
target.zip = tar;
|
||||
target.extract = extract;
|
||||
target.buffer = buffer;
|
||||
|
|
|
@ -4,8 +4,7 @@ module.exports = (remoteAuthority, https) => {
|
|||
return {
|
||||
transformIncoming: (uri) => {
|
||||
switch (uri.scheme) {
|
||||
case "https": return { scheme: "file", path: uri.path };
|
||||
case "http": return { scheme: "file", path: uri.path };
|
||||
case "https": case "http": return { scheme: "file", path: uri.path };
|
||||
case "file": return { scheme: "vscode-local", path: uri.path };
|
||||
default: return uri;
|
||||
}
|
||||
|
|
|
@ -1,3 +1 @@
|
|||
module.exports = (remoteAuthority) => {
|
||||
return require("./uriTransformerHttp")(remoteAuthority, true);
|
||||
};
|
||||
module.exports = (remoteAuthority) => require("./uriTransformerHttp")(remoteAuthority, true);
|
||||
|
|
32
src/util.ts
32
src/util.ts
|
@ -25,8 +25,6 @@ export const generateCertificate = async (): Promise<{ cert: string, certKey: st
|
|||
util.promisify(fs.exists)(paths.certKey),
|
||||
]);
|
||||
|
||||
await mkdirp(tmpdir);
|
||||
|
||||
if (!exists[0] || !exists[1]) {
|
||||
const pem = require.__$__nodeRequire(path.resolve(__dirname, "../node_modules/pem/lib/pem")) as typeof import("pem");
|
||||
const certs = await new Promise<import("pem").CertificateCreationResult>((resolve, reject): void => {
|
||||
|
@ -37,6 +35,7 @@ export const generateCertificate = async (): Promise<{ cert: string, certKey: st
|
|||
resolve(result);
|
||||
});
|
||||
});
|
||||
await mkdirp(tmpdir);
|
||||
await Promise.all([
|
||||
util.promisify(fs.writeFile)(paths.cert, certs.certificate),
|
||||
util.promisify(fs.writeFile)(paths.certKey, certs.serviceKey),
|
||||
|
@ -46,16 +45,10 @@ export const generateCertificate = async (): Promise<{ cert: string, certKey: st
|
|||
return paths;
|
||||
};
|
||||
|
||||
let secure: boolean;
|
||||
export const useHttpsTransformer = (): void => {
|
||||
secure = true;
|
||||
};
|
||||
|
||||
let transformer: string = "uriTransformerHttp";
|
||||
export const useHttpsTransformer = (): string => transformer = "uriTransformerHttps";
|
||||
export const uriTransformerPath = (): string => {
|
||||
return getPathFromAmdModule(
|
||||
require,
|
||||
"vs/server/src/uriTransformerHttp" + (secure ? "s": ""),
|
||||
);
|
||||
return getPathFromAmdModule(require, `vs/server/src/${transformer}`);
|
||||
};
|
||||
|
||||
export const getUriTransformer = (remoteAuthority: string): URITransformer => {
|
||||
|
@ -87,25 +80,16 @@ export const isWsl = async (): Promise<boolean> => {
|
|||
};
|
||||
|
||||
export const open = async (url: string): Promise<void> => {
|
||||
let command: string;
|
||||
const args = <string[]>[];
|
||||
const options = <cp.SpawnOptions>{};
|
||||
const platform = await isWsl() ? "wsl" : process.platform;
|
||||
switch (platform) {
|
||||
case "darwin":
|
||||
command = "open";
|
||||
break;
|
||||
case "win32":
|
||||
case "wsl":
|
||||
let command = platform === "darwin" ? "open" : "xdg-open";
|
||||
if (platform === "win32" || platform === "wsl") {
|
||||
command = platform === "wsl" ? "cmd.exe" : "cmd";
|
||||
args.push("/c", "start", '""', "/b");
|
||||
url = url.replace(/&/g, "^&");
|
||||
default:
|
||||
command = "xdg-open";
|
||||
break;
|
||||
}
|
||||
args.push(url);
|
||||
const proc = cp.spawn(command, args, options);
|
||||
const proc = cp.spawn(command, [...args, url], options);
|
||||
await new Promise((resolve, reject) => {
|
||||
proc.on("error", reject);
|
||||
proc.on("close", (code) => {
|
||||
|
@ -125,8 +109,6 @@ export const unpackExecutables = async (): Promise<void> => {
|
|||
const destination = path.join(tmpdir, path.basename(rgPath || ""));
|
||||
if (rgPath && !(await util.promisify(fs.exists)(destination))) {
|
||||
await mkdirp(tmpdir);
|
||||
// TODO: I'm not sure why but copyFile doesn't work in the Docker build.
|
||||
// await util.promisify(fs.copyFile)(rgPath, destination);
|
||||
await util.promisify(fs.writeFile)(destination, await util.promisify(fs.readFile)(rgPath));
|
||||
await util.promisify(fs.chmod)(destination, "755");
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue