diff --git a/src/server/server.ts b/src/server/server.ts index e8e6d1fcfab..020b2d3a342 100644 --- a/src/server/server.ts +++ b/src/server/server.ts @@ -9,6 +9,14 @@ namespace ts.server { gzipSync(buf: Buffer): Buffer } = require("zlib"); + interface NodeChildProcess { + send(message: any, sendHandle?: any): void; + } + + const childProcess: { + fork(modulePath: string): NodeChildProcess; + } = require("child_process"); + interface ReadLineOptions { input: NodeJS.ReadableStream; output?: NodeJS.WritableStream; @@ -151,10 +159,65 @@ namespace ts.server { } } + class NodeTypingsInstaller implements ITypingsInstaller { + private installer: NodeChildProcess; + private session: Session; + private cachePath: string; + + constructor(private readonly logger: server.Logger) { + switch (process.platform) { + case "win32": + this.cachePath = normalizeSlashes(combinePaths(process.env.LOCALAPPDATA || process.env.APPDATA, "Microsoft/TypeScript")); + break; + case "darwin": + case "linux": + // TODO: + break; + } + } + + bind(session: Session) { + if (this.logger.hasLevel(LogLevel.requestTime)) { + this.logger.info("Binding...") + } + + this.installer = childProcess.fork(combinePaths(__dirname, "typingsInstaller.js")); + (this.installer).on("message", (m: any) => this.handleMessage(m)); + } + + enqueueInstallTypingsRequest(project: Project, typingOptions: TypingOptions): void { + const request: InstallTypingsRequest = { + projectName: project.getProjectName(), + fileNames: project.getFileNames(), + compilerOptions: project.getCompilerOptions(), + typingOptions, + projectRootPath: (project.projectKind === ProjectKind.Inferred ? "" : getDirectoryPath(project.getProjectName())), // TODO: fixme + safeListPath: (combinePaths(process.cwd(), "typingSafeList.json")), // TODO: fixme + packageNameToTypingLocation: {}, // TODO: fixme + cachePath: this.cachePath + }; + if (this.logger.hasLevel(LogLevel.verbose)) { + this.logger.info(`Sending request: ${JSON.stringify(request)}`); + } + this.installer.send(request); + } + + C = 1; + private handleMessage(response: InstallTypingsResponse) { + if (this.logger.hasLevel(LogLevel.verbose)) { + this.logger.info(`Received response: ${JSON.stringify(response)}`) + } + require("fs").appendFileSync("E:\\sources\\git\\tss.txt", this.C + " !!!::" + JSON.stringify(response) + "\r\n"); + this.C++; + this.session.onTypingsInstalled(response); + require("fs").appendFileSync("E:\\sources\\git\\tss.txt", this.C + " !!!::" + "done" + "\r\n"); + } + } + class IOSession extends Session { - constructor(host: ServerHost, cancellationToken: HostCancellationToken, useSingleInferredProject: boolean, logger: ts.server.Logger) { - // TODO: fixme - super(host, cancellationToken, useSingleInferredProject, undefined, Buffer.byteLength, maxUncompressedMessageSize, compress, process.hrtime, logger); + constructor(host: ServerHost, cancellationToken: HostCancellationToken, useSingleInferredProject: boolean, logger: server.Logger) { + super(host, cancellationToken, useSingleInferredProject, new NodeTypingsInstaller(logger), Buffer.byteLength, maxUncompressedMessageSize, compress, process.hrtime, logger); + (this.typingsInstaller).bind(this); } exit() { diff --git a/src/server/session.ts b/src/server/session.ts index 1f7cee09c2e..4319f5031cd 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -143,7 +143,7 @@ namespace ts.server { private host: ServerHost, cancellationToken: HostCancellationToken, useSingleInferredProject: boolean, - typingsInstaller: ITypingsInstaller, + protected readonly typingsInstaller: ITypingsInstaller, private byteLength: (buf: string, encoding?: string) => number, private maxUncompressedMessageSize: number, private compress: (s: string) => CompressedData, @@ -1452,6 +1452,15 @@ namespace ts.server { } } + public onTypingsInstalled(response: InstallTypingsResponse) { + const project = this.projectService.findProject(response.projectName); + if (!project) { + return; + } + this.projectService.typingsCache.updateTypingsForProject(response.projectName, response.compilerOptions, response.typingOptions, response.typings); + project.updateGraph(); + } + public onMessage(message: string) { this.gcTimer.scheduleCollect(); let start: number[]; diff --git a/src/server/typingsCache.ts b/src/server/typingsCache.ts index dcb17f64eb9..97c90647b34 100644 --- a/src/server/typingsCache.ts +++ b/src/server/typingsCache.ts @@ -2,7 +2,7 @@ namespace ts.server { export interface ITypingsInstaller { - enqueueInstallTypingsRequest(p: Project): void; + enqueueInstallTypingsRequest(p: Project, typingOptions: TypingOptions): void; } export const nullTypingsInstaller: ITypingsInstaller = { @@ -12,7 +12,7 @@ namespace ts.server { class TypingsCacheEntry { readonly typingOptions: TypingOptions; readonly compilerOptions: CompilerOptions; - readonly typings: Path[]; + readonly typings: string[]; } const emptyArray: any[] = []; @@ -88,11 +88,19 @@ namespace ts.server { const entry = this.perProjectCache[project.getProjectName()]; if (!entry || typingOptionsChanged(typingOptions, entry.typingOptions) || compilerOptionsChanged(project.getCompilerOptions(), entry.compilerOptions)) { - this.installer.enqueueInstallTypingsRequest(project); + this.installer.enqueueInstallTypingsRequest(project, typingOptions); } return entry ? entry.typings : emptyArray; } + updateTypingsForProject(projectName: string, compilerOptions: CompilerOptions, typingOptions: TypingOptions, newTypings: string[]) { + this.perProjectCache[projectName] = { + compilerOptions, + typingOptions, + typings: newTypings + }; + } + deleteTypingsForProject(project: Project) { delete this.perProjectCache[project.getProjectName()]; } diff --git a/src/server/typingsInstaller/nodeTypingsInstaller.ts b/src/server/typingsInstaller/nodeTypingsInstaller.ts index ac8b7200524..32a7cfd680a 100644 --- a/src/server/typingsInstaller/nodeTypingsInstaller.ts +++ b/src/server/typingsInstaller/nodeTypingsInstaller.ts @@ -4,7 +4,7 @@ namespace ts.server.typingsInstaller { export class NodeTypingsInstaller extends TypingsInstaller { private execSync: { (command: string, options: { stdio: "ignore" }): any }; - private exec: { (command: string, options: {}, callback?: (error: Error, stdout: string, stderr: string) => void): any }; + private exec: { (command: string, options: { cwd: string }, callback?: (error: Error, stdout: string, stderr: string) => void): any }; constructor() { super(); this.execSync = require("child_process").execSync; @@ -13,14 +13,14 @@ namespace ts.server.typingsInstaller { init() { super.init(); - process.on("install", (req: InstallTypingsRequest) => { + process.on("message", (req: InstallTypingsRequest) => { this.install(req); }) } protected isPackageInstalled(packageName: string) { try { - this.execSync(`npm list --global --depth=1 ${name}`, { stdio: "ignore" }); + this.execSync(`npm list --global --depth=1 ${packageName}`, { stdio: "ignore" }); return true; } catch (e) { @@ -30,7 +30,7 @@ namespace ts.server.typingsInstaller { protected installPackage(packageName: string) { try { - this.execSync(`npm install --global ${name}`, { stdio: "ignore" }); + this.execSync(`npm install --global ${packageName}`, { stdio: "ignore" }); return true; } catch (e) { @@ -42,12 +42,17 @@ namespace ts.server.typingsInstaller { return sys; } + C = 1; + protected sendResponse(response: InstallTypingsResponse) { + (response).___id = [this.C]; + this.C++; + log("sendResponse::" + JSON.stringify(response)); process.send(response); } protected runTsd(cachePath: string, typingsToInstall: string[], postInstallAction: (installedTypings: string[]) => void): void { - this.exec(`tsd install ${typingsToInstall.join(" ")} -ros`, {}, (err, stdout, stderr) => { + this.exec(`tsd install ${typingsToInstall.join(" ")} -ros`, { cwd: cachePath }, (err, stdout, stderr) => { const i = stdout.indexOf("running install"); if (i < 0) { return; diff --git a/src/server/typingsInstaller/typingsInstaller.ts b/src/server/typingsInstaller/typingsInstaller.ts index aadb5241dde..012053544e4 100644 --- a/src/server/typingsInstaller/typingsInstaller.ts +++ b/src/server/typingsInstaller/typingsInstaller.ts @@ -2,7 +2,9 @@ /// namespace ts.server.typingsInstaller { - + export function log(s: string) { + require("fs").appendFileSync("E:\\sources\\git\\installer.txt", s + "\r\n"); + } const DefaultTsdSettings = JSON.stringify({ version: "v4", repo: "DefinitelyTyped/DefinitelyTyped", @@ -20,6 +22,7 @@ namespace ts.server.typingsInstaller { if (!this.isTsdInstalled) { this.isTsdInstalled = this.installPackage("tsd"); } + log(`start ${this.isTsdInstalled}`); } install(req: InstallTypingsRequest) { @@ -27,6 +30,7 @@ namespace ts.server.typingsInstaller { return; } + log(`install ${JSON.stringify(req)}`); const discoverTypingsResult = JsTyping.discoverTypings( this.getInstallTypingHost(), req.fileNames, @@ -36,6 +40,7 @@ namespace ts.server.typingsInstaller { req.typingOptions, req.compilerOptions); + log(`install ${JSON.stringify(discoverTypingsResult)}`); // respond with whatever cached typings we have now this.sendResponse(this.createResponse(req, discoverTypingsResult.cachedTypingPaths)); // start watching files @@ -46,6 +51,7 @@ namespace ts.server.typingsInstaller { private installTypings(req: InstallTypingsRequest, currentlyCachedTypings: string[], typingsToInstall: string[]) { typingsToInstall = filter(typingsToInstall, x => !hasProperty(this.missingTypings, x)); + log(`install ${JSON.stringify(typingsToInstall)}`); if (typingsToInstall.length === 0) { return; } @@ -57,9 +63,11 @@ namespace ts.server.typingsInstaller { host.writeFile(tsdPath, DefaultTsdSettings); } - this.runTsd(tsdPath, typingsToInstall, installedTypings => { + this.runTsd(req.cachePath, typingsToInstall, installedTypings => { // TODO: record new missing package names // TODO: watch project directory + installedTypings = installedTypings.map(x => getNormalizedAbsolutePath(x, req.cachePath)); + log(`include ${JSON.stringify(installedTypings)}`); this.sendResponse(this.createResponse(req, currentlyCachedTypings.concat(installedTypings))); }); }