Skip to content
This repository was archived by the owner on Jun 11, 2020. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 32 additions & 38 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 0 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
"npm": "^5.4.2",
"npm-registry-client": "^8.1.0",
"oboe": "^2.1.3",
"semver": "^5.3.0",
"source-map-support": "^0.4.0",
"tar": "^2.2.1",
"typescript": "next",
Expand All @@ -31,7 +30,6 @@
"@types/node": "^8.0.15",
"@types/node-fetch": "^1.6.7",
"@types/oboe": "^2.0.28",
"@types/semver": "^5.3.30",
"@types/source-map-support": "^0.4.0",
"@types/tar": "^1.0.27",
"@types/yargs": "^8.0.1",
Expand Down
106 changes: 85 additions & 21 deletions src/check-parse-results.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import * as semver from "semver";
import { Options } from "./lib/common";
import { fetchNpmInfo } from "./lib/npm-client";
import { fetchNpmInfo, NpmInfoVersion, NpmInfoVersions } from "./lib/npm-client";
import { AllPackages, AnyPackage, TypingsData } from "./lib/packages";
import { Semver } from "./lib/versions";
import { Logger, logger, writeLog } from "./util/logging";
import { best, done, multiMapAdd, nAtATime } from "./util/util";
import { best, done, mapDefined, multiMapAdd, nAtATime } from "./util/util";

if (!module.parent) {
done(main(true, Options.defaults));
Expand All @@ -21,11 +21,23 @@ export default async function main(includeNpmChecks: boolean, options: Options):
checkForDuplicates(packages, pkg => pkg.libraryName, "Library Name", log);
checkForDuplicates(packages, pkg => pkg.projectName, "Project Name", log);

const dependedOn = new Set<string>();
for (const pkg of packages) {
if (pkg instanceof TypingsData) {
for (const dep of pkg.dependencies) {
dependedOn.add(dep.name);
}
for (const dep of pkg.testDependencies) {
dependedOn.add(dep);
}
}
}

if (includeNpmChecks) {
await nAtATime(10, allPackages.allTypings(), pkg => checkNpm(pkg, log), {
await nAtATime(10, allPackages.allTypings(), pkg => checkNpm(pkg, log, dependedOn), {
name: "Checking for typed packages...",
flavor: pkg => pkg.desc,
options
options,
});
}

Expand Down Expand Up @@ -88,30 +100,82 @@ function checkPathMappings(allPackages: AllPackages): void {
}
}

async function checkNpm(pkg: TypingsData, log: Logger): Promise<void> {
const asOfVersion = await firstPackageVersionWithTypes(pkg.name);
if (asOfVersion) {
const ourVersion = `${pkg.major}.${pkg.minor}`;
log(`Typings already defined for ${pkg.name} (${pkg.libraryName}) as of ${asOfVersion} (our version: ${ourVersion})`);
async function checkNpm(
{ major, minor, name, libraryName, projectName, contributors }: TypingsData,
log: Logger,
dependedOn: ReadonlySet<string>,
): Promise<void> {
if (notNeededExceptions.has(name)) {
return;
}
}

export async function packageHasTypes(packageName: string): Promise<boolean> {
return (await firstPackageVersionWithTypes(packageName)) !== undefined;
const info = await fetchNpmInfo(name);
const versions = getRegularVersions(info.versions);
const firstTypedVersion = best(mapDefined(versions, ({ hasTypes, version }) => hasTypes ? version : undefined), (a, b) => b.greaterThan(a));
// A package might have added types but removed them later, so check the latest version too
if (firstTypedVersion === undefined || !best(versions, (a, b) => a.version.greaterThan(b.version))!.hasTypes) {
return;
}

const ourVersion = `${major}.${minor}`;

log("");
log(`Typings already defined for ${name} (${libraryName}) as of ${firstTypedVersion.versionString} (our version: ${ourVersion})`);
const contributorUrls = contributors.map(c => {
const gh = "https://github.com/";
return c.url.startsWith(gh) ? `@${c.url.slice(gh.length)}` : `${c.name} (${c.url})`;
}).join(", ");
log(" To fix this:");
log(` git checkout -b not-needed-${name}`);
log(` yarn not-needed ${name} ${firstTypedVersion.versionString} ${projectName}${libraryName !== name ? ` ${JSON.stringify(libraryName)}` : ""}`);
log(` git add --all && git commit -m "${name}: Provides its own types" && git push -u origin not-needed-${name}`);
log(` And comment PR: This will deprecate \`@types/${name}\` in favor of just \`${name}\`. CC ${contributorUrls}`);
if (new Semver(major, minor, 0, /*isPrerelease*/ false).greaterThan(firstTypedVersion)) {
log(" WARNING: our version is greater!");
}
if (dependedOn.has(name)) {
log(" WARNING: other packages depend on this!");
}
}

async function firstPackageVersionWithTypes(packageName: string): Promise<string | undefined> {
export async function packageHasTypes(packageName: string): Promise<boolean> {
const info = await fetchNpmInfo(packageName);
// Info may be empty if the package is not on NPM
return info.versions && firstVersionWithTypes(info.versions);
return hasTypes(info.versions[info.version]);
}

function firstVersionWithTypes(versions: { [version: string]: {} }): string | undefined {
const versionsWithTypings = Object.entries(versions).filter(([_version, info]) => hasTypes(info)).map(([version]) => version);
// Type annotation needed because of https://github.com/Microsoft/TypeScript/issues/12915
return best<string>(versionsWithTypings, semver.lt);
function getRegularVersions(versions: NpmInfoVersions | undefined): ReadonlyArray<{ readonly version: Semver; readonly hasTypes: boolean; }> {
// Versions can be undefined if an NPM package doesn't exist.
return versions === undefined ? [] : mapDefined(Object.entries(versions), ([versionString, info]) => {
const version = Semver.tryParse(versionString, /*isPrerelease*/ false);
return version === undefined ? undefined : { version, hasTypes: hasTypes(info) };
});
}

function hasTypes(info: any): boolean {
function hasTypes(info: NpmInfoVersion): boolean {
return "types" in info || "typings" in info;
}

const notNeededExceptions: ReadonlySet<string> = new Set([
// Declares to bundle types, but they're also in the `.npmignore` (https://github.com/nkovacic/angular-touchspin/issues/21)
"angular-touchspin",
// "typings" points to the wrong file (https://github.com/Microsoft/Bing-Maps-V8-TypeScript-Definitions/issues/31)
"bingmaps",
// Types are bundled, but not officially released (https://github.com/DefinitelyTyped/DefinitelyTyped/pull/22313#issuecomment-353225893)
"dwt",
// Waiting on some typing errors to be fixed (https://github.com/julien-c/epub/issues/30)
"epub",
// Typings file is not in package.json "files" list (https://github.com/silentmatt/expr-eval/issues/127)
"expr-eval",
// NPM package "express-serve-static-core" isn't a real package -- express-serve-static-core exists only for the purpose of types
"express-serve-static-core",
// Has "typings": "index.d.ts" but does not actually bundle typings. https://github.com/kolodny/immutability-helper/issues/79
"immutability-helper",
// Has `"typings": "compiled/typings/node-mysql-wrapper/node-mysql-wrapper.d.ts",`, but `compiled/typings` doesn't exist.
// Package hasn't updated in 2 years and author seems to have deleted their account, so no chance of being fixed.
"node-mysql-wrapper",
// raspi packages bundle types, but can only be installed on a Raspberry Pi, so they are duplicated to DefinitelyTyped.
// See https://github.com/DefinitelyTyped/DefinitelyTyped/pull/21618
"raspi", "raspi-board", "raspi-peripheral",
// Declare "typings" but don't actually have them yet (https://github.com/stampit-org/stampit/issues/245)
"stampit",
]);
9 changes: 5 additions & 4 deletions src/lib/npm-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,10 +92,11 @@ export interface NpmInfo {
readonly versions: NpmInfoVersions;
}
export interface NpmInfoVersions {
readonly [version: string]: {
readonly typesPublisherContentHash: string;
readonly deprecated?: string;
};
readonly [version: string]: NpmInfoVersion;
}
export interface NpmInfoVersion {
readonly typesPublisherContentHash: string;
readonly deprecated?: string;
}
export async function fetchNpmInfo(escapedPackageName: string): Promise<NpmInfo> {
const uri = npmRegistry + escapedPackageName;
Expand Down
Loading