From dd70fdbb76c694b7d1ca3898db3ed3d30a88531b Mon Sep 17 00:00:00 2001 From: Vladimir Matveev Date: Mon, 15 Aug 2016 15:59:31 -0700 Subject: [PATCH] add logging to typings installer, read npm bin path to start globally installed binaries --- .../typingsInstaller/nodeTypingsInstaller.ts | 61 +++++++++++- .../typingsInstaller/typingsInstaller.ts | 96 +++++++++++++++++-- 2 files changed, 143 insertions(+), 14 deletions(-) diff --git a/src/server/typingsInstaller/nodeTypingsInstaller.ts b/src/server/typingsInstaller/nodeTypingsInstaller.ts index cb4b522f620..44efda6f0a6 100644 --- a/src/server/typingsInstaller/nodeTypingsInstaller.ts +++ b/src/server/typingsInstaller/nodeTypingsInstaller.ts @@ -7,6 +7,10 @@ namespace ts.server.typingsInstaller { homedir(): string } = require("os"); + const fs: { + appendFileSync(file: string, content: string): void + } = require("fs"); + function getGlobalCacheLocation() { let basePath: string; switch (process.platform) { @@ -25,13 +29,29 @@ namespace ts.server.typingsInstaller { return combinePaths(normalizeSlashes(basePath), "Microsoft/TypeScript"); } + class FileLog implements Log { + constructor(private readonly logFile?: string) { + } + + isEnabled() { + return this.logFile !== undefined; + } + writeLine(text: string) { + fs.appendFileSync(this.logFile, text + sys.newLine); + } + } + export class NodeTypingsInstaller extends TypingsInstaller { - private execSync: { (command: string, options: { stdio: "ignore" }): any }; + private execSync: { (command: string, options: { stdio: "ignore" | "pipe" }): Buffer | string }; private exec: { (command: string, options: { cwd: string }, callback?: (error: Error, stdout: string, stderr: string) => void): any }; + + private npmBinPath: string; + + private tsdRunCount = 1; readonly installTypingHost: InstallTypingHost = sys; - constructor() { - super(getGlobalCacheLocation(), toPath("typingSafeList.json", __dirname, createGetCanonicalFileName(sys.useCaseSensitiveFileNames))); + constructor(log?: Log) { + super(getGlobalCacheLocation(), toPath("typingSafeList.json", __dirname, createGetCanonicalFileName(sys.useCaseSensitiveFileNames)), log); const { exec, execSync } = require("child_process"); this.execSync = execSync; this.exec = exec; @@ -39,6 +59,18 @@ namespace ts.server.typingsInstaller { init() { super.init(); + try { + this.npmBinPath = this.execSync("npm -g bin", { stdio: "pipe" }).toString().trim(); + if (this.log.isEnabled()) { + this.log.writeLine(`Global npm bin path '${this.npmBinPath}'`); + } + } + catch(e) { + this.npmBinPath = ""; + if (this.log.isEnabled()) { + this.log.writeLine(`Error when getting npm bin path: ${e}. Set bin path to ""`); + } + } process.on("message", (req: InstallTypingsRequest) => { this.install(req); }) @@ -65,11 +97,24 @@ namespace ts.server.typingsInstaller { } protected sendResponse(response: InstallTypingsResponse) { + if (this.log.isEnabled()) { + this.log.writeLine(`Sending response: ${JSON.stringify(response)}`) + } process.send(response); } protected runTsd(cachePath: string, typingsToInstall: string[], postInstallAction: (installedTypings: string[]) => void): void { - this.exec(`tsd install ${typingsToInstall.join(" ")} -ros`, { cwd: cachePath }, (err, stdout, stderr) => { + const id = this.tsdRunCount; + this.tsdRunCount++; + const tsdPath = combinePaths(this.npmBinPath, "tsd"); + if (this.log.isEnabled()) { + this.log.writeLine(`Running tsd ${id}, tsd path '${tsdPath}, typings to install: ${JSON.stringify(typingsToInstall)}. cache path '${cachePath}'`) + } + this.exec(`${tsdPath} install ${typingsToInstall.join(" ")} -ros`, { cwd: cachePath }, (err, stdout, stderr) => { + if (this.log.isEnabled()) { + this.log.writeLine(`TSD ${id} stdout: ${stdout}`); + this.log.writeLine(`TSD ${id} stderr: ${stderr}`) + } const i = stdout.indexOf("running install"); if (i < 0) { return; @@ -87,6 +132,12 @@ namespace ts.server.typingsInstaller { } } - const installer = new NodeTypingsInstaller(); + const log = new FileLog(process.env.TI_LOG_FILE); + process.on("uncaughtException", (e: Error) => { + if (log.isEnabled()) { + log.writeLine(`Unhandled exception: ${e} at ${e.stack}`); + } + }) + 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 d611cc374ce..911b5d965b0 100644 --- a/src/server/typingsInstaller/typingsInstaller.ts +++ b/src/server/typingsInstaller/typingsInstaller.ts @@ -13,6 +13,16 @@ namespace ts.server.typingsInstaller { installed: MapLike; } + export interface Log { + isEnabled(): boolean; + writeLine(text: string): void; + } + + const nullLog: Log = { + isEnabled: () => false, + writeLine: () => {} + } + function tsdTypingToFileName(cachePath: string, tsdTypingFile: string) { return combinePaths(cachePath, `typings/${tsdTypingFile}`); } @@ -32,24 +42,47 @@ namespace ts.server.typingsInstaller { abstract readonly installTypingHost: InstallTypingHost; - constructor(readonly globalCachePath: string, readonly safeListPath: Path) { + constructor(readonly globalCachePath: string, readonly safeListPath: Path, protected readonly log = nullLog) { + if (this.log.isEnabled()) { + this.log.writeLine(`Global cache location '${globalCachePath}', safe file path '${safeListPath}'`); + } } init() { this.isTsdInstalled = this.isPackageInstalled("tsd"); + if (this.log.isEnabled()) { + this.log.writeLine(`isTsdInstalled: ${this.isTsdInstalled}`); + } + if (!this.isTsdInstalled) { + if (this.log.isEnabled()) { + this.log.writeLine(`tsd is not installed, installing tsd...`); + } this.isTsdInstalled = this.installPackage("tsd"); + if (this.log.isEnabled()) { + this.log.writeLine(`isTsdInstalled: ${this.isTsdInstalled}`); + } } this.processCacheLocation(this.globalCachePath); } install(req: InstallTypingsRequest) { if (!this.isTsdInstalled) { + if (this.log.isEnabled()) { + this.log.writeLine(`tsd is not installed, ignoring request...`); + } return; } + + if (this.log.isEnabled()) { + this.log.writeLine(`Got install request ${JSON.stringify(req)}`); + } // load existing typing information from the cache if (req.cachePath) { + if (this.log.isEnabled()) { + this.log.writeLine(`Request specifies cache path '${req.cachePath}', loading cached information...`); + } this.processCacheLocation(req.cachePath); } @@ -61,6 +94,10 @@ namespace ts.server.typingsInstaller { this.packageNameToTypingLocation, req.typingOptions, req.compilerOptions); + + if (this.log.isEnabled()) { + this.log.writeLine(`Finished typings discovery: ${JSON.stringify(discoverTypingsResult)}`); + } // respond with whatever cached typings we have now this.sendResponse(this.createResponse(req, discoverTypingsResult.cachedTypingPaths)); @@ -68,17 +105,29 @@ namespace ts.server.typingsInstaller { // start watching files this.watchFiles(discoverTypingsResult.filesToWatch); - // install typings and - this.installTypings(req, discoverTypingsResult.cachedTypingPaths, discoverTypingsResult.newTypingNames); + // install typings + this.installTypings(req, req.cachePath || this.globalCachePath, discoverTypingsResult.cachedTypingPaths, discoverTypingsResult.newTypingNames); } private processCacheLocation(cacheLocation: string) { + if (this.log.isEnabled()) { + this.log.writeLine(`Processing cache location '${cacheLocation}'`); + } if (this.knownCachesSet[cacheLocation]) { + if (this.log.isEnabled()) { + this.log.writeLine(`Cache location was already processed...`) + } return; } const tsdJson = combinePaths(cacheLocation, "tsd.json"); + if (this.log.isEnabled()) { + this.log.writeLine(`Trying to find '${tsdJson}'...`); + } if (this.installTypingHost.fileExists(tsdJson)) { const tsdConfig = JSON.parse(this.installTypingHost.readFile(tsdJson)); + if (this.log.isEnabled()) { + this.log.writeLine(`Loaded content of '${tsdJson}': ${JSON.stringify(tsdConfig)}`); + } if (tsdConfig.installed) { for (const key in tsdConfig.installed) { // key is / @@ -92,30 +141,53 @@ namespace ts.server.typingsInstaller { continue; } if (existingTypingFile) { - // TODO: log warning + if (this.log.isEnabled()) { + this.log.writeLine(`New typing for package ${packageName} from '${typingFile}' conflicts with existing typing file '${existingTypingFile}'`); + } + } + if (this.log.isEnabled()) { + this.log.writeLine(`Adding entry into typings cache: '${packageName}' => '${typingFile}'`); } this.packageNameToTypingLocation[packageName] = typingFile; } } } + if (this.log.isEnabled()) { + this.log.writeLine(`Finished processing cache location '${cacheLocation}'`); + } this.knownCachesSet[cacheLocation] = true; } - private installTypings(req: InstallTypingsRequest, currentlyCachedTypings: string[], typingsToInstall: string[]) { + private installTypings(req: InstallTypingsRequest, cachePath: string, currentlyCachedTypings: string[], typingsToInstall: string[]) { + if (this.log.isEnabled()) { + this.log.writeLine(`Installing typings ${JSON.stringify(typingsToInstall)}`); + } typingsToInstall = filter(typingsToInstall, x => !this.missingTypingsSet[x]); if (typingsToInstall.length === 0) { + if (this.log.isEnabled()) { + this.log.writeLine(`All typings are known to be missing - no need to go any further`); + } return; } // TODO: install typings and send response when they are ready - const tsdPath = combinePaths(req.cachePath, "tsd.json"); + const tsdPath = combinePaths(cachePath, "tsd.json"); + if (this.log.isEnabled()) { + this.log.writeLine(`Tsd config file: ${tsdPath}`); + } if (!this.installTypingHost.fileExists(tsdPath)) { - this.ensureDirectoryExists(req.cachePath, this.installTypingHost); + if (this.log.isEnabled()) { + this.log.writeLine(`Tsd config file '${tsdPath}' is missing, creating new one...`); + } + this.ensureDirectoryExists(cachePath, this.installTypingHost); this.installTypingHost.writeFile(tsdPath, DefaultTsdSettings); } - this.runTsd(req.cachePath, typingsToInstall, installedTypings => { + this.runTsd(cachePath, typingsToInstall, installedTypings => { // TODO: watch project directory + if (this.log.isEnabled()) { + this.log.writeLine(`Requested to install typings ${JSON.stringify(typingsToInstall)}, installed typings ${JSON.stringify(installedTypings)}`); + } const installedPackages: Map = createMap(); const installedTypingFiles: string[] = []; for (const t of installedTypings) { @@ -124,10 +196,16 @@ namespace ts.server.typingsInstaller { continue; } installedPackages[packageName] = true; - installedTypingFiles.push(tsdTypingToFileName(req.cachePath, t)); + installedTypingFiles.push(tsdTypingToFileName(cachePath, t)); + } + if (this.log.isEnabled()) { + this.log.writeLine(`Installed typing files ${JSON.stringify(installedTypingFiles)}`); } for (const toInstall of typingsToInstall) { if (!installedPackages[toInstall]) { + if (this.log.isEnabled()) { + this.log.writeLine(`New missing typing package '${toInstall}'`); + } this.missingTypingsSet[toInstall] = true; } }