diff --git a/Changelog.md b/Changelog.md index b4133a5a3..f5c9e57e8 100644 --- a/Changelog.md +++ b/Changelog.md @@ -2,7 +2,7 @@ ## Unreleased -- Node.js: Yarn and npm workspace packages now appear as individual build targets (e.g. `yarn@./:my-package`, `npm@./:my-package`), enabling per-package dependency scoping via `.fossa.yml`. +- Node.js: Yarn, npm, pnpm, and bun workspace packages now appear as individual build targets (e.g. `yarn@./:my-package`, `npm@./:my-package`, `pnpm@./:my-package`, `bun@./:my-package`), enabling per-package dependency scoping via `.fossa.yml`. - Project edit: Fix 500 error when running `fossa project edit --policy` on existing projects ([#1688](https://github.com/fossas/fossa-cli/pull/1688)) ## 3.17.0 diff --git a/docs/references/strategies/languages/nodejs/bun.md b/docs/references/strategies/languages/nodejs/bun.md index ca6a4571b..76962eea1 100644 --- a/docs/references/strategies/languages/nodejs/bun.md +++ b/docs/references/strategies/languages/nodejs/bun.md @@ -39,6 +39,12 @@ Workspace entries are keyed by their relative path from the root, with `""` representing the root workspace. Each workspace declares its own `dependencies`, `devDependencies`, and `optionalDependencies`. +Each workspace package (including the root) is exposed as an individual build +target (e.g. `bun@./:my-app`, `bun@./:lib-utils`). When a subset of targets +is selected via `.fossa.yml`, only those packages' dependencies are included +in the analysis. When no filtering is applied, all targets are selected and +all dependencies from every workspace package are included. + ### Packages Package keys use a slash-delimited path for nested `node_modules`: diff --git a/docs/references/strategies/languages/nodejs/pnpm.md b/docs/references/strategies/languages/nodejs/pnpm.md index 30a4a099d..c2f2e7e43 100644 --- a/docs/references/strategies/languages/nodejs/pnpm.md +++ b/docs/references/strategies/languages/nodejs/pnpm.md @@ -152,7 +152,12 @@ CLI will infer the package name and version using `/${dependencyName}/${dependen ``` * Peer dependencies will be included in the analysis (they are treated like any other dependency). -* Pnpm workspaces are supported. +* Pnpm workspaces are supported. Each workspace package (including the root) + is exposed as an individual build target (e.g. `pnpm@./:my-app`, + `pnpm@./:lib-utils`). When a subset of targets is selected, only those + packages' dependencies are included in the analysis. When no filtering is + applied, all targets are selected and all dependencies from every workspace + package are included in the analysis. * Development dependencies (`dev: true`) are ignored by default from analysis. To include them in the analysis, execute CLI with `--include-unused` flag e.g. `fossa analyze --include-unused`. * Optional dependencies are included in the analysis by default. They can be ignored in FOSSA UI. * `fossa-cli` supports lockFileVersion: 4.x, 5.x, 6.x, 7.x, 8.x, and 9.x. diff --git a/src/Strategy/Node.hs b/src/Strategy/Node.hs index 17cf7ab16..8acf8f49a 100644 --- a/src/Strategy/Node.hs +++ b/src/Strategy/Node.hs @@ -9,6 +9,8 @@ module Strategy.Node ( getDeps, findWorkspaceBuildTargets, extractDepListsForTargets, + resolveImporterPaths, + resolveBunWorkspacePaths, ) where import Algebra.Graph.AdjacencyMap qualified as AM @@ -39,7 +41,7 @@ import Data.Maybe (catMaybes, isJust, mapMaybe) import Data.Set (Set) import Data.Set qualified as Set import Data.Set.NonEmpty qualified as NonEmptySet -import Data.String.Conversion (decodeUtf8, toString) +import Data.String.Conversion (decodeUtf8, toString, toText) import Data.Tagged (applyTag) import Data.Text (Text) import Data.Text qualified as Text @@ -73,6 +75,7 @@ import Path ( Rel, mkRelFile, parent, + stripProperPrefix, toFilePath, (), ) @@ -99,6 +102,7 @@ import Strategy.Node.Pnpm.PnpmLock qualified as PnpmLock import Strategy.Node.Pnpm.Workspace (PnpmWorkspace (workspaceSpecs)) import Strategy.Node.YarnV1.YarnLock qualified as V1 import Strategy.Node.YarnV2.YarnLock qualified as V2 +import System.FilePath.Posix qualified as FP import Types ( BuildTarget (BuildTarget), DependencyResults (DependencyResults), @@ -160,6 +164,8 @@ mkProject project = do projectBuildTargets' = case project of Yarn _ _ -> findWorkspaceBuildTargets graph NPMLock _ _ -> findWorkspaceBuildTargets graph + Pnpm _ _ -> findWorkspaceBuildTargets graph + Bun _ _ -> findWorkspaceBuildTargets graph _ -> ProjectWithoutTargets Manifest rootManifest <- fromEitherShow $ findWorkspaceRootManifest graph pure $ @@ -204,20 +210,80 @@ getDeps :: m DependencyResults getDeps targets (Yarn yarnLockFile graph) = analyzeYarn targets yarnLockFile graph getDeps targets (NPMLock packageLockFile graph) = analyzeNpmLock targets packageLockFile graph -getDeps _ (Pnpm pnpmLockFile _) = analyzePnpmLock pnpmLockFile -getDeps _ (Bun bunLockFile _) = analyzeBunLock bunLockFile +getDeps targets (Pnpm pnpmLockFile graph) = analyzePnpmLock targets pnpmLockFile graph +getDeps targets (Bun bunLockFile graph) = analyzeBunLock targets bunLockFile graph getDeps _ (NPM graph) = analyzeNpm graph -analyzePnpmLock :: (Has Diagnostics sig m, Has ReadFS sig m, Has Logger sig m) => Manifest -> m DependencyResults -analyzePnpmLock (Manifest pnpmLockFile) = do - result <- PnpmLock.analyze pnpmLockFile +analyzePnpmLock :: (Has Diagnostics sig m, Has ReadFS sig m, Has Logger sig m) => FoundTargets -> Manifest -> PkgJsonGraph -> m DependencyResults +analyzePnpmLock targets (Manifest pnpmLockFile) graph = do + let selectedImporterPaths = resolveImporterPaths targets graph + result <- PnpmLock.analyze selectedImporterPaths pnpmLockFile pure $ DependencyResults result Complete [pnpmLockFile] -analyzeBunLock :: (Has Diagnostics sig m, Has ReadFS sig m) => Manifest -> m DependencyResults -analyzeBunLock (Manifest bunLockFile) = do - result <- BunLock.analyze bunLockFile +analyzeBunLock :: (Has Diagnostics sig m, Has ReadFS sig m) => FoundTargets -> Manifest -> PkgJsonGraph -> m DependencyResults +analyzeBunLock targets (Manifest bunLockFile) graph = do + let selectedWorkspacePaths = resolveBunWorkspacePaths targets graph + result <- BunLock.analyze selectedWorkspacePaths bunLockFile pure $ DependencyResults result Complete [bunLockFile] +-- | Map selected build targets (package names) to pnpm importer paths +-- (relative paths from the workspace root like ".", "packages/a"). +-- When 'ProjectWithoutTargets', returns Nothing (no filtering). +-- When 'FoundTargets', looks up each target's package name in the +-- PkgJsonGraph to find its manifest path, then computes the relative +-- path from the workspace root. +resolveImporterPaths :: FoundTargets -> PkgJsonGraph -> Maybe (Set Text) +resolveImporterPaths ProjectWithoutTargets _ = Nothing +resolveImporterPaths (FoundTargets targets) graph@PkgJsonGraph{..} = + case findWorkspaceRootManifest graph of + Left _ -> Nothing + Right (Manifest rootManifest) -> + Just $ Set.fromList [p | (name, p) <- namePathPairs, name `Set.member` targetNames] + where + rootDir = parent rootManifest + targetNames = Set.map unBuildTarget (NonEmptySet.toSet targets) + + namePathPairs :: [(Text, Text)] + namePathPairs = + [ (name, manifestToImporterPath m) + | (Manifest m, pj) <- Map.toList jsonLookup + , Just name <- [packageName pj] + ] + + manifestToImporterPath :: Path Abs File -> Text + manifestToImporterPath m = + let manifestDir = parent m + in if manifestDir == rootDir + then "." + else maybe "." (toText . FP.dropTrailingPathSeparator . toFilePath) (stripProperPrefix rootDir manifestDir) + +-- | Like 'resolveImporterPaths' but for bun lockfiles, which use @""@ +-- for the root workspace instead of @"."@. +resolveBunWorkspacePaths :: FoundTargets -> PkgJsonGraph -> Maybe (Set Text) +resolveBunWorkspacePaths ProjectWithoutTargets _ = Nothing +resolveBunWorkspacePaths (FoundTargets targets) graph@PkgJsonGraph{..} = + case findWorkspaceRootManifest graph of + Left _ -> Nothing + Right (Manifest rootManifest) -> + Just $ Set.fromList [p | (name, p) <- namePathPairs, name `Set.member` targetNames] + where + rootDir = parent rootManifest + targetNames = Set.map unBuildTarget (NonEmptySet.toSet targets) + + namePathPairs :: [(Text, Text)] + namePathPairs = + [ (name, manifestToWorkspacePath m) + | (Manifest m, pj) <- Map.toList jsonLookup + , Just name <- [packageName pj] + ] + + manifestToWorkspacePath :: Path Abs File -> Text + manifestToWorkspacePath m = + let manifestDir = parent m + in if manifestDir == rootDir + then "" + else maybe "" (toText . FP.dropTrailingPathSeparator . toFilePath) (stripProperPrefix rootDir manifestDir) + analyzeNpmLock :: (Has Diagnostics sig m, Has ReadFS sig m) => FoundTargets -> Manifest -> PkgJsonGraph -> m DependencyResults analyzeNpmLock targets (Manifest npmLockFile) graph = do npmLockVersion <- detectNpmLockVersion npmLockFile diff --git a/src/Strategy/Node/Bun/BunLock.hs b/src/Strategy/Node/Bun/BunLock.hs index 8574c6f14..6079daa5a 100644 --- a/src/Strategy/Node/Bun/BunLock.hs +++ b/src/Strategy/Node/Bun/BunLock.hs @@ -29,6 +29,7 @@ import Data.Aeson ( import Data.Foldable (for_) import Data.Map (Map) import Data.Map qualified as Map +import Data.Set (Set) import Data.Set qualified as Set import Data.Text (Text) import Data.Text qualified as Text @@ -187,13 +188,17 @@ parseResolution res in (name, Text.drop 1 rest) -- | Analyze a bun.lock file and produce a dependency graph. +-- When @selectedWorkspacePaths@ is 'Nothing', all workspaces are included. +-- When @Just paths@, only workspaces whose key is in @paths@ contribute +-- direct dependencies. analyze :: (Has ReadFS sig m, Has Diagnostics sig m) => + Maybe (Set Text) -> Path Abs File -> m (Graphing Dependency) -analyze file = do +analyze selectedWorkspacePaths file = do lockfile <- context "Parsing bun.lock" $ readContentsJsonc file - context "Building dependency graph" $ pure $ buildGraph lockfile + context "Building dependency graph" $ pure $ buildGraph selectedWorkspacePaths lockfile -- | Build a dependency graph from a parsed bun lockfile. -- @@ -209,9 +214,9 @@ analyze file = do -- Uses 'LabeledGrapher' so that vertices are environment-agnostic and -- environments accumulate as labels, avoiding duplicate vertices when -- the same package appears in both prod and dev across workspaces. -buildGraph :: BunLockfile -> Graphing Dependency -buildGraph lockfile = run . withLabeling vertexToDependency $ do - for_ allWorkspaces $ \workspace -> do +buildGraph :: Maybe (Set Text) -> BunLockfile -> Graphing Dependency +buildGraph selectedWorkspacePaths lockfile = run . withLabeling vertexToDependency $ do + for_ filteredWorkspaces $ \workspace -> do markDirectDeps EnvProduction workspace.wsDependencies markDirectDeps EnvDevelopment workspace.wsDevDependencies markDirectDeps EnvProduction workspace.wsOptionalDependencies @@ -232,6 +237,11 @@ buildGraph lockfile = run . withLabeling vertexToDependency $ do allWorkspaces :: [BunWorkspace] allWorkspaces = Map.elems $ workspaces lockfile + filteredWorkspaces :: [BunWorkspace] + filteredWorkspaces = case selectedWorkspacePaths of + Nothing -> allWorkspaces + Just paths -> Map.elems $ Map.filterWithKey (\k _ -> k `Set.member` paths) (workspaces lockfile) + devDepNames :: Set.Set PackageName devDepNames = Set.fromList $ concatMap (Map.keys . wsDevDependencies) allWorkspaces diff --git a/src/Strategy/Node/Pnpm/PnpmLock.hs b/src/Strategy/Node/Pnpm/PnpmLock.hs index 0d43fd184..cf90da3d4 100644 --- a/src/Strategy/Node/Pnpm/PnpmLock.hs +++ b/src/Strategy/Node/Pnpm/PnpmLock.hs @@ -297,23 +297,33 @@ instance FromJSON Resolution where gitRes :: Object -> Parser Resolution gitRes obj = GitResolve <$> (GitResolution <$> obj .: "repo" <*> obj .: "commit") -analyze :: (Has ReadFS sig m, Has Logger sig m, Has Diagnostics sig m) => Path Abs File -> m (Graphing Dependency) -analyze file = context "Analyzing Npm Lockfile (v3)" $ do +-- | Analyze a pnpm lockfile. When @selectedImporterPaths@ is 'Nothing', +-- all importers are included. When @Just paths@, only importers whose +-- key is in @paths@ are treated as direct dependency sources. +analyze :: (Has ReadFS sig m, Has Logger sig m, Has Diagnostics sig m) => Maybe (Set.Set Text) -> Path Abs File -> m (Graphing Dependency) +analyze selectedImporterPaths file = context "Analyzing Npm Lockfile (v3)" $ do pnpmLockFile <- context "Parsing pnpm-lock file" $ readContentsYaml file case lockFileVersion pnpmLockFile of PnpmLockLt4 raw -> logWarn . pretty $ "pnpm-lock file is using older lockFileVersion: " <> raw <> " of, which is not officially supported!" _ -> pure () - context "Building dependency graph" $ pure $ buildGraph pnpmLockFile + context "Building dependency graph" $ pure $ buildGraph selectedImporterPaths pnpmLockFile -- Build the dependency graph, labeling direct deps with their environment -- (prod/dev). hydrateDepEnvs then propagates those environments to all -- transitive successors. -buildGraph :: PnpmLockfile -> Graphing Dependency -buildGraph lockFile = withoutLocalPackages . hydrateDepEnvs $ +-- +-- When @selectedImporterPaths@ is 'Nothing', all importers are included. +-- When @Just paths@, only importers whose key is in @paths@ are treated as +-- direct dependency sources (used for workspace build target filtering). +buildGraph :: Maybe (Set.Set Text) -> PnpmLockfile -> Graphing Dependency +buildGraph selectedImporterPaths lockFile = withoutLocalPackages . hydrateDepEnvs $ run . withLabeling applyLabels $ do - for_ (toList lockFile.importers) $ \(_, projectImporters) -> do + let filteredImporters = case selectedImporterPaths of + Nothing -> toList lockFile.importers + Just paths -> filter (\(k, _) -> k `Set.member` paths) (toList lockFile.importers) + for_ filteredImporters $ \(_, projectImporters) -> do for_ (Map.toList $ directDependencies projectImporters) $ \(depName, ProjectMapDepMetadata depVersion) -> for_ (toResolvedDependency depName depVersion) $ \dep -> do direct dep diff --git a/test/Bun/BunLockSpec.hs b/test/Bun/BunLockSpec.hs index ad2372050..ae176cad8 100644 --- a/test/Bun/BunLockSpec.hs +++ b/test/Bun/BunLockSpec.hs @@ -50,6 +50,7 @@ spec = do jsoncSpec jsoncPath dependenciesSpec depsPath workspacesSpec wsPath + workspaceFilterSpec wsPath bunProjectSpec bunProjectPath gitDepsSpec gitDepsPath mixedEnvsSpec mixedEnvsPath @@ -152,6 +153,33 @@ workspacesSpec path = names `shouldNotContain` "@types/app" names `shouldNotContain` "utils" +-- | Workspace target filtering: verify that filtering by workspace path +-- scopes which workspaces contribute direct dependencies. +workspaceFilterSpec :: Path Abs File -> Spec +workspaceFilterSpec path = + describe "workspace target filtering" $ do + describe "filtered to root only" $ + checkGraphWithFilter (Just (Set.singleton "")) path $ \graph -> do + let directDeps = Graphing.directList graph + + it "should include root dev deps as direct" $ + directDeps `shouldContainDep` mkDevDep "typescript" "5.3.3" + + it "should not include sub-workspace deps as direct" $ do + let directNames = map dependencyName directDeps + directNames `shouldNotContain` "lodash" + + describe "filtered to sub-workspace only" $ + checkGraphWithFilter (Just (Set.singleton "packages/app")) path $ \graph -> do + let directDeps = Graphing.directList graph + + it "should include sub-workspace deps as direct" $ + directDeps `shouldContainDep` mkProdDep "lodash" "4.17.21" + + it "should not include root deps as direct" $ do + let directNames = map dependencyName directDeps + directNames `shouldNotContain` "typescript" + -- | Bun project: a real-world bun.lock from the bun project itself. -- Covers scoped packages, git refs, nested package keys, optional/peer deps -- in packages, and large dependency counts. @@ -232,13 +260,16 @@ mixedEnvsSpec path = -- | Parse a bun.lock in IO for graph tests (outside the effect stack). checkGraph :: Path Abs File -> (Graphing Dependency -> Spec) -> Spec -checkGraph path graphSpec = do +checkGraph = checkGraphWithFilter Nothing + +checkGraphWithFilter :: Maybe (Set.Set Text) -> Path Abs File -> (Graphing Dependency -> Spec) -> Spec +checkGraphWithFilter wsFilter path graphSpec = do result <- runIO $ parseBunLockIO path case result of Left err -> describe (fromAbsFile path) $ it "should parse" (expectationFailure err) - Right lockfile -> graphSpec (buildGraph lockfile) + Right lockfile -> graphSpec (buildGraph wsFilter lockfile) parseBunLockIO :: Path Abs File -> IO (Either String BunLockfile) parseBunLockIO path = do diff --git a/test/Node/NodeSpec.hs b/test/Node/NodeSpec.hs index e1a937067..21ce9d550 100644 --- a/test/Node/NodeSpec.hs +++ b/test/Node/NodeSpec.hs @@ -13,7 +13,7 @@ import Data.Tagged (applyTag) import Graphing qualified import Path (Abs, Dir, Path, mkRelDir, mkRelFile, ()) import Path.IO (getCurrentDir) -import Strategy.Node (NodeProject (NPMLock), discover, extractDepListsForTargets, findWorkspaceBuildTargets, getDeps) +import Strategy.Node (NodeProject (NPMLock), discover, extractDepListsForTargets, findWorkspaceBuildTargets, getDeps, resolveBunWorkspacePaths, resolveImporterPaths) import Strategy.Node.PackageJson ( FlatDeps (..), Manifest (..), @@ -57,6 +57,8 @@ spec = do npmLockAnalysisSpec currDir workspaceBuildTargetsSpec currDir extractDepListsForTargetsSpec currDir + resolveImporterPathsSpec currDir + resolveBunWorkspacePathsSpec currDir discoveredWorkSpaceProj :: Path Abs Dir -> DiscoveredProject NodeProject discoveredWorkSpaceProj currDir = @@ -238,6 +240,56 @@ extractDepListsForTargetsSpec currDir = describe "extractDepListsForTargets" $ d ] directDeps result `shouldBe` applyTag @Production expectedDirect +resolveImporterPathsSpec :: Path Abs Dir -> Spec +resolveImporterPathsSpec currDir = describe "resolveImporterPaths" $ do + let graph = workspaceGraphWithDeps currDir + + it "returns Nothing for ProjectWithoutTargets" $ do + resolveImporterPaths ProjectWithoutTargets graph `shouldBe` Nothing + + it "maps root target to \".\"" $ do + let targets = + maybe ProjectWithoutTargets FoundTargets . nonEmpty $ + Set.fromList [BuildTarget "workspace-test"] + resolveImporterPaths targets graph `shouldBe` Just (Set.singleton ".") + + it "maps workspace member targets to relative paths" $ do + let targets = + maybe ProjectWithoutTargets FoundTargets . nonEmpty $ + Set.fromList [BuildTarget "pkg-a", BuildTarget "pkg-b"] + resolveImporterPaths targets graph `shouldBe` Just (Set.fromList ["pkg-a", "nested/pkg-b"]) + + it "maps all targets including root" $ do + let targets = + maybe ProjectWithoutTargets FoundTargets . nonEmpty $ + Set.fromList [BuildTarget "workspace-test", BuildTarget "pkg-a", BuildTarget "pkg-b"] + resolveImporterPaths targets graph `shouldBe` Just (Set.fromList [".", "pkg-a", "nested/pkg-b"]) + +resolveBunWorkspacePathsSpec :: Path Abs Dir -> Spec +resolveBunWorkspacePathsSpec currDir = describe "resolveBunWorkspacePaths" $ do + let graph = workspaceGraphWithDeps currDir + + it "returns Nothing for ProjectWithoutTargets" $ do + resolveBunWorkspacePaths ProjectWithoutTargets graph `shouldBe` Nothing + + it "maps root target to empty string" $ do + let targets = + maybe ProjectWithoutTargets FoundTargets . nonEmpty $ + Set.fromList [BuildTarget "workspace-test"] + resolveBunWorkspacePaths targets graph `shouldBe` Just (Set.singleton "") + + it "maps workspace member targets to relative paths" $ do + let targets = + maybe ProjectWithoutTargets FoundTargets . nonEmpty $ + Set.fromList [BuildTarget "pkg-a", BuildTarget "pkg-b"] + resolveBunWorkspacePaths targets graph `shouldBe` Just (Set.fromList ["pkg-a", "nested/pkg-b"]) + + it "maps all targets including root" $ do + let targets = + maybe ProjectWithoutTargets FoundTargets . nonEmpty $ + Set.fromList [BuildTarget "workspace-test", BuildTarget "pkg-a", BuildTarget "pkg-b"] + resolveBunWorkspacePaths targets graph `shouldBe` Just (Set.fromList ["", "pkg-a", "nested/pkg-b"]) + -- | A workspace graph with actual dependencies for testing extractDepListsForTargets. workspaceGraphWithDeps :: Path Abs Dir -> PkgJsonGraph workspaceGraphWithDeps currDir = diff --git a/test/Pnpm/PnpmLockSpec.hs b/test/Pnpm/PnpmLockSpec.hs index 523a81856..a07cb618f 100644 --- a/test/Pnpm/PnpmLockSpec.hs +++ b/test/Pnpm/PnpmLockSpec.hs @@ -89,10 +89,13 @@ lodash = mempty checkGraph :: Path Abs File -> (Graphing Dependency -> Spec) -> Spec -checkGraph pathToFixture buildGraphSpec = do +checkGraph = checkGraphWithFilter Nothing + +checkGraphWithFilter :: Maybe (Set.Set Text) -> Path Abs File -> (Graphing Dependency -> Spec) -> Spec +checkGraphWithFilter importerFilter pathToFixture buildGraphSpec = do eitherDecodedLockFile <- runIO $ decodeFileEither (toString pathToFixture) case eitherDecodedLockFile of - Right pnpmLock -> buildGraphSpec (buildGraph pnpmLock) + Right pnpmLock -> buildGraphSpec (buildGraph importerFilter pnpmLock) Left err -> describe "pnpm-lock" $ it "should parse lockfile" (expectationFailure $ prettyPrintParseException err) @@ -106,6 +109,11 @@ spec = do checkGraph pnpmLockPath pnpmLockGraphSpec checkGraph pnpmLockWithoutWorkspacePath pnpmLockGraphWithoutWorkspaceSpec + -- v5 workspace target filtering + describe "workspace target filtering" $ do + checkGraphWithFilter (Just (Set.singleton ".")) pnpmLockPath pnpmLockFilteredRootOnlySpec + checkGraphWithFilter (Just (Set.singleton "packages/a")) pnpmLockPath pnpmLockFilteredWorkspaceOnlySpec + -- v6 format let pnpmLockV6 = currentDir $(mkRelFile "test/Pnpm/testdata/pnpm-lock-v6.yaml") let pnpmLockV6WithWorkspace = currentDir $(mkRelFile "test/Pnpm/testdata/pnpm-lock-v6-workspace.yaml") @@ -487,3 +495,31 @@ pnpmLockV9MultiVersionSpec graph = do expectDep (mkProdDep "sax@1.2.1") graph -- sax@1.4.4 is dev-only (from app-b) expectDep (mkDevDep "sax@1.4.4") graph + +-- | When filtered to root importer only ("."), workspace package deps +-- (aws-sdk@1.0.0, commander@9.2.0) should not appear as direct. +pnpmLockFilteredRootOnlySpec :: Graphing Dependency -> Spec +pnpmLockFilteredRootOnlySpec graph = do + describe "filtered to root importer only" $ do + it "should only include root direct deps" $ do + expectDirect + [ mkProdDep "aws-sdk@2.1148.0" + , colors + , lodash + , mkDevDep "react@18.1.0" + , mkProdDep "glob@8.0.3" + , mkProdDep "chokidar@1.0.0" + ] + graph + +-- | When filtered to workspace importer only ("packages/a"), root deps +-- (aws-sdk@2.1148.0, colors, lodash, react) should not appear as direct. +pnpmLockFilteredWorkspaceOnlySpec :: Graphing Dependency -> Spec +pnpmLockFilteredWorkspaceOnlySpec graph = do + describe "filtered to workspace importer only" $ do + it "should only include workspace-a direct deps" $ do + expectDirect + [ mkProdDep "aws-sdk@1.0.0" + , mkProdDep "commander@9.2.0" + ] + graph