From d335923b7a503897d4f913ae6525d366d26eb971 Mon Sep 17 00:00:00 2001 From: Jannis Hell Date: Mon, 28 Feb 2022 17:23:10 +0100 Subject: [PATCH] Target as a function (#1052) Co-authored-by: Raine Revere --- src/cli-options.ts | 20 +++++++++++++++- src/lib/queryVersions.ts | 22 +++++++++-------- src/types.ts | 9 ++++--- test/index.test.ts | 52 +++++++++++++++++++++++++++++++++++++++- 4 files changed, 88 insertions(+), 15 deletions(-) diff --git a/src/cli-options.ts b/src/cli-options.ts index 23dfbf5a..6dfe4c96 100644 --- a/src/cli-options.ts +++ b/src/cli-options.ts @@ -39,7 +39,24 @@ by each project's maintainers. Default.`]) other version numbers that are higher. Includes prereleases.`]) table.push(['patch', `Upgrade to the highest patch version without bumping the minor or major versions.`]) - return `Set the target version that is upgraded to (default: "latest").\n\n${table.toString()}` + return `Set the target version that is upgraded to (default: "latest"). + +${table.toString()} + +You can also specify a custom function in your .ncurc.js file, or when importing npm-check-updates: + + ${chalk.gray(`/** Custom target. + @param dependencyName The name of the dependency. + @param parsedVersion A parsed Semver object from semver-utils. + (See https://git.coolaj86.com/coolaj86/semver-utils.js#semverutils-parse-semverstring) + @returns One of the valid target values (specified in the table above). + */`)} + ${chalk.cyan('target')}: (dependencyName, [{ semver, version, operator, major, minor, patch, release, build }]) ${chalk.cyan('=>')} { + ${chalk.red('if')} (major ${chalk.red('===')} ${chalk.blue('0')}) ${chalk.red('return')} ${chalk.yellow('\'minor\'')} + ${chalk.red('return')} ${chalk.yellow('\'latest\'')} + } + +` } // store CLI options separately from bin file so that they can be used to build type definitions @@ -307,6 +324,7 @@ As a comparison: without using the --peer option, ncu will suggest the latest ve arg: 'value', description: 'Target version to upgrade to: latest, newest, greatest, minor, patch. Run "ncu --help --target" for details.` (default: "latest")', help: getHelpTargetTable(), + type: 'string | TargetFunction', }, { long: 'timeout', diff --git a/src/lib/queryVersions.ts b/src/lib/queryVersions.ts index d605a288..6c4c3381 100644 --- a/src/lib/queryVersions.ts +++ b/src/lib/queryVersions.ts @@ -3,6 +3,7 @@ import cint from 'cint' import chalk from 'chalk' import pMap from 'p-map' import ProgressBar from 'progress' +import { parseRange } from 'semver-utils' import { supportedVersionTargets } from '../constants' import getPackageManager from './getPackageManager' import packageManagers from '../package-managers' @@ -28,14 +29,6 @@ async function queryVersions(packageMap: Index, options: Options = bar.render() } - // set the getPackageVersion function from options.target - // TODO: Remove "as GetVersion" and fix types - const getPackageVersion = packageManager[target as keyof typeof packageManager] as GetVersion - if (!getPackageVersion) { - const packageManagerSupportedVersionTargets = supportedVersionTargets.filter(t => t in packageManager) - return Promise.reject(new Error(`Unsupported target "${target}" for ${options.packageManager || 'npm'}. Supported version targets are: ${packageManagerSupportedVersionTargets.join(', ')}`)) - } - /** * Ignore 404 errors from getPackageVersion by having them return `null` * instead of rejecting. @@ -47,6 +40,7 @@ async function queryVersions(packageMap: Index, options: Options = const npmAlias = parseNpmAlias(packageMap[dep]) const [name, version] = npmAlias || [dep, packageMap[dep]] + const targetResult = typeof target === 'string' ? target : target(name, parseRange(version)) let versionNew: Version | null = null @@ -55,11 +49,11 @@ async function queryVersions(packageMap: Index, options: Options = // override packageManager and getPackageVersion just for this dependency const packageManager = packageManagers.gitTags - const getPackageVersion = packageManager[target as keyof typeof packageManager] as GetVersion + const getPackageVersion = packageManager[targetResult as keyof typeof packageManager] as GetVersion if (!getPackageVersion) { const packageManagerSupportedVersionTargets = supportedVersionTargets.filter(t => t in packageManager) - return Promise.reject(new Error(`Unsupported target "${target}" for github urls. Supported version targets are: ${packageManagerSupportedVersionTargets.join(', ')}`)) + return Promise.reject(new Error(`Unsupported target "${targetResult}" for github urls. Supported version targets are: ${packageManagerSupportedVersionTargets.join(', ')}`)) } versionNew = await getPackageVersion(name, version, { ...options, @@ -68,6 +62,14 @@ async function queryVersions(packageMap: Index, options: Options = }) } else { + // set the getPackageVersion function from options.target + // TODO: Remove "as GetVersion" and fix types + const getPackageVersion = packageManager[targetResult as keyof typeof packageManager] as GetVersion + if (!getPackageVersion) { + const packageManagerSupportedVersionTargets = supportedVersionTargets.filter(t => t in packageManager) + return Promise.reject(new Error(`Unsupported target "${targetResult}" for ${options.packageManager || 'npm'}. Supported version targets are: ${packageManagerSupportedVersionTargets.join(', ')}`)) + } + try { versionNew = await getPackageVersion(name, version, { ...options, diff --git a/src/types.ts b/src/types.ts index 54e5e299..0fe8e429 100644 --- a/src/types.ts +++ b/src/types.ts @@ -25,9 +25,12 @@ export type Version = string export type VersionSpec = string export type VersionLevel = 'major' | 'minor' | 'patch' -type FilterFunction = (packageName: string, version: SemVer[]) => boolean +export type FilterFunction = (packageName: string, versionRange: SemVer[]) => boolean export type FilterRejectPattern = string | string[] | RegExp | RegExp[] | FilterFunction +export type TargetFunction = (packageName: string, versionRange: SemVer[]) => string +export type Target = string | TargetFunction + export interface Packument { name: string, deprecated?: boolean, @@ -287,9 +290,9 @@ export interface RunOptions { silent?: boolean, /** - * Target version to upgrade to: latest, newest, greatest, minor, patch. Run "ncu --help --target" for details.` (default: "latest") + * Target function that returns version to upgrade to: latest, newest, greatest, minor, patch. Run "ncu --help --target" for details.` (default: "latest") */ - target?: string, + target?: Target, /** * Global timeout in milliseconds. (default: no global timeout and 30 seconds per npm-registry-fetch). diff --git a/test/index.test.ts b/test/index.test.ts index be0ff8dd..550aeb0c 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -4,6 +4,7 @@ import chai from 'chai' import chaiAsPromised from 'chai-as-promised' import chaiString from 'chai-string' import * as ncu from '../src/' +import { FilterFunction, TargetFunction } from '../src/types' chai.should() chai.use(chaiAsPromised) @@ -370,11 +371,60 @@ describe('run', function () { ;(pkgData as any).chalk.should.equal('2.4.2') }) - it('skip non-semver versions with --target', async () => { + it('skip non-semver versions with --target patch', async () => { const pkgData = await ncu.run({ target: 'patch', packageData: '{ "dependencies": { "test": "github:a/b" } }' }) pkgData!.should.not.have.property('test') }) + it('custom target function to mimic semver', async () => { + // eslint-disable-next-line jsdoc/require-jsdoc + const target:TargetFunction = (name, [{ operator }]) => operator === '^' ? 'minor' : operator === '~' ? 'patch' : 'latest' + const pkgData = await ncu.run({ + target, + packageData: JSON.stringify({ + dependencies: { + 'eslint-plugin-jsdoc': '~36.1.0', + jsonlines: '0.1.0', + juggernaut: '1.0.0', + mocha: '^8.3.2', + } + }) + }) + pkgData!.should.have.property('eslint-plugin-jsdoc') + ;(pkgData as any)['eslint-plugin-jsdoc'].should.equal('~36.1.1') + pkgData!.should.have.property('jsonlines') + ;(pkgData as any).jsonlines.should.equal('0.1.1') + pkgData!.should.have.property('juggernaut') + ;(pkgData as any).juggernaut.should.equal('2.1.1') + pkgData!.should.have.property('mocha') + ;(pkgData as any).mocha.should.equal('^8.4.0') + }) + + it('custom target and filter function to mimic semver', async () => { + // eslint-disable-next-line jsdoc/require-jsdoc + const target:TargetFunction = (name, [{ operator }]) => operator === '^' ? 'minor' : operator === '~' ? 'patch' : 'latest' + // eslint-disable-next-line jsdoc/require-jsdoc + const filter:FilterFunction = (_, [{ major, operator }]) => !(major === '0' || major === undefined || operator === undefined) + const pkgData = await ncu.run({ + filter, + target, + packageData: JSON.stringify({ + dependencies: { + 'eslint-plugin-jsdoc': '~36.1.0', + jsonlines: '0.1.0', + juggernaut: '1.0.0', + mocha: '^8.3.2', + } + }) + }) + pkgData!.should.have.property('eslint-plugin-jsdoc') + ;(pkgData as any)['eslint-plugin-jsdoc'].should.equal('~36.1.1') + pkgData!.should.not.have.property('jsonlines') + pkgData!.should.not.have.property('juggernaut') + pkgData!.should.have.property('mocha') + ;(pkgData as any).mocha.should.equal('^8.4.0') + }) + }) // end 'target' describe('filterVersion', () => {