Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: resolve direct deps to lowest version when resolution-mode is time-based #5298

Merged
merged 3 commits into from Sep 2, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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