Improve waspc node version checking and error messages (#1210)

* increase minimum version of process dependency

* improve node version error messages
This commit is contained in:
Craig McIlwrath 2023-05-26 08:47:58 -04:00 committed by GitHub
parent 3f2bb72a0e
commit ce587a9fad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 83 additions and 29 deletions

View File

@ -8,6 +8,7 @@ import Data.Char (isSpace)
import Data.List (intercalate)
import Main.Utf8 (withUtf8)
import System.Environment (getArgs)
import System.Exit (exitFailure)
import Wasp.Cli.Command (runCommand)
import Wasp.Cli.Command.BashCompletion (bashCompletion, generateBashCompletionScript, printBashCompletionInstruction)
import Wasp.Cli.Command.Build (build)
@ -30,7 +31,10 @@ import qualified Wasp.Cli.Command.Telemetry as Telemetry
import Wasp.Cli.Command.Test (test)
import Wasp.Cli.Command.Uninstall (uninstall)
import Wasp.Cli.Command.WaspLS (runWaspLS)
import Wasp.Cli.Message (cliSendMessage)
import Wasp.Cli.Terminal (title)
import qualified Wasp.Generator.Node.Version as NodeVersion
import qualified Wasp.Message as Message
import Wasp.Util (indent)
import qualified Wasp.Util.Terminal as Term
import Wasp.Version (waspVersion)
@ -62,6 +66,16 @@ main = withUtf8 . (`E.catch` handleInternalErrors) $ do
telemetryThread <- Async.async $ runCommand $ Telemetry.considerSendingData commandCall
-- Before calling any command, check that the node requirement is met. Node is
-- not needed for every command, but checking for every command was decided
-- to be more robust than trying to only check for commands that require it.
-- See https://github.com/wasp-lang/wasp/issues/1134#issuecomment-1554065668
NodeVersion.getAndCheckNodeVersion >>= \case
Left errorMsg -> do
cliSendMessage $ Message.Failure "Node requirement not met" errorMsg
exitFailure
Right _ -> pure ()
case commandCall of
Command.Call.New newArgs -> runCommand $ createNewProject newArgs
Command.Call.Start -> runCommand start
@ -95,11 +109,11 @@ main = withUtf8 . (`E.catch` handleInternalErrors) $ do
handleInternalErrors :: E.ErrorCall -> IO ()
handleInternalErrors e = putStrLn $ "\nInternal Wasp error (bug in compiler):\n" ++ indent 2 (show e)
{- ORMOLU_DISABLE -}
printUsage :: IO ()
printUsage =
putStrLn $
unlines
{- ORMOLU_DISABLE -}
[ title "USAGE",
" wasp <command> [command-args]",
"",
@ -165,11 +179,11 @@ dbCli args = case args of
["studio"] -> runDbCommand Command.Db.Studio.studio
_ -> printDbUsage
{- ORMOLU_DISABLE -}
printDbUsage :: IO ()
printDbUsage =
putStrLn $
unlines
{- ORMOLU_DISABLE -}
[ title "USAGE",
" wasp db <command> [command-args]",
"",

View File

@ -23,7 +23,6 @@ import qualified System.Process as P
import UnliftIO.Exception (bracket)
import qualified Wasp.Generator.Job as J
import qualified Wasp.Generator.Node.Version as NodeVersion
import qualified Wasp.SemanticVersion as SV
-- TODO:
-- Switch from Data.Conduit.Process to Data.Conduit.Process.Typed.
@ -97,15 +96,12 @@ runNodeCommandAsJob = runNodeCommandAsJobWithExtraEnv []
runNodeCommandAsJobWithExtraEnv :: [(String, String)] -> Path' Abs (Dir a) -> String -> [String] -> J.JobType -> J.Job
runNodeCommandAsJobWithExtraEnv extraEnvVars fromDir command args jobType chan =
NodeVersion.getNodeVersion >>= \case
NodeVersion.getAndCheckNodeVersion >>= \case
Left errorMsg -> exitWithError (ExitFailure 1) (T.pack errorMsg)
Right nodeVersion ->
if SV.isVersionInRange nodeVersion NodeVersion.nodeVersionRange
then do
envVars <- getAllEnvVars
let nodeCommandProcess = (P.proc command args) {P.env = Just envVars, P.cwd = Just $ SP.fromAbsDir fromDir}
runProcessAsJob nodeCommandProcess jobType chan
else exitWithError (ExitFailure 1) (T.pack $ NodeVersion.makeNodeVersionMismatchMessage nodeVersion)
Right _ -> do
envVars <- getAllEnvVars
let nodeCommandProcess = (P.proc command args) {P.env = Just envVars, P.cwd = Just $ SP.fromAbsDir fromDir}
runProcessAsJob nodeCommandProcess jobType chan
where
-- Haskell will use the first value for variable name it finds. Since env
-- vars in 'extraEnvVars' should override the the inherited env vars, we

View File

@ -1,5 +1,6 @@
module Wasp.Generator.Node.Version
( getNodeVersion,
( getAndCheckNodeVersion,
getNodeVersion,
nodeVersionRange,
latestMajorNodeVersion,
waspNodeRequirementMessage,
@ -13,25 +14,56 @@ import qualified System.Process as P
import Text.Read (readMaybe)
import qualified Text.Regex.TDFA as R
import qualified Wasp.SemanticVersion as SV
import Wasp.Util (indent)
-- | Gets the installed node version, if any is installed, and checks that it
-- meets Wasp's version requirement.
--
-- Returns a string representing the error
-- condition if node's version could not be found or if the version does not
-- meet the requirements.
getAndCheckNodeVersion :: IO (Either String SV.Version)
getAndCheckNodeVersion =
getNodeVersion >>= \case
Left errorMsg -> return $ Left errorMsg
Right nodeVersion ->
if SV.isVersionInRange nodeVersion nodeVersionRange
then return $ Right nodeVersion
else return $ Left $ makeNodeVersionMismatchMessage nodeVersion
-- | Gets the installed node version, if any is installed, and returns it.
--
-- Returns a string representing the error condition if node's version could
-- not be found.
getNodeVersion :: IO (Either String SV.Version)
getNodeVersion = do
(exitCode, stdout, stderr) <-
P.readProcessWithExitCode "node" ["--version"] ""
-- Node result is one of:
-- 1. @Left processError@, when an error occurs trying to run the process
-- 2. @Right (ExitCode, stdout, stderr)@, when the node process runs and terminates
nodeResult <-
(Right <$> P.readProcessWithExitCode "node" ["--version"] "")
`catchIOError` ( \e ->
if isDoesNotExistError e
then return (ExitFailure 1, "", "Command 'node' not found.")
else ioError e
then return $ Left nodeNotFoundMessage
else return $ Left $ makeNodeUnknownErrorMessage e
)
return $ case exitCode of
ExitFailure _ ->
return $ case nodeResult of
Left procErr ->
Left
( "Running 'node --version' failed: "
++ stderr
++ " "
++ waspNodeRequirementMessage
( unlines
[ procErr,
waspNodeRequirementMessage
]
)
ExitSuccess -> case parseNodeVersion stdout of
Right (ExitFailure code, _, stderr) ->
Left
( unlines
[ "Running `node --version` failed (exit code " ++ show code ++ "):",
indent 2 stderr,
waspNodeRequirementMessage
]
)
Right (ExitSuccess, stdout, _) -> case parseNodeVersion stdout of
Nothing ->
Left
( "Wasp failed to parse node version."
@ -49,11 +81,21 @@ parseNodeVersion nodeVersionStr =
return $ SV.Version mjr mnr ptc
_ -> Nothing
nodeNotFoundMessage :: String
nodeNotFoundMessage = "`node` command not found!"
makeNodeUnknownErrorMessage :: IOError -> String
makeNodeUnknownErrorMessage err =
unlines
[ "An unknown error occured while trying to run `node --version`:",
indent 2 $ show err
]
waspNodeRequirementMessage :: String
waspNodeRequirementMessage =
unwords
[ "Wasp requires node " ++ show nodeVersionRange ++ ".",
"Check Wasp docs for more details: https://wasp-lang.dev/docs/quick-start#requirements."
[ "Wasp requires Node " ++ show nodeVersionRange ++ " to be installed and in PATH.",
"Check Wasp documentation for more details: https://wasp-lang.dev/docs/quick-start#requirements."
]
nodeVersionRange :: SV.Range
@ -72,8 +114,10 @@ latestMajorNodeVersion = latestNodeLTSVersion
makeNodeVersionMismatchMessage :: SV.Version -> String
makeNodeVersionMismatchMessage nodeVersion =
unwords
[ "Your node version does not match Wasp's requirements.",
"You are running node " ++ show nodeVersion ++ ".",
unlines
[ unwords
[ "Your Node version does not meet Wasp's requirements!",
"You are running Node " ++ show nodeVersion ++ "."
],
waspNodeRequirementMessage
]

View File

@ -107,7 +107,7 @@ library
, exceptions ^>= 0.10.4
, split ^>= 0.2.3
, conduit-extra ^>= 1.3.5
, process ^>= 1.6.13
, process ^>= 1.6.17
, cryptohash-sha256 ^>= 0.11.102
, mustache ^>= 2.3.2
, parsec ^>= 3.1.14