fix: update on a subset of projects should work with dedupe-peer-dependents (#6178)

This commit is contained in:
Zoltan Kochan
2023-03-05 12:01:52 +02:00
committed by GitHub
parent b11fa7093c
commit 670bea8440
13 changed files with 163 additions and 62 deletions

View File

@@ -0,0 +1,6 @@
---
"@pnpm/resolve-dependencies": major
"@pnpm/core": major
---
The update options are passed on per project basis. So the `update` and `updateMatching` options are options of importers/projects.

View File

@@ -14,3 +14,4 @@ export { ProjectOptions, UnexpectedStoreError, UnexpectedVirtualStoreDirError }
export { InstallOptions } from './install/extendInstallOptions'
export { WorkspacePackages } from '@pnpm/resolver-base'
export { UpdateMatchingFunction } from '@pnpm/resolve-dependencies'

View File

@@ -54,9 +54,6 @@ export interface StrictInstallOptions {
reporter: ReporterFunction
force: boolean
forcePublicHoistPattern: boolean
update: boolean
updateMatching?: (pkgName: string) => boolean
updatePackageManifest?: boolean
depth: number
lockfileDir: string
modulesDir: string
@@ -196,7 +193,6 @@ const defaults = async (opts: InstallOptions) => {
process.platform === 'cygwin' ||
!process.setgid ||
process.getuid() !== 0,
update: false,
useLockfile: true,
saveLockfile: true,
useGitBranchLockfile: false,

View File

@@ -45,6 +45,7 @@ import {
DependenciesGraphNode,
PinnedVersion,
resolveDependencies,
UpdateMatchingFunction,
WantedDependency,
} from '@pnpm/resolve-dependencies'
import {
@@ -89,36 +90,50 @@ const BROKEN_LOCKFILE_INTEGRITY_ERRORS = new Set([
const DEV_PREINSTALL = 'pnpm:devPreinstall'
export type DependenciesMutation = (
{
mutation: 'install'
pruneDirectDependencies?: boolean
} | {
allowNew?: boolean
dependencySelectors: string[]
mutation: 'installSome'
peer?: boolean
pruneDirectDependencies?: boolean
pinnedVersion?: PinnedVersion
targetDependenciesField?: DependenciesField
} | {
mutation: 'uninstallSome'
dependencyNames: string[]
targetDependenciesField?: DependenciesField
} | {
mutation: 'unlink'
} | {
mutation: 'unlinkSome'
dependencyNames: string[]
}
)
interface InstallMutationOptions {
update?: boolean
updateMatching?: UpdateMatchingFunction
updatePackageManifest?: boolean
}
export interface InstallDepsMutation extends InstallMutationOptions {
mutation: 'install'
pruneDirectDependencies?: boolean
}
export interface InstallSomeDepsMutation extends InstallMutationOptions {
allowNew?: boolean
dependencySelectors: string[]
mutation: 'installSome'
peer?: boolean
pruneDirectDependencies?: boolean
pinnedVersion?: PinnedVersion
targetDependenciesField?: DependenciesField
}
export interface UninstallSomeDepsMutation {
mutation: 'uninstallSome'
dependencyNames: string[]
targetDependenciesField?: DependenciesField
}
export interface UnlinkDepsMutation {
mutation: 'unlink'
}
export interface UnlinkSomeDepsMutation {
mutation: 'unlinkSome'
dependencyNames: string[]
}
export type DependenciesMutation = InstallDepsMutation | InstallSomeDepsMutation | UninstallSomeDepsMutation | UnlinkDepsMutation | UnlinkSomeDepsMutation
export async function install (
manifest: ProjectManifest,
opts: Omit<InstallOptions, 'allProjects'> & {
preferredVersions?: PreferredVersions
pruneDirectDependencies?: boolean
}
} & InstallMutationOptions
) {
const rootDir = opts.dir ?? process.cwd()
const projects = await mutateModules(
@@ -127,6 +142,9 @@ export async function install (
mutation: 'install',
pruneDirectDependencies: opts.pruneDirectDependencies,
rootDir,
update: opts.update,
updateMatching: opts.updateMatching,
updatePackageManifest: opts.updatePackageManifest,
},
],
{
@@ -165,10 +183,17 @@ export async function mutateModulesInSingleProject (
rootDir: string
modulesDir?: string
},
maybeOpts: Omit<MutateModulesOptions, 'allProjects'>
maybeOpts: Omit<MutateModulesOptions, 'allProjects'> & InstallMutationOptions
): Promise<UpdatedProject> {
const [updatedProject] = await mutateModules(
[project],
[
{
...project,
update: maybeOpts.update,
updateMatching: maybeOpts.updateMatching,
updatePackageManifest: maybeOpts.updatePackageManifest,
} as MutatedProject,
],
{
...maybeOpts,
allProjects: [{
@@ -195,7 +220,7 @@ export async function mutateModules (
throw new PnpmError('OPTIONAL_DEPS_REQUIRE_PROD_DEPS', 'Optional dependencies cannot be installed without production dependencies')
}
const installsOnly = projects.every((project) => project.mutation === 'install')
const installsOnly = projects.every((project) => project.mutation === 'install' && !project.update && !project.updateMatching)
if (!installsOnly) opts.strictPeerDependencies = false
// @ts-expect-error
opts['forceNewModules'] = installsOnly
@@ -306,7 +331,6 @@ export async function mutateModules (
opts.frozenLockfileIfExists && ctx.existsWantedLockfile
if (
!ctx.lockfileHadConflicts &&
!opts.update &&
!opts.fixLockfile &&
!opts.dedupe &&
installsOnly &&
@@ -396,7 +420,9 @@ export async function mutateModules (
if (BROKEN_LOCKFILE_INTEGRITY_ERRORS.has(error.code)) {
needsFullResolution = true
// Ideally, we would not update but currently there is no other way to redownload the integrity of the package
opts.update = true
for (const project of projects) {
(project as InstallMutationOptions).update = true
}
}
// A broken lockfile may be caused by a badly resolved Git conflict
logger.warn({
@@ -432,14 +458,14 @@ export async function mutateModules (
case 'install': {
await installCase({
...projectOpts,
updatePackageManifest: opts.updatePackageManifest ?? opts.update,
updatePackageManifest: (projectOpts as InstallDepsMutation).updatePackageManifest ?? (projectOpts as InstallDepsMutation).update,
})
break
}
case 'installSome': {
await installSome({
...projectOpts,
updatePackageManifest: opts.updatePackageManifest !== false,
updatePackageManifest: (projectOpts as InstallSomeDepsMutation).updatePackageManifest !== false,
})
break
}
@@ -510,7 +536,7 @@ export async function mutateModules (
const wantedDependencies = getWantedDependencies(project.manifest, {
autoInstallPeers: opts.autoInstallPeers,
includeDirect: opts.includeDirect,
updateWorkspaceDependencies: opts.update,
updateWorkspaceDependencies: project.update,
nodeExecPath: opts.nodeExecPath,
})
.map((wantedDependency) => ({ ...wantedDependency, updateSpec: true, preserveNonSemverVersionSpec: true }))
@@ -549,7 +575,7 @@ export async function mutateModules (
devDependencies,
optional: project.targetDependenciesField === 'optionalDependencies',
optionalDependencies,
updateWorkspaceDependencies: opts.update,
updateWorkspaceDependencies: project.update,
preferredSpecs,
overrides: opts.overrides,
})
@@ -684,7 +710,7 @@ export async function addDependenciesToPackage (
peer?: boolean
pinnedVersion?: 'major' | 'minor' | 'patch'
targetDependenciesField?: DependenciesField
}
} & InstallMutationOptions
) {
const rootDir = opts.dir ?? process.cwd()
const projects = await mutateModules(
@@ -697,6 +723,9 @@ export async function addDependenciesToPackage (
pinnedVersion: opts.pinnedVersion,
rootDir,
targetDependenciesField: opts.targetDependenciesField,
update: opts.update,
updateMatching: opts.updateMatching,
updatePackageManifest: opts.updatePackageManifest,
},
],
{
@@ -798,8 +827,9 @@ const _installInContext: InstallFunction = async (projects, ctx, opts) => {
stage: 'resolution_started',
})
const update = projects.some((project) => (project as InstallMutationOptions).update)
const preferredVersions = opts.preferredVersions ?? (
!opts.update
!update
? getPreferredVersionsFromLockfileAndManifests(ctx.wantedLockfile.packages, Object.values(ctx.projects).map(({ manifest }) => manifest))
: undefined
)
@@ -853,7 +883,7 @@ const _installInContext: InstallFunction = async (projects, ctx, opts) => {
allowNonAppliedPatches: opts.allowNonAppliedPatches,
autoInstallPeers: opts.autoInstallPeers,
currentLockfile: ctx.currentLockfile,
defaultUpdateDepth: (opts.update || (opts.updateMatching != null)) ? opts.depth : -1,
defaultUpdateDepth: opts.depth,
dedupePeerDependents: opts.dedupePeerDependents,
dryRun: opts.lockfileOnly,
engineStrict: opts.engineStrict,
@@ -875,7 +905,6 @@ const _installInContext: InstallFunction = async (projects, ctx, opts) => {
saveWorkspaceProtocol: opts.saveWorkspaceProtocol,
storeController: opts.storeController,
tag: opts.tag,
updateMatching: opts.updateMatching,
virtualStoreDir: ctx.virtualStoreDir,
wantedLockfile: ctx.wantedLockfile,
workspacePackages: opts.workspacePackages,
@@ -1248,7 +1277,9 @@ const installInContext: InstallFunction = async (projects, ctx, opts) => {
) throw error
opts.needsFullResolution = true
// Ideally, we would not update but currently there is no other way to redownload the integrity of the package
opts.update = true
for (const project of projects) {
(project as InstallMutationOptions).update = true
}
logger.warn({
error,
message: error.message,

View File

@@ -1,6 +1,6 @@
import * as path from 'path'
import { promises as fs } from 'fs'
import { prepareEmpty, preparePackages } from '@pnpm/prepare'
import { prepareEmpty, prepare, preparePackages } from '@pnpm/prepare'
import { PnpmError } from '@pnpm/error'
import {
PackageManifestLog,
@@ -716,6 +716,7 @@ test('lockfile locks npm dependencies', async () => {
const reporter = sinon.spy()
await addDistTag({ package: '@pnpm.e2e/dep-of-pkg-with-1-dep', version: '100.0.0', distTag: 'latest' })
await addDistTag({ package: '@pnpm.e2e/pkg-with-1-dep', version: '100.0.0', distTag: 'latest' })
const manifest = await addDependenciesToPackage({}, ['@pnpm.e2e/pkg-with-1-dep'], await testDefaults({ save: true, reporter }))
@@ -815,7 +816,7 @@ test('should throw error when trying to install a package without name', async (
// Covers https://github.com/pnpm/pnpm/issues/1193
test('rewrites node_modules created by npm', async () => {
const project = prepareEmpty()
const project = prepare()
await execa('npm', ['install', 'rimraf@2.5.1', '@types/node', '--save'])
@@ -1004,6 +1005,7 @@ test('all the subdeps of dependencies are linked when a node_modules is partiall
test('subdep symlinks are updated if the lockfile has new subdep versions specified', async () => {
await addDistTag({ package: '@pnpm.e2e/dep-of-pkg-with-1-dep', version: '100.0.0', distTag: 'latest' })
await addDistTag({ package: '@pnpm.e2e/pkg-with-1-dep', version: '100.0.0', distTag: 'latest' })
const project = prepareEmpty()
await mutateModulesInSingleProject({

View File

@@ -927,10 +927,12 @@ test('update workspace range', async () => {
dependencySelectors: ['dep1', 'dep2', 'dep3', 'dep4', 'dep5', 'dep6'],
mutation: 'installSome',
rootDir: path.resolve('project-1'),
update: true,
},
{
mutation: 'install',
rootDir: path.resolve('project-2'),
update: true,
},
], await testDefaults({
allProjects: [
@@ -972,7 +974,6 @@ test('update workspace range', async () => {
rootDir: path.resolve('project-2'),
},
],
update: true,
workspacePackages: {
dep1: {
'2.0.0': {
@@ -1071,10 +1072,12 @@ test('update workspace range when save-workspace-protocol is "rolling"', async (
dependencySelectors: ['dep1', 'dep2', 'dep3', 'dep4', 'dep5', 'dep6'],
mutation: 'installSome',
rootDir: path.resolve('project-1'),
update: true,
},
{
mutation: 'install',
rootDir: path.resolve('project-2'),
update: true,
},
], await testDefaults({
allProjects: [
@@ -1113,7 +1116,6 @@ test('update workspace range when save-workspace-protocol is "rolling"', async (
},
],
saveWorkspaceProtocol: 'rolling',
update: true,
workspacePackages: {
dep1: {
'2.0.0': {

View File

@@ -184,6 +184,7 @@ test('multiple save to package.json with `exact` versions (@rstacruz/tap-spec &
})
test('save to package.json with save prefix ~', async () => {
await addDistTag({ package: '@pnpm.e2e/pkg-with-1-dep', version: '100.0.0', distTag: 'latest' })
prepareEmpty()
const manifest = await addDependenciesToPackage({}, ['@pnpm.e2e/pkg-with-1-dep'], await testDefaults({ pinnedVersion: 'minor' }))
@@ -211,10 +212,9 @@ test('an update bumps the versions in the manifest', async () => {
},
mutation: 'install',
rootDir: process.cwd(),
},
await testDefaults({
update: true,
}))
},
await testDefaults())
expect(manifest).toStrictEqual({
dependencies: {

View File

@@ -28,6 +28,7 @@ import {
MutatedProject,
mutateModules,
ProjectOptions,
UpdateMatchingFunction,
WorkspacePackages,
} from '@pnpm/core'
import isSubdir from 'is-subdir'
@@ -79,6 +80,8 @@ type RecursiveOptions = CreateStoreControllerOptions & Pick<Config,
forcePublicHoistPattern?: boolean
ignoredPackages?: Set<string>
update?: boolean
updatePackageManifest?: boolean
updateMatching?: UpdateMatchingFunction
useBetaCli?: boolean
allProjectsGraph: ProjectsGraph
selectedProjectsGraph: ProjectsGraph
@@ -234,6 +237,9 @@ export async function recursive (
}),
rootDir,
targetDependenciesField,
update: opts.update,
updateMatching: opts.updateMatching,
updatePackageManifest: opts.updatePackageManifest,
} as MutatedProject)
return
case 'install':
@@ -242,6 +248,9 @@ export async function recursive (
mutation,
pruneDirectDependencies: opts.pruneDirectDependencies,
rootDir,
update: opts.update,
updateMatching: opts.updateMatching,
updatePackageManifest: opts.updatePackageManifest,
} as MutatedProject)
}
}))

View File

@@ -26,7 +26,7 @@ import promiseShare from 'promise-share'
import difference from 'ramda/src/difference'
import { getWantedDependencies, WantedDependency } from './getWantedDependencies'
import { depPathToRef } from './depPathToRef'
import { createNodeIdForLinkedLocalPkg } from './resolveDependencies'
import { createNodeIdForLinkedLocalPkg, UpdateMatchingFunction } from './resolveDependencies'
import {
Importer,
LinkedDependency,
@@ -53,6 +53,7 @@ export {
LinkedDependency,
ResolvedPackage,
PinnedVersion,
UpdateMatchingFunction,
WantedDependency,
}
@@ -81,6 +82,8 @@ export type ImporterToResolve = Importer<{
binsDir: string
manifest: ProjectManifest
originalManifest?: ProjectManifest
update?: boolean
updateMatching?: UpdateMatchingFunction
updatePackageManifest: boolean
targetDependenciesField?: DependenciesField
}
@@ -100,7 +103,6 @@ export async function resolveDependencies (
defaultUpdateDepth: opts.defaultUpdateDepth,
lockfileOnly: opts.dryRun,
preferredVersions: opts.preferredVersions,
updateAll: Boolean(opts.updateMatching),
virtualStoreDir: opts.virtualStoreDir,
workspacePackages: opts.workspacePackages,
})

View File

@@ -159,7 +159,6 @@ export interface ResolutionContext {
registries: Registries
resolutionMode?: 'highest' | 'time-based' | 'lowest-direct'
virtualStoreDir: string
updateMatching?: (pkgName: string) => boolean
useLockfileV6?: boolean
workspacePackages?: WorkspacePackages
missingPeersOfChildrenByPkgId: Record<string, { parentImporterId: string, missingPeersOfChildren: MissingPeersOfChildren }>
@@ -240,6 +239,8 @@ type ParentPkg = Pick<PkgAddress, 'nodeId' | 'installable' | 'depPath' | 'rootDi
export type ParentPkgAliases = Record<string, PkgAddress | true>
export type UpdateMatchingFunction = (pkgName: string) => boolean
interface ResolvedDependenciesOptions {
currentDepth: number
parentPkg: ParentPkg
@@ -252,6 +253,7 @@ interface ResolvedDependenciesOptions {
publishedBy?: Date
pickLowestVersion?: boolean
resolvedDependencies?: ResolvedDependencies
updateMatching?: UpdateMatchingFunction
updateDepth: number
prefix: string
}
@@ -647,8 +649,8 @@ async function resolveDependenciesOfDependency (
const update = ((extendedWantedDep.infoFromLockfile?.dependencyLockfile) == null) ||
(
updateShouldContinue && (
(ctx.updateMatching == null) ||
ctx.updateMatching(extendedWantedDep.infoFromLockfile.name!)
(options.updateMatching == null) ||
options.updateMatching(extendedWantedDep.infoFromLockfile.name!)
)
) || Boolean(
(ctx.workspacePackages != null) &&
@@ -672,6 +674,7 @@ async function resolveDependenciesOfDependency (
publishedBy: options.publishedBy,
update,
updateDepth,
updateMatching: options.updateMatching,
}
const resolveDependencyResult = await resolveDependency(extendedWantedDep.wantedDependency, ctx, resolveDependencyOpts)
@@ -706,6 +709,7 @@ async function resolveDependenciesOfDependency (
parentDepth: options.currentDepth,
updateDepth,
prefix: options.prefix,
updateMatching: options.updateMatching,
})
return {
resolveDependencyResult,
@@ -751,6 +755,7 @@ async function resolveChildren (
dependencyLockfile,
parentDepth,
updateDepth,
updateMatching,
prefix,
}: {
parentPkg: PkgAddress
@@ -758,6 +763,7 @@ async function resolveChildren (
parentDepth: number
updateDepth: number
prefix: string
updateMatching?: UpdateMatchingFunction
},
{
parentPkgAliases,
@@ -802,6 +808,7 @@ async function resolveChildren (
publishedBy,
resolvedDependencies,
updateDepth,
updateMatching,
}
)
ctx.childrenByParentDepPath[parentPkg.depPath] = pkgAddresses.map((child) => ({
@@ -1001,6 +1008,7 @@ interface ResolveDependencyOptions {
pickLowestVersion?: boolean
update: boolean
updateDepth: number
updateMatching?: UpdateMatchingFunction
}
type ResolveDependencyResult = PkgAddress | LinkedDependency | null

View File

@@ -52,6 +52,7 @@ export interface Importer<T> {
export interface ImporterToResolveGeneric<T> extends Importer<T> {
updatePackageManifest: boolean
updateMatching?: (pkgName: string) => boolean
hasRemovedDependencies?: boolean
preferredVersions?: PreferredVersions
wantedDependencies: Array<T & WantedDependency & { updateDepth: number }>
@@ -79,7 +80,6 @@ export interface ResolveDependenciesOptions {
preferWorkspacePackages?: boolean
resolutionMode?: 'highest' | 'time-based' | 'lowest-direct'
resolvePeersFromWorkspaceRoot?: boolean
updateMatching?: (pkgName: string) => boolean
linkWorkspacePackagesDepth?: number
lockfileDir: string
storeController: StoreController
@@ -122,7 +122,6 @@ export async function resolveDependencyTree<T> (
resolutionMode: opts.resolutionMode,
skipped: wantedToBeSkippedPackageIds,
storeController: opts.storeController,
updateMatching: opts.updateMatching,
virtualStoreDir: opts.virtualStoreDir,
wantedLockfile: opts.wantedLockfile,
appliedPatches: new Set<string>(),
@@ -154,6 +153,7 @@ export async function resolveDependencyTree<T> (
...projectSnapshot.optionalDependencies,
},
updateDepth: -1,
updateMatching: importer.updateMatching,
prefix: importer.rootDir,
}
return {

View File

@@ -15,7 +15,6 @@ export async function toResolveImporter (
defaultUpdateDepth: number
lockfileOnly: boolean
preferredVersions?: PreferredVersions
updateAll: boolean
virtualStoreDir: string
workspacePackages: WorkspacePackages
},
@@ -29,6 +28,7 @@ export async function toResolveImporter (
virtualStoreDir: opts.virtualStoreDir,
workspacePackages: opts.workspacePackages,
})
const defaultUpdateDepth = (project.update === true || (project.updateMatching != null)) ? opts.defaultUpdateDepth : -1
const existingDeps = nonLinkedDependencies
.filter(({ alias }) => !project.wantedDependencies.some((wantedDep) => wantedDep.alias === alias))
let wantedDependencies!: Array<WantedDependency & { isNew?: boolean, updateDepth: number }>
@@ -39,22 +39,22 @@ export async function toResolveImporter (
]
.map((dep) => ({
...dep,
updateDepth: opts.defaultUpdateDepth,
updateDepth: defaultUpdateDepth,
}))
} else {
// Direct local tarballs are always checked,
// so their update depth should be at least 0
const updateLocalTarballs = (dep: WantedDependency) => ({
...dep,
updateDepth: opts.updateAll
? opts.defaultUpdateDepth
updateDepth: project.updateMatching != null
? defaultUpdateDepth
: (prefIsLocalTarball(dep.pref) ? 0 : -1),
})
wantedDependencies = [
...project.wantedDependencies.map(
opts.defaultUpdateDepth < 0
defaultUpdateDepth < 0
? updateLocalTarballs
: (dep) => ({ ...dep, updateDepth: opts.defaultUpdateDepth })),
: (dep) => ({ ...dep, updateDepth: defaultUpdateDepth })),
...existingDeps.map(updateLocalTarballs),
]
}

View File

@@ -6,6 +6,7 @@ import { preparePackages } from '@pnpm/prepare'
import { addDistTag } from '@pnpm/registry-mock'
import { sync as readYamlFile } from 'read-yaml-file'
import { createPeersFolderSuffix } from '@pnpm/dependency-path'
import { sync as loadJsonFile } from 'load-json-file'
import { sync as writeYamlFile } from 'write-yaml-file'
import { execPnpm } from '../utils'
@@ -46,3 +47,46 @@ auto-install-peers=false`, 'utf8')
expect(depPaths).toContain(`/@pnpm.e2e/abc/1.0.0${createPeersFolderSuffix([{ name: '@pnpm.e2e/peer-a', version: '1.0.0' }, { name: '@pnpm.e2e/peer-b', version: '1.0.0' }, { name: '@pnpm.e2e/peer-c', version: '1.0.0' }])}`)
expect(depPaths).toContain(`/@pnpm.e2e/abc-parent-with-ab/1.0.0${createPeersFolderSuffix([{ name: '@pnpm.e2e/peer-c', version: '1.0.0' }])}`)
})
test('partial update in a workspace should work with dedupe-peer-dependents is true', async () => {
await addDistTag({ package: '@pnpm.e2e/abc-parent-with-ab', version: '1.0.0', distTag: 'latest' })
await addDistTag({ package: '@pnpm.e2e/abc', version: '1.0.0', distTag: 'latest' })
await addDistTag({ package: '@pnpm.e2e/peer-a', version: '1.0.0', distTag: 'latest' })
await addDistTag({ package: '@pnpm.e2e/peer-b', version: '1.0.0', distTag: 'latest' })
await addDistTag({ package: '@pnpm.e2e/peer-c', version: '1.0.0', distTag: 'latest' })
preparePackages([
{
location: 'project-1',
package: {
name: 'project-1',
dependencies: {
'@pnpm.e2e/abc-grand-parent-with-c': '^1.0.0',
},
},
},
{
location: 'project-2',
package: {
name: 'project-2',
dependencies: {
'@pnpm.e2e/abc-grand-parent-with-c': '^1.0.0',
},
},
},
])
writeYamlFile('pnpm-workspace.yaml', { packages: ['**', '!store/**'] })
writeFileSync('.npmrc', `dedupe-peer-dependents=true
auto-install-peers=false`, 'utf8')
await execPnpm(['install'])
await addDistTag({ package: '@pnpm.e2e/abc-parent-with-ab', version: '1.0.1', distTag: 'latest' })
process.chdir('project-2')
await execPnpm(['update'])
process.chdir('..')
expect(loadJsonFile<any>('project-1/package.json').dependencies['@pnpm.e2e/abc-grand-parent-with-c']).toBe('^1.0.0') // eslint-disable-line
expect(loadJsonFile<any>('project-2/package.json').dependencies['@pnpm.e2e/abc-grand-parent-with-c']).toBe('^1.0.1') // eslint-disable-line
})