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:
1
packages/vscode/.gitignore
vendored
Normal file
1
packages/vscode/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
bin
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
133
packages/vscode/src/client.ts
Normal file
133
packages/vscode/src/client.ts
Normal 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;
|
||||
});
|
11
packages/vscode/src/fill/amd.ts
Normal file
11
packages/vscode/src/fill/amd.ts
Normal 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 : "";
|
||||
};
|
1
packages/vscode/src/fill/graceful-fs.ts
Normal file
1
packages/vscode/src/fill/graceful-fs.ts
Normal file
@ -0,0 +1 @@
|
||||
export const gracefulify = (): void => undefined;
|
@ -1,8 +1,13 @@
|
||||
module.exports = {
|
||||
getCurrentKeyboardLayout: (): null => {
|
||||
class NativeKeymap {
|
||||
|
||||
public getCurrentKeyboardLayout(): null {
|
||||
return null;
|
||||
},
|
||||
getKeyMap: (): undefined[] => {
|
||||
}
|
||||
|
||||
public getKeyMap(): undefined[] {
|
||||
return [];
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export = new NativeKeymap();
|
||||
|
@ -70,4 +70,4 @@ const ptyType: nodePtyType = {
|
||||
|
||||
};
|
||||
|
||||
module.exports = ptyType;
|
||||
exports = ptyType;
|
||||
|
2
packages/vscode/src/fill/package.ts
Normal file
2
packages/vscode/src/fill/package.ts
Normal file
@ -0,0 +1,2 @@
|
||||
// TODO: obtain this in a reasonable way.
|
||||
export default { name: "vscode", version: "1.31.1" };
|
7
packages/vscode/src/fill/paths.ts
Normal file
7
packages/vscode/src/fill/paths.ts
Normal file
@ -0,0 +1,7 @@
|
||||
const paths = {
|
||||
appData: "/tmp",
|
||||
defaultUserData: "/tmp",
|
||||
};
|
||||
|
||||
export let getAppDataPath = (): string => paths.appData;
|
||||
export let getDefaultUserDataPath = (): string => paths.defaultUserData;
|
24
packages/vscode/src/fill/product.ts
Normal file
24
packages/vscode/src/fill/product.ts
Normal 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;
|
3
packages/vscode/src/fill/require.ts
Normal file
3
packages/vscode/src/fill/require.ts
Normal file
@ -0,0 +1,3 @@
|
||||
// TODO: ?
|
||||
// tslint:disable-next-line no-any
|
||||
(global as any).requireToUrl = (path: string): string => `${location.protocol}//{location.host}/${path}`;
|
186
packages/vscode/src/fill/spdlog.ts
Normal file
186
packages/vscode/src/fill/spdlog.ts
Normal 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.
|
||||
};
|
22
packages/vscode/src/fill/stdioElectron.ts
Normal file
22
packages/vscode/src/fill/stdioElectron.ts
Normal 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();
|
78
packages/vscode/src/fill/storageDatabase.ts
Normal file
78
packages/vscode/src/fill/storageDatabase.ts
Normal 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;
|
274
packages/vscode/src/fill/windowsService.ts
Normal file
274
packages/vscode/src/fill/windowsService.ts
Normal 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;
|
@ -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";
|
||||
|
129
packages/vscode/src/protocol.ts
Normal file
129
packages/vscode/src/protocol.ts
Normal 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));
|
||||
}
|
||||
|
||||
}
|
151
packages/vscode/src/startup.ts
Normal file
151
packages/vscode/src/startup.ts
Normal 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,
|
||||
};
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
};
|
123
packages/vscode/webpack.config.bootstrap.js
Normal file
123
packages/vscode/webpack.config.bootstrap.js
Normal 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"
|
||||
});
|
||||
}
|
||||
}
|
||||
}),
|
||||
],
|
||||
});
|
||||
};
|
@ -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"
|
||||
|
Reference in New Issue
Block a user