feat: excluding deps from update (#5432)

ref #5408
This commit is contained in:
Zoltan Kochan
2022-09-30 17:52:33 +03:00
committed by GitHub
parent 2bf89ec02f
commit abb41a6263
6 changed files with 176 additions and 36 deletions

View File

@@ -0,0 +1,22 @@
---
"@pnpm/plugin-commands-installation": minor
"pnpm": minor
---
It is possible now to update all dependencies except the listed ones using `!`. For instance, update all dependencies, except `lodash`:
```
pnpm update !lodash
```
It also works with pattends, for instance:
```
pnpm update !@babel/*
```
And it may be combined with other patterns:
```
pnpm update @babel/* !@babel/core
```

View File

@@ -0,0 +1,5 @@
---
"@pnpm/matcher": minor
---
New function added that returns the number of the matched pattern: matcherWithIndex().

View File

@@ -1,14 +1,24 @@
import escapeStringRegexp from 'escape-string-regexp'
type Matcher = (input: string) => boolean
type MatcherWithIndex = (input: string) => number
export default function matcher (patterns: string[] | string): Matcher {
if (typeof patterns === 'string') return matcherWhenOnlyOnePattern(patterns)
const m = matcherWithIndex(Array.isArray(patterns) ? patterns : [patterns])
return (input) => m(input) !== -1
}
interface MatcherFunction {
match: Matcher
ignore: boolean
}
export function matcherWithIndex (patterns: string[]): MatcherWithIndex {
switch (patterns.length) {
case 0: return () => false
case 1: return matcherWhenOnlyOnePattern(patterns[0])
case 0: return () => -1
case 1: return matcherWhenOnlyOnePatternWithIndex(patterns[0])
}
const matchArr: Array<{ match: Matcher, ignore: boolean }> = []
const matchArr: MatcherFunction[] = []
let hasIgnore = false
for (const pattern of patterns) {
if (isIgnorePattern(pattern)) {
@@ -19,19 +29,33 @@ export default function matcher (patterns: string[] | string): Matcher {
}
}
if (!hasIgnore) {
return (input: string) => matchArr.some(({ match }) => match(input))
return matchInputWithNonIgnoreMatchers.bind(null, matchArr)
}
return (input: string) => {
let isMatched = false
for (const { ignore, match } of matchArr) {
if (ignore) {
isMatched = !match(input)
} else if (!isMatched && match(input)) {
isMatched = true
return matchInputWithMatchersArray.bind(null, matchArr)
}
function matchInputWithNonIgnoreMatchers (matchArr: MatcherFunction[], input: string): number {
for (let i = 0; i < matchArr.length; i++) {
if (matchArr[i].match(input)) return i
}
return -1
}
function matchInputWithMatchersArray (matchArr: MatcherFunction[], input: string): number {
let matchedPatternIndex = -1
for (let i = 0; i < matchArr.length; i++) {
const { ignore, match } = matchArr[i]
if (ignore) {
if (match(input)) {
matchedPatternIndex = -1
} else if (matchedPatternIndex === -1) {
matchedPatternIndex = i
}
} else if (matchedPatternIndex === -1 && match(input)) {
matchedPatternIndex = i
}
return isMatched
}
return matchedPatternIndex
}
function matcherFromPattern (pattern: string): Matcher {
@@ -52,8 +76,16 @@ function isIgnorePattern (pattern: string): boolean {
return pattern.startsWith('!')
}
function matcherWhenOnlyOnePattern (pattern: string): Matcher {
return isIgnorePattern(pattern)
? () => false
: matcherFromPattern(pattern)
function matcherWhenOnlyOnePatternWithIndex (pattern: string): MatcherWithIndex {
const m = matcherWhenOnlyOnePattern(pattern)
return (input) => m(input) ? 0 : -1
}
function matcherWhenOnlyOnePattern (pattern: string): Matcher {
if (!isIgnorePattern(pattern)) {
return matcherFromPattern(pattern)
}
const ignorePattern = pattern.substring(1)
const m = matcherFromPattern(ignorePattern)
return (input) => !m(input)
}

View File

@@ -1,4 +1,4 @@
import matcher from '@pnpm/matcher'
import matcher, { matcherWithIndex } from '@pnpm/matcher'
test('matcher()', () => {
{
@@ -47,3 +47,55 @@ test('matcher()', () => {
expect(match('eslint-plugin-bar')).toBe(true)
}
})
test('matcherWithIndex()', () => {
{
const match = matcherWithIndex(['*'])
expect(match('@eslint/plugin-foo')).toBe(0)
expect(match('express')).toBe(0)
}
{
const match = matcherWithIndex(['eslint-*'])
expect(match('eslint-plugin-foo')).toBe(0)
expect(match('express')).toBe(-1)
}
{
const match = matcherWithIndex(['*plugin*'])
expect(match('@eslint/plugin-foo')).toBe(0)
expect(match('express')).toBe(-1)
}
{
const match = matcherWithIndex(['a*c'])
expect(match('abc')).toBe(0)
}
{
const match = matcherWithIndex(['*-positive'])
expect(match('is-positive')).toBe(0)
}
{
const match = matcherWithIndex(['foo', 'bar'])
expect(match('foo')).toBe(0)
expect(match('bar')).toBe(1)
expect(match('express')).toBe(-1)
}
{
const match = matcherWithIndex(['eslint-*', '!eslint-plugin-bar'])
expect(match('eslint-plugin-foo')).toBe(0)
expect(match('eslint-plugin-bar')).toBe(-1)
}
{
const match = matcherWithIndex(['!eslint-plugin-bar', 'eslint-*'])
expect(match('eslint-plugin-foo')).toBe(0)
expect(match('eslint-plugin-bar')).toBe(1)
}
{
const match = matcherWithIndex(['eslint-*', '!eslint-plugin-*', 'eslint-plugin-bar'])
expect(match('eslint-config-foo')).toBe(0)
expect(match('eslint-plugin-foo')).toBe(-1)
expect(match('eslint-plugin-bar')).toBe(2)
}
{
const match = matcherWithIndex(['!@pnpm.e2e/peer-*'])
expect(match('@pnpm.e2e/foo')).toBe(0)
}
})

View File

@@ -9,7 +9,7 @@ import PnpmError from '@pnpm/error'
import { arrayOfWorkspacePackagesToMap } from '@pnpm/find-workspace-packages'
import logger from '@pnpm/logger'
import { filterDependenciesByType } from '@pnpm/manifest-utils'
import matcher from '@pnpm/matcher'
import { matcherWithIndex } from '@pnpm/matcher'
import { rebuild } from '@pnpm/plugin-commands-rebuild'
import { requireHooks } from '@pnpm/pnpmfile'
import sortPackages from '@pnpm/sort-packages'
@@ -500,26 +500,22 @@ export function matchDependencies (
}
export function createMatcher (params: string[]) {
const matchers = params.map((param) => {
const atIndex = param.indexOf('@', 1)
let pattern!: string
let spec!: string
const patterns: string[] = []
const specs: string[] = []
for (const param of params) {
const atIndex = param.indexOf('@', param[0] === '!' ? 2 : 1)
if (atIndex === -1) {
pattern = param
spec = ''
patterns.push(param)
specs.push('')
} else {
pattern = param.slice(0, atIndex)
spec = param.slice(atIndex + 1)
patterns.push(param.slice(0, atIndex))
specs.push(param.slice(atIndex + 1))
}
return {
match: matcher(pattern),
spec,
}
})
}
const matcher = matcherWithIndex(patterns)
return (depName: string) => {
for (const { spec, match } of matchers) {
if (match(depName)) return spec
}
return null
const index = matcher(depName)
if (index === -1) return null
return specs[index]
}
}

View File

@@ -40,6 +40,39 @@ test('update with "*" pattern', async () => {
expect(lockfile.packages['/@pnpm.e2e/foo/1.0.0']).toBeTruthy()
})
test('update with negation pattern', async () => {
await addDistTag({ package: '@pnpm.e2e/peer-a', version: '1.0.1', distTag: 'latest' })
await addDistTag({ package: '@pnpm.e2e/peer-c', version: '2.0.0', distTag: 'latest' })
await addDistTag({ package: '@pnpm.e2e/foo', version: '2.0.0', distTag: 'latest' })
const project = prepare({
dependencies: {
'@pnpm.e2e/peer-a': '1.0.0',
'@pnpm.e2e/peer-c': '1.0.0',
'@pnpm.e2e/foo': '1.0.0',
},
})
await install.handler({
...DEFAULT_OPTS,
dir: process.cwd(),
workspaceDir: process.cwd(),
})
await update.handler({
...DEFAULT_OPTS,
dir: process.cwd(),
latest: true,
workspaceDir: process.cwd(),
}, ['!@pnpm.e2e/peer-*'])
const lockfile = await project.readLockfile()
expect(lockfile.packages['/@pnpm.e2e/peer-a/1.0.0']).toBeTruthy()
expect(lockfile.packages['/@pnpm.e2e/peer-c/1.0.0']).toBeTruthy()
expect(lockfile.packages['/@pnpm.e2e/foo/2.0.0']).toBeTruthy()
})
test('update: fail when both "latest" and "workspace" are true', async () => {
preparePackages([
{