Update installed types if older than those listed in the registry

This commit is contained in:
uniqueiniquity
2018-01-11 09:13:33 -08:00
parent 23324345e2
commit e72ea6f7b1
9 changed files with 137 additions and 30 deletions

View File

@@ -252,7 +252,7 @@ namespace ts.server {
private requestMap = createMap<QueuedOperation>(); // Maps operation ID to newest requestQueue entry with that ID
/** We will lazily request the types registry on the first call to `isKnownTypesPackageName` and store it in `typesRegistryCache`. */
private requestedRegistry: boolean;
private typesRegistryCache: Map<void> | undefined;
private typesRegistryCache: Map<MapLike<string>> | undefined;
// This number is essentially arbitrary. Processing more than one typings request
// at a time makes sense, but having too many in the pipe results in a hang

View File

@@ -77,7 +77,7 @@ declare namespace ts.server {
/* @internal */
export interface TypesRegistryResponse extends TypingInstallerResponse {
readonly kind: EventTypesRegistry;
readonly typesRegistry: MapLike<void>;
readonly typesRegistry: MapLike<MapLike<string>>;
}
export interface PackageInstalledResponse extends ProjectResponse {

View File

@@ -41,15 +41,15 @@ namespace ts.server.typingsInstaller {
}
interface TypesRegistryFile {
entries: MapLike<void>;
entries: MapLike<MapLike<string>>;
}
function loadTypesRegistryFile(typesRegistryFilePath: string, host: InstallTypingHost, log: Log): Map<void> {
function loadTypesRegistryFile(typesRegistryFilePath: string, host: InstallTypingHost, log: Log): Map<MapLike<string>> {
if (!host.fileExists(typesRegistryFilePath)) {
if (log.isEnabled()) {
log.writeLine(`Types registry file '${typesRegistryFilePath}' does not exist`);
}
return createMap<void>();
return createMap<MapLike<string>>();
}
try {
const content = <TypesRegistryFile>JSON.parse(host.readFile(typesRegistryFilePath));
@@ -59,7 +59,7 @@ namespace ts.server.typingsInstaller {
if (log.isEnabled()) {
log.writeLine(`Error when loading types registry file '${typesRegistryFilePath}': ${(<Error>e).message}, ${(<Error>e).stack}`);
}
return createMap<void>();
return createMap<MapLike<string>>();
}
}
@@ -77,7 +77,7 @@ namespace ts.server.typingsInstaller {
export class NodeTypingsInstaller extends TypingsInstaller {
private readonly nodeExecSync: ExecSync;
private readonly npmPath: string;
readonly typesRegistry: Map<void>;
readonly typesRegistry: Map<MapLike<string>>;
private delayedInitializationError: InitializationFailedResponse | undefined;
@@ -141,7 +141,7 @@ namespace ts.server.typingsInstaller {
this.closeProject(req);
break;
case "typesRegistry": {
const typesRegistry: { [key: string]: void } = {};
const typesRegistry: { [key: string]: MapLike<string> } = {};
this.typesRegistry.forEach((value, key) => {
typesRegistry[key] = value;
});

View File

@@ -1,6 +1,7 @@
/// <reference path="../../compiler/core.ts" />
/// <reference path="../../compiler/moduleNameResolver.ts" />
/// <reference path="../../services/jsTyping.ts"/>
/// <reference path="../../services/semver.ts"/>
/// <reference path="../types.ts"/>
/// <reference path="../shared.ts"/>
@@ -9,6 +10,10 @@ namespace ts.server.typingsInstaller {
devDependencies: MapLike<any>;
}
interface NpmLock {
dependencies: { [packageName: string]: { version: string } };
}
export interface Log {
isEnabled(): boolean;
writeLine(text: string): void;
@@ -104,7 +109,7 @@ namespace ts.server.typingsInstaller {
private installRunCount = 1;
private inFlightRequestCount = 0;
abstract readonly typesRegistry: Map<void>;
abstract readonly typesRegistry: Map<MapLike<string>>;
constructor(
protected readonly installTypingHost: InstallTypingHost,
@@ -217,15 +222,18 @@ namespace ts.server.typingsInstaller {
}
const typeDeclarationTimestamps = loadTypeDeclarationTimestampFile(timestampsFilePath || combinePaths(cacheLocation, timestampsFileName), this.installTypingHost, this.log);
const packageJson = combinePaths(cacheLocation, "package.json");
const packageLockJson = combinePaths(cacheLocation, "package-lock.json");
if (this.log.isEnabled()) {
this.log.writeLine(`Trying to find '${packageJson}'...`);
}
if (this.installTypingHost.fileExists(packageJson)) {
if (this.installTypingHost.fileExists(packageJson) && this.installTypingHost.fileExists(packageLockJson)) {
const npmConfig = <NpmConfig>JSON.parse(this.installTypingHost.readFile(packageJson));
const npmLock = <NpmLock>JSON.parse(this.installTypingHost.readFile(packageLockJson));
if (this.log.isEnabled()) {
this.log.writeLine(`Loaded content of '${packageJson}': ${JSON.stringify(npmConfig)}`);
this.log.writeLine(`Loaded content of '${packageLockJson}'`);
}
if (npmConfig.devDependencies) {
if (npmConfig.devDependencies && npmLock.dependencies) {
for (const key in npmConfig.devDependencies) {
// key is @types/<package name>
const packageName = getBaseFileName(key);
@@ -259,8 +267,11 @@ namespace ts.server.typingsInstaller {
this.log.writeLine(`Adding entry into timestamp cache: '${key}' => '${timestamp}'`);
}
}
const info = getProperty(npmLock.dependencies, key);
const version = info && info.version;
const semver = Semver.parse(version);
// timestamp guaranteed to not be undefined by above check
const newTyping: JsTyping.CachedTyping = { typingLocation: typingFile, timestamp: getProperty(typeDeclarationTimestamps, key) };
const newTyping: JsTyping.CachedTyping = { typingLocation: typingFile, timestamp: getProperty(typeDeclarationTimestamps, key), version: semver };
this.packageNameToTypingLocation.set(packageName, newTyping);
}
}
@@ -277,10 +288,6 @@ namespace ts.server.typingsInstaller {
if (this.log.isEnabled()) this.log.writeLine(`'${typing}' is in missingTypingsSet - skipping...`);
return false;
}
if (this.packageNameToTypingLocation.get(typing) && !JsTyping.isTypingExpired(this.packageNameToTypingLocation.get(typing))) {
if (this.log.isEnabled()) this.log.writeLine(`'${typing}' already has a typing - skipping...`);
return false;
}
const validationResult = JsTyping.validatePackageName(typing);
if (validationResult !== JsTyping.PackageNameValidationResult.Ok) {
// add typing name to missing set so we won't process it again
@@ -292,8 +299,17 @@ namespace ts.server.typingsInstaller {
if (this.log.isEnabled()) this.log.writeLine(`Entry for package '${typing}' does not exist in local types registry - skipping...`);
return false;
}
if (this.packageNameToTypingLocation.get(typing) && isTypingUpToDate(this.packageNameToTypingLocation.get(typing), this.typesRegistry.get(typing))) {
if (this.log.isEnabled()) this.log.writeLine(`'${typing}' already has an up-to-date typing - skipping...`);
return false;
}
return true;
});
function isTypingUpToDate(cachedTyping: JsTyping.CachedTyping, availableTypingVersions: MapLike<string>) {
const availableVersion = Semver.parse(getProperty(availableTypingVersions, `ts${ts.version}`));
return !availableVersion.greaterThan(cachedTyping.version);
}
}
protected ensurePackageDirectoryExists(directory: string) {
@@ -364,7 +380,8 @@ namespace ts.server.typingsInstaller {
}
const newTimestamp = Date.now();
const newTyping: JsTyping.CachedTyping = { typingLocation: typingFile, timestamp: newTimestamp };
const newVersion = Semver.parse(this.typesRegistry.get(packageName)[`ts${ts.versionMajorMinor}`]);
const newTyping: JsTyping.CachedTyping = { typingLocation: typingFile, timestamp: newTimestamp, version: newVersion };
this.packageNameToTypingLocation.set(packageName, newTyping);
typeDeclarationTimestamps[typesPackageName(packageName)] = newTimestamp;
installedTypingFiles.push(typingFile);