mirror of
https://github.com/pnpm/pnpm.git
synced 2026-04-10 18:18:56 -04:00
fix: perf improvement + don't fail if an injected package has no package.json (#4304)
This commit is contained in:
5
.changeset/red-mice-smile.md
Normal file
5
.changeset/red-mice-smile.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"pnpm": minor
|
||||
---
|
||||
|
||||
When checking that a package is linked from the store, check the existence of the package and read its stats with a single filesystem operation [#4304](https://github.com/pnpm/pnpm/pull/4304).
|
||||
5
.changeset/six-walls-fetch.md
Normal file
5
.changeset/six-walls-fetch.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@pnpm/package-store": patch
|
||||
---
|
||||
|
||||
When checking whether a package is linked from the store, don't fail if the package has no `package.json` file.
|
||||
@@ -1,4 +1,4 @@
|
||||
import { constants, promises as fs } from 'fs'
|
||||
import { constants, promises as fs, Stats } from 'fs'
|
||||
import path from 'path'
|
||||
import { globalInfo, globalWarn } from '@pnpm/logger'
|
||||
import { packageImportMethodLogger } from '@pnpm/core-loggers'
|
||||
@@ -107,9 +107,11 @@ async function hardlinkPkg (
|
||||
to: string,
|
||||
opts: ImportOptions
|
||||
) {
|
||||
const pkgJsonPath = path.join(to, 'package.json')
|
||||
|
||||
if (!opts.fromStore || opts.force || !await exists(pkgJsonPath) || !await pkgLinkedToStore(pkgJsonPath, opts.filesMap['package.json'], to)) {
|
||||
if (
|
||||
!opts.fromStore ||
|
||||
opts.force ||
|
||||
!await pkgLinkedToStore(opts.filesMap, to)
|
||||
) {
|
||||
await importIndexedDir(importFile, to, opts.filesMap)
|
||||
return 'hardlink'
|
||||
}
|
||||
@@ -131,18 +133,32 @@ async function linkOrCopy (existingPath: string, newPath: string) {
|
||||
}
|
||||
|
||||
async function pkgLinkedToStore (
|
||||
pkgJsonPath: string,
|
||||
pkgJsonPathInStore: string,
|
||||
filesMap: Record<string, string>,
|
||||
to: string
|
||||
) {
|
||||
if (await isSameFile(pkgJsonPath, pkgJsonPathInStore)) return true
|
||||
if (filesMap['package.json']) {
|
||||
if (await isSameFile(path.join(to, 'package.json'), filesMap['package.json'])) {
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
// An injected package might not have a package.json.
|
||||
// This will probably only even happen in a Bit workspace.
|
||||
const [anyFile] = Object.keys(filesMap)
|
||||
if (await isSameFile(path.join(to, anyFile), filesMap[anyFile])) return true
|
||||
}
|
||||
globalInfo(`Relinking ${to} from the store`)
|
||||
return false
|
||||
}
|
||||
|
||||
async function isSameFile (file1: string, file2: string) {
|
||||
const stats = await Promise.all([fs.stat(file1), fs.stat(file2)])
|
||||
return stats[0].ino === stats[1].ino
|
||||
async function isSameFile (linkedFile: string, fileFromStore: string) {
|
||||
let stats0!: Stats
|
||||
try {
|
||||
stats0 = await fs.stat(linkedFile)
|
||||
} catch (err: any) { // eslint-disable-line
|
||||
if (err.code === 'ENOENT') return false
|
||||
}
|
||||
const stats1 = await fs.stat(fileFromStore)
|
||||
return stats0.ino === stats1.ino
|
||||
}
|
||||
|
||||
export async function copyPkg (
|
||||
|
||||
@@ -145,3 +145,51 @@ test('packageImportMethod=hardlink: fall back to copying if hardlinking fails',
|
||||
expect(fsMock.promises.copyFile).toBeCalledWith(path.join('hash1'), path.join('project', '_tmp', 'package.json'))
|
||||
expect(fsMock.promises.copyFile).toBeCalledWith(path.join('hash2'), path.join('project', '_tmp', 'index.js'))
|
||||
})
|
||||
|
||||
test('packageImportMethod=hardlink does not relink package from store if package.json is linked from the store', async () => {
|
||||
const importPackage = createImportPackage('hardlink')
|
||||
fsMock.promises.copyFile = jest.fn()
|
||||
fsMock.promises.rename = jest.fn()
|
||||
fsMock.promises.stat = jest.fn(() => ({ ino: 1 }))
|
||||
expect(await importPackage('project/package', {
|
||||
filesMap: {
|
||||
'index.js': 'hash2',
|
||||
'package.json': 'hash1',
|
||||
},
|
||||
force: false,
|
||||
fromStore: true,
|
||||
})).toBe(undefined)
|
||||
})
|
||||
|
||||
test('packageImportMethod=hardlink relinks package from store if package.json is not linked from the store', async () => {
|
||||
const importPackage = createImportPackage('hardlink')
|
||||
fsMock.promises.copyFile = jest.fn()
|
||||
fsMock.promises.rename = jest.fn()
|
||||
let ino = 0
|
||||
fsMock.promises.stat = jest.fn(() => ({ ino: ++ino }))
|
||||
expect(await importPackage('project/package', {
|
||||
filesMap: {
|
||||
'index.js': 'hash2',
|
||||
'package.json': 'hash1',
|
||||
},
|
||||
force: false,
|
||||
fromStore: true,
|
||||
})).toBe('hardlink')
|
||||
})
|
||||
|
||||
test('packageImportMethod=hardlink does not relink package from store if package.json is not present in the store', async () => {
|
||||
const importPackage = createImportPackage('hardlink')
|
||||
fsMock.promises.copyFile = jest.fn()
|
||||
fsMock.promises.rename = jest.fn()
|
||||
fsMock.promises.stat = jest.fn((file) => {
|
||||
expect(typeof file).toBe('string')
|
||||
return { ino: 1 }
|
||||
})
|
||||
expect(await importPackage('project/package', {
|
||||
filesMap: {
|
||||
'index.js': 'hash2',
|
||||
},
|
||||
force: false,
|
||||
fromStore: true,
|
||||
})).toBe(undefined)
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user