diff --git a/src/server/server.ts b/src/server/server.ts index afa0b134736..208d0bbd384 100644 --- a/src/server/server.ts +++ b/src/server/server.ts @@ -12,6 +12,7 @@ namespace ts.server { interface NodeChildProcess { send(message: any, sendHandle?: any): void; on(message: "message", f: (m: any) => void): void; + kill(): void; } const childProcess: { @@ -175,6 +176,10 @@ namespace ts.server { this.installer = childProcess.fork(combinePaths(__dirname, "typingsInstaller.js")); this.installer.on("message", m => this.handleMessage(m)); + process.on("exit", () => { + this.installer.kill(); + }) + } onProjectClosed(p: Project): void { diff --git a/src/server/typingsInstaller/nodeTypingsInstaller.ts b/src/server/typingsInstaller/nodeTypingsInstaller.ts index f422b55e32d..99a2513cd9f 100644 --- a/src/server/typingsInstaller/nodeTypingsInstaller.ts +++ b/src/server/typingsInstaller/nodeTypingsInstaller.ts @@ -52,6 +52,9 @@ namespace ts.server.typingsInstaller { constructor(log?: Log) { super(getGlobalCacheLocation(), toPath("typingSafeList.json", __dirname, createGetCanonicalFileName(sys.useCaseSensitiveFileNames)), log); + if (this.log.isEnabled()) { + this.log.writeLine(`Process id: ${process.pid}`); + } const { exec, execSync } = require("child_process"); this.execSync = execSync; this.exec = exec; @@ -84,20 +87,34 @@ namespace ts.server.typingsInstaller { protected isPackageInstalled(packageName: string) { try { - this.execSync(`npm list --global --depth=1 ${packageName}`, { stdio: "ignore" }); + const output = this.execSync(`npm list --global --depth=1 ${packageName}`, { stdio: "pipe" }).toString(); + if (this.log.isEnabled()) { + this.log.writeLine(`IsPackageInstalled::stdout '${output}'`); + } return true; } catch (e) { + if (this.log.isEnabled()) { + this.log.writeLine(`IsPackageInstalled::err::stdout '${e.stdout && e.stdout.toString()}'`); + this.log.writeLine(`IsPackageInstalled::err::stderr '${e.stdout && e.stderr.toString()}'`); + } return false; } } protected installPackage(packageName: string) { try { - this.execSync(`npm install --global ${packageName}`, { stdio: "ignore" }); + const output = this.execSync(`npm install --global ${packageName}`, { stdio: "pipe" }).toString(); + if (this.log.isEnabled()) { + this.log.writeLine(`installPackage::stdout '${output}'`); + } return true; } catch (e) { + if (this.log.isEnabled()) { + this.log.writeLine(`installPackage::err::stdout '${e.stdout && e.stdout.toString()}'`); + this.log.writeLine(`installPackage::err::stderr '${e.stdout && e.stderr.toString()}'`); + } return false; } } @@ -107,6 +124,9 @@ namespace ts.server.typingsInstaller { this.log.writeLine(`Sending response: ${JSON.stringify(response)}`) } process.send(response); + if (this.log.isEnabled()) { + this.log.writeLine(`Response has been sent.`) + } } protected runTsd(cachePath: string, typingsToInstall: string[], postInstallAction: (installedTypings: string[]) => void): void { @@ -139,11 +159,15 @@ namespace ts.server.typingsInstaller { } const log = new FileLog(process.env.TI_LOG_FILE); - process.on("uncaughtException", (e: Error) => { - if (log.isEnabled()) { + if (log.isEnabled()) { + process.on("uncaughtException", (e: Error) => { log.writeLine(`Unhandled exception: ${e} at ${e.stack}`); - } - }) + }); + process.on("disconnect", () => { + log.writeLine(`Parent process has exited, shutting down...`); + process.exit(0); + }); + } const installer = new NodeTypingsInstaller(log); installer.init(); } \ No newline at end of file diff --git a/src/server/typingsInstaller/typingsInstaller.ts b/src/server/typingsInstaller/typingsInstaller.ts index b7862431869..e1f026d37f9 100644 --- a/src/server/typingsInstaller/typingsInstaller.ts +++ b/src/server/typingsInstaller/typingsInstaller.ts @@ -72,7 +72,7 @@ namespace ts.server.typingsInstaller { this.closeWatchers(req.projectName); } - private closeWatchers(projectName: string): boolean { + private closeWatchers(projectName: string): void { if (this.log.isEnabled()) { this.log.writeLine(`Closing file watchers for project '${projectName}'`); } @@ -82,7 +82,7 @@ namespace ts.server.typingsInstaller { this.log.writeLine(`No watchers are registered for project '${projectName}'`); } - return false; + return; } for (const w of watchers) { w.close(); @@ -93,8 +93,6 @@ namespace ts.server.typingsInstaller { if (this.log.isEnabled()) { this.log.writeLine(`Closing file watchers for project '${projectName}' - done.`); } - - return true; } install(req: DiscoverTypings) { @@ -137,7 +135,14 @@ namespace ts.server.typingsInstaller { this.watchFiles(req.projectName, discoverTypingsResult.filesToWatch); // install typings - this.installTypings(req, req.cachePath || this.globalCachePath, discoverTypingsResult.cachedTypingPaths, discoverTypingsResult.newTypingNames); + if (discoverTypingsResult.newTypingNames.length) { + this.installTypings(req, req.cachePath || this.globalCachePath, discoverTypingsResult.cachedTypingPaths, discoverTypingsResult.newTypingNames); + } + else { + if (this.log.isEnabled()) { + this.log.writeLine(`No new typings were requested as a result of typings discovery`); + } + } } private processCacheLocation(cacheLocation: string) { @@ -259,17 +264,23 @@ namespace ts.server.typingsInstaller { if (!files.length) { return; } + // shut down existing watchers + this.closeWatchers(projectName); + + // handler should be invoked once for the entire set of files since it will trigger full rediscovery of typings + let isInvoked = false; const watchers: FileWatcher[] = []; for (const file of files) { const w = this.installTypingHost.watchFile(file, f => { if (this.log.isEnabled()) { - this.log.writeLine(`FS notification for '${f}', sending 'clean' message for project '${projectName}'`); + this.log.writeLine(`Got FS notification for ${f}, handler is already invoked '${isInvoked}'`); } - this.closeWatchers(projectName); - this.sendResponse({ projectName: projectName, kind: "invalidate" }) + this.sendResponse({ projectName: projectName, kind: "invalidate" }); + isInvoked = true; }); watchers.push(w); } + this.projectWatchers[projectName] = watchers; } private createSetTypings(request: DiscoverTypings, typings: string[]): SetTypings {