Skip to content

Commit

Permalink
feat: when packages are filter due to engine not matching, we need to…
Browse files Browse the repository at this point in the history
… print it out, just like we do in peer checks #1422
  • Loading branch information
rbnayax committed Jun 8, 2024
1 parent 0f17df2 commit 2f77ec6
Show file tree
Hide file tree
Showing 20 changed files with 267 additions and 44 deletions.
35 changes: 35 additions & 0 deletions src/lib/getEnginesNodeFromRegistry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import ProgressBar from 'progress'
import { Index } from '../types/IndexType'
import { Options } from '../types/Options'
import { Version } from '../types/Version'
import getPackageManager from './getPackageManager'

/**
* Get the engines.node versions from the NPM repository based on the version target.
*
* @param packageMap An object whose keys are package name and values are version
* @param [options={}] Options.
* @returns Promised {packageName: engines.node} collection
*/
async function getEnginesNodeFromRegistry(packageMap: Index<Version>, options: Options) {
const packageManager = getPackageManager(options, options.packageManager)
if (!packageManager.getEngines) return {}

const numItems = Object.keys(packageMap).length
let bar: ProgressBar
if (!options.json && options.loglevel !== 'silent' && options.loglevel !== 'verbose' && numItems > 0) {
bar = new ProgressBar('[:bar] :current/:total :percent', { total: numItems, width: 20 })
bar.render()
}

return Object.entries(packageMap).reduce(async (accumPromise, [pkg, version]) => {
const enginesNode = (await packageManager.getEngines!(pkg, version)).node
if (bar) {
bar.tick()
}
const accum = await accumPromise
return { ...accum, [pkg]: enginesNode }
}, Promise.resolve<Index<Version | undefined>>({}))
}

export default getEnginesNodeFromRegistry
49 changes: 49 additions & 0 deletions src/lib/getIgnoredUpgradesDueToEnginesNode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { minVersion, satisfies } from 'semver'
import { IgnoredUpgradeDueToEnginesNode } from '../types/IgnoredUpgradeDueToEnginesNode'
import { Index } from '../types/IndexType'
import { Maybe } from '../types/Maybe'
import { Options } from '../types/Options'
import { Version } from '../types/Version'
import { VersionSpec } from '../types/VersionSpec'
import getEnginesNodeFromRegistry from './getEnginesNodeFromRegistry'
import upgradePackageDefinitions from './upgradePackageDefinitions'

/** Checks if package.json min node version satisfies given package engine.node spec */
const satisfiesNodeEngine = (enginesNode: Maybe<Version>, optionsEnginesNodeMinVersion: string) =>
!enginesNode || satisfies(optionsEnginesNodeMinVersion, enginesNode)

/** Get all upgrades that are ignored due to incompatible engines.node. */
export async function getIgnoredUpgradesDueToEnginesNode(
current: Index<VersionSpec>,
upgraded: Index<VersionSpec>,
options: Options = {},
) {
if (!options.nodeEngineVersion) return {}
const optionsEnginesNodeMinVersion = minVersion(options.nodeEngineVersion)?.version
if (!optionsEnginesNodeMinVersion) return {}
const [upgradedLatestVersions] = await upgradePackageDefinitions(current, {
...options,
enginesNode: false,
nodeEngineVersion: undefined,
loglevel: 'silent',
})
const enginesNodes = await getEnginesNodeFromRegistry(upgradedLatestVersions, options)
return Object.entries(upgradedLatestVersions)
.filter(
([pkgName, newVersion]) =>
upgraded[pkgName] !== newVersion && !satisfiesNodeEngine(enginesNodes[pkgName], optionsEnginesNodeMinVersion),
)
.reduce(
(accum, [pkgName, newVersion]) => ({
...accum,
[pkgName]: {
from: current[pkgName],
to: newVersion,
enginesNode: enginesNodes[pkgName]!,
},
}),
{} as Index<IgnoredUpgradeDueToEnginesNode>,
)
}

export default getIgnoredUpgradesDueToEnginesNode
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { satisfies } from 'semver'
import { IgnoredUpgrade } from '../types/IgnoredUpgrade'
import { IgnoredUpgradeDueToPeerDeps } from '../types/IgnoredUpgradeDueToPeerDeps'
import { Index } from '../types/IndexType'
import { Options } from '../types/Options'
import { Version } from '../types/Version'
import { VersionSpec } from '../types/VersionSpec'
import upgradePackageDefinitions from './upgradePackageDefinitions'

/** Get all upgrades that are ignored due to incompatible peer dependencies. */
export async function getIgnoredUpgrades(
export async function getIgnoredUpgradesDueToPeerDeps(
current: Index<VersionSpec>,
upgraded: Index<VersionSpec>,
upgradedPeerDependencies: Index<Index<Version>>,
Expand Down Expand Up @@ -41,8 +41,8 @@ export async function getIgnoredUpgrades(
),
},
}),
{} as Index<IgnoredUpgrade>,
{} as Index<IgnoredUpgradeDueToPeerDeps>,
)
}

export default getIgnoredUpgrades
export default getIgnoredUpgradesDueToPeerDeps
30 changes: 13 additions & 17 deletions src/lib/getPeerDependenciesFromRegistry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,23 +59,19 @@ async function getPeerDependenciesFromRegistry(packageMap: Index<Version>, optio
bar.render()
}

const peerDependencies: Index<Index<string>> = Object.entries(packageMap).reduce(
async (accumPromise, [pkg, version]) => {
const dep = await packageManager.getPeerDependencies!(pkg, version)
if (bar) {
bar.tick()
}
const accum = await accumPromise
const newAcc: Index<Index<string>> = { ...accum, [pkg]: dep }
const circularData = isCircularPeer(newAcc, pkg)
if (circularData.isCircular) {
delete newAcc[pkg][circularData.offendingPackage]
}
return newAcc
},
{},
)
return peerDependencies
return Object.entries(packageMap).reduce(async (accumPromise, [pkg, version]) => {
const dep = await packageManager.getPeerDependencies!(pkg, version)
if (bar) {
bar.tick()
}
const accum = await accumPromise
const newAcc: Index<Index<string>> = { ...accum, [pkg]: dep }
const circularData = isCircularPeer(newAcc, pkg)
if (circularData.isCircular) {
delete newAcc[pkg][circularData.offendingPackage]
}
return newAcc
}, Promise.resolve<Index<Index<string>>>({}))
}

export default getPeerDependenciesFromRegistry
23 changes: 21 additions & 2 deletions src/lib/logging.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
* Loggin functions.
*/
import Table from 'cli-table3'
import { IgnoredUpgrade } from '../types/IgnoredUpgrade'
import { IgnoredUpgradeDueToEnginesNode } from '../types/IgnoredUpgradeDueToEnginesNode'
import { IgnoredUpgradeDueToPeerDeps } from '../types/IgnoredUpgradeDueToPeerDeps'
import { Index } from '../types/IndexType'
import { Options } from '../types/Options'
import { VersionResult } from '../types/VersionResult'
Expand Down Expand Up @@ -368,7 +369,7 @@ export async function printUpgrades(
}

/** Print updates that were ignored due to incompatible peer dependencies. */
export function printIgnoredUpdates(options: Options, ignoredUpdates: Index<IgnoredUpgrade>) {
export function printIgnoredUpdatesDueToPeerDeps(options: Options, ignoredUpdates: Index<IgnoredUpgradeDueToPeerDeps>) {
print(options, `\nIgnored incompatible updates (peer dependencies):\n`)
const table = renderDependencyTable(
Object.entries(ignoredUpdates).map(([pkgName, { from, to, reason }]) => {
Expand All @@ -382,3 +383,21 @@ export function printIgnoredUpdates(options: Options, ignoredUpdates: Index<Igno
)
print(options, table)
}

/** Print updates that were ignored due to incompatible engines.node. */
export function printIgnoredUpdatesDueToEnginesNode(
options: Options,
ignoredUpdates: Index<IgnoredUpgradeDueToEnginesNode>,
) {
print(options, `\nIgnored incompatible updates (engines node):\n`)
const table = renderDependencyTable(
Object.entries(ignoredUpdates).map(([pkgName, { from, to, enginesNode }]) => [
pkgName,
from,
'→',
colorizeDiff(from, to),
`reason: requires node ${enginesNode}`,
]),
)
print(options, table)
}
28 changes: 24 additions & 4 deletions src/lib/runLocal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,20 @@ import { Version } from '../types/Version'
import { VersionSpec } from '../types/VersionSpec'
import chalk from './chalk'
import getCurrentDependencies from './getCurrentDependencies'
import getIgnoredUpgrades from './getIgnoredUpgrades'
import { getIgnoredUpgradesDueToEnginesNode } from './getIgnoredUpgradesDueToEnginesNode'
import getIgnoredUpgradesDueToPeerDeps from './getIgnoredUpgradesDueToPeerDeps'
import getPackageManager from './getPackageManager'
import getPeerDependenciesFromRegistry from './getPeerDependenciesFromRegistry'
import keyValueBy from './keyValueBy'
import { print, printIgnoredUpdates, printJson, printSorted, printUpgrades, toDependencyTable } from './logging'
import {
print,
printIgnoredUpdatesDueToEnginesNode,
printIgnoredUpdatesDueToPeerDeps,
printJson,
printSorted,
printUpgrades,
toDependencyTable,
} from './logging'
import { pick } from './pick'
import programError from './programError'
import resolveDepSections from './resolveDepSections'
Expand Down Expand Up @@ -246,9 +255,20 @@ async function runLocal(
},
)
if (options.peer) {
const ignoredUpdates = await getIgnoredUpgrades(current, upgraded, upgradedPeerDependencies!, options)
const ignoredUpdates = await getIgnoredUpgradesDueToPeerDeps(
current,
upgraded,
upgradedPeerDependencies!,
options,
)
if (Object.keys(ignoredUpdates).length > 0) {
printIgnoredUpdatesDueToPeerDeps(options, ignoredUpdates)
}
}
if (options.enginesNode) {
const ignoredUpdates = await getIgnoredUpgradesDueToEnginesNode(current, upgraded, options)
if (Object.keys(ignoredUpdates).length > 0) {
printIgnoredUpdates(options, ignoredUpdates)
printIgnoredUpdatesDueToEnginesNode(options, ignoredUpdates)
}
}
}
Expand Down
13 changes: 13 additions & 0 deletions src/package-managers/npm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -665,6 +665,19 @@ export const getPeerDependencies = async (packageName: string, version: Version)
return result ? parseJson(result, { command: [...args, '--json'].join(' ') }) : {}
}

/**
* Fetches the engines list from the registry for a specific package version.
*
* @param packageName
* @param version
* @returns Promised engines collection
*/
export const getEngines = async (packageName: string, version: Version): Promise<Index<Version | undefined>> => {
const args = ['view', `${packageName}@${version}`, 'engines']
const result = await spawnNpm(args, {}, { rejectOnError: false })
return result ? parseJson<{ node?: string }>(result, { command: [...args, '--json'].join(' ') }) : {}
}

/**
* Fetches the list of all installed packages.
*
Expand Down
2 changes: 1 addition & 1 deletion src/package-managers/pnpm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,4 +107,4 @@ export default async function spawnPnpm(
return stdout
}

export { defaultPrefix, getPeerDependencies, packageAuthorChanged } from './npm'
export { defaultPrefix, getPeerDependencies, getEngines, packageAuthorChanged } from './npm'
2 changes: 2 additions & 0 deletions src/package-managers/yarn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -294,3 +294,5 @@ export const patch = withNpmConfigFromYarn(npm.patch)
export const semver = withNpmConfigFromYarn(npm.semver)

export default spawnYarn

export { getEngines } from './npm'
9 changes: 0 additions & 9 deletions src/types/IgnoredUpgrade.ts

This file was deleted.

8 changes: 8 additions & 0 deletions src/types/IgnoredUpgradeDueToEnginesNode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { Version } from './Version'

/** An object that represents an upgrade that was ignored due to mismatch of engines.node */
export interface IgnoredUpgradeDueToEnginesNode {
from: Version
to: Version
enginesNode: Version
}
9 changes: 9 additions & 0 deletions src/types/IgnoredUpgradeDueToPeerDeps.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { Index } from './IndexType'
import { Version } from './Version'

/** An object that represents an upgrade that was ignored due to peer dependencies, along with the reason. */
export interface IgnoredUpgradeDueToPeerDeps {
from: Version
to: Version
reason: Index<string>
}
4 changes: 2 additions & 2 deletions src/types/Options.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Cacher } from './Cacher'
import { Index } from './IndexType'
import { RunOptions } from './RunOptions'
import { VersionSpec } from './VersionSpec'
import { Version } from './Version'

/** Internal, normalized options for all ncu behavior. Includes RunOptions that are specified in the CLI or passed to the ncu module, as well as meta information including CLI arguments, package information, and ncurc config. */
export type Options = RunOptions & {
Expand All @@ -10,7 +10,7 @@ export type Options = RunOptions & {
cli?: boolean
distTag?: string
json?: boolean
nodeEngineVersion?: VersionSpec
nodeEngineVersion?: Version
packageData?: string
peerDependencies?: Index<any>
rcConfigPath?: string
Expand Down
3 changes: 2 additions & 1 deletion src/types/PackageFile.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Index } from './IndexType'
import { PackageFileRepository } from './PackageFileRepository'
import { Version } from './Version'
import { VersionSpec } from './VersionSpec'

type NestedVersionSpecs = {
Expand All @@ -12,7 +13,7 @@ export interface PackageFile {
devDependencies?: Index<VersionSpec>
// deno only
imports?: Index<VersionSpec>
engines?: Index<VersionSpec>
engines?: Index<Version | undefined>
name?: string
// https://nodejs.org/api/packages.html#packagemanager
packageManager?: string
Expand Down
1 change: 1 addition & 0 deletions src/types/PackageManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,5 @@ export interface PackageManager {
options?: Options,
) => Promise<boolean>
getPeerDependencies?: (packageName: string, version: Version) => Promise<Index<Version>>
getEngines?: (packageName: string, version: Version) => Promise<Index<Version | undefined>>
}
6 changes: 5 additions & 1 deletion src/types/RunOptions.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
"description": "A very generic object.",
"type": "object"
},
"Index<string|undefined>": {
"description": "A very generic object.",
"type": "object"
},
"NestedVersionSpecs": {
"additionalProperties": {
"anyOf": [
Expand All @@ -30,7 +34,7 @@
"description": "A very generic object."
},
"engines": {
"$ref": "#/definitions/Index<string>",
"$ref": "#/definitions/Index<string|undefined>",
"description": "A very generic object."
},
"imports": {
Expand Down
38 changes: 38 additions & 0 deletions test/getEnginesNodeFromRegistry.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { chalkInit } from '../src/lib/chalk'
import getEnginesNodeFromRegistry from '../src/lib/getEnginesNodeFromRegistry'
import chaiSetup from './helpers/chaiSetup'

chaiSetup()

describe('getEnginesNodeFromRegistry', function () {
it('single package', async () => {
await chalkInit()
const data = await getEnginesNodeFromRegistry({ del: '2.0.0' }, {})
data.should.deep.equal({
del: '>=0.10.0',
})
})

it('single package empty', async () => {
await chalkInit()
const data = await getEnginesNodeFromRegistry({ 'ncu-test-return-version': '1.0' }, {})
data.should.deep.equal({ 'ncu-test-return-version': undefined })
})

it('multiple packages', async () => {
await chalkInit()
const data = await getEnginesNodeFromRegistry(
{
'ncu-test-return-version': '1.0.0',
'ncu-test-peer': '1.0.0',
del: '2.0.0',
},
{},
)
data.should.deep.equal({
'ncu-test-return-version': undefined,
'ncu-test-peer': undefined,
del: '>=0.10.0',
})
})
})
Loading

0 comments on commit 2f77ec6

Please sign in to comment.