-
-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Create wasp build start
command
#2796
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 90 commits
3e05664
9ea053f
e06a292
5e7c8c3
128f70e
ddc9a32
5b57571
9e72db1
5b44f5c
87734fc
9a2831b
b1a4e97
30c4a7c
f262562
6d86658
01ac717
0b91a74
a35f5a6
af0772c
df97486
c326246
fd4b9d5
80e43ed
39bd54e
9c35873
7794642
7b02f44
a427e85
9a3d570
305987a
b78c7b6
3a0f7e6
54a2a28
827e6ab
c69ddfc
fddbca7
3c3327d
c9d389c
b2f1801
1071176
806c4b1
670a34d
512d0c9
64dea5f
edd6700
20f2d13
252a269
98be630
b0f06be
d63e3f9
05fc1b4
675f760
32bb4e8
02de3fb
b9700c2
e57b2e7
369d829
92bdf6e
1651b84
0462b9f
3328dd8
810218f
6172984
2c3cbf4
396ec95
6c8b47a
2220f20
8d1e5a5
000c033
316a97a
3f2db84
d57b233
56ce5a7
399f664
0ea6f52
3c1ce25
acb5b9e
f4a892e
315a5b6
f10fe23
54e37d6
b75cb86
3ebeda6
6ec1329
5335604
9a31194
c38eb7f
00e6a5a
67ff2e4
c09269c
28aa46b
c8b5642
c8ba324
8a39527
d8e3b2d
975c62c
4580549
01de580
421c00c
7c15c77
7b48205
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Martinsos marked this conversation as resolved.
Show resolved
Hide resolved
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
module Wasp.Cli.Command.BuildStart | ||
( buildStart, | ||
) | ||
where | ||
|
||
import Control.Concurrent.Async (concurrently) | ||
import Control.Concurrent.Chan (newChan) | ||
import Control.Monad.Except (MonadError (throwError), runExceptT) | ||
import Control.Monad.IO.Class (liftIO) | ||
import Data.Function ((&)) | ||
import Wasp.Cli.Command (Command, CommandError (CommandError), require) | ||
import Wasp.Cli.Command.BuildStart.ArgumentsParser (buildStartArgsParser) | ||
import Wasp.Cli.Command.BuildStart.Client (buildClient, startClient) | ||
import Wasp.Cli.Command.BuildStart.Config (BuildStartConfig, makeBuildStartConfig) | ||
import Wasp.Cli.Command.BuildStart.Server (buildServer, startServer) | ||
import Wasp.Cli.Command.Call (Arguments) | ||
import Wasp.Cli.Command.Compile (analyze) | ||
import Wasp.Cli.Command.Message (cliSendMessageC) | ||
import Wasp.Cli.Command.Require (BuildDirExists (BuildDirExists), InWaspProject (InWaspProject)) | ||
import Wasp.Cli.Util.Parser (parseArguments) | ||
import Wasp.Job.Except (ExceptJob) | ||
import qualified Wasp.Job.Except as ExceptJob | ||
import Wasp.Job.IO (readJobMessagesAndPrintThemPrefixed) | ||
import qualified Wasp.Message as Msg | ||
|
||
buildStart :: Arguments -> Command () | ||
buildStart args = do | ||
buildStartArgs <- | ||
parseArguments "wasp build start" buildStartArgsParser args | ||
& either (throwError . CommandError "Parsing arguments failed") return | ||
|
||
BuildDirExists _ <- require | ||
|
||
InWaspProject waspProjectDir <- require | ||
appSpec <- analyze waspProjectDir | ||
|
||
-- TODO: Find a way to easily check we can connect to the DB. We'd like to | ||
-- throw a clear error if not available. (See #2858) | ||
-- | ||
-- It is not a big problem right now, because Prisma will fail shortly after | ||
-- the server starts if the DB is not running anyway, and with a very clear | ||
-- error message that we print. | ||
|
||
config <- makeBuildStartConfig appSpec buildStartArgs waspProjectDir | ||
|
||
buildAndStartServerAndClient config | ||
|
||
buildAndStartServerAndClient :: BuildStartConfig -> Command () | ||
buildAndStartServerAndClient config = do | ||
cliSendMessageC $ Msg.Start "Building client..." | ||
runAndPrintJob "Building client failed." $ | ||
buildClient config | ||
cliSendMessageC $ Msg.Success "Client built." | ||
|
||
cliSendMessageC $ Msg.Start "Building server..." | ||
runAndPrintJob "Building server failed." $ | ||
buildServer config | ||
cliSendMessageC $ Msg.Success "Server built." | ||
|
||
cliSendMessageC $ Msg.Start "Starting client and server..." | ||
runAndPrintJob "Starting Wasp app failed." $ | ||
ExceptJob.race_ | ||
(startClient config) | ||
(startServer config) | ||
where | ||
runAndPrintJob :: String -> ExceptJob -> Command () | ||
runAndPrintJob errorMessage job = do | ||
liftIO (runAndPrintJobIO job) | ||
>>= either (throwError . CommandError errorMessage) return | ||
|
||
runAndPrintJobIO :: ExceptJob -> IO (Either String ()) | ||
runAndPrintJobIO job = do | ||
chan <- newChan | ||
(result, _) <- | ||
concurrently | ||
(runExceptT $ job chan) | ||
(readJobMessagesAndPrintThemPrefixed chan) | ||
return result |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
module Wasp.Cli.Command.BuildStart.ArgumentsParser | ||
( buildStartArgsParser, | ||
BuildStartArgs (..), | ||
) | ||
where | ||
|
||
import qualified Options.Applicative as Opt | ||
import Wasp.Cli.Util.EnvVarArgument (EnvVarFileArgument, envVarFileReader, envVarReader) | ||
import Wasp.Env (EnvVar) | ||
|
||
data BuildStartArgs = BuildStartArgs | ||
{ clientEnvironmentVariables :: [EnvVar], | ||
clientEnvironmentFiles :: [EnvVarFileArgument], | ||
serverEnvironmentVariables :: [EnvVar], | ||
serverEnvironmentFiles :: [EnvVarFileArgument] | ||
} | ||
|
||
buildStartArgsParser :: Opt.Parser BuildStartArgs | ||
buildStartArgsParser = | ||
BuildStartArgs | ||
<$> Opt.many clientEnvironmentVariableParser | ||
<*> Opt.many clientEnvironmentFileParser | ||
<*> Opt.many serverEnvironmentVariableParser | ||
<*> Opt.many serverEnvironmentFileParser | ||
where | ||
clientEnvironmentVariableParser = | ||
makeEnvironmentVariableParser "client" "client-env" 'c' | ||
clientEnvironmentFileParser = | ||
makeEnvironmentFileParser "client" "client-env-file" | ||
|
||
serverEnvironmentVariableParser = | ||
makeEnvironmentVariableParser "server" "server-env" 's' | ||
serverEnvironmentFileParser = | ||
makeEnvironmentFileParser "server" "server-env-file" | ||
|
||
makeEnvironmentVariableParser :: String -> String -> Char -> Opt.Parser EnvVar | ||
makeEnvironmentVariableParser targetName longOptionName shortOptionName = | ||
Opt.option envVarReader $ | ||
Opt.long longOptionName | ||
<> Opt.short shortOptionName | ||
<> Opt.metavar "NAME=VALUE" | ||
<> Opt.help ("Set an environment variable for the " <> targetName <> " (can be used multiple times)") | ||
|
||
makeEnvironmentFileParser :: String -> String -> Opt.Parser EnvVarFileArgument | ||
makeEnvironmentFileParser targetName longOptionName = | ||
Opt.option envVarFileReader $ | ||
Opt.long longOptionName | ||
<> Opt.metavar "FILE_PATH" | ||
<> Opt.help ("Load environment variables for the " <> targetName <> " from a file (can be used multiple times)") | ||
<> Opt.action "file" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
module Wasp.Cli.Command.BuildStart.Client | ||
( buildClient, | ||
startClient, | ||
) | ||
where | ||
|
||
import Data.Function ((&)) | ||
import StrongPath ((</>)) | ||
import Wasp.Cli.Command.BuildStart.Config (BuildStartConfig) | ||
import qualified Wasp.Cli.Command.BuildStart.Config as Config | ||
import qualified Wasp.Generator.WebAppGenerator.Common as WebApp | ||
import qualified Wasp.Job as J | ||
import Wasp.Job.Except (ExceptJob, toExceptJob) | ||
import Wasp.Job.Process (runNodeCommandAsJob, runNodeCommandAsJobWithExtraEnv) | ||
|
||
buildClient :: BuildStartConfig -> ExceptJob | ||
buildClient config = | ||
runNodeCommandAsJobWithExtraEnv | ||
envVars | ||
webAppDir | ||
"npm" | ||
["run", "build"] | ||
J.WebApp | ||
& toExceptJob (("Building the client failed with exit code: " <>) . show) | ||
where | ||
envVars = Config.clientEnvVars config | ||
webAppDir = buildDir </> WebApp.webAppRootDirInProjectRootDir | ||
buildDir = Config.buildDir config | ||
|
||
startClient :: BuildStartConfig -> ExceptJob | ||
startClient config = | ||
runNodeCommandAsJob | ||
webAppDir | ||
"npm" | ||
[ "run", | ||
"preview", -- `preview` launches a static file server for the built client. | ||
"--", | ||
"--port", | ||
port, | ||
"--strictPort" -- This will make it fail if the port is already in use. | ||
] | ||
J.WebApp | ||
& toExceptJob (("Serving the client failed with exit code: " <>) . show) | ||
where | ||
port = show $ fst $ Config.clientPortAndUrl config | ||
|
||
buildDir = Config.buildDir config | ||
webAppDir = buildDir </> WebApp.webAppRootDirInProjectRootDir |
cprecioso marked this conversation as resolved.
Show resolved
Hide resolved
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wdyt about this solution? Moved all the environment variable calculation to the config creationg, so we can just pass an And split the constructor into two parts, one initial one and another where we put the envs, as that part has to be |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
module Wasp.Cli.Command.BuildStart.Config | ||
( BuildStartConfig, | ||
buildDir, | ||
clientEnvVars, | ||
clientPortAndUrl, | ||
dockerContainerName, | ||
dockerImageName, | ||
makeBuildStartConfig, | ||
serverEnvVars, | ||
serverUrl, | ||
) | ||
where | ||
|
||
import Control.Monad.Except (MonadError (throwError), MonadIO (liftIO)) | ||
import Data.Char (toLower) | ||
import Data.List (intercalate) | ||
import StrongPath ((</>)) | ||
import qualified StrongPath as SP | ||
import Wasp.AppSpec (AppSpec) | ||
import qualified Wasp.AppSpec.Valid as ASV | ||
import Wasp.Cli.Command (Command, CommandError (CommandError)) | ||
import Wasp.Cli.Command.BuildStart.ArgumentsParser (BuildStartArgs) | ||
import qualified Wasp.Cli.Command.BuildStart.ArgumentsParser as Args | ||
import Wasp.Cli.Util.EnvVarArgument (EnvVarFileArgument, readEnvVarFile) | ||
import Wasp.Env (EnvVar, forceEnvVars, nubEnvVars) | ||
import Wasp.Generator.Common (ProjectRootDir) | ||
import Wasp.Generator.ServerGenerator.Common (defaultDevServerUrl) | ||
import qualified Wasp.Generator.ServerGenerator.Common as Server | ||
import Wasp.Generator.WebAppGenerator.Common (defaultClientPort, getDefaultDevClientUrl) | ||
import qualified Wasp.Generator.WebAppGenerator.Common as WebApp | ||
import Wasp.Project.Common (WaspProjectDir, buildDirInDotWaspDir, dotWaspDirInWaspProjectDir, makeAppUniqueId) | ||
|
||
data BuildStartConfig = BuildStartConfig | ||
{ appUniqueId :: String, | ||
clientPortAndUrl :: (Int, String), | ||
serverEnvVars :: [EnvVar], | ||
clientEnvVars :: [EnvVar], | ||
buildDir :: SP.Path' SP.Abs (SP.Dir ProjectRootDir) | ||
} | ||
|
||
makeBuildStartConfig :: AppSpec -> BuildStartArgs -> SP.Path' SP.Abs (SP.Dir WaspProjectDir) -> Command BuildStartConfig | ||
makeBuildStartConfig appSpec args projectDir = do | ||
let config = makeBuildStartConfigWithoutEnvVars appSpec projectDir | ||
infomiho marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
serverEnvVars' <- | ||
readAndForceEnvVars | ||
[ (Server.clientUrlEnvVarName, show $ fst $ clientPortAndUrl config), | ||
infomiho marked this conversation as resolved.
Show resolved
Hide resolved
|
||
(Server.serverUrlEnvVarName, serverUrl config) | ||
] | ||
(Args.serverEnvironmentVariables args) | ||
(Args.serverEnvironmentFiles args) | ||
|
||
clientEnvVars' <- | ||
readAndForceEnvVars | ||
[ (WebApp.serverUrlEnvVarName, serverUrl config) | ||
] | ||
(Args.clientEnvironmentVariables args) | ||
(Args.clientEnvironmentFiles args) | ||
|
||
return $ | ||
config | ||
{ serverEnvVars = serverEnvVars', | ||
clientEnvVars = clientEnvVars' | ||
} | ||
|
||
makeBuildStartConfigWithoutEnvVars :: AppSpec -> SP.Path' SP.Abs (SP.Dir WaspProjectDir) -> BuildStartConfig | ||
makeBuildStartConfigWithoutEnvVars appSpec projectDir = | ||
BuildStartConfig | ||
{ appUniqueId = appUniqueId', | ||
buildDir = buildDir', | ||
clientPortAndUrl = (clientPort, clientUrl), | ||
serverEnvVars = [], | ||
clientEnvVars = [] | ||
} | ||
where | ||
buildDir' = projectDir </> dotWaspDirInWaspProjectDir </> buildDirInDotWaspDir | ||
appUniqueId' = makeAppUniqueId projectDir appName | ||
(appName, _) = ASV.getApp appSpec | ||
|
||
-- This assumes that `getDefaultDevClientUrl` uses `defaultClientPort` internally. | ||
-- If that changes, we also need to change this. | ||
clientPort = defaultClientPort | ||
clientUrl = getDefaultDevClientUrl appSpec | ||
infomiho marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
-- NOTE(carlos): For now, creating these URLs and ports below uses the default | ||
-- values we've hardcoded in the generator. In the future, we might want to make | ||
-- these configurable via the Wasp app spec or command line arguments. | ||
|
||
serverUrl :: BuildStartConfig -> String | ||
infomiho marked this conversation as resolved.
Show resolved
Hide resolved
|
||
serverUrl _ = defaultDevServerUrl | ||
|
||
dockerImageName :: BuildStartConfig -> String | ||
dockerImageName config = | ||
map toLower $ -- Lowercase because Docker image names require it. | ||
appUniqueId config <> "-server" | ||
|
||
dockerContainerName :: BuildStartConfig -> String | ||
dockerContainerName config = | ||
map toLower $ -- Lowercase because Docker container names require it. | ||
appUniqueId config <> "-server-container" | ||
|
||
readAndForceEnvVars :: [EnvVar] -> [EnvVar] -> [EnvVarFileArgument] -> Command [EnvVar] | ||
readAndForceEnvVars forced existing files = do | ||
readVars <- liftIO $ readEnvVars existing files | ||
forceEnvVarsCommand forced readVars | ||
|
||
readEnvVars :: [EnvVar] -> [EnvVarFileArgument] -> IO [EnvVar] | ||
readEnvVars pairs files = do | ||
pairsFromFiles <- mapM readEnvVarFile files | ||
let allEnvVars = pairs <> concat pairsFromFiles | ||
return $ nubEnvVars allEnvVars | ||
|
||
forceEnvVarsCommand :: [EnvVar] -> [EnvVar] -> Command [EnvVar] | ||
forceEnvVarsCommand forced existing = | ||
case forceEnvVars forced existing of | ||
Left duplicateNames -> | ||
throwError $ | ||
CommandError "Duplicate environment variables" $ | ||
("The following environment variables will be overwritten by Wasp and should be removed: " <>) $ | ||
intercalate ", " duplicateNames | ||
Right combined -> return combined | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
infomiho marked this conversation as resolved.
Show resolved
Hide resolved
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
module Wasp.Cli.Command.BuildStart.Server | ||
( buildServer, | ||
startServer, | ||
) | ||
where | ||
|
||
import Data.Function ((&)) | ||
import qualified StrongPath as SP | ||
import System.Process (proc) | ||
import Wasp.Cli.Command.BuildStart.Config (BuildStartConfig) | ||
import qualified Wasp.Cli.Command.BuildStart.Config as Config | ||
import qualified Wasp.Job as J | ||
import Wasp.Job.Except (ExceptJob, toExceptJob) | ||
import Wasp.Job.Process (runProcessAsJob) | ||
|
||
buildServer :: BuildStartConfig -> ExceptJob | ||
buildServer config = | ||
runProcessAsJob | ||
(proc "docker" ["build", "--tag", dockerImageName, dockerContextDir]) | ||
J.Server | ||
& toExceptJob (("Building the server failed with exit code: " <>) . show) | ||
where | ||
dockerContextDir = SP.fromAbsDir buildDir | ||
buildDir = Config.buildDir config | ||
dockerImageName = Config.dockerImageName config | ||
|
||
startServer :: BuildStartConfig -> ExceptJob | ||
startServer config = | ||
runProcessAsJob | ||
( proc | ||
"docker" | ||
( ["run", "--name", dockerContainerName, "--rm", "--network", "host"] | ||
<> envVarParams | ||
<> [dockerImageName] | ||
) | ||
) | ||
J.Server | ||
& toExceptJob (("Running the server failed with exit code: " <>) . show) | ||
where | ||
envVarParams = toEnvVarParams $ Config.serverEnvVars config | ||
dockerContainerName = Config.dockerContainerName config | ||
dockerImageName = Config.dockerImageName config | ||
|
||
toEnvVarParams list = | ||
list >>= \(name, value) -> ["--env", name <> "=" <> value] |
Uh oh!
There was an error while loading. Please reload this page.