mirror of
https://github.com/wasp-lang/wasp.git
synced 2024-09-11 07:16:09 +03:00
Adapt wasp clean
and fix NPM dep installation (#1603)
--------- Co-authored-by: Martin Sosic <sosic.martin@gmail.com>
This commit is contained in:
parent
df5a1358e6
commit
1456a89222
@ -19,7 +19,6 @@ import Wasp.Cli.Command.CreateNewProject (createNewProject)
|
||||
import qualified Wasp.Cli.Command.CreateNewProject.AI as Command.CreateNewProject.AI
|
||||
import Wasp.Cli.Command.Db (runDbCommand)
|
||||
import qualified Wasp.Cli.Command.Db.Migrate as Command.Db.Migrate
|
||||
import qualified Wasp.Cli.Command.Db.Reset as Command.Db.Reset
|
||||
import qualified Wasp.Cli.Command.Db.Seed as Command.Db.Seed
|
||||
import qualified Wasp.Cli.Command.Db.Studio as Command.Db.Studio
|
||||
import Wasp.Cli.Command.Deploy (deploy)
|
||||
@ -158,7 +157,7 @@ printUsage =
|
||||
cmd " start Runs Wasp app in development mode, watching for file changes.",
|
||||
cmd " start db Starts managed development database for you.",
|
||||
cmd " db <db-cmd> [args] Executes a database command. Run 'wasp db' for more info.",
|
||||
cmd " clean Deletes all generated code and other cached artifacts.",
|
||||
cmd " clean Deletes all generated code, all cached artifacts, and the node_modules dir.",
|
||||
" Wasp equivalent of 'have you tried closing and opening it again?'.",
|
||||
cmd " build Generates full web app code, ready for deployment. Use when deploying or ejecting.",
|
||||
cmd " deploy Deploys your Wasp app to cloud hosting providers.",
|
||||
@ -200,7 +199,6 @@ dbCli :: [String] -> IO ()
|
||||
dbCli args = case args of
|
||||
["start"] -> runCommand Command.Start.Db.start
|
||||
"migrate-dev" : optionalMigrateArgs -> runDbCommand $ Command.Db.Migrate.migrateDev optionalMigrateArgs
|
||||
["reset"] -> runDbCommand Command.Db.Reset.reset
|
||||
["seed"] -> runDbCommand $ Command.Db.Seed.seed Nothing
|
||||
["seed", seedName] -> runDbCommand $ Command.Db.Seed.seed $ Just seedName
|
||||
["studio"] -> runDbCommand Command.Db.Studio.studio
|
||||
|
@ -26,7 +26,26 @@ bashCompletion = do
|
||||
["db", cmdPrefix] -> listMatchingCommands cmdPrefix dbSubCommands
|
||||
_ -> liftIO . putStrLn $ ""
|
||||
where
|
||||
commands = ["new", "version", "waspls", "start", "db", "clean", "uninstall", "build", "telemetry", "deps", "info", "completion", "completion:generate"]
|
||||
commands =
|
||||
[ "new",
|
||||
"new:ai",
|
||||
"version",
|
||||
"waspls",
|
||||
"completion",
|
||||
"completion:generate",
|
||||
"uninstall",
|
||||
"start",
|
||||
"db",
|
||||
"clean",
|
||||
"build",
|
||||
"deploy",
|
||||
"telemetry",
|
||||
"deps",
|
||||
"dockerfile",
|
||||
"info",
|
||||
"test",
|
||||
"studio"
|
||||
]
|
||||
dbSubCommands = ["migrate-dev", "studio"]
|
||||
listMatchingCommands :: String -> [String] -> Command ()
|
||||
listMatchingCommands cmdPrefix cmdList = listCommands $ filter (cmdPrefix `isPrefixOf`) cmdList
|
||||
|
@ -3,26 +3,18 @@ module Wasp.Cli.Command.Clean
|
||||
)
|
||||
where
|
||||
|
||||
import Control.Monad.IO.Class (liftIO)
|
||||
import qualified StrongPath as SP
|
||||
import System.Directory
|
||||
( doesDirectoryExist,
|
||||
removeDirectoryRecursive,
|
||||
)
|
||||
import Wasp.Cli.Command (Command)
|
||||
import Wasp.Cli.Command.Message (cliSendMessageC)
|
||||
import Wasp.Cli.Command.Common (deleteDirectoryIfExistsVerbosely)
|
||||
import Wasp.Cli.Command.Require (InWaspProject (InWaspProject), require)
|
||||
import qualified Wasp.Message as Msg
|
||||
import Wasp.Project.Common (dotWaspDirInWaspProjectDir)
|
||||
import Wasp.Project.Common (dotWaspDirInWaspProjectDir, nodeModulesDirInWaspProjectDir)
|
||||
|
||||
clean :: Command ()
|
||||
clean = do
|
||||
InWaspProject waspProjectDir <- require
|
||||
let dotWaspDirFp = SP.toFilePath $ waspProjectDir SP.</> dotWaspDirInWaspProjectDir
|
||||
cliSendMessageC $ Msg.Start "Deleting .wasp/ directory..."
|
||||
doesDotWaspDirExist <- liftIO $ doesDirectoryExist dotWaspDirFp
|
||||
if doesDotWaspDirExist
|
||||
then do
|
||||
liftIO $ removeDirectoryRecursive dotWaspDirFp
|
||||
cliSendMessageC $ Msg.Success "Deleted .wasp/ directory."
|
||||
else cliSendMessageC $ Msg.Success "Nothing to delete: .wasp directory does not exist."
|
||||
|
||||
let dotWaspDir = waspProjectDir SP.</> dotWaspDirInWaspProjectDir
|
||||
let nodeModulesDir = waspProjectDir SP.</> nodeModulesDirInWaspProjectDir
|
||||
|
||||
deleteDirectoryIfExistsVerbosely dotWaspDir
|
||||
deleteDirectoryIfExistsVerbosely nodeModulesDir
|
||||
|
@ -1,15 +1,21 @@
|
||||
module Wasp.Cli.Command.Common
|
||||
( readWaspCompileInfo,
|
||||
throwIfExeIsNotAvailable,
|
||||
deleteDirectoryIfExistsVerbosely,
|
||||
)
|
||||
where
|
||||
|
||||
import Control.Monad.Except
|
||||
import qualified Control.Monad.Except as E
|
||||
import StrongPath (Abs, Dir, Path')
|
||||
import qualified StrongPath as SP
|
||||
import StrongPath.Operations
|
||||
import System.Directory (findExecutable)
|
||||
import System.Directory
|
||||
( findExecutable,
|
||||
)
|
||||
import Wasp.Cli.Command (Command, CommandError (..))
|
||||
import Wasp.Cli.Command.Message (cliSendMessageC)
|
||||
import qualified Wasp.Message as Msg
|
||||
import Wasp.Project (WaspProjectDir)
|
||||
import qualified Wasp.Project.Common as Project.Common
|
||||
import Wasp.Util (ifM)
|
||||
@ -34,3 +40,16 @@ throwIfExeIsNotAvailable exeName explanationMsg = do
|
||||
Nothing ->
|
||||
E.throwError $
|
||||
CommandError ("Couldn't find `" <> exeName <> "` executable") explanationMsg
|
||||
|
||||
deleteDirectoryIfExistsVerbosely :: Path' Abs (Dir d) -> Command ()
|
||||
deleteDirectoryIfExistsVerbosely dir = do
|
||||
cliSendMessageC $ Msg.Start $ "Deleting the " ++ dirName ++ " directory..."
|
||||
dirExist <- liftIO $ IOUtil.doesDirectoryExist dir
|
||||
if dirExist
|
||||
then do
|
||||
liftIO $ IOUtil.removeDirectory dir
|
||||
cliSendMessageC $ Msg.Success $ "Deleted the " ++ dirName ++ " directory."
|
||||
else do
|
||||
cliSendMessageC $ Msg.Success $ "Nothing to delete: The " ++ dirName ++ " directory does not exist."
|
||||
where
|
||||
dirName = SP.toFilePath $ basename dir
|
||||
|
@ -19,33 +19,50 @@ import Wasp.Cli.Message (cliSendMessage)
|
||||
import qualified Wasp.Generator.Common as Wasp.Generator
|
||||
import qualified Wasp.Message as Msg
|
||||
import Wasp.Project (CompileError, CompileWarning, WaspProjectDir)
|
||||
import Wasp.Project.Common (extClientCodeDirInWaspProjectDir, extServerCodeDirInWaspProjectDir, extSharedCodeDirInWaspProjectDir)
|
||||
import Wasp.Project.Common (srcDirInWaspProjectDir)
|
||||
|
||||
-- TODO: Idea: Read .gitignore file, and ignore everything from it. This will then also cover the
|
||||
-- .wasp dir, and users can easily add any custom stuff they want ignored. But, we also have to
|
||||
-- be ready for the case when there is no .gitignore, that could be possible.
|
||||
|
||||
-- | Forever listens for any file changes in waspProjectDir, and if there is a change,
|
||||
-- compiles Wasp source files in waspProjectDir and regenerates files in outDir.
|
||||
-- It will defer recompilation until no new change was detected in the last second.
|
||||
-- It also takes 'ongoingCompilationResultMVar' MVar, into which it stores the result
|
||||
-- (warnings, errors) of the latest (re)compile whenever it happens. If there is already
|
||||
-- something in the MVar, it will get overwritten.
|
||||
-- | Forever listens for any file changes at the very top level of @waspProjectDir@, and also for
|
||||
-- any changes at any depth in the @waspProjectDir@/src/ dir. If there is a change, compiles Wasp
|
||||
-- source files in @waspProjectDir@ and regenerates files in @outDir@. It will defer recompilation
|
||||
-- until no new change was detected in the last second. It also takes 'ongoingCompilationResultMVar'
|
||||
-- MVar, into which it stores the result (warnings, errors) of the latest (re)compile whenever it
|
||||
-- happens. If there is already something in the MVar, it will get overwritten.
|
||||
watch ::
|
||||
Path' Abs (Dir WaspProjectDir) ->
|
||||
Path' Abs (Dir Wasp.Generator.ProjectRootDir) ->
|
||||
MVar ([CompileWarning], [CompileError]) ->
|
||||
IO ()
|
||||
watch waspProjectDir outDir ongoingCompilationResultMVar = FSN.withManager $ \mgr -> do
|
||||
currentTime <- getCurrentTime
|
||||
chan <- newChan
|
||||
_ <- FSN.watchDirChan mgr (SP.fromAbsDir waspProjectDir) eventFilter chan
|
||||
let watchProjectSubdirTree path = FSN.watchTreeChan mgr (SP.fromAbsDir $ waspProjectDir </> path) eventFilter chan
|
||||
_ <- watchProjectSubdirTree extClientCodeDirInWaspProjectDir
|
||||
_ <- watchProjectSubdirTree extServerCodeDirInWaspProjectDir
|
||||
_ <- watchProjectSubdirTree extSharedCodeDirInWaspProjectDir
|
||||
listenForEvents chan currentTime
|
||||
_ <- watchFilesAtTopLevelOfWaspProjectDir mgr chan
|
||||
_ <- watchFilesAtAllLevelsOfSrcDirInWaspProjectDir mgr chan
|
||||
listenForEvents chan =<< getCurrentTime
|
||||
where
|
||||
watchFilesAtTopLevelOfWaspProjectDir mgr chan =
|
||||
FSN.watchDirChan mgr (SP.fromAbsDir waspProjectDir) eventFilter chan
|
||||
where
|
||||
eventFilter :: FSN.Event -> Bool
|
||||
eventFilter event =
|
||||
-- TODO: Might be valuable to also filter out files from .gitignore.
|
||||
not (isEditorTmpFile filename)
|
||||
&& filename /= "package-lock.json"
|
||||
where
|
||||
filename = FP.takeFileName $ FSN.eventPath event
|
||||
|
||||
watchFilesAtAllLevelsOfSrcDirInWaspProjectDir mgr chan =
|
||||
FSN.watchTreeChan mgr (SP.fromAbsDir $ waspProjectDir </> srcDirInWaspProjectDir) eventFilter chan
|
||||
where
|
||||
eventFilter :: FSN.Event -> Bool
|
||||
eventFilter event =
|
||||
-- TODO: Might be valuable to also filter out files from .gitignore.
|
||||
not (isEditorTmpFile filename)
|
||||
where
|
||||
filename = FP.takeFileName $ FSN.eventPath event
|
||||
|
||||
listenForEvents :: Chan FSN.Event -> UTCTime -> IO ()
|
||||
listenForEvents chan lastCompileTime = do
|
||||
event <- readChan chan
|
||||
@ -114,12 +131,13 @@ watch waspProjectDir outDir ongoingCompilationResultMVar = FSN.withManager $ \mg
|
||||
-- create next to the source code. Bad thing here is that users can't modify this,
|
||||
-- so better approach would be probably to use information from .gitignore instead, or
|
||||
-- maybe combining the two somehow.
|
||||
eventFilter :: FSN.Event -> Bool
|
||||
eventFilter event =
|
||||
let filename = FP.takeFileName $ FSN.eventPath event
|
||||
in not (null filename)
|
||||
&& take 2 filename /= ".#" -- Ignore emacs lock files.
|
||||
&& not (head filename == '#' && last filename == '#') -- Ignore emacs auto-save files.
|
||||
&& last filename /= '~' -- Ignore emacs and vim backup files.
|
||||
&& not (head filename == '.' && ".swp" `isSuffixOf` filename) -- Ignore vim swp files.
|
||||
&& not (head filename == '.' && ".un~" `isSuffixOf` filename) -- Ignore vim undo files.
|
||||
isEditorTmpFile :: String -> Bool
|
||||
isEditorTmpFile "" = False
|
||||
isEditorTmpFile filename =
|
||||
or
|
||||
[ take 2 filename == ".#", -- Emacs lock files.
|
||||
head filename == '#' && last filename == '#', -- Emacs auto-save files.
|
||||
last filename == '~', -- Emacs and vim backup files.
|
||||
head filename == '.' && ".swp" `isSuffixOf` filename, -- Vim swp files.
|
||||
head filename == '.' && ".un~" `isSuffixOf` filename -- Vim undo files.
|
||||
]
|
||||
|
@ -4,15 +4,16 @@ module Wasp.Generator.NpmDependencies
|
||||
( DependencyConflictError (..),
|
||||
getDependenciesPackageJsonEntry,
|
||||
getDevDependenciesPackageJsonEntry,
|
||||
getUserNpmDepsForPackage,
|
||||
combineNpmDepsForPackage,
|
||||
NpmDepsForPackage (..),
|
||||
NpmDepsForPackageError (..),
|
||||
conflictErrorToMessage,
|
||||
genNpmDepsForPackage,
|
||||
NpmDepsForFullStack,
|
||||
NpmDepsForFramework,
|
||||
NpmDepsForWasp (..),
|
||||
NpmDepsForUser (..),
|
||||
buildNpmDepsForFullStack,
|
||||
buildWaspFrameworkNpmDeps,
|
||||
)
|
||||
where
|
||||
|
||||
@ -27,16 +28,15 @@ import qualified Wasp.AppSpec.App.Dependency as D
|
||||
import qualified Wasp.AppSpec.PackageJson as AS.PackageJson
|
||||
import Wasp.Generator.Monad (Generator, GeneratorError (..), logAndThrowGeneratorError)
|
||||
|
||||
data NpmDepsForFullStack = NpmDepsForFullStack
|
||||
data NpmDepsForFramework = NpmDepsForFramework
|
||||
{ npmDepsForServer :: NpmDepsForPackage,
|
||||
npmDepsForWebApp :: NpmDepsForPackage
|
||||
}
|
||||
deriving (Show, Eq, Generic)
|
||||
|
||||
instance ToJSON NpmDepsForFullStack where
|
||||
toEncoding = genericToEncoding defaultOptions
|
||||
instance ToJSON NpmDepsForFramework
|
||||
|
||||
instance FromJSON NpmDepsForFullStack
|
||||
instance FromJSON NpmDepsForFramework
|
||||
|
||||
data NpmDepsForPackage = NpmDepsForPackage
|
||||
{ dependencies :: [D.Dependency],
|
||||
@ -44,6 +44,10 @@ data NpmDepsForPackage = NpmDepsForPackage
|
||||
}
|
||||
deriving (Show, Generic)
|
||||
|
||||
instance ToJSON NpmDepsForPackage
|
||||
|
||||
instance FromJSON NpmDepsForPackage
|
||||
|
||||
data NpmDepsForWasp = NpmDepsForWasp
|
||||
{ waspDependencies :: [D.Dependency],
|
||||
waspDevDependencies :: [D.Dependency]
|
||||
@ -54,12 +58,11 @@ data NpmDepsForUser = NpmDepsForUser
|
||||
{ userDependencies :: [D.Dependency],
|
||||
userDevDependencies :: [D.Dependency]
|
||||
}
|
||||
deriving (Show)
|
||||
deriving (Show, Eq, Generic)
|
||||
|
||||
instance ToJSON NpmDepsForPackage where
|
||||
toEncoding = genericToEncoding defaultOptions
|
||||
instance ToJSON NpmDepsForUser
|
||||
|
||||
instance FromJSON NpmDepsForPackage
|
||||
instance FromJSON NpmDepsForUser
|
||||
|
||||
data NpmDepsForPackageError = NpmDepsForPackageError
|
||||
{ dependenciesConflictErrors :: [DependencyConflictError],
|
||||
@ -89,12 +92,12 @@ genNpmDepsForPackage spec npmDepsForWasp =
|
||||
++ devDependenciesConflictErrors conflictErrorDeps
|
||||
)
|
||||
|
||||
buildNpmDepsForFullStack :: AppSpec -> NpmDepsForWasp -> NpmDepsForWasp -> Either String NpmDepsForFullStack
|
||||
buildNpmDepsForFullStack spec forServer forWebApp =
|
||||
buildWaspFrameworkNpmDeps :: AppSpec -> NpmDepsForWasp -> NpmDepsForWasp -> Either String NpmDepsForFramework
|
||||
buildWaspFrameworkNpmDeps spec forServer forWebApp =
|
||||
case (combinedServerDeps, combinedWebAppDeps) of
|
||||
(Right a, Right b) ->
|
||||
Right
|
||||
NpmDepsForFullStack
|
||||
NpmDepsForFramework
|
||||
{ npmDepsForServer = a,
|
||||
npmDepsForWebApp = b
|
||||
}
|
||||
@ -135,6 +138,12 @@ sortedDependencies a = (sort $ dependencies a, sort $ devDependencies a)
|
||||
-- to combine them together, returning (Right) a new NpmDepsForPackage
|
||||
-- that combines them, and on error (Left), returns a NpmDepsForPackageError
|
||||
-- which describes which dependencies are in conflict.
|
||||
-- TODO: The comment above and function name are not exactly correct any more,
|
||||
-- as user deps don't get combined with the wasp deps any more, instead user deps
|
||||
-- are just checked against wasp deps to see if there are any conflicts, and then
|
||||
-- wasp deps are more or less returned as they are (maybe with some changes? But certainly no user deps added).
|
||||
-- This function deserves rewriting / rethinking. This should be addressed while solving
|
||||
-- GH issue https://github.com/wasp-lang/wasp/issues/1644 .
|
||||
combineNpmDepsForPackage :: NpmDepsForWasp -> NpmDepsForUser -> Either NpmDepsForPackageError NpmDepsForPackage
|
||||
combineNpmDepsForPackage npmDepsForWasp npmDepsForUser =
|
||||
if null conflictErrors && null devConflictErrors
|
||||
|
@ -1,114 +1,136 @@
|
||||
module Wasp.Generator.NpmInstall
|
||||
( isNpmInstallNeeded,
|
||||
installNpmDependenciesWithInstallRecord,
|
||||
( installNpmDependenciesWithInstallRecord,
|
||||
)
|
||||
where
|
||||
|
||||
import Control.Concurrent (Chan, newChan, readChan, threadDelay, writeChan)
|
||||
import Control.Concurrent.Async (concurrently)
|
||||
import Control.Monad (when)
|
||||
import Control.Monad.Except (MonadError (throwError), runExceptT)
|
||||
import Control.Monad.IO.Class (liftIO)
|
||||
import qualified Data.Aeson as Aeson
|
||||
import qualified Data.ByteString.Lazy as B
|
||||
import Data.Function ((&))
|
||||
import Data.Functor ((<&>))
|
||||
import qualified Data.Text as T
|
||||
import StrongPath (Abs, Dir, File', Path', Rel, relfile, (</>))
|
||||
import Debug.Pretty.Simple (pTrace)
|
||||
import StrongPath (Abs, Dir, Path')
|
||||
import qualified StrongPath as SP
|
||||
import System.Directory (doesFileExist, removeFile)
|
||||
import System.Exit (ExitCode (..))
|
||||
import UnliftIO (race)
|
||||
import Wasp.AppSpec (AppSpec)
|
||||
import Wasp.AppSpec (AppSpec (waspProjectDir))
|
||||
import Wasp.Generator.Common (ProjectRootDir)
|
||||
import Wasp.Generator.Job (Job, JobMessage, JobType)
|
||||
import qualified Wasp.Generator.Job as J
|
||||
import Wasp.Generator.Job.IO.PrefixedWriter (PrefixedWriter, printJobMessagePrefixed, runPrefixedWriter)
|
||||
import Wasp.Generator.Monad (GeneratorError (..), GeneratorWarning (..))
|
||||
import qualified Wasp.Generator.NpmDependencies as N
|
||||
import Wasp.Generator.Monad (GeneratorError (..))
|
||||
import Wasp.Generator.NpmInstall.Common (AllNpmDeps (..), getAllNpmDeps)
|
||||
import Wasp.Generator.NpmInstall.InstalledNpmDepsLog (forgetInstalledNpmDepsLog, loadInstalledNpmDepsLog, saveInstalledNpmDepsLog)
|
||||
import qualified Wasp.Generator.SdkGenerator as SdkGenerator
|
||||
import Wasp.Generator.ServerGenerator as SG
|
||||
import qualified Wasp.Generator.ServerGenerator.Setup as ServerSetup
|
||||
import Wasp.Generator.WebAppGenerator as WG
|
||||
import qualified Wasp.Generator.WebAppGenerator.Setup as WebAppSetup
|
||||
import Wasp.Project.Common (WaspProjectDir)
|
||||
|
||||
-- | Figure out if npm install is needed.
|
||||
--
|
||||
-- Redundant npm installs can be avoided if the dependencies specified
|
||||
-- by the user and wasp have not changed since the last time this ran.
|
||||
--
|
||||
-- Npm instal is needed only if the dependencies described in the user wasp file are
|
||||
-- different from the dependencies that we just installed. To this end, this
|
||||
-- code keeps track of the dependencies installed with a metadata file, which
|
||||
-- it updates after each install.
|
||||
--
|
||||
-- NOTE: we assume that the dependencies in package.json are the same as the
|
||||
-- ones we derive from the AppSpec. We derive them the same way but it does
|
||||
-- involve different code paths.
|
||||
-- This module could work in an completely different way, independently
|
||||
-- from AppSpec at all. It could work by ensuring a `npm install` is
|
||||
-- consistent with a metadata file derived from `package.json` during its
|
||||
-- previous run. This would be more decoupled from the rest of the system.
|
||||
-- Npm conflict handling could be ignored in that case, because it would work
|
||||
-- from the record of what's in package.json.
|
||||
isNpmInstallNeeded :: AppSpec -> Path' Abs (Dir ProjectRootDir) -> IO (Either String (Maybe N.NpmDepsForFullStack))
|
||||
isNpmInstallNeeded spec dstDir = do
|
||||
let errorOrNpmDepsForFullStack = N.buildNpmDepsForFullStack spec (SG.npmDepsForWasp spec) (WG.npmDepsForWasp spec)
|
||||
case errorOrNpmDepsForFullStack of
|
||||
Left message -> return $ Left $ "determining npm deps to install failed: " ++ message
|
||||
Right npmDepsForFullStack -> do
|
||||
isInstallNeeded <- isNpmInstallDifferent npmDepsForFullStack dstDir
|
||||
return $
|
||||
Right $
|
||||
if isInstallNeeded
|
||||
then Just npmDepsForFullStack
|
||||
else Nothing
|
||||
|
||||
-- Run npm install for desired AppSpec dependencies, recording what we installed
|
||||
-- Installation may fail, in which the installation record is removed.
|
||||
-- Runs `npm install` for:
|
||||
-- 1. User's Wasp project (based on their package.json): user deps.
|
||||
-- 2. Wasp's generated webapp project: wasp deps.
|
||||
-- 3. Wasp's generated server project: wasp deps.
|
||||
-- (1) runs first, (2) and (3) run concurrently after it.
|
||||
-- It collects the output produced by these commands to pass them along to IO with a prefix.
|
||||
installNpmDependenciesWithInstallRecord ::
|
||||
N.NpmDepsForFullStack ->
|
||||
Path' Abs (Dir WaspProjectDir) ->
|
||||
AppSpec ->
|
||||
Path' Abs (Dir ProjectRootDir) ->
|
||||
IO ([GeneratorWarning], [GeneratorError])
|
||||
installNpmDependenciesWithInstallRecord npmDepsForFullStack waspProjectDir dstDir = do
|
||||
-- in case anything fails during installation that would leave node modules in
|
||||
-- a broken state, we remove the file before we start npm install
|
||||
fileExists <- doesFileExist dependenciesInstalledFp
|
||||
when fileExists $ removeFile dependenciesInstalledFp
|
||||
-- now actually do the installation
|
||||
npmInstallResult <- installNpmDependencies waspProjectDir dstDir
|
||||
case npmInstallResult of
|
||||
Left npmInstallError -> do
|
||||
return ([], [GenericGeneratorError $ "npm install failed: " ++ npmInstallError])
|
||||
Right () -> do
|
||||
-- on successful npm install, record what we installed
|
||||
B.writeFile dependenciesInstalledFp (Aeson.encode npmDepsForFullStack)
|
||||
return ([], [])
|
||||
IO (Either GeneratorError ())
|
||||
installNpmDependenciesWithInstallRecord spec dstDir = runExceptT $ do
|
||||
messagesChan <- liftIO newChan
|
||||
|
||||
allNpmDeps <- getAllNpmDeps spec & onLeftThrowError
|
||||
|
||||
liftIO (areThereNpmDepsToInstall allNpmDeps dstDir) >>= \case
|
||||
False -> pure ()
|
||||
True -> do
|
||||
-- In case anything fails during installation that would leave node modules in
|
||||
-- a broken state, we remove the log of installed npm deps before we start npm install.
|
||||
liftIO $ forgetInstalledNpmDepsLog dstDir
|
||||
|
||||
liftIO (installProjectNpmDependencies messagesChan (waspProjectDir spec))
|
||||
>>= onLeftThrowError
|
||||
|
||||
liftIO (installWebAppAndServerNpmDependencies messagesChan dstDir)
|
||||
>>= onLeftThrowError
|
||||
|
||||
liftIO $ saveInstalledNpmDepsLog allNpmDeps dstDir
|
||||
|
||||
pure ()
|
||||
where
|
||||
dependenciesInstalledFp = SP.fromAbsFile $ dstDir </> installedFullStackNpmDependenciesFileInProjectRootDir
|
||||
onLeftThrowError =
|
||||
either (\e -> throwError $ GenericGeneratorError $ "npm install failed: " ++ e) pure
|
||||
|
||||
-- Returns True only if the stored full stack dependencies are different from the
|
||||
-- the full stack dependencies in the argument. If an installation record is missing
|
||||
-- then it's always different.
|
||||
isNpmInstallDifferent :: N.NpmDepsForFullStack -> Path' Abs (Dir ProjectRootDir) -> IO Bool
|
||||
isNpmInstallDifferent appSpecFullStackNpmDependencies dstDir = do
|
||||
installedFullStackNpmDependencies <- loadInstalledFullStackNpmDependencies dstDir
|
||||
return $ Just appSpecFullStackNpmDependencies /= installedFullStackNpmDependencies
|
||||
-- Installs npm dependencies from the user's package.json, by running `npm install` .
|
||||
installProjectNpmDependencies ::
|
||||
Chan JobMessage -> SP.Path SP.System Abs (Dir WaspProjectDir) -> IO (Either String ())
|
||||
installProjectNpmDependencies messagesChan projectDir =
|
||||
handleProjectInstallMessages messagesChan `concurrently` installProjectDepsJob
|
||||
<&> snd
|
||||
<&> \case
|
||||
ExitFailure code -> Left $ "Project setup failed with exit code " ++ show code ++ "."
|
||||
_success -> Right ()
|
||||
where
|
||||
installProjectDepsJob =
|
||||
installNpmDependenciesAndReport (SdkGenerator.installNpmDependencies projectDir) messagesChan J.Wasp
|
||||
handleProjectInstallMessages :: Chan J.JobMessage -> IO ()
|
||||
handleProjectInstallMessages = runPrefixedWriter . processMessages
|
||||
where
|
||||
processMessages :: Chan J.JobMessage -> PrefixedWriter ()
|
||||
processMessages chan = do
|
||||
jobMsg <- liftIO $ readChan chan
|
||||
case J._data jobMsg of
|
||||
J.JobOutput {} -> printJobMessagePrefixed jobMsg >> processMessages chan
|
||||
J.JobExit {} -> return ()
|
||||
|
||||
-- TODO: we probably want to put this in a `waspmeta` directory in the future
|
||||
installedFullStackNpmDependenciesFileInProjectRootDir :: Path' (Rel ProjectRootDir) File'
|
||||
installedFullStackNpmDependenciesFileInProjectRootDir = [relfile|installedFullStackNpmDependencies.json|]
|
||||
-- Install npm dependencies for the Wasp's generated webapp and server projects.
|
||||
installWebAppAndServerNpmDependencies ::
|
||||
Chan JobMessage -> SP.Path SP.System Abs (Dir ProjectRootDir) -> IO (Either String ())
|
||||
installWebAppAndServerNpmDependencies messagesChan dstDir =
|
||||
handleSetupJobsMessages messagesChan
|
||||
`concurrently` (installServerDepsJob `concurrently` installWebAppDepsJob)
|
||||
<&> snd
|
||||
<&> \case
|
||||
(ExitSuccess, ExitSuccess) -> Right ()
|
||||
exitCodes -> Left $ setupFailedMessage exitCodes
|
||||
where
|
||||
installServerDepsJob = installNpmDependenciesAndReport (ServerSetup.installNpmDependencies dstDir) messagesChan J.Server
|
||||
installWebAppDepsJob = installNpmDependenciesAndReport (WebAppSetup.installNpmDependencies dstDir) messagesChan J.WebApp
|
||||
|
||||
-- Load the record of the dependencies we installed from disk.
|
||||
loadInstalledFullStackNpmDependencies :: Path' Abs (Dir ProjectRootDir) -> IO (Maybe N.NpmDepsForFullStack)
|
||||
loadInstalledFullStackNpmDependencies dstDir = do
|
||||
let dependenciesInstalledFp = SP.fromAbsFile $ dstDir </> installedFullStackNpmDependenciesFileInProjectRootDir
|
||||
fileExists <- doesFileExist dependenciesInstalledFp
|
||||
if fileExists
|
||||
then do
|
||||
fileContents <- B.readFile dependenciesInstalledFp
|
||||
return (Aeson.decode fileContents :: Maybe N.NpmDepsForFullStack)
|
||||
else return Nothing
|
||||
handleSetupJobsMessages = runPrefixedWriter . processMessages (False, False)
|
||||
where
|
||||
processMessages :: (Bool, Bool) -> Chan J.JobMessage -> PrefixedWriter ()
|
||||
processMessages (True, True) _ = return ()
|
||||
processMessages (isWebAppDone, isServerDone) chan = do
|
||||
jobMsg <- liftIO $ readChan chan
|
||||
case J._data jobMsg of
|
||||
J.JobOutput {} ->
|
||||
printJobMessagePrefixed jobMsg
|
||||
>> processMessages (isWebAppDone, isServerDone) chan
|
||||
J.JobExit {} -> case J._jobType jobMsg of
|
||||
J.WebApp -> processMessages (True, isServerDone) chan
|
||||
J.Server -> processMessages (isWebAppDone, True) chan
|
||||
J.Db -> error "This should never happen. No Db job should be active."
|
||||
J.Wasp -> error "This should never happen. No Wasp job should be active."
|
||||
|
||||
setupFailedMessage (serverExitCode, webAppExitCode) =
|
||||
let serverErrorMessage = case serverExitCode of
|
||||
ExitFailure code -> " Server setup failed with exit code " ++ show code ++ "."
|
||||
_success -> ""
|
||||
webAppErrorMessage = case webAppExitCode of
|
||||
ExitFailure code -> " Web app setup failed with exit code " ++ show code ++ "."
|
||||
_success -> ""
|
||||
in "Setup failed!" ++ serverErrorMessage ++ webAppErrorMessage
|
||||
|
||||
installNpmDependenciesAndReport :: Job -> Chan JobMessage -> JobType -> IO ExitCode
|
||||
installNpmDependenciesAndReport installJob chan jobType = do
|
||||
writeChan chan $ J.JobMessage {J._data = J.JobOutput "Starting npm install\n" J.Stdout, J._jobType = jobType}
|
||||
result <- installJob chan `race` reportInstallationProgress chan jobType
|
||||
case result of
|
||||
Left exitCode -> return exitCode
|
||||
Right _ -> error "This should never happen, reporting installation progress should run forever."
|
||||
|
||||
reportInstallationProgress :: Chan JobMessage -> JobType -> IO ()
|
||||
reportInstallationProgress chan jobType = reportPeriodically allPossibleMessages
|
||||
@ -130,78 +152,21 @@ reportInstallationProgress chan jobType = reportPeriodically allPossibleMessages
|
||||
"You've been waiting so patiently, just wait a little longer (for the installation to finish)..."
|
||||
]
|
||||
|
||||
installNpmDependenciesAndReport :: Job -> Chan JobMessage -> JobType -> IO ExitCode
|
||||
installNpmDependenciesAndReport installJob chan jobType = do
|
||||
writeChan chan $ J.JobMessage {J._data = J.JobOutput "Starting npm install\n" J.Stdout, J._jobType = jobType}
|
||||
result <- installJob chan `race` reportInstallationProgress chan jobType
|
||||
case result of
|
||||
Left exitCode -> return exitCode
|
||||
Right _ -> error "This should never happen, reporting installation progress should run forever."
|
||||
|
||||
{- HLINT ignore installNpmDependencies "Redundant <$>" -}
|
||||
|
||||
-- Run the individual `npm install` commands for both server and webapp projects
|
||||
-- It runs these concurrently, collects the output produced by these commands
|
||||
-- to pass them along to IO with a prefix
|
||||
installNpmDependencies :: Path' Abs (Dir WaspProjectDir) -> Path' Abs (Dir ProjectRootDir) -> IO (Either String ())
|
||||
installNpmDependencies projectDir dstDir = do
|
||||
messagesChan <- newChan
|
||||
installProjectNpmDependencies messagesChan projectDir >>= \case
|
||||
ExitFailure code -> return $ Left $ "Project setup failed with exit code " ++ show code ++ "."
|
||||
_success -> do
|
||||
installWebAppAndServerNpmDependencies messagesChan dstDir <&> \case
|
||||
(ExitSuccess, ExitSuccess) -> Right ()
|
||||
exitCodes -> Left $ setupFailedMessage exitCodes
|
||||
where
|
||||
setupFailedMessage (serverExitCode, webAppExitCode) =
|
||||
let serverErrorMessage = case serverExitCode of
|
||||
ExitFailure code -> " Server setup failed with exit code " ++ show code ++ "."
|
||||
_success -> ""
|
||||
webAppErrorMessage = case webAppExitCode of
|
||||
ExitFailure code -> " Web app setup failed with exit code " ++ show code ++ "."
|
||||
_success -> ""
|
||||
in "Setup failed!" ++ serverErrorMessage ++ webAppErrorMessage
|
||||
|
||||
installProjectNpmDependencies ::
|
||||
Chan JobMessage -> SP.Path SP.System Abs (Dir WaspProjectDir) -> IO ExitCode
|
||||
installProjectNpmDependencies messagesChan projectDir =
|
||||
snd <$> handleProjectInstallMessages messagesChan `concurrently` installProjectDepsJob
|
||||
where
|
||||
installProjectDepsJob =
|
||||
installNpmDependenciesAndReport
|
||||
(SdkGenerator.installNpmDependencies projectDir)
|
||||
messagesChan
|
||||
J.Wasp
|
||||
handleProjectInstallMessages :: Chan J.JobMessage -> IO ()
|
||||
handleProjectInstallMessages = runPrefixedWriter . processMessages
|
||||
where
|
||||
processMessages :: Chan J.JobMessage -> PrefixedWriter ()
|
||||
processMessages chan = do
|
||||
jobMsg <- liftIO $ readChan chan
|
||||
case J._data jobMsg of
|
||||
J.JobOutput {} -> printJobMessagePrefixed jobMsg >> processMessages chan
|
||||
J.JobExit {} -> return ()
|
||||
|
||||
installWebAppAndServerNpmDependencies ::
|
||||
Chan JobMessage -> SP.Path SP.System Abs (Dir ProjectRootDir) -> IO (ExitCode, ExitCode)
|
||||
installWebAppAndServerNpmDependencies messagesChan dstDir =
|
||||
snd <$> handleSetupJobsMessages messagesChan `concurrently` (installServerDepsJob `concurrently` installWebAppDepsJob)
|
||||
where
|
||||
installServerDepsJob = installNpmDependenciesAndReport (ServerSetup.installNpmDependencies dstDir) messagesChan J.Server
|
||||
installWebAppDepsJob = installNpmDependenciesAndReport (WebAppSetup.installNpmDependencies dstDir) messagesChan J.WebApp
|
||||
|
||||
handleSetupJobsMessages = runPrefixedWriter . processMessages (False, False)
|
||||
where
|
||||
processMessages :: (Bool, Bool) -> Chan J.JobMessage -> PrefixedWriter ()
|
||||
processMessages (True, True) _ = return ()
|
||||
processMessages (isWebAppDone, isServerDone) chan = do
|
||||
jobMsg <- liftIO $ readChan chan
|
||||
case J._data jobMsg of
|
||||
J.JobOutput {} ->
|
||||
printJobMessagePrefixed jobMsg
|
||||
>> processMessages (isWebAppDone, isServerDone) chan
|
||||
J.JobExit {} -> case J._jobType jobMsg of
|
||||
J.WebApp -> processMessages (True, isServerDone) chan
|
||||
J.Server -> processMessages (isWebAppDone, True) chan
|
||||
J.Db -> error "This should never happen. No Db job should be active."
|
||||
J.Wasp -> error "This should never happen. No Wasp job should be active."
|
||||
-- | Figure out if installation of npm deps is needed, be it for user npm deps (top level
|
||||
-- package.json), for wasp framework npm deps (web app, server), or for wasp sdk npm deps.
|
||||
--
|
||||
-- To this end, this code keeps track of the dependencies installed with a metadata file, which
|
||||
-- it updates after each install.
|
||||
--
|
||||
-- TODO(martin): Here, we do a single check for all the deps. This means we don't know if user deps
|
||||
-- or wasp sdk deps or wasp framework deps need installing, and so the user of this function will
|
||||
-- likely run `npm install` for all of them, which means 3 times (for user npm deps (+ wasp sdk
|
||||
-- deps, those are all done with single npm install), for wasp webapp npm deps, for wasp server
|
||||
-- npm deps). We could, relatively easily, since we already differentiate all these deps, return
|
||||
-- exact info on which deps need installation, and therefore run only needed npm installs. We
|
||||
-- could return such info by either returning a triple (Bool, Bool, Bool) for (user+sdk, webapp,
|
||||
-- server) deps, or we could return a list of enum which says which deps to install.
|
||||
areThereNpmDepsToInstall :: AllNpmDeps -> Path' Abs (Dir ProjectRootDir) -> IO Bool
|
||||
areThereNpmDepsToInstall allNpmDeps dstDir = do
|
||||
installedNpmDeps <- loadInstalledNpmDepsLog dstDir
|
||||
return $ installedNpmDeps /= Just allNpmDeps
|
||||
|
42
waspc/src/Wasp/Generator/NpmInstall/Common.hs
Normal file
42
waspc/src/Wasp/Generator/NpmInstall/Common.hs
Normal file
@ -0,0 +1,42 @@
|
||||
{-# LANGUAGE DeriveGeneric #-}
|
||||
|
||||
module Wasp.Generator.NpmInstall.Common
|
||||
( AllNpmDeps (..),
|
||||
getAllNpmDeps,
|
||||
)
|
||||
where
|
||||
|
||||
import Data.Aeson (FromJSON, ToJSON)
|
||||
import GHC.Generics (Generic)
|
||||
import Wasp.AppSpec (AppSpec)
|
||||
import qualified Wasp.Generator.NpmDependencies as N
|
||||
import qualified Wasp.Generator.SdkGenerator as SdkGenerator
|
||||
import qualified Wasp.Generator.ServerGenerator as SG
|
||||
import qualified Wasp.Generator.WebAppGenerator as WG
|
||||
|
||||
data AllNpmDeps = AllNpmDeps
|
||||
{ _userNpmDeps :: !N.NpmDepsForUser, -- Deps coming from user's package.json .
|
||||
_waspFrameworkNpmDeps :: !N.NpmDepsForFramework, -- Deps coming from Wasp's framework code (webapp, server) package.jsons.
|
||||
_waspSdkNpmDeps :: !N.NpmDepsForPackage -- Deps coming from Wasp's SDK's package.json .
|
||||
}
|
||||
deriving (Eq, Show, Generic)
|
||||
|
||||
instance ToJSON AllNpmDeps
|
||||
|
||||
instance FromJSON AllNpmDeps
|
||||
|
||||
getAllNpmDeps :: AppSpec -> Either String AllNpmDeps
|
||||
getAllNpmDeps spec =
|
||||
let userNpmDeps = N.getUserNpmDepsForPackage spec
|
||||
errorOrWaspFrameworkNpmDeps =
|
||||
N.buildWaspFrameworkNpmDeps spec (SG.npmDepsForWasp spec) (WG.npmDepsForWasp spec)
|
||||
waspSdkNpmDeps = SdkGenerator.npmDepsForSdk spec
|
||||
in case errorOrWaspFrameworkNpmDeps of
|
||||
Left message -> Left $ "determining npm deps to install failed: " ++ message
|
||||
Right waspFrameworkNpmDeps ->
|
||||
Right $
|
||||
AllNpmDeps
|
||||
{ _userNpmDeps = userNpmDeps,
|
||||
_waspFrameworkNpmDeps = waspFrameworkNpmDeps,
|
||||
_waspSdkNpmDeps = waspSdkNpmDeps
|
||||
}
|
42
waspc/src/Wasp/Generator/NpmInstall/InstalledNpmDepsLog.hs
Normal file
42
waspc/src/Wasp/Generator/NpmInstall/InstalledNpmDepsLog.hs
Normal file
@ -0,0 +1,42 @@
|
||||
module Wasp.Generator.NpmInstall.InstalledNpmDepsLog
|
||||
( loadInstalledNpmDepsLog,
|
||||
saveInstalledNpmDepsLog,
|
||||
forgetInstalledNpmDepsLog,
|
||||
)
|
||||
where
|
||||
|
||||
import qualified Data.Aeson as Aeson
|
||||
import qualified Data.ByteString.Lazy as B
|
||||
import StrongPath (Abs, Dir, File', Path', Rel, relfile, (</>))
|
||||
import qualified StrongPath as SP
|
||||
import System.Directory (doesFileExist)
|
||||
import Wasp.Generator.Common (ProjectRootDir)
|
||||
import Wasp.Generator.NpmInstall.Common (AllNpmDeps)
|
||||
import Wasp.Util.IO (deleteFileIfExists)
|
||||
|
||||
-- Load the log of the npm dependencies we installed, from disk.
|
||||
loadInstalledNpmDepsLog :: Path' Abs (Dir ProjectRootDir) -> IO (Maybe AllNpmDeps)
|
||||
loadInstalledNpmDepsLog dstDir = do
|
||||
fileExists <- doesFileExist $ SP.fromAbsFile logFilePath
|
||||
if fileExists
|
||||
then do
|
||||
fileContents <- B.readFile $ SP.fromAbsFile logFilePath
|
||||
return (Aeson.decode fileContents :: Maybe AllNpmDeps)
|
||||
else return Nothing
|
||||
where
|
||||
logFilePath = getInstalledNpmDepsLogFilePath dstDir
|
||||
|
||||
-- Save the record of the Wasp's (webapp + server) npm dependencies we installed, to disk.
|
||||
saveInstalledNpmDepsLog :: AllNpmDeps -> Path' Abs (Dir ProjectRootDir) -> IO ()
|
||||
saveInstalledNpmDepsLog deps dstDir =
|
||||
B.writeFile (SP.fromAbsFile $ getInstalledNpmDepsLogFilePath dstDir) (Aeson.encode deps)
|
||||
|
||||
forgetInstalledNpmDepsLog :: Path' Abs (Dir ProjectRootDir) -> IO ()
|
||||
forgetInstalledNpmDepsLog dstDir =
|
||||
deleteFileIfExists $ getInstalledNpmDepsLogFilePath dstDir
|
||||
|
||||
getInstalledNpmDepsLogFilePath :: Path' Abs (Dir ProjectRootDir) -> Path' Abs File'
|
||||
getInstalledNpmDepsLogFilePath dstDir = dstDir </> installedNpmDepsLogFileInProjectRootDir
|
||||
|
||||
installedNpmDepsLogFileInProjectRootDir :: Path' (Rel ProjectRootDir) File'
|
||||
installedNpmDepsLogFileInProjectRootDir = [relfile|installedNpmDepsLog.json|]
|
@ -43,39 +43,42 @@ genPackageJson spec =
|
||||
[relfile|package.json|]
|
||||
( Just $
|
||||
object
|
||||
[ "depsChunk" .= N.getDependenciesPackageJsonEntry npmDepsForSdk,
|
||||
"devDepsChunk" .= N.getDevDependenciesPackageJsonEntry npmDepsForSdk
|
||||
[ "depsChunk" .= N.getDependenciesPackageJsonEntry npmDeps,
|
||||
"devDepsChunk" .= N.getDevDependenciesPackageJsonEntry npmDeps
|
||||
]
|
||||
)
|
||||
where
|
||||
npmDepsForSdk =
|
||||
N.NpmDepsForPackage
|
||||
{ N.dependencies =
|
||||
AS.Dependency.fromList
|
||||
[ ("@prisma/client", show prismaVersion),
|
||||
("prisma", show prismaVersion),
|
||||
("@tanstack/react-query", "^4.29.0"),
|
||||
("axios", "^1.4.0"),
|
||||
("express", "~4.18.1"),
|
||||
("jsonwebtoken", "^8.5.1"),
|
||||
("mitt", "3.0.0"),
|
||||
("react", "^18.2.0"),
|
||||
("react-router-dom", "^5.3.3"),
|
||||
("react-hook-form", "^7.45.4"),
|
||||
("secure-password", "^4.0.0"),
|
||||
("superjson", "^1.12.2"),
|
||||
("@types/express-serve-static-core", "^4.17.13")
|
||||
]
|
||||
++ depsRequiredForAuth spec
|
||||
-- This must be installed in the SDK because it lists prisma/client as a dependency.
|
||||
-- Installing it inside .wasp/out/server/node_modules would also
|
||||
-- install prisma/client in the same folder, which would cause our
|
||||
-- runtime to load the wrong (uninitialized prisma/client)
|
||||
-- TODO(filip): Find a better way to handle duplicate
|
||||
-- dependencies: https://github.com/wasp-lang/wasp/issues/1640
|
||||
++ ServerAuthG.depsRequiredByAuth spec,
|
||||
N.devDependencies = AS.Dependency.fromList []
|
||||
}
|
||||
npmDeps = npmDepsForSdk spec
|
||||
|
||||
npmDepsForSdk :: AppSpec -> N.NpmDepsForPackage
|
||||
npmDepsForSdk spec =
|
||||
N.NpmDepsForPackage
|
||||
{ N.dependencies =
|
||||
AS.Dependency.fromList
|
||||
[ ("@prisma/client", show prismaVersion),
|
||||
("prisma", show prismaVersion),
|
||||
("@tanstack/react-query", "^4.29.0"),
|
||||
("axios", "^1.4.0"),
|
||||
("express", "~4.18.1"),
|
||||
("jsonwebtoken", "^8.5.1"),
|
||||
("mitt", "3.0.0"),
|
||||
("react", "^18.2.0"),
|
||||
("react-router-dom", "^5.3.3"),
|
||||
("react-hook-form", "^7.45.4"),
|
||||
("secure-password", "^4.0.0"),
|
||||
("superjson", "^1.12.2"),
|
||||
("@types/express-serve-static-core", "^4.17.13")
|
||||
]
|
||||
++ depsRequiredForAuth spec
|
||||
-- This must be installed in the SDK because it lists prisma/client as a dependency.
|
||||
-- Installing it inside .wasp/out/server/node_modules would also
|
||||
-- install prisma/client in the same folder, which would cause our
|
||||
-- runtime to load the wrong (uninitialized prisma/client)
|
||||
-- TODO(filip): Find a better way to handle duplicate
|
||||
-- dependencies: https://github.com/wasp-lang/wasp/issues/1640
|
||||
++ ServerAuthG.depsRequiredByAuth spec,
|
||||
N.devDependencies = AS.Dependency.fromList []
|
||||
}
|
||||
|
||||
depsRequiredForAuth :: AppSpec -> [AS.Dependency.Dependency]
|
||||
depsRequiredForAuth spec =
|
||||
|
@ -5,30 +5,20 @@ where
|
||||
|
||||
import Control.Monad (when)
|
||||
import StrongPath (Abs, Dir, Path')
|
||||
import Wasp.AppSpec (AppSpec (waspProjectDir))
|
||||
import Wasp.AppSpec (AppSpec)
|
||||
import Wasp.Generator.Common (ProjectRootDir)
|
||||
import qualified Wasp.Generator.DbGenerator as DbGenerator
|
||||
import Wasp.Generator.Monad (GeneratorError (..), GeneratorWarning (..))
|
||||
import Wasp.Generator.NpmInstall (installNpmDependenciesWithInstallRecord, isNpmInstallNeeded)
|
||||
import Wasp.Generator.NpmInstall (installNpmDependenciesWithInstallRecord)
|
||||
import qualified Wasp.Message as Msg
|
||||
|
||||
runSetup :: AppSpec -> Path' Abs (Dir ProjectRootDir) -> Msg.SendMessage -> IO ([GeneratorWarning], [GeneratorError])
|
||||
runSetup spec dstDir sendMessage = do
|
||||
runNpmInstallIfNeeded spec dstDir sendMessage >>= \case
|
||||
npmInstallResults@(_, []) -> (npmInstallResults <>) <$> setUpDatabase spec dstDir sendMessage
|
||||
npmInstallResults -> return npmInstallResults
|
||||
|
||||
runNpmInstallIfNeeded :: AppSpec -> Path' Abs (Dir ProjectRootDir) -> Msg.SendMessage -> IO ([GeneratorWarning], [GeneratorError])
|
||||
runNpmInstallIfNeeded spec dstDir sendMessage = do
|
||||
isNpmInstallNeeded spec dstDir >>= \case
|
||||
Left errorMessage -> return ([], [GenericGeneratorError errorMessage])
|
||||
Right maybeFullStackDeps -> case maybeFullStackDeps of
|
||||
Nothing -> return ([], [])
|
||||
Just fullStackDeps -> do
|
||||
(npmInstallWarnings, npmInstallErrors) <-
|
||||
installNpmDependenciesWithInstallRecord fullStackDeps (waspProjectDir spec) dstDir
|
||||
when (null npmInstallErrors) (sendMessage $ Msg.Success "Successfully completed npm install.")
|
||||
return (npmInstallWarnings, npmInstallErrors)
|
||||
installNpmDependenciesWithInstallRecord spec dstDir >>= \case
|
||||
Right () -> do
|
||||
sendMessage $ Msg.Success "Successfully completed npm install."
|
||||
setUpDatabase spec dstDir sendMessage
|
||||
Left e -> return ([], [e])
|
||||
|
||||
setUpDatabase :: AppSpec -> Path' Abs (Dir ProjectRootDir) -> Msg.SendMessage -> IO ([GeneratorWarning], [GeneratorError])
|
||||
setUpDatabase spec dstDir sendMessage = do
|
||||
|
@ -1,19 +1,22 @@
|
||||
module Wasp.Project.Common
|
||||
( findFileInWaspProjectDir,
|
||||
CompileError,
|
||||
CompileWarning,
|
||||
WaspProjectDir,
|
||||
packageJsonInWaspProjectDir,
|
||||
( WaspProjectDir,
|
||||
DotWaspDir,
|
||||
NodeModulesDir,
|
||||
findFileInWaspProjectDir,
|
||||
dotWaspDirInWaspProjectDir,
|
||||
dotWaspRootFileInWaspProjectDir,
|
||||
dotWaspInfoFileInGeneratedCodeDir,
|
||||
extServerCodeDirInWaspProjectDir,
|
||||
extClientCodeDirInWaspProjectDir,
|
||||
extSharedCodeDirInWaspProjectDir,
|
||||
generatedCodeDirInDotWaspDir,
|
||||
buildDirInDotWaspDir,
|
||||
waspProjectDirFromProjectRootDir,
|
||||
dotWaspRootFileInWaspProjectDir,
|
||||
dotWaspInfoFileInGeneratedCodeDir,
|
||||
srcDirInWaspProjectDir,
|
||||
extServerCodeDirInWaspProjectDir,
|
||||
extClientCodeDirInWaspProjectDir,
|
||||
extSharedCodeDirInWaspProjectDir,
|
||||
packageJsonInWaspProjectDir,
|
||||
nodeModulesDirInWaspProjectDir,
|
||||
CompileError,
|
||||
CompileWarning,
|
||||
)
|
||||
where
|
||||
|
||||
@ -26,6 +29,8 @@ data WaspProjectDir -- Root dir of Wasp project, containing source files.
|
||||
|
||||
data DotWaspDir -- Here we put everything that wasp generates.
|
||||
|
||||
data NodeModulesDir
|
||||
|
||||
-- | NOTE: If you change the depth of this path, also update @waspProjectDirFromProjectRootDir@ below.
|
||||
-- TODO: SHould this be renamed to include word "root"?
|
||||
dotWaspDirInWaspProjectDir :: Path' (Rel WaspProjectDir) (Dir DotWaspDir)
|
||||
@ -53,18 +58,24 @@ dotWaspRootFileInWaspProjectDir = [relfile|.wasproot|]
|
||||
dotWaspInfoFileInGeneratedCodeDir :: Path' (Rel Wasp.Generator.Common.ProjectRootDir) File'
|
||||
dotWaspInfoFileInGeneratedCodeDir = [relfile|.waspinfo|]
|
||||
|
||||
srcDirInWaspProjectDir :: Path' (Rel WaspProjectDir) (Dir SourceExternalCodeDir)
|
||||
srcDirInWaspProjectDir = [reldir|src|]
|
||||
|
||||
extServerCodeDirInWaspProjectDir :: Path' (Rel WaspProjectDir) (Dir SourceExternalCodeDir)
|
||||
extServerCodeDirInWaspProjectDir = [reldir|src|]
|
||||
extServerCodeDirInWaspProjectDir = srcDirInWaspProjectDir
|
||||
|
||||
extClientCodeDirInWaspProjectDir :: Path' (Rel WaspProjectDir) (Dir SourceExternalCodeDir)
|
||||
extClientCodeDirInWaspProjectDir = [reldir|src|]
|
||||
extClientCodeDirInWaspProjectDir = srcDirInWaspProjectDir
|
||||
|
||||
extSharedCodeDirInWaspProjectDir :: Path' (Rel WaspProjectDir) (Dir SourceExternalCodeDir)
|
||||
extSharedCodeDirInWaspProjectDir = [reldir|src|]
|
||||
extSharedCodeDirInWaspProjectDir = srcDirInWaspProjectDir
|
||||
|
||||
packageJsonInWaspProjectDir :: Path' (Rel WaspProjectDir) File'
|
||||
packageJsonInWaspProjectDir = [relfile|package.json|]
|
||||
|
||||
nodeModulesDirInWaspProjectDir :: Path' (Rel WaspProjectDir) (Dir NodeModulesDir)
|
||||
nodeModulesDirInWaspProjectDir = [reldir|node_modules|]
|
||||
|
||||
findFileInWaspProjectDir ::
|
||||
Path' Abs (Dir WaspProjectDir) ->
|
||||
Path' (Rel WaspProjectDir) File' ->
|
||||
|
@ -6,10 +6,12 @@ module Wasp.Util.IO
|
||||
deleteDirectoryIfExists,
|
||||
deleteFileIfExists,
|
||||
doesFileExist,
|
||||
doesDirectoryExist,
|
||||
readFile,
|
||||
readFileStrict,
|
||||
writeFile,
|
||||
removeFile,
|
||||
removeDirectory,
|
||||
tryReadFile,
|
||||
isDirectoryEmpty,
|
||||
writeFileFromText,
|
||||
@ -17,7 +19,7 @@ module Wasp.Util.IO
|
||||
)
|
||||
where
|
||||
|
||||
import Control.Monad (filterM, when)
|
||||
import Control.Monad (filterM)
|
||||
import Control.Monad.Extra (whenM)
|
||||
import qualified Data.ByteString.Lazy as B
|
||||
import Data.Text (Text)
|
||||
@ -85,10 +87,8 @@ listDirectory absDirPath = do
|
||||
-- with relative paths, define a new function (e.g., `readFileRel`).
|
||||
|
||||
deleteDirectoryIfExists :: Path' Abs (Dir d) -> IO ()
|
||||
deleteDirectoryIfExists dirPath = do
|
||||
let dirPathStr = SP.fromAbsDir dirPath
|
||||
exists <- SD.doesDirectoryExist dirPathStr
|
||||
when exists $ SD.removeDirectoryRecursive dirPathStr
|
||||
deleteDirectoryIfExists dirPath =
|
||||
whenM (doesDirectoryExist dirPath) (removeDirectory dirPath)
|
||||
|
||||
deleteFileIfExists :: Path' Abs (File f) -> IO ()
|
||||
deleteFileIfExists filePath = whenM (doesFileExist filePath) $ removeFile filePath
|
||||
@ -96,6 +96,9 @@ deleteFileIfExists filePath = whenM (doesFileExist filePath) $ removeFile filePa
|
||||
doesFileExist :: Path' Abs (File f) -> IO Bool
|
||||
doesFileExist = SD.doesFileExist . SP.fromAbsFile
|
||||
|
||||
doesDirectoryExist :: Path' Abs (Dir f) -> IO Bool
|
||||
doesDirectoryExist = SD.doesDirectoryExist . SP.fromAbsDir
|
||||
|
||||
readFile :: Path' Abs (File f) -> IO String
|
||||
readFile = P.readFile . SP.fromAbsFile
|
||||
|
||||
@ -114,6 +117,9 @@ writeFileFromText = T.IO.writeFile . SP.fromAbsFile
|
||||
removeFile :: Path' Abs (File f) -> IO ()
|
||||
removeFile = SD.removeFile . SP.fromAbsFile
|
||||
|
||||
removeDirectory :: Path' Abs (Dir d) -> IO ()
|
||||
removeDirectory = SD.removeDirectoryRecursive . SP.fromAbsDir
|
||||
|
||||
tryReadFile :: FilePath -> IO (Maybe Text)
|
||||
tryReadFile fp =
|
||||
(Just <$> Text.IO.readFile fp)
|
||||
|
@ -199,7 +199,7 @@ spec_Evaluator = do
|
||||
let typeDefs = TD.addDeclType @Special $ TD.empty
|
||||
let source =
|
||||
[ "special Test {",
|
||||
" imps: [import { field } from \"@server/main.js\", import main from \"@server/main.js\"],",
|
||||
" imps: [import { field } from \"@src/main.js\", import main from \"@src/main.js\"],",
|
||||
" json: {=json { \"key\": 1 } json=}",
|
||||
"}"
|
||||
]
|
||||
|
@ -9,7 +9,6 @@ import Data.List (intercalate)
|
||||
import Data.Maybe (fromJust)
|
||||
import qualified StrongPath as SP
|
||||
import Test.Tasty.Hspec
|
||||
import qualified Wasp.AI.GenerateNewProject.Common as Auth
|
||||
import Wasp.Analyzer
|
||||
import Wasp.Analyzer.Parser (Ctx)
|
||||
import qualified Wasp.Analyzer.TypeChecker as TC
|
||||
@ -18,7 +17,6 @@ import qualified Wasp.AppSpec.App as App
|
||||
import qualified Wasp.AppSpec.App.Auth as Auth
|
||||
import qualified Wasp.AppSpec.App.Client as Client
|
||||
import qualified Wasp.AppSpec.App.Db as Db
|
||||
import qualified Wasp.AppSpec.App.Dependency as Dependency
|
||||
import qualified Wasp.AppSpec.App.EmailSender as EmailSender
|
||||
import qualified Wasp.AppSpec.App.Server as Server
|
||||
import qualified Wasp.AppSpec.App.Wasp as Wasp
|
||||
@ -51,25 +49,22 @@ spec_Analyzer = do
|
||||
" userEntity: User,",
|
||||
" methods: {",
|
||||
" usernameAndPassword: {",
|
||||
" userSignupFields: import { getUserFields } from \"@server/auth/signup.js\",",
|
||||
" userSignupFields: import { getUserFields } from \"@src/auth/signup.js\",",
|
||||
" }",
|
||||
" },",
|
||||
" onAuthFailedRedirectTo: \"/\",",
|
||||
" },",
|
||||
" dependencies: [",
|
||||
" (\"redux\", \"^4.0.5\")",
|
||||
" ],",
|
||||
" server: {",
|
||||
" setupFn: import { setupServer } from \"@server/bar.js\"",
|
||||
" setupFn: import { setupServer } from \"@src/bar.js\"",
|
||||
" },",
|
||||
" client: {",
|
||||
" rootComponent: import { App } from \"@client/App.jsx\",",
|
||||
" setupFn: import { setupClient } from \"@client/baz.js\",",
|
||||
" rootComponent: import { App } from \"@src/App.jsx\",",
|
||||
" setupFn: import { setupClient } from \"@src/baz.js\",",
|
||||
" baseDir: \"/\"",
|
||||
" },",
|
||||
" db: {",
|
||||
" system: PostgreSQL,",
|
||||
" seeds: [ import { devSeedSimple } from \"@server/dbSeeds.js\" ],",
|
||||
" seeds: [ import { devSeedSimple } from \"@src/dbSeeds.js\" ],",
|
||||
" prisma: {",
|
||||
" clientPreviewFeatures: [\"extendedWhereUnique\"],",
|
||||
" dbExtensions: [{ name: \"pg_trgm\", version: \"1.0.0\" }]",
|
||||
@ -89,23 +84,23 @@ spec_Analyzer = do
|
||||
"psl=}",
|
||||
"",
|
||||
"page HomePage {",
|
||||
" component: import Home from \"@client/pages/Main\"",
|
||||
" component: import Home from \"@src/pages/Main\"",
|
||||
"}",
|
||||
"",
|
||||
"page ProfilePage {",
|
||||
" component: import { profilePage } from \"@client/pages/Profile\",",
|
||||
" component: import { profilePage } from \"@src/pages/Profile\",",
|
||||
" authRequired: true",
|
||||
"}",
|
||||
"",
|
||||
"route HomeRoute { path: \"/\", to: HomePage }",
|
||||
"",
|
||||
"query getUsers {",
|
||||
" fn: import { getAllUsers } from \"@server/foo.js\",",
|
||||
" fn: import { getAllUsers } from \"@src/foo.js\",",
|
||||
" entities: [User]",
|
||||
"}",
|
||||
"",
|
||||
"action updateUser {",
|
||||
" fn: import { updateUser } from \"@server/foo.js\",",
|
||||
" fn: import { updateUser } from \"@src/foo.js\",",
|
||||
" entities: [User],",
|
||||
" auth: true",
|
||||
"}",
|
||||
@ -113,7 +108,7 @@ spec_Analyzer = do
|
||||
"job BackgroundJob {",
|
||||
" executor: PgBoss,",
|
||||
" perform: {",
|
||||
" fn: import { backgroundJob } from \"@server/jobs/baz.js\",",
|
||||
" fn: import { backgroundJob } from \"@src/jobs/baz.js\",",
|
||||
" executorOptions: {",
|
||||
" pgBoss: {=json { \"retryLimit\": 1 } json=}",
|
||||
" }",
|
||||
@ -155,10 +150,6 @@ spec_Analyzer = do
|
||||
Auth.onAuthFailedRedirectTo = "/",
|
||||
Auth.onAuthSucceededRedirectTo = Nothing
|
||||
},
|
||||
App.dependencies =
|
||||
Just
|
||||
[ Dependency.Dependency {Dependency.name = "redux", Dependency.version = "^4.0.5"}
|
||||
],
|
||||
App.server =
|
||||
Just
|
||||
Server.Server
|
||||
@ -350,7 +341,7 @@ spec_Analyzer = do
|
||||
let source =
|
||||
unlines
|
||||
[ "route HomeRoute { path: \"/\", to: HomePage }",
|
||||
"page HomePage { component: import Home from \"@client/HomePage.js\" }"
|
||||
"page HomePage { component: import Home from \"@src/HomePage.js\" }"
|
||||
]
|
||||
isRight (analyze source) `shouldBe` True
|
||||
|
||||
@ -360,22 +351,22 @@ spec_Analyzer = do
|
||||
unlines
|
||||
[ "app MyApp {",
|
||||
" title: \"My app\",",
|
||||
" dependencies: [",
|
||||
" (\"bar\", 13),",
|
||||
" (\"foo\", 14)",
|
||||
" ]",
|
||||
" db: {",
|
||||
" seeds: [ (\"foo\", \"bar\") ],",
|
||||
" }",
|
||||
"}"
|
||||
]
|
||||
analyze source
|
||||
`errorMessageShouldBe` ( ctx (4, 5) (4, 15),
|
||||
`errorMessageShouldBe` ( ctx (4, 14) (4, 27),
|
||||
intercalate
|
||||
"\n"
|
||||
[ "Type error:",
|
||||
" Expected type: (string, string)",
|
||||
" Actual type: (string, number)",
|
||||
" Expected type: external import",
|
||||
" Actual type: (string, string)",
|
||||
"",
|
||||
" -> For dictionary field 'dependencies':",
|
||||
" -> In list"
|
||||
" -> For dictionary field 'db':",
|
||||
" -> For dictionary field 'seeds':",
|
||||
" -> In list"
|
||||
]
|
||||
)
|
||||
|
||||
@ -384,22 +375,19 @@ spec_Analyzer = do
|
||||
unlines
|
||||
[ "app MyApp {",
|
||||
" title: \"My app\",",
|
||||
" dependencies: [",
|
||||
" { name: \"bar\", version: 13 },",
|
||||
" { name: \"foo\", version: \"1.2.3\" }",
|
||||
" ]",
|
||||
" db: {",
|
||||
" seeds: [ 42, \"foo\" ],",
|
||||
" }",
|
||||
"}"
|
||||
]
|
||||
analyze source
|
||||
`errorMessageShouldBe` ( ctx (5, 29) (5, 35),
|
||||
`errorMessageShouldBe` ( ctx (4, 18) (4, 22),
|
||||
intercalate
|
||||
"\n"
|
||||
[ "Type error:",
|
||||
" Can't mix the following types:",
|
||||
" - number",
|
||||
" - string",
|
||||
"",
|
||||
" -> For dictionary field 'version'"
|
||||
" - string"
|
||||
]
|
||||
)
|
||||
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
module AppSpec.ValidTest where
|
||||
|
||||
import qualified Data.Map as M
|
||||
import Data.Maybe (fromJust)
|
||||
import Fixtures (systemSPRoot)
|
||||
import qualified StrongPath as SP
|
||||
@ -17,6 +18,7 @@ import qualified Wasp.AppSpec.Core.Decl as AS.Decl
|
||||
import qualified Wasp.AppSpec.Core.Ref as AS.Core.Ref
|
||||
import qualified Wasp.AppSpec.Entity as AS.Entity
|
||||
import qualified Wasp.AppSpec.ExtImport as AS.ExtImport
|
||||
import qualified Wasp.AppSpec.PackageJson as AS.PJS
|
||||
import qualified Wasp.AppSpec.Page as AS.Page
|
||||
import qualified Wasp.AppSpec.Route as AS.Route
|
||||
import qualified Wasp.AppSpec.Valid as ASV
|
||||
@ -344,7 +346,6 @@ spec_AppSpecValid = do
|
||||
AS.App.server = Nothing,
|
||||
AS.App.client = Nothing,
|
||||
AS.App.auth = Nothing,
|
||||
AS.App.dependencies = Nothing,
|
||||
AS.App.head = Nothing,
|
||||
AS.App.emailSender = Nothing,
|
||||
AS.App.webSocket = Nothing
|
||||
@ -359,6 +360,12 @@ spec_AppSpecValid = do
|
||||
AS.externalClientFiles = [],
|
||||
AS.externalServerFiles = [],
|
||||
AS.externalSharedFiles = [],
|
||||
AS.packageJson =
|
||||
AS.PJS.PackageJson
|
||||
{ AS.PJS.name = "testApp",
|
||||
AS.PJS.dependencies = M.empty,
|
||||
AS.PJS.devDependencies = M.empty
|
||||
},
|
||||
AS.isBuild = False,
|
||||
AS.migrationsDir = Nothing,
|
||||
AS.devEnvVarsClient = [],
|
||||
|
@ -45,7 +45,7 @@ spec_combineNpmDepsForPackage = do
|
||||
devDependenciesConflictErrors = []
|
||||
}
|
||||
|
||||
it "wasp deps completely overlap with user deps, no duplication" $ do
|
||||
it "wasp deps completely overlap with user deps: all wasp deps are dropped" $ do
|
||||
let npmDepsForWasp =
|
||||
NpmDepsForWasp
|
||||
{ waspDependencies = waspDeps,
|
||||
@ -53,26 +53,18 @@ spec_combineNpmDepsForPackage = do
|
||||
}
|
||||
let npmDepsForUser =
|
||||
NpmDepsForUser
|
||||
{ userDependencies =
|
||||
D.fromList
|
||||
[ ("a", "1"),
|
||||
("b", "2")
|
||||
],
|
||||
{ userDependencies = waspDeps,
|
||||
userDevDependencies = []
|
||||
}
|
||||
|
||||
combineNpmDepsForPackage npmDepsForWasp npmDepsForUser
|
||||
`shouldBe` Right
|
||||
NpmDepsForPackage
|
||||
{ dependencies =
|
||||
D.fromList
|
||||
[ ("a", "1"),
|
||||
("b", "2")
|
||||
],
|
||||
{ dependencies = [],
|
||||
devDependencies = []
|
||||
}
|
||||
|
||||
it "user dependencies supplement wasp dependencies" $ do
|
||||
it "user dependencies have no overlap with wasp deps: wasp deps remain the same" $ do
|
||||
let npmDepsForWasp =
|
||||
NpmDepsForWasp
|
||||
{ waspDependencies = waspDeps,
|
||||
@ -91,17 +83,11 @@ spec_combineNpmDepsForPackage = do
|
||||
combineNpmDepsForPackage npmDepsForWasp npmDepsForUser
|
||||
`shouldBe` Right
|
||||
NpmDepsForPackage
|
||||
{ dependencies =
|
||||
D.fromList
|
||||
[ ("a", "1"),
|
||||
("b", "2"),
|
||||
("c", "3"),
|
||||
("d", "4")
|
||||
],
|
||||
{ dependencies = waspDeps,
|
||||
devDependencies = []
|
||||
}
|
||||
|
||||
it "user dependencies partially overlap wasp dependencies, so only non-overlapping supplement" $ do
|
||||
it "user dependencies partially overlap wasp dependencies, so intersection gets removed from wasp deps" $ do
|
||||
let npmDepsForWasp =
|
||||
NpmDepsForWasp
|
||||
{ waspDependencies = waspDeps,
|
||||
@ -120,12 +106,7 @@ spec_combineNpmDepsForPackage = do
|
||||
combineNpmDepsForPackage npmDepsForWasp npmDepsForUser
|
||||
`shouldBe` Right
|
||||
NpmDepsForPackage
|
||||
{ dependencies =
|
||||
D.fromList
|
||||
[ ("a", "1"),
|
||||
("b", "2"),
|
||||
("d", "4")
|
||||
],
|
||||
{ dependencies = D.fromList [("b", "2")],
|
||||
devDependencies = []
|
||||
}
|
||||
|
||||
@ -183,7 +164,7 @@ spec_combineNpmDepsForPackage = do
|
||||
devDependenciesConflictErrors = []
|
||||
}
|
||||
|
||||
it "dev dependencies are also combined" $ do
|
||||
it "both dev deps and normal deps are same for user and wasp: all wasp deps are removed" $ do
|
||||
let npmDepsForWasp =
|
||||
NpmDepsForWasp
|
||||
{ waspDependencies = waspDeps,
|
||||
@ -192,35 +173,17 @@ spec_combineNpmDepsForPackage = do
|
||||
|
||||
let npmDepsForUser =
|
||||
NpmDepsForUser
|
||||
{ userDependencies =
|
||||
D.fromList
|
||||
[ ("a", "1"),
|
||||
("d", "4")
|
||||
],
|
||||
userDevDependencies =
|
||||
D.fromList
|
||||
[ ("alpha", "10"),
|
||||
("gamma", "30")
|
||||
]
|
||||
{ userDependencies = waspDeps,
|
||||
userDevDependencies = waspDevDeps
|
||||
}
|
||||
combineNpmDepsForPackage npmDepsForWasp npmDepsForUser
|
||||
`shouldBe` Right
|
||||
NpmDepsForPackage
|
||||
{ dependencies =
|
||||
D.fromList
|
||||
[ ("a", "1"),
|
||||
("b", "2"),
|
||||
("d", "4")
|
||||
],
|
||||
devDependencies =
|
||||
D.fromList
|
||||
[ ("alpha", "10"),
|
||||
("beta", "20"),
|
||||
("gamma", "30")
|
||||
]
|
||||
{ dependencies = [],
|
||||
devDependencies = []
|
||||
}
|
||||
|
||||
it "wasp dev dependency overlaps with user dependency, should remain devDependency" $ do
|
||||
it "wasp dev dependency overlaps with user non-dev dependency: should have no effect" $ do
|
||||
let npmDepsForWasp =
|
||||
NpmDepsForWasp
|
||||
{ waspDependencies = waspDeps,
|
||||
@ -239,16 +202,8 @@ spec_combineNpmDepsForPackage = do
|
||||
combineNpmDepsForPackage npmDepsForWasp npmDepsForUser
|
||||
`shouldBe` Right
|
||||
NpmDepsForPackage
|
||||
{ dependencies =
|
||||
D.fromList
|
||||
[ ("a", "1"),
|
||||
("b", "2")
|
||||
],
|
||||
devDependencies =
|
||||
D.fromList
|
||||
[ ("alpha", "10"),
|
||||
("beta", "20")
|
||||
]
|
||||
{ dependencies = waspDeps,
|
||||
devDependencies = waspDevDeps
|
||||
}
|
||||
|
||||
it "conflictErrorToMessage" $ do
|
||||
|
@ -1,5 +1,6 @@
|
||||
module Generator.WebAppGeneratorTest where
|
||||
|
||||
import qualified Data.Map as M
|
||||
import Fixtures
|
||||
import qualified StrongPath as SP
|
||||
import System.FilePath ((</>))
|
||||
@ -8,6 +9,7 @@ import qualified Wasp.AppSpec as AS
|
||||
import qualified Wasp.AppSpec.App as AS.App
|
||||
import qualified Wasp.AppSpec.App.Wasp as AS.Wasp
|
||||
import qualified Wasp.AppSpec.Core.Decl as AS.Decl
|
||||
import qualified Wasp.AppSpec.PackageJson as AS.PJS
|
||||
import Wasp.Generator.FileDraft
|
||||
import qualified Wasp.Generator.FileDraft.CopyAndModifyTextFileDraft as CMTextFD
|
||||
import qualified Wasp.Generator.FileDraft.CopyDirFileDraft as CopyDirFD
|
||||
@ -39,7 +41,6 @@ spec_WebAppGenerator = do
|
||||
AS.App.server = Nothing,
|
||||
AS.App.client = Nothing,
|
||||
AS.App.auth = Nothing,
|
||||
AS.App.dependencies = Nothing,
|
||||
AS.App.head = Nothing,
|
||||
AS.App.emailSender = Nothing,
|
||||
AS.App.webSocket = Nothing
|
||||
@ -49,6 +50,12 @@ spec_WebAppGenerator = do
|
||||
AS.externalClientFiles = [],
|
||||
AS.externalServerFiles = [],
|
||||
AS.externalSharedFiles = [],
|
||||
AS.packageJson =
|
||||
AS.PJS.PackageJson
|
||||
{ AS.PJS.name = "testApp",
|
||||
AS.PJS.dependencies = M.empty,
|
||||
AS.PJS.devDependencies = M.empty
|
||||
},
|
||||
AS.isBuild = False,
|
||||
AS.migrationsDir = Nothing,
|
||||
AS.devEnvVarsServer = [],
|
||||
|
@ -293,6 +293,8 @@ library
|
||||
Wasp.Generator.Monad
|
||||
Wasp.Generator.NpmDependencies
|
||||
Wasp.Generator.NpmInstall
|
||||
Wasp.Generator.NpmInstall.Common
|
||||
Wasp.Generator.NpmInstall.InstalledNpmDepsLog
|
||||
Wasp.Generator.SdkGenerator
|
||||
Wasp.Generator.ServerGenerator
|
||||
Wasp.Generator.ServerGenerator.JsImport
|
||||
@ -499,7 +501,6 @@ library cli-lib
|
||||
Wasp.Cli.Command.CreateNewProject.StarterTemplates.Templating
|
||||
Wasp.Cli.Command.Db
|
||||
Wasp.Cli.Command.Db.Migrate
|
||||
Wasp.Cli.Command.Db.Reset
|
||||
Wasp.Cli.Command.Db.Seed
|
||||
Wasp.Cli.Command.Db.Studio
|
||||
Wasp.Cli.Command.Deps
|
||||
|
Loading…
Reference in New Issue
Block a user