feat: linking packages to subdependencies

The `link-workspace-packages` setting can be `deep` now.
When it is `deep`, the resolution algorithm will always prefer
packages from the workspace over packages from the registry.

close #2552
PR #2554
This commit is contained in:
Zoltan Kochan
2020-05-16 20:20:45 +03:00
committed by GitHub
parent b8d0c9982d
commit 242cf8737b
15 changed files with 368 additions and 104 deletions

View File

@@ -0,0 +1,7 @@
---
"@pnpm/config": minor
"@pnpm/plugin-commands-installation": minor
---
The `link-workspace-packages` setting may be set to `deep`. When using `deep`,
workspace packages are linked into subdependencies, not only to direct dependencies of projects.

View File

@@ -0,0 +1,10 @@
---
"@pnpm/resolve-dependencies": major
---
The `alwaysTryWorkspacePackages` option is removed. A new option called `linkWorkspacePackagesDepth` is added.
When `linkWorkspacePackageDepth` is `0`, workspace packages are linked to direct dependencies even if these direct
dependencies are not using workspace ranges (so this is similar to the old `alwaysTryWorkspacePackages=true`).
`linkWorkspacePackageDepth` also allows to link workspace packages to subdependencies by setting the max depth.
Setting it to `Infinity` will make the resolution algorithm always prefer packages from the workspace over packages
from the registry.

View File

@@ -0,0 +1,10 @@
---
"supi": minor
---
The `linkWorkspacePackages` option is removed. A new option called `linkWorkspacePackagesDepth` is added.
When `linkWorkspacePackageDepth` is `0`, workspace packages are linked to direct dependencies even if these direct
dependencies are not using workspace ranges (so this is similar to the old `linkWorkspacePackages=true`).
`linkWorkspacePackageDepth` also allows to link workspace packages to subdependencies by setting the max depth.
Setting it to `Infinity` will make the resolution algorithm always prefer packages from the workspace over packages
from the registry.

View File

@@ -104,7 +104,7 @@ export interface Config {
workspaceConcurrency: number,
workspaceDir?: string,
reporter?: string,
linkWorkspacePackages: boolean,
linkWorkspacePackages: boolean | 'deep',
sort: boolean,
strictPeerDependencies: boolean,
lockfileDir?: string,

View File

@@ -45,7 +45,7 @@ export const types = Object.assign({
'ignore-pnpmfile': Boolean,
'ignore-workspace-root-check': Boolean,
'independent-leaves': Boolean,
'link-workspace-packages': Boolean,
'link-workspace-packages': [Boolean, 'deep'],
'lockfile': Boolean,
'lockfile-dir': String,
'lockfile-directory': String, // TODO: deprecate

View File

@@ -138,6 +138,7 @@ export default async function handler (
// The dependencies should be built first,
// so ignoring scripts for now
ignoreScripts: !!workspacePackages || opts.ignoreScripts,
linkWorkspacePackagesDepth: opts.linkWorkspacePackages === 'deep' ? Infinity : opts.linkWorkspacePackages ? 0 : -1,
sideEffectsCacheRead: opts.sideEffectsCache || opts.sideEffectsCacheReadonly,
sideEffectsCacheWrite: opts.sideEffectsCache,
storeController: store.ctrl,

View File

@@ -109,6 +109,7 @@ export default async function recursive (
: {}
const targetDependenciesField = getSaveType(opts)
const installOpts = Object.assign(opts, {
linkWorkspacePackagesDepth: opts.linkWorkspacePackages === 'deep' ? Infinity : opts.linkWorkspacePackages ? 0 : -1,
ownLifecycleHooksStdio: 'pipe',
peer: opts.savePeer,
pruneLockfileImporters: (!opts.ignoredPackages || opts.ignoredPackages.size === 0)

View File

@@ -17,6 +17,7 @@ import resolveDependencies, {
LinkedDependency,
PendingNode,
PkgAddress,
ResolvedPackage,
ResolvedPackagesByPackageId,
} from './resolveDependencies'
@@ -28,7 +29,7 @@ export type ResolvedDirectDependency = {
optional: boolean,
dev: boolean,
resolution: Resolution,
id: string,
pkgId: string,
version: string,
name: string,
normalizedPref?: string,
@@ -45,7 +46,6 @@ export interface Importer {
export default async function (
importers: Importer[],
opts: {
alwaysTryWorkspacePackages?: boolean,
currentLockfile: Lockfile,
dryRun: boolean,
engineStrict: boolean,
@@ -58,6 +58,7 @@ export default async function (
resolutionStrategy?: 'fast' | 'fewer-dependencies',
pnpmVersion: string,
sideEffectsCache: boolean,
linkWorkspacePackagesDepth?: number,
lockfileDir: string,
storeController: StoreController,
tag: string,
@@ -78,6 +79,7 @@ export default async function (
dryRun: opts.dryRun,
engineStrict: opts.engineStrict,
force: opts.force,
linkWorkspacePackagesDepth: opts.linkWorkspacePackagesDepth ?? -1,
lockfileDir: opts.lockfileDir,
nodeVersion: opts.nodeVersion,
outdatedDependencies: {} as {[pkgId: string]: string},
@@ -107,7 +109,7 @@ export default async function (
resolutionStrategy: opts.resolutionStrategy || 'fast',
}
const resolveOpts = {
alwaysTryWorkspacePackages: opts.alwaysTryWorkspacePackages,
alwaysTryWorkspacePackages: (opts.linkWorkspacePackagesDepth ?? -1) >= 0,
currentDepth: 0,
parentDependsOnPeers: true,
parentNodeId: `>${importer.id}>`,
@@ -158,12 +160,18 @@ export default async function (
if (dep.isLinkedDependency === true) {
return dep
}
const resolvedPackage = ctx.dependenciesTree[dep.nodeId].resolvedPackage as ResolvedPackage
return {
...ctx.dependenciesTree[dep.nodeId].resolvedPackage,
alias: dep.alias,
dev: resolvedPackage.dev,
name: resolvedPackage.name,
normalizedPref: dep.normalizedPref,
optional: resolvedPackage.optional,
pkgId: resolvedPackage.id,
resolution: resolvedPackage.resolution,
version: resolvedPackage.version,
}
}) as ResolvedDirectDependency[],
}),
directNodeIdsByAlias: directNonLinkedDeps
.reduce((acc, dependency) => {
acc[dependency.alias] = dependency.nodeId
@@ -197,6 +205,10 @@ function buildTree (
) {
const childrenNodeIds = {}
for (const child of children) {
if (child.pkgId.startsWith('link:')) {
childrenNodeIds[child.alias] = child.pkgId
continue
}
if (nodeIdContainsSequence(parentNodeId, parentId, child.pkgId)) {
continue
}
@@ -204,7 +216,13 @@ function buildTree (
childrenNodeIds[child.alias] = childNodeId
installable = installable && !ctx.skipped.has(child.pkgId)
ctx.dependenciesTree[childNodeId] = {
children: () => buildTree(ctx, childNodeId, child.pkgId, ctx.childrenByParentId[child.pkgId], depth + 1, installable),
children: () => buildTree(ctx,
childNodeId,
child.pkgId,
ctx.childrenByParentId[child.pkgId],
depth + 1,
installable
),
depth,
installable,
resolvedPackage: ctx.resolvedPackagesByPackageId[child.pkgId],

View File

@@ -64,12 +64,21 @@ export function nodeIdToParents (
})
}
export interface DependenciesTreeNode {
children: (() => {[alias: string]: string}) | {[alias: string]: string}, // child nodeId by child alias name
// child nodeId by child alias name in case of non-linked deps
export type ChildrenMap = {
[alias: string]: string,
}
export type DependenciesTreeNode = {
children: (() => ChildrenMap) | ChildrenMap,
installable: boolean,
} & ({
resolvedPackage: ResolvedPackage,
depth: number,
installable: boolean,
}
} | {
resolvedPackage: { version: string },
depth: -1,
})
export interface DependenciesTree {
// a node ID is the join of the package's keypath with a colon
@@ -87,7 +96,7 @@ export interface LinkedDependency {
optional: boolean,
dev: boolean,
resolution: DirectoryResolution,
id: string,
pkgId: string,
version: string,
name: string,
normalizedPref?: string,
@@ -103,7 +112,10 @@ export interface PendingNode {
}
export interface ChildrenByParentId {
[parentId: string]: Array<{alias: string, pkgId: string}>,
[parentId: string]: Array<{
alias: string,
pkgId: string,
}>,
}
export interface ResolutionContext {
@@ -116,6 +128,7 @@ export interface ResolutionContext {
wantedLockfile: Lockfile,
updateLockfile: boolean,
currentLockfile: Lockfile,
linkWorkspacePackagesDepth: number,
lockfileDir: string,
sideEffectsCache: boolean,
storeController: StoreController,
@@ -136,7 +149,7 @@ export interface ResolutionContext {
const ENGINE_NAME = `${process.platform}-${process.arch}-node-${process.version.split('.')[0]}`
export interface PkgAddress {
export type PkgAddress = {
alias: string,
depIsLinked: boolean,
isNew: boolean,
@@ -146,9 +159,15 @@ export interface PkgAddress {
normalizedPref?: string, // is returned only for root dependencies
installable: boolean,
pkg: PackageManifest,
version?: string,
updated: boolean,
useManifestInfoFromLockfile: boolean,
}
} & ({
isLinkedDependency: true,
version: string,
} | {
isLinkedDependency: void,
})
export interface ResolvedPackage {
id: string,
@@ -246,7 +265,18 @@ export default async function resolveDependencies (
const resolveDependencyResult = await resolveDependency(extendedWantedDep.wantedDependency, ctx, resolveDependencyOpts)
if (!resolveDependencyResult) return null
if (resolveDependencyResult.isLinkedDependency || !resolveDependencyResult.isNew) return resolveDependencyResult
if (resolveDependencyResult.isLinkedDependency) {
ctx.dependenciesTree[resolveDependencyResult.pkgId] = {
children: {},
depth: -1,
installable: true,
resolvedPackage: {
version: resolveDependencyResult.version,
},
}
return resolveDependencyResult
}
if (!resolveDependencyResult.isNew) return resolveDependencyResult
const resolveChildren = async function (preferredVersions: PreferredVersions) {
const resolvedPackage = ctx.resolvedPackagesByPackageId[resolveDependencyResult.pkgId]
@@ -254,6 +284,8 @@ export default async function resolveDependencies (
? undefined
: extendedWantedDep.infoFromLockfile?.resolvedDependencies
const optionalDependencyNames = extendedWantedDep.infoFromLockfile?.optionalDependencyNames
const workspacePackages = options.workspacePackages && ctx.linkWorkspacePackagesDepth > options.currentDepth
? options.workspacePackages : undefined
const children = await resolveDependencies(
ctx,
getWantedDependencies(resolveDependencyResult.pkg, {
@@ -279,6 +311,7 @@ export default async function resolveDependencies (
proceed: !resolveDependencyResult.depIsLinked,
resolvedDependencies,
updateDepth,
workspacePackages,
}
) as PkgAddress[]
ctx.childrenByParentId[resolveDependencyResult.pkgId] = children.map((child) => ({
@@ -287,7 +320,7 @@ export default async function resolveDependencies (
}))
ctx.dependenciesTree[resolveDependencyResult.nodeId] = {
children: children.reduce((chn, child) => {
chn[child.alias] = child.nodeId
chn[child.alias] = child.nodeId ?? child.pkgId
return chn
}, {}),
depth: options.currentDepth,
@@ -313,8 +346,8 @@ export default async function resolveDependencies (
...options.preferredVersions,
}
for (const { pkgId } of pkgAddresses) {
if (!pkgId) continue // This will happen only with linked dependencies
const resolvedPackage = ctx.resolvedPackagesByPackageId[pkgId]
if (!resolvedPackage) continue // This will happen only with linked dependencies
if (!newPreferredVersions[resolvedPackage.name]) {
newPreferredVersions[resolvedPackage.name] = {}
}
@@ -522,7 +555,7 @@ async function resolveDependency (
downloadPriority: -options.currentDepth,
lockfileDir: ctx.lockfileDir,
preferredVersions: options.preferredVersions,
projectDir: ctx.prefix,
projectDir: options.currentDepth > 0 ? ctx.lockfileDir : ctx.prefix,
registry: wantedDependency.alias && pickRegistryForPackage(ctx.registries, wantedDependency.alias) || ctx.registries.default,
sideEffectsCache: ctx.sideEffectsCache,
// Unfortunately, even when run with --lockfile-only, we need the *real* package.json
@@ -571,24 +604,16 @@ async function resolveDependency (
if (pkgResponse.body.isLocal) {
const manifest = pkgResponse.body.manifest || await pkgResponse.bundledManifest!() // tslint:disable-line:no-string-literal
if (options.currentDepth > 0) {
logger.warn({
message: `Ignoring file dependency because it is not a root dependency ${wantedDependency}`,
prefix: ctx.prefix,
})
return null
} else {
return {
alias: wantedDependency.alias || manifest.name,
dev: wantedDependency.dev,
id: pkgResponse.body.id,
isLinkedDependency: true,
name: manifest.name,
normalizedPref: pkgResponse.body.normalizedPref,
optional: wantedDependency.optional,
resolution: pkgResponse.body.resolution,
version: manifest.version,
}
return {
alias: wantedDependency.alias || manifest.name,
dev: wantedDependency.dev,
isLinkedDependency: true,
name: manifest.name,
normalizedPref: pkgResponse.body.normalizedPref,
optional: wantedDependency.optional,
pkgId: pkgResponse.body.id,
resolution: pkgResponse.body.resolution,
version: manifest.version,
}
}
@@ -728,6 +753,7 @@ async function resolveDependency (
// Next fields are actually only needed when isNew = true
installable,
isLinkedDependency: undefined,
pkg,
updated: pkgResponse.body.updated,
useManifestInfoFromLockfile,

View File

@@ -14,12 +14,12 @@ import pnpmPkgJson from '../pnpmPkgJson'
import { ReporterFunction } from '../types'
export interface StrictInstallOptions {
linkWorkspacePackages?: boolean,
forceSharedLockfile: boolean,
frozenLockfile: boolean,
frozenLockfileIfExists: boolean,
extraBinPaths: string[],
useLockfile: boolean,
linkWorkspacePackagesDepth: number,
lockfileOnly: boolean,
preferFrozenLockfile: boolean,
saveWorkspaceProtocol: boolean,

View File

@@ -28,11 +28,11 @@ import { write as writeModulesYaml } from '@pnpm/modules-yaml'
import readModulesDirs from '@pnpm/read-modules-dir'
import { safeReadPackageFromDir as safeReadPkgFromDir } from '@pnpm/read-package-json'
import resolveDependencies, {
ResolvedDirectDependency,
ResolvedPackage,
} from '@pnpm/resolve-dependencies'
import {
PreferredVersions,
Resolution,
WorkspacePackages,
} from '@pnpm/resolver-base'
import {
@@ -621,12 +621,12 @@ async function installInContext (
} = await resolveDependencies(
projectsToResolve,
{
alwaysTryWorkspacePackages: opts.linkWorkspacePackages,
currentLockfile: ctx.currentLockfile,
dryRun: opts.lockfileOnly,
engineStrict: opts.engineStrict,
force: opts.force,
hooks: opts.hooks,
linkWorkspacePackagesDepth: opts.linkWorkspacePackagesDepth ?? (opts.saveWorkspaceProtocol ? 0 : -1),
lockfileDir: opts.lockfileDir,
nodeVersion: opts.nodeVersion,
pnpmVersion: opts.packageManager.name === 'pnpm' ? opts.packageManager.version : '',
@@ -909,16 +909,7 @@ function addDirectDependenciesToLockfile (
newManifest: ProjectManifest,
projectSnapshot: ProjectSnapshot,
linkedPackages: Array<{alias: string}>,
directDependencies: Array<{
alias: string,
optional: boolean,
dev: boolean,
resolution: Resolution,
id: string,
version: string,
name: string,
normalizedPref?: string,
}>,
directDependencies: ResolvedDirectDependency[],
registries: Registries
): ProjectSnapshot {
const newProjectSnapshot = {
@@ -937,19 +928,12 @@ function addDirectDependenciesToLockfile (
return acc
}, {})
const optionalDependencies = R.keys(newManifest.optionalDependencies)
const dependencies = R.difference(R.keys(newManifest.dependencies), optionalDependencies)
const devDependencies = R.difference(R.difference(R.keys(newManifest.devDependencies), optionalDependencies), dependencies)
const allDeps = [
...optionalDependencies,
...devDependencies,
...dependencies,
]
const allDeps = Array.from(new Set(Object.keys(getAllDependenciesFromManifest(newManifest))))
for (const alias of allDeps) {
if (directDependenciesByAlias[alias]) {
const dep = directDependenciesByAlias[alias]
const ref = depPathToRef(dep.id, {
const ref = depPathToRef(dep.pkgId, {
alias: dep.alias,
realName: dep.name,
registries,

View File

@@ -505,6 +505,10 @@ async function linkAllModules (
await Promise.all(
Object.keys(childrenToLink)
.map(async (childAlias) => {
if (childrenToLink[childAlias].startsWith('link:')) {
await limitLinking(() => symlinkDependency(path.resolve(opts.lockfileDir, childrenToLink[childAlias].substr(5)), modules, childAlias))
return
}
const pkg = depGraph[childrenToLink[childAlias]]
if (!pkg.installable && pkg.optional) return
if (childAlias === name) {

View File

@@ -5,6 +5,7 @@ import {
createNodeId,
DependenciesTree,
DependenciesTreeNode,
ResolvedPackage,
splitNodeId,
} from '@pnpm/resolve-dependencies'
import { Resolution } from '@pnpm/resolver-base'
@@ -122,7 +123,7 @@ export default function (
R.values(depGraph).forEach((node) => {
node.children = R.keys(node.children).reduce((acc, alias) => {
acc[alias] = pathsByNodeId[node.children[alias]]
acc[alias] = pathsByNodeId[node.children[alias]] ?? node.children[alias]
return acc
}, {})
})
@@ -156,8 +157,10 @@ function resolvePeersOfNode (
}
): {[alias: string]: string} {
const node = ctx.dependenciesTree[nodeId]
if (ctx.purePkgs.has(node.resolvedPackage.depPath) && ctx.depGraph[node.resolvedPackage.depPath].depth <= node.depth) {
ctx.pathsByNodeId[nodeId] = node.resolvedPackage.depPath
if (node.depth === -1) return {}
const resolvedPackage = node.resolvedPackage as ResolvedPackage
if (ctx.purePkgs.has(resolvedPackage.depPath) && ctx.depGraph[resolvedPackage.depPath].depth <= node.depth) {
ctx.pathsByNodeId[nodeId] = resolvedPackage.depPath
return {}
}
@@ -166,17 +169,24 @@ function resolvePeersOfNode (
? parentParentPkgs
: {
...parentParentPkgs,
...toPkgByName(Object.keys(children).map((alias) => ({ alias, nodeId: children[alias], node: ctx.dependenciesTree[children[alias]] }))),
...toPkgByName(
Object.keys(children).map((alias) => ({
alias,
node: ctx.dependenciesTree[children[alias]],
nodeId: children[alias],
}))
),
}
const unknownResolvedPeersOfChildren = resolvePeersOfChildren(children, parentPkgs, ctx)
const resolvedPeers = R.isEmpty(node.resolvedPackage.peerDependencies)
const resolvedPeers = R.isEmpty(resolvedPackage.peerDependencies)
? {}
: resolvePeers({
currentDepth: node.depth,
dependenciesTree: ctx.dependenciesTree,
node,
nodeId,
parentPkgs,
resolvedPackage,
rootDir: ctx.rootDir,
strictPeerDependencies: ctx.strictPeerDependencies,
})
@@ -185,13 +195,13 @@ function resolvePeersOfNode (
let modules: string
let depPath: string
const localLocation = path.join(ctx.virtualStoreDir, pkgIdToFilename(node.resolvedPackage.depPath, ctx.lockfileDir))
const localLocation = path.join(ctx.virtualStoreDir, pkgIdToFilename(resolvedPackage.depPath, ctx.lockfileDir))
const isPure = R.isEmpty(allResolvedPeers)
if (isPure) {
modules = path.join(localLocation, 'node_modules')
depPath = node.resolvedPackage.depPath
if (R.isEmpty(node.resolvedPackage.peerDependencies)) {
ctx.purePkgs.add(node.resolvedPackage.depPath)
depPath = resolvedPackage.depPath
if (R.isEmpty(resolvedPackage.peerDependencies)) {
ctx.purePkgs.add(resolvedPackage.depPath)
}
} else {
const peersFolderSuffix = createPeersFolderSuffix(
@@ -200,53 +210,53 @@ function resolvePeersOfNode (
version: ctx.dependenciesTree[allResolvedPeers[alias]].resolvedPackage.version,
})))
modules = path.join(`${localLocation}${peersFolderSuffix}`, 'node_modules')
depPath = `${node.resolvedPackage.depPath}${peersFolderSuffix}`
depPath = `${resolvedPackage.depPath}${peersFolderSuffix}`
}
ctx.pathsByNodeId[nodeId] = depPath
if (!ctx.depGraph[depPath] || ctx.depGraph[depPath].depth > node.depth) {
const independent = ctx.independentLeaves && node.resolvedPackage.independent
const centralLocation = node.resolvedPackage.engineCache || path.join(node.resolvedPackage.path, 'node_modules', node.resolvedPackage.name)
const independent = ctx.independentLeaves && resolvedPackage.independent
const centralLocation = resolvedPackage.engineCache || path.join(resolvedPackage.path, 'node_modules', resolvedPackage.name)
const peripheralLocation = !independent
? path.join(modules, node.resolvedPackage.name)
? path.join(modules, resolvedPackage.name)
: centralLocation
const unknownPeers = Object.keys(unknownResolvedPeersOfChildren)
if (unknownPeers.length) {
if (!node.resolvedPackage.additionalInfo.peerDependencies) {
node.resolvedPackage.additionalInfo.peerDependencies = {}
if (!resolvedPackage.additionalInfo.peerDependencies) {
resolvedPackage.additionalInfo.peerDependencies = {}
}
for (const unknownPeer of unknownPeers) {
if (!node.resolvedPackage.additionalInfo.peerDependencies[unknownPeer]) {
node.resolvedPackage.additionalInfo.peerDependencies[unknownPeer] = '*'
if (!resolvedPackage.additionalInfo.peerDependencies[unknownPeer]) {
resolvedPackage.additionalInfo.peerDependencies[unknownPeer] = '*'
}
}
}
ctx.depGraph[depPath] = {
additionalInfo: node.resolvedPackage.additionalInfo,
additionalInfo: resolvedPackage.additionalInfo,
children: Object.assign(children, resolvedPeers),
depPath,
depth: node.depth,
dev: node.resolvedPackage.dev,
fetchingBundledManifest: node.resolvedPackage.fetchingBundledManifest,
fetchingFiles: node.resolvedPackage.fetchingFiles,
hasBin: node.resolvedPackage.hasBin,
hasBundledDependencies: node.resolvedPackage.hasBundledDependencies,
dev: resolvedPackage.dev,
fetchingBundledManifest: resolvedPackage.fetchingBundledManifest,
fetchingFiles: resolvedPackage.fetchingFiles,
hasBin: resolvedPackage.hasBin,
hasBundledDependencies: resolvedPackage.hasBundledDependencies,
independent,
installable: node.installable,
isBuilt: !!node.resolvedPackage.engineCache,
isBuilt: !!resolvedPackage.engineCache,
isPure,
modules,
name: node.resolvedPackage.name,
optional: node.resolvedPackage.optional,
optionalDependencies: node.resolvedPackage.optionalDependencies,
packageId: node.resolvedPackage.id,
name: resolvedPackage.name,
optional: resolvedPackage.optional,
optionalDependencies: resolvedPackage.optionalDependencies,
packageId: resolvedPackage.id,
peripheralLocation,
prepare: node.resolvedPackage.prepare,
prod: node.resolvedPackage.prod,
requiresBuild: node.resolvedPackage.requiresBuild,
resolution: node.resolvedPackage.resolution,
version: node.resolvedPackage.version,
prepare: resolvedPackage.prepare,
prod: resolvedPackage.prod,
requiresBuild: resolvedPackage.requiresBuild,
resolution: resolvedPackage.resolution,
version: resolvedPackage.version,
}
}
return allResolvedPeers
@@ -287,9 +297,10 @@ function resolvePeersOfChildren (
function resolvePeers (
ctx: {
currentDepth: number,
nodeId: string,
node: DependenciesTreeNode,
parentPkgs: ParentRefs,
resolvedPackage: ResolvedPackage,
dependenciesTree: DependenciesTree,
rootDir: string,
strictPeerDependencies: boolean,
@@ -298,8 +309,8 @@ function resolvePeers (
[alias: string]: string,
} {
const resolvedPeers: {[alias: string]: string} = {}
for (const peerName in ctx.node.resolvedPackage.peerDependencies) { // tslint:disable-line:forin
const peerVersionRange = ctx.node.resolvedPackage.peerDependencies[peerName]
for (const peerName in ctx.resolvedPackage.peerDependencies) { // tslint:disable-line:forin
const peerVersionRange = ctx.resolvedPackage.peerDependencies[peerName]
let resolved = ctx.parentPkgs[peerName]
@@ -312,13 +323,13 @@ function resolvePeers (
}
} catch (err) {
if (
ctx.node.resolvedPackage.additionalInfo.peerDependenciesMeta?.[peerName]?.optional === true
ctx.resolvedPackage.additionalInfo.peerDependenciesMeta?.[peerName]?.optional === true
) {
continue
}
const friendlyPath = nodeIdToFriendlyPath(ctx.nodeId, ctx.dependenciesTree)
const message = oneLine`
${friendlyPath ? `${friendlyPath}: ` : ''}${packageFriendlyId(ctx.node.resolvedPackage)}
${friendlyPath ? `${friendlyPath}: ` : ''}${packageFriendlyId(ctx.resolvedPackage)}
requires a peer of ${peerName}@${peerVersionRange} but none was installed.`
if (ctx.strictPeerDependencies) {
throw new PnpmError('MISSING_PEER_DEPENDENCY', message)
@@ -334,7 +345,7 @@ function resolvePeers (
if (!semver.satisfies(resolved.version, peerVersionRange)) {
const friendlyPath = nodeIdToFriendlyPath(ctx.nodeId, ctx.dependenciesTree)
const message = oneLine`
${friendlyPath ? `${friendlyPath}: ` : ''}${packageFriendlyId(ctx.node.resolvedPackage)}
${friendlyPath ? `${friendlyPath}: ` : ''}${packageFriendlyId(ctx.resolvedPackage)}
requires a peer of ${peerName}@${peerVersionRange} but version ${resolved.version} was installed.`
if (ctx.strictPeerDependencies) {
throw new PnpmError('INVALID_PEER_DEPENDENCY', message)
@@ -345,7 +356,7 @@ function resolvePeers (
})
}
if (resolved.depth === ctx.node.depth + 1) {
if (resolved.depth === ctx.currentDepth + 1) {
// if the resolved package is a regular dependency of the package
// then there is no need to link it in
continue
@@ -364,7 +375,7 @@ function nodeIdToFriendlyPath (nodeId: string, dependenciesTree: DependenciesTre
const parts = splitNodeId(nodeId).slice(0, -1)
const result = R.scan((prevNodeId, pkgId) => createNodeId(prevNodeId, pkgId), '>', parts)
.slice(2)
.map((nid) => dependenciesTree[nid].resolvedPackage.name)
.map((nid) => (dependenciesTree[nid].resolvedPackage as ResolvedPackage).name)
.join(' > ')
return result
}

View File

@@ -32,7 +32,7 @@ export default function (
for (const depPath of Object.keys(depGraph)) {
const depNode = depGraph[depPath]
const [updatedOptionalDeps, updatedDeps] = R.partition(
(child) => depNode.optionalDependencies.has(depGraph[child.depPath].name),
(child) => depNode.optionalDependencies.has(child.alias),
Object.keys(depNode.children).map((alias) => ({ alias, depPath: depNode.children[alias] }))
)
lockfile.packages[depPath] = toLockfileDependency(pendingRequiresBuilds, depNode.additionalInfo, {
@@ -196,6 +196,9 @@ function updateResolvedDeps (
const newResolvedDeps = R.fromPairs<string>(
updatedDeps
.map(({ alias, depPath }): R.KeyValuePair<string, string> => {
if (depPath.startsWith('link:')) {
return [alias, depPath]
}
const depNode = depGraph[depPath]
return [
alias,

View File

@@ -2,7 +2,7 @@ import assertProject from '@pnpm/assert-project'
import { LOCKFILE_VERSION, WANTED_LOCKFILE } from '@pnpm/constants'
import { readCurrentLockfile } from '@pnpm/lockfile-file'
import { prepareEmpty, preparePackages } from '@pnpm/prepare'
import { REGISTRY_MOCK_PORT } from '@pnpm/registry-mock'
import rimraf = require('@zkochan/rimraf')
import path = require('path')
import exists = require('path-exists')
import sinon = require('sinon')
@@ -843,3 +843,192 @@ test('remove dependencies of a project that was removed from the workspace (duri
await project.hasNot(`.pnpm/is-negative@1.0.0`)
}
})
test('do not resolve a subdependency from the workspace by default', async (t) => {
preparePackages(t, [
{
location: 'project',
package: { name: 'project' },
},
{
location: 'dep-of-pkg-with-1-dep',
package: { name: 'dep-of-pkg-with-1-dep' },
},
])
const importers: MutatedProject[] = [
{
buildIndex: 0,
manifest: {
name: 'project',
version: '1.0.0',
dependencies: {
'pkg-with-1-dep': '100.0.0',
},
},
mutation: 'install',
rootDir: path.resolve('project'),
},
{
buildIndex: 0,
manifest: {
name: 'dep-of-pkg-with-1-dep',
version: '100.1.0',
},
mutation: 'install',
rootDir: path.resolve('dep-of-pkg-with-1-dep'),
},
]
const workspacePackages = {
'dep-of-pkg-with-1-dep': {
'100.1.0': {
dir: path.resolve('dep-of-pkg-with-1-dep'),
manifest: {
name: 'dep-of-pkg-with-1-dep',
version: '100.1.0',
},
},
},
}
await mutateModules(importers, await testDefaults({ workspacePackages }))
const project = assertProject(t, process.cwd())
const wantedLockfile = await project.readLockfile()
t.equal(wantedLockfile.packages['/pkg-with-1-dep/100.0.0'].dependencies['dep-of-pkg-with-1-dep'], '100.1.0')
})
test('resolve a subdependency from the workspace', async (t) => {
preparePackages(t, [
{
location: 'project',
package: { name: 'project' },
},
{
location: 'dep-of-pkg-with-1-dep',
package: { name: 'dep-of-pkg-with-1-dep' },
},
])
const importers: MutatedProject[] = [
{
buildIndex: 0,
manifest: {
name: 'project',
version: '1.0.0',
dependencies: {
'pkg-with-1-dep': '100.0.0',
},
},
mutation: 'install',
rootDir: path.resolve('project'),
},
{
buildIndex: 0,
manifest: {
name: 'dep-of-pkg-with-1-dep',
version: '100.1.0',
},
mutation: 'install',
rootDir: path.resolve('dep-of-pkg-with-1-dep'),
},
]
const workspacePackages = {
'dep-of-pkg-with-1-dep': {
'100.1.0': {
dir: path.resolve('dep-of-pkg-with-1-dep'),
manifest: {
name: 'dep-of-pkg-with-1-dep',
version: '100.1.0',
},
},
},
}
await mutateModules(importers, await testDefaults({ linkWorkspacePackagesDepth: Infinity, workspacePackages }))
const project = assertProject(t, process.cwd())
const wantedLockfile = await project.readLockfile()
t.equal(wantedLockfile.packages['/pkg-with-1-dep/100.0.0'].dependencies['dep-of-pkg-with-1-dep'], 'link:dep-of-pkg-with-1-dep')
await rimraf('node_modules')
// Testing that headless installation does not fail with links in subdeps
await mutateModules(importers, await testDefaults({
frozenLockfile: true,
workspacePackages,
}))
})
test('resolve a subdependency from the workspace and use it as a peer', async (t) => {
preparePackages(t, [
{
location: 'project',
package: { name: 'project' },
},
{
location: 'peer-a',
package: { name: 'peer-a' },
},
])
const importers: MutatedProject[] = [
{
buildIndex: 0,
manifest: {
name: 'project',
version: '1.0.0',
dependencies: {
'abc-grand-parent-with-c': '1.0.0',
'abc-parent-with-ab': '1.0.0',
},
},
mutation: 'install',
rootDir: path.resolve('project'),
},
{
buildIndex: 0,
manifest: {
name: 'peer-a',
version: '1.0.1',
},
mutation: 'install',
rootDir: path.resolve('peer-a'),
},
]
const workspacePackages = {
'peer-a': {
'1.0.1': {
dir: path.resolve('peer-a'),
manifest: {
name: 'peer-a',
version: '1.0.1',
},
},
},
}
await mutateModules(importers, await testDefaults({ linkWorkspacePackagesDepth: Infinity, workspacePackages }))
const project = assertProject(t, process.cwd())
const wantedLockfile = await project.readLockfile()
t.deepEqual(
Object.keys(wantedLockfile.packages),
[
'/abc-grand-parent-with-c/1.0.0',
'/abc-parent-with-ab/1.0.0',
'/abc-parent-with-ab/1.0.0_peer-c@1.0.1',
'/abc/1.0.0_20890f3ae006d9839e924c7177030952',
'/abc/1.0.0_peer-a@1.0.1+peer-b@1.0.0',
'/dep-of-pkg-with-1-dep/100.0.0',
'/is-positive/1.0.0',
'/peer-b/1.0.0',
'/peer-c/1.0.1',
]
)
t.equal(wantedLockfile.packages['/abc-parent-with-ab/1.0.0'].dependencies['peer-a'], 'link:peer-a')
t.equal(wantedLockfile.packages['/abc/1.0.0_peer-a@1.0.1+peer-b@1.0.0'].dependencies['peer-a'], 'link:peer-a')
})