fix: updating dependencies node_modules when node-linker is hoisted (#7117)

close #7107
This commit is contained in:
Zoltan Kochan
2023-09-21 12:10:31 +03:00
committed by GitHub
parent 98377afd34
commit 1f32d3eb89
4 changed files with 54 additions and 2 deletions

View File

@@ -0,0 +1,8 @@
---
"@pnpm/headless": patch
"@pnpm/core": patch
"pnpm": patch
---
When the `node-linker` is set to `hoisted`, the `package.json` files of the existing dependencies inside `node_modules` are checked to verify their actual versions. The data in the `node_modules/.modules.yaml` and `node_modules/.pnpm/lock.yaml` may not be fully reliable, as an installation may fail after changes to dependencies were made but before those state files were updated.

View File

@@ -1,5 +1,7 @@
import fs from 'fs'
import { prepareEmpty } from '@pnpm/prepare'
import { addDependenciesToPackage } from '@pnpm/core'
import { sync as loadJsonFile } from 'load-json-file'
import { testDefaults } from './utils'
test('packageImportMethod can be set to copy', async () => {
@@ -22,3 +24,27 @@ test('copy does not fail on package that self-requires itself', async () => {
const lockfile = await project.readLockfile()
expect(lockfile.packages['/@pnpm.e2e/requires-itself@1.0.0'].dependencies).toStrictEqual({ 'is-positive': '1.0.0' })
})
test('packages are updated in node_modules, when packageImportMethod is set to copy and modules manifest and current lockfile are incorrect', async () => {
prepareEmpty()
const opts = await testDefaults({ fastUnpack: false, force: false, nodeLinker: 'hoisted' }, {}, {}, { packageImportMethod: 'copy' })
await addDependenciesToPackage({}, ['is-negative@1.0.0'], opts)
const modulesManifestContent = fs.readFileSync('node_modules/.modules.yaml')
const currentLockfile = fs.readFileSync('node_modules/.pnpm/lock.yaml')
{
const pkg = loadJsonFile<any>('node_modules/is-negative/package.json') // eslint-disable-line
expect(pkg.version).toBe('1.0.0')
}
await addDependenciesToPackage({}, ['is-negative@2.0.0'], opts)
{
const pkg = loadJsonFile<any>('node_modules/is-negative/package.json') // eslint-disable-line
expect(pkg.version).toBe('2.0.0')
}
fs.writeFileSync('node_modules/.modules.yaml', modulesManifestContent, 'utf8')
fs.writeFileSync('node_modules/.pnpm/lock.yaml', currentLockfile, 'utf8')
await addDependenciesToPackage({}, ['is-negative@1.0.0'], opts)
const pkg = loadJsonFile<any>('node_modules/is-negative/package.json') // eslint-disable-line
expect(pkg.version).toBe('1.0.0')
})

View File

@@ -124,7 +124,7 @@ async function linkAllPkgsInOrder (
await limitLinking(async () => {
const { importMethod, isBuilt } = await storeController.importPackage(depNode.dir, {
filesResponse,
force: opts.force || depNode.depPath !== prevGraph[dir]?.depPath,
force: true,
disableRelinkLocalDirDeps: opts.disableRelinkLocalDirDeps,
keepModulesDir: true,
requiresBuild: depNode.requiresBuild || depNode.patchFile != null,

View File

@@ -12,6 +12,7 @@ import {
} from '@pnpm/lockfile-utils'
import { type IncludedDependencies } from '@pnpm/modules-yaml'
import { packageIsInstallable } from '@pnpm/package-is-installable'
import { safeReadPackageJsonFromDir } from '@pnpm/read-package-json'
import { type PatchFile, type Registries } from '@pnpm/types'
import {
type FetchPackageToStoreFunction,
@@ -193,8 +194,12 @@ async function fetchDeps (
// It will only be missing if the user manually removed it.
// That shouldn't normally happen but Bit CLI does remove node_modules in component directories:
// https://github.com/teambit/bit/blob/5e1eed7cd122813ad5ea124df956ee89d661d770/scopes/dependencies/dependency-resolver/dependency-installer.ts#L169
//
// We also verify that the package that is present has the expected version.
// This check is required because there is no guarantee the modules manifest and current lockfile were
// successfully saved after node_modules was changed during installation.
const skipFetch = opts.currentHoistedLocations?.[depPath]?.includes(depLocation) &&
await pathExists(path.join(opts.lockfileDir, depLocation))
await dirHasPackageJsonWithVersion(path.join(opts.lockfileDir, depLocation), pkgVersion)
const pkgResolution = {
id: packageId,
resolution,
@@ -254,6 +259,19 @@ async function fetchDeps (
return depHierarchy
}
async function dirHasPackageJsonWithVersion (dir: string, expectedVersion?: string): Promise<boolean> {
if (!expectedVersion) return pathExists(dir)
try {
const manifest = await safeReadPackageJsonFromDir(dir)
return manifest?.version === expectedVersion
} catch (err: any) { // eslint-disable-line
if (err.code === 'ENOENT') {
return pathExists(dir)
}
throw err
}
}
function getChildren (
pkgSnapshot: PackageSnapshot,
pkgLocationsByDepPath: Record<string, string[]>,