diff --git a/src/server/typingsInstaller/nodeTypingsInstaller.ts b/src/server/typingsInstaller/nodeTypingsInstaller.ts index 0d22b1b364..1e3448e068 100644 --- a/src/server/typingsInstaller/nodeTypingsInstaller.ts +++ b/src/server/typingsInstaller/nodeTypingsInstaller.ts @@ -1,8 +1,14 @@ -/// +/// /// namespace ts.server.typingsInstaller { + interface RunInstallRequest { + readonly cachePath: string; + readonly typingsToInstall: string[]; + readonly postInstallAction: (installedTypings: string[]) => void; + } + const throttleLimit = 5; const fs: { appendFileSync(file: string, content: string): void } = require("fs"); @@ -25,6 +31,8 @@ namespace ts.server.typingsInstaller { private npmBinPath: string; private installRunCount = 1; + private throttleCount = 0; + private delayedRunInstallRequests: RunInstallRequest[] = []; readonly installTypingHost: InstallTypingHost = sys; constructor(globalTypingsCacheLocation: string, log: Log) { @@ -90,24 +98,55 @@ namespace ts.server.typingsInstaller { } protected runInstall(cachePath: string, typingsToInstall: string[], postInstallAction: (installedTypings: string[]) => void): void { + if (this.throttleCount === throttleLimit) { + const request = { + cachePath: cachePath, + typingsToInstall: typingsToInstall, + postInstallAction: postInstallAction + }; + this.delayedRunInstallRequests.push(request); + return; + } const id = this.installRunCount; this.installRunCount++; let execInstallCmdCount = 0; const filteredTypings: string[] = []; + const delayedTypingsToInstall: string[] = []; for (const typing of typingsToInstall) { - const command = `npm view @types/${typing} --silent name`; - this.execAsync("npm view", command, cachePath, id, (err, stdout, stderr) => { - if (stdout) { - filteredTypings.push(typing); - } - execInstallCmdCount++; - if (execInstallCmdCount === typingsToInstall.length) { - installFilteredTypings(this, filteredTypings); - } + if (this.throttleCount === throttleLimit) { + delayedTypingsToInstall.push(typing); + continue; + } + execNpmViewTyping(this, typing); + } + + function execNpmViewTyping(self: NodeTypingsInstaller, typing: string) { + self.throttleCount++; + const command = `npm view @types/${typing} --silent name`; + self.execAsync("npm view", command, cachePath, id, (err, stdout, stderr) => { + if (stdout) { + filteredTypings.push(typing); + } + execInstallCmdCount++; + self.throttleCount--; + if (delayedTypingsToInstall.length > 0) { + return execNpmViewTyping(self, delayedTypingsToInstall.pop()); + } + if (execInstallCmdCount === typingsToInstall.length) { + installFilteredTypings(self, filteredTypings); + if (self.delayedRunInstallRequests.length > 0) { + const request = self.delayedRunInstallRequests.pop(); + return self.runInstall(request.cachePath, request.typingsToInstall, request.postInstallAction); + } + } }); } function installFilteredTypings(self: NodeTypingsInstaller, filteredTypings: string[]) { + if (filteredTypings.length === 0) { + reportInstalledTypings(self); + return; + } const command = `npm install ${filteredTypings.map(t => "@types/" + t).join(" ")} --save-dev`; self.execAsync("npm install", command, cachePath, id, (err, stdout, stderr) => { if (stdout) {