use localhost:<eventPort> to send notifications when typings are updated

This commit is contained in:
Vladimir Matveev
2016-08-18 14:29:21 -07:00
parent 3f18fbc145
commit edfd104e56
4 changed files with 90 additions and 22 deletions
+45 -9
View File
@@ -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<string> }): 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");
});
+15 -6
View File
@@ -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 (<ConfiguredProject>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 : <any>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 : <any>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
};
}
@@ -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}`);
+16 -1
View File
@@ -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 <Path>project.getProjectName();
case ProjectKind.Inferred:
// TODO: fixme
return <Path>"";
case ProjectKind.External:
const projectName = project.getProjectName();
const host = project.projectService.host;
return host.fileExists(projectName) ? <Path>getDirectoryPath(projectName) : <Path>projectName;
}
}
export function createInstallTypingsRequest(project: Project, typingOptions: TypingOptions, cachePath?: string): DiscoverTypings {
return {
projectName: project.getProjectName(),
fileNames: project.getFileNames(),
compilerOptions: project.getCompilerOptions(),
typingOptions,
projectRootPath: <Path>(project.projectKind === ProjectKind.Inferred ? "" : getDirectoryPath(project.getProjectName())), // TODO: fixme
projectRootPath: getProjectRootPath(project),
cachePath,
kind: "discover"
};