diff --git a/deprecations.d.ts b/deprecations.d.ts new file mode 100644 index 0000000..0fb0f2a --- /dev/null +++ b/deprecations.d.ts @@ -0,0 +1,10 @@ +type NonZeroDigit = '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'; +type Digit = '0' | NonZeroDigit; +type Digits = `${NonZeroDigit}${Digit | string}`; + +type Triple = `${Digits}.${Digits}.${Digits}`; +type PreRelease = `-${string}`; + +export type Semver = `${Triple}${PreRelease | ''}`; + +export type DeprecationsByVersion = { [version: Semver]: string }; diff --git a/index.js b/index.js index c50acb5..0798a1e 100644 --- a/index.js +++ b/index.js @@ -5,28 +5,43 @@ const fromEntries = require('object.fromentries'); const entries = require('object.entries'); const pacote = require('pacote'); -const isString = (str) => typeof str === 'string'; +const isString = /** @type {(str: unknown) => str is string} */ (str) => typeof str === 'string'; const { from } = Array; +/** @typedef {import('./deprecations').DeprecationsByVersion} DeprecationsByVersion */ + +/** @type {(...packageNames: T[]) => Promise>} */ module.exports = function deprecations(packageName, ...morePackageNames) { if (arguments.length < 1) { throw new TypeError('at least 1 package name is required'); } + // @ts-expect-error https://github.com/microsoft/TypeScript/issues/57164 if (!every(arguments, isString)) { throw new TypeError('module names must all be strings'); } - return Promise.all(from(arguments, async (name) => { - const { versions } = await pacote.packument(name, { fullMetadata: true }); - return [ - name, - fromEntries(entries(versions).map(([version, { deprecated }]) => [ - version, - deprecated, - ])), - ]; - })).then(fromEntries); + const depEntries = Promise.all(from( + arguments, + /** @type {(name: string) => Promise<[name, DeprecationsByVersion]>} */ async (name) => { + const { versions } = await pacote.packument(name, { fullMetadata: true }); + // eslint-disable-next-line no-extra-parens + const obj = /** @type {DeprecationsByVersion} */ ( + fromEntries(entries(versions).map(([ + version, + { deprecated }, + ]) => [ + version, + deprecated, + ])) + ); + return [ + name, + obj, + ]; + }, + )); + return depEntries.then(fromEntries); }; diff --git a/package.json b/package.json index 7b82d3f..72749a3 100644 --- a/package.json +++ b/package.json @@ -46,13 +46,19 @@ }, "devDependencies": { "@ljharb/eslint-config": "^21.1.0", + "@types/array.prototype.every": "^1.1.0", + "@types/object.entries": "^1.1.0", + "@types/object.fromentries": "^2.0.4", + "@types/pacote": "^11.1.8", + "@types/tape": "^5.6.4", "aud": "^2.0.4", "eslint": "=8.8.0", "in-publish": "^2.0.1", "npmignore": "^0.3.1", "nyc": "^15.1.0", "safe-publish-latest": "^2.0.0", - "tape": "^5.7.4" + "tape": "^5.7.4", + "typescript": "^5.4.0-dev.20240127" }, "engines": { "node": "^18.17.0 || >=20.5.0" diff --git a/test/index.js b/test/index.js index 297205d..8352bd8 100644 --- a/test/index.js +++ b/test/index.js @@ -2,20 +2,20 @@ const deprecations = require('../'); const test = require('tape'); +const fromEntries = require('object.fromentries'); +/** @type {(obj: T) => Partial} */ const compact = function (obj) { - return Object.keys(obj).reduce((map, key) => { - if (obj[key]) { - map[key] = obj[key]; // eslint-disable-line no-param-reassign - } - return map; - }, {}); + return fromEntries(Object.entries(obj).filter(([, value]) => value)); }; test('errors', (t) => { t.throws(deprecations, new TypeError('at least 1 package name is required')); + // @ts-expect-error t.throws(() => { deprecations(3); }, new TypeError('module names must all be strings')); + // @ts-expect-error t.throws(() => { deprecations('foo', 3); }, new TypeError('module names must all be strings')); + // @ts-expect-error t.throws(() => { deprecations(3, 'foo'); }, new TypeError('module names must all be strings')); t.end(); }); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..210a000 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,49 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig.json to read more about this file */ + + /* Projects */ + + /* Language and Environment */ + "target": "es2022", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ + // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ + // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ + "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ + // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ + + /* Modules */ + "module": "commonjs", /* Specify what module code is generated. */ + // "rootDir": "./", /* Specify the root folder within your source files. */ + // "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ + // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ + // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ + // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ + // "typeRoots": ["types"], /* Specify multiple folders that act like `./node_modules/@types`. */ + "resolveJsonModule": true, /* Enable importing .json files. */ + // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ + + /* JavaScript Support */ + "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */ + "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ + "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */ + + /* Emit */ + "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ + "declarationMap": true, /* Create sourcemaps for d.ts files. */ + "noEmit": true, /* Disable emitting files from a compilation. */ + + /* Interop Constraints */ + "allowSyntheticDefaultImports": true, /* Allow `import x from y` when a module doesn't have a default export. */ + "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */ + "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ + + /* Type Checking */ + "strict": true, /* Enable all strict type-checking options. */ + + /* Completeness */ + // "skipLibCheck": true /* Skip type checking all .d.ts files. */ + }, + "exclude": [ + "coverage", + ], +}