Skip to content

Commit

Permalink
Add target=@[dist-tag] option support (#1134)
Browse files Browse the repository at this point in the history
Co-authored-by: Иван Малюгин <[email protected]>
Co-authored-by: Raine Revere <[email protected]>
  • Loading branch information
3 people authored Jun 16, 2022
1 parent 56c9790 commit 0b9dedc
Show file tree
Hide file tree
Showing 12 changed files with 357 additions and 142 deletions.
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,8 @@ ncu "/^(?!react-).*$/" # windows
- `0.1.0``0.2.1`
- With `--target patch`, only update patch:
- `0.1.0``0.1.2`
- With `--target @next`, update to the version published on the `next` tag:
- `0.1.0` -> `0.1.1-next.1`

## Options

Expand Down Expand Up @@ -206,10 +208,10 @@ ncu "/^(?!react-).*$/" # windows
package info. (default: 3)
-s, --silent Don't output anything (--loglevel silent).
--stdin Read package.json from stdin.
-t, --target <value> Target version or function that returns version
to upgrade to: latest, newest, greatest, minor,
patch. Run "ncu --help --target" for details.
(default: "latest")
-t, --target <value> Determines the version to upgrade to: latest,
newest, greatest, minor, patch, @[tag], or
[function]. Run "ncu --help --target" for
details. (default: "latest")
--timeout <ms> Global timeout in milliseconds. (default: no
global timeout and 30 seconds per
npm-registry-fetch)
Expand Down
17 changes: 7 additions & 10 deletions src/cli-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,24 +32,20 @@ const getHelpTargetTable = (): string => {

table.push([
'greatest',
`Upgrade to the highest version number, regardless of release date or tag.
`Upgrade to the highest version number published, regardless of release date or tag.
Includes prereleases.`,
])
table.push([
'latest',
`Upgrade to whatever the package's "latest" git tag points to. It's usually the
non-prerelease version with the highest version number, but is ultimately decided
by each project's maintainers. Default.`,
])
table.push(['latest', `Upgrade to whatever the package's "latest" git tag points to. Excludes pre is specified.`])
table.push(['minor', 'Upgrade to the highest minor version without bumping the major version.'])
table.push([
'newest',
`Upgrade to the version with the most recent publish date, even if there are
other version numbers that are higher. Includes prereleases.`,
])
table.push(['patch', `Upgrade to the highest patch version without bumping the minor or major versions.`])
table.push(['@[tag]', `Upgrade to the version published to a specific tag, e.g. 'next' or 'beta'.`])

return `Set the target version that is upgraded to. (default: "latest")
return `Determines the version to upgrade to. (default: "latest")
${table.toString()}
Expand Down Expand Up @@ -354,9 +350,10 @@ As a comparison: without using the --peer option, ncu will suggest the latest ve
short: 't',
arg: 'value',
description:
'Target version or function that returns version to upgrade to: latest, newest, greatest, minor, patch. Run "ncu --help --target" for details. (default: "latest")',
'Determines the version to upgrade to: latest, newest, greatest, minor, patch, @[tag], or [function]. Run "ncu --help --target" for details. (default: "latest")',
help: getHelpTargetTable(),
type: `'latest' | 'newest' | 'greatest' | 'minor' | 'patch' | TargetFunction`,
// eslint-disable-next-line no-template-curly-in-string
type: `'latest' | 'newest' | 'greatest' | 'minor' | 'patch' | ${'`@${string}`'} | TargetFunction`,
},
{
long: 'timeout',
Expand Down
8 changes: 5 additions & 3 deletions src/lib/isUpgradeable.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Version } from '../types/Version'
import { VersionSpec } from '../types/VersionSpec'
import { fixPseudoVersion, stringify, isWildCard } from '../version-util'
import { fixPseudoVersion, stringify, isWildCard, isComparable } from '../version-util'
import * as semver from 'semver'
import semverutils from 'semver-utils'

Expand Down Expand Up @@ -40,8 +40,10 @@ function isUpgradeable(current: VersionSpec, latest: Version) {
return (
isValidCurrent &&
isValidLatest &&
!semver.satisfies(latestNormalized, range.operator === '<' ? current : version) &&
!semver.ltr(latestNormalized, version)
// allow an upgrade if two prerelease versions can't be compared by semver
(!isComparable(latestNormalized, version) ||
(!semver.satisfies(latestNormalized, range.operator === '<' ? current : version) &&
!semver.ltr(latestNormalized, version)))
)
}

Expand Down
119 changes: 54 additions & 65 deletions src/lib/queryVersions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import { VersionSpec } from '../types/VersionSpec'
async function queryVersions(packageMap: Index<VersionSpec>, options: Options = {}): Promise<Index<VersionResult>> {
const target = options.target || 'latest'
const packageList = Object.keys(packageMap)
const packageManager = getPackageManager(options.packageManager)
const globalPackageManager = getPackageManager(options.packageManager)

let bar: ProgressBar
if (!options.json && options.loglevel !== 'silent' && options.loglevel !== 'verbose' && packageList.length > 0) {
Expand All @@ -42,79 +42,68 @@ async function queryVersions(packageMap: Index<VersionSpec>, options: Options =
async function getPackageVersionProtected(dep: VersionSpec): Promise<VersionResult> {
const npmAlias = parseNpmAlias(packageMap[dep])
const [name, version] = npmAlias || [dep, packageMap[dep]]
const targetResult = typeof target === 'string' ? target : target(name, parseRange(version))
let targetResult = typeof target === 'string' ? target : target(name, parseRange(version))
let distTag = 'latest'

if (targetResult[0] === '@') {
distTag = targetResult.slice(1)
targetResult = 'distTag'
}

let versionNew: Version | null = null
const isGithubDependency = isGithubUrl(packageMap[dep])

// use gitTags package manager for git urls
if (isGithubUrl(packageMap[dep])) {
// override packageManager and getPackageVersion just for this dependency
const packageManager = packageManagers.gitTags
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 github urls. Supported version targets are: ${packageManagerSupportedVersionTargets.join(
', ',
)}`,
),
)
}
// use gitTags package manager for git urls (for this dependency only)
const packageManager = isGithubDependency ? packageManagers.gitTags : globalPackageManager
const packageManagerName = isGithubDependency ? 'github urls' : options.packageManager || 'npm'

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 ${packageManagerName}. Supported version targets are: ` +
packageManagerSupportedVersionTargets.join(', ') +
(!isGithubDependency ? ' and custom distribution tags, following "@" (example: @next)' : ''),
),
)
}

try {
versionNew = await getPackageVersion(name, version, {
...options,
// upgrade prereleases to newer prereleases by default
pre: options.pre != null ? options.pre : isPre(version),
distTag,
pre: options.pre != null ? options.pre : distTag !== 'latest' || isPre(version),
retry: options.retry ?? 2,
})
} 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,
// upgrade prereleases to newer prereleases by default
pre: options.pre != null ? options.pre : isPre(version),
retry: options.retry ?? 2,
})
versionNew = npmAlias && versionNew ? createNpmAlias(name, versionNew) : versionNew
} catch (err: any) {
const errorMessage = err ? (err.message || err).toString() : ''
if (errorMessage.match(/E404|ENOTFOUND|404 Not Found/i)) {
return {
error: `${errorMessage.replace(
/ - Not found$/i,
'',
)}. Either your internet connection is down or unstable and all ${
options.retry
} retry attempts failed, or the registry is not accessible, or the package does not exist.`,
}
} else {
// print a hint about the --timeout option for network timeout errors
if (!process.env.NCU_TESTS && /(Response|network) timeout/i.test(errorMessage)) {
console.error(
'\n\n' +
chalk.red(
'FetchError: Request Timeout. npm-registry-fetch defaults to 30000 (30 seconds). Try setting the --timeout option (in milliseconds) to override this.',
) +
'\n',
)
}

throw err
versionNew = !isGithubDependency && npmAlias && versionNew ? createNpmAlias(name, versionNew) : versionNew
} catch (err: any) {
const errorMessage = err ? (err.message || err).toString() : ''
if (errorMessage.match(/E404|ENOTFOUND|404 Not Found/i)) {
return {
error: `${errorMessage.replace(
/ - Not found$/i,
'',
)}. Either your internet connection is down or unstable and all ${
options.retry
} retry attempts failed, or the registry is not accessible, or the package does not exist.`,
}
} else {
// print a hint about the --timeout option for network timeout errors
if (!process.env.NCU_TESTS && /(Response|network) timeout/i.test(errorMessage)) {
console.error(
'\n\n' +
chalk.red(
'FetchError: Request Timeout. npm-registry-fetch defaults to 30000 (30 seconds). Try setting the --timeout option (in milliseconds) to override this.',
) +
'\n',
)
}

throw err
}
}

Expand Down
34 changes: 30 additions & 4 deletions src/package-managers/npm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,8 @@ export async function defaultPrefix(options: Options): Promise<string | undefine
}

/**
* Fetches the highest version number, regardless of tag or publish time.
*
* @param packageName
* @param currentVersion
* @param options
Expand All @@ -331,7 +333,7 @@ export const greatest: GetVersion = async (packageName, currentVersion, options
}

/**
* Requests the list of peer dependencies for a specific package version
* Fetches the list of peer dependencies for a specific package version.
*
* @param packageName
* @param version
Expand All @@ -344,6 +346,8 @@ export const getPeerDependencies = async (packageName: string, version: Version)
}

/**
* Fetches the list of all installed packages.
*
* @param [options]
* @param [options.cwd]
* @param [options.global]
Expand All @@ -365,13 +369,15 @@ export const list = async (options: Options = {}) => {
}

/**
* Fetches the version of a package published to options.distTag.
*
* @param packageName
* @param currentVersion
* @param options
* @returns
*/
export const latest: GetVersion = async (packageName, currentVersion, options = {}) => {
const latest = (await viewOne(packageName, 'dist-tags.latest', currentVersion, {
export const distTag: GetVersion = async (packageName, currentVersion, options: Options = {}) => {
const revision = (await viewOne(packageName, `dist-tags.${options.distTag}`, currentVersion, {
registry: options.registry,
timeout: options.timeout,
retry: options.retry,
Expand All @@ -381,7 +387,10 @@ export const latest: GetVersion = async (packageName, currentVersion, options =
// 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 (revision && filterPredicate(options)(revision)) return revision.version

// If we use a custom dist-tag, we do not want to get other 'pre' versions, just the ones from this dist-tag
if (options.distTag && options.distTag !== 'latest') return null

// if latest is a prerelease version and --pre is not specified
// or latest is deprecated
Expand All @@ -391,6 +400,19 @@ export const latest: GetVersion = async (packageName, currentVersion, options =
}

/**
* Fetches the version published to the latest tag.
*
* @param packageName
* @param currentVersion
* @param options
* @returns
*/
export const latest: GetVersion = async (packageName: string, currentVersion: Version, options: Options = {}) =>
distTag(packageName, currentVersion, { ...options, distTag: 'latest' })

/**
* Fetches the most recently published version, regardless of version number.
*
* @param packageName
* @param currentVersion
* @param options
Expand All @@ -417,6 +439,8 @@ export const newest: GetVersion = async (packageName, currentVersion, options =
}

/**
* Fetches the highest version with the same major version as currentVersion.
*
* @param packageName
* @param currentVersion
* @param options
Expand All @@ -432,6 +456,8 @@ export const minor: GetVersion = async (packageName, currentVersion, options = {
}

/**
* Fetches the highest version with the same minor and major version as currentVersion.
*
* @param packageName
* @param currentVersion
* @param options
Expand Down
Loading

0 comments on commit 0b9dedc

Please sign in to comment.