Skip to content

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

Open
wants to merge 95 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 39 commits
Commits
Show all changes
95 commits
Select commit Hold shift + click to select a range
3e05664
wip
cprecioso May 26, 2025
9ea053f
Use common abstractions
cprecioso Jun 2, 2025
e06a292
Remove build
cprecioso Jun 2, 2025
5e7c8c3
Remove unused imports
cprecioso Jun 2, 2025
128f70e
Fix
cprecioso Jun 2, 2025
ddc9a32
Merge branch 'main' into 1883-implement-wasp-build-start-command-that…
cprecioso Jun 2, 2025
5b57571
Merge branch 'main' into 1883-implement-wasp-build-start-command-that…
cprecioso Jun 2, 2025
9e72db1
Just learned about `ExceptT`
cprecioso Jun 3, 2025
5b44f5c
Now it works!
cprecioso Jun 3, 2025
87734fc
Merge branch 'main' into 1883-implement-wasp-build-start-command-that…
cprecioso Jun 4, 2025
9a2831b
Allow `dbExecuteTest` to run without built .env files
cprecioso Jun 4, 2025
b1a4e97
Pass the error channel around for printing if needed
cprecioso Jun 4, 2025
30c4a7c
Print Prisma output when there's an unknown error
cprecioso Jun 4, 2025
f262562
Allow requirement to be parametrized by folder
cprecioso Jun 4, 2025
6d86658
Merge better prisma
cprecioso Jun 4, 2025
01ac717
Add checking DB connection
cprecioso Jun 4, 2025
0b91a74
Improve JobExcept abstraction
cprecioso Jun 5, 2025
a35f5a6
Correct
cprecioso Jun 5, 2025
af0772c
Add Build dir check
cprecioso Jun 5, 2025
df97486
Remove workaround for interactive Prisma
cprecioso Jun 16, 2025
c326246
Merge branch 'cprecioso/push-rmwtsrklmvrs' into cprecioso/push-yppoqw…
cprecioso Jun 16, 2025
fd4b9d5
Better env handling
cprecioso Jun 16, 2025
80e43ed
Simple
cprecioso Jun 16, 2025
39bd54e
Merge branch 'main' into cprecioso/push-yppoqwmksmyl
cprecioso Jun 17, 2025
9c35873
Add new file to cabal
cprecioso Jun 17, 2025
7794642
Add note back
cprecioso Jun 17, 2025
7b02f44
Naming changes
cprecioso Jun 17, 2025
a427e85
Return prisma path
cprecioso Jun 17, 2025
9a3d570
Merge branch 'cprecioso/push-yppoqwmksmyl' into 1883-implement-wasp-b…
cprecioso Jun 17, 2025
305987a
Come back comment
cprecioso Jun 17, 2025
b78c7b6
Stop trying to make DBConnectionEstablished happen
cprecioso Jun 17, 2025
3a0f7e6
Undo :(
cprecioso Jun 17, 2025
54a2a28
Merge commit '3a0f7e61' into 1883-implement-wasp-build-start-command-…
cprecioso Jun 17, 2025
827e6ab
Pass DATABASE_URL
cprecioso Jun 17, 2025
c69ddfc
Do not hardcode ports and urls
cprecioso Jun 17, 2025
fddbca7
Move unique id
cprecioso Jun 17, 2025
3c3327d
Use common wasp app name logic
cprecioso Jun 17, 2025
c9d389c
Change name convention to match wasp-app-runner
cprecioso Jun 17, 2025
b2f1801
Better comment
cprecioso Jun 17, 2025
1071176
Rename `DbConnectionEstablished` to `DbConnectionEstablishedFromOutDir`
cprecioso Jun 23, 2025
806c4b1
Merge
cprecioso Jun 23, 2025
670a34d
Address comments (1)
cprecioso Jun 23, 2025
512d0c9
Address review (2)
cprecioso Jun 23, 2025
64dea5f
Address review (3)
cprecioso Jun 23, 2025
edd6700
Address review (4)
cprecioso Jun 23, 2025
20f2d13
Merge branch 'main' into 1883-implement-wasp-build-start-command-that…
cprecioso Jun 23, 2025
252a269
Merge branch 'main' into 1883-implement-wasp-build-start-command-that…
cprecioso Jul 1, 2025
98be630
Address comments
cprecioso Jul 1, 2025
b0f06be
Explicit Docker lowercase
cprecioso Jul 3, 2025
d63e3f9
Merge branch 'main' into 1883-implement-wasp-build-start-command-that…
cprecioso Jul 4, 2025
05fc1b4
Refactor env variables
cprecioso Jul 4, 2025
675f760
Merge branch 'push-uuqynktmlxnr' into 1883-implement-wasp-build-start…
cprecioso Jul 4, 2025
32bb4e8
Update
cprecioso Jul 4, 2025
02de3fb
Merge branch 'main' into push-uuqynktmlxnr
cprecioso Jul 11, 2025
b9700c2
Merge branch 'push-uuqynktmlxnr' into 1883-implement-wasp-build-start…
cprecioso Jul 11, 2025
e57b2e7
Implement env args for build start
cprecioso Jul 11, 2025
369d829
Extract parseArguments
cprecioso Jul 11, 2025
92bdf6e
Update CLI help
cprecioso Jul 11, 2025
1651b84
Address reviews
cprecioso Jul 16, 2025
0462b9f
Comments
cprecioso Jul 16, 2025
3328dd8
comments
cprecioso Jul 16, 2025
810218f
Address
cprecioso Jul 16, 2025
6172984
Address
cprecioso Jul 16, 2025
2c3cbf4
Merge branch 'main' into 1883-implement-wasp-build-start-command-that…
cprecioso Jul 16, 2025
396ec95
Merge branch 'main' into push-uuqynktmlxnr
cprecioso Jul 16, 2025
6c8b47a
Merge branch 'push-uuqynktmlxnr' into 1883-implement-wasp-build-start…
cprecioso Jul 16, 2025
2220f20
Rename
cprecioso Jul 17, 2025
8d1e5a5
Remove from starter
cprecioso Jul 17, 2025
000c033
Qualify AuthG
cprecioso Jul 17, 2025
316a97a
Merge branch 'push-uuqynktmlxnr' into 1883-implement-wasp-build-start…
cprecioso Jul 17, 2025
3f2db84
Update
cprecioso Jul 17, 2025
d57b233
comments
cprecioso Jul 17, 2025
56ce5a7
Comment
cprecioso Jul 17, 2025
399f664
Comments
cprecioso Jul 17, 2025
0ea6f52
Merge branch 'main' into 1883-implement-wasp-build-start-command-that…
cprecioso Jul 18, 2025
3c1ce25
Merge branch 'main' into 1883-implement-wasp-build-start-command-that…
cprecioso Jul 18, 2025
acb5b9e
Comment
cprecioso Jul 18, 2025
f4a892e
Add comment
cprecioso Jul 18, 2025
315a5b6
Always parse env vars
cprecioso Jul 18, 2025
f10fe23
Parse env var files
cprecioso Jul 18, 2025
54e37d6
Parse env var args when creating config
cprecioso Jul 18, 2025
b75cb86
forceEnvVars impl
cprecioso Jul 18, 2025
3ebeda6
Force env vars in config
cprecioso Jul 18, 2025
6ec1329
Reorder
cprecioso Jul 18, 2025
5335604
Reorder
cprecioso Jul 18, 2025
9a31194
Reorder
cprecioso Jul 18, 2025
c38eb7f
Remove unneeded import
cprecioso Jul 18, 2025
00e6a5a
Merge branch 'main' into 1883-implement-wasp-build-start-command-that…
cprecioso Jul 22, 2025
67ff2e4
Add changelog
cprecioso Jul 22, 2025
c09269c
docs
cprecioso Jul 22, 2025
28aa46b
Fix changelog
cprecioso Jul 23, 2025
c8b5642
Move WorkingDir
cprecioso Jul 23, 2025
c8ba324
Together
cprecioso Jul 23, 2025
8a39527
Renames
cprecioso Jul 23, 2025
d8e3b2d
Add unit tests
cprecioso Jul 23, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions waspc/cli/exe/Main.hs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import System.Exit (exitFailure)
import Wasp.Cli.Command (runCommand)
import Wasp.Cli.Command.BashCompletion (bashCompletion, generateBashCompletionScript, printBashCompletionInstruction)
import Wasp.Cli.Command.Build (build)
import Wasp.Cli.Command.BuildStart (buildStart)
import qualified Wasp.Cli.Command.Call as Command.Call
import Wasp.Cli.Command.Clean (clean)
import Wasp.Cli.Command.Compile (compile)
Expand Down Expand Up @@ -58,6 +59,7 @@ main = withUtf8 . (`E.catch` handleInternalErrors) $ do
["uninstall"] -> Command.Call.Uninstall
["version"] -> Command.Call.Version
["build"] -> Command.Call.Build
["build", "start"] -> Command.Call.BuildStart
["telemetry"] -> Command.Call.Telemetry
["deps"] -> Command.Call.Deps
["dockerfile"] -> Command.Call.Dockerfile
Expand Down Expand Up @@ -111,6 +113,7 @@ main = withUtf8 . (`E.catch` handleInternalErrors) $ do
Command.Call.Studio -> runCommand studio
Command.Call.Uninstall -> runCommand uninstall
Command.Call.Build -> runCommand build
Command.Call.BuildStart -> runCommand buildStart
Command.Call.Telemetry -> runCommand Telemetry.telemetry
Command.Call.Deps -> runCommand deps
Command.Call.Dockerfile -> runCommand printDockerfile
Expand Down Expand Up @@ -179,6 +182,7 @@ printUsage =
cmd " clean Deletes all generated code, all cached artifacts, and the node_modules dir.",
" Wasp equivalent of 'have you tried closing and opening it again?'.",
cmd " build Generates full web app code, ready for deployment. Use when deploying or ejecting.",
cmd " build start Builds the app and then starts it in production mode.",
cmd " deploy Deploys your Wasp app to cloud hosting providers.",
cmd " telemetry Prints telemetry status.",
cmd " deps Prints the dependencies that Wasp uses in your project.",
Expand Down
175 changes: 175 additions & 0 deletions waspc/cli/src/Wasp/Cli/Command/BuildStart.hs
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

-- TODO: Find a way to easily check we can connect to the DB.
Copy link
Contributor

Choose a reason for hiding this comment

The 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 DATABASE_URL set up (or even better if the database is not reachable).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One extra thing I noticed, sometimes we output the red text, sometimes we don't:

Screenshot 2025-06-20 at 12 25 18 Screenshot 2025-06-20 at 12 25 35

Copy link
Member Author

Choose a reason for hiding this comment

The 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

Copy link
Member

Choose a reason for hiding this comment

The 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
-- 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.

let (appName, _) = ASV.getApp appSpec
let imageName = makeAppImageName waspProjectDir appName
let containerName = makeAppContainerName waspProjectDir appName

result <-
liftIO $
runExceptT $
buildAndStartEverything waspProjectDir appSpec imageName containerName

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 ()
buildAndStartEverything waspProjectDir appSpec dockerImageName dockerContainerName =
do
liftIO $ cliSendMessage $ Msg.Start "Preparing client..."
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

runAndPrintJob jobExcept = ExceptT $ do
chan <- newChan
(_, result) <-
concurrently
(readJobMessagesAndPrintThemPrefixed chan)
(runExceptT $ jobExcept chan)
return result

buildClient :: SP.Path' SP.Abs (SP.Dir ProjectRootDir) -> JobExcept
buildClient buildDir =
runNodeCommandAsJobWithExtraEnv
[("REACT_APP_API_URL", defaultDevServerUrl)]
Copy link
Member

Choose a reason for hiding this comment

The 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.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you suggest me where? 😅

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you search the project for envvarname, you will find databaseUrlEnvVarName :: String in Wasp.Project.DB. You can follow that analogously.

Copy link
Member Author

@cprecioso cprecioso Jul 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I checked and REACT_APP_API_URL (as well as in the server WASP_WEB_CLIENT_URL, WASP_SERVER_URL, JWT_SECRET) are not mentioned anywhere else in our Haskell code. Do we still want to extract it?

I did update the DATABASE_URL mention to databaseUrlEnvVarName

Copy link
Member

@Martinsos Martinsos Jul 3, 2025

Choose a reason for hiding this comment

The 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 (REACT_APP_API_URL). Such wide knowledge of Wasp shouldn't be hardcoded in such a local place, there should be a central place where this kind of knowledge is encoded.

Copy link
Member Author

Choose a reason for hiding this comment

The 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
startClient buildDir =
runNodeCommandAsJob
webAppDir
"npm"
[ "exec",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd maybe replace this with a new npm script called preview which does the same thing. It's more common and we don't have to reach for npm exec which is not that common (I've never used it tbh, that's what prompted this comment).

"vite",
"--",
"preview", -- `preview` launches vite just as a webserver to the built files
"--port",
show defaultClientPort,
"--strictPort" -- This will make it fail if the port is already in use
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd love to see strictPort in dev as well! Should be done in #2427

Copy link
Member Author

Choose a reason for hiding this comment

The 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])
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
Copy link
Member

Choose a reason for hiding this comment

The 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 wasp build start, the JWT will change. That will what, invalidate any sessions in the browser -> is that it, anything else? Is that going to be annoying for them? Or maybe it is good, forces them to test login :D?

What are other options?

Copy link
Contributor

Choose a reason for hiding this comment

The 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.

Copy link
Member Author

@cprecioso cprecioso Jun 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it matters that much if the session gets invalidated, it's not for production use, it's for testing.

I agree


What are other options?

  1. Automatic
    1. We generate it randomly each time
    2. We hash some part of the AppSpec and use it as a JWT
    3. We hardcode a single universal value (like in Wasp App Runner)
  2. Manual
    1. We ask for an environment value
    2. We ask for a command line parameter
  3. Hybrid (any combination of (1) but we use (2) if present)

I went for the (IMO) simplest but correct option. Wdyt?

Copy link
Member

@Martinsos Martinsos Jun 26, 2025

Choose a reason for hiding this comment

The 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.
That said, I am tempted to go with:

  1. hardcoded value, since we are testing indeed and that is simpler / more epxected (I think?)
  2. OR whatever we are doing when wasp start is run, since it makes sense to replicate that behaviour here as close as possible. Maybe this is connected with the whole discussion about how are we passing env vars compared to wasp start -> if we indeed replicate what is happening there, then we don't need to deal with JWT secret explicitly at all here?

Copy link
Member Author

@cprecioso cprecioso Jul 11, 2025

Choose a reason for hiding this comment

The 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"]
++
-- 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"]
Copy link
Member

Choose a reason for hiding this comment

The 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 wasp start db -> because in that case, your DATABASE_URL .env.server won't be used, instead wasp start db's database url will be used, so we warn users that way about it. Also, in the background, if I remember correctly, wasp start injects wasp start db's DATABASE_URL into generated .env in the server (not 100% anymore, maybe that changed, but it was working like that at some point).

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 wasp db start alongside wasp build start? I think yes, it makes sense they can use that one still, why would we force them to run it manually suddenly?

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.
But, if I remember correctly, we don't generate server/.env for build/ dir. Because we don't want to risk them deploying that file by accident, which makes sense. Which means you don't have an easy way to get that database url for wasp db start.

This is tricky, and is so because we never indented to run wasp build stuff on the machine like this. But this is expected complexity: we are introducing new concepts here, the concept of running wasp build, and we need to decide on what that means, how is it going to be run, because there isn't one definitive answer, it is up to us to figure that out and then make it work.

One idea that comes to my mind is generating that server/.env when doing wasp build. That sounds like the cleaner option than you trying to patch up stuff here and trying to emulate what wasp start is doing -> instead you want that same logic doing it both for wasp start and wasp build. So ideally, wasp build would actually generate server/.env file for us, and that is, we just pick that one up. Yes, then there is risk of deploying it, but I guess we can maybe put it in some location outside of dockerfile context, where it is harder to include it by accident, and we can also maybe tell docker to ignore any files with .env or .env.*? There is always the scenario of them somehow deploying the server code manually (people do it), so we also have to take that into account, they might not use our Dockerfile.

Oh and if we are really going for replicating the same env var situation like we have for wasp start, then shouldn't we pass the whole environment to the Dockerfile? I mean how can we know what have they set in env vars, we can't, we need to pass everything right?

Or, we could allow them to somehow specify which env vars to pass to Dockerfile, maybe. Maybe that is an option for wasp build start.
Btw what if they want to pass some vars inline, like SOMEVAR=foo wasp build start? Can we also enable that?

Uff I wrote a lot of stuff here. I will try to summarize it:

  1. We have to decide in which environment we run the wasp start build -> is it all the same as when running wasp start -> same env vars / dotenv files, same database -> or is it different? And how much can users customize that?
  2. If we are going with using the same environment as for wasp start, we will have to make sure we actually ensure it:
    2.1. Passing environment env vars: Dockerfile makes things tricky with it not normally passing all the env vars though (if I got that right?)
    2.2. Passing dotenv vars: We (I think, pls check) don't generate build/server/.env like we generate out/server/.env. We don't generate it for security reasons, but it might make sense now to actually generate it and look for others way to achieve that security.
    2.3. Passing inline env vars: If users do SOMEVAR=foo wasp build start, will that pass through? If not, should we do something about that?

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.

Copy link
Member

Choose a reason for hiding this comment

The 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!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The crucial line for me is:

Oh and if we are really going for replicating the same env var situation like we have for wasp start, then shouldn't we pass the whole environment to the Dockerfile? I mean how can we know what have they set in env vars, we can't, we need to pass everything right?

wasp build start is a development command that helps you run your built project and see that's working okay on your machine. I think about it like this:wasp start-build-app-in-dev.

As a user, I'd like for the setup to be as seamless as possible:

  • pick up my stuff from .env.server
  • inject the DATABASE_URL if needed so I can use wasp start db database
  • inject all the env variables that I might pass in SOME=stuff wasp build start

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 wasp start to run the app in production.

Copy link
Member

Choose a reason for hiding this comment

The 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 wasp build start to work if wasp start also worked, right?

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 wasp start, I think that is probably good
If not, we need to be explicit about it so they know what is expected from them as an additional step.

Does that make sense, is that what you were also imagining in your comment above?

Copy link
Contributor

@sodic sodic Jul 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just want to try out my built app and make sure it works.

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.

  • Explicitly specifying the environment variables (possibly by specifying the file with their definitions, not necessarily one by one).
  • Not doing anything special to support wasp db start. If you want to connect to that DB, send its connection string as an environment variable.

That means we wouldn't be reading it from .env.server auomatically. But I don't see this as a shortcoming.

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).

Copy link
Contributor

@sodic sodic Jul 7, 2025

Choose a reason for hiding this comment

The 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.

Benefit of (1) is that user can easily run wasp build, then wasp build start, and see that nothing went wrong while compiling it for building -> it runs the same as it did when I was doing wasp start just a bit earlier.

The fact that users have a reason to do this is a problem we should solve elsewhere.

Solving it with wasp build start is addressing the symptom (we give users a way to verify nothing unexpected happens during the build) instead of the disease (there shouldn't be anything unexpected happening during the build if wasp start works fine).

For user, bad side might be that even though they can very easily check that what they built works, they are not really ready to deploy yet -> there are more steps they will need to do once time for that comes.

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 wasp build start, the closer this command gets to wasp start, which in turn makes the statement above more accurate.

To be able to run wasp build start often, to confirm app is building correctly and the same as with wasp start (option (1)),

Just to drive my point home - if Wasp works correctly, I believe this check is redundant (of course it works, why wouldn't it?)

or is it better to be able to run wasp build less often / in more complicated manner (option (2)), but then when you do, you know you are very close to how it is going to be in the deployed environment? In a way, we are choosing if wasp build start is closer to wasp start (1) or wasp deploy (2).

So yes, I vote for this :)

Copy link
Member

Choose a reason for hiding this comment

The 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 wasp build start not working when wasp start does, because it does some extra checks / runs some extra stuff. That is experience I had with Docusaurus -> I ran build and I was ufffff, so many broken links, I just want to deploy and now I have to deal with all this. That said, maybe that is not ideal experience either.

But it makes more sense to me also that you are focused on wasp build start when you are about to deploy, not running it all the time after wasp start.
So ok, I think I would vote for option (2) also.
But we gotta be very explicit about it.

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?

Copy link
Member

Choose a reason for hiding this comment

The 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 wasp build start to behave as close to production. That means also requiring him to define env vars explicitly and separately of dev environment. Which means that the fact that Docker doesn't inherit env vars is actually a plus for us! Because it replicates production environment better.

Copy link
Contributor

Choose a reason for hiding this comment

The 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 wasp build isn't broken (which was my line of thinking, but I was coming from the headless tests mindset which is not how most of our users think).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added the arguments -e|--server-env and --server-env-file to wasp build start, which pass that to Docker.
Now we'll only set the WASP_SERVER_URL and WASP_WEB_CLIENT_URL when starting docker, everything else should be explicit.

++ toDockerEnvFlags
[ ("WASP_WEB_CLIENT_URL", clientUrl),
("WASP_SERVER_URL", defaultDevServerUrl),
("JWT_SECRET", jwtSecret)
]
++ [dockerImageName]
)
J.Server
chan
)
& toJobExcept (("Running the server failed with exit code: " <>) . show)
where
envFilePath = SP.fromAbsFile $ projectDir </> dotEnvServer

randomAsciiAlphaNum :: RandomGen g => Int -> g -> String
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one could go into Wasp.Util into one of the modules or create new one for random strings.

randomAsciiAlphaNum len gen = take len $ filter isAlphaNum $ randoms gen
where
isAlphaNum c = isAsciiUpper c || isAsciiLower c || isDigit c

toDockerEnvFlags :: [(String, String)] -> [String]
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 =
map toLower $
makeAppUniqueId waspProjectDir appName <> "-server-container"
1 change: 1 addition & 0 deletions waspc/cli/src/Wasp/Cli/Command/Call.hs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ data Call
| Compile
| Db Arguments -- db args
| Build
| BuildStart
| Version
| Telemetry
| Deps
Expand Down
18 changes: 18 additions & 0 deletions waspc/cli/src/Wasp/Cli/Command/Require.hs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ module Wasp.Cli.Command.Require
Requirable (checkRequirement),
DbConnectionEstablished (DbConnectionEstablished),
InWaspProject (InWaspProject),
BuildDirExists (BuildDirExists),
)
where

Expand Down Expand Up @@ -100,3 +101,20 @@ instance Requirable InWaspProject where
( "Couldn't find wasp project root - make sure"
++ " you are running this command from a Wasp project."
)

data BuildDirExists = BuildDirExists deriving (Typeable)

instance Requirable BuildDirExists where
checkRequirement = do
InWaspProject waspProjectDir <- require
let buildDir =
waspProjectDir
SP.</> Project.Common.dotWaspDirInWaspProjectDir
SP.</> Project.Common.buildDirInDotWaspDir
doesBuildDirExist <- liftIO $ doesPathExist (SP.fromAbsDir buildDir)
unless doesBuildDirExist $ do
throwError $
CommandError
"Build directory does not exist"
"Run `wasp build` first."
return BuildDirExists
7 changes: 3 additions & 4 deletions waspc/cli/src/Wasp/Cli/Command/Start/Db.hs
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,8 @@ import Wasp.Cli.Command.Compile (analyze)
import Wasp.Cli.Command.Message (cliSendMessageC)
import Wasp.Cli.Command.Require (InWaspProject (InWaspProject), require)
import qualified Wasp.Message as Msg
import Wasp.Project.Common (WaspProjectDir)
import Wasp.Project.Common (WaspProjectDir, makeAppUniqueId)
import Wasp.Project.Db (databaseUrlEnvVarName)
import Wasp.Project.Db.Dev (makeDevDbUniqueId)
import qualified Wasp.Project.Db.Dev.Postgres as Dev.Postgres
import Wasp.Project.Env (dotEnvServer)
import Wasp.Util (whenM)
Expand Down Expand Up @@ -156,7 +155,7 @@ startPostgreDevDb waspProjectDir appName = do
makeWaspDevDbDockerVolumeName :: Path' Abs (Dir WaspProjectDir) -> String -> String
makeWaspDevDbDockerVolumeName waspProjectDir appName =
take maxDockerVolumeNameLength $
waspDevDbDockerVolumePrefix <> "-" <> makeDevDbUniqueId waspProjectDir appName
waspDevDbDockerVolumePrefix <> "-" <> makeAppUniqueId waspProjectDir appName

waspDevDbDockerVolumePrefix :: String
waspDevDbDockerVolumePrefix = "wasp-dev-db"
Expand All @@ -168,7 +167,7 @@ maxDockerVolumeNameLength = 255
makeWaspDevDbDockerContainerName :: Path' Abs (Dir WaspProjectDir) -> String -> String
makeWaspDevDbDockerContainerName waspProjectDir appName =
take maxDockerContainerNameLength $
waspDevDbDockerVolumePrefix <> "-" <> makeDevDbUniqueId waspProjectDir appName
waspDevDbDockerVolumePrefix <> "-" <> makeAppUniqueId waspProjectDir appName

maxDockerContainerNameLength :: Int
maxDockerContainerNameLength = 63
37 changes: 37 additions & 0 deletions waspc/src/Wasp/Job/Except.hs
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 =
ExceptT $
action chan
<&> fromExitCode exitCodeToErrorMessage

fromExitCode :: (Int -> String) -> ExitCode -> Either String ()
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
17 changes: 16 additions & 1 deletion waspc/src/Wasp/Project/Common.hs
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,16 @@ module Wasp.Project.Common
srcTsConfigInWaspLangProject,
srcTsConfigInWaspTsProject,
waspProjectDirFromAppComponentDir,
makeAppUniqueId,
)
where

import StrongPath (Abs, Dir, File, File', Path', Rel, reldir, relfile, toFilePath, (</>))
import Data.Char (isAsciiLower, isAsciiUpper, isDigit)
import StrongPath (Abs, Dir, File, File', Path', Rel, fromAbsDir, reldir, relfile, toFilePath, (</>))
import System.Directory (doesFileExist)
import Wasp.AppSpec.ExternalFiles (SourceExternalCodeDir, SourceExternalPublicDir)
import qualified Wasp.Generator.Common as G.Common
import qualified Wasp.Util as U

type CompileError = String

Expand Down Expand Up @@ -128,3 +131,15 @@ findFileInWaspProjectDir waspDir file = do
let fileAbsFp = waspDir </> file
fileExists <- doesFileExist $ toFilePath fileAbsFp
return $ if fileExists then Just fileAbsFp else Nothing

-- Returns a unique id that can be used for global identification of a specific Wasp project.
-- Id is no longer than 30 chars, all of them ascii letters, numbers, hyphen or underscore.
-- It is designed this way to make it easily usable in many scenarios.
-- It contains app name (or big part of it), to make it also readable for humans.
-- It is not resistant to Wasp project moving or being renamed, and will change in that case.
makeAppUniqueId :: Path' Abs (Dir WaspProjectDir) -> String -> String
makeAppUniqueId waspProjectDir appName = take 19 sanitizedAppName <> "-" <> take 10 projectPathHash
where
projectPathHash = U.hexToString $ U.checksumFromString $ fromAbsDir waspProjectDir
sanitizedAppName = filter isSafeChar appName
isSafeChar c = isAsciiLower c || isAsciiUpper c || isDigit c || c == '_' || c == '-'
25 changes: 0 additions & 25 deletions waspc/src/Wasp/Project/Db/Dev.hs

This file was deleted.

7 changes: 3 additions & 4 deletions waspc/src/Wasp/Project/Db/Dev/Postgres.hs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@ where

import StrongPath (Abs, Dir, Path')
import Wasp.Db.Postgres (makeConnectionUrl, postgresMaxDbNameLength)
import Wasp.Project.Common (WaspProjectDir)
import Wasp.Project.Db.Dev (makeDevDbUniqueId)
import Wasp.Project.Common (WaspProjectDir, makeAppUniqueId)

defaultDevUser :: String
defaultDevUser = "postgresWaspDevUser"
Expand All @@ -24,11 +23,11 @@ defaultDevPass = "postgresWaspDevPass"
-- the db name will also change.
makeDevDbName :: Path' Abs (Dir WaspProjectDir) -> String -> String
makeDevDbName waspProjectDir appName =
-- We use makeDevDbUniqueId to construct a db name instead of a hardcoded value like "waspDevDb"
-- We use makeAppUniqueId to construct a db name instead of a hardcoded value like "waspDevDb"
-- in order to avoid the situation where one Wasp app accidentally connects to a db that another
-- Wasp app has started. This way db name is unique for the specific Wasp app, and another Wasp app
-- can't connect to it by accident.
take postgresMaxDbNameLength $ makeDevDbUniqueId waspProjectDir appName
take postgresMaxDbNameLength $ makeAppUniqueId waspProjectDir appName

defaultDevPort :: Int
defaultDevPort = 5432 -- 5432 is default port for PostgreSQL db.
Expand Down
Loading
Loading