Archived
1
0

Getting the client to run (#12)

* Clean up workbench and integrate initialization data

* Uncomment Electron fill

* Run server & client together

* Clean up Electron fill & patch

* Bind fs methods

This makes them usable with the promise form:
`promisify(access)(...)`.

* Add space between tag and title to browser logger

* Add typescript dep to server and default __dirname for path

* Serve web files from server

* Adjust some dev options

* Rework workbench a bit to use a class and catch unexpected errors

* No mkdirs for now, fix util fill, use bash with exec

* More fills, make general client abstract

* More fills

* Fix cp.exec

* Fix require calls in fs fill being aliased

* Create data and storage dir

* Implement fs.watch

Using exec for now.

* Implement storage database fill

* Fix os export and homedir

* Add comment to use navigator.sendBeacon

* Fix fs callbacks (some args are optional)

* Make sure data directory exists when passing it back

* Update patch

* Target es5

* More fills

* Add APIs required for bootstrap-fork to function (#15)

* Add bootstrap-fork execution

* Add createConnection

* Bundle bootstrap-fork into cli

* Remove .node directory created from spdlog

* Fix npm start

* Remove unnecessary comment

* Add webpack-hot-middleware if CLI env is not set

* Add restarting to shared process

* Fix starting with yarn
This commit is contained in:
Asher
2019-01-18 15:46:40 -06:00
committed by Kyle Carberry
parent 05899b5edf
commit 72bf4547d4
80 changed files with 5183 additions and 9697 deletions

1
packages/vscode/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
bin

View File

@ -1,5 +1,12 @@
{
"name": "@coder/vscode",
"description": "VS Code implementation of the browser-based IDE client.",
"main": "src/index.ts"
"main": "src/index.ts",
"scripts": {
"build:bootstrap-fork": "../../node_modules/.bin/webpack --config ./webpack.config.bootstrap.js"
},
"dependencies": {
"spdlog": "^0.7.2",
"string-replace-loader": "^2.1.1"
}
}

View File

@ -0,0 +1,133 @@
import "./fill/require";
import "./fill/storageDatabase";
import "./fill/windowsService";
import { fork } from "child_process";
import { Client as IDEClient, IURI, IURIFactory } from "@coder/ide";
import { logger } from "@coder/logger";
import { registerContextMenuListener } from "vs/base/parts/contextmenu/electron-main/contextmenu";
import { LogLevel } from "vs/platform/log/common/log";
import { toLocalISOString } from "vs/base/common/date";
// import { RawContextKey, IContextKeyService } from "vs/platform/contextkey/common/contextkey";
import { URI } from "vs/base/common/uri";
import { Protocol, ISharedProcessInitData } from "./protocol";
import * as paths from "./fill/paths";
import "./firefox";
export class Client extends IDEClient {
private readonly sharedProcessLogger = logger.named("shr proc");
private readonly windowId = parseInt(toLocalISOString(new Date()).replace(/[-:.TZ]/g, ""), 10);
private readonly version = "hello"; // TODO: pull from package.json probably
private readonly bootstrapForkLocation = "/bootstrap"; // TODO: location.
public readonly protocolPromise: Promise<Protocol>;
private protoResolve: ((protocol: Protocol) => void) | undefined;
public constructor() {
super();
process.env.VSCODE_LOGS = "/tmp/vscode-online/logs"; // TODO: use tmpdir or get log directory from init data.
this.protocolPromise = new Promise((resolve): void => {
this.protoResolve = resolve;
});
}
protected initialize(): Promise<void> {
this.task("Start shared process", 5, async () => {
const protocol = await this.forkSharedProcess();
this.protoResolve!(protocol);
}).catch(() => undefined);
registerContextMenuListener();
return this.task("Start workbench", 1000, async (initData) => {
const { startup } = require("./startup");
await startup({
machineId: "1",
windowId: this.windowId,
logLevel: LogLevel.Info,
mainPid: 1,
appRoot: initData.dataDirectory,
execPath: initData.tmpDirectory,
userEnv: {},
nodeCachedDataDir: initData.tmpDirectory,
perfEntries: [],
_: [],
folderUri: URI.file(initData.dataDirectory),
});
// TODO: Set up clipboard context.
// const workbench = workbenchShell.workbench;
// const contextKeys = workbench.workbenchParams.serviceCollection.get(IContextKeyService) as IContextKeyService;
// const clipboardContextKey = new RawContextKey("nativeClipboard", this.clipboard.isSupported);
// const bounded = clipboardContextKey.bindTo(contextKeys);
// this.clipboard.onPermissionChange((enabled) => {
// bounded.set(enabled);
// });
this.clipboard.initialize();
}, this.initData);
}
public async forkSharedProcess(): Promise<Protocol> {
const childProcess = fork(this.bootstrapForkLocation, ["--shared"], {
env: {
"VSCODE_ALLOW_IO": "true",
"AMD_ENTRYPOINT": "vs/code/electron-browser/sharedProcess/sharedProcessClient",
},
});
childProcess.stderr.on("data", (data) => {
this.sharedProcessLogger.error("stderr: " + data);
});
const protocol = Protocol.fromProcess(childProcess);
await new Promise((resolve, reject): void => {
protocol.onClose(() => {
reject(new Error("unable to establish connection to shared process"));
});
const listener = protocol.onMessage((message) => {
const messageStr = message.toString();
this.sharedProcessLogger.debug(messageStr);
switch (messageStr) {
case "handshake:hello":
protocol.send(Buffer.from(JSON.stringify({
// Using the version so if we get a new mount, it spins up a new
// shared process.
socketPath: `/tmp/vscode-online/shared-${this.version}.sock`,
serviceUrl: "", // TODO
logsDir: process.env.VSCODE_LOGS,
windowId: this.windowId,
logLevel: LogLevel.Info,
} as ISharedProcessInitData)));
break;
case "handshake:ready":
listener.dispose();
resolve();
break;
}
});
});
return protocol;
}
protected createUriFactory(): IURIFactory {
return {
// TODO: not sure why this is an error.
// tslint:disable-next-line no-any
create: <URI>(uri: IURI): URI => URI.from(uri) as any,
file: (path: string): IURI => URI.file(path),
parse: (raw: string): IURI => URI.parse(raw),
};
}
}
export const client = new Client();
client.initData.then((initData) => {
paths.appData = initData.dataDirectory;
paths.defaultUserData = initData.dataDirectory;
});

View File

@ -0,0 +1,11 @@
import { URI } from "vs/base/common/uri";
export const getPathFromAmdModule = (_: typeof require, relativePath: string): string => {
if (process.mainModule && process.mainModule.filename) {
const index = process.mainModule.filename.lastIndexOf("/");
return process.mainModule.filename.slice(0, index);
}
return relativePath ? URI.parse(require.toUrl(relativePath)).fsPath : "";
};

View File

@ -0,0 +1 @@
export const gracefulify = (): void => undefined;

View File

@ -1,8 +1,13 @@
module.exports = {
getCurrentKeyboardLayout: (): null => {
class NativeKeymap {
public getCurrentKeyboardLayout(): null {
return null;
},
getKeyMap: (): undefined[] => {
}
public getKeyMap(): undefined[] {
return [];
},
};
}
}
export = new NativeKeymap();

View File

@ -70,4 +70,4 @@ const ptyType: nodePtyType = {
};
module.exports = ptyType;
exports = ptyType;

View File

@ -0,0 +1,2 @@
// TODO: obtain this in a reasonable way.
export default { name: "vscode", version: "1.31.1" };

View File

@ -0,0 +1,7 @@
const paths = {
appData: "/tmp",
defaultUserData: "/tmp",
};
export let getAppDataPath = (): string => paths.appData;
export let getDefaultUserDataPath = (): string => paths.defaultUserData;

View File

@ -0,0 +1,24 @@
import { IProductConfiguration } from "vs/platform/node/product";
const product = {
nameShort: "VSCode",
nameLong: "vscode online",
dataFolderName: ".vscode-online",
extensionsGallery: {
serviceUrl: "",
},
extensionExecutionEnvironments: {
"wayou.vscode-todo-highlight": "worker",
"vscodevim.vim": "worker",
"coenraads.bracket-pair-colorizer": "worker",
},
fetchUrl: "",
} as IProductConfiguration;
if (process.env['VSCODE_DEV']) {
product.nameShort += ' Dev';
product.nameLong += ' Dev';
product.dataFolderName += '-dev';
}
export default product;

View File

@ -0,0 +1,3 @@
// TODO: ?
// tslint:disable-next-line no-any
(global as any).requireToUrl = (path: string): string => `${location.protocol}//{location.host}/${path}`;

View File

@ -0,0 +1,186 @@
import { exec } from "child_process";
import { promisify } from "util";
import { appendFile, stat, readdir } from "fs";
import { RotatingLogger as NodeRotatingLogger } from "spdlog";
import { logger, field } from "@coder/logger";
import { escapePath } from "@coder/protocol";
// TODO: It would be better to spawn an actual spdlog instance on the server and
// use that for the logging. Or maybe create an instance when the server starts,
// and just always use that one (make it part of the protocol).
export class RotatingLogger implements NodeRotatingLogger {
private format = true;
private buffer = "";
private flushPromise: Promise<void> | undefined;
private name: string;
private logDirectory: string;
private escapedLogDirectory: string;
private fullFilePath: string;
private fileName: string;
private fileExt: string | undefined;
private escapedFilePath: string;
private filesize: number;
private filecount: number;
public constructor(name: string, filePath: string, filesize: number, filecount: number) {
this.name = name;
this.filesize = filesize;
this.filecount = filecount;
this.fullFilePath = filePath;
const slashIndex = filePath.lastIndexOf("/");
const dotIndex = filePath.lastIndexOf(".");
this.logDirectory = slashIndex !== -1 ? filePath.substring(0, slashIndex) : "/";
this.fileName = filePath.substring(slashIndex + 1, dotIndex !== -1 ? dotIndex : undefined);
this.fileExt = dotIndex !== -1 ? filePath.substring(dotIndex + 1) : undefined;
this.escapedLogDirectory = escapePath(this.logDirectory);
this.escapedFilePath = escapePath(filePath);
this.flushPromise = new Promise((resolve): void => {
exec(`mkdir -p ${this.escapedLogDirectory}; touch ${this.escapedFilePath}`, async (error) => {
if (!error) {
try {
await this.doFlush();
} catch (e) {
error = e;
}
}
if (error) {
logger.error(error.message, field("error", error));
}
this.flushPromise = undefined;
resolve();
});
});
}
public trace(message: string): void {
this.write("trace", message);
}
public debug(message: string): void {
this.write("debug", message);
}
public info(message: string): void {
this.write("info", message);
}
public warn(message: string): void {
this.write("warn", message);
}
public error(message: string): void {
this.write("error", message);
}
public critical(message: string): void {
this.write("critical", message);
}
public setLevel(): void {
// Should output everything.
}
public clearFormatters(): void {
this.format = false;
}
/**
* Flushes the buffer. Only one process runs at a time to prevent race
* conditions.
*/
public flush(): Promise<void> {
if (!this.flushPromise) {
this.flushPromise = this.doFlush().then(() => {
this.flushPromise = undefined;
}).catch((error) => {
this.flushPromise = undefined;
logger.error(error.message, field("error", error));
});
}
return this.flushPromise;
}
public drop(): void {
this.buffer = "";
}
private pad(num: number, length: number = 2, prefix: string = "0"): string {
const str = num.toString();
return (length > str.length ? prefix.repeat(length - str.length) : "") + str;
}
private write(severity: string, message: string): void {
if (this.format) {
const date = new Date();
const dateStr = `${date.getFullYear()}-${this.pad(date.getMonth() + 1)}-${this.pad(date.getDate())}`
+ ` ${this.pad(date.getHours())}:${this.pad(date.getMinutes())}:${this.pad(date.getSeconds())}.${this.pad(date.getMilliseconds(), 3)}`;
this.buffer += `[${dateStr}] [${this.name}] [${severity}] `;
}
this.buffer += message;
if (this.format) {
this.buffer += "\n";
}
this.flush();
}
private async rotate(): Promise<void> {
const stats = await promisify(stat)(this.fullFilePath);
if (stats.size < this.filesize) {
return;
}
const reExt = typeof this.fileExt !== "undefined" ? `\\.${this.fileExt}` : "";
const re = new RegExp(`^${this.fileName}(?:\\.(\\d+))?${reExt}$`);
const orderedFiles: string[] = [];
(await promisify(readdir)(this.logDirectory)).forEach((file) => {
const match = re.exec(file);
if (match) {
orderedFiles[typeof match[1] !== "undefined" ? parseInt(match[1], 10) : 0] = file;
}
});
// Rename in reverse so we don't overwrite before renaming something.
let count = 0;
const command = orderedFiles.map((file) => {
const fileExt = typeof this.fileExt !== "undefined" ? `.${this.fileExt}` : "";
const newFile = `${this.logDirectory}/${this.fileName}.${++count}${fileExt}`;
return count >= this.filecount
? `rm ${escapePath(this.logDirectory + "/" + file)}`
: `mv ${escapePath(this.logDirectory + "/" + file)} ${escapePath(newFile)}`;
}).reverse().concat([
`touch ${escapePath(this.fullFilePath)}`,
]).join(";");
await promisify(exec)(command);
}
/**
* Flushes the entire buffer, including anything added in the meantime, and
* rotates the log if necessary.
*/
private async doFlush(): Promise<void> {
const writeBuffer = async (): Promise<void> => {
const toWrite = this.buffer;
this.buffer = "";
await promisify(appendFile)(this.fullFilePath, toWrite);
};
while (this.buffer.length > 0) {
await writeBuffer();
await this.rotate();
}
}
}
export const setAsyncMode = (): void => {
// Nothing to do.
};

View File

@ -0,0 +1,22 @@
import { StdioIpcHandler } from "@coder/server/src/ipc";
import { IpcRenderer } from "electron";
export * from "@coder/ide/src/fill/electron";
class StdioIpcRenderer extends StdioIpcHandler implements IpcRenderer {
public sendTo(windowId: number, channel: string, ...args: any[]): void {
throw new Error("Method not implemented.");
}
public sendToHost(channel: string, ...args: any[]): void {
throw new Error("Method not implemented.");
}
public eventNames(): string[] {
return super.eventNames() as string[];
}
}
export const ipcRenderer = new StdioIpcRenderer();

View File

@ -0,0 +1,78 @@
import { readFile, writeFile } from "fs";
import { promisify } from "util";
import { Event } from "vs/base/common/event";
import * as storage from "vs/base/node/storage";
export class StorageDatabase implements storage.IStorageDatabase {
public readonly onDidChangeItemsExternal = Event.None;
private items = new Map<string, string>();
private fetched: boolean = false;
public constructor(private readonly path: string) {
window.addEventListener("unload", () => {
if (!navigator.sendBeacon) {
throw new Error("cannot save state");
}
// TODO: Need to use navigator.sendBeacon instead of the web socket, or we
// need to save when there is a change. Should we save as a sqlite3
// database instead of JSON? Could send to the server the way the global
// storage works. Or maybe fill `vscode-sqlite3` to do that.
this.save();
});
}
public async getItems(): Promise<Map<string, string>> {
if (this.fetched) {
return this.items;
}
try {
const contents = await promisify(readFile)(this.path, "utf8");
const json = JSON.parse(contents);
Object.keys(json).forEach((key) => {
this.items.set(key, json[key]);
});
} catch (error) {
if (error.code && error.code !== "ENOENT") {
throw error;
}
}
this.fetched = true;
return this.items;
}
public updateItems(request: storage.IUpdateRequest): Promise<void> {
if (request.insert) {
request.insert.forEach((value, key) => this.items.set(key, value));
}
if (request.delete) {
request.delete.forEach(key => this.items.delete(key));
}
return Promise.resolve();
}
public close(): Promise<void> {
return Promise.resolve();
}
public checkIntegrity(): Promise<string> {
return Promise.resolve("ok");
}
private save(): Promise<void> {
const json: { [key: string]: string } = {};
this.items.forEach((value, key) => {
json[key] = value;
});
return promisify(writeFile)(this.path, JSON.stringify(json));
}
}
// @ts-ignore
storage.SQLiteStorageDatabase = StorageDatabase;

View File

@ -0,0 +1,274 @@
import * as electron from "electron";
import { Emitter } from "@coder/events";
import * as windowsIpc from "vs/platform/windows/node/windowsIpc";
import { IWindowsService, INativeOpenDialogOptions, MessageBoxOptions, SaveDialogOptions, OpenDialogOptions, IMessageBoxResult, IDevToolsOptions, IEnterWorkspaceResult, CrashReporterStartOptions, INewWindowOptions } from "vs/platform/windows/common/windows";
import { ParsedArgs } from "vs/platform/environment/common/environment";
import { IWorkspaceIdentifier, IWorkspaceFolderCreationData, ISingleFolderWorkspaceIdentifier } from "vs/platform/workspaces/common/workspaces";
import { URI } from "vs/base/common/uri";
import { IRecentlyOpened } from "vs/platform/history/common/history";
import { ISerializableCommandAction } from "vs/platform/actions/common/actions";
// TODO: Might make sense to hook these straight in if we can.
// import { WindowsService as VSWindowsService } from "vs/platform/windows/electron-main/windowsService";
// import { WindowsManager } from "vs/code/electron-main/windows";
/**
* Instead of going to the shared process, we'll directly run these methods on
* the client. This setup means we can only control the current window.
*/
class WindowsService implements IWindowsService {
// tslint:disable-next-line no-any
public _serviceBrand: any;
private openEmitter = new Emitter<number>();
private focusEmitter = new Emitter<number>();
private blurEmitter = new Emitter<number>();
private maximizeEmitter = new Emitter<number>();
private unmaximizeEmitter = new Emitter<number>();
private recentlyOpenedChangeEmitter = new Emitter<void>();
public onWindowOpen = this.openEmitter.event;
public onWindowFocus = this.focusEmitter.event;
public onWindowBlur = this.blurEmitter.event;
public onWindowMaximize = this.maximizeEmitter.event;
public onWindowUnmaximize = this.unmaximizeEmitter.event;
public onRecentlyOpenedChange = this.recentlyOpenedChangeEmitter.event;
private window = new electron.BrowserWindow();
// Dialogs
public pickFileFolderAndOpen(_options: INativeOpenDialogOptions): Promise<void> {
throw new Error("not implemented");
}
public pickFileAndOpen(_options: INativeOpenDialogOptions): Promise<void> {
throw new Error("not implemented");
}
public pickFolderAndOpen(_options: INativeOpenDialogOptions): Promise<void> {
throw new Error("not implemented");
}
public pickWorkspaceAndOpen(_options: INativeOpenDialogOptions): Promise<void> {
throw new Error("not implemented");
}
public showMessageBox(_windowId: number, _options: MessageBoxOptions): Promise<IMessageBoxResult> {
throw new Error("not implemented");
}
public showSaveDialog(_windowId: number, _options: SaveDialogOptions): Promise<string> {
throw new Error("not implemented");
}
public showOpenDialog(_windowId: number, _options: OpenDialogOptions): Promise<string[]> {
throw new Error("not implemented");
}
public reloadWindow(windowId: number, _args?: ParsedArgs): Promise<void> {
return Promise.resolve(this.getWindowById(windowId).reload());
}
public openDevTools(_windowId: number, _options?: IDevToolsOptions): Promise<void> {
throw new Error("not implemented");
}
public toggleDevTools(_windowId: number): Promise<void> {
throw new Error("not implemented");
}
public closeWorkspace(_windowId: number): Promise<void> {
throw new Error("not implemented");
}
public enterWorkspace(_windowId: number, _path: string): Promise<IEnterWorkspaceResult> {
throw new Error("not implemented");
}
public createAndEnterWorkspace(_windowId: number, _folders?: IWorkspaceFolderCreationData[], _path?: string): Promise<IEnterWorkspaceResult> {
throw new Error("not implemented");
}
public saveAndEnterWorkspace(_windowId: number, _path: string): Promise<IEnterWorkspaceResult> {
throw new Error("not implemented");
}
public toggleFullScreen(windowId: number): Promise<void> {
const win = this.getWindowById(windowId);
return Promise.resolve(win.setFullScreen(!win.isFullScreen()));
}
public setRepresentedFilename(windowId: number, fileName: string): Promise<void> {
return Promise.resolve(this.getWindowById(windowId).setRepresentedFilename(fileName));
}
public addRecentlyOpened(_files: URI[]): Promise<void> {
throw new Error("not implemented");
}
public removeFromRecentlyOpened(_paths: (IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | URI | string)[]): Promise<void> {
throw new Error("not implemented");
}
public clearRecentlyOpened(): Promise<void> {
throw new Error("not implemented");
}
public getRecentlyOpened(_windowId: number): Promise<IRecentlyOpened> {
// TODO: properly implement.
return Promise.resolve({
workspaces: [],
files: [],
});
}
public focusWindow(windowId: number): Promise<void> {
return Promise.resolve(this.getWindowById(windowId).focus());
}
public closeWindow(_windowId: number): Promise<void> {
throw new Error("not implemented");
}
public isFocused(windowId: number): Promise<boolean> {
return Promise.resolve(this.getWindowById(windowId).isFocused());
}
public isMaximized(_windowId: number): Promise<boolean> {
throw new Error("not implemented");
}
public maximizeWindow(_windowId: number): Promise<void> {
throw new Error("not implemented");
}
public unmaximizeWindow(_windowId: number): Promise<void> {
throw new Error("not implemented");
}
public minimizeWindow(_windowId: number): Promise<void> {
throw new Error("not implemented");
}
public onWindowTitleDoubleClick(_windowId: number): Promise<void> {
throw new Error("not implemented");
}
public setDocumentEdited(_windowId: number, _flag: boolean): Promise<void> {
throw new Error("not implemented");
}
public quit(): Promise<void> {
throw new Error("not implemented");
}
public relaunch(_options: { addArgs?: string[], removeArgs?: string[] }): Promise<void> {
throw new Error("not implemented");
}
// macOS Native Tabs
public newWindowTab(): Promise<void> {
throw new Error("not implemented");
}
public showPreviousWindowTab(): Promise<void> {
throw new Error("not implemented");
}
public showNextWindowTab(): Promise<void> {
throw new Error("not implemented");
}
public moveWindowTabToNewWindow(): Promise<void> {
throw new Error("not implemented");
}
public mergeAllWindowTabs(): Promise<void> {
throw new Error("not implemented");
}
public toggleWindowTabsBar(): Promise<void> {
throw new Error("not implemented");
}
// macOS TouchBar
public updateTouchBar(_windowId: number, _items: ISerializableCommandAction[][]): Promise<void> {
throw new Error("not implemented");
}
// Shared process
public whenSharedProcessReady(): Promise<void> {
// TODO: Update once shared process is tied in.
return Promise.resolve();
}
public toggleSharedProcess(): Promise<void> {
throw new Error("not implemented");
}
// Global methods
public openWindow(_windowId: number, _paths: URI[], _options?: { forceNewWindow?: boolean, forceReuseWindow?: boolean, forceOpenWorkspaceAsFile?: boolean, args?: ParsedArgs }): Promise<void> {
throw new Error("not implemented");
}
public openNewWindow(_options?: INewWindowOptions): Promise<void> {
throw new Error("not implemented");
}
public showWindow(windowId: number): Promise<void> {
return Promise.resolve(this.getWindowById(windowId).show());
}
public getWindows(): Promise<{ id: number; workspace?: IWorkspaceIdentifier; folderUri?: ISingleFolderWorkspaceIdentifier; title: string; filename?: string; }[]> {
throw new Error("not implemented");
}
public getWindowCount(): Promise<number> {
return Promise.resolve(1);
}
public log(_severity: string, ..._messages: string[]): Promise<void> {
throw new Error("not implemented");
}
public showItemInFolder(_path: string): Promise<void> {
throw new Error("not implemented");
}
public getActiveWindowId(): Promise<number | undefined> {
return Promise.resolve(1);
}
public openExternal(_url: string): Promise<boolean> {
throw new Error("not implemented");
}
public startCrashReporter(_config: CrashReporterStartOptions): Promise<void> {
throw new Error("not implemented");
}
public openAboutDialog(): Promise<void> {
throw new Error("not implemented");
}
public resolveProxy(windowId: number, url: string): Promise<string | undefined> {
return new Promise((resolve): void => {
this.getWindowById(windowId).webContents.session.resolveProxy(url, (proxy) => {
resolve(proxy);
});
});
}
/**
* Get window by ID. For now this is always the current window.
*/
private getWindowById(_windowId: number): electron.BrowserWindow {
return this.window;
}
}
// @ts-ignore
windowsIpc.WindowsChannelClient = WindowsService;

View File

@ -1,38 +1 @@
import { field, logger, time } from "@coder/logger";
import { Client, IURI, setUriFactory } from "@coder/ide";
import { URI } from "vs/base/common/uri";
import "./firefox";
const load = (): Promise<void> => {
return new Promise((resolve, reject): void => {
setUriFactory({
// TODO: not sure why this is an error.
// tslint:disable-next-line no-any
create: <URI>(uri: IURI): URI => URI.from(uri) as any,
file: (path: string): IURI => URI.file(path),
parse: (raw: string): IURI => URI.parse(raw),
});
const client = new Client({
mkDirs: [
"~/vscode/extensions",
"~/.config/User",
],
});
const importTime = time(1500);
import(/* webpackPrefetch: true */ "./workbench").then((module) => {
logger.info("Loaded workbench bundle", field("duration", importTime));
const initTime = time(1500);
return module.initialize(client).then(() => {
logger.info("Initialized workbench", field("duration", initTime));
resolve();
});
}).catch((error) => {
reject(error);
});
});
};
export { load };
export * from "./client";

View File

@ -0,0 +1,129 @@
import { ChildProcess } from "child_process";
import { EventEmitter } from "events";
import { Protocol as VSProtocol } from "vs/base/parts/ipc/node/ipc.net";
import { LogLevel } from "vs/platform/log/common/log";
export interface ISharedProcessInitData {
socketPath: string;
serviceUrl: string;
logsDir: string;
windowId: number;
logLevel: LogLevel;
}
export interface IStdio {
onMessage: (cb: (data: string | Buffer) => void) => void;
sendMessage: (data: string | Buffer) => void;
onExit?: (cb: () => void) => void;
}
/**
* An implementation of net.Socket that uses stdio streams.
*/
class Socket {
private readonly emitter: EventEmitter;
public constructor(private readonly stdio: IStdio, ignoreFirst: boolean = false) {
this.emitter = new EventEmitter();
let first = true;
stdio.onMessage((data) => {
if (ignoreFirst && first) {
first = false;
return;
}
this.emitter.emit("data", Buffer.from(data.toString()));
});
if (stdio.onExit) {
stdio.onExit(() => {
this.emitter.emit("close");
});
}
}
public removeListener(event: string, listener: () => void): void {
this.emitter.removeListener(event, listener);
}
public once(event: string, listener: () => void): void {
this.emitter.once(event, listener);
}
public on(event: string, listener: () => void): void {
this.emitter.on(event, listener);
}
public end(): void {
// TODO: figure it out
}
public get destroyed(): boolean {
return false;
}
public write(data: string | Buffer): void {
this.stdio.sendMessage(data);
}
}
/**
* A protocol around a process, stream, or worker.
*/
export class Protocol extends VSProtocol {
public static fromProcess(childProcess: ChildProcess): Protocol {
return Protocol.fromStdio({
onMessage: (cb): void => {
childProcess.stdout.on("data", (data: string | Buffer) => {
cb(data);
});
},
sendMessage: (data): void => {
childProcess.stdin.write(data);
},
onExit: (cb): void => {
childProcess.on("exit", cb);
},
});
}
public static fromStream(
inStream: { on: (event: "data", cb: (b: string | Buffer) => void) => void },
outStream: { write: (b: string | Buffer) => void },
): Protocol {
return Protocol.fromStdio({
onMessage: (cb): void => {
inStream.on("data", (data) => {
cb(data);
});
},
sendMessage: (data): void => {
outStream.write(data);
},
});
}
public static fromWorker(worker: {
onmessage: (event: MessageEvent) => void;
postMessage: (data: string, origin?: string | string[]) => void;
}, ignoreFirst: boolean = false): Protocol {
return Protocol.fromStdio({
onMessage: (cb): void => {
worker.onmessage = (event: MessageEvent): void => {
cb(event.data);
};
},
sendMessage: (data): void => {
worker.postMessage(data.toString());
},
}, ignoreFirst);
}
public static fromStdio(stdio: IStdio, ignoreFirst?: boolean): Protocol {
return new Protocol(new Socket(stdio, ignoreFirst));
}
}

View File

@ -0,0 +1,151 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import "vs/loader";
// Base
import "vs/base/common/strings";
import "vs/base/common/errors";
// Configuration
import "vs/workbench/services/configuration/common/configurationExtensionPoint";
// Editor
import "vs/editor/editor.all";
// Platform
import "vs/platform/widget/browser/contextScopedHistoryWidget";
import "vs/platform/label/electron-browser/label.contribution";
// Menus/Actions
import "vs/workbench/services/actions/electron-browser/menusExtensionPoint";
// Views
import "vs/workbench/api/browser/viewsContainersExtensionPoint";
import "vs/workbench/api/browser/viewsExtensionPoint";
// Localizations
import "vs/workbench/parts/localizations/electron-browser/localizations.contribution";
// Workbench
import "vs/workbench/browser/actions/toggleActivityBarVisibility";
import "vs/workbench/browser/actions/toggleStatusbarVisibility";
import "vs/workbench/browser/actions/toggleSidebarVisibility";
import "vs/workbench/browser/actions/toggleSidebarPosition";
import "vs/workbench/browser/actions/toggleEditorLayout";
import "vs/workbench/browser/actions/toggleZenMode";
import "vs/workbench/browser/actions/toggleCenteredLayout";
import "vs/workbench/browser/actions/toggleTabsVisibility";
import "vs/workbench/parts/preferences/electron-browser/preferences.contribution";
import "vs/workbench/parts/preferences/browser/keybindingsEditorContribution";
import "vs/workbench/parts/logs/electron-browser/logs.contribution";
import "vs/workbench/browser/parts/quickopen/quickopen.contribution";
import "vs/workbench/parts/quickopen/browser/quickopen.contribution";
import "vs/workbench/browser/parts/editor/editorPicker";
import "vs/workbench/browser/parts/quickinput/quickInput.contribution";
import "vs/workbench/parts/files/electron-browser/explorerViewlet";
import "vs/workbench/parts/files/electron-browser/fileActions.contribution";
import "vs/workbench/parts/files/electron-browser/files.contribution";
import "vs/workbench/parts/backup/common/backup.contribution";
import "vs/workbench/parts/stats/node/stats.contribution";
import "vs/workbench/parts/splash/electron-browser/partsSplash.contribution";
import "vs/workbench/parts/search/electron-browser/search.contribution";
import "vs/workbench/parts/search/browser/searchView";
import "vs/workbench/parts/search/browser/openAnythingHandler";
import "vs/workbench/parts/scm/electron-browser/scm.contribution";
import "vs/workbench/parts/scm/electron-browser/scmViewlet";
import "vs/workbench/parts/debug/electron-browser/debug.contribution";
// import "vs/workbench/parts/debug/browser/debugQuickOpen";
// import "vs/workbench/parts/debug/electron-browser/repl";
// import "vs/workbench/parts/debug/browser/debugViewlet";
import "vs/workbench/parts/markers/electron-browser/markers.contribution";
import "vs/workbench/parts/comments/electron-browser/comments.contribution";
import "vs/workbench/parts/html/electron-browser/html.contribution";
import "vs/workbench/parts/url/electron-browser/url.contribution";
import "vs/workbench/parts/webview/electron-browser/webview.contribution";
import "vs/workbench/parts/welcome/walkThrough/electron-browser/walkThrough.contribution";
import "vs/workbench/parts/extensions/electron-browser/extensions.contribution";
import "vs/workbench/parts/extensions/browser/extensionsQuickOpen";
import "vs/workbench/parts/extensions/electron-browser/extensionsViewlet";
import "vs/workbench/parts/welcome/page/electron-browser/welcomePage.contribution";
import "vs/workbench/parts/output/electron-browser/output.contribution";
import "vs/workbench/parts/output/browser/outputPanel";
import "vs/workbench/parts/terminal/electron-browser/terminal.contribution";
import "vs/workbench/parts/terminal/browser/terminalQuickOpen";
import "vs/workbench/parts/terminal/electron-browser/terminalPanel";
import "vs/workbench/electron-browser/workbench";
// import "vs/workbench/parts/relauncher/electron-browser/relauncher.contribution";
import "vs/workbench/parts/tasks/electron-browser/task.contribution";
import "vs/workbench/parts/emmet/browser/emmet.browser.contribution";
import "vs/workbench/parts/emmet/electron-browser/emmet.contribution";
import "vs/workbench/parts/codeEditor/codeEditor.contribution";
import "vs/workbench/parts/execution/electron-browser/execution.contribution";
import "vs/workbench/parts/snippets/electron-browser/snippets.contribution";
import "vs/workbench/parts/snippets/electron-browser/snippetsService";
import "vs/workbench/parts/snippets/electron-browser/insertSnippet";
import "vs/workbench/parts/snippets/electron-browser/configureSnippets";
import "vs/workbench/parts/snippets/electron-browser/tabCompletion";
import "vs/workbench/parts/themes/electron-browser/themes.contribution";
// import "vs/workbench/parts/feedback/electron-browser/feedback.contribution";
import "vs/workbench/parts/welcome/gettingStarted/electron-browser/gettingStarted.contribution";
import "vs/workbench/parts/update/electron-browser/update.contribution";
// import "vs/workbench/parts/surveys/electron-browser/nps.contribution";
// import "vs/workbench/parts/surveys/electron-browser/languageSurveys.contribution";
import "vs/workbench/parts/performance/electron-browser/performance.contribution";
// import "vs/workbench/parts/cli/electron-browser/cli.contribution";
import "vs/workbench/api/electron-browser/extensionHost.contribution";
import "vs/workbench/electron-browser/main.contribution";
import { startup } from "vs/workbench/electron-browser/main";
// import "vs/workbench/parts/themes/test/electron-browser/themes.test.contribution";
import "vs/workbench/parts/watermark/electron-browser/watermark";
import "vs/workbench/parts/welcome/overlay/browser/welcomeOverlay";
import "vs/workbench/parts/outline/electron-browser/outline.contribution";
import "vs/workbench/services/bulkEdit/electron-browser/bulkEditService";
import "vs/workbench/parts/experiments/electron-browser/experiments.contribution";
import { URI } from "vs/base/common/uri";
export {
URI,
startup,
};

View File

@ -1,59 +0,0 @@
import { IStorageService, StorageScope } from "vs/platform/storage/common/storage";
export class StorageService implements IStorageService {
public _serviceBrand: any;
private _globalObject: { [key: string]: any };
private _workspaceObject: { [ key: string]: any };
public constructor(globalState: object, workspaceState: object) {
this._globalObject = globalState;
this._workspaceObject = workspaceState;
}
public get globalObject() {
return this._globalObject;
}
public get workspaceObject() {
return this._workspaceObject;
}
public store(key: string, value: any, scope?: StorageScope): void {
this.getObject(scope)[key] = value;
}
public remove(key: string, scope?: StorageScope): void {
delete this.getObject(scope)[key];
}
public get(key: string, scope?: StorageScope, defaultValue?: string): string {
return this.getObject(scope)[key] || defaultValue;
}
public getInteger(key: string, scope?: StorageScope, defaultValue?: number): number {
return parseInt(this.get(key, scope), 10) || defaultValue;
}
public getBoolean(key: string, scope?: StorageScope, defaultValue?: boolean): boolean {
const v = this.get(key, scope);
if (typeof v !== "undefined") {
return v === "true";
}
return defaultValue;
}
private getObject(scope = StorageScope.GLOBAL): { [key: string]: any } {
switch (scope) {
case StorageScope.GLOBAL:
return this._globalObject;
case StorageScope.WORKSPACE:
return this._workspaceObject;
default:
throw new Error("unsupported storage scope");
}
}
}

View File

@ -1,224 +0,0 @@
import { Client } from "@coder/ide";
import { Emitter } from "@coder/events";
import { logger } from "@coder/logger";
import { Protocol } from "vs/base/parts/ipc/node/ipc.net";
import { IModelService } from "vs/editor/common/services/modelService";
import { ICodeEditorService } from "vs/editor/browser/services/codeEditorService";
import { registerContextMenuListener } from "vs/base/parts/contextmenu/electron-main/contextmenu";
import { Workbench } from "vs/workbench/electron-browser/workbench";
import { IDecorationsService } from "vs/workbench/services/decorations/browser/decorations";
import { LogLevel } from "vs/platform/log/common/log";
import { INotificationService, Severity } from "vs/platform/notification/common/notification";
import { toLocalISOString } from "vs/base/common/date";
import { RawContextKey, IContextKeyService } from "vs/platform/contextkey/common/contextkey";
import { StorageService } from "./storageService";
let protoResolve: (protocol: Protocol) => void;
export const protocolPromise = new Promise<Protocol>((res) => {
protoResolve = res;
});
let storageResolve: (storageService: StorageService) => void;
export const getStorageService = new Promise<StorageService>((res) => {
storageResolve = res;
});
export let systemExtensionsLocation: string;
export let forkedBinLocation: string;
const hasNativeClipboard = typeof navigator !== "undefined" && typeof (navigator as any).clipboard !== "undefined" && typeof (navigator as any).clipboard.readText !== "undefined";
let isEnabled: boolean = false;
const clipboardEnabledEmitter = new Emitter<boolean>();
export const nativeClipboard: {
readonly contextKey: RawContextKey<boolean>;
readonly instance: {
readText(): Promise<string>;
writeText(value: string): Promise<void>;
};
readonly onChange: Event<boolean>;
readonly isEnabled: boolean;
} = {
contextKey: new RawContextKey('nativeClipboard', hasNativeClipboard),
instance: hasNativeClipboard ? (navigator as any).clipboard : undefined,
get onChange(): Event<boolean> {
return clipboardEnabledEmitter.event;
},
get isEnabled(): boolean {
return isEnabled;
}
};
let workbench: Workbench;
function getModelService(): IModelService {
return workbench.workbenchParams.serviceCollection.get<IModelService>(IModelService) as IModelService;
}
function getCodeEditorService(): ICodeEditorService {
return workbench.workbenchParams.serviceCollection.get(ICodeEditorService) as ICodeEditorService;
}
function getNotificationService(): INotificationService {
return workbench.workbenchParams.serviceCollection.get(INotificationService) as INotificationService;
}
export const initialize = async (client: Client): Promise<void> => {
window.addEventListener("contextmenu", (event) => {
event.preventDefault();
});
// TODO: Fetch configuration.
const storageServicePromise = client.wrapTask("Set configurations", 5, async (state) => {
const storageService = new StorageService(state.global, state.workspace);
storageResolve(storageService);
return storageService;
}, client.state);
// Set up window ID for logging. We'll also use a static logging directory
// otherwise we'd have to get the log directory back from the currently
// running shared process. This allows us to not wait for that. Each window
// will still have its own logging within that directory.
const windowId = parseInt(toLocalISOString(new Date()).replace(/[-:.TZ]/g, ""), 10);
process.env.VSCODE_LOGS = "/tmp/vscode-logs";
client.wrapTask("Start shared process", 5, async (api, wush, mountPath) => {
const session = wush.execute({
command: "bash -c 'VSCODE_ALLOW_IO=true"
+ " AMD_ENTRYPOINT=vs/code/electron-browser/sharedProcess/sharedProcessClient"
+ ` nice -n -17 ${nodePath} ${bootstrapForkLocation} --client'`,
});
const sharedProcessLogger = logger.named("shr proc");
session.onStderr((data) => {
sharedProcessLogger.error("stderr: " + data);
});
session.onDone(() => {
workbenchPromise.then(() => {
getNotificationService().prompt(
Severity.Error,
"Shared process terminated unexpectedly.",
[{
label: "Reload IDE",
run: (): void => {
window.location.reload();
},
}],
);
});
});
const protocol = Protocol.fromStdio({
onMessage: (cb) => {
session.onStdout((data) => {
cb(Buffer.from(data as any));
}, true);
},
sendMessage: (data) => {
session.sendStdin(data);
},
});
await new Promise((resolve) => {
const listener = protocol.onMessage((message) => {
const messageStr = message.toString();
sharedProcessLogger.debug(messageStr);
switch (messageStr) {
case "handshake:hello":
protocol.send(Buffer.from(JSON.stringify({
// Using the mount path so if we get a new mount, it spins up a new shared
// process since it or the builtin extensions could contain changes.
sharedIPCHandle: `/tmp/vscode-shared${mountPath.replace(/\//g, "-")}.sock`,
serviceUrl: api.environment.appURL("extensions-api"),
logsDir: process.env.VSCODE_LOGS,
nodePath,
bootstrapForkLocation,
args: {},
windowId,
logLevel: LogLevel.Info,
} as ISharedProcessInitData)));
break;
case "handshake:ready":
listener.dispose();
resolve();
break;
}
});
});
protoResolve(protocol);
}, client.api, client.wush, mountPromise);
const { startup, URI } = require('vs/workbench/workbench.main');
require("os").homedir = () => {
// TODO: update this as well as folderURL
return "/root";
};
require("path").posix = require("path");
registerContextMenuListener();
const workbenchPromise = client.wrapTask("Start workbench", 1000, async (workspace, mountPath) => {
const workbenchShellPromise = startup({
machineId: "1",
windowId,
logLevel: LogLevel.Info,
mainPid: 1,
appRoot: mountPath,
execPath: "/tmp",
userEnv: {},
nodeCachedDataDir: "/tmp",
perfEntries: [],
_: undefined,
folderUri: URI.file(workspace.mountUri.path),
});
const workbenchShell = await workbenchShellPromise;
workbench = workbenchShell.workbench;
const contextKeys = workbench.workbenchParams.serviceCollection.get(IContextKeyService) as IContextKeyService;
const bounded = nativeClipboard.contextKey.bindTo(contextKeys);
const navigatorClip = (navigator as any).clipboard;
const navigatorPerms = (navigator as any).permissions;
if (navigatorClip && navigatorPerms) {
navigatorPerms.query({
name: "clipboard-read",
}).then((permissionStatus) => {
const updateStatus = () => {
if (permissionStatus.state === "denied") {
isEnabled = false;
clipboardEnabledEmitter.emit(false);
bounded.set(false);
} else {
isEnabled = true;
clipboardEnabledEmitter.emit(true);
bounded.set(true);
}
};
updateStatus();
permissionStatus.onchange = () => {
updateStatus();
};
});
}
const decorations = workbench.workbenchParams.serviceCollection.get(IDecorationsService) as IDecorationsService;
await registerCollaboratorDecorations(client, decorations);
return workbenchShell;
}, client.mkDirs);
client.wrapTask("Set up saving state", 5, async () => {
if (!navigator.sendBeacon) {
throw new Error("cannot save state");
}
// TODO: save storageSevice.globalObject and storageService.workspaceObject
});
await workbenchPromise;
};

View File

@ -0,0 +1,123 @@
const path = require("path");
const webpack = require("webpack");
const root = path.resolve(__dirname, "..", "..");
const fills = path.join(root, "packages", "ide", "src", "fill");
const vscodeFills = path.join(root, "packages", "vscode", "src", "fill");
const merge = require("webpack-merge");
module.exports = (env) => {
const afterCompileCommand = env && env.afterCompileCommand;
return merge(require(path.join(root, "scripts", "webpack.general.config.js"))({
typescriptCompilerOptions: {
target: "es5",
},
}), {
entry: path.join(root, "lib/vscode/src/bootstrap-fork.js"),
mode: "development",
target: "node",
externals: ["node-pty", "spdlog"],
output: {
chunkFilename: "[name].bundle.js",
path: path.resolve(__dirname, "./bin"),
publicPath: "/",
filename: "bootstrap-fork.js",
libraryTarget: "commonjs",
globalObject: "this",
},
module: {
rules: [{
loader: "string-replace-loader",
test: /\.(js|ts)$/,
options: {
multiple: [
{
search: "require\\.toUrl\\(",
replace: "requireToUrl(",
flags: "g",
},
{
search: "require\\.__\\$__nodeRequire",
replace: "require",
flags: "g",
},
],
},
}, {
loader: "string-replace-loader",
test: /vs\/loader\.js/,
options: {
multiple: [
{
search: "var recorder = moduleManager.getRecorder\\(\\);",
replace: `
var recorder = moduleManager.getRecorder();
const context = require.context("../", true, /.*/);
if (scriptSrc.indexOf("file:///") !== -1) {
const vsSrc = scriptSrc.split("file:///")[1].split(".js")[0];
if (vsSrc && vsSrc.startsWith("vs/")) {
scriptSrc = \`node|./\${vsSrc}\`;
}
}
`,
flags: "g",
},
{
search: "nodeRequire\\(",
replace: "require(",
flags: "g",
},
{
search: "moduleExports_1 = require\\(",
replace: "moduleExports_1 = context(",
flags: "g",
},
],
},
}, {
test: /\.wasm$/,
type: "javascript/auto",
}, {
// Ignore a bunch of file types we don't have loaders for. Also ignore
// test directories, some files with invalid JSON, and files we don't
// actually require but throw warnings or errors. This all seems to be a
// case of dynamic loading including things we won't require.
// This also results in the bundle being significantly smaller which
// makes uglify much faster.
test: /(\/vs\/code\/electron-main\/)|(\/test\/)|(OSSREADME\.json$)|(\.(test\.ts|test\.js|d\.ts|qwoff|node|html|txt|exe|wuff|md|sh|scpt|less)$)/,
use: ["ignore-loader"]
}],
},
resolve: {
alias: {
"gc-signals": path.join(fills, "empty.ts"),
"native-keymap": path.join(fills, "native-keymap.ts"),
"windows-process-tree": path.resolve(fills, "empty.ts"),
"electron": path.join(vscodeFills, "stdioElectron.ts"),
"vs/platform/node/product": path.resolve(vscodeFills, "product.ts"),
"vs/platform/node/package": path.resolve(vscodeFills, "package.ts"),
"vs/base/node/paths": path.resolve(vscodeFills, "paths.ts"),
"vs/base/common/amd": path.resolve(vscodeFills, "amd.ts"),
"vs": path.resolve(root, "lib/vscode/src/vs"),
},
},
resolveLoader: {
alias: {
"vs/css": path.resolve(vscodeFills, "css.js"),
},
},
plugins: [
new webpack.ProgressPlugin((percentage, msg) => {
if (percentage === 1) {
if (afterCompileCommand) {
require("child_process").execSync(afterCompileCommand, {
stdio: "inherit"
});
}
}
}),
],
});
};

View File

@ -2,3 +2,122 @@
# yarn lockfile v1
ajv-keywords@^3.1.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.2.0.tgz#e86b819c602cf8821ad637413698f1dec021847a"
integrity sha1-6GuBnGAs+IIa1jdBNpjx3sAhhHo=
ajv@^6.1.0:
version "6.7.0"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.7.0.tgz#e3ce7bb372d6577bb1839f1dfdfcbf5ad2948d96"
integrity sha512-RZXPviBTtfmtka9n9sy1N5M5b82CbxWIR6HIis4s3WQTXDJamc/0gpCWNGz6EWdWp4DOfjzJfhz/AS9zVPjjWg==
dependencies:
fast-deep-equal "^2.0.1"
fast-json-stable-stringify "^2.0.0"
json-schema-traverse "^0.4.1"
uri-js "^4.2.2"
big.js@^5.2.2:
version "5.2.2"
resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328"
integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==
bindings@^1.3.0:
version "1.3.1"
resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.3.1.tgz#21fc7c6d67c18516ec5aaa2815b145ff77b26ea5"
integrity sha512-i47mqjF9UbjxJhxGf+pZ6kSxrnI3wBLlnGI2ArWJ4r0VrvDS7ZYXkprq/pLaBWYq4GM0r4zdHY+NNRqEMU7uew==
emojis-list@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-2.1.0.tgz#4daa4d9db00f9819880c79fa457ae5b09a1fd389"
integrity sha1-TapNnbAPmBmIDHn6RXrlsJof04k=
fast-deep-equal@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49"
integrity sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=
fast-json-stable-stringify@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2"
integrity sha1-1RQsDK7msRifh9OnYREGT4bIu/I=
json-schema-traverse@^0.4.1:
version "0.4.1"
resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660"
integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==
json5@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe"
integrity sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==
dependencies:
minimist "^1.2.0"
loader-utils@^1.1.0:
version "1.2.3"
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.2.3.tgz#1ff5dc6911c9f0a062531a4c04b609406108c2c7"
integrity sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==
dependencies:
big.js "^5.2.2"
emojis-list "^2.0.0"
json5 "^1.0.1"
minimist@0.0.8:
version "0.0.8"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=
minimist@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=
mkdirp@^0.5.1:
version "0.5.1"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=
dependencies:
minimist "0.0.8"
nan@^2.8.0:
version "2.12.1"
resolved "https://registry.yarnpkg.com/nan/-/nan-2.12.1.tgz#7b1aa193e9aa86057e3c7bbd0ac448e770925552"
integrity sha512-JY7V6lRkStKcKTvHO5NVSQRv+RV+FIL5pvDoLiAtSL9pKlC5x9PKQcZDsq7m4FO4d57mkhC6Z+QhAh3Jdk5JFw==
punycode@^2.1.0:
version "2.1.1"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
schema-utils@^0.4.5:
version "0.4.7"
resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-0.4.7.tgz#ba74f597d2be2ea880131746ee17d0a093c68187"
integrity sha512-v/iwU6wvwGK8HbU9yi3/nhGzP0yGSuhQMzL6ySiec1FSrZZDkhm4noOSWzrNFo/jEc+SJY6jRTwuwbSXJPDUnQ==
dependencies:
ajv "^6.1.0"
ajv-keywords "^3.1.0"
spdlog@^0.7.2:
version "0.7.2"
resolved "https://registry.yarnpkg.com/spdlog/-/spdlog-0.7.2.tgz#9298753d7694b9ee9bbfd7e01ea1e4c6ace1e64d"
integrity sha512-rHfWCaWMD4NindDnql6rc6kn7Bs8JR92jhiUpCl3D6v+jYcQ6GozMLig0RliOOR8st5mU+IHLZnr15fBys5x/Q==
dependencies:
bindings "^1.3.0"
mkdirp "^0.5.1"
nan "^2.8.0"
string-replace-loader@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/string-replace-loader/-/string-replace-loader-2.1.1.tgz#b72e7b57b6ef04efe615aff0ad989b5c14ca63d1"
integrity sha512-0Nvw1LDclF45AFNuYPcD2Jvkv0mwb/dQSnJZMvhqGrT+zzmrpG3OJFD600qfQfNUd5aqfp7fCm2mQMfF7zLbyQ==
dependencies:
loader-utils "^1.1.0"
schema-utils "^0.4.5"
uri-js@^4.2.2:
version "4.2.2"
resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0"
integrity sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==
dependencies:
punycode "^2.1.0"