feat: ignore packages listed in package.json > pnpm.updateConfig.ignoreDependencies on update/outdated commands (#5408)

closes #5358

Co-authored-by: Zoltan Kochan <z@kochan.io>
This commit is contained in:
Shinobu Hayashi
2022-10-02 10:55:37 +09:00
committed by GitHub
parent 5179dfe6d9
commit d665f3ff7a
13 changed files with 204 additions and 9 deletions

View File

@@ -0,0 +1,8 @@
---
"@pnpm/outdated": minor
"@pnpm/plugin-commands-installation": minor
"@pnpm/plugin-commands-outdated": minor
"@pnpm/types": minor
---
Ignore packages listed in package.json > pnpm.updateConfig.ignoreDependencies fields on update/outdated command [#5358](https://github.com/pnpm/pnpm/issues/5358)

View File

@@ -0,0 +1,15 @@
{
"name": "with-pnpm-update-ignore",
"version": "1.0.0",
"dependencies": {
"is-positive": "1.0.0",
"is-negative": "1.0.0"
},
"pnpm": {
"updateConfig": {
"ignoreDependencies": [
"is-positive"
]
}
}
}

View File

@@ -0,0 +1,21 @@
lockfileVersion: 5.4
specifiers:
is-negative: 1.0.0
is-positive: 1.0.0
dependencies:
is-negative: 1.0.0
is-positive: 1.0.0
packages:
/is-negative/1.0.0:
resolution: {integrity: sha512-1aKMsFUc7vYQGzt//8zhkjRWPoYkajY/I5MJEvrc0pDoHXrW7n5ri8DYxhy3rR+Dk0QFl7GjHHsZU1sppQrWtw==}
engines: {node: '>=0.10.0'}
dev: false
/is-positive/1.0.0:
resolution: {integrity: sha512-xxzPGZ4P2uN6rROUa5N9Z7zTX6ERuE0hs6GUOc/cKBLF2NqKc16UwqHMt3tFg4CO6EBTE5UecUasg+3jZx3Ckg==}
engines: {node: '>=0.10.0'}
dev: false

View File

@@ -34,6 +34,7 @@ export default async function outdated (
compatible?: boolean
currentLockfile: Lockfile | null
getLatestManifest: GetLatestManifestFunction
ignoreDependencies?: Set<string>
include?: IncludedDependencies
lockfileDir: string
manifest: ProjectManifest
@@ -69,8 +70,10 @@ export default async function outdated (
pkgs.map(async (alias) => {
const ref = opts.wantedLockfile!.importers[importerId][depType]![alias]
// ignoring linked packages. (For backward compatibility)
if (ref.startsWith('file:')) {
if (
ref.startsWith('file:') || // ignoring linked packages. (For backward compatibility)
opts.ignoreDependencies?.has(alias)
) {
return
}

View File

@@ -18,6 +18,7 @@ export default async function outdatedDepsOfProjects (
args: string[],
opts: Omit<ManifestGetterOptions, 'fullMetadata' | 'lockfileDir'> & {
compatible?: boolean
ignoreDependencies?: Set<string>
include: IncludedDependencies
} & Partial<Pick<ManifestGetterOptions, 'fullMetadata' | 'lockfileDir'>>
): Promise<OutdatedPackage[][]> {
@@ -44,6 +45,7 @@ export default async function outdatedDepsOfProjects (
compatible: opts.compatible,
currentLockfile,
getLatestManifest,
ignoreDependencies: opts.ignoreDependencies,
include: opts.include,
lockfileDir,
manifest,

View File

@@ -1 +1,7 @@
module.exports = require('../../jest.config.js')
module.exports = {
...require('../../jest.config.js'),
// This is a temporary workaround.
// Currently, multiple tests use the @pnpm.e2e/foo package and they change it's dist-tags.
// These tests are in separate files, so sometimes they will simultaneously set the dist tag and fail because they expect different versions to be tagged.
maxWorkers: 1,
}

View File

@@ -24,7 +24,7 @@ import getOptionsFromRootManifest from './getOptionsFromRootManifest'
import getPinnedVersion from './getPinnedVersion'
import getSaveType from './getSaveType'
import getNodeExecPath from './nodeExecPath'
import recursive, { createMatcher, matchDependencies } from './recursive'
import recursive, { createMatcher, matchDependencies, makeIgnorePatterns, UpdateDepsMatcher } from './recursive'
import updateToLatestSpecsFromManifest, { createLatestSpecs } from './updateToLatestSpecsFromManifest'
import { createWorkspaceSpecs, updateToWorkspacePackagesFromManifest } from './updateWorkspaceDependencies'
@@ -199,7 +199,18 @@ when running add/update with the --workspace option')
}
}
const updateMatch = opts.update && (params.length > 0) ? createMatcher(params) : null
let updateMatch: UpdateDepsMatcher | null
if (opts.update) {
if (params.length === 0) {
const ignoreDeps = manifest.pnpm?.updateConfig?.ignoreDependencies
if (ignoreDeps?.length) {
params = makeIgnorePatterns(ignoreDeps)
}
}
updateMatch = params.length ? createMatcher(params) : null
} else {
updateMatch = null
}
if (updateMatch != null) {
params = matchDependencies(updateMatch, manifest, includeDirect)
if (params.length === 0 && opts.depth === 0) {

View File

@@ -173,8 +173,18 @@ export default async function recursive (
optionalDependencies: true,
}
const updateMatch = cmdFullName === 'update' && (params.length > 0) ? createMatcher(params) : null
let updateMatch: UpdateDepsMatcher | null
if (cmdFullName === 'update') {
if (params.length === 0) {
const ignoreDeps = manifestsByPath[opts.workspaceDir]?.manifest?.pnpm?.updateConfig?.ignoreDependencies
if (ignoreDeps?.length) {
params = makeIgnorePatterns(ignoreDeps)
}
}
updateMatch = params.length ? createMatcher(params) : null
} else {
updateMatch = null
}
// For a workspace with shared lockfile
if (opts.lockfileDir && ['add', 'install', 'remove', 'update', 'import'].includes(cmdFullName)) {
let importers = await getImporters()
@@ -499,7 +509,9 @@ export function matchDependencies (
return matchedDeps
}
export function createMatcher (params: string[]) {
export type UpdateDepsMatcher = (input: string) => string | null
export function createMatcher (params: string[]): UpdateDepsMatcher {
const patterns: string[] = []
const specs: string[] = []
for (const param of params) {
@@ -519,3 +531,7 @@ export function createMatcher (params: string[]) {
return specs[index]
}
}
export function makeIgnorePatterns (ignoredDependencies: string[]): string[] {
return ignoredDependencies.map(depName => `!${depName}`)
}

View File

@@ -224,3 +224,92 @@ test('update should work normal when set empty string version', async () => {
expect(lockfile.devDependencies['@pnpm.e2e/foo']).toEqual('2.0.0')
expect(lockfile.devDependencies['@pnpm.e2e/peer-c']).toEqual('2.0.0')
})
test('ignore packages in package.json > updateConfig.ignoreDependencies fields in update command', async () => {
await addDistTag({ package: '@pnpm.e2e/foo', version: '100.0.0', distTag: 'latest' })
await addDistTag({ package: '@pnpm.e2e/bar', version: '100.0.0', distTag: 'latest' })
const project = prepare({
dependencies: {
'@pnpm.e2e/foo': '100.0.0',
'@pnpm.e2e/bar': '100.0.0',
},
pnpm: {
updateConfig: {
ignoreDependencies: [
'@pnpm.e2e/foo',
],
},
},
})
await install.handler({
...DEFAULT_OPTS,
dir: process.cwd(),
workspaceDir: process.cwd(),
})
const lockfile = await project.readLockfile()
expect(lockfile.packages['/@pnpm.e2e/foo/100.0.0']).toBeTruthy()
expect(lockfile.packages['/@pnpm.e2e/bar/100.0.0']).toBeTruthy()
await addDistTag({ package: '@pnpm.e2e/foo', version: '100.1.0', distTag: 'latest' })
await addDistTag({ package: '@pnpm.e2e/bar', version: '100.1.0', distTag: 'latest' })
await update.handler({
...DEFAULT_OPTS,
dir: process.cwd(),
workspaceDir: process.cwd(),
latest: true,
})
const lockfileUpdated = await project.readLockfile()
expect(lockfileUpdated.packages['/@pnpm.e2e/foo/100.0.0']).toBeTruthy()
expect(lockfileUpdated.packages['/@pnpm.e2e/bar/100.1.0']).toBeTruthy()
})
test('not ignore packages if these are specified in parameter even if these are listed in package.json > pnpm.update.ignoreDependencies fields in update command', async () => {
await addDistTag({ package: '@pnpm.e2e/foo', version: '100.0.0', distTag: 'latest' })
await addDistTag({ package: '@pnpm.e2e/bar', version: '100.0.0', distTag: 'latest' })
const project = prepare({
dependencies: {
'@pnpm.e2e/foo': '100.0.0',
'@pnpm.e2e/bar': '100.0.0',
},
pnpm: {
updateConfig: {
ignoreDependencies: [
'@pnpm.e2e/foo',
],
},
},
})
await install.handler({
...DEFAULT_OPTS,
dir: process.cwd(),
workspaceDir: process.cwd(),
})
const lockfile = await project.readLockfile()
expect(lockfile.packages['/@pnpm.e2e/foo/100.0.0']).toBeTruthy()
expect(lockfile.packages['/@pnpm.e2e/bar/100.0.0']).toBeTruthy()
await addDistTag({ package: '@pnpm.e2e/foo', version: '100.1.0', distTag: 'latest' })
await addDistTag({ package: '@pnpm.e2e/bar', version: '100.1.0', distTag: 'latest' })
await update.handler({
...DEFAULT_OPTS,
dir: process.cwd(),
workspaceDir: process.cwd(),
}, ['@pnpm.e2e/foo@latest', '@pnpm.e2e/bar@latest'])
const lockfileUpdated = await project.readLockfile()
expect(lockfileUpdated.packages['/@pnpm.e2e/foo/100.1.0']).toBeTruthy()
expect(lockfileUpdated.packages['/@pnpm.e2e/bar/100.1.0']).toBeTruthy()
})

View File

@@ -167,15 +167,17 @@ export async function handler (
const pkgs = Object.values(opts.selectedProjectsGraph).map((wsPkg) => wsPkg.package)
return outdatedRecursive(pkgs, params, { ...opts, include })
}
const manifest = await readProjectManifestOnly(opts.dir, opts)
const packages = [
{
dir: opts.dir,
manifest: await readProjectManifestOnly(opts.dir, opts),
manifest,
},
]
const [outdatedPackages] = await outdatedDepsOfProjects(packages, params, {
...opts,
fullMetadata: opts.long,
ignoreDependencies: new Set(manifest?.pnpm?.updateConfig?.ignoreDependencies ?? []),
include,
retry: {
factor: opts.fetchRetryFactor,

View File

@@ -50,9 +50,11 @@ export default async (
opts: OutdatedCommandOptions & { include: IncludedDependencies }
) => {
const outdatedMap = {} as Record<string, OutdatedInWorkspace>
const rootManifest = pkgs.find(({ dir }) => dir === opts.lockfileDir ?? opts.dir)
const outdatedPackagesByProject = await outdatedDepsOfProjects(pkgs, params, {
...opts,
fullMetadata: opts.long,
ignoreDependencies: new Set(rootManifest?.manifest?.pnpm?.updateConfig?.ignoreDependencies ?? []),
retry: {
factor: opts.fetchRetryFactor,
maxTimeout: opts.fetchRetryMaxtimeout,

View File

@@ -15,6 +15,7 @@ const hasOutdatedDepsFixtureAndExternalLockfile = path.join(fixtures, 'has-outda
const hasNotOutdatedDepsFixture = path.join(fixtures, 'has-not-outdated-deps')
const hasMajorOutdatedDepsFixture = path.join(fixtures, 'has-major-outdated-deps')
const hasNoLockfileFixture = path.join(fixtures, 'has-no-lockfile')
const withPnpmUpdateIgnore = path.join(fixtures, 'with-pnpm-update-ignore')
const REGISTRY_URL = `http://localhost:${REGISTRY_MOCK_PORT}`
@@ -317,3 +318,19 @@ test('pnpm outdated: print only compatible versions', async () => {
└─────────────┴─────────┴────────┘
`)
})
test('ignore packages in package.json > pnpm.updateConfig.ignoreDependencies in outdated command', async () => {
const { output, exitCode } = await outdated.handler({
...OUTDATED_OPTIONS,
dir: withPnpmUpdateIgnore,
})
expect(exitCode).toBe(1)
expect(stripAnsi(output)).toBe(`\
┌─────────────┬─────────┬────────┐
│ Package │ Current │ Latest │
├─────────────┼─────────┼────────┤
│ is-negative │ 1.0.0 │ 2.1.0 │
└─────────────┴─────────┴────────┘
`)
})

View File

@@ -131,6 +131,9 @@ export type ProjectManifest = BaseManifest & {
allowedDeprecatedVersions?: AllowedDeprecatedVersions
allowNonAppliedPatches?: boolean
patchedDependencies?: Record<string, string>
updateConfig?: {
ignoreDependencies?: string[]
}
}
private?: boolean
resolutions?: Record<string, string>