import * as cp from "child_process"; import * as fs from "fs"; import * as path from "path"; import * as zlib from "zlib"; import * as vm from "vm"; import { isCli } from "../constants"; let ipcMsgBuffer: Buffer[] | undefined = []; let ipcMsgListener = process.send ? (d: Buffer): number => ipcMsgBuffer!.push(d) : undefined; if (ipcMsgListener) { process.on("message", ipcMsgListener); } /** * Requires a module from the filesystem. * * Will load from the CLI if file is included inside of the default extensions dir */ // tslint:disable-next-line:no-any const requireFilesystemModule = (id: string, builtInExtensionsDir: string): any => { const mod = require("module") as typeof import("module"); const customMod = new mod.Module(id); customMod.filename = id; // tslint:disable-next-line:no-any customMod.paths = (mod)._nodeModulePaths(path.dirname(id)); if (id.startsWith(builtInExtensionsDir)) { customMod.loaded = true; const fileName = id.endsWith(".js") ? id : `${id}.js`; const req = vm.runInThisContext(mod.wrap(fs.readFileSync(fileName).toString()), { displayErrors: true, filename: id + fileName, }); req(customMod.exports, customMod.require.bind(customMod), customMod, fileName, path.dirname(id)); return customMod.exports; } return customMod.require(id); }; /** * Called from forking a module */ export const requireFork = (modulePath: string, args: string[], builtInExtensionsDir: string): void => { const Module = require("module") as typeof import("module"); const oldRequire = Module.prototype.require; // tslint:disable-next-line:no-any Module.prototype.require = (id: string): any => { if (id === "typescript") { return require("typescript"); } return oldRequire(id); }; if (!process.send) { throw new Error("No IPC messaging initialized"); } process.argv = ["", "", ...args]; requireFilesystemModule(modulePath, builtInExtensionsDir); if (ipcMsgBuffer && ipcMsgListener) { process.removeListener("message", ipcMsgListener); // tslint:disable-next-line:no-any ipcMsgBuffer.forEach((i) => process.emit("message" as any, i as any)); ipcMsgBuffer = undefined; ipcMsgListener = undefined; } }; export const requireModule = (modulePath: string, dataDir: string, builtInExtensionsDir: string): void => { process.env.AMD_ENTRYPOINT = modulePath; const xml = require("xhr2"); xml.XMLHttpRequest.prototype._restrictedHeaders["user-agent"] = false; // tslint:disable-next-line no-any this makes installing extensions work. (global as any).XMLHttpRequest = xml.XMLHttpRequest; const mod = require("module") as typeof import("module"); const promiseFinally = require("promise.prototype.finally") as { shim: () => void }; promiseFinally.shim(); /** * Used for loading extensions. Using __non_webpack_require__ didn't work * as it was not resolving to the FS. */ // tslint:disable-next-line:no-any (global as any).nativeNodeRequire = (id: string): any => { return requireFilesystemModule(id, builtInExtensionsDir); }; if (isCli) { /** * Needed for properly forking external modules within the CLI */ // tslint:disable-next-line:no-any (cp).fork = (modulePath: string, args: ReadonlyArray = [], options?: cp.ForkOptions): cp.ChildProcess => { return cp.spawn(process.execPath, ["--fork", modulePath, "--args", JSON.stringify(args), "--data-dir", dataDir], { ...options, stdio: [null, null, null, "ipc"], }); }; } let content: Buffer | undefined; const readFile = (name: string): Buffer => { return fs.readFileSync(path.join(process.env.BUILD_DIR as string || path.join(__dirname, "../.."), "./build", name)); }; if (isCli) { content = zlib.gunzipSync(readFile("bootstrap-fork.js.gz")); } else { content = readFile("../../vscode/bin/bootstrap-fork.js"); } eval(content.toString()); }; /** * Uses the internal bootstrap-fork.js to load a module * @example * const cp = forkModule("vs/code/electron-browser/sharedProcess/sharedProcessMain"); * cp.stdout.on("data", (data) => console.log(data.toString("utf8"))); * cp.stderr.on("data", (data) => console.log(data.toString("utf8"))); * @param modulePath Path of the VS Code module to load. */ export const forkModule = (modulePath: string, args: string[], options: cp.ForkOptions, dataDir?: string): cp.ChildProcess => { let proc: cp.ChildProcess; const forkArgs = ["--bootstrap-fork", modulePath]; if (args) { forkArgs.push("--args", JSON.stringify(args)); } if (options.env) { // This prevents vscode from trying to load original-fs from electron. delete options.env.ELECTRON_RUN_AS_NODE; forkArgs.push("--env", JSON.stringify(options.env)); } if (dataDir) { forkArgs.push("--data-dir", dataDir); } const forkOptions: cp.ForkOptions = { stdio: [null, null, null, "ipc"], }; if (isCli) { proc = cp.spawn(process.execPath, forkArgs, forkOptions); } else { proc = cp.spawn(process.execPath, ["--require", "ts-node/register", "--require", "tsconfig-paths/register", process.argv[1], ...forkArgs], forkOptions); } return proc; };