mirror of
https://github.com/pnpm/pnpm.git
synced 2026-02-15 01:24:07 -05:00
feat: when patch package does not specify a version, use locally installed version by default. (#6197)
close #6192
This commit is contained in:
8
.changeset/khaki-icons-rest.md
Normal file
8
.changeset/khaki-icons-rest.md
Normal file
@@ -0,0 +1,8 @@
|
||||
---
|
||||
"@pnpm/plugin-commands-patching": patch
|
||||
"@pnpm/audit": patch
|
||||
"@pnpm/list": patch
|
||||
"pnpm": patch
|
||||
---
|
||||
|
||||
When patch package does not specify a version, use locally installed version by default [#6192](https://github.com/pnpm/pnpm/issues/6192).
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -47,3 +47,5 @@ RELEASE.md
|
||||
|
||||
## custom modules-dir fixture
|
||||
__fixtures__/custom-modules-dir/**/fake_modules/
|
||||
__fixtures__/custom-modules-dir/cache/
|
||||
__fixtures__/custom-modules-dir/patches/
|
||||
|
||||
@@ -6,7 +6,7 @@ import { Lockfile } from '@pnpm/lockfile-types'
|
||||
import { DependenciesField } from '@pnpm/types'
|
||||
import { lockfileToAuditTree } from './lockfileToAuditTree'
|
||||
import { AuditReport } from './types'
|
||||
import { searchForPackages, PackageNode } from '@pnpm/list'
|
||||
import { searchForPackages, flattenSearchedPackages } from '@pnpm/list'
|
||||
|
||||
export * from './types'
|
||||
|
||||
@@ -95,28 +95,7 @@ async function searchPackagePaths (
|
||||
pkg: string
|
||||
) {
|
||||
const pkgs = await searchForPackages([pkg], projectDirs, searchOpts)
|
||||
const paths: string[] = []
|
||||
|
||||
for (const pkg of pkgs) {
|
||||
_walker([
|
||||
...(pkg.optionalDependencies ?? []),
|
||||
...(pkg.dependencies ?? []),
|
||||
...(pkg.devDependencies ?? []),
|
||||
...(pkg.unsavedDependencies ?? []),
|
||||
], path.relative(searchOpts.lockfileDir, pkg.path) || '.')
|
||||
}
|
||||
return paths
|
||||
|
||||
function _walker (packages: PackageNode[], depPath: string) {
|
||||
for (const pkg of packages) {
|
||||
const nextDepPath = `${depPath} > ${pkg.name}@${pkg.version}`
|
||||
if (pkg.dependencies?.length) {
|
||||
_walker(pkg.dependencies, nextDepPath)
|
||||
} else {
|
||||
paths.push(nextDepPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
return flattenSearchedPackages(pkgs, { lockfileDir: searchOpts.lockfileDir }).map(({ depPath }) => depPath)
|
||||
}
|
||||
|
||||
export class AuditEndpointNotExistsError extends PnpmError {
|
||||
|
||||
@@ -37,13 +37,17 @@
|
||||
"@pnpm/plugin-commands-patching": "workspace:*",
|
||||
"@pnpm/prepare": "workspace:*",
|
||||
"@pnpm/registry-mock": "3.5.0",
|
||||
"@pnpm/test-fixtures": "workspace:*",
|
||||
"@types/ramda": "0.28.20",
|
||||
"@types/semver": "7.3.13",
|
||||
"write-yaml-file": "^4.2.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@pnpm/cli-utils": "workspace:*",
|
||||
"@pnpm/config": "workspace:*",
|
||||
"@pnpm/constants": "workspace:*",
|
||||
"@pnpm/error": "workspace:*",
|
||||
"@pnpm/modules-yaml": "workspace:*",
|
||||
"@pnpm/parse-wanted-dependency": "workspace:*",
|
||||
"@pnpm/patching.apply-patch": "workspace:*",
|
||||
"@pnpm/pick-registry-for-package": "workspace:*",
|
||||
@@ -51,10 +55,15 @@
|
||||
"@pnpm/read-package-json": "workspace:*",
|
||||
"@pnpm/read-project-manifest": "workspace:*",
|
||||
"@pnpm/store-connection-manager": "workspace:*",
|
||||
"@pnpm/lockfile-file": "workspace:*",
|
||||
"@pnpm/lockfile-utils": "workspace:*",
|
||||
"enquirer": "^2.3.6",
|
||||
"escape-string-regexp": "^4.0.0",
|
||||
"ramda": "npm:@pnpm/ramda@0.28.1",
|
||||
"realpath-missing": "^1.1.0",
|
||||
"render-help": "^1.0.3",
|
||||
"safe-execa": "^0.1.3",
|
||||
"semver": "^7.3.8",
|
||||
"tempy": "^1.0.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
import path from 'path'
|
||||
import { parseWantedDependency, ParseWantedDependencyResult } from '@pnpm/parse-wanted-dependency'
|
||||
import { prompt } from 'enquirer'
|
||||
import { readCurrentLockfile } from '@pnpm/lockfile-file'
|
||||
import { nameVerFromPkgSnapshot } from '@pnpm/lockfile-utils'
|
||||
import { PnpmError } from '@pnpm/error'
|
||||
import { WANTED_LOCKFILE } from '@pnpm/constants'
|
||||
import { readModulesManifest } from '@pnpm/modules-yaml'
|
||||
import realpathMissing from 'realpath-missing'
|
||||
import semver from 'semver'
|
||||
import { Config } from '@pnpm/config'
|
||||
|
||||
type GetPatchedDependencyOptions = {
|
||||
lockfileDir: string
|
||||
} & Pick<Config, 'virtualStoreDir' | 'modulesDir'>
|
||||
|
||||
export async function getPatchedDependency (rawDependency: string, opts: GetPatchedDependencyOptions): Promise<ParseWantedDependencyResult> {
|
||||
const dep = parseWantedDependency(rawDependency)
|
||||
|
||||
const { versions, preferredVersions } = await getVersionsFromLockfile(dep, opts)
|
||||
|
||||
if (!preferredVersions.length) {
|
||||
throw new PnpmError(
|
||||
'PATCH_VERSION_NOT_FOUND',
|
||||
`Can not find ${rawDependency} in project ${opts.lockfileDir}, ${versions.length ? `you can specify currently installed version: ${versions.join(', ')}.` : `did you forget to install ${rawDependency}?`}`
|
||||
)
|
||||
}
|
||||
|
||||
dep.alias = dep.alias ?? rawDependency
|
||||
if (preferredVersions.length > 1) {
|
||||
const { version } = await prompt<{
|
||||
version: string
|
||||
}>({
|
||||
type: 'select',
|
||||
name: 'version',
|
||||
message: 'Choose which version to patch',
|
||||
choices: versions,
|
||||
})
|
||||
dep.pref = version
|
||||
} else {
|
||||
dep.pref = preferredVersions[0]
|
||||
}
|
||||
|
||||
return dep
|
||||
}
|
||||
|
||||
async function getVersionsFromLockfile (dep: ParseWantedDependencyResult, opts: GetPatchedDependencyOptions) {
|
||||
const modulesDir = await realpathMissing(path.join(opts.lockfileDir, opts.modulesDir ?? 'node_modules'))
|
||||
const modules = await readModulesManifest(modulesDir)
|
||||
const lockfile = (modules?.virtualStoreDir && await readCurrentLockfile(modules.virtualStoreDir, {
|
||||
ignoreIncompatible: true,
|
||||
})) ?? null
|
||||
|
||||
if (!lockfile) {
|
||||
throw new PnpmError(
|
||||
'PATCH_NO_LOCKFILE',
|
||||
`No ${WANTED_LOCKFILE} found: Cannot patch without a lockfile`
|
||||
)
|
||||
}
|
||||
|
||||
const pkgName = dep.alias && dep.pref ? dep.alias : (dep.pref ?? dep.alias)
|
||||
|
||||
const versions = Object.entries(lockfile.packages ?? {})
|
||||
.map(([depPath, pkgSnapshot]) => nameVerFromPkgSnapshot(depPath, pkgSnapshot))
|
||||
.filter(({ name }) => name === pkgName)
|
||||
.map(({ version }) => version)
|
||||
|
||||
return {
|
||||
versions,
|
||||
preferredVersions: versions.filter(version => dep.alias && dep.pref ? semver.satisfies(version, dep.pref) : true),
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,9 @@ import pick from 'ramda/src/pick'
|
||||
import renderHelp from 'render-help'
|
||||
import tempy from 'tempy'
|
||||
import { PnpmError } from '@pnpm/error'
|
||||
import { writePackage, ParseWantedDependencyResult } from './writePackage'
|
||||
import { ParseWantedDependencyResult } from '@pnpm/parse-wanted-dependency'
|
||||
import { writePackage } from './writePackage'
|
||||
import { getPatchedDependency } from './getPatchedDependency'
|
||||
|
||||
export function rcOptionsTypes () {
|
||||
return pick([], allTypes)
|
||||
@@ -48,7 +50,16 @@ export function help () {
|
||||
})
|
||||
}
|
||||
|
||||
export type PatchCommandOptions = Pick<Config, 'dir' | 'registries' | 'tag' | 'storeDir' | 'rootProjectManifest' | 'lockfileDir'> & CreateStoreControllerOptions & {
|
||||
export type PatchCommandOptions = Pick<Config,
|
||||
| 'dir'
|
||||
| 'registries'
|
||||
| 'tag'
|
||||
| 'storeDir'
|
||||
| 'rootProjectManifest'
|
||||
| 'lockfileDir'
|
||||
| 'modulesDir'
|
||||
| 'virtualStoreDir'
|
||||
> & CreateStoreControllerOptions & {
|
||||
editDir?: string
|
||||
reporter?: (logObj: LogBase) => void
|
||||
ignoreExisting?: boolean
|
||||
@@ -58,14 +69,24 @@ export async function handler (opts: PatchCommandOptions, params: string[]) {
|
||||
if (opts.editDir && fs.existsSync(opts.editDir) && fs.readdirSync(opts.editDir).length > 0) {
|
||||
throw new PnpmError('PATCH_EDIT_DIR_EXISTS', `The target directory already exists: '${opts.editDir}'`)
|
||||
}
|
||||
if (!params[0]) {
|
||||
throw new PnpmError('MISSING_PACKAGE_NAME', '`pnpm patch` requires the package name')
|
||||
}
|
||||
const editDir = opts.editDir ?? tempy.directory()
|
||||
const patchedDep = await writePackage(params[0], editDir, opts)
|
||||
const lockfileDir = opts.lockfileDir ?? opts.dir ?? process.cwd()
|
||||
const patchedDep = await getPatchedDependency(params[0], {
|
||||
lockfileDir,
|
||||
modulesDir: opts.modulesDir,
|
||||
virtualStoreDir: opts.virtualStoreDir,
|
||||
})
|
||||
|
||||
await writePackage(patchedDep, editDir, opts)
|
||||
if (!opts.ignoreExisting && opts.rootProjectManifest?.pnpm?.patchedDependencies) {
|
||||
tryPatchWithExistingPatchFile({
|
||||
patchedDep,
|
||||
patchedDir: editDir,
|
||||
patchedDependencies: opts.rootProjectManifest.pnpm.patchedDependencies,
|
||||
lockfileDir: opts.lockfileDir ?? opts.dir ?? process.cwd(),
|
||||
lockfileDir,
|
||||
})
|
||||
}
|
||||
return `You can now edit the following folder: ${editDir}
|
||||
|
||||
@@ -11,6 +11,7 @@ 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'
|
||||
|
||||
export const rcOptionsTypes = cliOptionsTypes
|
||||
|
||||
@@ -37,7 +38,7 @@ export async function handler (opts: install.InstallCommandOptions & Pick<Config
|
||||
const patchedPkgManifest = await readPackageJsonFromDir(userDir)
|
||||
const pkgNameAndVersion = `${patchedPkgManifest.name}@${patchedPkgManifest.version}`
|
||||
const srcDir = tempy.directory()
|
||||
await writePackage(pkgNameAndVersion, srcDir, opts)
|
||||
await writePackage(parseWantedDependency(pkgNameAndVersion), srcDir, opts)
|
||||
const patchContent = await diffFolders(srcDir, userDir)
|
||||
const patchFileName = pkgNameAndVersion.replace('/', '__')
|
||||
await fs.promises.writeFile(path.join(patchesDir, `${patchFileName}.patch`), patchContent, 'utf8')
|
||||
|
||||
@@ -4,14 +4,11 @@ import {
|
||||
CreateStoreControllerOptions,
|
||||
} from '@pnpm/store-connection-manager'
|
||||
import { pickRegistryForPackage } from '@pnpm/pick-registry-for-package'
|
||||
import { parseWantedDependency, ParseWantedDependencyResult } from '@pnpm/parse-wanted-dependency'
|
||||
|
||||
export { ParseWantedDependencyResult }
|
||||
import type { ParseWantedDependencyResult } from '@pnpm/parse-wanted-dependency'
|
||||
|
||||
export type WritePackageOptions = CreateStoreControllerOptions & Pick<Config, 'registries'>
|
||||
|
||||
export async function writePackage (pkg: string, dest: string, opts: WritePackageOptions): Promise<ParseWantedDependencyResult> {
|
||||
const dep = parseWantedDependency(pkg)
|
||||
export async function writePackage (dep: ParseWantedDependencyResult, dest: string, opts: WritePackageOptions) {
|
||||
const store = await createOrConnectStoreController({
|
||||
...opts,
|
||||
packageImportMethod: 'clone-or-copy',
|
||||
@@ -28,5 +25,4 @@ export async function writePackage (pkg: string, dest: string, opts: WritePackag
|
||||
filesResponse,
|
||||
force: true,
|
||||
})
|
||||
return dep
|
||||
}
|
||||
|
||||
@@ -10,31 +10,51 @@ import { patch, patchCommit } from '@pnpm/plugin-commands-patching'
|
||||
import { readProjectManifest } from '@pnpm/read-project-manifest'
|
||||
import { REGISTRY_MOCK_PORT } from '@pnpm/registry-mock'
|
||||
import { DEFAULT_OPTS } from './utils/index'
|
||||
import { fixtures } from '@pnpm/test-fixtures'
|
||||
import * as enquirer from 'enquirer'
|
||||
|
||||
jest.mock('enquirer', () => ({ prompt: jest.fn() }))
|
||||
|
||||
// eslint-disable-next-line
|
||||
const prompt = enquirer.prompt as any
|
||||
const f = fixtures(__dirname)
|
||||
const customModulesDirFixture = f.find('custom-modules-dir')
|
||||
|
||||
const basePatchOption = {
|
||||
pnpmHomeDir: '',
|
||||
rawConfig: {
|
||||
registry: `http://localhost:${REGISTRY_MOCK_PORT}/`,
|
||||
},
|
||||
registries: { default: `http://localhost:${REGISTRY_MOCK_PORT}/` },
|
||||
userConfig: {},
|
||||
virtualStoreDir: 'node_modules/.pnpm',
|
||||
}
|
||||
|
||||
describe('patch and commit', () => {
|
||||
let defaultPatchOption: patch.PatchCommandOptions
|
||||
|
||||
beforeEach(() => {
|
||||
beforeEach(async () => {
|
||||
prepare({
|
||||
dependencies: {
|
||||
'is-positive': '1.0.0',
|
||||
},
|
||||
})
|
||||
|
||||
const cacheDir = path.resolve('cache')
|
||||
const storeDir = path.resolve('store')
|
||||
|
||||
defaultPatchOption = {
|
||||
...basePatchOption,
|
||||
cacheDir,
|
||||
dir: process.cwd(),
|
||||
pnpmHomeDir: '',
|
||||
rawConfig: {
|
||||
registry: `http://localhost:${REGISTRY_MOCK_PORT}/`,
|
||||
},
|
||||
registries: { default: `http://localhost:${REGISTRY_MOCK_PORT}/` },
|
||||
storeDir,
|
||||
userConfig: {},
|
||||
}
|
||||
|
||||
await install.handler({
|
||||
...DEFAULT_OPTS,
|
||||
cacheDir,
|
||||
storeDir,
|
||||
dir: process.cwd(),
|
||||
saveLockfile: true,
|
||||
})
|
||||
})
|
||||
|
||||
test('patch and commit', async () => {
|
||||
@@ -55,6 +75,8 @@ describe('patch and commit', () => {
|
||||
await patchCommit.handler({
|
||||
...DEFAULT_OPTS,
|
||||
dir: process.cwd(),
|
||||
frozenLockfile: false,
|
||||
fixLockfile: true,
|
||||
}, [patchDir])
|
||||
|
||||
const { manifest } = await readProjectManifest(process.cwd())
|
||||
@@ -84,6 +106,8 @@ describe('patch and commit', () => {
|
||||
await patchCommit.handler({
|
||||
...DEFAULT_OPTS,
|
||||
dir: process.cwd(),
|
||||
frozenLockfile: false,
|
||||
fixLockfile: true,
|
||||
}, [patchDir])
|
||||
|
||||
expect(fs.readFileSync('node_modules/is-positive/index.js', 'utf8')).toContain('// test patching')
|
||||
@@ -111,6 +135,8 @@ describe('patch and commit', () => {
|
||||
await patchCommit.handler({
|
||||
...DEFAULT_OPTS,
|
||||
dir: process.cwd(),
|
||||
frozenLockfile: false,
|
||||
fixLockfile: true,
|
||||
}, [patchDir])
|
||||
|
||||
expect(fs.readFileSync('node_modules/is-positive/index.js', 'utf8')).toContain('// test patching')
|
||||
@@ -126,6 +152,8 @@ describe('patch and commit', () => {
|
||||
await patchCommit.handler({
|
||||
...DEFAULT_OPTS,
|
||||
dir: process.cwd(),
|
||||
frozenLockfile: false,
|
||||
fixLockfile: true,
|
||||
}, [patchDir])
|
||||
|
||||
const { manifest } = await readProjectManifest(process.cwd())
|
||||
@@ -171,6 +199,8 @@ describe('patch and commit', () => {
|
||||
await patchCommit.handler({
|
||||
...DEFAULT_OPTS,
|
||||
dir: process.cwd(),
|
||||
frozenLockfile: false,
|
||||
fixLockfile: true,
|
||||
}, [patchDir])
|
||||
|
||||
const { manifest } = await readProjectManifest(process.cwd())
|
||||
@@ -187,12 +217,101 @@ describe('patch and commit', () => {
|
||||
expect(fs.existsSync(path.join(patchDir, 'license'))).toBe(true)
|
||||
expect(fs.readFileSync(path.join(patchDir, 'index.js'), 'utf8')).not.toContain('// test patching')
|
||||
})
|
||||
|
||||
test('patch throw an error if no package specified', async () => {
|
||||
await expect(() => patch.handler({ ...defaultPatchOption }, []))
|
||||
.rejects.toThrow('`pnpm patch` requires the package name')
|
||||
})
|
||||
|
||||
test('should throw an error if no installed versions found for patched package', async () => {
|
||||
await expect(() => patch.handler(defaultPatchOption, ['chalk']))
|
||||
.rejects.toThrow(`Can not find chalk in project ${process.cwd()}, did you forget to install chalk?`)
|
||||
})
|
||||
|
||||
test('should throw an error if no preferred versions found for patched package', async () => {
|
||||
await expect(() => patch.handler(defaultPatchOption, ['is-positive@2.0.0']))
|
||||
.rejects.toThrow(`Can not find is-positive@2.0.0 in project ${process.cwd()}, you can specify currently installed version: 1.0.0.`)
|
||||
})
|
||||
|
||||
test('patch package with installed version', async () => {
|
||||
const output = await patch.handler(defaultPatchOption, ['is-positive@1'])
|
||||
const patchDir = getPatchDirFromPatchOutput(output)
|
||||
const tempDir = os.tmpdir()
|
||||
expect(patchDir).toContain(tempDir)
|
||||
expect(fs.existsSync(patchDir)).toBe(true)
|
||||
expect(JSON.parse(fs.readFileSync(path.join(patchDir, 'package.json'), 'utf8')).version).toBe('1.0.0')
|
||||
})
|
||||
})
|
||||
|
||||
describe('prompt to choose version', () => {
|
||||
let defaultPatchOption: patch.PatchCommandOptions
|
||||
let cacheDir: string
|
||||
let storeDir: string
|
||||
beforeEach(() => {
|
||||
prepare({
|
||||
dependencies: {
|
||||
ava: '5.2.0',
|
||||
chalk: '4.1.2',
|
||||
},
|
||||
})
|
||||
cacheDir = path.resolve('cache')
|
||||
storeDir = path.resolve('store')
|
||||
defaultPatchOption = {
|
||||
...basePatchOption,
|
||||
cacheDir,
|
||||
dir: process.cwd(),
|
||||
storeDir,
|
||||
}
|
||||
})
|
||||
|
||||
test('prompt to choose version if multiple version founded for patched package', async () => {
|
||||
await install.handler({
|
||||
...DEFAULT_OPTS,
|
||||
cacheDir,
|
||||
storeDir,
|
||||
dir: process.cwd(),
|
||||
saveLockfile: true,
|
||||
})
|
||||
prompt.mockResolvedValue({
|
||||
version: '5.2.0',
|
||||
})
|
||||
|
||||
const output = await patch.handler(defaultPatchOption, ['chalk'])
|
||||
|
||||
expect(prompt.mock.calls[0][0].choices).toEqual(expect.arrayContaining(['5.2.0', '4.1.2']))
|
||||
prompt.mockClear()
|
||||
|
||||
const patchDir = getPatchDirFromPatchOutput(output)
|
||||
const tempDir = os.tmpdir()
|
||||
|
||||
expect(patchDir).toContain(tempDir)
|
||||
expect(fs.existsSync(patchDir)).toBe(true)
|
||||
expect(JSON.parse(fs.readFileSync(path.join(patchDir, 'package.json'), 'utf8')).version).toBe('5.2.0')
|
||||
expect(fs.existsSync(path.join(patchDir, 'source/index.js'))).toBe(true)
|
||||
|
||||
fs.appendFileSync(path.join(patchDir, 'source/index.js'), '// test patching', 'utf8')
|
||||
await patchCommit.handler({
|
||||
...DEFAULT_OPTS,
|
||||
dir: process.cwd(),
|
||||
frozenLockfile: false,
|
||||
fixLockfile: true,
|
||||
}, [patchDir])
|
||||
|
||||
const { manifest } = await readProjectManifest(process.cwd())
|
||||
expect(manifest.pnpm?.patchedDependencies).toStrictEqual({
|
||||
'chalk@5.2.0': 'patches/chalk@5.2.0.patch',
|
||||
})
|
||||
const patchContent = fs.readFileSync('patches/chalk@5.2.0.patch', 'utf8')
|
||||
expect(patchContent).toContain('diff --git')
|
||||
expect(patchContent).toContain('// test patching')
|
||||
expect(fs.readFileSync('node_modules/.pnpm/ava@5.2.0/node_modules/chalk/source/index.js', 'utf8')).toContain('// test patching')
|
||||
})
|
||||
})
|
||||
|
||||
describe('patching should work when there is a no EOL in the patched file', () => {
|
||||
let defaultPatchOption: patch.PatchCommandOptions
|
||||
|
||||
beforeEach(() => {
|
||||
beforeEach(async () => {
|
||||
prepare({
|
||||
dependencies: {
|
||||
'safe-execa': '0.1.2',
|
||||
@@ -203,16 +322,19 @@ describe('patching should work when there is a no EOL in the patched file', () =
|
||||
const storeDir = path.resolve('store')
|
||||
|
||||
defaultPatchOption = {
|
||||
...basePatchOption,
|
||||
cacheDir,
|
||||
dir: process.cwd(),
|
||||
pnpmHomeDir: '',
|
||||
rawConfig: {
|
||||
registry: `http://localhost:${REGISTRY_MOCK_PORT}/`,
|
||||
},
|
||||
registries: { default: `http://localhost:${REGISTRY_MOCK_PORT}/` },
|
||||
storeDir,
|
||||
userConfig: {},
|
||||
}
|
||||
|
||||
await install.handler({
|
||||
...DEFAULT_OPTS,
|
||||
cacheDir,
|
||||
storeDir,
|
||||
dir: process.cwd(),
|
||||
saveLockfile: true,
|
||||
})
|
||||
})
|
||||
it('should work when adding content on a newline', async () => {
|
||||
const output = await patch.handler(defaultPatchOption, ['safe-execa@0.1.2'])
|
||||
@@ -228,6 +350,8 @@ describe('patching should work when there is a no EOL in the patched file', () =
|
||||
await patchCommit.handler({
|
||||
...DEFAULT_OPTS,
|
||||
dir: process.cwd(),
|
||||
frozenLockfile: false,
|
||||
fixLockfile: true,
|
||||
}, [userPatchDir])
|
||||
|
||||
const { manifest } = await readProjectManifest(process.cwd())
|
||||
@@ -254,6 +378,8 @@ describe('patching should work when there is a no EOL in the patched file', () =
|
||||
await patchCommit.handler({
|
||||
...DEFAULT_OPTS,
|
||||
dir: process.cwd(),
|
||||
frozenLockfile: false,
|
||||
fixLockfile: true,
|
||||
}, [userPatchDir])
|
||||
|
||||
const { manifest } = await readProjectManifest(process.cwd())
|
||||
@@ -300,15 +426,10 @@ describe('patch and commit in workspaces', () => {
|
||||
storeDir = path.resolve('store')
|
||||
|
||||
defaultPatchOption = {
|
||||
...basePatchOption,
|
||||
cacheDir,
|
||||
dir: process.cwd(),
|
||||
pnpmHomeDir: '',
|
||||
rawConfig: {
|
||||
registry: `http://localhost:${REGISTRY_MOCK_PORT}/`,
|
||||
},
|
||||
registries: { default: `http://localhost:${REGISTRY_MOCK_PORT}/` },
|
||||
storeDir,
|
||||
userConfig: {},
|
||||
}
|
||||
})
|
||||
|
||||
@@ -322,11 +443,11 @@ describe('patch and commit in workspaces', () => {
|
||||
allProjects,
|
||||
allProjectsGraph,
|
||||
dir: process.cwd(),
|
||||
lockfileDir: process.cwd(),
|
||||
selectedProjectsGraph,
|
||||
workspaceDir: process.cwd(),
|
||||
saveLockfile: true,
|
||||
})
|
||||
|
||||
const output = await patch.handler(defaultPatchOption, ['is-positive@1.0.0'])
|
||||
const patchDir = getPatchDirFromPatchOutput(output)
|
||||
const tempDir = os.tmpdir()
|
||||
@@ -350,6 +471,8 @@ describe('patch and commit in workspaces', () => {
|
||||
lockfileDir: process.cwd(),
|
||||
workspaceDir: process.cwd(),
|
||||
saveLockfile: true,
|
||||
frozenLockfile: false,
|
||||
fixLockfile: true,
|
||||
}, [patchDir])
|
||||
|
||||
const { manifest } = await readProjectManifest(process.cwd())
|
||||
@@ -366,6 +489,66 @@ describe('patch and commit in workspaces', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('patch with custom modules-dir and virtual-store-dir', () => {
|
||||
const cacheDir = path.resolve(customModulesDirFixture, 'cache')
|
||||
const storeDir = path.resolve(customModulesDirFixture, 'store')
|
||||
const defaultPatchOption = {
|
||||
...basePatchOption,
|
||||
cacheDir,
|
||||
dir: customModulesDirFixture,
|
||||
storeDir,
|
||||
modulesDir: 'fake_modules',
|
||||
virtualStoreDir: 'fake_modules/.fake_store',
|
||||
}
|
||||
|
||||
test('should work with custom modules-dir and virtual-store-dir', async () => {
|
||||
const manifest = fs.readFileSync(path.join(customModulesDirFixture, 'package.json'), 'utf8')
|
||||
const lockfileYaml = fs.readFileSync(path.join(customModulesDirFixture, 'pnpm-lock.yaml'), 'utf8')
|
||||
const { allProjects, allProjectsGraph, selectedProjectsGraph } = await readProjects(customModulesDirFixture, [])
|
||||
await install.handler({
|
||||
...DEFAULT_OPTS,
|
||||
cacheDir,
|
||||
storeDir,
|
||||
dir: customModulesDirFixture,
|
||||
lockfileDir: customModulesDirFixture,
|
||||
allProjects,
|
||||
allProjectsGraph,
|
||||
selectedProjectsGraph,
|
||||
workspaceDir: customModulesDirFixture,
|
||||
saveLockfile: true,
|
||||
modulesDir: 'fake_modules',
|
||||
virtualStoreDir: 'fake_modules/.fake_store',
|
||||
})
|
||||
const output = await patch.handler(defaultPatchOption, ['is-positive@1'])
|
||||
const patchDir = getPatchDirFromPatchOutput(output)
|
||||
const tempDir = os.tmpdir()
|
||||
expect(patchDir).toContain(tempDir)
|
||||
expect(fs.existsSync(patchDir)).toBe(true)
|
||||
expect(JSON.parse(fs.readFileSync(path.join(patchDir, 'package.json'), 'utf8')).version).toBe('1.0.0')
|
||||
|
||||
fs.appendFileSync(path.join(patchDir, 'index.js'), '// test patching', 'utf8')
|
||||
|
||||
await patchCommit.handler({
|
||||
...DEFAULT_OPTS,
|
||||
dir: customModulesDirFixture,
|
||||
saveLockfile: true,
|
||||
frozenLockfile: false,
|
||||
fixLockfile: true,
|
||||
allProjects,
|
||||
allProjectsGraph,
|
||||
selectedProjectsGraph,
|
||||
modulesDir: 'fake_modules',
|
||||
virtualStoreDir: 'fake_modules/.fake_store',
|
||||
lockfileDir: customModulesDirFixture,
|
||||
workspaceDir: customModulesDirFixture,
|
||||
}, [patchDir])
|
||||
expect(fs.readFileSync(path.join(customModulesDirFixture, 'packages/bar/fake_modules/is-positive/index.js'), 'utf8')).toContain('// test patching')
|
||||
// restore package.json and package-lock.yaml
|
||||
fs.writeFileSync(path.join(customModulesDirFixture, 'package.json'), manifest, 'utf8')
|
||||
fs.writeFileSync(path.join(customModulesDirFixture, 'pnpm-lock.yaml'), lockfileYaml, 'utf8')
|
||||
})
|
||||
})
|
||||
|
||||
function getPatchDirFromPatchOutput (output: string) {
|
||||
const [firstLine] = output.split('\n')
|
||||
return firstLine.substring(firstLine.indexOf(':') + 1).trim()
|
||||
|
||||
@@ -12,6 +12,9 @@
|
||||
{
|
||||
"path": "../../__utils__/prepare"
|
||||
},
|
||||
{
|
||||
"path": "../../__utils__/test-fixtures"
|
||||
},
|
||||
{
|
||||
"path": "../../cli/cli-utils"
|
||||
},
|
||||
@@ -21,12 +24,24 @@
|
||||
{
|
||||
"path": "../../config/pick-registry-for-package"
|
||||
},
|
||||
{
|
||||
"path": "../../lockfile/lockfile-file"
|
||||
},
|
||||
{
|
||||
"path": "../../lockfile/lockfile-utils"
|
||||
},
|
||||
{
|
||||
"path": "../../packages/constants"
|
||||
},
|
||||
{
|
||||
"path": "../../packages/error"
|
||||
},
|
||||
{
|
||||
"path": "../../packages/parse-wanted-dependency"
|
||||
},
|
||||
{
|
||||
"path": "../../pkg-manager/modules-yaml"
|
||||
},
|
||||
{
|
||||
"path": "../../pkg-manager/plugin-commands-installation"
|
||||
},
|
||||
|
||||
30
pnpm-lock.yaml
generated
30
pnpm-lock.yaml
generated
@@ -2486,12 +2486,24 @@ importers:
|
||||
'@pnpm/config':
|
||||
specifier: workspace:*
|
||||
version: link:../../config/config
|
||||
'@pnpm/constants':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/constants
|
||||
'@pnpm/error':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/error
|
||||
'@pnpm/lockfile-file':
|
||||
specifier: workspace:*
|
||||
version: link:../../lockfile/lockfile-file
|
||||
'@pnpm/lockfile-utils':
|
||||
specifier: workspace:*
|
||||
version: link:../../lockfile/lockfile-utils
|
||||
'@pnpm/logger':
|
||||
specifier: ^5.0.0
|
||||
version: 5.0.0
|
||||
'@pnpm/modules-yaml':
|
||||
specifier: workspace:*
|
||||
version: link:../../pkg-manager/modules-yaml
|
||||
'@pnpm/parse-wanted-dependency':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/parse-wanted-dependency
|
||||
@@ -2513,18 +2525,27 @@ importers:
|
||||
'@pnpm/store-connection-manager':
|
||||
specifier: workspace:*
|
||||
version: link:../../store/store-connection-manager
|
||||
enquirer:
|
||||
specifier: ^2.3.6
|
||||
version: 2.3.6
|
||||
escape-string-regexp:
|
||||
specifier: ^4.0.0
|
||||
version: 4.0.0
|
||||
ramda:
|
||||
specifier: npm:@pnpm/ramda@0.28.1
|
||||
version: /@pnpm/ramda@0.28.1
|
||||
realpath-missing:
|
||||
specifier: ^1.1.0
|
||||
version: 1.1.0
|
||||
render-help:
|
||||
specifier: ^1.0.3
|
||||
version: 1.0.3
|
||||
safe-execa:
|
||||
specifier: ^0.1.3
|
||||
version: 0.1.3
|
||||
semver:
|
||||
specifier: ^7.3.8
|
||||
version: 7.3.8
|
||||
tempy:
|
||||
specifier: ^1.0.1
|
||||
version: 1.0.1
|
||||
@@ -2541,9 +2562,15 @@ importers:
|
||||
'@pnpm/registry-mock':
|
||||
specifier: 3.5.0
|
||||
version: 3.5.0(typanion@3.12.1)
|
||||
'@pnpm/test-fixtures':
|
||||
specifier: workspace:*
|
||||
version: link:../../__utils__/test-fixtures
|
||||
'@types/ramda':
|
||||
specifier: 0.28.20
|
||||
version: 0.28.20
|
||||
'@types/semver':
|
||||
specifier: 7.3.13
|
||||
version: 7.3.13
|
||||
write-yaml-file:
|
||||
specifier: ^4.2.0
|
||||
version: 4.2.0
|
||||
@@ -8006,7 +8033,7 @@ packages:
|
||||
'@pnpm/find-workspace-dir': 5.0.1
|
||||
'@pnpm/find-workspace-packages': 5.0.36(@pnpm/logger@5.0.0)(@yarnpkg/core@4.0.0-rc.14)(typanion@3.12.1)
|
||||
'@pnpm/logger': 5.0.0
|
||||
'@pnpm/types': 8.10.0
|
||||
'@pnpm/types': 8.9.0
|
||||
'@yarnpkg/core': 4.0.0-rc.14(typanion@3.12.1)
|
||||
load-json-file: 7.0.1
|
||||
meow: 10.1.5
|
||||
@@ -8555,7 +8582,6 @@ packages:
|
||||
/@pnpm/types@8.9.0:
|
||||
resolution: {integrity: sha512-3MYHYm8epnciApn6w5Fzx6sepawmsNU7l6lvIq+ER22/DPSrr83YMhU/EQWnf4lORn2YyiXFj0FJSyJzEtIGmw==}
|
||||
engines: {node: '>=14.6'}
|
||||
dev: false
|
||||
|
||||
/@pnpm/util.lex-comparator@1.0.0:
|
||||
resolution: {integrity: sha512-3aBQPHntVgk5AweBWZn+1I/fqZ9krK/w01197aYVkAJQGftb+BVWgEepxY5GChjSW12j52XX+CmfynYZ/p0DFQ==}
|
||||
|
||||
@@ -168,7 +168,7 @@ export async function main (inputArgv: string[]) {
|
||||
}
|
||||
|
||||
if (
|
||||
(cmd === 'install' || cmd === 'import' || cmd === 'dedupe' || cmd === 'patch-commit') &&
|
||||
(cmd === 'install' || cmd === 'import' || cmd === 'dedupe' || cmd === 'patch-commit' || cmd === 'patch') &&
|
||||
typeof workspaceDir === 'string'
|
||||
) {
|
||||
cliOptions['recursive'] = true
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import path from 'path'
|
||||
import { readProjectManifestOnly } from '@pnpm/read-project-manifest'
|
||||
import { DependenciesField, Registries } from '@pnpm/types'
|
||||
import { buildDependenciesHierarchy } from '@pnpm/reviewing.dependencies-hierarchy'
|
||||
import { PackageNode, buildDependenciesHierarchy } from '@pnpm/reviewing.dependencies-hierarchy'
|
||||
import { createPackagesSearcher } from './createPackagesSearcher'
|
||||
import { renderJson } from './renderJson'
|
||||
import { renderParseable } from './renderParseable'
|
||||
@@ -18,6 +19,36 @@ const DEFAULTS = {
|
||||
showExtraneous: true,
|
||||
}
|
||||
|
||||
export function flattenSearchedPackages (pkgs: PackageDependencyHierarchy[], opts: {
|
||||
lockfileDir: string
|
||||
}) {
|
||||
const flattedPkgs: Array<PackageDependencyHierarchy & { depPath: string }> = []
|
||||
for (const pkg of pkgs) {
|
||||
_walker([
|
||||
...(pkg.optionalDependencies ?? []),
|
||||
...(pkg.dependencies ?? []),
|
||||
...(pkg.devDependencies ?? []),
|
||||
...(pkg.unsavedDependencies ?? []),
|
||||
], path.relative(opts.lockfileDir, pkg.path) || '.')
|
||||
}
|
||||
|
||||
return flattedPkgs
|
||||
|
||||
function _walker (packages: PackageNode[], depPath: string) {
|
||||
for (const pkg of packages) {
|
||||
const nextDepPath = `${depPath} > ${pkg.name}@${pkg.version}`
|
||||
if (pkg.dependencies?.length) {
|
||||
_walker(pkg.dependencies, nextDepPath)
|
||||
} else {
|
||||
flattedPkgs.push({
|
||||
depPath: nextDepPath,
|
||||
...pkg,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function searchForPackages (
|
||||
packages: string[],
|
||||
projectPaths: string[],
|
||||
|
||||
Reference in New Issue
Block a user