mirror of
https://github.com/pnpm/pnpm.git
synced 2025-12-23 23:29:17 -05:00
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:
7
.changeset/beige-ducks-rescue.md
Normal file
7
.changeset/beige-ducks-rescue.md
Normal 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.
|
||||
10
.changeset/heavy-eyes-ring.md
Normal file
10
.changeset/heavy-eyes-ring.md
Normal 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.
|
||||
10
.changeset/serious-geckos-taste.md
Normal file
10
.changeset/serious-geckos-taste.md
Normal 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.
|
||||
@@ -104,7 +104,7 @@ export interface Config {
|
||||
workspaceConcurrency: number,
|
||||
workspaceDir?: string,
|
||||
reporter?: string,
|
||||
linkWorkspacePackages: boolean,
|
||||
linkWorkspacePackages: boolean | 'deep',
|
||||
sort: boolean,
|
||||
strictPeerDependencies: boolean,
|
||||
lockfileDir?: string,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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],
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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')
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user