Skip to content

Commit 179a6e2

Browse files
committed
Correctly identify transitive dev deps in V3 package-lock files
1 parent e8985df commit 179a6e2

3 files changed

Lines changed: 119 additions & 7 deletions

File tree

src/Strategy/Node/Npm/PackageLockV3.hs

Lines changed: 45 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,9 @@ analyze file = context "Analyzing Npm Lockfile (v3)" $ do
221221
-- --
222222
buildGraph :: PackageLockV3 -> Graphing Dependency
223223
buildGraph pkgLockV3 = run . evalGrapher $ do
224+
let initialDevDepPaths = Set.fromList $ map toTopLevelPackage $ Map.keys $ plV3PkgDevDependencies $ rootPackage pkgLockV3
225+
let allDevDepPaths = collectAllTransitivePaths initialDevDepPaths
226+
224227
for_ (Map.toList $ packages pkgLockV3) $ \(pkgPathKey, pkgMetadata) -> do
225228
case pkgPathKey of
226229
-- For root package,
@@ -262,7 +265,7 @@ buildGraph pkgLockV3 = run . evalGrapher $ do
262265
]
263266

264267
let resolvedDeps :: [Dependency]
265-
resolvedDeps = mapMaybe toDependency directDeps
268+
resolvedDeps = mapMaybe (toDependency allDevDepPaths) directDeps
266269

267270
traverse_ direct resolvedDeps
268271

@@ -300,8 +303,11 @@ buildGraph pkgLockV3 = run . evalGrapher $ do
300303
, plV3PkgOptionalDependencies pkgMetadata
301304
]
302305

306+
let workspaceDevDeps = Set.fromList $ map (vendoredPathElseTopLevelPath workspaceRootPath) $ Map.keys $ plV3PkgDevDependencies pkgMetadata
307+
let workspaceAllDevDeps = allDevDepPaths `Set.union` workspaceDevDeps
308+
303309
let resolvedDeps :: [Dependency]
304-
resolvedDeps = mapMaybe toDependency directDeps
310+
resolvedDeps = mapMaybe (toDependency workspaceAllDevDeps) directDeps
305311

306312
traverse_ direct resolvedDeps
307313

@@ -329,7 +335,7 @@ buildGraph pkgLockV3 = run . evalGrapher $ do
329335
-- For each transitive dependency, Adds edge between parent package, and transitive dep.
330336
PackageLockV3PathKey prefixPath depPackageName -> do
331337
let currentDep :: Maybe Dependency
332-
currentDep = toDependency (PackageLockV3PathKey prefixPath depPackageName)
338+
currentDep = toDependency allDevDepPaths (PackageLockV3PathKey prefixPath depPackageName)
333339

334340
let vendorPathPrefix :: Text
335341
vendorPathPrefix = prefixPath <> (unPackageName depPackageName)
@@ -345,13 +351,45 @@ buildGraph pkgLockV3 = run . evalGrapher $ do
345351
]
346352

347353
let resolvedDeepDeps :: [Dependency]
348-
resolvedDeepDeps = mapMaybe toDependency deepDeps
354+
resolvedDeepDeps = mapMaybe (toDependency allDevDepPaths) deepDeps
349355

350356
case currentDep of
351357
Nothing -> pure () -- This will never happen, given that we are iterating on same package path.
352358
Just parentDep -> do
353359
for_ resolvedDeepDeps $ edge parentDep
354360
where
361+
-- Returns the set of all paths that are transitive to the given set of paths.
362+
collectAllTransitivePaths :: Set.Set PackagePath -> Set.Set PackagePath
363+
collectAllTransitivePaths paths =
364+
let go visited toVisit
365+
| Set.null toVisit = visited
366+
| otherwise =
367+
let (current, rest) = Set.deleteFindMin toVisit
368+
in if Set.member current visited
369+
then go visited rest
370+
else
371+
let newChildren = collectTransitivePaths current
372+
newVisited = Set.insert current visited
373+
newToVisit = rest `Set.union` (newChildren `Set.difference` newVisited)
374+
in go newVisited newToVisit
375+
in go Set.empty paths
376+
377+
-- Returns the set of all transitive paths for given package path.
378+
collectTransitivePaths :: PackagePath -> Set.Set PackagePath
379+
collectTransitivePaths pkgPath =
380+
case Map.lookup pkgPath (packages pkgLockV3) of
381+
Nothing -> Set.empty
382+
Just pkgMetadata ->
383+
let vendorPrefix = case pkgPath of
384+
PackageLockV3PathKey prefix pkgName -> prefix <> unPackageName pkgName
385+
_ -> ""
386+
directDeps = concatMap Map.keys
387+
[ plV3PkgDependencies pkgMetadata
388+
, plV3PkgPeerDependencies pkgMetadata
389+
, plV3PkgOptionalDependencies pkgMetadata
390+
]
391+
in Set.fromList $ map (vendoredPathElseTopLevelPath vendorPrefix) directDeps
392+
355393
-- Prefer resolution path in following order of precedent:
356394
--
357395
-- 1) {prefix}/node_modules/{pkgName}
@@ -374,8 +412,8 @@ buildGraph pkgLockV3 = run . evalGrapher $ do
374412
map (`PackageLockV3PathKey` pkgName) $
375413
vendorPrefixes prefix (getRootWorkspace prefix)
376414

377-
toDependency :: PackagePath -> Maybe Dependency
378-
toDependency pkgPath =
415+
toDependency :: Set.Set PackagePath -> PackagePath -> Maybe Dependency
416+
toDependency devPaths pkgPath =
379417
case (pkgPath, Map.lookup pkgPath $ packages pkgLockV3) of
380418
(PackageLockV3PathKey _ packageName, Just meta) ->
381419
Just $
@@ -385,7 +423,7 @@ buildGraph pkgLockV3 = run . evalGrapher $ do
385423
, dependencyVersion = CEq <$> (plV3PkgVersion meta)
386424
, dependencyLocations = catMaybes [unNpmLockV3Resolved $ plV3PkgResolved meta]
387425
, dependencyEnvironments =
388-
if plV3PkgDev meta
426+
if plV3PkgDev meta || Set.member pkgPath devPaths
389427
then Set.singleton EnvDevelopment
390428
else Set.singleton EnvProduction
391429
, dependencyTags = Map.empty

test/Node/PackageLockV3Spec.hs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@ multiLevelVendorLockPath = $(mkRelFile "multi-level-vendor-package-lock.json")
4848
workspaceNestedModulePackageLockPath :: Path Rel File
4949
workspaceNestedModulePackageLockPath = $(mkRelFile "ws-nested-module-package-lock.json")
5050

51+
devDependenciesLockPath :: Path Rel File
52+
devDependenciesLockPath = $(mkRelFile "dev-dependencies-package-lock.json")
53+
5154
spec :: Spec
5255
spec = do
5356
currentDir <- runIO getCurrentDir
@@ -66,6 +69,7 @@ spec = do
6669
checkGraph (graphingFixtureDir </> fixtureSimplePackageLockPath) simplePackageLockGraphSpec
6770
checkGraph (graphingFixtureDir </> workspaceNestedModulePackageLockPath) workspaceNestedModulePackageLockGraphSpec
6871
checkGraph (graphingFixtureDir </> multiLevelVendorLockPath) multiLevelVendorLockGraphSpec
72+
checkGraph (graphingFixtureDir </> devDependenciesLockPath) devDependenciesLockGraphSpec
6973

7074
vendorPrefixesSpec :: Spec
7175
vendorPrefixesSpec = do
@@ -423,6 +427,22 @@ mkDep nameAtVersion env = do
423427
(maybe mempty Set.singleton env)
424428
mempty
425429

430+
devDependenciesLockGraphSpec :: Graphing Dependency -> Spec
431+
devDependenciesLockGraphSpec graph = do
432+
let hasDep :: Dependency -> Expectation
433+
hasDep dep = expectDep dep graph
434+
435+
describe "buildGraph with devDependency environment propagation" $ do
436+
it "should correctly assign environments to all dependencies" $ do
437+
let allExpectedDeps =
438+
[ mkProdDep "prod-dep@1.0.0"
439+
, mkProdDep "prod-transitive@1.0.0"
440+
, mkDevDep "dev-dep@1.0.0"
441+
, mkDevDep "dev-transitive@1.0.0"
442+
, mkDevDep "shared-dep@1.0.0"
443+
]
444+
mapM_ hasDep allExpectedDeps
445+
426446
checkGraph :: Path Abs File -> (Graphing Dependency -> Spec) -> Spec
427447
checkGraph pathToFixture buildGraphSpec = do
428448
maybePkgLockV3 <- runIO $ eitherDecodeFileStrict' (toString pathToFixture)

test/Node/testdata/lockfileV3/graphing/dev-dependencies-package-lock.json

Lines changed: 54 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)