2016-08-12 20:04:43 +02:00
|
|
|
namespace ts.server.typingsInstaller {
|
2016-08-26 01:25:34 +02:00
|
|
|
interface NpmConfig {
|
|
|
|
devDependencies: MapLike<any>;
|
2016-08-15 20:48:28 +02:00
|
|
|
}
|
|
|
|
|
2018-01-11 18:13:33 +01:00
|
|
|
interface NpmLock {
|
|
|
|
dependencies: { [packageName: string]: { version: string } };
|
|
|
|
}
|
|
|
|
|
2016-08-16 00:59:31 +02:00
|
|
|
export interface Log {
|
|
|
|
isEnabled(): boolean;
|
|
|
|
writeLine(text: string): void;
|
|
|
|
}
|
2016-08-17 23:47:54 +02:00
|
|
|
|
2016-08-16 00:59:31 +02:00
|
|
|
const nullLog: Log = {
|
|
|
|
isEnabled: () => false,
|
2016-10-19 23:15:14 +02:00
|
|
|
writeLine: noop
|
2016-08-17 23:47:54 +02:00
|
|
|
};
|
2016-08-16 00:59:31 +02:00
|
|
|
|
2016-11-15 21:53:46 +01:00
|
|
|
function typingToFileName(cachePath: string, packageName: string, installTypingHost: InstallTypingHost, log: Log): string {
|
|
|
|
try {
|
|
|
|
const result = resolveModuleName(packageName, combinePaths(cachePath, "index.d.ts"), { moduleResolution: ModuleResolutionKind.NodeJs }, installTypingHost);
|
|
|
|
return result.resolvedModule && result.resolvedModule.resolvedFileName;
|
|
|
|
}
|
|
|
|
catch (e) {
|
|
|
|
if (log.isEnabled()) {
|
|
|
|
log.writeLine(`Failed to resolve ${packageName} in folder '${cachePath}': ${(<Error>e).message}`);
|
|
|
|
}
|
|
|
|
return undefined;
|
|
|
|
}
|
2016-08-15 20:48:28 +02:00
|
|
|
}
|
|
|
|
|
2016-10-01 20:56:51 +02:00
|
|
|
|
2016-10-13 19:19:18 +02:00
|
|
|
export type RequestCompletedAction = (success: boolean) => void;
|
2017-09-07 18:14:59 +02:00
|
|
|
interface PendingRequest {
|
2016-09-20 23:14:51 +02:00
|
|
|
requestId: number;
|
2017-10-18 00:04:09 +02:00
|
|
|
packageNames: string[];
|
2016-09-20 23:14:51 +02:00
|
|
|
cwd: string;
|
2016-11-10 23:28:34 +01:00
|
|
|
onRequestCompleted: RequestCompletedAction;
|
2017-09-07 18:14:59 +02:00
|
|
|
}
|
2016-09-20 23:14:51 +02:00
|
|
|
|
2016-08-12 20:04:43 +02:00
|
|
|
export abstract class TypingsInstaller {
|
2017-11-21 01:43:02 +01:00
|
|
|
private readonly packageNameToTypingLocation: Map<JsTyping.CachedTyping> = createMap<JsTyping.CachedTyping>();
|
2016-10-28 00:50:21 +02:00
|
|
|
private readonly missingTypingsSet: Map<true> = createMap<true>();
|
2018-01-11 20:11:26 +01:00
|
|
|
private readonly knownCachesSet: Map<true> = createMap<true>();
|
2016-10-28 00:50:21 +02:00
|
|
|
private readonly projectWatchers: Map<FileWatcher[]> = createMap<FileWatcher[]>();
|
2017-07-27 19:54:47 +02:00
|
|
|
private safeList: JsTyping.SafeList | undefined;
|
2016-09-20 23:14:51 +02:00
|
|
|
readonly pendingRunRequests: PendingRequest[] = [];
|
|
|
|
|
2016-09-19 22:56:30 +02:00
|
|
|
private installRunCount = 1;
|
2016-09-20 23:14:51 +02:00
|
|
|
private inFlightRequestCount = 0;
|
2016-08-16 23:21:09 +02:00
|
|
|
|
2018-01-11 18:13:33 +01:00
|
|
|
abstract readonly typesRegistry: Map<MapLike<string>>;
|
2016-08-15 20:48:28 +02:00
|
|
|
|
2016-09-20 23:14:51 +02:00
|
|
|
constructor(
|
2017-07-14 23:26:13 +02:00
|
|
|
protected readonly installTypingHost: InstallTypingHost,
|
|
|
|
private readonly globalCachePath: string,
|
|
|
|
private readonly safeListPath: Path,
|
2017-07-28 01:07:50 +02:00
|
|
|
private readonly typesMapLocation: Path,
|
2017-07-14 23:26:13 +02:00
|
|
|
private readonly throttleLimit: number,
|
2016-09-20 23:14:51 +02:00
|
|
|
protected readonly log = nullLog) {
|
2016-08-16 00:59:31 +02:00
|
|
|
if (this.log.isEnabled()) {
|
2017-07-28 01:07:50 +02:00
|
|
|
this.log.writeLine(`Global cache location '${globalCachePath}', safe file path '${safeListPath}', types map path ${typesMapLocation}`);
|
2016-08-16 00:59:31 +02:00
|
|
|
}
|
2018-01-05 01:44:01 +01:00
|
|
|
this.processCacheLocation(this.globalCachePath);
|
2016-08-12 20:04:43 +02:00
|
|
|
}
|
|
|
|
|
2016-08-16 23:21:09 +02:00
|
|
|
closeProject(req: CloseProject) {
|
|
|
|
this.closeWatchers(req.projectName);
|
|
|
|
}
|
|
|
|
|
2016-08-17 22:01:48 +02:00
|
|
|
private closeWatchers(projectName: string): void {
|
2016-08-16 23:21:09 +02:00
|
|
|
if (this.log.isEnabled()) {
|
|
|
|
this.log.writeLine(`Closing file watchers for project '${projectName}'`);
|
|
|
|
}
|
2016-12-05 23:13:32 +01:00
|
|
|
const watchers = this.projectWatchers.get(projectName);
|
2016-08-16 23:21:09 +02:00
|
|
|
if (!watchers) {
|
|
|
|
if (this.log.isEnabled()) {
|
|
|
|
this.log.writeLine(`No watchers are registered for project '${projectName}'`);
|
|
|
|
}
|
2016-08-17 22:01:48 +02:00
|
|
|
return;
|
2016-08-16 23:21:09 +02:00
|
|
|
}
|
|
|
|
for (const w of watchers) {
|
|
|
|
w.close();
|
|
|
|
}
|
|
|
|
|
2016-12-05 23:13:32 +01:00
|
|
|
this.projectWatchers.delete(projectName);
|
2016-08-16 23:21:09 +02:00
|
|
|
|
|
|
|
if (this.log.isEnabled()) {
|
|
|
|
this.log.writeLine(`Closing file watchers for project '${projectName}' - done.`);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
install(req: DiscoverTypings) {
|
2016-08-16 00:59:31 +02:00
|
|
|
if (this.log.isEnabled()) {
|
|
|
|
this.log.writeLine(`Got install request ${JSON.stringify(req)}`);
|
|
|
|
}
|
2016-08-12 20:04:43 +02:00
|
|
|
|
2016-09-26 20:33:25 +02:00
|
|
|
// load existing typing information from the cache
|
2016-08-15 20:48:28 +02:00
|
|
|
if (req.cachePath) {
|
2016-08-16 00:59:31 +02:00
|
|
|
if (this.log.isEnabled()) {
|
|
|
|
this.log.writeLine(`Request specifies cache path '${req.cachePath}', loading cached information...`);
|
|
|
|
}
|
2018-01-11 20:11:26 +01:00
|
|
|
this.processCacheLocation(req.cachePath);
|
2016-08-15 20:48:28 +02:00
|
|
|
}
|
|
|
|
|
2017-07-27 19:54:47 +02:00
|
|
|
if (this.safeList === undefined) {
|
2017-07-28 01:07:50 +02:00
|
|
|
this.initializeSafeList();
|
2017-07-27 19:54:47 +02:00
|
|
|
}
|
2016-08-12 20:04:43 +02:00
|
|
|
const discoverTypingsResult = JsTyping.discoverTypings(
|
2016-08-15 20:48:28 +02:00
|
|
|
this.installTypingHost,
|
2017-08-23 22:01:14 +02:00
|
|
|
this.log.isEnabled() ? (s => this.log.writeLine(s)) : undefined,
|
2016-08-12 20:04:43 +02:00
|
|
|
req.fileNames,
|
|
|
|
req.projectRootPath,
|
2017-07-27 19:54:47 +02:00
|
|
|
this.safeList,
|
2016-08-15 20:48:28 +02:00
|
|
|
this.packageNameToTypingLocation,
|
2016-11-19 02:46:06 +01:00
|
|
|
req.typeAcquisition,
|
2018-01-11 20:11:26 +01:00
|
|
|
req.unresolvedImports,
|
|
|
|
this.typesRegistry);
|
2016-08-17 23:47:54 +02:00
|
|
|
|
2016-08-16 00:59:31 +02:00
|
|
|
if (this.log.isEnabled()) {
|
|
|
|
this.log.writeLine(`Finished typings discovery: ${JSON.stringify(discoverTypingsResult)}`);
|
|
|
|
}
|
2016-08-12 20:04:43 +02:00
|
|
|
|
2016-08-12 21:14:25 +02:00
|
|
|
// start watching files
|
2016-08-16 23:49:45 +02:00
|
|
|
this.watchFiles(req.projectName, discoverTypingsResult.filesToWatch);
|
2016-08-15 20:48:28 +02:00
|
|
|
|
2016-08-16 00:59:31 +02:00
|
|
|
// install typings
|
2016-08-17 22:01:48 +02:00
|
|
|
if (discoverTypingsResult.newTypingNames.length) {
|
2018-01-11 20:11:26 +01:00
|
|
|
this.installTypings(req, req.cachePath || this.globalCachePath, discoverTypingsResult.cachedTypingPaths, discoverTypingsResult.newTypingNames);
|
2016-08-17 22:01:48 +02:00
|
|
|
}
|
|
|
|
else {
|
2017-11-16 21:37:40 +01:00
|
|
|
this.sendResponse(this.createSetTypings(req, discoverTypingsResult.cachedTypingPaths));
|
2016-08-17 22:01:48 +02:00
|
|
|
if (this.log.isEnabled()) {
|
|
|
|
this.log.writeLine(`No new typings were requested as a result of typings discovery`);
|
|
|
|
}
|
|
|
|
}
|
2016-08-12 20:04:43 +02:00
|
|
|
}
|
|
|
|
|
2017-07-28 01:07:50 +02:00
|
|
|
private initializeSafeList() {
|
|
|
|
// Prefer the safe list from the types map if it exists
|
|
|
|
if (this.typesMapLocation) {
|
|
|
|
const safeListFromMap = JsTyping.loadTypesMap(this.installTypingHost, this.typesMapLocation);
|
|
|
|
if (safeListFromMap) {
|
|
|
|
this.log.writeLine(`Loaded safelist from types map file '${this.typesMapLocation}'`);
|
|
|
|
this.safeList = safeListFromMap;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
this.log.writeLine(`Failed to load safelist from types map file '${this.typesMapLocation}'`);
|
|
|
|
}
|
|
|
|
this.safeList = JsTyping.loadSafeList(this.installTypingHost, this.safeListPath);
|
|
|
|
}
|
|
|
|
|
2018-01-11 20:11:26 +01:00
|
|
|
private processCacheLocation(cacheLocation: string) {
|
2016-08-16 00:59:31 +02:00
|
|
|
if (this.log.isEnabled()) {
|
|
|
|
this.log.writeLine(`Processing cache location '${cacheLocation}'`);
|
|
|
|
}
|
2018-01-11 20:11:26 +01:00
|
|
|
if (this.knownCachesSet.has(cacheLocation)) {
|
2016-08-16 00:59:31 +02:00
|
|
|
if (this.log.isEnabled()) {
|
2016-08-17 23:47:54 +02:00
|
|
|
this.log.writeLine(`Cache location was already processed...`);
|
2016-08-16 00:59:31 +02:00
|
|
|
}
|
2016-08-15 20:48:28 +02:00
|
|
|
return;
|
|
|
|
}
|
2016-08-26 01:25:34 +02:00
|
|
|
const packageJson = combinePaths(cacheLocation, "package.json");
|
2018-01-11 18:13:33 +01:00
|
|
|
const packageLockJson = combinePaths(cacheLocation, "package-lock.json");
|
2016-08-16 00:59:31 +02:00
|
|
|
if (this.log.isEnabled()) {
|
2016-08-26 01:25:34 +02:00
|
|
|
this.log.writeLine(`Trying to find '${packageJson}'...`);
|
2016-08-16 00:59:31 +02:00
|
|
|
}
|
2018-01-11 18:13:33 +01:00
|
|
|
if (this.installTypingHost.fileExists(packageJson) && this.installTypingHost.fileExists(packageLockJson)) {
|
2016-08-26 01:25:34 +02:00
|
|
|
const npmConfig = <NpmConfig>JSON.parse(this.installTypingHost.readFile(packageJson));
|
2018-01-11 18:13:33 +01:00
|
|
|
const npmLock = <NpmLock>JSON.parse(this.installTypingHost.readFile(packageLockJson));
|
2016-08-16 00:59:31 +02:00
|
|
|
if (this.log.isEnabled()) {
|
2016-09-22 23:34:08 +02:00
|
|
|
this.log.writeLine(`Loaded content of '${packageJson}': ${JSON.stringify(npmConfig)}`);
|
2018-01-11 18:13:33 +01:00
|
|
|
this.log.writeLine(`Loaded content of '${packageLockJson}'`);
|
2016-08-16 00:59:31 +02:00
|
|
|
}
|
2018-01-11 18:13:33 +01:00
|
|
|
if (npmConfig.devDependencies && npmLock.dependencies) {
|
2016-08-26 01:25:34 +02:00
|
|
|
for (const key in npmConfig.devDependencies) {
|
2018-02-09 19:36:49 +01:00
|
|
|
if (!hasProperty(npmLock.dependencies, key)) {
|
|
|
|
// if package in package.json but not package-lock.json, skip adding to cache so it is reinstalled on next use
|
|
|
|
continue;
|
|
|
|
}
|
2016-08-26 01:25:34 +02:00
|
|
|
// key is @types/<package name>
|
2016-08-27 01:37:31 +02:00
|
|
|
const packageName = getBaseFileName(key);
|
2016-08-15 20:48:28 +02:00
|
|
|
if (!packageName) {
|
|
|
|
continue;
|
|
|
|
}
|
2016-11-15 21:53:46 +01:00
|
|
|
const typingFile = typingToFileName(cacheLocation, packageName, this.installTypingHost, this.log);
|
2016-08-26 01:25:34 +02:00
|
|
|
if (!typingFile) {
|
2016-12-05 23:13:32 +01:00
|
|
|
this.missingTypingsSet.set(packageName, true);
|
2016-08-26 01:25:34 +02:00
|
|
|
continue;
|
|
|
|
}
|
2016-12-05 23:13:32 +01:00
|
|
|
const existingTypingFile = this.packageNameToTypingLocation.get(packageName);
|
2016-08-15 20:48:28 +02:00
|
|
|
if (existingTypingFile) {
|
2017-12-29 23:21:55 +01:00
|
|
|
if (existingTypingFile.typingLocation === typingFile) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2016-08-16 00:59:31 +02:00
|
|
|
if (this.log.isEnabled()) {
|
|
|
|
this.log.writeLine(`New typing for package ${packageName} from '${typingFile}' conflicts with existing typing file '${existingTypingFile}'`);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (this.log.isEnabled()) {
|
|
|
|
this.log.writeLine(`Adding entry into typings cache: '${packageName}' => '${typingFile}'`);
|
2016-08-15 20:48:28 +02:00
|
|
|
}
|
2018-01-11 18:13:33 +01:00
|
|
|
const info = getProperty(npmLock.dependencies, key);
|
|
|
|
const version = info && info.version;
|
|
|
|
const semver = Semver.parse(version);
|
2018-01-11 20:11:26 +01:00
|
|
|
const newTyping: JsTyping.CachedTyping = { typingLocation: typingFile, version: semver };
|
2017-11-21 01:43:02 +01:00
|
|
|
this.packageNameToTypingLocation.set(packageName, newTyping);
|
2016-08-15 20:48:28 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-08-16 00:59:31 +02:00
|
|
|
if (this.log.isEnabled()) {
|
|
|
|
this.log.writeLine(`Finished processing cache location '${cacheLocation}'`);
|
|
|
|
}
|
2018-01-11 20:11:26 +01:00
|
|
|
this.knownCachesSet.set(cacheLocation, true);
|
2016-08-15 20:48:28 +02:00
|
|
|
}
|
|
|
|
|
2017-12-04 22:36:01 +01:00
|
|
|
private filterTypings(typingsToInstall: ReadonlyArray<string>): ReadonlyArray<string> {
|
|
|
|
return typingsToInstall.filter(typing => {
|
|
|
|
if (this.missingTypingsSet.get(typing)) {
|
|
|
|
if (this.log.isEnabled()) this.log.writeLine(`'${typing}' is in missingTypingsSet - skipping...`);
|
|
|
|
return false;
|
2016-10-01 20:56:51 +02:00
|
|
|
}
|
2017-12-04 22:36:01 +01:00
|
|
|
const validationResult = JsTyping.validatePackageName(typing);
|
|
|
|
if (validationResult !== JsTyping.PackageNameValidationResult.Ok) {
|
2016-10-01 20:56:51 +02:00
|
|
|
// add typing name to missing set so we won't process it again
|
2016-12-05 23:13:32 +01:00
|
|
|
this.missingTypingsSet.set(typing, true);
|
2017-12-04 22:36:01 +01:00
|
|
|
if (this.log.isEnabled()) this.log.writeLine(JsTyping.renderPackageNameValidationFailure(validationResult, typing));
|
|
|
|
return false;
|
2016-10-01 20:56:51 +02:00
|
|
|
}
|
2017-12-04 22:36:01 +01:00
|
|
|
if (!this.typesRegistry.has(typing)) {
|
|
|
|
if (this.log.isEnabled()) this.log.writeLine(`Entry for package '${typing}' does not exist in local types registry - skipping...`);
|
|
|
|
return false;
|
|
|
|
}
|
2018-01-11 20:11:26 +01:00
|
|
|
if (this.packageNameToTypingLocation.get(typing) && JsTyping.isTypingUpToDate(this.packageNameToTypingLocation.get(typing), this.typesRegistry.get(typing))) {
|
2018-01-11 18:13:33 +01:00
|
|
|
if (this.log.isEnabled()) this.log.writeLine(`'${typing}' already has an up-to-date typing - skipping...`);
|
|
|
|
return false;
|
|
|
|
}
|
2017-12-04 22:36:01 +01:00
|
|
|
return true;
|
|
|
|
});
|
2016-10-01 20:56:51 +02:00
|
|
|
}
|
|
|
|
|
2016-11-07 22:36:08 +01:00
|
|
|
protected ensurePackageDirectoryExists(directory: string) {
|
|
|
|
const npmConfigPath = combinePaths(directory, "package.json");
|
2016-08-16 00:59:31 +02:00
|
|
|
if (this.log.isEnabled()) {
|
2016-11-07 22:36:08 +01:00
|
|
|
this.log.writeLine(`Npm config file: ${npmConfigPath}`);
|
2016-08-16 00:59:31 +02:00
|
|
|
}
|
2016-11-07 22:36:08 +01:00
|
|
|
if (!this.installTypingHost.fileExists(npmConfigPath)) {
|
2016-08-16 00:59:31 +02:00
|
|
|
if (this.log.isEnabled()) {
|
2016-11-07 22:36:08 +01:00
|
|
|
this.log.writeLine(`Npm config file: '${npmConfigPath}' is missing, creating new one...`);
|
2016-08-16 00:59:31 +02:00
|
|
|
}
|
2016-11-07 22:36:08 +01:00
|
|
|
this.ensureDirectoryExists(directory, this.installTypingHost);
|
2017-11-06 18:45:52 +01:00
|
|
|
this.installTypingHost.writeFile(npmConfigPath, '{ "private": true }');
|
2016-08-12 21:14:25 +02:00
|
|
|
}
|
2016-11-07 22:36:08 +01:00
|
|
|
}
|
2016-08-12 21:14:25 +02:00
|
|
|
|
2018-01-11 20:11:26 +01:00
|
|
|
private installTypings(req: DiscoverTypings, cachePath: string, currentlyCachedTypings: string[], typingsToInstall: string[]) {
|
2016-08-16 00:59:31 +02:00
|
|
|
if (this.log.isEnabled()) {
|
2016-11-07 22:36:08 +01:00
|
|
|
this.log.writeLine(`Installing typings ${JSON.stringify(typingsToInstall)}`);
|
2016-08-16 00:59:31 +02:00
|
|
|
}
|
2016-11-11 01:52:36 +01:00
|
|
|
const filteredTypings = this.filterTypings(typingsToInstall);
|
2017-01-30 16:49:17 +01:00
|
|
|
if (filteredTypings.length === 0) {
|
2016-08-16 00:59:31 +02:00
|
|
|
if (this.log.isEnabled()) {
|
2017-11-20 23:43:25 +01:00
|
|
|
this.log.writeLine(`All typings are known to be missing or invalid - no need to install more typings`);
|
2016-08-16 00:59:31 +02:00
|
|
|
}
|
2017-11-20 23:43:25 +01:00
|
|
|
this.sendResponse(this.createSetTypings(req, currentlyCachedTypings));
|
2016-11-07 22:36:08 +01:00
|
|
|
return;
|
2016-08-12 20:04:43 +02:00
|
|
|
}
|
2016-08-12 21:14:25 +02:00
|
|
|
|
2016-11-07 22:36:08 +01:00
|
|
|
this.ensurePackageDirectoryExists(cachePath);
|
|
|
|
|
|
|
|
const requestId = this.installRunCount;
|
|
|
|
this.installRunCount++;
|
|
|
|
|
2016-11-29 19:14:22 +01:00
|
|
|
// send progress event
|
|
|
|
this.sendResponse(<BeginInstallTypes>{
|
|
|
|
kind: EventBeginInstallTypes,
|
|
|
|
eventId: requestId,
|
|
|
|
typingsInstallerVersion: ts.version, // qualified explicitly to prevent occasional shadowing
|
|
|
|
projectName: req.projectName
|
|
|
|
});
|
|
|
|
|
2017-01-30 16:49:17 +01:00
|
|
|
const scopedTypings = filteredTypings.map(typingsName);
|
2016-11-07 22:36:08 +01:00
|
|
|
this.installTypingsAsync(requestId, scopedTypings, cachePath, ok => {
|
2016-11-29 19:14:22 +01:00
|
|
|
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) {
|
2016-12-05 23:13:32 +01:00
|
|
|
this.missingTypingsSet.set(typing, true);
|
2016-11-29 19:14:22 +01:00
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
2016-11-07 22:36:08 +01:00
|
|
|
|
2016-11-29 19:14:22 +01:00
|
|
|
// TODO: watch project directory
|
2016-11-11 01:52:36 +01:00
|
|
|
if (this.log.isEnabled()) {
|
2016-11-29 19:14:22 +01:00
|
|
|
this.log.writeLine(`Installed typings ${JSON.stringify(scopedTypings)}`);
|
2016-11-11 01:52:36 +01:00
|
|
|
}
|
2016-11-29 19:14:22 +01:00
|
|
|
const installedTypingFiles: string[] = [];
|
|
|
|
for (const packageName of filteredTypings) {
|
|
|
|
const typingFile = typingToFileName(cachePath, packageName, this.installTypingHost, this.log);
|
|
|
|
if (!typingFile) {
|
2016-12-05 23:13:32 +01:00
|
|
|
this.missingTypingsSet.set(packageName, true);
|
2016-11-29 19:14:22 +01:00
|
|
|
continue;
|
|
|
|
}
|
2017-11-28 05:29:10 +01:00
|
|
|
|
2018-01-19 22:13:51 +01:00
|
|
|
// packageName is guaranteed to exist in typesRegistry by filterTypings
|
|
|
|
const distTags = this.typesRegistry.get(packageName);
|
2018-01-19 23:10:06 +01:00
|
|
|
const newVersion = Semver.parse(distTags[`ts${ts.versionMajorMinor}`] || distTags[latestDistTag]);
|
2018-01-11 20:11:26 +01:00
|
|
|
const newTyping: JsTyping.CachedTyping = { typingLocation: typingFile, version: newVersion };
|
2017-11-28 05:29:10 +01:00
|
|
|
this.packageNameToTypingLocation.set(packageName, newTyping);
|
2016-11-29 19:14:22 +01:00
|
|
|
installedTypingFiles.push(typingFile);
|
2016-08-26 01:25:34 +02:00
|
|
|
}
|
2016-11-29 19:14:22 +01:00
|
|
|
if (this.log.isEnabled()) {
|
|
|
|
this.log.writeLine(`Installed typing files ${JSON.stringify(installedTypingFiles)}`);
|
2016-09-01 06:14:24 +02:00
|
|
|
}
|
2016-11-29 19:14:22 +01:00
|
|
|
|
|
|
|
this.sendResponse(this.createSetTypings(req, currentlyCachedTypings.concat(installedTypingFiles)));
|
2016-08-16 00:59:31 +02:00
|
|
|
}
|
2016-11-29 19:14:22 +01:00
|
|
|
finally {
|
2017-08-10 21:52:15 +02:00
|
|
|
const response: EndInstallTypes = {
|
2016-11-29 19:14:22 +01:00
|
|
|
kind: EventEndInstallTypes,
|
|
|
|
eventId: requestId,
|
|
|
|
projectName: req.projectName,
|
|
|
|
packagesToInstall: scopedTypings,
|
|
|
|
installSuccess: ok,
|
|
|
|
typingsInstallerVersion: ts.version // qualified explicitly to prevent occasional shadowing
|
2017-08-10 21:52:15 +02:00
|
|
|
};
|
|
|
|
this.sendResponse(response);
|
2016-08-15 20:48:28 +02:00
|
|
|
}
|
2016-08-12 21:14:25 +02:00
|
|
|
});
|
2016-08-12 20:04:43 +02:00
|
|
|
}
|
|
|
|
|
2016-08-13 08:04:17 +02:00
|
|
|
private ensureDirectoryExists(directory: string, host: InstallTypingHost): void {
|
|
|
|
const directoryName = getDirectoryPath(directory);
|
|
|
|
if (!host.directoryExists(directoryName)) {
|
|
|
|
this.ensureDirectoryExists(directoryName, host);
|
|
|
|
}
|
|
|
|
if (!host.directoryExists(directory)) {
|
|
|
|
host.createDirectory(directory);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-08-16 23:49:45 +02:00
|
|
|
private watchFiles(projectName: string, files: string[]) {
|
2016-08-16 23:21:09 +02:00
|
|
|
if (!files.length) {
|
|
|
|
return;
|
|
|
|
}
|
2016-08-17 22:01:48 +02:00
|
|
|
// shut down existing watchers
|
|
|
|
this.closeWatchers(projectName);
|
|
|
|
|
|
|
|
// handler should be invoked once for the entire set of files since it will trigger full rediscovery of typings
|
|
|
|
let isInvoked = false;
|
2016-08-16 23:21:09 +02:00
|
|
|
const watchers: FileWatcher[] = [];
|
|
|
|
for (const file of files) {
|
|
|
|
const w = this.installTypingHost.watchFile(file, f => {
|
|
|
|
if (this.log.isEnabled()) {
|
2016-08-17 22:01:48 +02:00
|
|
|
this.log.writeLine(`Got FS notification for ${f}, handler is already invoked '${isInvoked}'`);
|
2016-08-16 23:21:09 +02:00
|
|
|
}
|
2016-10-21 06:15:47 +02:00
|
|
|
if (!isInvoked) {
|
2017-07-07 16:26:58 +02:00
|
|
|
this.sendResponse({ projectName, kind: server.ActionInvalidate });
|
2016-10-21 06:15:47 +02:00
|
|
|
isInvoked = true;
|
|
|
|
}
|
2016-11-08 06:13:11 +01:00
|
|
|
}, /*pollingInterval*/ 2000);
|
2016-08-16 23:21:09 +02:00
|
|
|
watchers.push(w);
|
|
|
|
}
|
2016-12-05 23:13:32 +01:00
|
|
|
this.projectWatchers.set(projectName, watchers);
|
2016-08-12 20:04:43 +02:00
|
|
|
}
|
|
|
|
|
2016-08-16 23:21:09 +02:00
|
|
|
private createSetTypings(request: DiscoverTypings, typings: string[]): SetTypings {
|
2016-08-12 20:04:43 +02:00
|
|
|
return {
|
|
|
|
projectName: request.projectName,
|
2016-11-19 02:46:06 +01:00
|
|
|
typeAcquisition: request.typeAcquisition,
|
2016-08-12 20:04:43 +02:00
|
|
|
compilerOptions: request.compilerOptions,
|
2016-08-16 23:21:09 +02:00
|
|
|
typings,
|
2016-10-26 00:24:21 +02:00
|
|
|
unresolvedImports: request.unresolvedImports,
|
2016-11-10 23:28:34 +01:00
|
|
|
kind: ActionSet
|
2016-08-12 20:04:43 +02:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2017-10-18 00:04:09 +02:00
|
|
|
private installTypingsAsync(requestId: number, packageNames: string[], cwd: string, onRequestCompleted: RequestCompletedAction): void {
|
|
|
|
this.pendingRunRequests.unshift({ requestId, packageNames, cwd, onRequestCompleted });
|
2016-09-20 23:14:51 +02:00
|
|
|
this.executeWithThrottling();
|
|
|
|
}
|
|
|
|
|
|
|
|
private executeWithThrottling() {
|
|
|
|
while (this.inFlightRequestCount < this.throttleLimit && this.pendingRunRequests.length) {
|
|
|
|
this.inFlightRequestCount++;
|
|
|
|
const request = this.pendingRunRequests.pop();
|
2017-10-18 00:04:09 +02:00
|
|
|
this.installWorker(request.requestId, request.packageNames, request.cwd, ok => {
|
2016-09-20 23:14:51 +02:00
|
|
|
this.inFlightRequestCount--;
|
2016-10-13 19:19:18 +02:00
|
|
|
request.onRequestCompleted(ok);
|
2016-09-20 23:14:51 +02:00
|
|
|
this.executeWithThrottling();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-10-18 00:04:09 +02:00
|
|
|
protected abstract installWorker(requestId: number, packageNames: string[], cwd: string, onRequestCompleted: RequestCompletedAction): void;
|
2016-11-29 19:14:22 +01:00
|
|
|
protected abstract sendResponse(response: SetTypings | InvalidateCachedTypings | BeginInstallTypes | EndInstallTypes): void;
|
2016-08-12 20:04:43 +02:00
|
|
|
}
|
2017-01-30 16:49:17 +01:00
|
|
|
|
|
|
|
/* @internal */
|
|
|
|
export function typingsName(packageName: string): string {
|
|
|
|
return `@types/${packageName}@ts${versionMajorMinor}`;
|
|
|
|
}
|
2018-01-19 23:10:06 +01:00
|
|
|
|
|
|
|
const latestDistTag = "latest";
|
2018-02-16 23:00:10 +01:00
|
|
|
}
|