mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-17 01:55:50 -06:00
Update installed types if older than those listed in the registry
This commit is contained in:
parent
23324345e2
commit
e72ea6f7b1
@ -63,7 +63,7 @@ namespace ts.projectSystem {
|
||||
readonly globalTypingsCacheLocation: string,
|
||||
throttleLimit: number,
|
||||
installTypingHost: server.ServerHost,
|
||||
readonly typesRegistry = createMap<void>(),
|
||||
readonly typesRegistry = createMap<MapLike<string>>(),
|
||||
log?: TI.Log) {
|
||||
super(installTypingHost, globalTypingsCacheLocation, safeList.path, customTypesMap.path, throttleLimit, log);
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
/// <reference path="../harness.ts" />
|
||||
/// <reference path="./tsserverProjectSystem.ts" />
|
||||
/// <reference path="../../server/typingsInstaller/typingsInstaller.ts" />
|
||||
/// <reference path="../../services/semver.ts" />
|
||||
|
||||
namespace ts.projectSystem {
|
||||
import TI = server.typingsInstaller;
|
||||
@ -10,13 +11,24 @@ namespace ts.projectSystem {
|
||||
interface InstallerParams {
|
||||
globalTypingsCacheLocation?: string;
|
||||
throttleLimit?: number;
|
||||
typesRegistry?: Map<void>;
|
||||
typesRegistry?: Map<MapLike<string>>;
|
||||
}
|
||||
|
||||
function createTypesRegistry(...list: string[]): Map<void> {
|
||||
const map = createMap<void>();
|
||||
function createTypesRegistry(...list: string[]): Map<MapLike<string>> {
|
||||
const versionMap = {
|
||||
"latest": "1.3.0",
|
||||
"ts2.0": "1.0.0",
|
||||
"ts2.1": "1.0.0",
|
||||
"ts2.2": "1.2.0",
|
||||
"ts2.3": "1.3.0",
|
||||
"ts2.4": "1.3.0",
|
||||
"ts2.5": "1.3.0",
|
||||
"ts2.6": "1.3.0",
|
||||
"ts2.7": "1.3.0"
|
||||
};
|
||||
const map = createMap<MapLike<string>>();
|
||||
for (const l of list) {
|
||||
map.set(l, undefined);
|
||||
map.set(l, versionMap);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
@ -51,7 +63,7 @@ namespace ts.projectSystem {
|
||||
const logs: string[] = [];
|
||||
return {
|
||||
log(message) {
|
||||
logs.push(message);
|
||||
logs.push(message);
|
||||
},
|
||||
finish() {
|
||||
return logs;
|
||||
@ -1149,7 +1161,17 @@ namespace ts.projectSystem {
|
||||
"types-registry": "^0.1.317"
|
||||
},
|
||||
devDependencies: {
|
||||
"@types/jquery": "^3.2.16"
|
||||
"@types/jquery": "^1.3.0"
|
||||
}
|
||||
})
|
||||
};
|
||||
const cacheLockConfig = {
|
||||
path: "/a/data/package-lock.json",
|
||||
content: JSON.stringify({
|
||||
dependencies: {
|
||||
"@types/jquery": {
|
||||
version: "1.3.0"
|
||||
}
|
||||
}
|
||||
})
|
||||
};
|
||||
@ -1157,7 +1179,7 @@ namespace ts.projectSystem {
|
||||
path: "/a/data/node_modules/@types/jquery/index.d.ts",
|
||||
content: "declare const $: { x: number }"
|
||||
};
|
||||
const host = createServerHost([file1, packageJson, timestamps, cacheConfig, jquery]);
|
||||
const host = createServerHost([file1, packageJson, timestamps, cacheConfig, cacheLockConfig, jquery]);
|
||||
const installer = new (class extends Installer {
|
||||
constructor() {
|
||||
super(host, { typesRegistry: createTypesRegistry("jquery") });
|
||||
@ -1299,7 +1321,7 @@ namespace ts.projectSystem {
|
||||
content: ""
|
||||
};
|
||||
const host = createServerHost([f, node]);
|
||||
const cache = createMapFromTemplate<JsTyping.CachedTyping>({ node: { typingLocation: node.path, timestamp: Date.now() } });
|
||||
const cache = createMapFromTemplate<JsTyping.CachedTyping>({ node: { typingLocation: node.path, timestamp: Date.now(), version: new Semver(1, 0, 0, /*isPrerelease*/ false) } });
|
||||
const logger = trackingLogger();
|
||||
const result = JsTyping.discoverTypings(host, logger.log, [f.path], getDirectoryPath(<Path>f.path), emptySafeList, cache, { enable: true }, ["fs", "bar"]);
|
||||
assert.deepEqual(logger.finish(), [
|
||||
@ -1359,8 +1381,8 @@ namespace ts.projectSystem {
|
||||
};
|
||||
const host = createServerHost([app]);
|
||||
const cache = createMapFromTemplate<JsTyping.CachedTyping>({
|
||||
node: { typingLocation: node.path, timestamp: Date.now() },
|
||||
commander: { typingLocation: commander.path, timestamp: date.getTime() }
|
||||
node: { typingLocation: node.path, timestamp: Date.now(), version: new Semver(1, 0, 0, /*isPrerelease*/ false) },
|
||||
commander: { typingLocation: commander.path, timestamp: date.getTime(), version: new Semver(1, 0, 0, /*isPrerelease*/ false) }
|
||||
});
|
||||
const logger = trackingLogger();
|
||||
const result = JsTyping.discoverTypings(host, logger.log, [app.path], getDirectoryPath(<Path>app.path), emptySafeList, cache, { enable: true }, ["http", "commander"]);
|
||||
@ -1433,12 +1455,22 @@ namespace ts.projectSystem {
|
||||
path: "/a/package.json",
|
||||
content: JSON.stringify({ dependencies: { commander: "1.0.0" } })
|
||||
};
|
||||
const packageLockFile = {
|
||||
path: "/a/cache/package-lock.json",
|
||||
content: JSON.stringify({
|
||||
dependencies: {
|
||||
"@types/commander": {
|
||||
version: "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, packageFile]);
|
||||
const host = createServerHost([f1, packageFile, packageLockFile]);
|
||||
let beginEvent: server.BeginInstallTypes;
|
||||
let endEvent: server.EndInstallTypes;
|
||||
const installer = new (class extends Installer {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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;
|
||||
});
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
/// <reference path='../compiler/types.ts' />
|
||||
/// <reference path='../compiler/core.ts' />
|
||||
/// <reference path='../compiler/commandLineParser.ts' />
|
||||
/// <reference path='../services/semver.ts' />
|
||||
|
||||
/* @internal */
|
||||
namespace ts.JsTyping {
|
||||
@ -29,6 +30,7 @@ namespace ts.JsTyping {
|
||||
export interface CachedTyping {
|
||||
typingLocation: string;
|
||||
timestamp: number;
|
||||
version: Semver;
|
||||
}
|
||||
|
||||
export function isTypingExpired(typing: JsTyping.CachedTyping | undefined) {
|
||||
@ -70,7 +72,7 @@ namespace ts.JsTyping {
|
||||
* @param fileNames are the file names that belong to the same project
|
||||
* @param projectRootPath is the path to the project root directory
|
||||
* @param safeListPath is the path used to retrieve the safe list
|
||||
* @param packageNameToTypingLocation is the map of package names to their cached typing locations and time of caching
|
||||
* @param packageNameToTypingLocation is the map of package names to their cached typing locations and time of caching and versions
|
||||
* @param typeAcquisition is used to customize the typing acquisition process
|
||||
* @param compilerOptions are used as a source for typing inference
|
||||
*/
|
||||
|
||||
55
src/services/semver.ts
Normal file
55
src/services/semver.ts
Normal file
@ -0,0 +1,55 @@
|
||||
/* @internal */
|
||||
namespace ts {
|
||||
function intOfString(str: string): number {
|
||||
const n = parseInt(str, 10);
|
||||
if (isNaN(n)) {
|
||||
throw new Error(`Error in parseInt(${JSON.stringify(str)})`);
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
export class Semver {
|
||||
static parse(semver: string): Semver {
|
||||
const isPrerelease = /^(.*)-next.\d+/.test(semver);
|
||||
const result = Semver.tryParse(semver, isPrerelease);
|
||||
if (!result) {
|
||||
throw new Error(`Unexpected semver: ${semver} (isPrerelease: ${isPrerelease})`);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static fromRaw({ major, minor, patch, isPrerelease }: Semver): Semver {
|
||||
return new Semver(major, minor, patch, isPrerelease);
|
||||
}
|
||||
|
||||
// This must parse the output of `versionString`.
|
||||
static tryParse(semver: string, isPrerelease: boolean): Semver | undefined {
|
||||
// Per the semver spec <http://semver.org/#spec-item-2>:
|
||||
// "A normal version number MUST take the form X.Y.Z where X, Y, and Z are non-negative integers, and MUST NOT contain leading zeroes."
|
||||
const rgx = isPrerelease ? /^(\d+)\.(\d+)\.0-next.(\d+)$/ : /^(\d+)\.(\d+)\.(\d+)$/;
|
||||
const match = rgx.exec(semver);
|
||||
return match ? new Semver(intOfString(match[1]), intOfString(match[2]), intOfString(match[3]), isPrerelease) : undefined;
|
||||
}
|
||||
|
||||
constructor(
|
||||
readonly major: number, readonly minor: number, readonly patch: number,
|
||||
/**
|
||||
* If true, this is `major.minor.0-next.patch`.
|
||||
* If false, this is `major.minor.patch`.
|
||||
*/
|
||||
readonly isPrerelease: boolean) { }
|
||||
|
||||
get versionString(): string {
|
||||
return this.isPrerelease ? `${this.major}.${this.minor}.0-next.${this.patch}` : `${this.major}.${this.minor}.${this.patch}`;
|
||||
}
|
||||
|
||||
equals(sem: Semver): boolean {
|
||||
return this.major === sem.major && this.minor === sem.minor && this.patch === sem.patch && this.isPrerelease === sem.isPrerelease;
|
||||
}
|
||||
|
||||
greaterThan(sem: Semver): boolean {
|
||||
return this.major > sem.major || this.major === sem.major
|
||||
&& (this.minor > sem.minor || this.minor === sem.minor && this.patch > sem.patch);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -61,6 +61,7 @@
|
||||
"services.ts",
|
||||
"transform.ts",
|
||||
"transpile.ts",
|
||||
"semver.ts",
|
||||
"shims.ts",
|
||||
"signatureHelp.ts",
|
||||
"symbolDisplay.ts",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user