mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-03-15 05:55:11 -05:00
send begin/end notifications when installing types packages (#12551)
This commit is contained in:
@@ -51,9 +51,8 @@ namespace ts.projectSystem {
|
||||
throttleLimit: number,
|
||||
installTypingHost: server.ServerHost,
|
||||
readonly typesRegistry = createMap<void>(),
|
||||
telemetryEnabled?: boolean,
|
||||
log?: TI.Log) {
|
||||
super(installTypingHost, globalTypingsCacheLocation, safeList.path, throttleLimit, telemetryEnabled, log);
|
||||
super(installTypingHost, globalTypingsCacheLocation, safeList.path, throttleLimit, log);
|
||||
}
|
||||
|
||||
safeFileList = safeList.path;
|
||||
|
||||
@@ -20,13 +20,12 @@ namespace ts.projectSystem {
|
||||
}
|
||||
|
||||
class Installer extends TestTypingsInstaller {
|
||||
constructor(host: server.ServerHost, p?: InstallerParams, telemetryEnabled?: boolean, log?: TI.Log) {
|
||||
constructor(host: server.ServerHost, p?: InstallerParams, log?: TI.Log) {
|
||||
super(
|
||||
(p && p.globalTypingsCacheLocation) || "/a/data",
|
||||
(p && p.throttleLimit) || 5,
|
||||
host,
|
||||
(p && p.typesRegistry),
|
||||
telemetryEnabled,
|
||||
log);
|
||||
}
|
||||
|
||||
@@ -36,7 +35,7 @@ namespace ts.projectSystem {
|
||||
}
|
||||
}
|
||||
|
||||
function executeCommand(self: Installer, host: TestServerHost, installedTypings: string[], typingFiles: FileOrFolder[], cb: TI.RequestCompletedAction): void {
|
||||
function executeCommand(self: Installer, host: TestServerHost, installedTypings: string[] | string, typingFiles: FileOrFolder[], cb: TI.RequestCompletedAction): void {
|
||||
self.addPostExecAction(installedTypings, success => {
|
||||
for (const file of typingFiles) {
|
||||
host.createFileOrFolder(file, /*createParentDirectory*/ true);
|
||||
@@ -907,7 +906,7 @@ namespace ts.projectSystem {
|
||||
const host = createServerHost([f1, packageJson]);
|
||||
const installer = new (class extends Installer {
|
||||
constructor() {
|
||||
super(host, { globalTypingsCacheLocation: "/tmp" }, /*telemetryEnabled*/ false, { isEnabled: () => true, writeLine: msg => messages.push(msg) });
|
||||
super(host, { globalTypingsCacheLocation: "/tmp" }, { isEnabled: () => true, writeLine: msg => messages.push(msg) });
|
||||
}
|
||||
installWorker(_requestId: number, _args: string[], _cwd: string, _cb: server.typingsInstaller.RequestCompletedAction) {
|
||||
assert(false, "runCommand should not be invoked");
|
||||
@@ -971,15 +970,18 @@ namespace ts.projectSystem {
|
||||
let seenTelemetryEvent = false;
|
||||
const installer = new (class extends Installer {
|
||||
constructor() {
|
||||
super(host, { globalTypingsCacheLocation: cachePath, typesRegistry: createTypesRegistry("commander") }, /*telemetryEnabled*/ true);
|
||||
super(host, { globalTypingsCacheLocation: cachePath, typesRegistry: createTypesRegistry("commander") });
|
||||
}
|
||||
installWorker(_requestId: number, _args: string[], _cwd: string, cb: server.typingsInstaller.RequestCompletedAction) {
|
||||
const installedTypings = ["@types/commander"];
|
||||
const typingFiles = [commander];
|
||||
executeCommand(this, host, installedTypings, typingFiles, cb);
|
||||
}
|
||||
sendResponse(response: server.SetTypings | server.InvalidateCachedTypings | server.TypingsInstallEvent) {
|
||||
if (response.kind === server.EventInstall) {
|
||||
sendResponse(response: server.SetTypings | server.InvalidateCachedTypings | server.BeginInstallTypes | server.EndInstallTypes) {
|
||||
if (response.kind === server.EventBeginInstallTypes) {
|
||||
return;
|
||||
}
|
||||
if (response.kind === server.EventEndInstallTypes) {
|
||||
assert.deepEqual(response.packagesToInstall, ["@types/commander"]);
|
||||
seenTelemetryEvent = true;
|
||||
return;
|
||||
@@ -997,4 +999,102 @@ namespace ts.projectSystem {
|
||||
checkProjectActualFiles(projectService.inferredProjects[0], [f1.path, commander.path]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("progress notifications", () => {
|
||||
it ("should be sent for success", () => {
|
||||
const f1 = {
|
||||
path: "/a/app.js",
|
||||
content: ""
|
||||
};
|
||||
const package = {
|
||||
path: "/a/package.json",
|
||||
content: JSON.stringify({ dependencies: { "commander": "1.0.0" } })
|
||||
};
|
||||
const cachePath = "/a/cache/";
|
||||
const commander = {
|
||||
path: cachePath + "node_modules/@types/commander/index.d.ts",
|
||||
content: "export let x: number"
|
||||
};
|
||||
const host = createServerHost([f1, package]);
|
||||
let beginEvent: server.BeginInstallTypes;
|
||||
let endEvent: server.EndInstallTypes;
|
||||
const installer = new (class extends Installer {
|
||||
constructor() {
|
||||
super(host, { globalTypingsCacheLocation: cachePath, typesRegistry: createTypesRegistry("commander") });
|
||||
}
|
||||
installWorker(_requestId: number, _args: string[], _cwd: string, cb: server.typingsInstaller.RequestCompletedAction) {
|
||||
const installedTypings = ["@types/commander"];
|
||||
const typingFiles = [commander];
|
||||
executeCommand(this, host, installedTypings, typingFiles, cb);
|
||||
}
|
||||
sendResponse(response: server.SetTypings | server.InvalidateCachedTypings | server.BeginInstallTypes | server.EndInstallTypes) {
|
||||
if (response.kind === server.EventBeginInstallTypes) {
|
||||
beginEvent = response;
|
||||
return;
|
||||
}
|
||||
if (response.kind === server.EventEndInstallTypes) {
|
||||
endEvent = response;
|
||||
return;
|
||||
}
|
||||
super.sendResponse(response);
|
||||
}
|
||||
})();
|
||||
const projectService = createProjectService(host, { typingsInstaller: installer });
|
||||
projectService.openClientFile(f1.path);
|
||||
|
||||
installer.installAll(/*expectedCount*/ 1);
|
||||
|
||||
assert.isTrue(!!beginEvent);
|
||||
assert.isTrue(!!endEvent);
|
||||
assert.isTrue(beginEvent.eventId === endEvent.eventId);
|
||||
assert.isTrue(endEvent.installSuccess);
|
||||
checkNumberOfProjects(projectService, { inferredProjects: 1 });
|
||||
checkProjectActualFiles(projectService.inferredProjects[0], [f1.path, commander.path]);
|
||||
});
|
||||
|
||||
it ("should be sent for error", () => {
|
||||
const f1 = {
|
||||
path: "/a/app.js",
|
||||
content: ""
|
||||
};
|
||||
const package = {
|
||||
path: "/a/package.json",
|
||||
content: JSON.stringify({ dependencies: { "commander": "1.0.0" } })
|
||||
};
|
||||
const cachePath = "/a/cache/";
|
||||
const host = createServerHost([f1, package]);
|
||||
let beginEvent: server.BeginInstallTypes;
|
||||
let endEvent: server.EndInstallTypes;
|
||||
const installer = new (class extends Installer {
|
||||
constructor() {
|
||||
super(host, { globalTypingsCacheLocation: cachePath, typesRegistry: createTypesRegistry("commander") });
|
||||
}
|
||||
installWorker(_requestId: number, _args: string[], _cwd: string, cb: server.typingsInstaller.RequestCompletedAction) {
|
||||
executeCommand(this, host, "", [], cb);
|
||||
}
|
||||
sendResponse(response: server.SetTypings | server.InvalidateCachedTypings | server.BeginInstallTypes | server.EndInstallTypes) {
|
||||
if (response.kind === server.EventBeginInstallTypes) {
|
||||
beginEvent = response;
|
||||
return;
|
||||
}
|
||||
if (response.kind === server.EventEndInstallTypes) {
|
||||
endEvent = response;
|
||||
return;
|
||||
}
|
||||
super.sendResponse(response);
|
||||
}
|
||||
})();
|
||||
const projectService = createProjectService(host, { typingsInstaller: installer });
|
||||
projectService.openClientFile(f1.path);
|
||||
|
||||
installer.installAll(/*expectedCount*/ 1);
|
||||
|
||||
assert.isTrue(!!beginEvent);
|
||||
assert.isTrue(!!endEvent);
|
||||
assert.isTrue(beginEvent.eventId === endEvent.eventId);
|
||||
assert.isFalse(endEvent.installSuccess);
|
||||
checkNumberOfProjects(projectService, { inferredProjects: 1 });
|
||||
checkProjectActualFiles(projectService.inferredProjects[0], [f1.path]);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -2117,6 +2117,40 @@ namespace ts.server.protocol {
|
||||
typingsInstallerVersion: string;
|
||||
}
|
||||
|
||||
export type BeginInstallTypesEventName = "beginInstallTypes";
|
||||
export type EndInstallTypesEventName = "endInstallTypes";
|
||||
|
||||
export interface BeginInstallTypesEvent extends Event {
|
||||
event: BeginInstallTypesEventName;
|
||||
body: BeginInstallTypesEventBody;
|
||||
}
|
||||
|
||||
export interface EndInstallTypesEvent extends Event {
|
||||
event: EndInstallTypesEventName;
|
||||
body: EndInstallTypesEventBody;
|
||||
}
|
||||
|
||||
export interface InstallTypesEventBody {
|
||||
/**
|
||||
* correlation id to match begin and end events
|
||||
*/
|
||||
eventId: number;
|
||||
/**
|
||||
* list of packages to install
|
||||
*/
|
||||
packages: ReadonlyArray<string>;
|
||||
}
|
||||
|
||||
export interface BeginInstallTypesEventBody extends InstallTypesEventBody {
|
||||
}
|
||||
|
||||
export interface EndInstallTypesEventBody extends InstallTypesEventBody {
|
||||
/**
|
||||
* true if installation succeeded, otherwise false
|
||||
*/
|
||||
success: boolean;
|
||||
}
|
||||
|
||||
export interface NavBarResponse extends Response {
|
||||
body?: NavigationBarItem[];
|
||||
}
|
||||
|
||||
@@ -198,7 +198,7 @@ namespace ts.server {
|
||||
private socket: NodeSocket;
|
||||
private projectService: ProjectService;
|
||||
private throttledOperations: ThrottledOperations;
|
||||
private telemetrySender: EventSender;
|
||||
private eventSender: EventSender;
|
||||
|
||||
constructor(
|
||||
private readonly telemetryEnabled: boolean,
|
||||
@@ -231,7 +231,7 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
setTelemetrySender(telemetrySender: EventSender) {
|
||||
this.telemetrySender = telemetrySender;
|
||||
this.eventSender = telemetrySender;
|
||||
}
|
||||
|
||||
attach(projectService: ProjectService) {
|
||||
@@ -291,12 +291,30 @@ namespace ts.server {
|
||||
});
|
||||
}
|
||||
|
||||
private handleMessage(response: SetTypings | InvalidateCachedTypings | TypingsInstallEvent) {
|
||||
private handleMessage(response: SetTypings | InvalidateCachedTypings | BeginInstallTypes | EndInstallTypes) {
|
||||
if (this.logger.hasLevel(LogLevel.verbose)) {
|
||||
this.logger.info(`Received response: ${JSON.stringify(response)}`);
|
||||
}
|
||||
if (response.kind === EventInstall) {
|
||||
if (this.telemetrySender) {
|
||||
|
||||
if (response.kind === EventBeginInstallTypes) {
|
||||
if (!this.eventSender) {
|
||||
return;
|
||||
}
|
||||
const body: protocol.BeginInstallTypesEventBody = {
|
||||
eventId: response.eventId,
|
||||
packages: response.packagesToInstall,
|
||||
};
|
||||
const eventName: protocol.BeginInstallTypesEventName = "beginInstallTypes";
|
||||
this.eventSender.event(body, eventName);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.kind === EventEndInstallTypes) {
|
||||
if (!this.eventSender) {
|
||||
return;
|
||||
}
|
||||
if (this.telemetryEnabled) {
|
||||
const body: protocol.TypingsInstalledTelemetryEventBody = {
|
||||
telemetryEventName: "typingsInstalled",
|
||||
payload: {
|
||||
@@ -306,10 +324,19 @@ namespace ts.server {
|
||||
}
|
||||
};
|
||||
const eventName: protocol.TelemetryEventName = "telemetry";
|
||||
this.telemetrySender.event(body, eventName);
|
||||
this.eventSender.event(body, eventName);
|
||||
}
|
||||
|
||||
const body: protocol.EndInstallTypesEventBody = {
|
||||
eventId: response.eventId,
|
||||
packages: response.packagesToInstall,
|
||||
success: response.installSuccess,
|
||||
};
|
||||
const eventName: protocol.EndInstallTypesEventName = "endInstallTypes";
|
||||
this.eventSender.event(body, eventName);
|
||||
return;
|
||||
}
|
||||
|
||||
this.projectService.updateTypingsForProject(response);
|
||||
if (response.kind == ActionSet && this.socket) {
|
||||
this.sendEvent(0, "setTypings", response);
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
namespace ts.server {
|
||||
export const ActionSet: ActionSet = "action::set";
|
||||
export const ActionInvalidate: ActionInvalidate = "action::invalidate";
|
||||
export const EventInstall: EventInstall = "event::install";
|
||||
export const EventBeginInstallTypes: EventBeginInstallTypes = "event::beginInstallTypes";
|
||||
export const EventEndInstallTypes: EventEndInstallTypes = "event::endInstallTypes";
|
||||
|
||||
export namespace Arguments {
|
||||
export const GlobalCacheLocation = "--globalTypingsCacheLocation";
|
||||
|
||||
22
src/server/types.d.ts
vendored
22
src/server/types.d.ts
vendored
@@ -43,10 +43,11 @@ declare namespace ts.server {
|
||||
|
||||
export type ActionSet = "action::set";
|
||||
export type ActionInvalidate = "action::invalidate";
|
||||
export type EventInstall = "event::install";
|
||||
export type EventBeginInstallTypes = "event::beginInstallTypes";
|
||||
export type EventEndInstallTypes = "event::endInstallTypes";
|
||||
|
||||
export interface TypingInstallerResponse {
|
||||
readonly kind: ActionSet | ActionInvalidate | EventInstall;
|
||||
readonly kind: ActionSet | ActionInvalidate | EventBeginInstallTypes | EventEndInstallTypes;
|
||||
}
|
||||
|
||||
export interface ProjectResponse extends TypingInstallerResponse {
|
||||
@@ -65,11 +66,20 @@ declare namespace ts.server {
|
||||
readonly kind: ActionInvalidate;
|
||||
}
|
||||
|
||||
export interface TypingsInstallEvent extends TypingInstallerResponse {
|
||||
readonly packagesToInstall: ReadonlyArray<string>;
|
||||
readonly kind: EventInstall;
|
||||
readonly installSuccess: boolean;
|
||||
export interface InstallTypes extends ProjectResponse {
|
||||
readonly kind: EventBeginInstallTypes | EventEndInstallTypes;
|
||||
readonly eventId: number;
|
||||
readonly typingsInstallerVersion: string;
|
||||
readonly packagesToInstall: ReadonlyArray<string>;
|
||||
}
|
||||
|
||||
export interface BeginInstallTypes extends InstallTypes {
|
||||
readonly kind: EventBeginInstallTypes;
|
||||
}
|
||||
|
||||
export interface EndInstallTypes extends InstallTypes {
|
||||
readonly kind: EventEndInstallTypes;
|
||||
readonly installSuccess: boolean;
|
||||
}
|
||||
|
||||
export interface InstallTypingHost extends JsTyping.TypingResolutionHost {
|
||||
|
||||
@@ -70,13 +70,12 @@ namespace ts.server.typingsInstaller {
|
||||
private readonly npmPath: string;
|
||||
readonly typesRegistry: Map<void>;
|
||||
|
||||
constructor(globalTypingsCacheLocation: string, throttleLimit: number, telemetryEnabled: boolean, log: Log) {
|
||||
constructor(globalTypingsCacheLocation: string, throttleLimit: number, log: Log) {
|
||||
super(
|
||||
sys,
|
||||
globalTypingsCacheLocation,
|
||||
toPath("typingSafeList.json", __dirname, createGetCanonicalFileName(sys.useCaseSensitiveFileNames)),
|
||||
throttleLimit,
|
||||
telemetryEnabled,
|
||||
log);
|
||||
if (this.log.isEnabled()) {
|
||||
this.log.writeLine(`Process id: ${process.pid}`);
|
||||
@@ -113,7 +112,7 @@ namespace ts.server.typingsInstaller {
|
||||
});
|
||||
}
|
||||
|
||||
protected sendResponse(response: SetTypings | InvalidateCachedTypings) {
|
||||
protected sendResponse(response: SetTypings | InvalidateCachedTypings | BeginInstallTypes | EndInstallTypes) {
|
||||
if (this.log.isEnabled()) {
|
||||
this.log.writeLine(`Sending response: ${JSON.stringify(response)}`);
|
||||
}
|
||||
@@ -149,7 +148,6 @@ namespace ts.server.typingsInstaller {
|
||||
|
||||
const logFilePath = findArgument(server.Arguments.LogFile);
|
||||
const globalTypingsCacheLocation = findArgument(server.Arguments.GlobalCacheLocation);
|
||||
const telemetryEnabled = hasArgument(server.Arguments.EnableTelemetry);
|
||||
|
||||
const log = new FileLog(logFilePath);
|
||||
if (log.isEnabled()) {
|
||||
@@ -163,6 +161,6 @@ namespace ts.server.typingsInstaller {
|
||||
}
|
||||
process.exit(0);
|
||||
});
|
||||
const installer = new NodeTypingsInstaller(globalTypingsCacheLocation, /*throttleLimit*/5, telemetryEnabled, log);
|
||||
const installer = new NodeTypingsInstaller(globalTypingsCacheLocation, /*throttleLimit*/5, log);
|
||||
installer.listen();
|
||||
}
|
||||
@@ -97,7 +97,6 @@ namespace ts.server.typingsInstaller {
|
||||
readonly globalCachePath: string,
|
||||
readonly safeListPath: Path,
|
||||
readonly throttleLimit: number,
|
||||
readonly telemetryEnabled: boolean,
|
||||
protected readonly log = nullLog) {
|
||||
if (this.log.isEnabled()) {
|
||||
this.log.writeLine(`Global cache location '${globalCachePath}', safe file path '${safeListPath}'`);
|
||||
@@ -309,47 +308,58 @@ namespace ts.server.typingsInstaller {
|
||||
const requestId = this.installRunCount;
|
||||
this.installRunCount++;
|
||||
|
||||
// send progress event
|
||||
this.sendResponse(<BeginInstallTypes>{
|
||||
kind: EventBeginInstallTypes,
|
||||
eventId: requestId,
|
||||
typingsInstallerVersion: ts.version, // qualified explicitly to prevent occasional shadowing
|
||||
projectName: req.projectName
|
||||
});
|
||||
|
||||
this.installTypingsAsync(requestId, scopedTypings, cachePath, ok => {
|
||||
if (this.telemetryEnabled) {
|
||||
this.sendResponse(<TypingsInstallEvent>{
|
||||
kind: EventInstall,
|
||||
try {
|
||||
if (!ok) {
|
||||
if (this.log.isEnabled()) {
|
||||
this.log.writeLine(`install request failed, marking packages as missing to prevent repeated requests: ${JSON.stringify(filteredTypings)}`);
|
||||
}
|
||||
for (const typing of filteredTypings) {
|
||||
this.missingTypingsSet[typing] = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: watch project directory
|
||||
if (this.log.isEnabled()) {
|
||||
this.log.writeLine(`Installed typings ${JSON.stringify(scopedTypings)}`);
|
||||
}
|
||||
const installedTypingFiles: string[] = [];
|
||||
for (const packageName of filteredTypings) {
|
||||
const typingFile = typingToFileName(cachePath, packageName, this.installTypingHost, this.log);
|
||||
if (!typingFile) {
|
||||
this.missingTypingsSet[packageName] = true;
|
||||
continue;
|
||||
}
|
||||
if (!this.packageNameToTypingLocation[packageName]) {
|
||||
this.packageNameToTypingLocation[packageName] = typingFile;
|
||||
}
|
||||
installedTypingFiles.push(typingFile);
|
||||
}
|
||||
if (this.log.isEnabled()) {
|
||||
this.log.writeLine(`Installed typing files ${JSON.stringify(installedTypingFiles)}`);
|
||||
}
|
||||
|
||||
this.sendResponse(this.createSetTypings(req, currentlyCachedTypings.concat(installedTypingFiles)));
|
||||
}
|
||||
finally {
|
||||
this.sendResponse(<EndInstallTypes>{
|
||||
kind: EventEndInstallTypes,
|
||||
eventId: requestId,
|
||||
projectName: req.projectName,
|
||||
packagesToInstall: scopedTypings,
|
||||
installSuccess: ok,
|
||||
typingsInstallerVersion: ts.version // qualified explicitly to prevent occasional shadowing
|
||||
});
|
||||
}
|
||||
|
||||
if (!ok) {
|
||||
if (this.log.isEnabled()) {
|
||||
this.log.writeLine(`install request failed, marking packages as missing to prevent repeated requests: ${JSON.stringify(filteredTypings)}`);
|
||||
}
|
||||
for (const typing of filteredTypings) {
|
||||
this.missingTypingsSet[typing] = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: watch project directory
|
||||
if (this.log.isEnabled()) {
|
||||
this.log.writeLine(`Installed typings ${JSON.stringify(scopedTypings)}`);
|
||||
}
|
||||
const installedTypingFiles: string[] = [];
|
||||
for (const packageName of filteredTypings) {
|
||||
const typingFile = typingToFileName(cachePath, packageName, this.installTypingHost, this.log);
|
||||
if (!typingFile) {
|
||||
this.missingTypingsSet[packageName] = true;
|
||||
continue;
|
||||
}
|
||||
if (!this.packageNameToTypingLocation[packageName]) {
|
||||
this.packageNameToTypingLocation[packageName] = typingFile;
|
||||
}
|
||||
installedTypingFiles.push(typingFile);
|
||||
}
|
||||
if (this.log.isEnabled()) {
|
||||
this.log.writeLine(`Installed typing files ${JSON.stringify(installedTypingFiles)}`);
|
||||
}
|
||||
|
||||
this.sendResponse(this.createSetTypings(req, currentlyCachedTypings.concat(installedTypingFiles)));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -417,6 +427,6 @@ namespace ts.server.typingsInstaller {
|
||||
}
|
||||
|
||||
protected abstract installWorker(requestId: number, args: string[], cwd: string, onRequestCompleted: RequestCompletedAction): void;
|
||||
protected abstract sendResponse(response: SetTypings | InvalidateCachedTypings | TypingsInstallEvent): void;
|
||||
protected abstract sendResponse(response: SetTypings | InvalidateCachedTypings | BeginInstallTypes | EndInstallTypes): void;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user