-
-
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
base: main
Are you sure you want to change the base?
Changes from 39 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
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,175 @@ | ||
module Wasp.Cli.Command.BuildStart | ||
( buildStart, | ||
) | ||
where | ||
|
||
import Control.Concurrent.Async (concurrently) | ||
import Control.Concurrent.Chan (newChan) | ||
import Control.Monad.Except (ExceptT (ExceptT), runExceptT) | ||
import Control.Monad.IO.Class (liftIO) | ||
import Data.Char (isAsciiLower, isAsciiUpper, isDigit, toLower) | ||
import Data.Function ((&)) | ||
import StrongPath ((</>)) | ||
import qualified StrongPath as SP | ||
import System.Process (proc) | ||
import System.Random (Random (randoms), RandomGen, newStdGen) | ||
import Wasp.AppSpec (AppSpec) | ||
import qualified Wasp.AppSpec.Valid as ASV | ||
import Wasp.Cli.Command (Command, require) | ||
import Wasp.Cli.Command.Compile (analyze) | ||
import Wasp.Cli.Command.Message (cliSendMessageC) | ||
import Wasp.Cli.Command.Require (BuildDirExists (BuildDirExists), InWaspProject (InWaspProject)) | ||
import Wasp.Cli.Message (cliSendMessage) | ||
import Wasp.Generator.Common (ProjectRootDir) | ||
import Wasp.Generator.ServerGenerator.Common (defaultDevServerUrl) | ||
import Wasp.Generator.WebAppGenerator.Common (defaultClientPort, getDefaultDevClientUrl) | ||
import qualified Wasp.Generator.WebAppGenerator.Common as Common | ||
import qualified Wasp.Job as J | ||
import Wasp.Job.Except (JobExcept, toJobExcept) | ||
import qualified Wasp.Job.Except as JobExcept | ||
import Wasp.Job.IO (readJobMessagesAndPrintThemPrefixed) | ||
import Wasp.Job.Process (runNodeCommandAsJob, runNodeCommandAsJobWithExtraEnv, runProcessAsJob) | ||
import qualified Wasp.Message as Msg | ||
import Wasp.Project.Common (WaspProjectDir, buildDirInDotWaspDir, dotWaspDirInWaspProjectDir, makeAppUniqueId) | ||
import Wasp.Project.Env (dotEnvServer) | ||
|
||
buildStart :: Command () | ||
buildStart = do | ||
InWaspProject waspProjectDir <- require | ||
appSpec <- analyze waspProjectDir | ||
BuildDirExists <- require | ||
Martinsos marked this conversation as resolved.
Show resolved
Hide resolved
Martinsos marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
-- TODO: Find a way to easily check we can connect to the DB. | ||
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. I came to comment the same thing, I'd love it if the process would fail early if I don't have the 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. 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. That's weird, it must be a race condition? I can't reproduce locally 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. @cprecioso any idea what might be causing it? Would be good to look into it now while fresh, or it will be released and who knows when will we catch it again. |
||
-- Right now we just assume it is running and let Prisma fail if it is not. It | ||
-- is not easy for us to do the same check as in other commands | ||
-- (`DBConnecionEstablished <- require`), because that needs a built Prisma | ||
Martinsos marked this conversation as resolved.
Show resolved
Hide resolved
|
||
-- schema, and currently we do that inside the Dockerfile, not in the | ||
-- `.wasp/build` folder, like the `wasp start` command does. | ||
-- 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. | ||
cprecioso marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
let (appName, _) = ASV.getApp appSpec | ||
let imageName = makeAppImageName waspProjectDir appName | ||
Martinsos marked this conversation as resolved.
Show resolved
Hide resolved
|
||
let containerName = makeAppContainerName waspProjectDir appName | ||
Martinsos marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
result <- | ||
liftIO $ | ||
runExceptT $ | ||
buildAndStartEverything waspProjectDir appSpec imageName containerName | ||
Martinsos marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
case result of | ||
Left err -> cliSendMessageC $ Msg.Failure "Build and start failed" err | ||
Right () -> cliSendMessageC $ Msg.Success "Build and start completed successfully." | ||
|
||
buildAndStartEverything :: SP.Path' SP.Abs (SP.Dir WaspProjectDir) -> AppSpec -> String -> String -> ExceptT String IO () | ||
cprecioso marked this conversation as resolved.
Show resolved
Hide resolved
|
||
buildAndStartEverything waspProjectDir appSpec dockerImageName dockerContainerName = | ||
do | ||
Martinsos marked this conversation as resolved.
Show resolved
Hide resolved
|
||
liftIO $ cliSendMessage $ Msg.Start "Preparing client..." | ||
Martinsos marked this conversation as resolved.
Show resolved
Hide resolved
|
||
runAndPrintJob $ buildClient buildDir | ||
liftIO $ cliSendMessage $ Msg.Success "Client prepared." | ||
|
||
liftIO $ cliSendMessage $ Msg.Start "Preparing server..." | ||
runAndPrintJob $ buildServer buildDir dockerImageName | ||
liftIO $ cliSendMessage $ Msg.Success "Server prepared." | ||
|
||
liftIO $ cliSendMessage $ Msg.Start "Starting client and server..." | ||
runAndPrintJob $ | ||
JobExcept.race_ | ||
(startClient buildDir) | ||
(startServer waspProjectDir clientUrl dockerImageName dockerContainerName) | ||
where | ||
buildDir = waspProjectDir </> dotWaspDirInWaspProjectDir </> buildDirInDotWaspDir | ||
|
||
clientUrl = getDefaultDevClientUrl appSpec | ||
cprecioso marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
runAndPrintJob jobExcept = ExceptT $ do | ||
Martinsos marked this conversation as resolved.
Show resolved
Hide resolved
|
||
chan <- newChan | ||
(_, result) <- | ||
concurrently | ||
(readJobMessagesAndPrintThemPrefixed chan) | ||
(runExceptT $ jobExcept chan) | ||
Martinsos marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return result | ||
|
||
buildClient :: SP.Path' SP.Abs (SP.Dir ProjectRootDir) -> JobExcept | ||
buildClient buildDir = | ||
runNodeCommandAsJobWithExtraEnv | ||
[("REACT_APP_API_URL", defaultDevServerUrl)] | ||
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. We should probably extract this env var to a single place in our Haskell project. 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. Can you suggest me where? 😅 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. If you search the project for envvarname, you will find 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. I checked and I did update the 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. I think I still would, it is the right thing to do. At least those you are using right now ( 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. |
||
webAppDir | ||
"npm" | ||
["run", "build"] | ||
J.WebApp | ||
& toJobExcept (("Building the client failed with exit code: " <>) . show) | ||
where | ||
webAppDir = buildDir </> Common.webAppRootDirInProjectRootDir | ||
|
||
startClient :: SP.Path' SP.Abs (SP.Dir ProjectRootDir) -> JobExcept | ||
Martinsos marked this conversation as resolved.
Show resolved
Hide resolved
|
||
startClient buildDir = | ||
runNodeCommandAsJob | ||
webAppDir | ||
"npm" | ||
[ "exec", | ||
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. I'd maybe replace this with a new npm script called |
||
"vite", | ||
"--", | ||
"preview", -- `preview` launches vite just as a webserver to the built files | ||
cprecioso marked this conversation as resolved.
Show resolved
Hide resolved
|
||
"--port", | ||
show defaultClientPort, | ||
"--strictPort" -- This will make it fail if the port is already in use | ||
Martinsos 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. I'd love to see 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. Added a comment there |
||
] | ||
J.WebApp | ||
& toJobExcept (("Serving the client failed with exit code: " <>) . show) | ||
where | ||
webAppDir = buildDir </> Common.webAppRootDirInProjectRootDir | ||
|
||
buildServer :: SP.Path' SP.Abs (SP.Dir ProjectRootDir) -> String -> JobExcept | ||
buildServer buildDir dockerImageName = | ||
runProcessAsJob | ||
(proc "docker" ["build", "--tag", dockerImageName, SP.fromAbsDir buildDir]) | ||
Martinsos marked this conversation as resolved.
Show resolved
Hide resolved
|
||
J.Server | ||
& toJobExcept (("Building the server failed with exit code: " <>) . show) | ||
|
||
startServer :: SP.Path' SP.Abs (SP.Dir WaspProjectDir) -> String -> String -> String -> JobExcept | ||
startServer projectDir clientUrl dockerImageName containerName = | ||
( \chan -> do | ||
jwtSecret <- randomAsciiAlphaNum 32 <$> newStdGen | ||
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. What did we say about this, are we ok with it? The thing is, each time they run the What are other options? 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. That's a fair point, we hard code it in the Wasp App Runner https://github.com/wasp-lang/runner-action/blob/main/src/build/server.ts#L112 I don't think it matters that much if the session gets invalidated, it's not for production use, it's for testing. 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.
I agree
I went for the (IMO) simplest but correct option. Wdyt? 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. Ok, so I think we can stick with this, it is not a biggie.
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. Now we'll be passing JWT_SECRET explicitly, based on #2796 (comment) |
||
|
||
runProcessAsJob | ||
( proc "docker" $ | ||
["run", "--name", containerName, "--rm", "--env-file", envFilePath, "--network", "host"] | ||
cprecioso marked this conversation as resolved.
Show resolved
Hide resolved
|
||
++ | ||
-- We specifically pass this environment variable from the current | ||
-- execution to the server container because Prisma will need it, | ||
-- and it is not set in the .env file (wasp start complains if so). | ||
["--env", "DATABASE_URL"] | ||
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. Aha, so docker by default doesn't inherit env vars from the environment it is running in? Makes sense I guess. So, that is why we explicitly tell it to pick up DATABASE_URL, because we expect it to be in the environment, not in .env.server. From what I remember, wasp doesn't always complain on DATABASE_URL being in the .env.server, but only when you run You this is somewhat complex but you should take it into account! Tricky hm. Question is, what with the database? Do we want them to be able to use If so, and if it is correct that Wasp embeds DATABASE_URL for it in server/.env while building it from .env.server (check that), then we would ideally be able to use this mechanism. This is tricky, and is so because we never indented to run One idea that comes to my mind is generating that server/.env when doing Oh and if we are really going for replicating the same env var situation like we have for Or, we could allow them to somehow specify which env vars to pass to Dockerfile, maybe. Maybe that is an option for Uff I wrote a lot of stuff here. I will try to summarize it:
I suggest including the rest of the team here, for a quick discussion on what is the best course of action, as they might have more experience with this part of Wasp + have seen people using it in different ways. 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 @sodic @FranjoMindek please check this out and say what you think about this! 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. The crucial line for me is:
As a user, I'd like for the setup to be as seamless as possible:
I just want to try out my built app and make sure it works. Also, I'd point out to users "THIS IS NOT MEANT FOR PRODUCTION USE" since we had users use 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 if going for that experience, I kind of assume you would expect The thing is, for that, we need to make sure it gets all the same env vars, which is not so simple. Because they could be coming from the shell environment even. If we can pass them same as to Does that make sense, is that what you were also imagining in your comment above? 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.
I'll offer an entirely different perspective :) If I got everything right, we want this command to run the Wasp app as it would run it in production. For me, that means.
That means we wouldn't be reading it from As a user, if I'm testing my deployment locally (which is what I understand this command does), I want to test that too: Do I know all the env vars I'll specify in my provider's dashboard and do they play well together. Take this with a grain of salt though. I'm lacking a lot context here and am still a little fuzzy on why we want to have this command (I get the core idea, but I'm not entirely clear on its KPIs let's say). 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. I'm all the way for Option 2: production experience. I'll add a little more to my reasoning.
The fact that users have a reason to do this is a problem we should solve elsewhere. Solving it with
Exactly, yes. I feel like option 1 doesn't really help with "testing the production build." It helps with "testing whether Wasp is broken for some weird reason." I could be missing something that's different by design though, so warn me if I am. Still, the more "automatic stuff" we do for
Just to drive my point home - if Wasp works correctly, I believe this check is redundant (of course it works, why wouldn't it?)
So yes, I vote for this :) 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. @sodic so all makes sense to me, one thing I would add is that I can still imagine But it makes more sense to me also that you are focused on Btw @cprecioso , do you have an idea what other solutions do? E.g. Astro, NextJS, Docusaurus, ... -> do they have a way to run built stuff (I know Docusaurus does) and what is the DX there? 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. Just to add to this that we had a chat today with Matija, he was deploying to production and getting errors, and it was 100% clear that he needs option (2) -> a way to test the built wasp app as close to production, but locally, so he can iterate faster / easily see errors. So what he needed what for 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. Just reading this now and I believe you guys figured out what our user persona really needs, which I think settled which option to choose. Good job! I think having it as close to production as possible is helpful, more so than having an easy way to verify 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. Added the arguments |
||
++ toDockerEnvFlags | ||
[ ("WASP_WEB_CLIENT_URL", clientUrl), | ||
("WASP_SERVER_URL", defaultDevServerUrl), | ||
Martinsos marked this conversation as resolved.
Show resolved
Hide resolved
|
||
("JWT_SECRET", jwtSecret) | ||
] | ||
++ [dockerImageName] | ||
) | ||
J.Server | ||
chan | ||
) | ||
& toJobExcept (("Running the server failed with exit code: " <>) . show) | ||
where | ||
envFilePath = SP.fromAbsFile $ projectDir </> dotEnvServer | ||
Martinsos marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
randomAsciiAlphaNum :: RandomGen g => Int -> g -> String | ||
Martinsos marked this conversation as resolved.
Show resolved
Hide resolved
Martinsos 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. This one could go into |
||
randomAsciiAlphaNum len gen = take len $ filter isAlphaNum $ randoms gen | ||
where | ||
isAlphaNum c = isAsciiUpper c || isAsciiLower c || isDigit c | ||
Martinsos marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
toDockerEnvFlags :: [(String, String)] -> [String] | ||
Martinsos marked this conversation as resolved.
Show resolved
Hide resolved
|
||
toDockerEnvFlags = concatMap (\(name, value) -> ["--env", name ++ "=" ++ value]) | ||
|
||
-- | Docker image name unique for the Wasp project with specified path and name. | ||
makeAppImageName :: SP.Path' SP.Abs (SP.Dir WaspProjectDir) -> String -> String | ||
makeAppImageName waspProjectDir appName = | ||
map toLower $ | ||
makeAppUniqueId waspProjectDir appName <> "-server" | ||
|
||
makeAppContainerName :: SP.Path' SP.Abs (SP.Dir WaspProjectDir) -> String -> String | ||
makeAppContainerName waspProjectDir appName = | ||
Martinsos marked this conversation as resolved.
Show resolved
Hide resolved
|
||
map toLower $ | ||
makeAppUniqueId waspProjectDir appName <> "-server-container" |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,6 +11,7 @@ data Call | |
| Compile | ||
| Db Arguments -- db args | ||
| Build | ||
| BuildStart | ||
| Version | ||
| Telemetry | ||
| Deps | ||
|
cprecioso marked this conversation as resolved.
Show resolved
Hide resolved
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
module Wasp.Job.Except | ||
( JobExcept, | ||
fromExitCode, | ||
toJobExcept, | ||
race_, | ||
) | ||
where | ||
|
||
import Control.Concurrent (Chan) | ||
import qualified Control.Concurrent.Async as Async | ||
import Control.Monad.Except (ExceptT (ExceptT), runExceptT) | ||
import Data.Functor (void, (<&>)) | ||
import System.Exit (ExitCode (ExitFailure, ExitSuccess)) | ||
import Wasp.Job (Job) | ||
import qualified Wasp.Job as J | ||
|
||
type JobExcept = Chan J.JobMessage -> ExceptT String IO () | ||
|
||
toJobExcept :: (Int -> String) -> Job -> JobExcept | ||
toJobExcept exitCodeToErrorMessage action chan = | ||
Martinsos marked this conversation as resolved.
Show resolved
Hide resolved
|
||
ExceptT $ | ||
action chan | ||
<&> fromExitCode exitCodeToErrorMessage | ||
|
||
fromExitCode :: (Int -> String) -> ExitCode -> Either String () | ||
Martinsos marked this conversation as resolved.
Show resolved
Hide resolved
|
||
fromExitCode _ ExitSuccess = Right () | ||
fromExitCode exitCodeToErrorMessage (ExitFailure code) = Left $ exitCodeToErrorMessage code | ||
|
||
race_ :: JobExcept -> JobExcept -> JobExcept | ||
race_ except1 except2 chan = | ||
ExceptT $ | ||
void . unwrapEither | ||
<$> Async.race | ||
(runExceptT $ except1 chan) | ||
(runExceptT $ except2 chan) | ||
where | ||
unwrapEither = either id id |
This file was deleted.
Uh oh!
There was an error while loading. Please reload this page.