Support multiple minor versions in parallel (#723)

* [test] Make DT mock mutable

* [definition-parser] Add tests for current behaviour

* [definition-parser] Support minor version directories

* [package] Update ts-jest

* [test] Fix failure reporting by not symbolicating errors at runtime.

* [README] Prefer npm scripts because those also enable runtime callstack symbolication

* [definition-parser] Cleanup

* [package] Remove dependency on semver pkg again

* [packages] Cleanup

* [packages] Unify and update docs on definitions data

* [definition-parser] Record old minor version path mappings

* [packages] Change DependencyVersion to consider minor versions

* [packages] Take minor versions into consideration

* [tslint] Make green

* [README] Update example snippet from definitions.json

* [definition-parser] Cleanup TODOs

* [definition-parser] Get rid of unneeded data

* Cleanup more comments

* Doc fixes.

Co-Authored-By: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com>

* [versions] Simplify code

* More doc fixes

* [definition-parser] Improve error output

Co-authored-by: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com>
This commit is contained in:
Eloy Durán
2020-02-14 02:13:17 +01:00
committed by Andrew Branch
parent 264713bf09
commit 67b3b8a78d
22 changed files with 777 additions and 268 deletions

View File

@@ -75,7 +75,7 @@ of time if the filesystem is very slow.
You can manually run this step locally with `npm run get-definitely-typed`.
Pass `--dry` to download the DefinitelyTyped copy and unzip it into memory.
> `node bin/parse-definitions.js`
> `npm run parse`
This generates the data file `data/definitions.json`.
All future steps depend on this file.
@@ -86,60 +86,62 @@ One can also pass `--single=package_name` to test this on a single package.
This file is a key/value mapping used by other steps in the process.
### Example entry
```js
"jquery": {
"authors": "Boris Yankov <https://github.com/borisyankov/>",
"definitionFilename": "jquery.d.ts",
"libraryDependencies": [],
"moduleDependencies": [],
"libraryMajorVersion": "1",
"libraryMinorVersion": "10",
"libraryName": "jQuery 1.10.x / 2.0.x",
"typingsPackageName": "jquery",
"projectName": "http://jquery.com/",
"sourceRepoURL": "https://www.github.com/DefinitelyTyped/DefinitelyTyped",
"kind": "Mixed",
"globals": [
"jQuery",
"$"
],
"declaredModules": [
"jquery"
],
"root": "C:\\github\\DefinitelyTyped\\jquery",
"files": [
"jquery.d.ts"
],
"contentHash": "5cfce9ba1a777bf2eecb20d0830f4f4bcd5eee2e1fd9936ca6c2f2201a44b618"
```json
{
"jquery": {
"3.3": {
"libraryName": "jquery",
"typingsPackageName": "jquery",
"projectName": "https://jquery.com",
"contributors": [
{
"name": "Boris Yankov",
"url": "https://github.com/borisyankov",
"githubUsername": "borisyankov"
}
],
"libraryMajorVersion": 3,
"libraryMinorVersion": 3,
"minTsVersion": "2.3",
"typesVersions": [],
"files": [
"JQuery.d.ts",
"JQueryStatic.d.ts",
"dist/jquery.slim.d.ts",
"index.d.ts",
"legacy.d.ts",
"misc.d.ts"
],
"license": "MIT",
"dependencies": [
{
"name": "sizzle",
"version": "*"
}
],
"testDependencies": [],
"pathMappings": [],
"packageJsonDependencies": [],
"contentHash": "6f3ac74aa9f284b3450b4dcbcabc842bfc2a70fa2d92e745851044d2bb78e94b",
"globals": [
"$",
"Symbol",
"jQuery"
],
"declaredModules": [
"jquery",
"jquery/dist/jquery.slim"
]
}
}
}
```
### Fields in `data/definitions.json`
* `"jquery"` (i.e. the property name): The name of the *folder* from the source repo
* `authors`: Author data parsed from a header comment in the entry point .d.ts file
* `definitionFilename`: The filename of the entry point .d.ts file. This file must be either `index.d.ts`, `folderName.d.ts` (where `folderName` is the folder name), or the only .d.ts file in the folder
* `libraryDependencies`: Which other definitions this file depends on. These will refer to *package names*, not *folder names*
* `libraryMajorVersion` / `libraryMinorVersion`: Version data parsed from a header comment in the entry point .d.ts. These values will be `0` if the entry point .d.ts file did not specify a version
* `libraryName`: Library name parsed from a header comment in the entry point .d.ts file
* `typingsPackageName`: The name on NPM that the type package will be published under
* `projectName`: Project name or URL information parsed from a header comment in the entry point .d.ts file
* `sourceRepoURL`: The URL to the originating type definition repo. Currently hardcoded to DefinitelyType's URL
* `kind`: One of the following strings based on the declarations in the folder:
* `Unknown`: The type of declaration could not be detected
* `MultipleModules`: Multiple ambient module declarations (`declare module "modName" {`) were found
* `Mixed`: At least one global declaration and exactly one ambient module declaration
* `DeclareModule`: Exactly one ambient module declaration and zero global declarations
* `Global`: Only global declarations. **Preferred**
* `ProperModule`: Only top-level `import` and `export` declarations. **Preferred**
* `ModuleAugmentation`: An ambient module declaration and at top-level `import` or `export` declaration. **Preferred**
* `UMD`: Only top-level `import` and `export` declarations, as well as a UMD declaration. **Preferred**
* `OldUMD`: Exactly one namespace declaration and exactly one ambient module declaration
* `globals`: A list of *values* declared in the global namespace. Note that this does not include types declared in the global namespace
* `declaredModules`: A list of modules declared. If `kind` is `ProperModule`, this list will explicitly list the containing folder name
* `root`: A full path to the declaration folder
* `files`: A list of the .d.ts files in the declaration folder
* `contentHash`: A hash of the names and contents of the `files` list, used for versioning
A key of the root object represents the name of the *folder* of a definition package, as it exists in the source repo. Its corresponding value holds a
an object that represents the versions of the package for which definitions are provided in parallel. Each version entry holds data about the package;
refer to [the `TypingsDataRaw` interface declaration](./src/lib/packages.ts) for details on this data.
## Contents of `logs/parser-log-summary.md`
@@ -179,7 +181,7 @@ This warning might not be appropriate; consider logging an issue.
# Check for conflicts
> `node bin/check-parse-results.js`
> `npm run check`
This is an optional script that checks for multiple declaration packages with the same library name or same project name.
@@ -209,7 +211,7 @@ This argument may be needed during development, but should not be used during ro
# Create a search index
> `node bin/create-search-index.js`
> `npm run index`
This script creates `data/search-index-min.json`, which (in the upload step) will be uploaded to Azure and used by [TypeSearch](https://github.com/microsoft/typesearch).
This step is not necessary for other steps in the process.
@@ -251,21 +253,21 @@ Empty arrays may be elided in future versions of the minified files.
# Generate packages on disk
> `node bin/generate-packages.js`
> `npm run generate`
This step writes all type packages to disk.
The output folder is specified in `settings.json` (see section "Settings").
You can also output a single package with e.g. `node bin/generate-packages.js --single abs`.
You can also output a single package with e.g. `npm run generate -- --single abs`.
(This will still require parsing every package first, as we may need information about referenced packages.)
## Arguments to `generate-packages`
## Arguments to `generate`
Use the `--single foo` option to generate just the package named "foo".
Use the `--all` option to generate even packages that have not changed.
Use the `--tgz` option to create `.tgz` archives as well. These should represent what is actually uploaded to NPM.
## Outputs of `generate-packages`
## Outputs of `generate`
### Package Folders
@@ -289,7 +291,7 @@ This file is currently uninteresting.
# Publish packages on disk
> `node bin/publish-packages.js`
> `npm run publish`
This step publishes the files to the NPM registry.
@@ -298,7 +300,7 @@ Several keys in `settings.json` affect this step; be sure to read this section.
Before publishing, the script checks the NPM registry to see if a package with the same version number has already been published.
If so, the publishing is skipped.
## Outputs of `publish-packages.js`
## Outputs of `publish`
### `logs/publishing.md`
@@ -310,7 +312,7 @@ Scripts should save this log under a unique filename so any errors may be review
# Publish registry
> `node bin/publish-registry.js [--dry]`
> `npm run publish -- [--dry]`
This step publishes the `types-registry` package on NPM, which keeps a list of all `@types` packages.
This step only happens if there are some new packages to register.
@@ -350,7 +352,7 @@ The script `npm run make-server-run` will trigger the local webhook just like Gi
"sourceRepository": "https://github.com/your/dummy-repo"
* Set the `GITHUB_SECRET` environment variable to `swordfish`
* `npm install; npm run build`
* `node bin/webhook.js --dry`
* `npm run webhook-dry`
* Make a test change:
* git clone https://github.com/your/dummy-repo.git
@@ -455,7 +457,7 @@ npm run validate [<package>]
for instance:
```sh
npm run validate node exress jquery
npm run validate node express jquery
```
will try to install the three packages, and run the tsc compiler on them.

View File

@@ -2,9 +2,7 @@ module.exports = {
"roots": [
"src"
],
"transform": {
"^.+\\.tsx?$": "ts-jest"
},
"preset": "ts-jest",
"testRegex": "(/__tests__/.*|\\.(test|spec))\\.tsx?$",
"moduleFileExtensions": [
"ts",

View File

@@ -49,6 +49,12 @@
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"dev": true
},
"semver": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
"dev": true
}
}
},
@@ -869,6 +875,13 @@
"requires": {
"semver": "^5.3.0",
"shimmer": "^1.1.0"
},
"dependencies": {
"semver": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ=="
}
}
},
"asynckit": {
@@ -1381,6 +1394,13 @@
"async-hook-jl": "^1.7.6",
"emitter-listener": "^1.0.1",
"semver": "^5.4.1"
},
"dependencies": {
"semver": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ=="
}
}
},
"co": {
@@ -1531,6 +1551,13 @@
"semver": "^5.5.0",
"shebang-command": "^1.2.0",
"which": "^1.2.9"
},
"dependencies": {
"semver": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ=="
}
}
},
"cssom": {
@@ -1703,6 +1730,13 @@
"integrity": "sha1-zJmvlhLCP7H/8TYSxy8sv6qNWhc=",
"requires": {
"semver": "^5.3.0"
},
"dependencies": {
"semver": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ=="
}
}
},
"diagnostic-channel-publishers": {
@@ -1809,6 +1843,13 @@
"semver": "^5.3.0",
"tslib": "^1.8.0",
"tsutils": "^2.29.0"
},
"dependencies": {
"semver": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ=="
}
}
}
}
@@ -4332,6 +4373,12 @@
"resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz",
"integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk="
},
"lodash.memoize": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
"integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=",
"dev": true
},
"lodash.set": {
"version": "4.3.2",
"resolved": "https://registry.npmjs.org/lodash.set/-/lodash.set-4.3.2.tgz",
@@ -4394,6 +4441,12 @@
"resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz",
"integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==",
"dev": true
},
"semver": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
"dev": true
}
}
},
@@ -4668,6 +4721,14 @@
"semver": "^5.5.0",
"shellwords": "^0.1.1",
"which": "^1.3.0"
},
"dependencies": {
"semver": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
"dev": true
}
}
},
"normalize-package-data": {
@@ -4679,6 +4740,13 @@
"resolve": "^1.10.0",
"semver": "2 || 3 || 4 || 5",
"validate-npm-package-license": "^3.0.1"
},
"dependencies": {
"semver": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ=="
}
}
},
"normalize-path": {
@@ -7760,6 +7828,13 @@
"osenv": "^0.1.5",
"semver": "^5.5.0",
"validate-npm-package-name": "^3.0.0"
},
"dependencies": {
"semver": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ=="
}
}
},
"npm-registry-client": {
@@ -7779,6 +7854,13 @@
"semver": "2 >=2.2.1 || 3.x || 4 || 5",
"slide": "^1.1.3",
"ssri": "^5.2.4"
},
"dependencies": {
"semver": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ=="
}
}
},
"npm-run-path": {
@@ -8531,11 +8613,6 @@
"resolved": "https://registry.npmjs.org/sax/-/sax-0.5.8.tgz",
"integrity": "sha1-1HLbIo6zMcJQaw6MFVJK25OdEsE="
},
"semver": {
"version": "5.6.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz",
"integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg=="
},
"set-blocking": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
@@ -9176,15 +9253,16 @@
"integrity": "sha1-/sAF+dyqJZo/lFnOWmkGq6TFRdo="
},
"ts-jest": {
"version": "23.10.5",
"resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-23.10.5.tgz",
"integrity": "sha512-MRCs9qnGoyKgFc8adDEntAOP64fWK1vZKnOYU1o2HxaqjdJvGqmkLCPCnVq1/If4zkUmEjKPnCiUisTrlX2p2A==",
"version": "24.2.0",
"resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-24.2.0.tgz",
"integrity": "sha512-Yc+HLyldlIC9iIK8xEN7tV960Or56N49MDP7hubCZUeI7EbIOTsas6rXCMB4kQjLACJ7eDOF4xWEO5qumpKsag==",
"dev": true,
"requires": {
"bs-logger": "0.x",
"buffer-from": "1.x",
"fast-json-stable-stringify": "2.x",
"json5": "2.x",
"lodash.memoize": "4.x",
"make-error": "1.x",
"mkdirp": "0.x",
"resolve": "1.x",
@@ -9198,19 +9276,10 @@
"integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=",
"dev": true
},
"json5": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.1.0.tgz",
"integrity": "sha512-8Mh9h6xViijj36g7Dxi+Y4S6hNGV96vcJZr/SrlHh1LR/pEn/8j/+qIBbs44YKl69Lrfctp4QD+AdWLTMqEZAQ==",
"dev": true,
"requires": {
"minimist": "^1.2.0"
}
},
"minimist": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
"semver": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
"dev": true
},
"yargs-parser": {
@@ -9248,6 +9317,14 @@
"semver": "^5.3.0",
"tslib": "^1.8.0",
"tsutils": "^2.27.2"
},
"dependencies": {
"semver": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
"dev": true
}
}
},
"tsutils": {

View File

@@ -41,33 +41,33 @@
"@types/travis-fold": "^0.1.0",
"@types/yargs": "^8.0.1",
"jest": "^24.9.0",
"ts-jest": "^23.10.5",
"ts-jest": "^24.2.0",
"tslint": "^5.13.0"
},
"scripts": {
"test": "npm run lint && npm run build && jest",
"build": "node node_modules/typescript/lib/tsc.js",
"watch": "node node_modules/typescript/lib/tsc.js --watch",
"clean": "node bin/clean.js",
"get-definitely-typed": "node bin/get-definitely-typed.js",
"parse": "node bin/parse-definitions.js",
"check": "node bin/check-parse-results.js",
"calculate-versions": "node bin/calculate-versions.js",
"generate": "node bin/generate-packages.js",
"validate": "node bin/validate.js",
"index": "node bin/create-search-index.js",
"publish": "node bin/publish-packages.js",
"publish-dry": "node bin/publish-packages.js --dry",
"publish-registry": "node bin/publish-registry.js",
"upload-blobs": "node bin/upload-blobs.js",
"full": "node bin/full.js",
"full-dry": "node bin/full.js --dry",
"clean": "node -r source-map-support/register bin/clean.js",
"get-definitely-typed": "node -r source-map-support/register bin/get-definitely-typed.js",
"parse": "node -r source-map-support/register bin/parse-definitions.js",
"check": "node -r source-map-support/register bin/check-parse-results.js",
"calculate-versions": "node -r source-map-support/register bin/calculate-versions.js",
"generate": "node -r source-map-support/register bin/generate-packages.js",
"validate": "node -r source-map-support/register bin/validate.js",
"index": "node -r source-map-support/register bin/create-search-index.js",
"publish": "node -r source-map-support/register bin/publish-packages.js",
"publish-dry": "node -r source-map-support/register bin/publish-packages.js --dry",
"publish-registry": "node -r source-map-support/register bin/publish-registry.js",
"upload-blobs": "node -r source-map-support/register bin/upload-blobs.js",
"full": "node -r source-map-support/register bin/full.js",
"full-dry": "node -r source-map-support/register bin/full.js --dry",
"lint": "node node_modules/tslint/bin/tslint --format stylish --project tsconfig.json",
"webhook-dry": "node ./bin/webhook.js --dry",
"make-server-run": "node bin/make-server-run.js",
"make-production-server-run": "node bin/make-server-run.js --remote",
"test-tsNext": "node bin/tester/test.js --all --tsNext",
"code-owners": "node bin/code-owners.js",
"webhook-dry": "node -r source-map-support/register bin/webhook.js --dry",
"make-server-run": "node -r source-map-support/register bin/make-server-run.js",
"make-production-server-run": "node -r source-map-support/register bin/make-server-run.js --remote",
"test-tsNext": "node -r source-map-support/register bin/tester/test.js --all --tsNext",
"code-owners": "node -r source-map-support/register bin/code-owners.js",
"push-production": "npm run build && git checkout production && git merge master && npm run build && git add bin && git commit -m \"Update bin\" && git push -u origin production"
},
"repository": {

View File

@@ -1,7 +1,7 @@
import { FS, getDefinitelyTyped } from "./get-definitely-typed";
import { Options } from "./lib/common";
import { NpmInfoRawVersions, NpmInfoVersion, UncachedNpmInfoClient } from "./lib/npm-client";
import { AllPackages, TypingsData } from "./lib/packages";
import { AllPackages, formatTypingVersion, TypingsData, TypingVersion } from "./lib/packages";
import { Semver } from "./lib/versions";
import { Logger, logger, loggerWithErrors, writeLog } from "./util/logging";
import { assertDefined, best, logUncaughtErrors, mapDefined, nAtATime } from "./util/util";
@@ -56,18 +56,26 @@ function checkTypeScriptVersions(allPackages: AllPackages): void {
function checkPathMappings(allPackages: AllPackages): void {
for (const pkg of allPackages.allTypings()) {
const pathMappings = new Map(pkg.pathMappings.map((p): [string, number] => [p.packageName, p.majorVersion]));
const pathMappings = new Map<string, TypingVersion>(pkg.pathMappings.map(p => [p.packageName, p.version]));
const unusedPathMappings = new Set(pathMappings.keys());
// If A depends on B, and B has path mappings, A must have the same mappings.
for (const dependency of allPackages.allDependencyTypings(pkg)) {
for (const { packageName, majorVersion } of dependency.pathMappings) {
if (pathMappings.get(packageName) !== majorVersion) {
for (const { packageName: transitiveDependencyName, version: transitiveDependencyVersion } of dependency.pathMappings) {
const pathMappingVersion = pathMappings.get(transitiveDependencyName);
if (
pathMappingVersion
&& (
pathMappingVersion.major !== transitiveDependencyVersion.major
|| pathMappingVersion.minor !== transitiveDependencyVersion.minor
)
) {
const expectedPathMapping = `${transitiveDependencyName}/v${formatTypingVersion(transitiveDependencyVersion)}`;
throw new Error(
`${pkg.desc} depends on ${dependency.desc}, which has a path mapping for ${packageName} v${majorVersion}. ` +
`${pkg.desc} depends on ${dependency.desc}, which has a path mapping for ${expectedPathMapping}. ` +
`${pkg.desc} must have the same path mappings as its dependencies.`);
}
unusedPathMappings.delete(packageName);
unusedPathMappings.delete(transitiveDependencyName);
}
unusedPathMappings.delete(dependency.name);

View File

@@ -3,6 +3,7 @@ import { Registry } from "./lib/common";
import { AllPackages, License, NotNeededPackage, readNotNeededPackages, TypesDataFile, TypingsData, TypingsDataRaw } from "./lib/packages";
import { createMockDT } from "./mocks";
import { testo } from "./util/test";
function createRawPackage(license: License): TypingsDataRaw {
return {
libraryName: "jquery",
@@ -65,7 +66,7 @@ testo({
expect(createReadme(typing)).toEqual(expect.stringContaining("Global values: none"));
},
basicPackageJson() {
const packages = AllPackages.from(createTypesData(), readNotNeededPackages(createMockDT()));
const packages = AllPackages.from(createTypesData(), readNotNeededPackages(createMockDT().fs));
const typing = new TypingsData(createRawPackage(License.MIT), /*isLatest*/ true);
expect(createPackageJSON(typing, "1.0", packages, Registry.NPM)).toEqual(`{
"name": "@types/jquery",
@@ -93,13 +94,13 @@ testo({
}`);
},
githubPackageJsonName() {
const packages = AllPackages.from(createTypesData(), readNotNeededPackages(createMockDT()));
const packages = AllPackages.from(createTypesData(), readNotNeededPackages(createMockDT().fs));
const typing = new TypingsData(createRawPackage(License.MIT), /*isLatest*/ true);
expect(createPackageJSON(typing, "1.0", packages, Registry.Github)).toEqual(
expect.stringContaining('"name": "@types/jquery"'));
},
githubPackageJsonRegistry() {
const packages = AllPackages.from(createTypesData(), readNotNeededPackages(createMockDT()));
const packages = AllPackages.from(createTypesData(), readNotNeededPackages(createMockDT().fs));
const typing = new TypingsData(createRawPackage(License.MIT), /*isLatest*/ true);
const s = createPackageJSON(typing, "1.0", packages, Registry.Github);
expect(s).toEqual(expect.stringContaining("publishConfig"));

View File

@@ -39,7 +39,7 @@ export default async function generatePackages(dt: FS, allPackages: AllPackages,
if (tgz) {
await writeTgz(pkg.outputDirectory, `${pkg.outputDirectory}.tgz`);
}
log(` * ${pkg.libraryName}`);
log(` * ${pkg.desc}`);
}
log("## Generating deprecated packages");
await withNpmCache(new UncachedNpmInfoClient(), async client => {
@@ -52,7 +52,7 @@ export default async function generatePackages(dt: FS, allPackages: AllPackages,
}
async function generateTypingPackage(typing: TypingsData, packages: AllPackages, version: string, dt: FS): Promise<void> {
const typesDirectory = dt.subDir("types").subDir(typing.name);
const packageFS = typing.isLatest ? typesDirectory : typesDirectory.subDir(`v${typing.major}`);
const packageFS = typing.isLatest ? typesDirectory : typesDirectory.subDir(typing.versionDirectoryName!);
await writeCommonOutputs(typing, createPackageJSON(typing, version, packages, Registry.NPM), createReadme(typing), Registry.NPM);
await writeCommonOutputs(typing, createPackageJSON(typing, version, packages, Registry.Github), createReadme(typing), Registry.Github);
@@ -139,7 +139,7 @@ function getDependencies(packageJsonDependencies: ReadonlyArray<PackageJsonDepen
const typesDependency = getFullNpmName(dependency.name);
// A dependency "foo" is already handled if we already have a dependency on the package "foo" or "@types/foo".
if (!packageJsonDependencies.some(d => d.name === dependency.name || d.name === typesDependency) && allPackages.hasTypingFor(dependency)) {
dependencies[typesDependency] = dependencySemver(dependency.majorVersion);
dependencies[typesDependency] = dependencySemver(dependency.version);
}
}
return sortObjectKeys(dependencies);

View File

@@ -0,0 +1,98 @@
// tslint:disable:object-literal-key-quotes
import { createMockDT } from "../mocks";
import { getTypingInfo } from "./definition-parser";
describe(getTypingInfo, () => {
it("keys data by major.minor version", () => {
const dt = createMockDT();
dt.addOldVersionOfPackage("jquery", "1.42");
dt.addOldVersionOfPackage("jquery", "2");
const info = getTypingInfo("jquery", dt.pkgFS("jquery"));
expect(Object.keys(info).sort()).toEqual(["1.42", "2.0", "3.3"]);
});
describe("concerning multiple versions", () => {
it("records what the version directory looks like on disk", () => {
const dt = createMockDT();
dt.addOldVersionOfPackage("jquery", "2");
dt.addOldVersionOfPackage("jquery", "1.5");
const info = getTypingInfo("jquery", dt.pkgFS("jquery"));
expect(info).toEqual({
"1.5": expect.objectContaining({
libraryVersionDirectoryName: "1.5",
}),
"2.0": expect.objectContaining({
libraryVersionDirectoryName: "2",
}),
"3.3": expect.objectContaining({
// The latest version does not have its own version directory
libraryVersionDirectoryName: undefined,
}),
});
});
it("records a path mapping to the version directory", () => {
const dt = createMockDT();
dt.addOldVersionOfPackage("jquery", "2");
dt.addOldVersionOfPackage("jquery", "1.5");
const info = getTypingInfo("jquery", dt.pkgFS("jquery"));
expect(info).toEqual({
"1.5": expect.objectContaining({
pathMappings: [{
packageName: "jquery",
version: { major: 1, minor: 5 },
}],
}),
"2.0": expect.objectContaining({
pathMappings: [{
packageName: "jquery",
version: { major: 2, minor: undefined },
}],
}),
"3.3": expect.objectContaining({
// The latest version does not have path mappings of its own
pathMappings: [],
}),
});
});
describe("validation thereof", () => {
it("throws if a directory exists for the latest major version", () => {
const dt = createMockDT();
dt.addOldVersionOfPackage("jquery", "3");
expect(() => {
getTypingInfo("jquery", dt.pkgFS("jquery"));
}).toThrow(
"The latest version is 3.3, so the subdirectory 'v3' is not allowed; " +
"since it applies to any 3.* version, up to and including 3.3.",
);
});
it("throws if a directory exists for the latest minor version", () => {
const dt = createMockDT();
dt.addOldVersionOfPackage("jquery", "3.3");
expect(() => {
getTypingInfo("jquery", dt.pkgFS("jquery"));
}).toThrow(
"The latest version is 3.3, so the subdirectory 'v3.3' is not allowed.",
);
});
it("does not throw when a minor version is older than the latest", () => {
const dt = createMockDT();
dt.addOldVersionOfPackage("jquery", "3.0");
expect(() => {
getTypingInfo("jquery", dt.pkgFS("jquery"));
}).not.toThrow();
});
});
});
});

View File

@@ -7,42 +7,83 @@ import {
} from "../util/util";
import { allReferencedFiles, createSourceFile, getModuleInfo, getTestDependencies } from "./module-info";
import { getLicenseFromPackageJson, PackageId, PackageJsonDependency, PathMapping, TypingsDataRaw, TypingsVersionsRaw } from "./packages";
import {
formatTypingVersion,
getLicenseFromPackageJson,
PackageId,
PackageJsonDependency,
PathMapping,
TypingsDataRaw,
TypingsVersionsRaw,
TypingVersion,
} from "./packages";
import { dependenciesWhitelist } from "./settings";
function matchesVersion(typingsDataRaw: TypingsDataRaw, version: TypingVersion, considerLibraryMinorVersion: boolean) {
return typingsDataRaw.libraryMajorVersion === version.major
&& (considerLibraryMinorVersion ?
(version.minor === undefined || typingsDataRaw.libraryMinorVersion === version.minor)
: true);
}
function formattedLibraryVersion(typingsDataRaw: TypingsDataRaw) {
return `${typingsDataRaw.libraryMajorVersion}.${typingsDataRaw.libraryMinorVersion}`;
}
/** @param fs Rooted at the package's directory, e.g. `DefinitelyTyped/types/abs` */
export function getTypingInfo(packageName: string, fs: FS): TypingsVersionsRaw {
if (packageName !== packageName.toLowerCase()) {
throw new Error(`Package name \`${packageName}\` should be strictly lowercase`);
}
interface OlderVersionDir { readonly directoryName: string; readonly majorVersion: number; }
interface OlderVersionDir { readonly directoryName: string; readonly version: TypingVersion; }
const [rootDirectoryLs, olderVersionDirectories] = split<string, OlderVersionDir>(fs.readdir(), fileOrDirectoryName => {
const majorVersion = parseMajorVersionFromDirectoryName(fileOrDirectoryName);
return majorVersion === undefined ? undefined : { directoryName: fileOrDirectoryName, majorVersion };
const version = parseVersionFromDirectoryName(fileOrDirectoryName);
return version === undefined ? undefined : { directoryName: fileOrDirectoryName, version };
});
const latestData = combineDataForAllTypesVersions(packageName, rootDirectoryLs, fs, undefined);
const latestVersion = latestData.libraryMajorVersion;
const considerLibraryMinorVersion = olderVersionDirectories.some(({ version }) => version.minor !== undefined);
const older = olderVersionDirectories.map(({ directoryName, majorVersion }) => {
if (majorVersion === latestVersion) {
throw new Error(`The latest major version is ${latestVersion}, but a directory v${latestVersion} exists.`);
const latestData: TypingsDataRaw = {
libraryVersionDirectoryName: undefined,
...combineDataForAllTypesVersions(packageName, rootDirectoryLs, fs, undefined),
};
const older = olderVersionDirectories.map(({ directoryName, version: directoryVersion }) => {
if (matchesVersion(latestData, directoryVersion, considerLibraryMinorVersion)) {
const latest = `${latestData.libraryMajorVersion}.${latestData.libraryMinorVersion}`;
throw new Error(
`The latest version is ${latest}, so the subdirectory '${directoryName}' is not allowed` +
(`v${latest}` === directoryName ?
"." : `; since it applies to any ${latestData.libraryMajorVersion}.* version, up to and including ${latest}.`),
);
}
const ls = fs.readdir(directoryName);
const data = combineDataForAllTypesVersions(packageName, ls, fs.subDir(directoryName), majorVersion);
const data: TypingsDataRaw = {
libraryVersionDirectoryName: formatTypingVersion(directoryVersion),
...combineDataForAllTypesVersions(packageName, ls, fs.subDir(directoryName), directoryVersion),
};
if (data.libraryMajorVersion !== majorVersion) {
throw new Error(
`Directory ${directoryName} indicates major version ${majorVersion}, but header indicates major version ${data.libraryMajorVersion}`);
if (!matchesVersion(data, directoryVersion, considerLibraryMinorVersion)) {
if (considerLibraryMinorVersion) {
throw new Error(
`Directory ${directoryName} indicates major.minor version ${directoryVersion.major}.${directoryVersion.minor}, ` +
`but header indicates major.minor version ${data.libraryMajorVersion}.${data.libraryMinorVersion}`,
);
} else {
throw new Error(
`Directory ${directoryName} indicates major version ${directoryVersion.major}, but header indicates major version ` +
data.libraryMajorVersion.toString(),
);
}
}
return data;
});
const res: TypingsVersionsRaw = {};
res[latestVersion] = latestData;
res[formattedLibraryVersion(latestData)] = latestData;
for (const o of older) {
res[o.libraryMajorVersion] = o;
res[formattedLibraryVersion(o)] = o;
}
return res;
}
@@ -69,27 +110,44 @@ function getTypesVersionsAndPackageJson(ls: ReadonlyArray<string>): LsMinusTypes
return { remainingLs, typesVersions, hasPackageJson: withoutPackageJson.length !== ls.length };
}
export function parseMajorVersionFromDirectoryName(directoryName: string): number | undefined {
const match = /^v(\d+)$/.exec(directoryName);
// tslint:disable-next-line no-null-keyword
return match === null ? undefined : Number(match[1]);
/**
* Parses a directory name into a version that either holds a single major version or a major and minor version.
*
* @example
*
* ```ts
* parseVersionFromDirectoryName("v1") // { major: 1 }
* parseVersionFromDirectoryName("v0.61") // { major: 0, minor: 61 }
* ```
*/
export function parseVersionFromDirectoryName(directoryName: string): TypingVersion | undefined {
const match = /^v(\d+)(\.(\d+))?$/.exec(directoryName);
if (match === null) {
return undefined;
} else {
return {
major: Number(match[1]),
minor: match[3] !== undefined ? Number(match[3]) : undefined, // tslint:disable-line strict-type-predicates (false positive)
};
}
}
function combineDataForAllTypesVersions(
typingsPackageName: string,
ls: ReadonlyArray<string>,
fs: FS,
oldMajorVersion: number | undefined,
): TypingsDataRaw {
directoryVersion: TypingVersion | undefined,
): Omit<TypingsDataRaw, "libraryVersionDirectoryName"> {
const { remainingLs, typesVersions, hasPackageJson } = getTypesVersionsAndPackageJson(ls);
// Every typesVersion has an index.d.ts, but only the root index.d.ts should have a header.
const { contributors, libraryMajorVersion, libraryMinorVersion, typeScriptVersion: minTsVersion, libraryName, projects } =
parseHeaderOrFail(readFileAndThrowOnBOM("index.d.ts", fs));
const dataForRoot = getTypingDataForSingleTypesVersion(undefined, typingsPackageName, fs.debugPath(), remainingLs, fs, oldMajorVersion);
const dataForRoot = getTypingDataForSingleTypesVersion(undefined, typingsPackageName, fs.debugPath(), remainingLs, fs, directoryVersion);
const dataForOtherTypesVersions = typesVersions.map(tsVersion => {
const subFs = fs.subDir(`ts${tsVersion}`);
return getTypingDataForSingleTypesVersion(tsVersion, typingsPackageName, fs.debugPath(), subFs.readdir(), subFs, oldMajorVersion);
return getTypingDataForSingleTypesVersion(tsVersion, typingsPackageName, fs.debugPath(), subFs.readdir(), subFs, directoryVersion);
});
const allTypesVersions = [dataForRoot, ...dataForOtherTypesVersions];
@@ -151,7 +209,7 @@ function getTypingDataForSingleTypesVersion(
packageDirectory: string,
ls: ReadonlyArray<string>,
fs: FS,
oldMajorVersion: number | undefined,
directoryVersion: TypingVersion | undefined,
): TypingDataFromIndividualTypeScriptVersion {
const tsconfig = fs.readJson("tsconfig.json") as TsConfig;
checkFilesFromTsConfig(packageName, tsconfig, fs.debugPath());
@@ -175,7 +233,7 @@ function getTypingDataForSingleTypesVersion(
),
);
const { dependencies, pathMappings } = calculateDependencies(packageName, tsconfig, dependenciesSet, oldMajorVersion);
const { dependencies, pathMappings } = calculateDependencies(packageName, tsconfig, dependenciesSet, directoryVersion);
const tsconfigPathsForHash = JSON.stringify(tsconfig.compilerOptions.paths);
return {
typescriptVersion,
@@ -270,7 +328,7 @@ function calculateDependencies(
packageName: string,
tsconfig: TsConfig,
dependencyNames: ReadonlySet<string>,
oldMajorVersion: number | undefined,
directoryVersion: TypingVersion | undefined,
): DependenciesAndPathMappings {
const paths = tsconfig.compilerOptions && tsconfig.compilerOptions.paths || {};
@@ -302,30 +360,36 @@ function calculateDependencies(
continue;
}
const majorVersion = parseDependencyVersionFromPath(dependencyName, dependencyName, pathMapping);
const pathMappingVersion = parseDependencyVersionFromPath(dependencyName, dependencyName, pathMapping);
if (dependencyName === packageName) {
if (oldMajorVersion === undefined) {
if (directoryVersion === undefined) {
throw new Error(`In ${packageName}: Latest version of a package should not have a path mapping for itself.`);
} else if (majorVersion !== oldMajorVersion) {
const correctPathMapping = [`${dependencyName}/v${oldMajorVersion}`];
} else if (
directoryVersion.major !== pathMappingVersion.major
|| directoryVersion.minor !== pathMappingVersion.minor
) {
const correctPathMapping = [`${dependencyName}/v${formatTypingVersion(directoryVersion)}`];
throw new Error(`In ${packageName}: Must have a "paths" entry of "${dependencyName}": ${JSON.stringify(correctPathMapping)}`);
}
} else {
if (dependencyNames.has(dependencyName)) {
dependencies.push({ name: dependencyName, majorVersion });
dependencies.push({ name: dependencyName, version: pathMappingVersion });
}
}
// Else, the path mapping may be necessary if it is for a dependency-of-a-dependency. We will check this in check-parse-results.
pathMappings.push({ packageName: dependencyName, majorVersion });
// Else, the path mapping may be necessary if it is for a transitive dependency. We will check this in check-parse-results.
pathMappings.push({ packageName: dependencyName, version: pathMappingVersion });
}
if (oldMajorVersion !== undefined && !(paths && packageName in paths)) {
throw new Error(`${packageName}: Older version ${oldMajorVersion} must have a path mapping for itself.`);
if (directoryVersion !== undefined && !(paths && packageName in paths)) {
const mapping = JSON.stringify([`${packageName}/v${formatTypingVersion(directoryVersion)}`]);
throw new Error(
`${packageName}: Older version ${formatTypingVersion(directoryVersion)} must have a "paths" entry of "${packageName}": ${mapping}`,
);
}
for (const dependency of dependencyNames) {
if (!dependencies.some(d => d.name === dependency) && !nodeBuiltins.has(dependency)) {
dependencies.push({ name: dependency, majorVersion: "*" });
dependencies.push({ name: dependency, version: "*" });
}
}
@@ -339,10 +403,9 @@ const nodeBuiltins: ReadonlySet<string> = new Set([
"string_decoder", "timers", "tls", "tty", "url", "util", "v8", "vm", "zlib",
]);
// e.g. parseDependencyVersionFromPath("../../foo/v0", "foo") should return "0"
function parseDependencyVersionFromPath(packageName: string, dependencyName: string, dependencyPath: string): number {
function parseDependencyVersionFromPath(packageName: string, dependencyName: string, dependencyPath: string): TypingVersion {
const versionString = withoutStart(dependencyPath, `${dependencyName}/`);
const version = versionString === undefined ? undefined : parseMajorVersionFromDirectoryName(versionString);
const version = versionString === undefined ? undefined : parseVersionFromDirectoryName(versionString);
if (version === undefined) {
throw new Error(`In ${packageName}, unexpected path mapping for ${dependencyName}: '${dependencyPath}'`);
}

View File

@@ -5,7 +5,7 @@ import { createMockDT } from "../mocks";
import { testo } from "../util/test";
import { allReferencedFiles, getModuleInfo, getTestDependencies } from "./module-info";
const fs = createMockDT();
const fs = createMockDT().fs;
function getBoringReferences() {
return allReferencedFiles(["index.d.ts", "boring-tests.ts"], fs.subDir("types").subDir("boring"), "boring", "types/boring");
}

View File

@@ -0,0 +1,38 @@
import { createMockDT } from "../mocks";
import { getTypingInfo } from "./definition-parser";
import { TypingsVersions } from "./packages";
describe(TypingsVersions, () => {
let versions: TypingsVersions;
beforeAll(() => {
const dt = createMockDT();
dt.addOldVersionOfPackage("jquery", "1");
dt.addOldVersionOfPackage("jquery", "2");
dt.addOldVersionOfPackage("jquery", "2.5");
versions = new TypingsVersions(getTypingInfo("jquery", dt.pkgFS("jquery")));
});
it("sorts the data from latest to oldest version", () => {
expect(Array.from(versions.getAll()).map(v => v.major)).toEqual([3, 2, 2, 1]);
});
it("returns the latest version", () => {
expect(versions.getLatest().major).toEqual(3);
});
it("finds the latest version when any version is wanted", () => {
expect(versions.get("*").major).toEqual(3);
});
it("finds the latest minor version for the given major version", () => {
expect(versions.get({ major: 2 }).major).toEqual(2);
expect(versions.get({ major: 2 }).minor).toEqual(5);
});
it("finds a specific version", () => {
expect(versions.get({ major: 2, minor: 0 }).major).toEqual(2);
expect(versions.get({ major: 2, minor: 0 }).minor).toEqual(0);
});
});

View File

@@ -6,7 +6,7 @@ import { assertSorted, joinPaths, mapValues, unmangleScopedPackage } from "../ut
import { readDataFile } from "./common";
import { outputDirPath, scopeName } from "./settings";
import { Semver } from "./versions";
import { compare as compareSemver, Semver } from "./versions";
export class AllPackages {
static async read(dt: FS): Promise<AllPackages> {
@@ -61,7 +61,7 @@ export class AllPackages {
tryResolve(dep: PackageId): PackageId {
const versions = this.data.get(getMangledNameForScopedPackage(dep.name));
return versions ? versions.get(dep.majorVersion).id : dep;
return versions ? versions.get(dep.version).id : dep;
}
/** Gets the latest version of a package. E.g. getLatest(node v6) was node v10 (before node v11 came out). */
@@ -90,9 +90,9 @@ export class AllPackages {
return pkg;
}
tryGetTypingsData({ name, majorVersion }: PackageId): TypingsData | undefined {
tryGetTypingsData({ name, version }: PackageId): TypingsData | undefined {
const versions = this.data.get(getMangledNameForScopedPackage(name));
return versions && versions.tryGet(majorVersion);
return versions && versions.tryGet(version);
}
allPackages(): ReadonlyArray<AnyPackage> {
@@ -114,10 +114,10 @@ export class AllPackages {
/** Returns all of the dependences *that have typings*, ignoring others, and including test dependencies. */
*allDependencyTypings(pkg: TypingsData): Iterable<TypingsData> {
for (const { name, majorVersion } of pkg.dependencies) {
for (const { name, version } of pkg.dependencies) {
const versions = this.data.get(getMangledNameForScopedPackage(name));
if (versions) {
yield versions.get(majorVersion);
yield versions.get(version);
}
}
@@ -152,10 +152,18 @@ function* flattenData(data: ReadonlyMap<string, TypingsVersions>): Iterable<Typi
export type AnyPackage = NotNeededPackage | TypingsData;
interface BaseRaw {
// The name of the library (human readable, e.g. might be "Moment.js" even though packageName is "moment")
/**
* The name of the library.
*
* A human readable version, e.g. it might be "Moment.js" even though `packageName` is "moment".
*/
readonly libraryName: string;
// The NPM name to publish this under, e.g. "jquery". Does not include "@types".
/**
* The NPM name to publish this under, e.g. "jquery".
*
* This does not include "@types".
*/
readonly typingsPackageName: string;
}
@@ -173,7 +181,7 @@ export abstract class PackageBase {
/** Short description for debug output. */
get desc(): string {
return this.isLatest ? this.name : `${this.name} v${this.major}`;
return this.isLatest ? this.name : `${this.name} v${this.major}.${this.minor}`;
}
constructor(data: BaseRaw) {
@@ -202,9 +210,10 @@ export abstract class PackageBase {
}
abstract readonly major: number;
abstract readonly minor: number;
get id(): PackageId {
return { name: this.name, majorVersion: this.major };
return { name: this.name, version: { major: this.major, minor: this.minor }};
}
get outputDirectory(): string {
@@ -272,8 +281,21 @@ export interface TypingsVersionsRaw {
[version: string]: TypingsDataRaw;
}
export interface TypingVersion {
major: number;
minor?: number;
}
export function formatTypingVersion(version: TypingVersion) {
return `${version.major}${version.minor === undefined ? "" : `.${version.minor}`}`;
}
/** If no version is specified, uses "*". */
export type DependencyVersion = number | "*";
export type DependencyVersion = TypingVersion | "*";
export function formatDependencyVersion(version: DependencyVersion) {
return version === "*" ? "*" : formatTypingVersion(version);
}
export interface PackageJsonDependency {
readonly name: string;
@@ -281,56 +303,115 @@ export interface PackageJsonDependency {
}
export interface TypingsDataRaw extends BaseRaw {
/**
* Other definitions, that exist in the same typings repo, that this package depends on.
*
* These will refer to *package names*, not *folder names*.
*/
readonly dependencies: ReadonlyArray<PackageId>;
// These are always the latest version.
// Will not include anything already in `dependencies`.
/**
* Other definitions, that exist in the same typings repo, that the tests, but not the types, of this package depend on.
*
* These are always the latest version and will not include anything already in `dependencies`.
*/
readonly testDependencies: ReadonlyArray<string>;
/**
* External packages, from outside the typings repo, that provide definitions that this package depends on.
*/
readonly packageJsonDependencies: ReadonlyArray<PackageJsonDependency>;
/**
* Represents that there was a path mapping to a package.
*
* Not all path mappings are direct dependencies, they may be necessary for transitive dependencies. However, where `dependencies` and
* `pathMappings` share a key, they *must* share the same value.
*/
readonly pathMappings: ReadonlyArray<PathMapping>;
// Parsed from "Definitions by:"
/**
* List of people that have contributed to the definitions in this package.
*
* These people will be requested for issue/PR review in the https://github.com/DefinitelyTyped/DefinitelyTyped repo.
*/
readonly contributors: ReadonlyArray<Author>;
// The major version of the library (e.g. "1" for 1.0, "2" for 2.0)
/**
* The [older] version of the library that this definition package refers to, as represented *on-disk*.
*
* @note The latest version always exists in the root of the package tree and thus does not have a value for this property.
*/
readonly libraryVersionDirectoryName?: string;
/**
* Major version of the library.
*
* This data is parsed from a header comment in the entry point `.d.ts` and will be `0` if the file did not specify a version.
*/
readonly libraryMajorVersion: number;
// The minor version of the library
/**
* Minor version of the library.
*
* This data is parsed from a header comment in the entry point `.d.ts` and will be `0` if the file did not specify a version.
*/
readonly libraryMinorVersion: number;
/**
* Minimum required TypeScript version to consume the definitions from this package.
*/
readonly minTsVersion: AllTypeScriptVersion;
/**
* List of TS versions that have their own directories, and corresponding "typesVersions" in package.json.
* Usually empty.
*/
readonly typesVersions: ReadonlyArray<TypeScriptVersion>;
// Files that should be published with this definition, e.g. ["jquery.d.ts", "jquery-extras.d.ts"]
// Does *not* include a partial `package.json` because that will not be copied directly.
/**
* Files that should be published with this definition, e.g. ["jquery.d.ts", "jquery-extras.d.ts"]
*
* Does *not* include a partial `package.json` because that will not be copied directly.
*/
readonly files: ReadonlyArray<string>;
// Whether a "package.json" exists
/**
* The license that this definition package is released under.
*
* Can be either MIT or Apache v2, defaults to MIT when not explicitly defined in this packages "package.json".
*/
readonly license: License;
readonly packageJsonDependencies: ReadonlyArray<PackageJsonDependency>;
// A hash computed from all files from this definition
/**
* A hash of the names and contents of the `files` list, used for versioning.
*/
readonly contentHash: string;
// Optionally-present name or URL of the project, e.g. "http://cordova.apache.org"
/**
* Name or URL of the project, e.g. "http://cordova.apache.org".
*/
readonly projectName: string;
// Names introduced into the global scope by this definition set
/**
* A list of *values* declared in the global namespace.
*
* @note This does not include *types* declared in the global namespace.
*/
readonly globals: ReadonlyArray<string>;
// External modules declared by this package. Includes the containing folder name when applicable (e.g. proper module)
/**
* External modules declared by this package. Includes the containing folder name when applicable (e.g. proper module).
*/
readonly declaredModules: ReadonlyArray<string>;
}
/**
* Represents that there was a path mapping to a package.
* Not all path mappings are direct dependencies: They may be necessary for dependencies-of-dependencies.
* But, where dependencies and pathMappings share a key, they must share the same value
* @see {TypingsDataRaw.pathMappings}
*/
export interface PathMapping {
readonly packageName: string;
readonly majorVersion: number;
readonly version: TypingVersion;
}
// TODO: support BSD -- but must choose a *particular* BSD license from the list at https://spdx.org/licenses/
@@ -349,47 +430,63 @@ export function getLicenseFromPackageJson(packageJsonLicense: unknown): License
throw new Error(`'package.json' license is ${JSON.stringify(packageJsonLicense)}.\nExpected one of: ${JSON.stringify(allLicenses)}}`);
}
class TypingsVersions {
private readonly map: ReadonlyMap<number, TypingsData>;
private readonly latest: number;
constructor(data: TypingsVersionsRaw) {
const versions = Object.keys(data).map(Number);
this.latest = Math.max(...versions);
this.map = new Map(versions.map((version): [number, TypingsData] =>
[version, new TypingsData(data[version], version === this.latest)]));
}
export class TypingsVersions {
private readonly map: ReadonlyMap<Semver, TypingsData>;
/**
* Values are reversed so that we publish the current version first.
* This is important because older versions repeatedly reset the "latest" tag to the current version.
* Sorted from latest to oldest.
*/
private readonly versions: Semver[];
constructor(data: TypingsVersionsRaw) {
const versionMappings = new Map(Object.keys(data).map(key => {
const version = Semver.parse(key, true);
if (version) {
return [version, key];
} else {
throw new Error(`Unable to parse version ${key}`);
}
}));
/**
* Sorted from latest to oldest so that we publish the current version first.
* This is important because older versions repeatedly reset the "latest" tag to the current version.
*/
this.versions = Array.from(versionMappings.keys()).sort(compareSemver).reverse();
this.map = new Map(this.versions.map(version => {
const dataKey = versionMappings.get(version)!;
return [version, new TypingsData(data[dataKey], version === this.versions[0])];
}));
}
getAll(): Iterable<TypingsData> {
return Array.from(this.map.values()).reverse();
return this.map.values();
}
get(majorVersion: DependencyVersion): TypingsData {
return majorVersion === "*" ? this.getLatest() : this.getExact(majorVersion);
get(version: DependencyVersion): TypingsData {
return version === "*" ? this.getLatest() : this.getLatestMatch(version);
}
tryGet(majorVersion: DependencyVersion): TypingsData | undefined {
return majorVersion === "*" ? this.getLatest() : this.tryGetExact(majorVersion);
tryGet(version: DependencyVersion): TypingsData | undefined {
return version === "*" ? this.getLatest() : this.tryGetLatestMatch(version);
}
getLatest(): TypingsData {
return this.getExact(this.latest);
return this.map.get(this.versions[0])!;
}
private getExact(majorVersion: number): TypingsData {
const data = this.tryGetExact(majorVersion);
private getLatestMatch(version: TypingVersion): TypingsData {
const data = this.tryGetLatestMatch(version);
if (!data) {
throw new Error(`Could not find version ${majorVersion}`);
throw new Error(`Could not find version ${version}`);
}
return data;
}
private tryGetExact(majorVersion: number): TypingsData | undefined {
return this.map.get(majorVersion);
private tryGetLatestMatch(version: TypingVersion): TypingsData | undefined {
const found = this.versions.find(v => v.major === version.major && (version.minor === undefined || v.minor === version.minor));
return found && this.map.get(found);
}
}
@@ -421,16 +518,20 @@ export class TypingsData extends PackageBase {
return this.data.dependencies;
}
get versionDirectoryName() {
return this.data.libraryVersionDirectoryName && `v${this.data.libraryVersionDirectoryName}`;
}
/** Path to this package, *relative* to the DefinitelyTyped directory. */
get subDirectoryPath(): string {
return this.isLatest ? this.name : `${this.name}/v${this.data.libraryMajorVersion}`;
return this.isLatest ? this.name : `${this.name}/v${this.versionDirectoryName}`;
}
}
/** Uniquely identifies a package. */
export interface PackageId {
readonly name: string;
readonly majorVersion: DependencyVersion;
readonly version: DependencyVersion;
}
export interface TypesDataFile {

View File

@@ -0,0 +1,31 @@
import { Semver } from "./versions";
describe(Semver, () => {
it("returns a formatted description", () => {
expect(new Semver(1, 2, 3).versionString).toEqual("1.2.3");
});
it("parses semver versions", () => {
expect(Semver.parse("0.42.1").versionString).toEqual("0.42.1");
});
it("parses versions that do not strictly adhere to semver", () => {
expect(Semver.parse("1", true).versionString).toEqual("1.0.0");
expect(Semver.parse("0.42", true).versionString).toEqual("0.42.0");
});
it("throws when a version cannot be parsed", () => {
expect(() => Semver.parse("1")).toThrow();
expect(() => Semver.parse("1", false)).toThrow();
});
it("returns whether or not it's equal to another Semver", () => {
expect(Semver.parse("1.2.3").equals(new Semver(1, 2, 3))).toBe(true);
expect(Semver.parse("1.2.3").equals(new Semver(3, 2, 1))).toBe(false);
});
it("returns whether or not it's greater than another Semver", () => {
expect(Semver.parse("1.2.3").greaterThan(new Semver(1, 2, 2))).toBe(true);
expect(Semver.parse("1.2.3").equals(new Semver(1, 2, 4))).toBe(false);
});
});

View File

@@ -78,8 +78,8 @@ function findActualLatest(times: Map<string, string>) {
/** Version of a package published to NPM. */
export class Semver {
static parse(semver: string): Semver {
const result = Semver.tryParse(semver);
static parse(semver: string, coerce?: boolean): Semver {
const result = Semver.tryParse(semver, coerce);
if (!result) {
throw new Error(`Unexpected semver: ${semver}`);
}
@@ -90,13 +90,27 @@ export class Semver {
return new Semver(major, minor, patch);
}
// This must parse the output of `versionString`.
static tryParse(semver: string): 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 = /^(\d+)\.(\d+)\.(\d+)$/;
/**
* 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.
*
* @note This must parse the output of `versionString`.
*
* @param semver The version string.
* @param coerce Without this optional parameter the version MUST follow the above semver spec. However, when set to `true` components after the
* major version may be omitted. I.e. `1` equals `1.0` and `1.0.0`.
*/
static tryParse(semver: string, coerce?: boolean): Semver | undefined {
const rgx = /^(\d+)(\.(\d+))?(\.(\d+))?$/;
const match = rgx.exec(semver);
return match ? new Semver(intOfString(match[1]), intOfString(match[2]), intOfString(match[3])) : undefined;
if (match) {
const { 1: major, 3: minor, 5: patch } = match;
if ((minor !== undefined && patch !== undefined) || coerce) { // tslint:disable-line:strict-type-predicates
return new Semver(intOfString(major), intOfString(minor || "0"), intOfString(patch || "0"));
}
}
return undefined;
}
constructor(readonly major: number, readonly minor: number, readonly patch: number) {}
@@ -106,12 +120,27 @@ export class Semver {
return `${major}.${minor}.${patch}`;
}
equals(sem: Semver): boolean {
return this.major === sem.major && this.minor === sem.minor && this.patch === sem.patch;
equals(other: Semver): boolean {
return compare(this, other) === 0;
}
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);
greaterThan(other: Semver): boolean {
return compare(this, other) === 1;
}
}
/**
* Returns 0 if equal, 1 if x > y, -1 if x < y
*/
export function compare(x: Semver, y: Semver) {
const versions: Array<[number, number]> = [[x.major, y.major], [x.minor, y.minor], [x.patch, y.patch]];
for (const [componentX, componentY] of versions) {
if (componentX > componentY) {
return 1;
}
if (componentX < componentY) {
return -1;
}
}
return 0;
}

View File

@@ -1,16 +1,82 @@
import { Dir, InMemoryDT } from "./get-definitely-typed";
import { parseHeaderOrFail } from "definitelytyped-header-parser";
import { Dir, FS, InMemoryDT } from "./get-definitely-typed";
import { Semver } from "./lib/versions";
class DTMock {
public readonly fs: FS;
private readonly root: Dir;
constructor() {
this.root = new Dir(undefined);
this.root.set("notNeededPackages.json", `{
"packages": [{
"libraryName": "Angular 2",
"typingsPackageName": "angular",
"asOfVersion": "1.2.3",
"sourceRepoURL": "https://github.com/angular/angular2"
}]
}`);
this.fs = new InMemoryDT(this.root, "DefinitelyTyped");
}
public pkgDir(packageName: string): Dir {
return this.root.subdir("types").subdir(packageName);
}
public pkgFS(packageName: string): FS {
return this.fs.subDir("types").subDir(packageName);
}
/**
* Creates a shallow copy of a package, meaning all entries in the old version directory that will be created refer to the copied entry from the
* latest version. The only exceptions are the `index.d.ts` and `tsconfig.json` files.
*
* The directory name will exactly follow the given `olderVersion`. I.e. `2` will become `v2`, whereas `2.2` will become `v2.2`.
*
* @param packageName The package of which an old version is to be added.
* @param olderVersion The older version that's to be added.
*/
public addOldVersionOfPackage(packageName: string, olderVersion: string) {
const latestDir = this.pkgDir(packageName);
const index = latestDir.get("index.d.ts") as string;
const latestHeader = parseHeaderOrFail(index);
const latestVersion = `${latestHeader.libraryMajorVersion}.${latestHeader.libraryMinorVersion}`;
const olderVersionParsed = Semver.parse(olderVersion, true)!;
const oldDir = latestDir.subdir(`v${olderVersion}`);
const tsconfig = JSON.parse(latestDir.get("tsconfig.json") as string);
oldDir.set("index.d.ts", index.replace(latestVersion, `${olderVersionParsed.major}.${olderVersionParsed.minor}`));
oldDir.set("tsconfig.json", JSON.stringify({
...tsconfig,
compilerOptions: {
...tsconfig.compilerOptions,
paths: {
[packageName]: [`${packageName}/v${olderVersion}`],
},
},
}));
latestDir.forEach((content, entry) => {
if (
content !== oldDir
&& entry !== "index.d.ts"
&& entry !== "tsconfig.json"
&& !(content instanceof Dir && /^v\d+(\.\d+)?$/.test(entry))
) {
oldDir.set(entry, content);
}
});
return oldDir;
}
}
export function createMockDT() {
const root = new Dir(undefined);
root.set("notNeededPackages.json", `{
"packages": [{
"libraryName": "Angular 2",
"typingsPackageName": "angular",
"asOfVersion": "1.2.3",
"sourceRepoURL": "https://github.com/angular/angular2"
}]
}`);
const types = root.subdir("types");
const boring = types.subdir("boring");
const dt = new DTMock();
const boring = dt.pkgDir("boring");
boring.set("index.d.ts", `// Type definitions for boring 1.0
// Project: https://boring.com
// Definitions by: Some Guy From Space <https://github.com/goodspaceguy420>
@@ -85,8 +151,8 @@ untested.d.ts
]
}`);
const globby = types.subdir("globby");
globby.set("index.d.ts", `// Type definitions for globby 0.1
const globby = dt.pkgDir("globby");
globby.set("index.d.ts", `// Type definitions for globby 0.2
// Project: https://globby-gloopy.com
// Definitions by: The Dragon Quest Slime <https://github.com/gloopyslime>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
@@ -135,7 +201,7 @@ var z = y;
"test/other-tests.ts"
]
}`);
const jquery = types.subdir("jquery");
const jquery = dt.pkgDir("jquery");
jquery.set("JQuery.d.ts", `
declare var jQuery: 1;
`);
@@ -180,5 +246,5 @@ console.log(jQuery);
`);
return new InMemoryDT(root, "DefinitelyTyped");
return dt;
}

View File

@@ -18,7 +18,7 @@ testo({
// },
async mockParse() {
const log = loggerWithErrors()[0];
const defs = await parseDefinitions(createMockDT(), undefined, log);
const defs = await parseDefinitions(createMockDT().fs, undefined, log);
expect(defs.allNotNeeded().length).toBe(1);
expect(defs.allTypings().length).toBe(3);
const j = defs.tryGetLatestVersion("jquery");

View File

@@ -4,10 +4,10 @@ import { createTypingsVersionRaw, testo } from "../util/test";
import { getAffectedPackages } from "./get-affected-packages";
const typesData: TypesDataFile = {
jquery: createTypingsVersionRaw("jquery", [], []),
known: createTypingsVersionRaw("known", [{ name: "jquery", majorVersion: 1 }], []),
known: createTypingsVersionRaw("known", [{ name: "jquery", version: { major: 1 }}], []),
"known-test": createTypingsVersionRaw("known-test", [], ["jquery"]),
"most-recent": createTypingsVersionRaw("most-recent", [{ name: "jquery", majorVersion: "*" }], []),
unknown: createTypingsVersionRaw("unknown", [{ name: "COMPLETELY-UNKNOWN", majorVersion: 1 }], []),
"most-recent": createTypingsVersionRaw("most-recent", [{ name: "jquery", version: "*" }], []),
unknown: createTypingsVersionRaw("unknown", [{ name: "COMPLETELY-UNKNOWN", version: { major: 1 }}], []),
"unknown-test": createTypingsVersionRaw("unknown-test", [], ["WAT"]),
};
@@ -18,13 +18,13 @@ const allPackages = AllPackages.from(typesData, notNeeded);
testo({
updatedPackage() {
const affected = getAffectedPackages(allPackages, [{ name: "jquery", majorVersion: 1 }]);
const affected = getAffectedPackages(allPackages, [{ name: "jquery", version: { major: 1 }}]);
expect(affected.changedPackages.length).toEqual(1);
expect((affected.changedPackages[0] as any).data).toEqual(typesData.jquery[1]);
expect((affected.changedPackages[0] as any).data).toEqual(typesData.jquery["1.0.0"]);
expect(affected.dependentPackages.length).toEqual(3);
},
deletedPackage() {
const affected = getAffectedPackages(allPackages, [{ name: "WAT", majorVersion: "*" }]);
const affected = getAffectedPackages(allPackages, [{ name: "WAT", version: "*" }]);
expect(affected.changedPackages.length).toEqual(0);
expect(affected.dependentPackages.length).toEqual(1);
},

View File

@@ -1,4 +1,4 @@
import { AllPackages, getMangledNameForScopedPackage, PackageBase, PackageId, TypingsData } from "../lib/packages";
import { AllPackages, formatDependencyVersion, getMangledNameForScopedPackage, PackageBase, PackageId, TypingsData } from "../lib/packages";
import { mapDefined, mapIter, sort } from "../util/util";
export interface Affected {
@@ -79,7 +79,7 @@ function getReverseDependencies(allPackages: AllPackages, changedPackages: Packa
}
}
for (const dependencyName of typing.testDependencies) {
const latest: PackageId = { name: dependencyName, majorVersion: "*" };
const latest: PackageId = { name: dependencyName, version: "*" };
const dependencies = map.get(packageIdToKey(allPackages.tryResolve(latest)));
if (dependencies) {
dependencies[1].add(typing.id);
@@ -90,5 +90,5 @@ function getReverseDependencies(allPackages: AllPackages, changedPackages: Packa
}
function packageIdToKey(pkg: PackageId): string {
return getMangledNameForScopedPackage(pkg.name) + "/v" + pkg.majorVersion;
return getMangledNameForScopedPackage(pkg.name) + "/v" + formatDependencyVersion(pkg.version);
}

View File

@@ -6,10 +6,10 @@ import { checkNotNeededPackage, getNotNeededPackages, GitDiff } from "./test-run
const typesData: TypesDataFile = {
jquery: createTypingsVersionRaw("jquery", [], []),
known: createTypingsVersionRaw("known", [{ name: "jquery", majorVersion: 1 }], []),
known: createTypingsVersionRaw("known", [{ name: "jquery", version: { major: 1 }}], []),
"known-test": createTypingsVersionRaw("known-test", [], ["jquery"]),
"most-recent": createTypingsVersionRaw("most-recent", [{ name: "jquery", majorVersion: "*" }], []),
unknown: createTypingsVersionRaw("unknown", [{ name: "COMPLETELY-UNKNOWN", majorVersion: 1 }], []),
"most-recent": createTypingsVersionRaw("most-recent", [{ name: "jquery", version: "*" }], []),
unknown: createTypingsVersionRaw("unknown", [{ name: "COMPLETELY-UNKNOWN", version: { major: 1 }}], []),
"unknown-test": createTypingsVersionRaw("unknown-test", [], ["WAT"]),
};

View File

@@ -7,9 +7,9 @@ import * as yargs from "yargs";
import { FS, getDefinitelyTyped } from "../get-definitely-typed";
import { Options, TesterOptions } from "../lib/common";
import { parseMajorVersionFromDirectoryName } from "../lib/definition-parser";
import { parseVersionFromDirectoryName } from "../lib/definition-parser";
import { NpmInfo, UncachedNpmInfoClient } from "../lib/npm-client";
import { AllPackages, DependencyVersion, NotNeededPackage, PackageId, TypingsData } from "../lib/packages";
import { AllPackages, DependencyVersion, formatDependencyVersion, NotNeededPackage, PackageId, TypingsData } from "../lib/packages";
import { sourceBranch, typesDirectoryName } from "../lib/settings";
import { Semver } from "../lib/versions";
import { npmInstallFlags } from "../util/io";
@@ -121,7 +121,7 @@ export function getNotNeededPackages(allPackages: AllPackages, diffs: GitDiff[])
When removing packages, you should only delete files that are a part of removed packages.`)
.name));
return mapIter(deletedPackages, p => {
if (allPackages.hasTypingFor({ name: p, majorVersion: "*" })) {
if (allPackages.hasTypingFor({ name: p, version: "*" })) {
throw new Error(`Please delete all files in ${p} when adding it to notNeededPackages.json.`);
}
return assertDefined(allPackages.getNotNeededPackage(p), `Deleted package ${p} is not in notNeededPackages.json.`);
@@ -282,22 +282,22 @@ async function runCommand(log: LoggerWithErrors, cwd: string | undefined, cmd: s
/** Returns all immediate subdirectories of the root directory that have changed. */
export function gitChanges(diffs: GitDiff[]): PackageId[] {
const changedPackages = new Map<string, Set<DependencyVersion>>();
const changedPackages = new Map<string, Map<string, DependencyVersion>>();
for (const diff of diffs) {
const dep = getDependencyFromFile(diff.file);
if (dep) {
const versions = changedPackages.get(dep.name);
if (!versions) {
changedPackages.set(dep.name, new Set([dep.majorVersion]));
changedPackages.set(dep.name, new Map([[formatDependencyVersion(dep.version), dep.version]]));
} else {
versions.add(dep.majorVersion);
versions.set(formatDependencyVersion(dep.version), dep.version);
}
}
}
return Array.from(flatMap(changedPackages, ([name, versions]) =>
mapIter(versions, majorVersion => ({ name, majorVersion }))));
mapIter(versions, ([_, version]) => ({ name, version }))));
}
/*
@@ -358,12 +358,11 @@ function getDependencyFromFile(file: string): PackageId | undefined {
}
if (subDirName) {
// Looks like "types/a/v3/c"
const majorVersion = parseMajorVersionFromDirectoryName(subDirName);
if (majorVersion !== undefined) {
return { name, majorVersion };
const version = parseVersionFromDirectoryName(subDirName);
if (version !== undefined) {
return { name, version };
}
}
return { name, majorVersion: "*" };
return { name, version: "*" };
}

View File

@@ -1,4 +1,5 @@
import { License, PackageId, TypingsVersionsRaw } from "../lib/packages";
export function testo(o: { [s: string]: () => void }) {
for (const k in o) {
test(k, o[k], 100_000);
@@ -9,7 +10,7 @@ export function createTypingsVersionRaw(
name: string, dependencies: PackageId[], testDependencies: string[],
): TypingsVersionsRaw {
return {
1: {
"1.0.0": {
libraryName: name,
typingsPackageName: name,
dependencies,

View File

@@ -3,14 +3,11 @@ import { ChildProcess, exec as node_exec, fork } from "child_process";
import * as crypto from "crypto";
import moment = require("moment");
import * as os from "os";
import * as sourceMapSupport from "source-map-support";
import { Options } from "../lib/common";
import ProgressBar from "./progress";
sourceMapSupport.install();
export function assertDefined<T>(x: T | undefined, message?: string | Error | undefined): T {
assert(x !== undefined, message);
return x!;