Skip to content

Commit

Permalink
feat: ignore packages listed in `package.json > pnpm.updateConfig.ign…
Browse files Browse the repository at this point in the history
…oreDependencies` on update/outdated commands (#5408)

closes #5358

Co-authored-by: Zoltan Kochan <z@kochan.io>
  • Loading branch information
Shinyaigeek and zkochan committed Oct 2, 2022
1 parent 5179dfe commit d665f3f
Show file tree
Hide file tree
Showing 13 changed files with 204 additions and 9 deletions.
8 changes: 8 additions & 0 deletions .changeset/rude-vans-build.md
@@ -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 changes: 15 additions & 0 deletions fixtures/with-pnpm-update-ignore/package.json
@@ -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 changes: 21 additions & 0 deletions fixtures/with-pnpm-update-ignore/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 5 additions & 2 deletions packages/outdated/src/outdated.ts
Expand Up @@ -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
Expand Down Expand Up @@ -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
}

Expand Down
2 changes: 2 additions & 0 deletions packages/outdated/src/outdatedDepsOfProjects.ts
Expand Up @@ -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[][]> {
Expand All @@ -44,6 +45,7 @@ export default async function outdatedDepsOfProjects (
compatible: opts.compatible,
currentLockfile,
getLatestManifest,
ignoreDependencies: opts.ignoreDependencies,
include: opts.include,
lockfileDir,
manifest,
Expand Down
8 changes: 7 additions & 1 deletion packages/plugin-commands-installation/jest.config.js
@@ -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,
}
15 changes: 13 additions & 2 deletions packages/plugin-commands-installation/src/installDeps.ts
Expand Up @@ -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'

Expand Down Expand Up @@ -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) {
Expand Down
22 changes: 19 additions & 3 deletions packages/plugin-commands-installation/src/recursive.ts
Expand Up @@ -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()
Expand Down Expand Up @@ -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) {
Expand All @@ -519,3 +531,7 @@ export function createMatcher (params: string[]) {
return specs[index]
}
}

export function makeIgnorePatterns (ignoredDependencies: string[]): string[] {
return ignoredDependencies.map(depName => `!${depName}`)
}
89 changes: 89 additions & 0 deletions packages/plugin-commands-installation/test/update/update.ts
Expand Up @@ -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()
})
4 changes: 3 additions & 1 deletion packages/plugin-commands-outdated/src/outdated.ts
Expand Up @@ -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,
Expand Down
2 changes: 2 additions & 0 deletions packages/plugin-commands-outdated/src/recursive.ts
Expand Up @@ -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,
Expand Down
17 changes: 17 additions & 0 deletions packages/plugin-commands-outdated/test/index.ts
Expand Up @@ -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}`

Expand Down Expand Up @@ -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 │
└─────────────┴─────────┴────────┘
`)
})
3 changes: 3 additions & 0 deletions packages/types/src/package.ts
Expand Up @@ -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>
Expand Down

0 comments on commit d665f3f

Please sign in to comment.