mirror of
https://github.com/pnpm/pnpm.git
synced 2026-04-10 18:18:56 -04:00
fix: re-link local tarball when contents change (without rename) during filtered install (#9805)
* test: ensure current lockfile updates when tarball integrity changes * fix: update store when local tarball contents change without rename
This commit is contained in:
8
.changeset/beige-camels-post.md
Normal file
8
.changeset/beige-camels-post.md
Normal file
@@ -0,0 +1,8 @@
|
||||
---
|
||||
"@pnpm/headless": patch
|
||||
"@pnpm/deps.graph-builder": patch
|
||||
"@pnpm/core": patch
|
||||
pnpm: patch
|
||||
---
|
||||
|
||||
Fix an edge case bug causing local tarballs to not re-link into the virtual store. This bug would happen when changing the contents of the tarball without renaming the file and running a filtered install.
|
||||
17
deps/graph-builder/src/lockfileToDepGraph.ts
vendored
17
deps/graph-builder/src/lockfileToDepGraph.ts
vendored
@@ -33,6 +33,7 @@ export interface DependenciesGraphNode {
|
||||
modules: string
|
||||
name: string
|
||||
fetching?: () => Promise<PkgRequestFetchResult>
|
||||
forceImportPackage?: boolean // Used to force re-imports from the store of local tarballs that have changed.
|
||||
dir: string
|
||||
children: Record<string, string>
|
||||
optionalDependencies: Set<string>
|
||||
@@ -186,6 +187,8 @@ async function buildGraphFromPackages (
|
||||
currentPackages[depPath] &&
|
||||
equals(currentPackages[depPath].dependencies, pkgSnapshot.dependencies)
|
||||
|
||||
const depIntegrityIsUnchanged = isIntegrityEqual(pkgSnapshot.resolution, currentPackages[depPath]?.resolution)
|
||||
|
||||
const modules = path.join(opts.virtualStoreDir, dirNameInVirtualStore, 'node_modules')
|
||||
const dir = path.join(modules, pkgName)
|
||||
locationByDepPath[depPath] = dir
|
||||
@@ -193,6 +196,7 @@ async function buildGraphFromPackages (
|
||||
let dirExists: boolean | undefined
|
||||
if (
|
||||
depIsPresent &&
|
||||
depIntegrityIsUnchanged &&
|
||||
isEmpty(currentPackages[depPath].optionalDependencies ?? {}) &&
|
||||
isEmpty(pkgSnapshot.optionalDependencies ?? {}) &&
|
||||
!opts.includeUnchangedDeps
|
||||
@@ -203,7 +207,7 @@ async function buildGraphFromPackages (
|
||||
}
|
||||
|
||||
let fetchResponse!: Partial<FetchResponse>
|
||||
if (depIsPresent && equals(currentPackages[depPath].optionalDependencies, pkgSnapshot.optionalDependencies)) {
|
||||
if (depIsPresent && depIntegrityIsUnchanged && equals(currentPackages[depPath].optionalDependencies, pkgSnapshot.optionalDependencies)) {
|
||||
if (dirExists ?? await pathExists(dir)) {
|
||||
fetchResponse = {}
|
||||
} else {
|
||||
@@ -236,6 +240,7 @@ async function buildGraphFromPackages (
|
||||
dir,
|
||||
fetching: fetchResponse.fetching,
|
||||
filesIndexFile: fetchResponse.filesIndexFile,
|
||||
forceImportPackage: !depIntegrityIsUnchanged,
|
||||
hasBin: pkgSnapshot.hasBin === true,
|
||||
hasBundledDependencies: pkgSnapshot.bundledDependencies != null,
|
||||
modules,
|
||||
@@ -290,3 +295,13 @@ function getChildrenPaths (
|
||||
}
|
||||
return children
|
||||
}
|
||||
|
||||
function isIntegrityEqual (resolutionA?: LockfileResolution, resolutionB?: LockfileResolution) {
|
||||
// The LockfileResolution type is a union, but it doesn't have a "tag"
|
||||
// field to perform a discriminant match on. Using a type assertion is
|
||||
// required to get the integrity field.
|
||||
const integrityA = (resolutionA as ({ integrity?: string } | undefined))?.integrity
|
||||
const integrityB = (resolutionB as ({ integrity?: string } | undefined))?.integrity
|
||||
|
||||
return integrityA === integrityB
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
mutateModules,
|
||||
type MutatedProject,
|
||||
mutateModulesInSingleProject,
|
||||
type ProjectOptions,
|
||||
} from '@pnpm/core'
|
||||
import { sync as rimraf } from '@zkochan/rimraf'
|
||||
import normalizePath from 'normalize-path'
|
||||
@@ -240,6 +241,66 @@ test('update tarball local package when its integrity changes', async () => {
|
||||
expect(manifestOfTarballDep.dependencies['is-positive']).toBe('^2.0.0')
|
||||
})
|
||||
|
||||
// Similar to the test above, but for a filtered install.
|
||||
// Regression test for https://github.com/pnpm/pnpm/pull/9805.
|
||||
test('update tarball local package when its integrity changes (filtered install)', async () => {
|
||||
const rootProject = prepareEmpty()
|
||||
const lockfileDir = rootProject.dir()
|
||||
|
||||
const manifests = {
|
||||
project1: {
|
||||
name: 'project1',
|
||||
},
|
||||
project2: {
|
||||
name: 'project2',
|
||||
},
|
||||
}
|
||||
preparePackages(Object.values(manifests), { tempDir: lockfileDir })
|
||||
const allProjects: ProjectOptions[] = Object.entries(manifests)
|
||||
.map(([id, manifest]) => ({
|
||||
buildIndex: 0,
|
||||
manifest,
|
||||
rootDir: path.join(lockfileDir, id) as ProjectRootDir,
|
||||
}))
|
||||
|
||||
const options = {
|
||||
...testDefaults({
|
||||
allProjects,
|
||||
}),
|
||||
lockfileDir,
|
||||
}
|
||||
|
||||
f.copy('tar-pkg-with-dep-1/tar-pkg-with-dep-1.0.0.tgz', path.resolve('.', 'tar.tgz'))
|
||||
await addDependenciesToPackage(
|
||||
manifests['project1'],
|
||||
['../tar.tgz'],
|
||||
{
|
||||
...options,
|
||||
dir: path.join(options.lockfileDir, 'project1'),
|
||||
})
|
||||
|
||||
const manifestOfTarballDep1 = JSON.parse(fs.readFileSync('project1/node_modules/tar-pkg-with-dep/package.json').toString())
|
||||
expect(manifestOfTarballDep1.dependencies['is-positive']).toBe('^1.0.0')
|
||||
|
||||
f.copy('tar-pkg-with-dep-2/tar-pkg-with-dep-1.0.0.tgz', path.resolve('.', 'tar.tgz'))
|
||||
|
||||
// Re-initialize the store controller that's created within the testDefaults()
|
||||
// function. Otherwise the fetchingLocker will contain results from a prior
|
||||
// installation and skip store fetches for the same package ID.
|
||||
const nextOptions = {
|
||||
...options,
|
||||
...testDefaults(allProjects),
|
||||
}
|
||||
const project1InstallOptions: MutatedProject = {
|
||||
mutation: 'install',
|
||||
rootDir: path.join(lockfileDir, 'project1') as ProjectRootDir,
|
||||
}
|
||||
await mutateModules([project1InstallOptions], nextOptions)
|
||||
|
||||
const manifestOfTarballDep2 = JSON.parse(fs.readFileSync('project1/node_modules/tar-pkg-with-dep/package.json').toString())
|
||||
expect(manifestOfTarballDep2.dependencies['is-positive']).toBe('^2.0.0')
|
||||
})
|
||||
|
||||
// Covers https://github.com/pnpm/pnpm/issues/1878
|
||||
test('do not update deps when installing in a project that has local tarball dep', async () => {
|
||||
await addDistTag({ package: '@pnpm.e2e/peer-a', version: '1.0.0', distTag: 'latest' })
|
||||
|
||||
@@ -886,7 +886,7 @@ async function linkAllPkgs (
|
||||
}
|
||||
const { importMethod, isBuilt } = await storeController.importPackage(depNode.dir, {
|
||||
filesResponse,
|
||||
force: opts.force,
|
||||
force: depNode.forceImportPackage ?? opts.force,
|
||||
disableRelinkLocalDirDeps: opts.disableRelinkLocalDirDeps,
|
||||
requiresBuild: depNode.patch != null || depNode.requiresBuild,
|
||||
sideEffectsCacheKey,
|
||||
|
||||
Reference in New Issue
Block a user