From 43d3091c368827446e03f03860ec1b36c7f3eea9 Mon Sep 17 00:00:00 2001 From: Raine Revere Date: Sat, 8 May 2021 18:04:44 -0600 Subject: [PATCH] Convert package-managers/npm to typescript. --- package-lock.json | 140 ++++++++++ package.json | 1 + src/package-managers/{npm.js => npm.ts} | 352 ++++++++++++------------ src/package-managers/yarn.js | 2 +- src/types.ts | 17 ++ src/types/cint.d.ts | 1 + src/types/libnpmconfig.d.ts | 1 + src/types/spawn-please.d.ts | 1 + src/version-util.ts | 2 +- tsconfig.json | 4 +- 10 files changed, 340 insertions(+), 181 deletions(-) rename src/package-managers/{npm.js => npm.ts} (51%) create mode 100644 src/types/libnpmconfig.d.ts create mode 100644 src/types/spawn-please.d.ts diff --git a/package-lock.json b/package-lock.json index d7de26760..27c6ae980 100644 --- a/package-lock.json +++ b/package-lock.json @@ -46,6 +46,7 @@ "@types/lodash": "^4.14.168", "@types/minimatch": "^3.0.4", "@types/node": "^15.0.1", + "@types/pacote": "^11.1.0", "@types/parse-github-url": "^1.0.0", "@types/progress": "^2.0.3", "@types/prompts": "^2.0.11", @@ -730,6 +731,67 @@ "integrity": "sha512-TMkXt0Ck1y0KKsGr9gJtWGjttxlZnnvDtphxUOSd0bfaR6Q1jle+sPvrzNR1urqYTWMinoKvjKfXUGsumaO1PA==", "dev": true }, + "node_modules/@types/node-fetch": { + "version": "2.5.10", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.5.10.tgz", + "integrity": "sha512-IpkX0AasN44hgEad0gEF/V6EgR5n69VEqPEgnmoM8GsIGro3PowbWs4tR6IhxUTyPLpOn+fiGG6nrQhcmoCuIQ==", + "dev": true, + "dependencies": { + "@types/node": "*", + "form-data": "^3.0.0" + } + }, + "node_modules/@types/node-fetch/node_modules/form-data": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", + "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", + "dev": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@types/npm-package-arg": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@types/npm-package-arg/-/npm-package-arg-6.1.0.tgz", + "integrity": "sha512-vbt5fb0y1svMhu++1lwtKmZL76d0uPChFlw7kEzyUmTwfmpHRcFb8i0R8ElT69q/L+QLgK2hgECivIAvaEDwag==", + "dev": true + }, + "node_modules/@types/npm-registry-fetch": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@types/npm-registry-fetch/-/npm-registry-fetch-8.0.0.tgz", + "integrity": "sha512-3dtNw1VMy1gnaklK0746/cOkJYMvdY/3FNuRDR5ih+WUWIbgTR6JvKq6hmhW4G1/o6lPdjHECPaWcowXgWZDyg==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/node-fetch": "*", + "@types/npm-package-arg": "*", + "@types/npmlog": "*", + "@types/ssri": "*" + } + }, + "node_modules/@types/npmlog": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@types/npmlog/-/npmlog-4.1.2.tgz", + "integrity": "sha512-4QQmOF5KlwfxJ5IGXFIudkeLCdMABz03RcUXu+LCb24zmln8QW6aDjuGl4d4XPVLf2j+FnjelHTP7dvceAFbhA==", + "dev": true + }, + "node_modules/@types/pacote": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/@types/pacote/-/pacote-11.1.0.tgz", + "integrity": "sha512-ULeeKzbZ3e5GRlqbVDUDgi0L0RYg4OJXLSrtOoyTmGJTAN7JdR0IyZ0kXCCjGzkZwh4ABM7TKISwyDkJqnbAgw==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/npm-registry-fetch": "*", + "@types/npmlog": "*", + "@types/ssri": "*" + } + }, "node_modules/@types/parse-github-url": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@types/parse-github-url/-/parse-github-url-1.0.0.tgz", @@ -775,6 +837,15 @@ "integrity": "sha512-p3ZoozEL036SPVLpMh1LuDebYQkes1rhBfXSFCbact9/FF6dkvR1UV/UqNO5wN5tvdlq+4jK+xTwgWwqRlFUJA==", "dev": true }, + "node_modules/@types/ssri": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@types/ssri/-/ssri-7.1.0.tgz", + "integrity": "sha512-CJR8I0rHwuhpS6YBq1q+StUlQBuxoyfVVZ3O1FDiXH1HJtNm90lErBsZpr2zBMF2x5d9khvq105CQ03EXkZzAQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "4.22.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.22.1.tgz", @@ -8048,6 +8119,66 @@ "integrity": "sha512-TMkXt0Ck1y0KKsGr9gJtWGjttxlZnnvDtphxUOSd0bfaR6Q1jle+sPvrzNR1urqYTWMinoKvjKfXUGsumaO1PA==", "dev": true }, + "@types/node-fetch": { + "version": "2.5.10", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.5.10.tgz", + "integrity": "sha512-IpkX0AasN44hgEad0gEF/V6EgR5n69VEqPEgnmoM8GsIGro3PowbWs4tR6IhxUTyPLpOn+fiGG6nrQhcmoCuIQ==", + "dev": true, + "requires": { + "@types/node": "*", + "form-data": "^3.0.0" + }, + "dependencies": { + "form-data": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", + "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + } + } + }, + "@types/npm-package-arg": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@types/npm-package-arg/-/npm-package-arg-6.1.0.tgz", + "integrity": "sha512-vbt5fb0y1svMhu++1lwtKmZL76d0uPChFlw7kEzyUmTwfmpHRcFb8i0R8ElT69q/L+QLgK2hgECivIAvaEDwag==", + "dev": true + }, + "@types/npm-registry-fetch": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@types/npm-registry-fetch/-/npm-registry-fetch-8.0.0.tgz", + "integrity": "sha512-3dtNw1VMy1gnaklK0746/cOkJYMvdY/3FNuRDR5ih+WUWIbgTR6JvKq6hmhW4G1/o6lPdjHECPaWcowXgWZDyg==", + "dev": true, + "requires": { + "@types/node": "*", + "@types/node-fetch": "*", + "@types/npm-package-arg": "*", + "@types/npmlog": "*", + "@types/ssri": "*" + } + }, + "@types/npmlog": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@types/npmlog/-/npmlog-4.1.2.tgz", + "integrity": "sha512-4QQmOF5KlwfxJ5IGXFIudkeLCdMABz03RcUXu+LCb24zmln8QW6aDjuGl4d4XPVLf2j+FnjelHTP7dvceAFbhA==", + "dev": true + }, + "@types/pacote": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/@types/pacote/-/pacote-11.1.0.tgz", + "integrity": "sha512-ULeeKzbZ3e5GRlqbVDUDgi0L0RYg4OJXLSrtOoyTmGJTAN7JdR0IyZ0kXCCjGzkZwh4ABM7TKISwyDkJqnbAgw==", + "dev": true, + "requires": { + "@types/node": "*", + "@types/npm-registry-fetch": "*", + "@types/npmlog": "*", + "@types/ssri": "*" + } + }, "@types/parse-github-url": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@types/parse-github-url/-/parse-github-url-1.0.0.tgz", @@ -8093,6 +8224,15 @@ "integrity": "sha512-p3ZoozEL036SPVLpMh1LuDebYQkes1rhBfXSFCbact9/FF6dkvR1UV/UqNO5wN5tvdlq+4jK+xTwgWwqRlFUJA==", "dev": true }, + "@types/ssri": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@types/ssri/-/ssri-7.1.0.tgz", + "integrity": "sha512-CJR8I0rHwuhpS6YBq1q+StUlQBuxoyfVVZ3O1FDiXH1HJtNm90lErBsZpr2zBMF2x5d9khvq105CQ03EXkZzAQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@typescript-eslint/eslint-plugin": { "version": "4.22.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.22.1.tgz", diff --git a/package.json b/package.json index 767e9072e..49fb74581 100644 --- a/package.json +++ b/package.json @@ -89,6 +89,7 @@ "@types/lodash": "^4.14.168", "@types/minimatch": "^3.0.4", "@types/node": "^15.0.1", + "@types/pacote": "^11.1.0", "@types/parse-github-url": "^1.0.0", "@types/progress": "^2.0.3", "@types/prompts": "^2.0.11", diff --git a/src/package-managers/npm.js b/src/package-managers/npm.ts similarity index 51% rename from src/package-managers/npm.js rename to src/package-managers/npm.ts index 36f1387e3..4932ed4c7 100644 --- a/src/package-managers/npm.js +++ b/src/package-managers/npm.ts @@ -1,20 +1,21 @@ 'use strict' -const _ = require('lodash') -const cint = require('cint') -const fs = require('fs') -const semver = require('semver') -const spawn = require('spawn-please') -const pacote = require('pacote') -const mem = require('mem') -const libnpmconfig = require('libnpmconfig') -const versionUtil = require('../version-util') -const { print } = require('../logging') +import _ from 'lodash' +import cint from 'cint' +import fs from 'fs' +import semver from 'semver' +import spawn from 'spawn-please' +import pacote from 'pacote' +import mem from 'mem' +import libnpmconfig from 'libnpmconfig' +import * as versionUtil from '../version-util' +import { print } from '../logging' +import { GetVersion, Index, Maybe, Options, NpmOptions, Packument, Version, VersionDeclaration } from '../types' const TIME_FIELDS = ['modified', 'created'] const npmConfigToPacoteMap = { - cafile: path => { + cafile: (path: string) => { // load-cafile, based on github.com/npm/cli/blob/40c1b0f/src/config/load-cafile.js if (!path) return const cadata = fs.readFileSync(path, 'utf8') @@ -31,16 +32,16 @@ const npmConfigToPacoteMap = { // needed until pacote supports full npm config compatibility // See: https://github.com/zkat/pacote/issues/156 -const npmConfig = {} -libnpmconfig.read().forEach((value, key) => { +const npmConfig: Index = {} +libnpmconfig.read().forEach((value: string, key: string) => { // replace env ${VARS} in strings with the process.env value const normalizedValue = typeof value !== 'string' ? value : value.replace(/\${([^}]+)}/, (_, envVar) => - process.env[envVar] + process.env[envVar] as string ) - const { [key]: pacoteKey } = npmConfigToPacoteMap + const { [key]: pacoteKey }: Index any)> = npmConfigToPacoteMap if (_.isString(pacoteKey)) { npmConfig[pacoteKey] = normalizedValue } @@ -66,7 +67,7 @@ npmConfig.cache = false * @param data * @returns */ -function parseJson(result, data) { +function parseJson(result: string, data: { command?: string, packageName?: string }) { let json // use a try-catch instead of .catch to avoid re-catching upstream errors try { @@ -86,7 +87,7 @@ function parseJson(result, data) { * @param upgradedVersion Upgraded version declaration (may be range) * @returns A promise that fullfills with boolean value. */ -async function packageAuthorChanged(packageName, currentVersion, upgradedVersion, options = {}) { +export async function packageAuthorChanged(packageName: string, currentVersion: VersionDeclaration, upgradedVersion: VersionDeclaration, options: Options = {}) { const result = await pacote.packument(packageName, { ...npmConfig, @@ -98,8 +99,8 @@ async function packageAuthorChanged(packageName, currentVersion, upgradedVersion const current = semver.minSatisfying(pkgVersions, currentVersion) const upgraded = semver.maxSatisfying(pkgVersions, upgradedVersion) if (current && upgraded && result.versions[current]._npmUser && result.versions[upgraded]._npmUser) { - const currentAuthor = result.versions[current]._npmUser.name - const latestAuthor = result.versions[upgraded]._npmUser.name + const currentAuthor = result.versions[current]._npmUser?.name + const latestAuthor = result.versions[upgraded]._npmUser?.name return !_.isEqual(currentAuthor, latestAuthor) } } @@ -107,19 +108,6 @@ async function packageAuthorChanged(packageName, currentVersion, upgradedVersion return false } -/** - * Returns the value of one of the properties retrieved by npm view. - * - * @param packageName Name of the package - * @param field Field such as "versions" or "dist-tags.latest" are parsed from the pacote result (https://www.npmjs.com/package/pacote#packument) - * @param currentVersion - * @returns Promised result - */ -async function viewOne(packageName, field, currentVersion, options = {}) { - const result = await viewManyMemoized(packageName, [field], currentVersion, options) - return result && result[field] -} - /** * Returns an object of specified values retrieved by npm view. * @@ -128,9 +116,9 @@ async function viewOne(packageName, field, currentVersion, options = {}) { * @param currentVersion * @returns Promised result */ -async function viewMany(packageName, fields, currentVersion, { registry, timeout } = {}) { +export async function viewMany(packageName: string, fields: string[], currentVersion: Version, { registry, timeout }: { registry?: string, timeout?: number } = {}) { if (currentVersion && (!semver.validRange(currentVersion) || versionUtil.isWildCard(currentVersion))) { - return Promise.resolve({}) + return Promise.resolve({} as Packument) } const result = await pacote.packument(packageName, { @@ -142,17 +130,30 @@ async function viewMany(packageName, fields, currentVersion, { registry, timeout return fields.reduce((accum, field) => ({ ...accum, [field]: field.startsWith('dist-tags.') && result.versions ? - result.versions[_.get(result, field)] : + result.versions[_.get(result, field) as any] : result[field] - }), {}) + }), {} as Packument) } /** Memoize viewMany for --deep performance. */ -const viewManyMemoized = mem(viewMany, { +export const viewManyMemoized = mem(viewMany, { cacheKey: ([packageName, fields, currentVersion]) => `${packageName}|${fields.join('|')}|${currentVersion}` }) +/** + * Returns the value of one of the properties retrieved by npm view. + * + * @param packageName Name of the package + * @param field Field such as "versions" or "dist-tags.latest" are parsed from the pacote result (https://www.npmjs.com/package/pacote#packument) + * @param currentVersion + * @returns Promised result + */ +export async function viewOne(packageName: string, field: string, currentVersion: Version, options: Options = {}) { + const result = await viewManyMemoized(packageName, [field], currentVersion, options) + return result && result[field as keyof Packument] +} + /** * Returns true if the node engine requirement is satisfied or not specified for a given package version. * @@ -160,7 +161,7 @@ const viewManyMemoized = mem(viewMany, { * @param nodeEngine The value of engines.node in the package file. * @returns True if the node engine requirement is satisfied or not specified. */ -function satisfiesNodeEngine(versionResult, nodeEngine) { +function satisfiesNodeEngine(versionResult: Packument, nodeEngine: Maybe): boolean { if (!nodeEngine) return true const minVersion = _.get(semver.minVersion(nodeEngine), 'version') if (!minVersion) return true @@ -175,7 +176,7 @@ function satisfiesNodeEngine(versionResult, nodeEngine) { * @param peerDependencies The list of peer dependencies. * @returns True if the peer dependencies are satisfied or not specified. */ -function satisfiesPeerDependencies(versionResult, peerDependencies) { +function satisfiesPeerDependencies(versionResult: Packument, peerDependencies: Index>) { if (!peerDependencies) return true return Object.values(peerDependencies).every( peers => peers[versionResult.name] === undefined || semver.satisfies(versionResult.version, peers[versionResult.name]) @@ -183,12 +184,13 @@ function satisfiesPeerDependencies(versionResult, peerDependencies) { } /** Returns a composite predicate that filters out deprecated, prerelease, and node engine incompatibilies from version objects returns by pacote.packument. */ -function filterPredicate(options) { +function filterPredicate(options: Options): (o: Packument) => boolean { return _.overEvery([ - options.deprecated ? null : o => !o.deprecated, - options.pre ? null : o => !versionUtil.isPre(o.version), - options.enginesNode ? o => satisfiesNodeEngine(o, options.enginesNode) : null, - options.peerDependencies ? o => satisfiesPeerDependencies(o, options.peerDependencies) : null, + options.deprecated ? null! : o => !o.deprecated, + options.pre ? null! : o => !versionUtil.isPre(o.version), + // TODO: options.enginesNode is a boolean, but satisfiesNodeEngine expects the value of engines.node + options.enginesNode ? o => satisfiesNodeEngine(o, options.enginesNode as any) : null!, + options.peerDependencies ? o => satisfiesPeerDependencies(o, options.peerDependencies!) : null!, ]) } @@ -200,7 +202,7 @@ function filterPredicate(options) { * @param [spawnOptions={}] * @returns */ -function spawnNpm(args, npmOptions = {}, spawnOptions = {}) { +function spawnNpm(args: string | string[], npmOptions: NpmOptions = {}, spawnOptions = {}) { const cmd = process.platform === 'win32' ? 'npm.cmd' : 'npm' args = Array.isArray(args) ? args : [args] @@ -221,7 +223,7 @@ function spawnNpm(args, npmOptions = {}, spawnOptions = {}) { * @param [options.prefix] * @returns */ -async function defaultPrefix(options) { +export async function defaultPrefix(options: Options) { if (options.prefix) { return Promise.resolve(options.prefix) @@ -252,148 +254,142 @@ async function defaultPrefix(options) { null } -module.exports = { - - npm: spawnNpm, - - /** - * Requests the list of peer dependencies for a specific package version - * - * @param packageName - * @param version - * @returns Promised {packageName: version} collection - */ - async getPeerDependencies(packageName, version) { - const result = await spawnNpm( - ['view', packageName + '@' + version, 'peerDependencies'], - {}, - { rejectOnError: false }) - return result ? parseJson(result, { command: 'npm view' }) : {} - }, +/** + * @param packageName + * @param currentVersion + * @param options + * @returns + */ +export const greatest: GetVersion = async (packageName, currentVersion, options = {}) => { + // known type based on 'versions' + const versions = await viewOne(packageName, 'versions', currentVersion, options) as Packument[] - /** - * @param [options] - * @param [options.cwd] - * @param [options.global] - * @param [options.prefix] - * @returns - */ - async list(options = {}) { - - const result = await spawnNpm('ls', options, { - ...options.cwd ? { cwd: options.cwd } : null, - rejectOnError: false - }) - const json = parseJson(result, { command: 'npm ls' }) - return cint.mapObject(json.dependencies, (name, info) => ({ - // unmet peer dependencies have a different structure - [name]: info.version || (info.required && info.required.version) - })) - }, + return _.last( + // eslint-disable-next-line fp/no-mutating-methods + _.filter(versions, filterPredicate(options)) + .map(o => o.version) + .sort(versionUtil.compareVersions) + ) || null +} - /** - * @param packageName - * @param currentVersion - * @param options - * @returns - */ - async latest(packageName, currentVersion, options = {}) { - - const latest = await viewOne(packageName, 'dist-tags.latest', currentVersion, { - registry: options.registry, - timeout: options.timeout, - }) - - // latest should not be deprecated - // if latest exists and latest is not a prerelease version, return it - // if latest exists and latest is a prerelease version and --pre is specified, return it - // if latest exists and latest not satisfies min version of engines.node - if (latest && filterPredicate(options)(latest)) return latest.version - - // if latest is a prerelease version and --pre is not specified - // or latest is deprecated - // find the next valid version - const versions = await viewOne(packageName, 'versions', currentVersion) - const validVersions = _.filter(versions, filterPredicate(options)) - - return _.last(validVersions.map(o => o.version)) - }, +/** + * Requests the list of peer dependencies for a specific package version + * + * @param packageName + * @param version + * @returns Promised {packageName: version} collection + */ +export const getPeerDependencies = async (packageName: string, version: Version): Promise> => { + const result = await spawnNpm( + ['view', packageName + '@' + version, 'peerDependencies'], + {}, + { rejectOnError: false }) + return result ? parseJson(result, { command: 'npm view' }) : {} +} - /** - * @param packageName - * @param currentVersion - * @param options - * @returns - */ - async newest(packageName, currentVersion, options = {}) { +/** + * @param [options] + * @param [options.cwd] + * @param [options.global] + * @param [options.prefix] + * @returns + */ +export const list = async (options: Options = {}) => { - const result = await viewManyMemoized(packageName, ['time', 'versions'], currentVersion, options) + const result = await spawnNpm('ls', options, { + ...options.cwd ? { cwd: options.cwd } : null, + rejectOnError: false + }) + const json = parseJson(result, { command: 'npm ls' }) + return cint.mapObject(json.dependencies, (name, info) => ({ + // unmet peer dependencies have a different structure + [name]: info.version || (info.required && info.required.version) + })) +} - const versionsSatisfyingNodeEngine = _.filter(result.versions, version => satisfiesNodeEngine(version, options.enginesNode)) - .map(o => o.version) +/** + * @param packageName + * @param currentVersion + * @param options + * @returns + */ +export const latest: GetVersion = async (packageName, currentVersion, options = {}) => { + + const latest = await viewOne(packageName, 'dist-tags.latest', currentVersion, { + registry: options.registry, + timeout: options.timeout, + }) as unknown as Packument // known type based on dist-tags.latest + + // latest should not be deprecated + // if latest exists and latest is not a prerelease version, return it + // if latest exists and latest is a prerelease version and --pre is specified, return it + // if latest exists and latest not satisfies min version of engines.node + if (latest && filterPredicate(options)(latest)) return latest.version + + // if latest is a prerelease version and --pre is not specified + // or latest is deprecated + // find the next valid version + // known type based on dist-tags.latest + const versions = await viewOne(packageName, 'versions', currentVersion) as Packument[] + const validVersions = _.filter(versions, filterPredicate(options)) + + return _.last(validVersions.map(o => o.version)) || null +} - const versions = Object.keys(result.time || {}).reduce((accum, key) => - accum.concat(TIME_FIELDS.includes(key) || versionsSatisfyingNodeEngine.includes(key) ? key : []), [] - ) +/** + * @param packageName + * @param currentVersion + * @param options + * @returns + */ +export const newest: GetVersion = async (packageName, currentVersion, options = {}) => { - const versionsWithTime = _.pullAll(versions, TIME_FIELDS) + const result = await viewManyMemoized(packageName, ['time', 'versions'], currentVersion, options) - return _.last(options.pre !== false - ? versions : - versionsWithTime.filter(version => !versionUtil.isPre(version)) - ) - }, + // TODO: options.enginesNode is a boolean, but satisfiesNodeEngine expects the value of engines.node + const versionsSatisfyingNodeEngine = _.filter(result.versions, version => satisfiesNodeEngine(version, options.enginesNode as any)) + .map((o: Packument) => o.version) - /** - * @param packageName - * @param currentVersion - * @param options - * @returns - */ - async greatest(packageName, currentVersion, options = {}) { - const versions = await viewOne(packageName, 'versions', currentVersion, options) - - return _.last( - // eslint-disable-next-line fp/no-mutating-methods - _.filter(versions, filterPredicate(options)) - .map(o => o.version) - .sort(versionUtil.compareVersions) - ) - }, + const versions = Object.keys(result.time || {}).reduce((accum: string[], key: string) => + accum.concat(TIME_FIELDS.includes(key) || versionsSatisfyingNodeEngine.includes(key) ? key : []), [] + ) - /** - * @param packageName - * @param currentVersion - * @param options - * @returns - */ - async minor(packageName, currentVersion, options = {}) { - const versions = await viewOne(packageName, 'versions', currentVersion, options) - return versionUtil.findGreatestByLevel( - _.filter(versions, filterPredicate(options)).map(o => o.version), - currentVersion, - 'minor' - ) - }, + const versionsWithTime = _.pullAll(versions, TIME_FIELDS) - /** - * @param packageName - * @param currentVersion - * @param options - * @returns - */ - async patch(packageName, currentVersion, options) { - const versions = await viewOne(packageName, 'versions', currentVersion, options) - return versionUtil.findGreatestByLevel( - _.filter(versions, filterPredicate(options)).map(o => o.version), - currentVersion, - 'patch' - ) - }, + return _.last(options.pre !== false + ? versions : + versionsWithTime.filter(version => !versionUtil.isPre(version)) + ) || null +} + +/** + * @param packageName + * @param currentVersion + * @param options + * @returns + */ +export const minor: GetVersion = async (packageName, currentVersion, options = {}) => { + const versions = await viewOne(packageName, 'versions', currentVersion, options) as Packument[] + return versionUtil.findGreatestByLevel( + _.filter(versions, filterPredicate(options)).map(o => o.version), + currentVersion, + 'minor' + ) +} - defaultPrefix, - packageAuthorChanged, - viewOne, - viewMany, - viewManyMemoized, +/** + * @param packageName + * @param currentVersion + * @param options + * @returns + */ +export const patch: GetVersion = async (packageName, currentVersion, options = {}) => { + const versions = await viewOne(packageName, 'versions', currentVersion, options) as Packument[] + return versionUtil.findGreatestByLevel( + _.filter(versions, filterPredicate(options)).map(o => o.version), + currentVersion, + 'patch' + ) } + +export const npm = spawnNpm diff --git a/src/package-managers/yarn.js b/src/package-managers/yarn.js index 51a17d83e..477faad38 100644 --- a/src/package-managers/yarn.js +++ b/src/package-managers/yarn.js @@ -9,7 +9,7 @@ const spawn = require('spawn-please') const libnpmconfig = require('libnpmconfig') const jsonlines = require('jsonlines') const versionUtil = require('../version-util') -const { viewOne, viewManyMemoized } = require('./npm.js') +const { viewOne, viewManyMemoized } = require('./npm') const TIME_FIELDS = ['modified', 'created'] diff --git a/src/types.ts b/src/types.ts index 25e44a243..e16702aa6 100644 --- a/src/types.ts +++ b/src/types.ts @@ -23,6 +23,17 @@ export type VersionDeclaration = string export type FilterPattern = string | string[] | RegExp | RegExp[] +export interface Packument { + name: string, + deprecated?: boolean, + engines: { + node: string, + }, + time: Index, + version: Version, + versions: Packument[], +} + export interface PackageFile { dependencies?: Index, devDependencies?: Index, @@ -31,6 +42,12 @@ export interface PackageFile { bundleDependencies?: Index, } +export interface NpmOptions { + global?: boolean, + prefix?: string, + registry?: string, +} + export interface RunOptions { /** diff --git a/src/types/cint.d.ts b/src/types/cint.d.ts index 997bc1ef0..554531bef 100644 --- a/src/types/cint.d.ts +++ b/src/types/cint.d.ts @@ -1,5 +1,6 @@ declare module 'cint' { export function filterObject(obj: Index, f: (key: string, value: T) => boolean): Index + export function mapObject(obj: Index, f: (key: string, value: T) => R): Index export function toArray(object: Index, f: (key: string, value: T) => R): R[] export function toObject(arr: T[], f: (value: T, i: number) => Index): Index } diff --git a/src/types/libnpmconfig.d.ts b/src/types/libnpmconfig.d.ts new file mode 100644 index 000000000..9eb740dc8 --- /dev/null +++ b/src/types/libnpmconfig.d.ts @@ -0,0 +1 @@ +declare module 'libnpmconfig' diff --git a/src/types/spawn-please.d.ts b/src/types/spawn-please.d.ts new file mode 100644 index 000000000..5bcf926f6 --- /dev/null +++ b/src/types/spawn-please.d.ts @@ -0,0 +1 @@ +declare module 'spawn-please' diff --git a/src/version-util.ts b/src/version-util.ts index d3c7c00f5..ee22c5bcd 100644 --- a/src/version-util.ts +++ b/src/version-util.ts @@ -207,7 +207,7 @@ export function compareVersions(a: string, b: string) { * @param level major|minor * @returns String representation of the suggested version. */ -export function findGreatestByLevel(versions: string[], current: string, level: 'major' | 'minor'): string +export function findGreatestByLevel(versions: string[], current: string, level: 'major' | 'minor' | 'patch'): string | null { if (!semver.validRange(current)) { diff --git a/tsconfig.json b/tsconfig.json index e0ad75e1b..6a0b90688 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -20,7 +20,9 @@ "noFallthroughCasesInSwitch": true, "noImplicitAny": true, "paths": { - "cint": ["./src/types/cint"] + "cint": ["./src/types/cint"], + "libnpmconfig": ["./src/types/libnpmconfig"], + "spawn-please": ["./src/types/spawn-please"] }, "resolveJsonModule": true, "outDir": "./build",