`resolutionMode: time-based` needs each version's publish time, which
`registry.npmjs.org` omits from abbreviated metadata, so pnpm falls back
to the far larger full packument. `registrySupportsTimeField` answered
that question for every registry at once, which is wrong in both
directions for a project that resolves from more than one: it either
buys full metadata from a registry that does carry `time`, or claims a
`time` field npmjs does not serve.
A registry entry now carries `supportsTimeField`, and the fallback is
decided per registry:
resolutionMode: time-based
registries:
https://npm.internal.example/:
supportsTimeField: true
The resolver asks a `needsFullMetadataFor(registry)` callback where it
used to read a single `fullMetadata` flag; the metadata mirror path and
the cache key are already keyed by registry, so two registries can
disagree within one install. A registry that declares nothing answers
exactly what the global setting says, so nothing changes unless a
project describes a registry.
The declaration reaches a pnpr server too — it already inverts the
client's `registries` through the same lookups, so a pnpr-served resolve
applies it to the resolution it runs on the caller's behalf.
86 lines
3.0 KiB
TypeScript
86 lines
3.0 KiB
TypeScript
import { afterEach, beforeEach, expect, test } from '@jest/globals'
|
|
import { createFetchFromRegistry } from '@pnpm/network.fetch'
|
|
import { createNpmResolver } from '@pnpm/resolving.npm-resolver'
|
|
import type { PackageMeta } from '@pnpm/resolving.registry.types'
|
|
import type { RegistriesByScope } from '@pnpm/types'
|
|
import { temporaryDirectory } from 'tempy'
|
|
|
|
import { getMockAgent, setupMockAgent, teardownMockAgent } from './utils/index.js'
|
|
|
|
const PUBLIC_REGISTRY = 'https://registry.npmjs.org/'
|
|
const TIME_REGISTRY = 'https://time.example.com/'
|
|
const ABBREVIATED_ACCEPT = 'application/vnd.npm.install-v1+json'
|
|
const TIME_PKG = '@time/pkg'
|
|
|
|
const registriesByScope: RegistriesByScope = {
|
|
default: PUBLIC_REGISTRY,
|
|
'@time': TIME_REGISTRY,
|
|
}
|
|
|
|
const fetch = createFetchFromRegistry({})
|
|
const getAuthHeader = () => undefined
|
|
const createResolveFromNpm = createNpmResolver.bind(null, fetch, getAuthHeader)
|
|
|
|
function metaOf (name: string): PackageMeta {
|
|
return {
|
|
name,
|
|
'dist-tags': { latest: '1.0.0' },
|
|
versions: {
|
|
'1.0.0': {
|
|
name,
|
|
version: '1.0.0',
|
|
dist: { shasum: '', tarball: `${PUBLIC_REGISTRY}${name}/-/pkg-1.0.0.tgz` },
|
|
},
|
|
},
|
|
time: { '1.0.0': '2020-02-01T00:00:00.000Z' },
|
|
}
|
|
}
|
|
|
|
beforeEach(async () => {
|
|
await setupMockAgent()
|
|
})
|
|
|
|
afterEach(async () => {
|
|
await teardownMockAgent()
|
|
})
|
|
|
|
/**
|
|
* The public registry omits `time` from its abbreviated metadata, so a
|
|
* time-based resolution has to read the full document from it. A registry that
|
|
* declares `supportsTimeField` does not, and paying for the full document at
|
|
* every registry because one of them needs it is the cost this removes.
|
|
*/
|
|
test('a registry that carries the time field is asked for abbreviated metadata, the others for full', async () => {
|
|
const accepts: Record<string, string | undefined> = {}
|
|
const capture = (registry: string) => (opts: { headers?: unknown }): { statusCode: number, data: PackageMeta } => {
|
|
const headers = opts.headers as Record<string, string>
|
|
accepts[registry] = headers.accept ?? headers.Accept
|
|
return {
|
|
statusCode: 200,
|
|
data: metaOf(registry === 'public' ? 'pkg' : TIME_PKG),
|
|
}
|
|
}
|
|
|
|
getMockAgent().get('https://registry.npmjs.org')
|
|
.intercept({ path: '/pkg', method: 'GET' })
|
|
.reply(capture('public'))
|
|
getMockAgent().get('https://time.example.com')
|
|
.intercept({ path: `/@${encodeURIComponent(TIME_PKG.slice(1))}`, method: 'GET' })
|
|
.reply(capture('time'))
|
|
|
|
const { resolveFromNpm } = createResolveFromNpm({
|
|
cacheDir: temporaryDirectory(),
|
|
storeDir: temporaryDirectory(),
|
|
registriesByScope,
|
|
// What `needsFullMetadataForRegistry` answers for a time-based resolution
|
|
// where only the second registry declares `supportsTimeField: true`.
|
|
needsFullMetadataFor: (registry: string) => registry !== TIME_REGISTRY,
|
|
})
|
|
|
|
await resolveFromNpm({ alias: 'pkg', bareSpecifier: '1.0.0' }, {})
|
|
await resolveFromNpm({ alias: TIME_PKG, bareSpecifier: '1.0.0' }, {})
|
|
|
|
expect(accepts.public).not.toContain(ABBREVIATED_ACCEPT)
|
|
expect(accepts.time).toContain(ABBREVIATED_ACCEPT)
|
|
})
|