feat(cli): forbid combining specs and --latest (#7567)

---------

Co-authored-by: Zoltan Kochan <z@kochan.io>
This commit is contained in:
Khải
2024-01-27 22:37:17 +07:00
committed by Zoltan Kochan
parent df9b16aa98
commit f43bdcf45d
5 changed files with 53 additions and 12 deletions

View File

@@ -0,0 +1,6 @@
---
"@pnpm/plugin-commands-installation": major
"pnpm": major
---
Throw an error if `pnpm update --latest` runs with arguments containing versions specs. For instance, `pnpm update --latest foo@next` is not allowed [#7567](https://github.com/pnpm/pnpm/pull/7567).

View File

@@ -480,14 +480,9 @@ export function createMatcher (params: string[]): UpdateDepsMatcher {
const patterns: string[] = []
const specs: string[] = []
for (const param of params) {
const atIndex = param.indexOf('@', param[0] === '!' ? 2 : 1)
if (atIndex === -1) {
patterns.push(param)
specs.push('')
} else {
patterns.push(param.slice(0, atIndex))
specs.push(param.slice(atIndex + 1))
}
const { pattern, versionSpec } = parseUpdateParam(param)
patterns.push(pattern)
specs.push(versionSpec ?? '')
}
const matcher = createMatcherWithIndex(patterns)
return (depName: string) => {
@@ -497,6 +492,20 @@ export function createMatcher (params: string[]): UpdateDepsMatcher {
}
}
export function parseUpdateParam (param: string): { pattern: string, versionSpec: string | undefined } {
const atIndex = param.indexOf('@', param[0] === '!' ? 2 : 1)
if (atIndex === -1) {
return {
pattern: param,
versionSpec: undefined,
}
}
return {
pattern: param.slice(0, atIndex),
versionSpec: param.slice(atIndex + 1),
}
}
export function makeIgnorePatterns (ignoredDependencies: string[]): string[] {
return ignoredDependencies.map(depName => `!${depName}`)
}

View File

@@ -9,6 +9,7 @@ import { types as allTypes } from '@pnpm/config'
import { globalInfo } from '@pnpm/logger'
import { createMatcher } from '@pnpm/matcher'
import { outdatedDepsOfProjects } from '@pnpm/outdated'
import { PnpmError } from '@pnpm/error'
import { prompt } from 'enquirer'
import chalk from 'chalk'
import pick from 'ramda/src/pick'
@@ -18,6 +19,7 @@ import renderHelp from 'render-help'
import { type InstallCommandOptions } from '../install'
import { installDeps } from '../installDeps'
import { type ChoiceRow, getUpdateChoices } from './getUpdateChoices'
import { parseUpdateParam } from '../recursive'
export function rcOptionsTypes () {
return pick([
'cache-dir',
@@ -275,6 +277,12 @@ async function update (
dependencies: string[],
opts: UpdateCommandOptions
) {
if (opts.latest) {
const dependenciesWithTags = dependencies.filter((name) => parseUpdateParam(name).versionSpec != null)
if (dependenciesWithTags.length) {
throw new PnpmError('LATEST_WITH_SPEC', `Specs are not allowed to be used with --latest (${dependenciesWithTags.join(', ')})`)
}
}
const includeDirect = makeIncludeDependenciesFromCLI(opts.cliOptions)
const include = {
dependencies: opts.rawConfig.production !== false,

View File

@@ -311,7 +311,7 @@ test('recursive update --latest foo should only update projects that have foo',
recursive: true,
selectedProjectsGraph,
workspaceDir: process.cwd(),
}, ['@zkochan/async-regex-replace', '@pnpm.e2e/foo', '@pnpm.e2e/qar@100.1.0'])
}, ['@zkochan/async-regex-replace', '@pnpm.e2e/foo'])
const lockfile = await readYamlFile<Lockfile>('./pnpm-lock.yaml')
@@ -319,7 +319,7 @@ test('recursive update --latest foo should only update projects that have foo',
'/@zkochan/async-regex-replace@0.2.0',
'/@pnpm.e2e/bar@100.0.0',
'/@pnpm.e2e/foo@100.1.0',
'/@pnpm.e2e/qar@100.1.0',
'/@pnpm.e2e/qar@100.0.0',
].sort())
})
@@ -369,12 +369,12 @@ test('recursive update --latest foo should only update packages that have foo',
recursive: true,
selectedProjectsGraph,
workspaceDir: process.cwd(),
}, ['@pnpm.e2e/foo', '@pnpm.e2e/qar@100.1.0'])
}, ['@pnpm.e2e/foo'])
{
const lockfile = await projects['project-1'].readLockfile()
expect(Object.keys(lockfile.packages ?? {})).toStrictEqual(['/@pnpm.e2e/foo@100.1.0', '/@pnpm.e2e/qar@100.1.0'])
expect(Object.keys(lockfile.packages ?? {})).toStrictEqual(['/@pnpm.e2e/foo@100.1.0', '/@pnpm.e2e/qar@100.0.0'])
}
{

View File

@@ -99,6 +99,24 @@ test('update: fail when both "latest" and "workspace" are true', async () => {
expect(err.message).toBe('Cannot use --latest with --workspace simultaneously')
})
test('update --latest forbids specs', async () => {
prepare()
let err!: PnpmError
try {
await update.handler({
...DEFAULT_OPTS,
dir: process.cwd(),
latest: true,
workspaceDir: process.cwd(),
}, ['foo@latest', 'bar@next', 'baz'])
} catch (_err: any) { // eslint-disable-line
err = _err
}
expect(err.code).toBe('ERR_PNPM_LATEST_WITH_SPEC')
expect(err.message).toBe('Specs are not allowed to be used with --latest (foo@latest, bar@next)')
})
describe('update by package name', () => {
beforeAll(async () => {
prepare({