Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# FOSSA CLI Changelog

## Unreleased

- pnpm: Fix v9 dev-dependency filtering when peer-suffixed lockfile keys collide ([#1724](https://github.com/fossas/fossa-cli/pull/1724)).

## 3.17.11

- Conan: Handle list-valued license in make_fossa_deps_conan ([#1719](https://github.com/fossas/fossa-cli/pull/1719)).
Expand Down
15 changes: 14 additions & 1 deletion src/Strategy/Node/Pnpm/PnpmLock.hs
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,20 @@ instance FromJSON PnpmLockFileSnapshots where

-- Remove the peer dependency suffix. It's present in the snapshot entry, but it's not present in packages
-- section which is where we look the dependency up.
let snapshots' = (HashMap.mapKeys withoutPeerDepSuffix) . toHashMapText $ snapshots
--
-- A single package can have several peer-suffixed snapshot keys (e.g.
-- @core@1.0.0(peerx@1.0.0)@ and @core@1.0.0(peery@1.0.0)@) that all
-- collapse to the same de-suffixed key. Merge (union) their dependency
-- lists instead of letting one variant overwrite the others, otherwise
-- edges that live only on a dropped variant are lost and their dev/prod
-- environment fails to propagate. (ANE-2852)
let snapshots' =
HashMap.fromListWith
(<>)
. map (\(k, v) -> (withoutPeerDepSuffix k, v))
. HashMap.toList
. toHashMapText
$ snapshots
pure $
PnpmLockFileSnapshots{snapshots = snapshots'}

Expand Down
26 changes: 26 additions & 0 deletions test/Pnpm/PnpmLockSpec.hs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
mkBothEnvDep :: Text -> Dependency
mkBothEnvDep nameAtVersion = do
let nameAndVersionSplit = Text.splitOn "@" nameAtVersion
name = head nameAndVersionSplit

Check warning on line 38 in test/Pnpm/PnpmLockSpec.hs

View workflow job for this annotation

GitHub Actions / macOS-intel-build

In the use of ‘head’

Check warning on line 38 in test/Pnpm/PnpmLockSpec.hs

View workflow job for this annotation

GitHub Actions / Windows-build

In the use of ‘head’

Check warning on line 38 in test/Pnpm/PnpmLockSpec.hs

View workflow job for this annotation

GitHub Actions / macOS-arm64-build

In the use of ‘head’
version = last nameAndVersionSplit
Dependency
NodeJSType
Expand All @@ -48,7 +48,7 @@
mkDep :: Text -> Maybe DepEnvironment -> Dependency
mkDep nameAtVersion env = do
let nameAndVersionSplit = Text.splitOn "@" nameAtVersion
name = head nameAndVersionSplit

Check warning on line 51 in test/Pnpm/PnpmLockSpec.hs

View workflow job for this annotation

GitHub Actions / macOS-intel-build

In the use of ‘head’

Check warning on line 51 in test/Pnpm/PnpmLockSpec.hs

View workflow job for this annotation

GitHub Actions / Windows-build

In the use of ‘head’

Check warning on line 51 in test/Pnpm/PnpmLockSpec.hs

View workflow job for this annotation

GitHub Actions / macOS-arm64-build

In the use of ‘head’
version = last nameAndVersionSplit
Dependency
NodeJSType
Expand Down Expand Up @@ -128,6 +128,7 @@
let pnpmLockV9LocalDep = currentDir </> $(mkRelFile "test/Pnpm/testdata/pnpm-9-local-dep/pnpm-lock.yaml")
let pnpmLockV9MultiVersion = currentDir </> $(mkRelFile "test/Pnpm/testdata/pnpm-9-multi-version/pnpm-lock.yaml")
let pnpmLockV9Catalogs = currentDir </> $(mkRelFile "test/Pnpm/testdata/pnpm-9-catalogs/pnpm-lock.yaml")
let pnpmLockV9PeerCollision = currentDir </> $(mkRelFile "test/Pnpm/testdata/pnpm-9-peer-collision/pnpm-lock.yaml")

describe "works with v9 format" $ do
checkGraph pnpmLockV9 pnpmLockV9GraphSpec
Expand All @@ -136,6 +137,7 @@
describe "local dep env propagation" $ checkGraph pnpmLockV9LocalDep pnpmLockV9LocalDepSpec
describe "multi-version env labeling" $ checkGraph pnpmLockV9MultiVersion pnpmLockV9MultiVersionSpec
describe "catalogs" $ checkGraph pnpmLockV9Catalogs pnpmLockV9CatalogsSpec
describe "colliding peer-suffixed snapshot keys" $ checkGraph pnpmLockV9PeerCollision pnpmLockV9PeerCollisionSpec

pnpmLockGraphSpec :: Graphing Dependency -> Spec
pnpmLockGraphSpec graph = do
Expand Down Expand Up @@ -490,6 +492,30 @@
-- sax@1.4.4 is dev-only (from app-b)
expectDep (mkDevDep "sax@1.4.4") graph

-- ANE-2852 regression: a dev-only package (`core`) appears in `snapshots:`
-- under several peer-suffixed keys that all collapse to the same de-suffixed
-- key. The collapse must merge (not overwrite) the colliding dependency lists,
-- otherwise a child reachable only through a dropped variant (`plugin-a` /
-- `plugin-b`) loses its dev path and leaks as production.
pnpmLockV9PeerCollisionSpec :: Graphing Dependency -> Spec
pnpmLockV9PeerCollisionSpec graph = do
let hasEdge :: Dependency -> Dependency -> Expectation
hasEdge = expectEdge graph

describe "buildGraph with colliding peer-suffixed snapshot keys" $ do
it "should mark the direct dev dependency as dev" $
expectDirect [mkDevDep "core@1.0.0"] graph

it "should keep edges from every colliding peer variant" $ do
hasEdge (mkDevDep "core@1.0.0") (mkDevDep "plugin-a@1.0.0")
hasEdge (mkDevDep "core@1.0.0") (mkDevDep "plugin-b@1.0.0")

it "should propagate dev to children of all merged variants" $ do
-- Without the merge, last-wins drops one variant and its child leaks
-- with an empty environment (reported as production).
expectDep (mkDevDep "plugin-a@1.0.0") graph
expectDep (mkDevDep "plugin-b@1.0.0") graph

pnpmLockV9CatalogsSpec :: Graphing Dependency -> Spec
pnpmLockV9CatalogsSpec graph = do
let hasEdge :: Dependency -> Dependency -> Expectation
Expand Down
55 changes: 55 additions & 0 deletions test/Pnpm/testdata/pnpm-9-peer-collision/pnpm-lock.yaml

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

Loading