mirror of
https://github.com/pnpm/pnpm.git
synced 2026-04-10 18:18:56 -04:00
fix: updating dependencies node_modules when node-linker is hoisted (#7117)
close #7107
This commit is contained in:
8
.changeset/plenty-spoons-search.md
Normal file
8
.changeset/plenty-spoons-search.md
Normal 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.
|
||||
|
||||
@@ -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')
|
||||
})
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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[]>,
|
||||
|
||||
Reference in New Issue
Block a user