diff --git a/waspc/cli/exe/Main.hs b/waspc/cli/exe/Main.hs index 3444eab1d..f4a4e1d00 100644 --- a/waspc/cli/exe/Main.hs +++ b/waspc/cli/exe/Main.hs @@ -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-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-args]", "", diff --git a/waspc/src/Wasp/Generator/Job/Process.hs b/waspc/src/Wasp/Generator/Job/Process.hs index 41938a286..f80ab1ca4 100644 --- a/waspc/src/Wasp/Generator/Job/Process.hs +++ b/waspc/src/Wasp/Generator/Job/Process.hs @@ -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 diff --git a/waspc/src/Wasp/Generator/Node/Version.hs b/waspc/src/Wasp/Generator/Node/Version.hs index 5e1c62684..77c53631a 100644 --- a/waspc/src/Wasp/Generator/Node/Version.hs +++ b/waspc/src/Wasp/Generator/Node/Version.hs @@ -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 ] diff --git a/waspc/waspc.cabal b/waspc/waspc.cabal index 3e8d8b828..e686d2402 100644 --- a/waspc/waspc.cabal +++ b/waspc/waspc.cabal @@ -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