Skip to content

Commit edfd104

Browse files
committed
use localhost:<eventPort> to send notifications when typings are updated
1 parent 3f18fbc commit edfd104

4 files changed

Lines changed: 90 additions & 22 deletions

File tree

src/server/server.ts

Lines changed: 45 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,23 @@ namespace ts.server {
99
gzipSync(buf: Buffer): Buffer
1010
} = require("zlib");
1111

12+
const net: {
13+
connect(options: { port: number }, onConnect?: () => void): NodeSocket
14+
} = require("net");
15+
16+
const childProcess: {
17+
fork(modulePath: string, args: string[], options?: { execArgv: string[], env?: MapLike<string> }): NodeChildProcess;
18+
} = require("child_process");
19+
1220
interface NodeChildProcess {
1321
send(message: any, sendHandle?: any): void;
1422
on(message: "message", f: (m: any) => void): void;
1523
kill(): void;
1624
}
1725

18-
const childProcess: {
19-
fork(modulePath: string): NodeChildProcess;
20-
} = require("child_process");
26+
interface NodeSocket {
27+
write(data: string, encoding: string): boolean;
28+
}
2129

2230
interface ReadLineOptions {
2331
input: NodeJS.ReadableStream;
@@ -106,6 +114,10 @@ namespace ts.server {
106114
}
107115
}
108116

117+
getLogFileName() {
118+
return this.logFilename;
119+
}
120+
109121
perftrc(s: string) {
110122
this.msg(s, Msg.Perf);
111123
}
@@ -163,9 +175,15 @@ namespace ts.server {
163175

164176
class NodeTypingsInstaller implements ITypingsInstaller {
165177
private installer: NodeChildProcess;
178+
private socket: NodeSocket;
166179
private projectService: ProjectService;
167180

168-
constructor(private readonly logger: server.Logger) {
181+
constructor(private readonly logger: server.Logger, private readonly eventPort: number) {
182+
if (eventPort) {
183+
const s = net.connect({ port: eventPort }, () => {
184+
this.socket = s;
185+
});
186+
}
169187
}
170188

171189
attach(projectService: ProjectService) {
@@ -174,7 +192,11 @@ namespace ts.server {
174192
this.logger.info("Binding...");
175193
}
176194

177-
this.installer = childProcess.fork(combinePaths(__dirname, "typingsInstaller.js"));
195+
let args: string[] = [];
196+
if (this.logger.loggingEnabled() && this.logger.getLogFileName()) {
197+
args = [ "--logFile", combinePaths(getDirectoryPath(normalizeSlashes(this.logger.getLogFileName())), `ti-${process.pid}.log`) ];
198+
}
199+
this.installer = childProcess.fork(combinePaths(__dirname, "typingsInstaller.js"), args);
178200
this.installer.on("message", m => this.handleMessage(m));
179201
process.on("exit", () => {
180202
this.installer.kill();
@@ -198,12 +220,15 @@ namespace ts.server {
198220
this.logger.info(`Received response: ${JSON.stringify(response)}`);
199221
}
200222
this.projectService.updateTypingsForProject(response);
223+
if (response.kind == "set" && this.socket) {
224+
this.socket.write(JSON.stringify({ kind: "updateTypings", message: response }) + "\r\n", "utf8");
225+
}
201226
}
202227
}
203228

204229
class IOSession extends Session {
205-
constructor(host: ServerHost, cancellationToken: HostCancellationToken, useSingleInferredProject: boolean, logger: server.Logger) {
206-
super(host, cancellationToken, useSingleInferredProject, new NodeTypingsInstaller(logger), Buffer.byteLength, maxUncompressedMessageSize, compress, process.hrtime, logger);
230+
constructor(host: ServerHost, cancellationToken: HostCancellationToken, eventPort: number, useSingleInferredProject: boolean, logger: server.Logger) {
231+
super(host, cancellationToken, useSingleInferredProject, new NodeTypingsInstaller(logger, eventPort), Buffer.byteLength, maxUncompressedMessageSize, compress, process.hrtime, logger);
207232
}
208233

209234
exit() {
@@ -435,8 +460,19 @@ namespace ts.server {
435460
};
436461
};
437462

438-
const useSingleInferredProject = sys.args.some(arg => arg === "--useSingleInferredProject");
439-
const ioSession = new IOSession(sys, cancellationToken, useSingleInferredProject, logger);
463+
let eventPort: number;
464+
{
465+
const index = sys.args.indexOf("--eventPort");
466+
if (index >= 0 && index < sys.args.length - 1) {
467+
const v = parseInt(sys.args[index + 1]);
468+
if (!isNaN(v)) {
469+
eventPort = v;
470+
}
471+
}
472+
}
473+
474+
const useSingleInferredProject = sys.args.indexOf("--useSingleInferredProject") >= 0;
475+
const ioSession = new IOSession(sys, cancellationToken, eventPort, useSingleInferredProject, logger);
440476
process.on("uncaughtException", function (err: Error) {
441477
ioSession.logError(err, "unknown");
442478
});

src/server/typingsCache.ts

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ namespace ts.server {
1717
readonly typingOptions: TypingOptions;
1818
readonly compilerOptions: CompilerOptions;
1919
readonly typings: TypingsArray;
20+
poisoned: boolean;
2021
}
2122

2223
const emptyArray: any[] = [];
@@ -27,10 +28,7 @@ namespace ts.server {
2728
return (<ConfiguredProject>proj).getTypingOptions();
2829
}
2930

30-
const enableAutoDiscovery =
31-
proj.projectKind === ProjectKind.Inferred &&
32-
proj.getCompilerOptions().allowJs &&
33-
proj.getFileNames().every(f => fileExtensionIsAny(f, jsOrDts));
31+
const enableAutoDiscovery = proj.getFileNames().every(f => fileExtensionIsAny(f, jsOrDts));
3432

3533
// TODO: add .d.ts files to excludes
3634
return { enableAutoDiscovery, include: emptyArray, exclude: emptyArray };
@@ -98,10 +96,20 @@ namespace ts.server {
9896
}
9997

10098
const entry = this.perProjectCache[project.getProjectName()];
99+
const result: TypingsArray = entry ? entry.typings : <any>emptyArray;
101100
if (!entry || typingOptionsChanged(typingOptions, entry.typingOptions) || compilerOptionsChanged(project.getCompilerOptions(), entry.compilerOptions)) {
101+
// something has been changed, issue a request to update typings
102102
this.installer.enqueueInstallTypingsRequest(project, typingOptions);
103+
// Note: entry is now poisoned since it does not really contain typings for a given combination of compiler options\typings options.
104+
// instead it acts as a placeholder to prevent issuing multiple requests
105+
this.perProjectCache[project.getProjectName()] = {
106+
compilerOptions: project.getCompilerOptions(),
107+
typingOptions,
108+
typings: result,
109+
poisoned: true
110+
};
103111
}
104-
return entry ? entry.typings : <any>emptyArray;
112+
return result;
105113
}
106114

107115
invalidateCachedTypingsForProject(project: Project) {
@@ -116,7 +124,8 @@ namespace ts.server {
116124
this.perProjectCache[projectName] = {
117125
compilerOptions,
118126
typingOptions,
119-
typings: toTypingsArray(newTypings)
127+
typings: toTypingsArray(newTypings),
128+
poisoned: false
120129
};
121130
}
122131

src/server/typingsInstaller/nodeTypingsInstaller.ts

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ namespace ts.server.typingsInstaller {
4242
}
4343

4444
export class NodeTypingsInstaller extends TypingsInstaller {
45-
private execSync: { (command: string, options: { stdio: "ignore" | "pipe" }): Buffer | string };
45+
private execSync: { (command: string, options: { stdio: "ignore" | "pipe", cwd?: string }): Buffer | string };
4646
private exec: { (command: string, options: { cwd: string }, callback?: (error: Error, stdout: string, stderr: string) => void): any };
4747
private npmBinPath: string;
4848

@@ -86,7 +86,7 @@ namespace ts.server.typingsInstaller {
8686

8787
protected isPackageInstalled(packageName: string) {
8888
try {
89-
const output = this.execSync(`npm list --global --depth=1 ${packageName}`, { stdio: "pipe" }).toString();
89+
const output = this.execSync(`npm list --silent --global --depth=1 ${packageName}`, { stdio: "pipe" }).toString();
9090
if (this.log.isEnabled()) {
9191
this.log.writeLine(`IsPackageInstalled::stdout '${output}'`);
9292
}
@@ -103,7 +103,7 @@ namespace ts.server.typingsInstaller {
103103

104104
protected installPackage(packageName: string) {
105105
try {
106-
const output = this.execSync(`npm install --global ${packageName}`, { stdio: "pipe" }).toString();
106+
const output = this.execSync(`npm install --silent --global ${packageName}`, { stdio: "pipe" }).toString();
107107
if (this.log.isEnabled()) {
108108
this.log.writeLine(`installPackage::stdout '${output}'`);
109109
}
@@ -132,10 +132,11 @@ namespace ts.server.typingsInstaller {
132132
const id = this.tsdRunCount;
133133
this.tsdRunCount++;
134134
const tsdPath = combinePaths(this.npmBinPath, "tsd");
135+
const command = `${tsdPath} install ${typingsToInstall.join(" ")} -ros`;
135136
if (this.log.isEnabled()) {
136-
this.log.writeLine(`Running tsd ${id}, tsd path '${tsdPath}, typings to install: ${JSON.stringify(typingsToInstall)}. cache path '${cachePath}'`);
137+
this.log.writeLine(`Running tsd ${id}, command '${command}'. cache path '${cachePath}'`);
137138
}
138-
this.exec(`${tsdPath} install ${typingsToInstall.join(" ")} -ros`, { cwd: cachePath }, (err, stdout, stderr) => {
139+
this.exec(command, { cwd: cachePath }, (err, stdout, stderr) => {
139140
if (this.log.isEnabled()) {
140141
this.log.writeLine(`TSD ${id} stdout: ${stdout}`);
141142
this.log.writeLine(`TSD ${id} stderr: ${stderr}`);
@@ -157,7 +158,14 @@ namespace ts.server.typingsInstaller {
157158
}
158159
}
159160

160-
const log = new FileLog(process.env.TI_LOG_FILE);
161+
let logFilePath: string;
162+
{
163+
const logFileIndex = sys.args.indexOf("--logFile");
164+
if (logFileIndex >= 0 && logFileIndex < sys.args.length - 1) {
165+
logFilePath = sys.args[logFileIndex + 1];
166+
}
167+
}
168+
const log = new FileLog(logFilePath);
161169
if (log.isEnabled()) {
162170
process.on("uncaughtException", (e: Error) => {
163171
log.writeLine(`Unhandled exception: ${e} at ${e.stack}`);

src/server/utilities.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ namespace ts.server {
1717
startGroup(): void;
1818
endGroup(): void;
1919
msg(s: string, type?: Msg.Types): void;
20+
getLogFileName(): string;
2021
}
2122

2223
export namespace Msg {
@@ -29,13 +30,27 @@ namespace ts.server {
2930
export type Types = Err | Info | Perf;
3031
}
3132

33+
function getProjectRootPath(project: Project): Path {
34+
switch (project.projectKind) {
35+
case ProjectKind.Configured:
36+
return <Path>project.getProjectName();
37+
case ProjectKind.Inferred:
38+
// TODO: fixme
39+
return <Path>"";
40+
case ProjectKind.External:
41+
const projectName = project.getProjectName();
42+
const host = project.projectService.host;
43+
return host.fileExists(projectName) ? <Path>getDirectoryPath(projectName) : <Path>projectName;
44+
}
45+
}
46+
3247
export function createInstallTypingsRequest(project: Project, typingOptions: TypingOptions, cachePath?: string): DiscoverTypings {
3348
return {
3449
projectName: project.getProjectName(),
3550
fileNames: project.getFileNames(),
3651
compilerOptions: project.getCompilerOptions(),
3752
typingOptions,
38-
projectRootPath: <Path>(project.projectKind === ProjectKind.Inferred ? "" : getDirectoryPath(project.getProjectName())), // TODO: fixme
53+
projectRootPath: getProjectRootPath(project),
3954
cachePath,
4055
kind: "discover"
4156
};

0 commit comments

Comments
 (0)