fix: pnpm patch should ignore files that are not included in the patched package (#6596)

close #6565

---------

Co-authored-by: Zoltan Kochan <z@kochan.io>
This commit is contained in:
Kirk Lin
2023-06-08 08:33:28 +08:00
committed by GitHub
parent 29a2a69806
commit f0d68ab2fd
5 changed files with 86 additions and 1 deletions

View File

@@ -0,0 +1,6 @@
---
"@pnpm/plugin-commands-patching": patch
"pnpm": patch
---
When patching a dependency, only consider files specified in the 'files' field of its package.json. Ignore all others [#6565](https://github.com/pnpm/pnpm/issues/6565)

View File

@@ -39,6 +39,7 @@
"@pnpm/registry-mock": "3.8.0",
"@pnpm/test-fixtures": "workspace:*",
"@types/normalize-path": "^3.0.0",
"@types/npm-packlist": "^3.0.0",
"@types/ramda": "0.28.20",
"@types/semver": "7.3.13",
"write-yaml-file": "^5.0.0"
@@ -60,7 +61,9 @@
"@pnpm/store-connection-manager": "workspace:*",
"enquirer": "^2.3.6",
"escape-string-regexp": "^4.0.0",
"fast-glob": "^3.2.12",
"normalize-path": "^3.0.0",
"npm-packlist": "^5.1.3",
"ramda": "npm:@pnpm/ramda@0.28.1",
"realpath-missing": "^1.1.0",
"render-help": "^1.0.3",

View File

@@ -5,14 +5,17 @@ import { type Config, types as allTypes } from '@pnpm/config'
import { install } from '@pnpm/plugin-commands-installation'
import { readPackageJsonFromDir } from '@pnpm/read-package-json'
import { tryReadProjectManifest } from '@pnpm/read-project-manifest'
import glob from 'fast-glob'
import normalizePath from 'normalize-path'
import pick from 'ramda/src/pick'
import equals from 'ramda/src/equals'
import execa from 'safe-execa'
import escapeStringRegexp from 'escape-string-regexp'
import renderHelp from 'render-help'
import tempy from 'tempy'
import { writePackage } from './writePackage'
import { parseWantedDependency } from '@pnpm/parse-wanted-dependency'
import packlist from 'npm-packlist'
export const rcOptionsTypes = cliOptionsTypes
@@ -49,7 +52,10 @@ export async function handler (opts: install.InstallCommandOptions & Pick<Config
const pkgNameAndVersion = `${patchedPkgManifest.name}@${patchedPkgManifest.version}`
const srcDir = tempy.directory()
await writePackage(parseWantedDependency(pkgNameAndVersion), srcDir, opts)
const patchContent = await diffFolders(srcDir, userDir)
const patchedPkgDir = await preparePkgFilesForDiff(userDir)
const patchContent = await diffFolders(srcDir, patchedPkgDir)
const patchFileName = pkgNameAndVersion.replace('/', '__')
await fs.promises.writeFile(path.join(patchesDir, `${patchFileName}.patch`), patchContent, 'utf8')
const { writeProjectManifest, manifest } = await tryReadProjectManifest(lockfileDir)
@@ -129,3 +135,36 @@ function removeTrailingAndLeadingSlash (p: string) {
}
return p
}
/**
* Link files from the source directory to a new temporary directory,
* but only if not all files in the source directory should be included in the package.
* If all files should be included, return the original source directory without creating any links.
* This is required in order for the diff to not include files that are not part of the package.
*/
async function preparePkgFilesForDiff (src: string): Promise<string> {
const files = Array.from(new Set((await packlist({ path: src })).map((f) => path.join(f))))
// If there are no extra files in the source directories, then there is no reason
// to copy.
if (await areAllFilesInPkg(files, src)) {
return src
}
const dest = tempy.directory()
await Promise.all(
files.map(async (file) => {
const srcFile = path.join(src, file)
const destFile = path.join(dest, file)
const destDir = path.dirname(destFile)
await fs.promises.mkdir(destDir, { recursive: true })
await fs.promises.link(srcFile, destFile)
})
)
return dest
}
async function areAllFilesInPkg (files: string[], basePath: string) {
const allFiles = await glob('**', {
cwd: basePath,
})
return equals(allFiles.sort(), files.sort())
}

View File

@@ -95,6 +95,34 @@ describe('patch and commit', () => {
expect(fs.existsSync('node_modules/is-positive/license')).toBe(false)
})
test('patch and commit with filtered files', async () => {
const output = await patch.handler(defaultPatchOption, ['is-positive@1.0.0'])
const patchDir = getPatchDirFromPatchOutput(output)
const tempDir = os.tmpdir() // temp dir depends on the operating system (@see tempy)
// store patch files in a temporary directory when not given editDir option
expect(patchDir).toContain(tempDir)
expect(fs.existsSync(patchDir)).toBe(true)
// sanity check to ensure that the license file contains the expected string
expect(fs.readFileSync(path.join(patchDir, 'license'), 'utf8')).toContain('The MIT License (MIT)')
fs.writeFileSync(path.join(patchDir, 'ignore.txt'), '', 'utf8')
const { manifest } = await readProjectManifest(patchDir)
expect(manifest?.files).toStrictEqual(['index.js'])
await patchCommit.handler({
...DEFAULT_OPTS,
cacheDir,
dir: process.cwd(),
frozenLockfile: false,
fixLockfile: true,
storeDir,
}, [patchDir])
expect(fs.existsSync('node_modules/is-positive/ignore.txt')).toBe(false)
})
test('patch and commit with a custom edit dir', async () => {
const editDir = path.join(tempy.directory())

9
pnpm-lock.yaml generated
View File

@@ -2633,9 +2633,15 @@ importers:
escape-string-regexp:
specifier: ^4.0.0
version: 4.0.0
fast-glob:
specifier: ^3.2.12
version: 3.2.12
normalize-path:
specifier: ^3.0.0
version: 3.0.0
npm-packlist:
specifier: ^5.1.3
version: 5.1.3
ramda:
specifier: npm:@pnpm/ramda@0.28.1
version: /@pnpm/ramda@0.28.1
@@ -2673,6 +2679,9 @@ importers:
'@types/normalize-path':
specifier: ^3.0.0
version: 3.0.0
'@types/npm-packlist':
specifier: ^3.0.0
version: 3.0.0
'@types/ramda':
specifier: 0.28.20
version: 0.28.20