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

fix: add fallback for time-based resolution #5302

Merged
merged 4 commits into from Sep 3, 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
5 changes: 5 additions & 0 deletions .changeset/fast-games-grow.md
@@ -0,0 +1,5 @@
---
"@pnpm/npm-resolver": patch
---

Pick a version even if it was published after the given date (if there is no better match).
25 changes: 21 additions & 4 deletions packages/npm-resolver/src/pickPackage.ts
Expand Up @@ -55,6 +55,17 @@ export interface PickPackageOptions {
dryRun: boolean
}

function pickPackageFromMetaUsingTime (
spec: RegistryPackageSpec,
preferredVersionSelectors: VersionSelectors | undefined,
meta: PackageMeta,
publishedBy?: Date
) {
const pickedPackage = pickPackageFromMeta(pickVersionByVersionRange, spec, preferredVersionSelectors, meta, publishedBy)
if (pickedPackage) return pickedPackage
return pickPackageFromMeta(pickLowestVersionByVersionRange, spec, preferredVersionSelectors, meta, publishedBy)
}

export default async (
ctx: {
fetch: (pkgName: string, registry: string, authHeaderValue?: string) => Promise<PackageMeta>
Expand All @@ -69,7 +80,10 @@ export default async (
opts: PickPackageOptions
): Promise<{meta: PackageMeta, pickedPackage: PackageInRegistry | null}> => {
opts = opts || {}
const _pickPackageFromMeta = pickPackageFromMeta.bind(null, opts.pickLowestVersion ? pickLowestVersionByVersionRange : pickVersionByVersionRange)
const _pickPackageFromMeta =
opts.publishedBy
? pickPackageFromMetaUsingTime
: (pickPackageFromMeta.bind(null, opts.pickLowestVersion ? pickLowestVersionByVersionRange : pickVersionByVersionRange))

validatePackageName(spec.name)

Expand Down Expand Up @@ -123,9 +137,12 @@ export default async (
if (opts.publishedBy) {
metaCachedInStore = metaCachedInStore ?? await limit(async () => loadMeta(pkgMirror))
if (metaCachedInStore?.cachedAt && new Date(metaCachedInStore.cachedAt) >= opts.publishedBy) {
return {
meta: metaCachedInStore,
pickedPackage: _pickPackageFromMeta(spec, opts.preferredVersionSelectors, metaCachedInStore, opts.publishedBy),
const pickedPackage = _pickPackageFromMeta(spec, opts.preferredVersionSelectors, metaCachedInStore, opts.publishedBy)
if (pickedPackage) {
return {
meta: metaCachedInStore,
pickedPackage,
}
}
}
}
Expand Down
23 changes: 23 additions & 0 deletions packages/npm-resolver/test/fixtures/bad-dates.json
@@ -0,0 +1,23 @@
{
"versions": {
"1.0.0": {
"name": "bad-dates",
"version": "1.0.0",
"_hasShrinkwrap": false,
"directories": {},
"dist": {
"integrity": "sha512-9cI+DmhNhA8ioT/3EJFnt0s1yehnAECyIOXdT+2uQGzcEEBaj8oNmVWj33+ZjPndMIFRQh8JeJlEu1uv5/J7pQ==",
"shasum": "88009856b64a2f1eb7d8bb0179418424ae0452cb",
"tarball": "https://registry.npmjs.org/bad-dates/-/bad-dates-1.0.0.tgz"
}
}
},
"time": {
"1.0.0": "2017-08-17T19:26:00.508Z"
},
"name": "bad-dates",
"dist-tags": {
"latest": "1.0.0"
},
"modified": "2017-08-17T19:26:00.508Z"
}
68 changes: 68 additions & 0 deletions packages/npm-resolver/test/publishedBy.test.ts
@@ -0,0 +1,68 @@
import fs from 'fs'
import path from 'path'
import { createFetchFromRegistry } from '@pnpm/fetch'
import _createResolveFromNpm from '@pnpm/npm-resolver'
import fixtures from '@pnpm/test-fixtures'
import loadJsonFile from 'load-json-file'
import nock from 'nock'
import tempy from 'tempy'

const f = fixtures(__dirname)
const registry = 'https://registry.npmjs.org/'

/* eslint-disable @typescript-eslint/no-explicit-any */
const badDatesMeta = loadJsonFile.sync<any>(f.find('bad-dates.json'))
/* eslint-enable @typescript-eslint/no-explicit-any */

const fetch = createFetchFromRegistry({})
const getCredentials = () => ({ authHeaderValue: undefined, alwaysAuth: undefined })
const createResolveFromNpm = _createResolveFromNpm.bind(null, fetch, getCredentials)

test('fall back to a newer version if there is no version published by the given date', async () => {
nock(registry)
.get('/bad-dates')
.reply(200, badDatesMeta)

const cacheDir = tempy.directory()
const resolve = createResolveFromNpm({
cacheDir,
filterMetadata: true,
fullMetadata: true,
})
const resolveResult = await resolve({ alias: 'bad-dates', pref: '^1.0.0' }, {
registry,
publishedBy: new Date('2015-08-17T19:26:00.508Z'),
})

expect(resolveResult!.resolvedVia).toBe('npm-registry')
expect(resolveResult!.id).toBe('registry.npmjs.org/bad-dates/1.0.0')
})

test('request metadata when the one in cache does not have a version satisfiyng the range', async () => {
const cacheDir = tempy.directory()
const cachedMeta = {
'dist-tags': {},
versions: {},
time: {},
cachedAt: '2016-08-17T19:26:00.508Z',
}
fs.mkdirSync(path.join(cacheDir, 'metadata-full-filtered/registry.npmjs.org'), { recursive: true })
fs.writeFileSync(path.join(cacheDir, 'metadata-full-filtered/registry.npmjs.org/bad-dates.json'), JSON.stringify(cachedMeta), 'utf8')

nock(registry)
.get('/bad-dates')
.reply(200, badDatesMeta)

const resolve = createResolveFromNpm({
cacheDir,
filterMetadata: true,
fullMetadata: true,
})
const resolveResult = await resolve({ alias: 'bad-dates', pref: '^1.0.0' }, {
registry,
publishedBy: new Date('2015-08-17T19:26:00.508Z'),
})

expect(resolveResult!.resolvedVia).toBe('npm-registry')
expect(resolveResult!.id).toBe('registry.npmjs.org/bad-dates/1.0.0')
})
11 changes: 8 additions & 3 deletions packages/plugin-commands-script-runners/test/testRecursive.ts
Expand Up @@ -5,6 +5,8 @@ import { preparePackages } from '@pnpm/prepare'
import execa from 'execa'
import { DEFAULT_OPTS, REGISTRY_URL } from './utils'

const pnpmBin = path.join(__dirname, '../../pnpm/bin/pnpm.cjs')

test('pnpm recursive test', async () => {
preparePackages([
{
Expand Down Expand Up @@ -51,7 +53,8 @@ test('pnpm recursive test', async () => {
])

const { allProjects, selectedProjectsGraph } = await readProjects(process.cwd(), [])
await execa('pnpm', [
await execa('node', [
pnpmBin,
'install',
'-r',
'--registry',
Expand Down Expand Up @@ -106,7 +109,8 @@ test('`pnpm recursive test` does not fail if none of the packages has a test com
])

const { allProjects, selectedProjectsGraph } = await readProjects(process.cwd(), [])
await execa('pnpm', [
await execa('node', [
pnpmBin,
'install',
'-r',
'--registry',
Expand Down Expand Up @@ -158,7 +162,8 @@ test('pnpm recursive test with filtering', async () => {
[{ namePattern: 'project-1' }],
{ workspaceDir: process.cwd() }
)
await execa('pnpm', [
await execa('node', [
pnpmBin,
'install',
'-r',
'--registry',
Expand Down
2 changes: 0 additions & 2 deletions packages/plugin-commands-store/test/storeStatus.ts
Expand Up @@ -60,7 +60,6 @@ test('CLI does not fail when store status does not find modified packages', asyn
`--store-dir=${storeDir}`,
`--registry=${REGISTRY}`,
'--verify-store-integrity',
'--config.resolution-mode=highest',
'add',
'eslint@3.4.0',
'gulp@4.0.2',
Expand All @@ -79,7 +78,6 @@ test('CLI does not fail when store status does not find modified packages', asyn
`--store-dir=${storeDir}`,
`--registry=${REGISTRY}`,
'--verify-store-integrity',
'--config.resolution-mode=highest',
])

const modulesState = await project.readModulesManifest()
Expand Down