From edfd104e564c2b0f159421e3eed9f80d897de8a0 Mon Sep 17 00:00:00 2001 From: Vladimir Matveev Date: Thu, 18 Aug 2016 14:29:21 -0700 Subject: [PATCH] use localhost: to send notifications when typings are updated --- src/server/server.ts | 54 +++++++++++++++---- src/server/typingsCache.ts | 21 +++++--- .../typingsInstaller/nodeTypingsInstaller.ts | 20 ++++--- src/server/utilities.ts | 17 +++++- 4 files changed, 90 insertions(+), 22 deletions(-) diff --git a/src/server/server.ts b/src/server/server.ts index 386cffb29e5..1ae385f7b8a 100644 --- a/src/server/server.ts +++ b/src/server/server.ts @@ -9,15 +9,23 @@ namespace ts.server { gzipSync(buf: Buffer): Buffer } = require("zlib"); + const net: { + connect(options: { port: number }, onConnect?: () => void): NodeSocket + } = require("net"); + + const childProcess: { + fork(modulePath: string, args: string[], options?: { execArgv: string[], env?: MapLike }): NodeChildProcess; + } = require("child_process"); + interface NodeChildProcess { send(message: any, sendHandle?: any): void; on(message: "message", f: (m: any) => void): void; kill(): void; } - const childProcess: { - fork(modulePath: string): NodeChildProcess; - } = require("child_process"); + interface NodeSocket { + write(data: string, encoding: string): boolean; + } interface ReadLineOptions { input: NodeJS.ReadableStream; @@ -106,6 +114,10 @@ namespace ts.server { } } + getLogFileName() { + return this.logFilename; + } + perftrc(s: string) { this.msg(s, Msg.Perf); } @@ -163,9 +175,15 @@ namespace ts.server { class NodeTypingsInstaller implements ITypingsInstaller { private installer: NodeChildProcess; + private socket: NodeSocket; private projectService: ProjectService; - constructor(private readonly logger: server.Logger) { + constructor(private readonly logger: server.Logger, private readonly eventPort: number) { + if (eventPort) { + const s = net.connect({ port: eventPort }, () => { + this.socket = s; + }); + } } attach(projectService: ProjectService) { @@ -174,7 +192,11 @@ namespace ts.server { this.logger.info("Binding..."); } - this.installer = childProcess.fork(combinePaths(__dirname, "typingsInstaller.js")); + let args: string[] = []; + if (this.logger.loggingEnabled() && this.logger.getLogFileName()) { + args = [ "--logFile", combinePaths(getDirectoryPath(normalizeSlashes(this.logger.getLogFileName())), `ti-${process.pid}.log`) ]; + } + this.installer = childProcess.fork(combinePaths(__dirname, "typingsInstaller.js"), args); this.installer.on("message", m => this.handleMessage(m)); process.on("exit", () => { this.installer.kill(); @@ -198,12 +220,15 @@ namespace ts.server { this.logger.info(`Received response: ${JSON.stringify(response)}`); } this.projectService.updateTypingsForProject(response); + if (response.kind == "set" && this.socket) { + this.socket.write(JSON.stringify({ kind: "updateTypings", message: response }) + "\r\n", "utf8"); + } } } class IOSession extends Session { - constructor(host: ServerHost, cancellationToken: HostCancellationToken, useSingleInferredProject: boolean, logger: server.Logger) { - super(host, cancellationToken, useSingleInferredProject, new NodeTypingsInstaller(logger), Buffer.byteLength, maxUncompressedMessageSize, compress, process.hrtime, logger); + constructor(host: ServerHost, cancellationToken: HostCancellationToken, eventPort: number, useSingleInferredProject: boolean, logger: server.Logger) { + super(host, cancellationToken, useSingleInferredProject, new NodeTypingsInstaller(logger, eventPort), Buffer.byteLength, maxUncompressedMessageSize, compress, process.hrtime, logger); } exit() { @@ -435,8 +460,19 @@ namespace ts.server { }; }; - const useSingleInferredProject = sys.args.some(arg => arg === "--useSingleInferredProject"); - const ioSession = new IOSession(sys, cancellationToken, useSingleInferredProject, logger); + let eventPort: number; + { + const index = sys.args.indexOf("--eventPort"); + if (index >= 0 && index < sys.args.length - 1) { + const v = parseInt(sys.args[index + 1]); + if (!isNaN(v)) { + eventPort = v; + } + } + } + + const useSingleInferredProject = sys.args.indexOf("--useSingleInferredProject") >= 0; + const ioSession = new IOSession(sys, cancellationToken, eventPort, useSingleInferredProject, logger); process.on("uncaughtException", function (err: Error) { ioSession.logError(err, "unknown"); }); diff --git a/src/server/typingsCache.ts b/src/server/typingsCache.ts index 2ab4f83b060..eb1d3283e08 100644 --- a/src/server/typingsCache.ts +++ b/src/server/typingsCache.ts @@ -17,6 +17,7 @@ namespace ts.server { readonly typingOptions: TypingOptions; readonly compilerOptions: CompilerOptions; readonly typings: TypingsArray; + poisoned: boolean; } const emptyArray: any[] = []; @@ -27,10 +28,7 @@ namespace ts.server { return (proj).getTypingOptions(); } - const enableAutoDiscovery = - proj.projectKind === ProjectKind.Inferred && - proj.getCompilerOptions().allowJs && - proj.getFileNames().every(f => fileExtensionIsAny(f, jsOrDts)); + const enableAutoDiscovery = proj.getFileNames().every(f => fileExtensionIsAny(f, jsOrDts)); // TODO: add .d.ts files to excludes return { enableAutoDiscovery, include: emptyArray, exclude: emptyArray }; @@ -98,10 +96,20 @@ namespace ts.server { } const entry = this.perProjectCache[project.getProjectName()]; + const result: TypingsArray = entry ? entry.typings : emptyArray; if (!entry || typingOptionsChanged(typingOptions, entry.typingOptions) || compilerOptionsChanged(project.getCompilerOptions(), entry.compilerOptions)) { + // something has been changed, issue a request to update typings this.installer.enqueueInstallTypingsRequest(project, typingOptions); + // Note: entry is now poisoned since it does not really contain typings for a given combination of compiler options\typings options. + // instead it acts as a placeholder to prevent issuing multiple requests + this.perProjectCache[project.getProjectName()] = { + compilerOptions: project.getCompilerOptions(), + typingOptions, + typings: result, + poisoned: true + }; } - return entry ? entry.typings : emptyArray; + return result; } invalidateCachedTypingsForProject(project: Project) { @@ -116,7 +124,8 @@ namespace ts.server { this.perProjectCache[projectName] = { compilerOptions, typingOptions, - typings: toTypingsArray(newTypings) + typings: toTypingsArray(newTypings), + poisoned: false }; } diff --git a/src/server/typingsInstaller/nodeTypingsInstaller.ts b/src/server/typingsInstaller/nodeTypingsInstaller.ts index 55b67621f33..e0a361f9411 100644 --- a/src/server/typingsInstaller/nodeTypingsInstaller.ts +++ b/src/server/typingsInstaller/nodeTypingsInstaller.ts @@ -42,7 +42,7 @@ namespace ts.server.typingsInstaller { } export class NodeTypingsInstaller extends TypingsInstaller { - private execSync: { (command: string, options: { stdio: "ignore" | "pipe" }): Buffer | string }; + private execSync: { (command: string, options: { stdio: "ignore" | "pipe", cwd?: string }): Buffer | string }; private exec: { (command: string, options: { cwd: string }, callback?: (error: Error, stdout: string, stderr: string) => void): any }; private npmBinPath: string; @@ -86,7 +86,7 @@ namespace ts.server.typingsInstaller { protected isPackageInstalled(packageName: string) { try { - const output = this.execSync(`npm list --global --depth=1 ${packageName}`, { stdio: "pipe" }).toString(); + const output = this.execSync(`npm list --silent --global --depth=1 ${packageName}`, { stdio: "pipe" }).toString(); if (this.log.isEnabled()) { this.log.writeLine(`IsPackageInstalled::stdout '${output}'`); } @@ -103,7 +103,7 @@ namespace ts.server.typingsInstaller { protected installPackage(packageName: string) { try { - const output = this.execSync(`npm install --global ${packageName}`, { stdio: "pipe" }).toString(); + const output = this.execSync(`npm install --silent --global ${packageName}`, { stdio: "pipe" }).toString(); if (this.log.isEnabled()) { this.log.writeLine(`installPackage::stdout '${output}'`); } @@ -132,10 +132,11 @@ namespace ts.server.typingsInstaller { const id = this.tsdRunCount; this.tsdRunCount++; const tsdPath = combinePaths(this.npmBinPath, "tsd"); + const command = `${tsdPath} install ${typingsToInstall.join(" ")} -ros`; if (this.log.isEnabled()) { - this.log.writeLine(`Running tsd ${id}, tsd path '${tsdPath}, typings to install: ${JSON.stringify(typingsToInstall)}. cache path '${cachePath}'`); + this.log.writeLine(`Running tsd ${id}, command '${command}'. cache path '${cachePath}'`); } - this.exec(`${tsdPath} install ${typingsToInstall.join(" ")} -ros`, { cwd: cachePath }, (err, stdout, stderr) => { + this.exec(command, { cwd: cachePath }, (err, stdout, stderr) => { if (this.log.isEnabled()) { this.log.writeLine(`TSD ${id} stdout: ${stdout}`); this.log.writeLine(`TSD ${id} stderr: ${stderr}`); @@ -157,7 +158,14 @@ namespace ts.server.typingsInstaller { } } - const log = new FileLog(process.env.TI_LOG_FILE); + let logFilePath: string; + { + const logFileIndex = sys.args.indexOf("--logFile"); + if (logFileIndex >= 0 && logFileIndex < sys.args.length - 1) { + logFilePath = sys.args[logFileIndex + 1]; + } + } + const log = new FileLog(logFilePath); if (log.isEnabled()) { process.on("uncaughtException", (e: Error) => { log.writeLine(`Unhandled exception: ${e} at ${e.stack}`); diff --git a/src/server/utilities.ts b/src/server/utilities.ts index 4f1811cc9a8..a915067bd32 100644 --- a/src/server/utilities.ts +++ b/src/server/utilities.ts @@ -17,6 +17,7 @@ namespace ts.server { startGroup(): void; endGroup(): void; msg(s: string, type?: Msg.Types): void; + getLogFileName(): string; } export namespace Msg { @@ -29,13 +30,27 @@ namespace ts.server { export type Types = Err | Info | Perf; } + function getProjectRootPath(project: Project): Path { + switch (project.projectKind) { + case ProjectKind.Configured: + return project.getProjectName(); + case ProjectKind.Inferred: + // TODO: fixme + return ""; + case ProjectKind.External: + const projectName = project.getProjectName(); + const host = project.projectService.host; + return host.fileExists(projectName) ? getDirectoryPath(projectName) : projectName; + } + } + export function createInstallTypingsRequest(project: Project, typingOptions: TypingOptions, cachePath?: string): DiscoverTypings { return { projectName: project.getProjectName(), fileNames: project.getFileNames(), compilerOptions: project.getCompilerOptions(), typingOptions, - projectRootPath: (project.projectKind === ProjectKind.Inferred ? "" : getDirectoryPath(project.getProjectName())), // TODO: fixme + projectRootPath: getProjectRootPath(project), cachePath, kind: "discover" };