Skip to content

Commit

Permalink
Print reason when node engine does not match (raineorshine#1424)
Browse files Browse the repository at this point in the history
  • Loading branch information
rbnayax authored Jul 7, 2024
1 parent f34082d commit 9694c21
Show file tree
Hide file tree
Showing 17 changed files with 283 additions and 42 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 { VersionSpec } from '../types/VersionSpec'
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<VersionSpec>, 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, options)).node
if (bar) {
bar.tick()
}
const accum = await accumPromise
return { ...accum, [pkg]: enginesNode }
}, Promise.resolve<Index<VersionSpec | 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<VersionSpec>, optionsEnginesNodeMinVersion: Version) =>
!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
33 changes: 32 additions & 1 deletion src/package-managers/npm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ const fetchPartialPackument = async (
fields: (keyof Packument)[],
tag: string | null,
opts: npmRegistryFetch.FetchOptions = {},
version?: Version,
): Promise<Partial<Packument>> => {
const corgiDoc = 'application/vnd.npm.install-v1+json; q=1.0, application/json; q=0.8, */*'
const fullDoc = 'application/json'
Expand All @@ -58,7 +59,10 @@ const fetchPartialPackument = async (
accept: opts.fullMetadata ? fullDoc : corgiDoc,
...opts.headers,
}
const url = path.join(registry, name)
let url = path.join(registry, name)
if (version) {
url = path.join(url, version)
}
const fetchOptions = {
...opts,
headers,
Expand Down Expand Up @@ -665,6 +669,33 @@ 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,
options: Options = {},
npmConfigLocal?: NpmConfig,
): Promise<Index<VersionSpec | undefined>> => {
const result = await fetchPartialPackument(
packageName,
[`engines`],
null,
{
...npmConfigLocal,
...npmConfig,
...(options.registry ? { registry: options.registry, silent: true } : null),
},
version,
)
return result.engines || {}
}

/**
* 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: 1 addition & 1 deletion src/package-managers/yarn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -295,4 +295,4 @@ export const semver = withNpmConfigFromYarn(npm.semver)

export default spawnYarn

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

This file was deleted.

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

/** An object that represents an upgrade that was ignored due to mismatch of engines.node */
export interface IgnoredUpgradeDueToEnginesNode {
from: Version
to: Version
enginesNode: VersionSpec
}
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>
}
7 changes: 7 additions & 0 deletions src/types/PackageManager.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { GetVersion } from './GetVersion'
import { Index } from './IndexType'
import { NpmConfig } from './NpmConfig'
import { Options } from './Options'
import { Version } from './Version'
import { VersionSpec } from './VersionSpec'
Expand All @@ -21,4 +22,10 @@ export interface PackageManager {
options?: Options,
) => Promise<boolean>
getPeerDependencies?: (packageName: string, version: Version) => Promise<Index<Version>>
getEngines?: (
packageName: string,
version: Version,
options: Options,
npmConfigLocal?: NpmConfig,
) => Promise<Index<VersionSpec | undefined>>
}
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 9694c21

Please sign in to comment.