Resolve dependencies refactor (#449)

Create a new resolveDependencies

Use Map to do differences between Wasp and user package.json dependencies.

This is now used internally in resolveNpmDeps.

Also fixes a bug in the conflict error message, where the wasp and user dependency
were previously not properly distinguished.

Add .vscode and move everything that appears text editor related to a section
This commit is contained in:
Martijn Faassen 2022-02-09 14:02:19 +01:00 committed by GitHub
parent 8a97732c71
commit 55d9e1bbd8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 135 additions and 63 deletions

11
waspc/.gitignore vendored
View File

@ -2,16 +2,19 @@
stack*.yaml.lock
dist-newstyle
waspc.cabal
.vscode
/out
*~
.dir-locals.el
.projectile
.hie/
.bin/
stan.html
# editor related
*.orig
*~
.dir-locals.el
.projectile
.vscode

View File

@ -80,6 +80,7 @@ library:
- template-haskell
- path-io
- cryptohash-sha256
- containers
executables:
wasp-cli:

View File

@ -3,6 +3,7 @@
module Wasp.AppSpec.App.Dependency
( Dependency (..),
fromList,
make,
)
where
@ -15,4 +16,7 @@ data Dependency = Dependency
deriving (Show, Eq, Data)
fromList :: [(String, String)] -> [Dependency]
fromList = map (\(n, v) -> Dependency {name = n, version = v})
fromList = map make
make :: (String, String) -> Dependency
make (n, v) = Dependency {name = n, version = v}

View File

@ -1,17 +1,66 @@
module Wasp.Generator.PackageJsonGenerator
( resolveNpmDeps,
resolveDependencies,
DependencyConflictError (DependencyConflictError),
npmDepsToPackageJsonEntry,
npmDevDepsToPackageJsonEntry,
)
where
import Data.Bifunctor (second)
import Data.List (find, intercalate)
import Data.Maybe (fromJust, isJust)
import Control.Arrow (ArrowChoice (left))
import Data.List (intercalate)
import qualified Data.Map as Map
import qualified Data.Maybe as Maybe
import qualified Wasp.AppSpec.App.Dependency as D
type NpmDependenciesConflictError = String
-- First entry is Wasp dependency, the second is the user Dependency that is
-- in conflict with it.
data DependencyConflictError = DependencyConflictError D.Dependency D.Dependency
deriving (Show, Eq)
-- | Takes wasp npm dependencies and user npm dependencies and figures out how
-- to combine them together, returning (Right) a list of npm dependencies to
-- be used on behalf of wasp and then also a list of npm dependencies to be
-- used on behalf of user. These lists might be the same as the initial ones,
-- but might also be different.
-- On error (Left), returns a list of conflicting wasp dep, user dep pairs.
resolveDependencies ::
[D.Dependency] ->
[D.Dependency] ->
Either
[DependencyConflictError]
([D.Dependency], [D.Dependency])
resolveDependencies waspDeps userDeps =
if null conflictErrors
then Right (waspDeps, Map.elems userDepsNotInWaspDeps)
else Left conflictErrors
where
waspDepsByName = makeDepsByName waspDeps
userDepsByName = makeDepsByName userDeps
overlappingDeps = waspDepsByName `Map.intersection` userDepsByName
userDepsNotInWaspDeps = userDepsByName `Map.difference` waspDepsByName
makeDepsByName :: [D.Dependency] -> Map.Map String D.Dependency
makeDepsByName = Map.fromList . fmap (\d -> (D.name d, d))
-- get all items in overlappingDeps, for each check whether there's
-- a conflicting version in userDepsMap, and if so report a conflict
conflictErrors :: [DependencyConflictError]
conflictErrors = Maybe.mapMaybe makeConflictErrorIfMismatchedVersion (Map.toAscList overlappingDeps)
where
makeConflictErrorIfMismatchedVersion :: (String, D.Dependency) -> Maybe DependencyConflictError
makeConflictErrorIfMismatchedVersion (waspDepName, waspDep) = do
userDep <- Map.lookup waspDepName userDepsByName
if D.version waspDep /= D.version userDep
then
Just $
DependencyConflictError
waspDep
userDep
else Nothing
-- | Takes wasp npm dependencies and user npm dependencies and figures out how to
-- combine them together, returning (Right) a list of npm dependencies to be used on
-- behalf of wasp and then also a list of npm dependencies to be used on behalf
@ -25,40 +74,20 @@ resolveNpmDeps ::
Either
[(D.Dependency, NpmDependenciesConflictError)]
([D.Dependency], [D.Dependency])
resolveNpmDeps waspDeps userDeps =
if null conflictingUserDeps
then Right (waspDeps, userDepsNotInWaspDeps)
else Left conflictingUserDeps
resolveNpmDeps waspDeps userDeps = left (map convertConflict) $ resolveDependencies waspDeps userDeps
where
conflictingUserDeps :: [(D.Dependency, NpmDependenciesConflictError)]
conflictingUserDeps =
map (second fromJust) $
filter (isJust . snd) $
map (\dep -> (dep, checkIfConflictingUserDep dep)) userDeps
checkIfConflictingUserDep :: D.Dependency -> Maybe NpmDependenciesConflictError
checkIfConflictingUserDep userDep =
let attachErrorMessage dep =
"Error: Dependency conflict for user dependency ("
++ D.name dep
++ ", "
++ D.version dep
++ "): "
++ "Version must be set to the exactly the same version as"
++ " the one wasp is using: "
++ D.version dep
in attachErrorMessage <$> find (areTwoDepsInConflict userDep) waspDeps
areTwoDepsInConflict :: D.Dependency -> D.Dependency -> Bool
areTwoDepsInConflict d1 d2 =
D.name d1 == D.name d2
&& D.version d1 /= D.version d2
userDepsNotInWaspDeps :: [D.Dependency]
userDepsNotInWaspDeps = filter (not . isDepWithNameInWaspDeps . D.name) userDeps
isDepWithNameInWaspDeps :: String -> Bool
isDepWithNameInWaspDeps name = any ((name ==) . D.name) waspDeps
convertConflict :: DependencyConflictError -> (D.Dependency, NpmDependenciesConflictError)
convertConflict (DependencyConflictError waspDep userDep) =
( userDep,
"Error: Dependency conflict for user dependency ("
++ D.name userDep
++ ", "
++ D.version userDep
++ "): "
++ "Version must be set to the exactly the same version as"
++ " the one wasp is using: "
++ D.version waspDep
)
npmDepsToPackageJsonEntryWithKey :: [D.Dependency] -> String -> String
npmDepsToPackageJsonEntryWithKey deps key =

View File

@ -2,35 +2,70 @@ module Generator.PackageJsonGeneratorTest where
import Test.Tasty.Hspec
import qualified Wasp.AppSpec.App.Dependency as D
import Wasp.Generator.PackageJsonGenerator (resolveNpmDeps)
import Wasp.Generator.PackageJsonGenerator
( DependencyConflictError (DependencyConflictError),
resolveDependencies,
resolveNpmDeps,
)
spec_resolveNpmDeps :: Spec
spec_resolveNpmDeps = do
let waspDeps =
[ ("axios", "^0.20.0"),
("lodash", "^4.17.15")
D.fromList
[ ("a", "1"),
("b", "2")
]
it "a conflicting version number is detected" $ do
let userDeps =
D.fromList
[ ("a", "1"),
("b", "3")
]
resolveDependencies waspDeps userDeps
`shouldBe` Left
[ DependencyConflictError
(D.make ("b", "2"))
(D.make ("b", "3"))
]
it "Concatenates two distincts lists of deps." $ do
it "wasp deps completely overlap with user deps, so no user deps required" $ do
let userDeps =
[ ("foo", "bar"),
("foo2", "bar2")
]
resolveNpmDeps (D.fromList waspDeps) (D.fromList userDeps)
`shouldBe` Right (D.fromList waspDeps, D.fromList userDeps)
D.fromList
[ ("a", "1"),
("b", "2")
]
resolveDependencies waspDeps userDeps
`shouldBe` Right (waspDeps, [])
it "Does not repeat dep if it is both user and wasp dep." $ do
it "no dependency name overlap so no conflict" $ do
let userDeps =
[ ("axios", "^0.20.0"),
("foo", "bar")
]
resolveNpmDeps (D.fromList waspDeps) (D.fromList userDeps)
`shouldBe` Right (D.fromList waspDeps, D.fromList [("foo", "bar")])
D.fromList
[ ("c", "1"),
("d", "2")
]
resolveDependencies waspDeps userDeps
`shouldBe` Right (waspDeps, userDeps)
it "Reports error if user dep version does not match wasp dep version." $ do
it "some dependencies names overlap, with the same version so dependency is not in user dep" $ do
let userDeps =
[ ("axios", "^1.20.0"),
("foo", "bar")
]
let Left conflicts = resolveNpmDeps (D.fromList waspDeps) (D.fromList userDeps)
map fst conflicts `shouldBe` D.fromList [("axios", "^1.20.0")]
D.fromList
[ ("a", "1"),
("d", "2")
]
resolveDependencies waspDeps userDeps
`shouldBe` Right (waspDeps, [D.make ("d", "2")])
it "Reports error if user dep version does not match wasp dep version" $ do
let userDeps =
D.fromList
[ ("a", "2"),
("foo", "bar")
]
let Left conflicts = resolveNpmDeps waspDeps userDeps
conflicts
`shouldBe` [ ( D.make ("a", "2"),
"Error: Dependency conflict for user dependency (a, 2): "
++ "Version must be set to the exactly "
++ "the same version as the one wasp is using: 1"
)
]