Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: pnpm.ignoredOptionalDependencies #7714

Merged
merged 16 commits into from
Feb 28, 2024
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
26 changes: 26 additions & 0 deletions .changeset/thin-icons-scream.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
---
"@pnpm/resolve-dependencies": minor
"@pnpm/merge-lockfile-changes": minor
"@pnpm/package-requester": minor
"@pnpm/directory-fetcher": minor
"@pnpm/tarball-fetcher": minor
"@pnpm/exec.pkg-requires-build": minor
"@pnpm/hooks.read-package-hook": minor
"@pnpm/lockfile-types": minor
"@pnpm/prune-lockfile": minor
"@pnpm/create-cafs-store": minor
"@pnpm/lockfile-file": minor
"@pnpm/fetcher-base": minor
"@pnpm/headless": minor
"@pnpm/deps.graph-builder": minor
"@pnpm/node.fetcher": minor
"@pnpm/core": minor
"@pnpm/cafs-types": minor
"@pnpm/types": minor
"@pnpm/config": minor
"@pnpm/store.cafs": minor
"@pnpm/worker": minor
"pnpm": minor
---

Add a field named `ignoredOptionalDependencies`. This is an array of strings. If an optional dependency has its name included in this array, it will be skipped.
3 changes: 3 additions & 0 deletions config/config/src/getOptionsFromRootManifest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export interface OptionsFromRootManifest {
onlyBuiltDependencies?: string[]
onlyBuiltDependenciesFile?: string
packageExtensions?: Record<string, PackageExtension>
ignoredOptionalDependencies?: string[]
patchedDependencies?: Record<string, string>
peerDependencyRules?: PeerDependencyRules
supportedArchitectures?: SupportedArchitectures
Expand All @@ -37,6 +38,7 @@ export function getOptionsFromRootManifest (manifestDir: string, manifest: Proje
const onlyBuiltDependencies = manifest.pnpm?.onlyBuiltDependencies
const onlyBuiltDependenciesFile = manifest.pnpm?.onlyBuiltDependenciesFile
const packageExtensions = manifest.pnpm?.packageExtensions
const ignoredOptionalDependencies = manifest.pnpm?.ignoredOptionalDependencies
const peerDependencyRules = manifest.pnpm?.peerDependencyRules
const allowedDeprecatedVersions = manifest.pnpm?.allowedDeprecatedVersions
const allowNonAppliedPatches = manifest.pnpm?.allowNonAppliedPatches
Expand All @@ -61,6 +63,7 @@ export function getOptionsFromRootManifest (manifestDir: string, manifest: Proje
overrides,
neverBuiltDependencies,
packageExtensions,
ignoredOptionalDependencies,
peerDependencyRules,
patchedDependencies,
supportedArchitectures,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import type { BaseManifest, ReadPackageHook } from '@pnpm/types'

export function createOptionalDependenciesRemover (toBeRemoved: string[]): ReadPackageHook {
return <Manifest extends BaseManifest> (manifest: Manifest) => removeOptionalDependencies(manifest, toBeRemoved)
}

function removeOptionalDependencies<Manifest extends BaseManifest> (manifest: Manifest, toBeRemoved: string[]): Manifest {
for (const optionalDependency in manifest.optionalDependencies) {
if (toBeRemoved.includes(optionalDependency)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

probably we need to support pattern matching. Same way like here:

const ignoreMissingMatcher = createMatcher(ignoreMissingPatterns)

delete manifest.optionalDependencies[optionalDependency]
delete manifest.dependencies?.[optionalDependency]
}
}
return manifest
}
6 changes: 6 additions & 0 deletions hooks/read-package-hook/src/createReadPackageHook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
} from '@pnpm/types'
import isEmpty from 'ramda/src/isEmpty'
import pipeWith from 'ramda/src/pipeWith'
import { createOptionalDependenciesRemover } from './createOptionalDependenciesRemover'
import { createPackageExtender } from './createPackageExtender'
import { createVersionsOverrider } from './createVersionsOverrider'
import { createPeerDependencyPatcher } from './createPeerDependencyPatcher'
Expand All @@ -17,13 +18,15 @@ export function createReadPackageHook (
ignoreCompatibilityDb,
lockfileDir,
overrides,
ignoredOptionalDependencies,
packageExtensions,
peerDependencyRules,
readPackageHook,
}: {
ignoreCompatibilityDb?: boolean
lockfileDir: string
overrides?: Record<string, string>
ignoredOptionalDependencies?: string[]
packageExtensions?: Record<string, PackageExtension>
peerDependencyRules?: PeerDependencyRules
readPackageHook?: ReadPackageHook[] | ReadPackageHook
Expand All @@ -44,6 +47,9 @@ export function createReadPackageHook (
if (!isEmpty(overrides ?? {})) {
hooks.push(createVersionsOverrider(overrides!, lockfileDir))
}
if (ignoredOptionalDependencies && !isEmpty(ignoredOptionalDependencies)) {
hooks.push(createOptionalDependenciesRemover(ignoredOptionalDependencies))
}
if (
peerDependencyRules != null &&
(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { createOptionalDependenciesRemover } from '../lib/createOptionalDependenciesRemover'

test('createOptionalDependenciesRemover() removes optional dependencies', async () => {
const removeOptionalDependencies = createOptionalDependenciesRemover(['foo', 'bar'])
expect(
await removeOptionalDependencies({
dependencies: {
foo: '0.1.2',
bar: '2.1.0',
baz: '1.0.0',
qux: '2.0.0',
},
optionalDependencies: {
foo: '0.1.2',
bar: '2.1.0',
baz: '1.0.0',
},
})
).toStrictEqual({
dependencies: {
baz: '1.0.0',
qux: '2.0.0',
},
optionalDependencies: {
baz: '1.0.0',
},
})
})

test('createOptionalDependenciesRemover() does not remove non-optional packages', async () => {
const removeOptionalDependencies = createOptionalDependenciesRemover(['foo', 'bar'])
expect(
await removeOptionalDependencies({
dependencies: {
foo: '0.1.2',
bar: '2.1.0',
baz: '1.0.0',
qux: '2.0.0',
},
optionalDependencies: {
foo: '0.1.2',
baz: '1.0.0',
},
})
).toStrictEqual({
dependencies: {
bar: '2.1.0',
baz: '1.0.0',
qux: '2.0.0',
},
optionalDependencies: {
baz: '1.0.0',
},
})
})
3 changes: 3 additions & 0 deletions lockfile/lockfile-file/src/lockfileFormatConverters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,9 @@ function normalizeLockfile (lockfile: InlineSpecifiersLockfile, opts: NormalizeL
if (!lockfileToSave.packageExtensionsChecksum) {
delete lockfileToSave.packageExtensionsChecksum
}
if (!lockfileToSave.ignoredOptionalDependencies?.length) {
delete lockfileToSave.ignoredOptionalDependencies
}
if (!lockfileToSave.pnpmfileChecksum) {
delete lockfileToSave.pnpmfileChecksum
}
Expand Down
1 change: 1 addition & 0 deletions lockfile/lockfile-types/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export interface Lockfile {
packages?: PackageSnapshots
overrides?: Record<string, string>
packageExtensionsChecksum?: string
ignoredOptionalDependencies?: string[]
patchedDependencies?: Record<string, PatchFile>
pnpmfileChecksum?: string
settings?: LockfileSettings
Expand Down
8 changes: 8 additions & 0 deletions lockfile/merge-lockfile-changes/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,14 @@ export function mergeLockfileChanges (ours: Lockfile, theirs: Lockfile): Lockfil
newLockfile.pnpmfileChecksum = pnpmfileChecksum
}

const ignoredOptionalDependencies = [...new Set([
...ours.ignoredOptionalDependencies ?? [],
...theirs.ignoredOptionalDependencies ?? [],
])]
if (ignoredOptionalDependencies.length) {
newLockfile.ignoredOptionalDependencies = ignoredOptionalDependencies
}

for (const importerId of Array.from(new Set([...Object.keys(ours.importers), ...Object.keys(theirs.importers)]))) {
newLockfile.importers[importerId] = {
specifiers: {},
Expand Down
3 changes: 3 additions & 0 deletions lockfile/prune-lockfile/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,9 @@ export function pruneLockfile (
if (lockfile.pnpmfileChecksum) {
prunedLockfile.pnpmfileChecksum = lockfile.pnpmfileChecksum
}
if (lockfile.ignoredOptionalDependencies && !isEmpty(lockfile.ignoredOptionalDependencies)) {
prunedLockfile.ignoredOptionalDependencies = lockfile.ignoredOptionalDependencies
}
return pruneSharedLockfile(prunedLockfile, opts)
}

Expand Down
1 change: 1 addition & 0 deletions packages/types/src/package.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ export type ProjectManifest = BaseManifest & {
onlyBuiltDependenciesFile?: string
overrides?: Record<string, string>
packageExtensions?: Record<string, PackageExtension>
ignoredOptionalDependencies?: string[]
peerDependencyRules?: PeerDependencyRules
allowedDeprecatedVersions?: AllowedDeprecatedVersions
allowNonAppliedPatches?: boolean
Expand Down
2 changes: 2 additions & 0 deletions pkg-manager/core/src/getPeerDependencyIssues.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export type ListMissingPeersOptions = Partial<GetContextOptions>
| 'nodeLinker'
| 'overrides'
| 'packageExtensions'
| 'ignoredOptionalDependencies'
| 'preferWorkspacePackages'
| 'saveWorkspaceProtocol'
| 'storeController'
Expand Down Expand Up @@ -69,6 +70,7 @@ export async function getPeerDependencyIssues (
overrides: opts.overrides,
packageExtensions: opts.packageExtensions,
readPackageHook: opts.hooks?.readPackage,
ignoredOptionalDependencies: opts.ignoredOptionalDependencies,
}),
},
linkWorkspacePackagesDepth: opts.linkWorkspacePackagesDepth ?? (opts.saveWorkspaceProtocol ? 0 : -1),
Expand Down
3 changes: 3 additions & 0 deletions pkg-manager/core/src/install/extendInstallOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ export interface StrictInstallOptions {
nodeLinker: 'isolated' | 'hoisted' | 'pnp'
nodeVersion: string
packageExtensions: Record<string, PackageExtension>
ignoredOptionalDependencies: string[]
pnpmfile: string
ignorePnpmfile: boolean
packageManager: {
Expand Down Expand Up @@ -197,6 +198,7 @@ const defaults = (opts: InstallOptions) => {
ignoreCompatibilityDb: false,
ignorePackageManifest: false,
packageExtensions: {},
ignoredOptionalDependencies: [] as string[],
packageManager,
preferFrozenLockfile: true,
preferWorkspacePackages: false,
Expand Down Expand Up @@ -272,6 +274,7 @@ export function extendOptions (
lockfileDir: extendedOpts.lockfileDir,
packageExtensions: extendedOpts.packageExtensions,
peerDependencyRules: extendedOpts.peerDependencyRules,
ignoredOptionalDependencies: extendedOpts.ignoredOptionalDependencies,
})
if (extendedOpts.lockfileOnly) {
extendedOpts.ignoreScripts = true
Expand Down
7 changes: 7 additions & 0 deletions pkg-manager/core/src/install/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,7 @@ export async function mutateModules (
autoInstallPeers: opts.autoInstallPeers,
excludeLinksFromLockfile: opts.excludeLinksFromLockfile,
overrides: opts.overrides,
ignoredOptionalDependencies: opts.ignoredOptionalDependencies?.sort(),
packageExtensionsChecksum,
patchedDependencies,
pnpmfileChecksum,
Expand All @@ -359,6 +360,7 @@ export async function mutateModules (
}
ctx.wantedLockfile.overrides = opts.overrides
ctx.wantedLockfile.packageExtensionsChecksum = packageExtensionsChecksum
ctx.wantedLockfile.ignoredOptionalDependencies = opts.ignoredOptionalDependencies
ctx.wantedLockfile.pnpmfileChecksum = pnpmfileChecksum
ctx.wantedLockfile.patchedDependencies = patchedDependencies
} else if (!frozenLockfile) {
Expand Down Expand Up @@ -710,6 +712,7 @@ function getOutdatedLockfileSetting (
onlyBuiltDependencies,
overrides,
packageExtensionsChecksum,
ignoredOptionalDependencies,
patchedDependencies,
autoInstallPeers,
excludeLinksFromLockfile,
Expand All @@ -719,6 +722,7 @@ function getOutdatedLockfileSetting (
overrides?: Record<string, string>
packageExtensionsChecksum?: string
patchedDependencies?: Record<string, PatchFile>
ignoredOptionalDependencies?: string[]
autoInstallPeers?: boolean
excludeLinksFromLockfile?: boolean
pnpmfileChecksum?: string
Expand All @@ -730,6 +734,9 @@ function getOutdatedLockfileSetting (
if (lockfile.packageExtensionsChecksum !== packageExtensionsChecksum) {
return 'packageExtensionsChecksum'
}
if (!equals(lockfile.ignoredOptionalDependencies?.sort() ?? [], ignoredOptionalDependencies?.sort() ?? [])) {
return 'ignoredOptionalDependencies'
}
if (!equals(lockfile.patchedDependencies ?? {}, patchedDependencies ?? {})) {
return 'patchedDependencies'
}
Expand Down
48 changes: 48 additions & 0 deletions pkg-manager/core/test/install/ignoredOptionalDependencies.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { prepareEmpty } from '@pnpm/prepare'
import { addDependenciesToPackage } from '@pnpm/core'
import {
testDefaults,
} from '../utils'

test('ignoredOptionalDependencies causes listed optional dependencies to be skipped', async () => {
const project = prepareEmpty()

await addDependenciesToPackage(
{},
['@pnpm.e2e/pkg-with-good-optional@1.0.0'],
testDefaults({ ignoredOptionalDependencies: ['is-positive'] })
)

const lockfile = project.readLockfile()
expect(lockfile.ignoredOptionalDependencies).toStrictEqual(['is-positive'])
expect(lockfile.packages).not.toHaveProperty(['/is-positive@1.0.0'])
expect(lockfile.packages).toHaveProperty(['/@pnpm.e2e/pkg-with-good-optional@1.0.0'])
})

test('empty ignoredOptionalDependencies is not recorded in lockfile', async () => {
const project = prepareEmpty()

await addDependenciesToPackage(
{},
['@pnpm.e2e/pkg-with-good-optional@1.0.0'],
testDefaults({ ignoredOptionalDependencies: [] })
)

const lockfile = project.readLockfile()
expect(lockfile).not.toHaveProperty(['ignoredOptionalDependencies'])
expect(lockfile.packages).toHaveProperty(['/is-positive@1.0.0'])
expect(lockfile.packages).toHaveProperty(['/@pnpm.e2e/pkg-with-good-optional@1.0.0'])
})

test('names in ignoredOptionalDependencies are sorted alphabetically in the lockfile', async () => {
const project = prepareEmpty()

await addDependenciesToPackage(
{},
['@pnpm.e2e/pkg-with-good-optional@1.0.0'],
testDefaults({ ignoredOptionalDependencies: ['foo', 'bar', 'baz', 'qux'] })
)

const lockfile = project.readLockfile()
expect(lockfile.ignoredOptionalDependencies).toStrictEqual(['bar', 'baz', 'foo', 'qux'])
})
35 changes: 35 additions & 0 deletions pnpm/test/install/ignoredOptionalDependencies.ts
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test should be moved to core too.

Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { type ProjectManifest } from '@pnpm/types'
import { prepare } from '@pnpm/prepare'
import { writeProjectManifest } from '@pnpm/write-project-manifest'
import { execPnpm } from '../utils'

test('adding or changing manifest.pnpm.ignoredOptionalDependencies should change lockfile.ignoredOptionalDependencies and module structure', async () => {
const manifest: ProjectManifest = {
dependencies: {
'@pnpm.e2e/pkg-with-good-optional': '1.0.0',
},
}
const project = prepare(manifest)
await execPnpm(['install'])
{
const lockfile = project.readLockfile()
expect(lockfile).not.toHaveProperty(['ignoredOptionalDependencies'])
expect(lockfile.packages).toHaveProperty(['/@pnpm.e2e/pkg-with-good-optional@1.0.0'])
expect(lockfile.packages).toHaveProperty(['/is-positive@1.0.0'])
}

await writeProjectManifest('package.json', {
...manifest,
pnpm: {
...manifest.pnpm,
ignoredOptionalDependencies: ['is-positive'],
},
})
await execPnpm(['install'])
{
const lockfile = project.readLockfile()
expect(lockfile.ignoredOptionalDependencies).toStrictEqual(['is-positive'])
expect(lockfile.packages).toHaveProperty(['/@pnpm.e2e/pkg-with-good-optional@1.0.0'])
expect(lockfile.packages).not.toHaveProperty(['/is-positive@1.0.0'])
}
})