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 Data.List (intercalate)
import Main.Utf8 (withUtf8) import Main.Utf8 (withUtf8)
import System.Environment (getArgs) import System.Environment (getArgs)
import System.Exit (exitFailure)
import Wasp.Cli.Command (runCommand) import Wasp.Cli.Command (runCommand)
import Wasp.Cli.Command.BashCompletion (bashCompletion, generateBashCompletionScript, printBashCompletionInstruction) import Wasp.Cli.Command.BashCompletion (bashCompletion, generateBashCompletionScript, printBashCompletionInstruction)
import Wasp.Cli.Command.Build (build) 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.Test (test)
import Wasp.Cli.Command.Uninstall (uninstall) import Wasp.Cli.Command.Uninstall (uninstall)
import Wasp.Cli.Command.WaspLS (runWaspLS) import Wasp.Cli.Command.WaspLS (runWaspLS)
import Wasp.Cli.Message (cliSendMessage)
import Wasp.Cli.Terminal (title) import Wasp.Cli.Terminal (title)
import qualified Wasp.Generator.Node.Version as NodeVersion
import qualified Wasp.Message as Message
import Wasp.Util (indent) import Wasp.Util (indent)
import qualified Wasp.Util.Terminal as Term import qualified Wasp.Util.Terminal as Term
import Wasp.Version (waspVersion) import Wasp.Version (waspVersion)
@ -62,6 +66,16 @@ main = withUtf8 . (`E.catch` handleInternalErrors) $ do
telemetryThread <- Async.async $ runCommand $ Telemetry.considerSendingData commandCall 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 case commandCall of
Command.Call.New newArgs -> runCommand $ createNewProject newArgs Command.Call.New newArgs -> runCommand $ createNewProject newArgs
Command.Call.Start -> runCommand start Command.Call.Start -> runCommand start
@ -95,11 +109,11 @@ main = withUtf8 . (`E.catch` handleInternalErrors) $ do
handleInternalErrors :: E.ErrorCall -> IO () handleInternalErrors :: E.ErrorCall -> IO ()
handleInternalErrors e = putStrLn $ "\nInternal Wasp error (bug in compiler):\n" ++ indent 2 (show e) handleInternalErrors e = putStrLn $ "\nInternal Wasp error (bug in compiler):\n" ++ indent 2 (show e)
{- ORMOLU_DISABLE -}
printUsage :: IO () printUsage :: IO ()
printUsage = printUsage =
putStrLn $ putStrLn $
unlines unlines
{- ORMOLU_DISABLE -}
[ title "USAGE", [ title "USAGE",
" wasp <command> [command-args]", " wasp <command> [command-args]",
"", "",
@ -165,11 +179,11 @@ dbCli args = case args of
["studio"] -> runDbCommand Command.Db.Studio.studio ["studio"] -> runDbCommand Command.Db.Studio.studio
_ -> printDbUsage _ -> printDbUsage
{- ORMOLU_DISABLE -}
printDbUsage :: IO () printDbUsage :: IO ()
printDbUsage = printDbUsage =
putStrLn $ putStrLn $
unlines unlines
{- ORMOLU_DISABLE -}
[ title "USAGE", [ title "USAGE",
" wasp db <command> [command-args]", " wasp db <command> [command-args]",
"", "",

View File

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

View File

@ -1,5 +1,6 @@
module Wasp.Generator.Node.Version module Wasp.Generator.Node.Version
( getNodeVersion, ( getAndCheckNodeVersion,
getNodeVersion,
nodeVersionRange, nodeVersionRange,
latestMajorNodeVersion, latestMajorNodeVersion,
waspNodeRequirementMessage, waspNodeRequirementMessage,
@ -13,25 +14,56 @@ import qualified System.Process as P
import Text.Read (readMaybe) import Text.Read (readMaybe)
import qualified Text.Regex.TDFA as R import qualified Text.Regex.TDFA as R
import qualified Wasp.SemanticVersion as SV 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 :: IO (Either String SV.Version)
getNodeVersion = do getNodeVersion = do
(exitCode, stdout, stderr) <- -- Node result is one of:
P.readProcessWithExitCode "node" ["--version"] "" -- 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 -> `catchIOError` ( \e ->
if isDoesNotExistError e if isDoesNotExistError e
then return (ExitFailure 1, "", "Command 'node' not found.") then return $ Left nodeNotFoundMessage
else ioError e else return $ Left $ makeNodeUnknownErrorMessage e
) )
return $ case exitCode of return $ case nodeResult of
ExitFailure _ -> Left procErr ->
Left Left
( "Running 'node --version' failed: " ( unlines
++ stderr [ procErr,
++ " " waspNodeRequirementMessage
++ 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 -> Nothing ->
Left Left
( "Wasp failed to parse node version." ( "Wasp failed to parse node version."
@ -49,11 +81,21 @@ parseNodeVersion nodeVersionStr =
return $ SV.Version mjr mnr ptc return $ SV.Version mjr mnr ptc
_ -> Nothing _ -> 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 :: String
waspNodeRequirementMessage = waspNodeRequirementMessage =
unwords unwords
[ "Wasp requires node " ++ show nodeVersionRange ++ ".", [ "Wasp requires Node " ++ show nodeVersionRange ++ " to be installed and in PATH.",
"Check Wasp docs for more details: https://wasp-lang.dev/docs/quick-start#requirements." "Check Wasp documentation for more details: https://wasp-lang.dev/docs/quick-start#requirements."
] ]
nodeVersionRange :: SV.Range nodeVersionRange :: SV.Range
@ -72,8 +114,10 @@ latestMajorNodeVersion = latestNodeLTSVersion
makeNodeVersionMismatchMessage :: SV.Version -> String makeNodeVersionMismatchMessage :: SV.Version -> String
makeNodeVersionMismatchMessage nodeVersion = makeNodeVersionMismatchMessage nodeVersion =
unwords unlines
[ "Your node version does not match Wasp's requirements.", [ unwords
"You are running node " ++ show nodeVersion ++ ".", [ "Your Node version does not meet Wasp's requirements!",
"You are running Node " ++ show nodeVersion ++ "."
],
waspNodeRequirementMessage waspNodeRequirementMessage
] ]

View File

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