mirror of
https://github.com/pnpm/pnpm.git
synced 2025-12-23 23:29:17 -05:00
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:
8
.changeset/rude-vans-build.md
Normal file
8
.changeset/rude-vans-build.md
Normal 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)
|
||||
15
fixtures/with-pnpm-update-ignore/package.json
Normal file
15
fixtures/with-pnpm-update-ignore/package.json
Normal 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"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
21
fixtures/with-pnpm-update-ignore/pnpm-lock.yaml
generated
Normal file
21
fixtures/with-pnpm-update-ignore/pnpm-lock.yaml
generated
Normal 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
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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}`)
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
})
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 │
|
||||
└─────────────┴─────────┴────────┘
|
||||
`)
|
||||
})
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user