Skip to content

Commit

Permalink
feat: resolve direct deps to lowest version when resolution-mode is t…
Browse files Browse the repository at this point in the history
…ime-based (#5298)

ref #5238
  • Loading branch information
zkochan committed Sep 2, 2022
1 parent 47140dc commit 2477365
Show file tree
Hide file tree
Showing 9 changed files with 74 additions and 13 deletions.
29 changes: 28 additions & 1 deletion packages/core/test/install/timeBasedResolutionMode.ts
@@ -1,5 +1,6 @@
import { prepareEmpty } from '@pnpm/prepare'
import { addDependenciesToPackage } from '@pnpm/core'
import { addDistTag } from '@pnpm/registry-mock'
import { addDependenciesToPackage, install } from '@pnpm/core'
import { testDefaults } from '../utils'

test('time-based resolution mode', async () => {
Expand All @@ -15,3 +16,29 @@ test('time-based resolution mode', async () => {
'/@pnpm.e2e/romeo/1.0.0',
])
})

test('the lowest version of a direct dependency is installed when resolution mode is time-based', async () => {
await addDistTag({ package: '@pnpm.e2e/foo', version: '100.1.0', distTag: 'latest' })
const project = prepareEmpty()

let manifest = await install({
dependencies: {
'@pnpm.e2e/foo': '^100.0.0',
},
}, await testDefaults({ resolutionMode: 'time-based' }))

{
const lockfile = await project.readLockfile()
expect(lockfile.packages['/@pnpm.e2e/foo/100.0.0']).toBeTruthy()
}

manifest = await install(manifest, await testDefaults({ resolutionMode: 'time-based', update: true }))

{
const lockfile = await project.readLockfile()
expect(lockfile.packages['/@pnpm.e2e/foo/100.1.0']).toBeTruthy()
}
expect(manifest.dependencies).toStrictEqual({
'@pnpm.e2e/foo': '^100.1.0',
})
})
2 changes: 2 additions & 0 deletions packages/npm-resolver/src/index.ts
Expand Up @@ -107,6 +107,7 @@ export type ResolveFromNpmOptions = {
alwaysTryWorkspacePackages?: boolean
defaultTag?: string
publishedBy?: Date
pickLowestVersion?: boolean
dryRun?: boolean
lockfileDir?: string
registry: string
Expand Down Expand Up @@ -152,6 +153,7 @@ async function resolveNpm (
let pickResult!: {meta: PackageMeta, pickedPackage: PackageInRegistry | null}
try {
pickResult = await ctx.pickPackage(spec, {
pickLowestVersion: opts.pickLowestVersion,
publishedBy: opts.publishedBy,
authHeaderValue,
dryRun: opts.dryRun === true,
Expand Down
18 changes: 10 additions & 8 deletions packages/npm-resolver/src/pickPackage.ts
Expand Up @@ -13,7 +13,7 @@ import pathTemp from 'path-temp'
import pick from 'ramda/src/pick'
import renameOverwrite from 'rename-overwrite'
import toRaw from './toRaw'
import pickPackageFromMeta from './pickPackageFromMeta'
import { pickPackageFromMeta, pickVersionByVersionRange, pickLowestVersionByVersionRange } from './pickPackageFromMeta'
import { RegistryPackageSpec } from './parsePref'

export interface PackageMeta {
Expand Down Expand Up @@ -50,6 +50,7 @@ export interface PickPackageOptions {
authHeaderValue?: string
publishedBy?: Date
preferredVersionSelectors: VersionSelectors | undefined
pickLowestVersion?: boolean
registry: string
dryRun: boolean
}
Expand All @@ -68,14 +69,15 @@ export default async (
opts: PickPackageOptions
): Promise<{meta: PackageMeta, pickedPackage: PackageInRegistry | null}> => {
opts = opts || {}
const _pickPackageFromMeta = pickPackageFromMeta.bind(null, opts.pickLowestVersion ? pickLowestVersionByVersionRange : pickVersionByVersionRange)

validatePackageName(spec.name)

const cachedMeta = ctx.metaCache.get(spec.name)
if (cachedMeta != null) {
return {
meta: cachedMeta,
pickedPackage: pickPackageFromMeta(spec, opts.preferredVersionSelectors, cachedMeta, opts.publishedBy),
pickedPackage: _pickPackageFromMeta(spec, opts.preferredVersionSelectors, cachedMeta, opts.publishedBy),
}
}

Expand All @@ -84,20 +86,20 @@ export default async (
const limit = metafileOperationLimits[pkgMirror] = metafileOperationLimits[pkgMirror] || pLimit(1)

let metaCachedInStore: PackageMeta | null | undefined
if (ctx.offline === true || ctx.preferOffline) {
if (ctx.offline === true || ctx.preferOffline === true || opts.pickLowestVersion) {
metaCachedInStore = await limit(async () => loadMeta(pkgMirror))

if (ctx.offline) {
if (metaCachedInStore != null) return {
meta: metaCachedInStore,
pickedPackage: pickPackageFromMeta(spec, opts.preferredVersionSelectors, metaCachedInStore, opts.publishedBy),
pickedPackage: _pickPackageFromMeta(spec, opts.preferredVersionSelectors, metaCachedInStore, opts.publishedBy),
}

throw new PnpmError('NO_OFFLINE_META', `Failed to resolve ${toRaw(spec)} in package mirror ${pkgMirror}`)
}

if (metaCachedInStore != null) {
const pickedPackage = pickPackageFromMeta(spec, opts.preferredVersionSelectors, metaCachedInStore, opts.publishedBy)
const pickedPackage = _pickPackageFromMeta(spec, opts.preferredVersionSelectors, metaCachedInStore, opts.publishedBy)
if (pickedPackage) {
return {
meta: metaCachedInStore,
Expand All @@ -123,7 +125,7 @@ export default async (
if (metaCachedInStore?.cachedAt && new Date(metaCachedInStore.cachedAt) >= opts.publishedBy) {
return {
meta: metaCachedInStore,
pickedPackage: pickPackageFromMeta(spec, opts.preferredVersionSelectors, metaCachedInStore, opts.publishedBy),
pickedPackage: _pickPackageFromMeta(spec, opts.preferredVersionSelectors, metaCachedInStore, opts.publishedBy),
}
}
}
Expand All @@ -148,7 +150,7 @@ export default async (
}
return {
meta,
pickedPackage: pickPackageFromMeta(spec, opts.preferredVersionSelectors, meta, opts.publishedBy),
pickedPackage: _pickPackageFromMeta(spec, opts.preferredVersionSelectors, meta, opts.publishedBy),
}
} catch (err: any) { // eslint-disable-line
err.spec = spec
Expand All @@ -158,7 +160,7 @@ export default async (
logger.debug({ message: `Using cached meta from ${pkgMirror}` })
return {
meta,
pickedPackage: pickPackageFromMeta(spec, opts.preferredVersionSelectors, meta, opts.publishedBy),
pickedPackage: _pickPackageFromMeta(spec, opts.preferredVersionSelectors, meta, opts.publishedBy),
}
}
}
Expand Down
21 changes: 18 additions & 3 deletions packages/npm-resolver/src/pickPackageFromMeta.ts
Expand Up @@ -4,7 +4,15 @@ import semver from 'semver'
import { RegistryPackageSpec } from './parsePref'
import { PackageInRegistry, PackageMeta } from './pickPackage'

export default function (
export type PickVersionByVersionRange = (
meta: PackageMeta,
versionRange: string,
preferredVerSels?: VersionSelectors,
publishedBy?: Date
) => string | null

export function pickPackageFromMeta (
pickVersionByVersionRangeFn: PickVersionByVersionRange,
spec: RegistryPackageSpec,
preferredVersionSelectors: VersionSelectors | undefined,
meta: PackageMeta,
Expand All @@ -20,7 +28,7 @@ export default function (
version = meta['dist-tags'][spec.fetchSpec]
break
case 'range':
version = pickVersionByVersionRange(meta, spec.fetchSpec, preferredVersionSelectors, publishedBy)
version = pickVersionByVersionRangeFn(meta, spec.fetchSpec, preferredVersionSelectors, publishedBy)
break
}
if (!version) return null
Expand All @@ -42,7 +50,14 @@ export default function (
}
}

function pickVersionByVersionRange (
export function pickLowestVersionByVersionRange (
meta: PackageMeta,
versionRange: string
) {
return semver.minSatisfying(Object.keys(meta.versions), versionRange, true)
}

export function pickVersionByVersionRange (
meta: PackageMeta,
versionRange: string,
preferredVerSels?: VersionSelectors,
Expand Down
1 change: 1 addition & 0 deletions packages/package-requester/src/packageRequester.ts
Expand Up @@ -172,6 +172,7 @@ async function resolveAndFetch (
alwaysTryWorkspacePackages: options.alwaysTryWorkspacePackages,
defaultTag: options.defaultTag,
publishedBy: options.publishedBy,
pickLowestVersion: options.pickLowestVersion,
lockfileDir: options.lockfileDir,
preferredVersions: options.preferredVersions,
preferWorkspacePackages: options.preferWorkspacePackages,
Expand Down
12 changes: 11 additions & 1 deletion packages/resolve-dependencies/src/resolveDependencies.ts
Expand Up @@ -233,6 +233,7 @@ interface ResolvedDependenciesOptions {
preferredDependencies?: ResolvedDependencies
proceed: boolean
publishedBy?: Date
pickLowestVersion?: boolean
resolvedDependencies?: ResolvedDependencies
updateDepth: number
prefix: string
Expand Down Expand Up @@ -299,6 +300,7 @@ interface ResolvedDependenciesResult {
}

export interface ImporterToResolve {
updatePackageManifest: boolean
preferredVersions: PreferredVersions
parentPkgAliases: ParentPkgAliases
wantedDependencies: Array<WantedDependency & { updateDepth?: number }>
Expand All @@ -322,6 +324,7 @@ async function resolveDependenciesOfImporters (
registries: ctx.registries,
resolvedDependencies: options.resolvedDependencies,
}))
const pickLowestVersion = ctx.resolutionMode === 'time-based'
const resolveResults = await Promise.all(
zipWith(async (extendedWantedDeps, importer) => {
const postponedResolutionsQueue: PostponedResolutionFunction[] = []
Expand All @@ -330,7 +333,11 @@ async function resolveDependenciesOfImporters (
extendedWantedDeps.map((extendedWantedDep) => resolveDependenciesOfDependency(
ctx,
importer.preferredVersions,
{ ...importer.options, parentPkgAliases: importer.parentPkgAliases },
{
...importer.options,
parentPkgAliases: importer.parentPkgAliases,
pickLowestVersion: pickLowestVersion && !importer.updatePackageManifest,
},
extendedWantedDep
))
)).forEach(({ resolveDependencyResult, postponedResolution }) => {
Expand Down Expand Up @@ -554,6 +561,7 @@ async function resolveDependenciesOfDependency (
parentPkgAliases: options.parentPkgAliases,
preferredVersions,
currentPkg: extendedWantedDep.infoFromLockfile ?? undefined,
pickLowestVersion: options.pickLowestVersion,
prefix: options.prefix,
proceed: extendedWantedDep.proceed || updateShouldContinue || ctx.updatedSet.size > 0,
publishedBy: options.publishedBy,
Expand Down Expand Up @@ -848,6 +856,7 @@ interface ResolveDependencyOptions {
prefix: string
proceed: boolean
publishedBy?: Date
pickLowestVersion?: boolean
update: boolean
updateDepth: number
}
Expand Down Expand Up @@ -904,6 +913,7 @@ async function resolveDependency (
expectedPkg: currentPkg,
defaultTag: ctx.defaultTag,
publishedBy: options.publishedBy,
pickLowestVersion: options.pickLowestVersion,
downloadPriority: -options.currentDepth,
lockfileDir: ctx.lockfileDir,
preferredVersions: options.preferredVersions,
Expand Down
2 changes: 2 additions & 0 deletions packages/resolve-dependencies/src/resolveDependencyTree.ts
Expand Up @@ -52,6 +52,7 @@ export interface Importer<T> {
}

export interface ImporterToResolveGeneric<T> extends Importer<T> {
updatePackageManifest: boolean
hasRemovedDependencies?: boolean
preferredVersions?: PreferredVersions
wantedDependencies: Array<T & WantedDependency & { updateDepth: number }>
Expand Down Expand Up @@ -150,6 +151,7 @@ export default async function<T> (
prefix: importer.rootDir,
}
return {
updatePackageManifest: importer.updatePackageManifest,
parentPkgAliases: fromPairs(
importer.wantedDependencies.filter(({ alias }) => alias).map(({ alias }) => [alias, true])
) as ParentPkgAliases,
Expand Down
1 change: 1 addition & 0 deletions packages/resolver-base/src/index.ts
Expand Up @@ -63,6 +63,7 @@ export interface PreferredVersions {
export interface ResolveOptions {
alwaysTryWorkspacePackages?: boolean
defaultTag?: string
pickLowestVersion?: boolean
publishedBy?: Date
projectDir: string
lockfileDir: string
Expand Down
1 change: 1 addition & 0 deletions packages/store-controller-types/src/index.ts
Expand Up @@ -95,6 +95,7 @@ export interface RequestPackageOptions {
*/
expectedPkg?: PkgNameVersion
defaultTag?: string
pickLowestVersion?: boolean
publishedBy?: Date
downloadPriority: number
projectDir: string
Expand Down

0 comments on commit 2477365

Please sign in to comment.