Skip to content

Commit 0bcbaf9

Browse files
authored
refactor: move out skip resolution logic from package requester (#10439)
1 parent 9c0637f commit 0bcbaf9

28 files changed

Lines changed: 403 additions & 275 deletions

File tree

config/deps-installer/test/resolveConfigDeps.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ test('configuration dependency is resolved', async () => {
1919
cacheDir: path.resolve('cache'),
2020
userConfig: {},
2121
store: storeController,
22+
storeDir: '.store',
2223
})
2324

2425
const workspaceManifest = readYamlFile<{ configDependencies: Record<string, string> }>('pnpm-workspace.yaml')

env/node.resolver/src/index.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { type FetchFromRegistry } from '@pnpm/fetching-types'
55
import {
66
type BinaryResolution,
77
type PlatformAssetResolution,
8+
type ResolveOptions,
89
type ResolveResult,
910
type VariationsResolution,
1011
type WantedDependency,
@@ -29,9 +30,19 @@ export async function resolveNodeRuntime (
2930
rawConfig: Record<string, string>
3031
offline?: boolean
3132
},
32-
wantedDependency: WantedDependency
33+
wantedDependency: WantedDependency,
34+
opts?: Partial<ResolveOptions>
3335
): Promise<NodeRuntimeResolveResult | null> {
3436
if (wantedDependency.alias !== 'node' || !wantedDependency.bareSpecifier?.startsWith('runtime:')) return null
37+
38+
if (opts?.currentPkg && !opts.update) {
39+
return {
40+
id: opts.currentPkg.id,
41+
resolution: opts.currentPkg.resolution as VariationsResolution,
42+
resolvedVia: 'nodejs.org',
43+
}
44+
}
45+
3546
if (ctx.offline) throw new PnpmError('NO_OFFLINE_NODEJS_RESOLUTION', 'Offline Node.js resolution is not supported')
3647
const versionSpec = wantedDependency.bareSpecifier.substring('runtime:'.length)
3748
const { releaseChannel, versionSpecifier } = parseEnvSpecifier(versionSpec)

pkg-manager/client/test/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ test('createClient()', () => {
99
registries: {
1010
default: 'https://reigstry.npmjs.org/',
1111
},
12+
storeDir: '.store',
1213
})
1314
expect(typeof client === 'object').toBeTruthy()
1415
})
@@ -21,6 +22,7 @@ test('createResolver()', () => {
2122
registries: {
2223
default: 'https://reigstry.npmjs.org/',
2324
},
25+
storeDir: '.store',
2426
})
2527
expect(typeof resolve === 'function').toBeTruthy()
2628
})

pkg-manager/package-requester/src/packageRequester.ts

Lines changed: 45 additions & 169 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {
1212
type FetchOptions,
1313
type FetchResult,
1414
} from '@pnpm/fetcher-base'
15-
import { type Cafs, type PackageFilesResponse } from '@pnpm/cafs-types'
15+
import { type Cafs } from '@pnpm/cafs-types'
1616
import gfs from '@pnpm/graceful-fs'
1717
import { logger } from '@pnpm/logger'
1818
import { packageIsInstallable } from '@pnpm/package-is-installable'
@@ -139,18 +139,12 @@ export function createPackageRequester (
139139
virtualStoreDirMaxLength: opts.virtualStoreDirMaxLength,
140140
strictStorePkgContentCheck: opts.strictStorePkgContentCheck,
141141
})
142-
const peekPackageFromStore = peekFromStore.bind(null, {
143-
getIndexFilePathInCafs,
144-
readPkgFromCafs,
145-
fetchingLockerForPeek: new Map(),
146-
})
147142
const requestPackage = resolveAndFetch.bind(null, {
148143
engineStrict: opts.engineStrict,
149144
nodeVersion: opts.nodeVersion,
150145
pnpmVersion: opts.pnpmVersion,
151146
force: opts.force,
152147
fetchPackageToStore,
153-
peekPackageFromStore,
154148
requestsQueue,
155149
resolve: opts.resolve,
156150
storeDir: opts.storeDir,
@@ -176,113 +170,57 @@ async function resolveAndFetch (
176170
requestsQueue: { add: <T>(fn: () => Promise<T>, opts: { priority: number }) => Promise<T> }
177171
resolve: ResolveFunction
178172
fetchPackageToStore: FetchPackageToStoreFunction
179-
peekPackageFromStore: (pkg: PkgNameVersion & {
180-
id: string
181-
resolution: Resolution
182-
}) => Promise<PeekFromStoreResult | undefined>
183173
storeDir: string
184174
},
185175
wantedDependency: WantedDependency & { optional?: boolean },
186176
options: RequestPackageOptions
187177
): Promise<PackageResponse> {
188-
let latest: string | undefined
189-
let manifest: DependencyManifest | undefined
190-
let normalizedBareSpecifier: string | undefined
191-
let alias: string | undefined
192178
let resolution = options.currentPkg?.resolution as Resolution
193179
let pkgId = options.currentPkg?.id
194-
const skipResolution = resolution && !options.update
195-
let forceFetch = false
196-
let updated = false
197-
let resolvedVia: string | undefined
198-
let publishedAt: string | undefined
199-
200-
async function performResolution () {
201-
// When skipResolution is set but a resolution is still performed due to
202-
// options.skipFetch, it's necessary to make sure the resolution doesn't
203-
// accidentally return a newer version of the package. When skipFetch is
204-
// set, the resolved package shouldn't be different. This is done by
205-
// overriding the preferredVersions object to only contain the current
206-
// package's version.
207-
//
208-
// A naive approach would be to change the bare specifier to be the exact
209-
// version of the current pkg if the bare specifier is a range, but this
210-
// would cause the version returned for calcSpecifier to be different.
211-
const preferredVersions: PreferredVersions = (skipResolution && options.currentPkg?.name != null && options.currentPkg?.version != null)
212-
? {
213-
...options.preferredVersions,
214-
[options.currentPkg.name]: { [options.currentPkg.version]: 'version' },
215-
}
216-
: options.preferredVersions
217-
218-
const resolveResult = await ctx.requestsQueue.add<ResolveResult>(async () => ctx.resolve(wantedDependency, {
219-
alwaysTryWorkspacePackages: options.alwaysTryWorkspacePackages,
220-
defaultTag: options.defaultTag,
221-
trustPolicy: options.trustPolicy,
222-
trustPolicyExclude: options.trustPolicyExclude,
223-
trustPolicyIgnoreAfter: options.trustPolicyIgnoreAfter,
224-
publishedBy: options.publishedBy,
225-
publishedByExclude: options.publishedByExclude,
226-
pickLowestVersion: options.pickLowestVersion,
227-
lockfileDir: options.lockfileDir,
228-
preferredVersions,
229-
preferWorkspacePackages: options.preferWorkspacePackages,
230-
projectDir: options.projectDir,
231-
workspacePackages: options.workspacePackages,
232-
update: options.update,
233-
injectWorkspacePackages: options.injectWorkspacePackages,
234-
calcSpecifier: options.calcSpecifier,
235-
pinnedVersion: options.pinnedVersion,
236-
}), { priority: options.downloadPriority })
237-
238-
manifest = resolveResult.manifest
239-
latest = resolveResult.latest
240-
resolvedVia = resolveResult.resolvedVia
241-
publishedAt = resolveResult.publishedAt
242-
243-
// If the integrity of a local tarball dependency has changed,
244-
// the local tarball should be unpacked, so a fetch to the store should be forced
245-
forceFetch = Boolean(
246-
((options.currentPkg?.resolution) != null) &&
247-
pkgId?.startsWith('file:') &&
248-
(options.currentPkg?.resolution as TarballResolution).integrity !== (resolveResult.resolution as TarballResolution).integrity
249-
)
250-
251-
updated = pkgId !== resolveResult.id || !resolution || forceFetch
252-
resolution = resolveResult.resolution
253-
pkgId = resolveResult.id
254-
normalizedBareSpecifier = resolveResult.normalizedBareSpecifier
255-
alias = resolveResult.alias
256-
}
257180

258-
// When fetching is skipped, we still need the package's manifest for
259-
// lockfile-only installs.
181+
// When we have a currentPkg but a resolution is still performed due to
182+
// options.skipFetch, it's necessary to make sure the resolution doesn't
183+
// accidentally return a newer version of the package. When skipFetch is
184+
// set, the resolved package shouldn't be different. This is done by
185+
// overriding the preferredVersions object to only contain the current
186+
// package's version.
260187
//
261-
// The package manifest could be retrieved from CAFS if the package has been
262-
// fetched previously. Otherwise we'll need to perform a resolution.
263-
//
264-
// The resolution step is never skipped for local dependencies.
265-
if (!skipResolution || options.skipFetch === true || Boolean(pkgId?.startsWith('file:')) || wantedDependency.optional === true) {
266-
// If the manifest for this package is in the store, retrieving it from the
267-
// Content-Addressable File Store will be significantly faster than fetching
268-
// it from metadata. This is because package metadata contains the
269-
// package.json contents of a package across all of its versions, which
270-
// takes a long time to parse.
271-
if (skipResolution && !pkgId?.startsWith('file:') && wantedDependency.optional !== true && pkgId != null) {
272-
const pkg = await ctx.peekPackageFromStore({
273-
...options.expectedPkg,
274-
id: pkgId,
275-
resolution,
276-
})
277-
manifest = pkg?.bundledManifest
188+
// A naive approach would be to change the bare specifier to be the exact
189+
// version of the current pkg if the bare specifier is a range, but this
190+
// would cause the version returned for calcSpecifier to be different.
191+
const preferredVersions: PreferredVersions = (resolution && !options.update && options.currentPkg?.name != null && options.currentPkg?.version != null)
192+
? {
193+
...options.preferredVersions,
194+
[options.currentPkg.name]: { [options.currentPkg.version]: 'version' },
278195
}
196+
: options.preferredVersions
279197

280-
// However, if the optimization above isn't possible or the package has
281-
// never been added to the CAFS, we'll need to perform a resolution.
282-
if (manifest == null) {
283-
await performResolution()
284-
}
285-
}
198+
const resolveResult = await ctx.requestsQueue.add<ResolveResult>(async () => ctx.resolve(wantedDependency, {
199+
...options,
200+
preferredVersions,
201+
currentPkg: (options.currentPkg?.id && options.currentPkg?.resolution)
202+
? {
203+
id: options.currentPkg.id,
204+
name: options.currentPkg.name,
205+
version: options.currentPkg.version,
206+
resolution: options.currentPkg.resolution,
207+
}
208+
: undefined,
209+
}), { priority: options.downloadPriority })
210+
211+
let { manifest } = resolveResult
212+
const {
213+
latest,
214+
resolvedVia,
215+
publishedAt,
216+
normalizedBareSpecifier,
217+
alias,
218+
forceFetch,
219+
} = resolveResult
220+
221+
const updated = pkgId !== resolveResult.id || !resolution || forceFetch === true
222+
resolution = resolveResult.resolution
223+
pkgId = resolveResult.id
286224

287225
const id = pkgId!
288226

@@ -319,8 +257,9 @@ async function resolveAndFetch (
319257
)
320258
)
321259
// We can skip fetching the package only if the manifest
322-
// is present after resolution
323-
if ((options.skipFetch === true || isInstallable === false) && (manifest != null)) {
260+
// is present after resolution AND we're not forcing a fetch
261+
// (forceFetch is set by resolvers when package content may have changed)
262+
if ((options.skipFetch === true || isInstallable === false) && !forceFetch && (manifest != null)) {
324263
return {
325264
body: {
326265
id,
@@ -342,7 +281,7 @@ async function resolveAndFetch (
342281
const fetchResult = ctx.fetchPackageToStore({
343282
allowBuild: options.allowBuild,
344283
fetchRawManifest: true,
345-
force: forceFetch,
284+
force: forceFetch === true,
346285
ignoreScripts: options.ignoreScripts,
347286
lockfileDir: options.lockfileDir,
348287
pkg: {
@@ -673,69 +612,6 @@ function fetchToStore (
673612
}
674613
}
675614

676-
interface PeekFromStoreResult {
677-
readonly bundledManifest?: BundledManifest
678-
readonly files: PackageFilesResponse
679-
}
680-
681-
/**
682-
* Look up requests from the store without mutating it. This is related to
683-
* {@link fetchToStore}, but does not perform a fetch if the lookup was not
684-
* found.
685-
*
686-
* This function will return undefined if the package was found in the store,
687-
* but fails integrity checks.
688-
*/
689-
async function peekFromStore (
690-
ctx: {
691-
readPkgFromCafs: (
692-
filesIndexFile: string,
693-
opts?: ReadPkgFromCafsOptions
694-
) => Promise<ReadPkgFromCafsResult>
695-
getIndexFilePathInCafs: (integrity: string, pkgId: string) => string
696-
fetchingLockerForPeek: Map<string, Promise<PeekFromStoreResult | undefined>>
697-
},
698-
pkg: PkgNameVersion & {
699-
id: string
700-
resolution: Resolution
701-
}
702-
): Promise<PeekFromStoreResult | undefined> {
703-
// This function only supports TarballResolution requests with a present
704-
// integrity.
705-
if (pkg.resolution.type != null || pkg.resolution.integrity == null) {
706-
return undefined
707-
}
708-
709-
const indexFilePathInCafs = ctx.getIndexFilePathInCafs(pkg.resolution.integrity, pkg.id)
710-
const existingRequest = ctx.fetchingLockerForPeek.get(indexFilePathInCafs)
711-
712-
if (existingRequest != null) {
713-
return existingRequest
714-
}
715-
716-
const request = ctx.readPkgFromCafs(indexFilePathInCafs, {
717-
readManifest: true,
718-
expectedPkg: pkg,
719-
})
720-
.then(({ files, manifest, verified }): PeekFromStoreResult | undefined => {
721-
// If the files in the store are corrupted or out of date, it's better to
722-
// fail the peek result and allow the caller to fall back to a resolution
723-
// or proper fetch.
724-
if (!verified) {
725-
return undefined
726-
}
727-
728-
return {
729-
files,
730-
bundledManifest: manifest == null ? manifest : normalizeBundledManifest(manifest),
731-
}
732-
})
733-
734-
ctx.fetchingLockerForPeek.set(indexFilePathInCafs, request)
735-
736-
return request
737-
}
738-
739615
async function readBundledManifest (pkgJsonPath: string): Promise<BundledManifest> {
740616
return pickBundledManifest(await readPackageJson(pkgJsonPath) as DependencyManifest)
741617
}

0 commit comments

Comments
 (0)