feature: Makes CLI output easier to see success and failure (#418)

Closes #212
This commit is contained in:
Shayne Czyzewski 2022-01-05 09:15:38 -05:00 committed by GitHub
parent 86441c8cee
commit cdee0ff0d3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 99 additions and 56 deletions

View File

@ -9,6 +9,7 @@ where
import Control.Monad.Except (ExceptT, MonadError, runExceptT)
import Control.Monad.IO.Class (MonadIO)
import qualified Wasp.Util.Terminal as Term
newtype Command a = Command {_runCommand :: ExceptT CommandError IO a}
deriving (Functor, Applicative, Monad, MonadIO, MonadError CommandError)
@ -17,7 +18,7 @@ runCommand :: Command a -> IO ()
runCommand cmd = do
errorOrResult <- runExceptT $ _runCommand cmd
case errorOrResult of
Left cmdError -> putStrLn $ "Error: " ++ _errorMsg cmdError
Left cmdError -> putStrLn $ Term.applyStyles [Term.Red] (_errorMsg cmdError)
Right _ -> return ()
-- TODO: What if we want to recognize errors in order to handle them?

View File

@ -16,9 +16,11 @@ import Wasp.Cli.Command (Command, CommandError (..))
import Wasp.Cli.Command.Common
( alphaWarningMessage,
findWaspProjectRootDirFromCwd,
waspSaysC,
)
import Wasp.Cli.Command.Compile (compileIOWithOptions)
import qualified Wasp.Cli.Common as Common
import Wasp.Cli.Terminal (asWaspFailureMessage, asWaspStartMessage, asWaspSuccessMessage)
import Wasp.CompileOptions (CompileOptions (..))
import qualified Wasp.Lib
@ -31,18 +33,17 @@ build = do
buildDirFilePath = SP.fromAbsDir buildDir
doesBuildDirExist <- liftIO $ doesDirectoryExist buildDirFilePath
when doesBuildDirExist $
liftIO $ do
putStrLn "Clearing the content of the .wasp/build directory..."
removeDirectoryRecursive buildDirFilePath
putStrLn "Successfully cleared the contents of the .wasp/build directory.\n"
when doesBuildDirExist $ do
waspSaysC $ asWaspStartMessage "Clearing the content of the .wasp/build directory..."
liftIO $ removeDirectoryRecursive buildDirFilePath
waspSaysC $ asWaspSuccessMessage "Successfully cleared the contents of the .wasp/build directory."
liftIO $ putStrLn "Building wasp project..."
waspSaysC $ asWaspStartMessage "Building wasp project..."
buildResult <- liftIO $ buildIO waspProjectDir buildDir
case buildResult of
Left compileError -> throwError $ CommandError $ "Build failed: " ++ compileError
Right () -> liftIO $ putStrLn "Code has been successfully built! Check it out in .wasp/build directory.\n"
liftIO $ putStrLn alphaWarningMessage
Left compileError -> throwError $ CommandError $ asWaspFailureMessage "Build failed:" ++ compileError
Right () -> waspSaysC $ asWaspSuccessMessage "Code has been successfully built! Check it out in .wasp/build directory."
waspSaysC alphaWarningMessage
buildIO ::
Path' Abs (Dir Common.WaspProjectDir) ->

View File

@ -9,19 +9,19 @@ import System.Directory
( doesDirectoryExist,
removeDirectoryRecursive,
)
import System.IO (hFlush, stdout)
import Wasp.Cli.Command (Command)
import Wasp.Cli.Command.Common (findWaspProjectRootDirFromCwd)
import Wasp.Cli.Command.Common (findWaspProjectRootDirFromCwd, waspSaysC)
import qualified Wasp.Cli.Common as Common
import Wasp.Cli.Terminal (asWaspStartMessage, asWaspSuccessMessage)
clean :: Command ()
clean = do
waspProjectDir <- findWaspProjectRootDirFromCwd
let dotWaspDirFp = SP.toFilePath $ waspProjectDir SP.</> Common.dotWaspDirInWaspProjectDir
liftIO $ putStrLn "Deleting .wasp/ directory..." >> hFlush stdout
waspSaysC $ asWaspStartMessage "Deleting .wasp/ directory..."
doesDotWaspDirExist <- liftIO $ doesDirectoryExist dotWaspDirFp
if doesDotWaspDirExist
then liftIO $ do
removeDirectoryRecursive dotWaspDirFp
putStrLn "Deleted .wasp/ directory."
else liftIO $ putStrLn "Nothing to delete: .wasp directory does not exist."
then do
liftIO $ removeDirectoryRecursive dotWaspDirFp
waspSaysC $ asWaspSuccessMessage "Deleted .wasp/ directory."
else waspSaysC "Nothing to delete: .wasp directory does not exist."

View File

@ -23,6 +23,7 @@ import Wasp.Cli.Common
( dotWaspRootFileInWaspProjectDir,
waspSays,
)
import Wasp.Cli.Terminal (asWaspFailureMessage)
import Wasp.Common (WaspProjectDir)
findWaspProjectRoot :: Path' Abs (Dir ()) -> Command (Path' Abs (Dir WaspProjectDir))
@ -40,10 +41,11 @@ findWaspProjectRoot currentDir = do
findWaspProjectRoot parentDir
where
notFoundError =
CommandError
( "Couldn't find wasp project root - make sure"
++ " you are running this command from Wasp project."
)
CommandError $
asWaspFailureMessage "Wasp command failed:"
++ ( "Couldn't find wasp project root - make sure"
++ " you are running this command from a Wasp project."
)
findWaspProjectRootDirFromCwd :: Command (Path' Abs (Dir WaspProjectDir))
findWaspProjectRootDirFromCwd = do

View File

@ -18,6 +18,7 @@ import Wasp.Cli.Command.Db.Migrate
copyDbMigrationsDir,
)
import qualified Wasp.Cli.Common as Common
import Wasp.Cli.Terminal (asWaspFailureMessage, asWaspStartMessage, asWaspSuccessMessage)
import Wasp.Common (WaspProjectDir)
import Wasp.CompileOptions (CompileOptions (..))
import qualified Wasp.Lib
@ -29,11 +30,11 @@ compile = do
waspProjectDir </> Common.dotWaspDirInWaspProjectDir
</> Common.generatedCodeDirInDotWaspDir
waspSaysC "Compiling wasp code..."
waspSaysC $ asWaspStartMessage "Compiling wasp code..."
compilationResult <- liftIO $ compileIO waspProjectDir outDir
case compilationResult of
Left compileError -> throwError $ CommandError $ "Compilation failed: " ++ compileError
Right () -> waspSaysC "Code has been successfully compiled, project has been generated.\n"
Left compileError -> throwError $ CommandError $ asWaspFailureMessage "Compilation failed:" ++ compileError
Right () -> waspSaysC $ asWaspSuccessMessage "Code has been successfully compiled, project has been generated."
-- | Compiles Wasp source code in waspProjectDir directory and generates a project
-- in given outDir directory.

View File

@ -19,6 +19,7 @@ import Wasp.AppSpec.ExternalCode (SourceExternalCodeDir)
import Wasp.Cli.Command (Command, CommandError (..))
import qualified Wasp.Cli.Command.Common as Command.Common
import qualified Wasp.Cli.Common as Common
import Wasp.Cli.Terminal (asWaspFailureMessage)
import qualified Wasp.Data
import Wasp.Lexer (reservedNames)
import qualified Wasp.Util.Terminal as Term
@ -27,9 +28,9 @@ newtype ProjectName = ProjectName {_projectName :: String}
createNewProject :: String -> Command ()
createNewProject (all isLetter -> False) =
throwError $ CommandError "Please use only letters for a new project's name."
throwError $ CommandError $ asWaspFailureMessage "Project creation failed:" ++ "Please use only letters for a new project's name."
createNewProject ((`elem` reservedNames) -> True) =
throwError . CommandError $ "Please pick a project name not one of these reserved words:\n\t" ++ intercalate "\n\t" reservedNames
throwError . CommandError $ asWaspFailureMessage "Project creation failed:" ++ "Please pick a project name not one of these reserved words:\n\t" ++ intercalate "\n\t" reservedNames
createNewProject name = createNewProject' (ProjectName name)
createNewProject' :: ProjectName -> Command ()
@ -38,10 +39,11 @@ createNewProject' (ProjectName projectName) = do
waspProjectDir <- case SP.parseAbsDir $ absCwd FP.</> projectName of
Left err ->
throwError $
CommandError
( "Failed to parse absolute path to wasp project dir: "
++ show err
)
CommandError $
asWaspFailureMessage "Project creation failed:"
++ ( "Failed to parse absolute path to wasp project dir: "
++ show err
)
Right sp -> return sp
liftIO $ do
createDirectorySP waspProjectDir

View File

@ -14,6 +14,7 @@ import Wasp.Cli.Command (Command, CommandError (..), runCommand)
import Wasp.Cli.Command.Common (findWaspProjectRootDirFromCwd, waspSaysC)
import Wasp.Cli.Command.Compile (compile)
import qualified Wasp.Cli.Common as Common
import Wasp.Cli.Terminal (asWaspFailureMessage, asWaspStartMessage, asWaspSuccessMessage)
import Wasp.Generator.DbGenerator.Jobs (runStudio)
import Wasp.Generator.Job.IO (readJobMessagesAndPrintThemPrefixed)
import Wasp.Generator.ServerGenerator.Setup (setupServer)
@ -35,18 +36,18 @@ makeDbCommand cmd = do
-- NOTE(matija): First we need make sure the code is generated.
compile
waspSaysC "\nSetting up database..."
waspSaysC $ asWaspStartMessage "Setting up database..."
chan <- liftIO newChan
-- NOTE(matija): What we do here is make sure that Prisma CLI is installed because db commands
-- (e.g. migrate) depend on it. We run setupServer which does even more than that, so we could make
-- this function more lightweight if needed.
(_, dbSetupResult) <- liftIO (concurrently (readJobMessagesAndPrintThemPrefixed chan) (setupServer genProjectDir chan))
case dbSetupResult of
ExitSuccess -> waspSaysC "\nDatabase successfully set up!" >> cmd
exitCode -> throwError $ CommandError $ dbSetupFailedMessage exitCode
ExitSuccess -> waspSaysC (asWaspSuccessMessage "Database successfully set up!") >> cmd
exitCode -> throwError $ CommandError $ asWaspFailureMessage $ dbSetupFailedMessage exitCode
where
dbSetupFailedMessage exitCode =
"\nDatabase setup failed"
"Database setup failed"
++ case exitCode of
ExitFailure code -> ": " ++ show code
_ -> ""
@ -59,7 +60,7 @@ studio = do
waspProjectDir </> Common.dotWaspDirInWaspProjectDir
</> Common.generatedCodeDirInDotWaspDir
waspSaysC "Running studio..."
waspSaysC $ asWaspStartMessage "Running studio..."
chan <- liftIO newChan
_ <- liftIO $ concurrently (readJobMessagesAndPrintThemPrefixed chan) (runStudio genProjectDir chan)

View File

@ -20,6 +20,7 @@ import Wasp.Cli.Command.Common
waspSaysC,
)
import qualified Wasp.Cli.Common as Cli.Common
import Wasp.Cli.Terminal (asWaspFailureMessage, asWaspStartMessage, asWaspSuccessMessage)
import Wasp.Common (WaspProjectDir, dbMigrationsDirInWaspProjectDir)
import Wasp.Generator.Common (ProjectRootDir)
import Wasp.Generator.DbGenerator (dbMigrationsDirInDbRootDir, dbRootDirInProjectRootDir)
@ -42,27 +43,27 @@ migrateDev = do
-- all the latest migrations are in the generated project (e.g. Wasp dev checked out something
-- new) - otherwise "dev" would create a new migration for that and we would end up with two
-- migrations doing the same thing (which might result in conflict, e.g. during db creation).
waspSaysC "Copying migrations folder from Wasp to Prisma project..."
waspSaysC $ asWaspStartMessage "Copying migrations folder from Wasp to Prisma project..."
copyDbMigrationDir waspProjectDir genProjectRootDir CopyMigDirDown
waspSaysC "Performing migration..."
waspSaysC $ asWaspStartMessage "Performing migration..."
migrateResult <- liftIO $ DbOps.migrateDev genProjectRootDir
case migrateResult of
Left migrateError ->
throwError $ CommandError $ "Migrate dev failed: " <> migrateError
Right () -> waspSaysC "Migration done."
throwError $ CommandError $ asWaspFailureMessage "Migrate dev failed:" ++ migrateError
Right () -> waspSaysC $ asWaspSuccessMessage "Migration done."
waspSaysC "Copying migrations folder from Prisma to Wasp project..."
waspSaysC $ asWaspStartMessage "Copying migrations folder from Prisma to Wasp project..."
copyDbMigrationDir waspProjectDir genProjectRootDir CopyMigDirUp
waspSaysC "All done!"
waspSaysC $ asWaspSuccessMessage "All done!"
where
copyDbMigrationDir waspProjectDir genProjectRootDir copyDirection = do
copyDbMigDirResult <-
liftIO $ copyDbMigrationsDir copyDirection waspProjectDir genProjectRootDir
case copyDbMigDirResult of
Nothing -> waspSaysC "Done copying migrations folder."
Just err -> throwError $ CommandError $ "Copying migration folder failed: " ++ err
Nothing -> waspSaysC $ asWaspSuccessMessage "Done copying migrations folder."
Just err -> throwError $ CommandError $ asWaspFailureMessage "Copying migration folder failed:" ++ err
data MigrationDirCopyDirection = CopyMigDirUp | CopyMigDirDown deriving (Eq)

View File

@ -15,6 +15,7 @@ import Wasp.Cli.Command.Common
import Wasp.Cli.Command.Compile (compileIO)
import Wasp.Cli.Command.Watch (watch)
import qualified Wasp.Cli.Common as Common
import Wasp.Cli.Terminal (asWaspFailureMessage, asWaspStartMessage, asWaspSuccessMessage)
import qualified Wasp.Lib
-- | Does initial compile of wasp code and then runs the generated project.
@ -24,11 +25,11 @@ start = do
waspRoot <- findWaspProjectRootDirFromCwd
let outDir = waspRoot </> Common.dotWaspDirInWaspProjectDir </> Common.generatedCodeDirInDotWaspDir
waspSaysC "Compiling wasp code..."
waspSaysC $ asWaspStartMessage "Compiling wasp code..."
compilationResult <- liftIO $ compileIO waspRoot outDir
case compilationResult of
Left compileError -> throwError $ CommandError $ "Compilation failed: " ++ compileError
Right () -> waspSaysC "Code has been successfully compiled, project has been generated.\n"
Left compileError -> throwError $ CommandError $ asWaspFailureMessage "Compilation failed:" ++ compileError
Right () -> waspSaysC $ asWaspSuccessMessage "Code has been successfully compiled, project has been generated."
-- TODO: Do smart install -> if we need to install stuff, install it, otherwise don't.
-- This should be responsibility of Generator, it should tell us how to install stuff.
@ -37,17 +38,17 @@ start = do
-- Then, next time, we give it data we have about last installation, and it uses that
-- to decide if installation needs to happen or not. If it happens, it returnes new data again.
-- Right now we have setup/installation being called, but it has not support for being "smart" yet.
waspSaysC "Setting up generated project..."
waspSaysC $ asWaspStartMessage "Setting up generated project..."
setupResult <- liftIO $ Wasp.Lib.setup outDir
case setupResult of
Left setupError -> throwError $ CommandError $ "\nSetup failed: " ++ setupError
Right () -> waspSaysC "\nSetup successful.\n"
Left setupError -> throwError $ CommandError $ asWaspFailureMessage "Setup failed:" ++ setupError
Right () -> waspSaysC $ asWaspSuccessMessage "Setup successful."
waspSaysC "\nListening for file changes..."
waspSaysC "Starting up generated project..."
waspSaysC $ asWaspStartMessage "Listening for file changes..."
waspSaysC $ asWaspStartMessage "Starting up generated project..."
watchOrStartResult <- liftIO $ race (watch waspRoot outDir) (Wasp.Lib.start outDir)
case watchOrStartResult of
Left () -> error "This should never happen, listening for file changes should never end but it did."
Right startResult -> case startResult of
Left startError -> throwError $ CommandError $ "Start failed: " ++ startError
Left startError -> throwError $ CommandError $ asWaspFailureMessage "Start failed:" ++ startError
Right () -> error "This should never happen, start should never end but it did."

View File

@ -17,6 +17,7 @@ import Wasp.Cli.Command.Common (waspSaysC)
import Wasp.Cli.Command.Telemetry.Common (ensureTelemetryCacheDirExists)
import qualified Wasp.Cli.Command.Telemetry.Project as TlmProject
import qualified Wasp.Cli.Command.Telemetry.User as TlmUser
import Wasp.Cli.Terminal (asWaspFailureMessage)
isTelemetryDisabled :: IO Bool
isTelemetryDisabled = isJust <$> ENV.lookupEnv "WASP_TELEMETRY_DISABLE"
@ -53,7 +54,7 @@ telemetry = do
considerSendingData :: Command.Call.Call -> Command ()
considerSendingData cmdCall = (`catchError` const (return ())) $ do
telemetryDisabled <- liftIO isTelemetryDisabled
when telemetryDisabled $ throwError $ CommandError "Telemetry disabled by user."
when telemetryDisabled $ throwError $ CommandError $ asWaspFailureMessage "Telemetry disabled by user."
telemetryCacheDirPath <- liftIO ensureTelemetryCacheDirExists

View File

@ -16,6 +16,7 @@ import qualified System.FilePath as FP
import Wasp.Cli.Command.Compile (compileIO)
import Wasp.Cli.Common (waspSays)
import qualified Wasp.Cli.Common as Common
import Wasp.Cli.Terminal (asWaspFailureMessage, asWaspStartMessage, asWaspSuccessMessage)
import qualified Wasp.Lib
-- TODO: Another possible problem: on re-generation, wasp re-generates a lot of files, even those that should not
@ -78,11 +79,11 @@ watch waspProjectDir outDir = FSN.withManager $ \mgr -> do
recompile :: IO ()
recompile = do
waspSays "Recompiling on file change..."
waspSays $ asWaspStartMessage "Recompiling on file change..."
compilationResult <- compileIO waspProjectDir outDir
case compilationResult of
Left err -> waspSays $ "Recompilation on file change failed: " ++ err
Right () -> waspSays "Recompilation on file change succeeded."
Left err -> waspSays $ asWaspFailureMessage "Recompilation on file change failed:" ++ err
Right () -> waspSays $ asWaspSuccessMessage "Recompilation on file change succeeded."
return ()
-- TODO: This is a hardcoded approach to ignoring most of the common tmp files that editors

View File

@ -1,5 +1,9 @@
module Wasp.Cli.Terminal
( title,
asWaspMessage,
asWaspStartMessage,
asWaspSuccessMessage,
asWaspFailureMessage,
)
where
@ -7,3 +11,30 @@ import qualified Wasp.Util.Terminal as Term
title :: String -> String
title = Term.applyStyles [Term.Bold]
asWaspMessage :: String -> String
asWaspMessage = waspMessageWithEmoji ""
asWaspStartMessage :: String -> String
asWaspStartMessage = waspMessageWithEmoji "🐝"
asWaspSuccessMessage :: String -> String
asWaspSuccessMessage = waspMessageWithEmoji ""
asWaspFailureMessage :: String -> String
-- Add a bit more padding on errors for more pronounced
-- visibility and better display of any following error context.
asWaspFailureMessage str = concat ["\n", waspMessageWithEmoji "" errorStr, "\n"]
where
errorStr = "[Error] " ++ str
waspMessageWithEmoji :: String -> String -> String
waspMessageWithEmoji emoji message = concat ["\n", prefix, " ", message, " ", suffix, "\n"]
where
prefix = emoji ++ " ---"
prefixAndMessageLength = length prefix + length message
idealLength = 80
-- Pad suffix until returned message is the ideal length. However, if we have to go
-- beyond ideal length due to input length, just use 3 at the end to match the prefix.
rightPadLength = max 3 (idealLength - prefixAndMessageLength)
suffix = concat (replicate rightPadLength "-")