mirror of
https://github.com/wasp-lang/wasp.git
synced 2024-12-23 00:51:36 +03:00
Implemented wasp start db
(managed dev db) (#1044)
Implemented 'wasp start db' + docs + e2e tests + refactoring.
This commit is contained in:
parent
a604518da6
commit
91a8063081
@ -8,15 +8,9 @@ Here, we implement it in Wasp, by following their [specification](https://realwo
|
||||
This app is deployed at https://wasp-rwa.netlify.app/ .
|
||||
|
||||
# Development
|
||||
|
||||
### Database
|
||||
Wasp needs postgre database running - provide it with database connection URL via env var `DATABASE_URL` - best to do it via .env file.
|
||||
|
||||
Easy way to get going with postgresql database: run db with `docker run --rm --publish 5432:5432 -v postgresql-data:/var/lib/postgresql/data --env POSTGRES_PASSWORD=devpass postgres`.
|
||||
`DATABASE_URL` in this case is `postgresql://postgres:devpass@localhost:5432/postgres`.
|
||||
|
||||
### Running
|
||||
`wasp start`
|
||||
`wasp start` to run the client and the server.
|
||||
`wasp start db` to run the dev database.
|
||||
|
||||
## TODO
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
app Thoughts {
|
||||
wasp: {
|
||||
version: "^0.8.0"
|
||||
version: "^0.9.1"
|
||||
},
|
||||
title: "Thoughts",
|
||||
db: { system: PostgreSQL },
|
||||
|
@ -21,6 +21,15 @@ export const fooBar : FooBar = (req, res, context) => {
|
||||
}
|
||||
```
|
||||
|
||||
### `wasp start db` -> Wasp can now run your dev database for you with a single command
|
||||
|
||||
Moving from SQLite to PostgreSQL with Wasp can feel like increase in complexity, because suddenly you have to care about running your PostgreSQL database, providing connection URL for it via env var, and if you checkout somebody's else Wasp project, or your old Wasp project that you have no memory of any more, you also have to figure all that out.
|
||||
|
||||
To help with that, we now added `wasp start db`, which runs a development database for you!
|
||||
That it, all you need to do is run `wasp start db` and you are good to go. No env var setting, no remembering how to run the db.
|
||||
|
||||
NOTE: Requires `docker` to be installed.
|
||||
|
||||
## v0.9.0
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
@ -20,4 +20,4 @@ jobs: $ncpus
|
||||
test-show-details: direct
|
||||
|
||||
-- WARNING: Run cabal update if your local package index is older than this date.
|
||||
index-state: 2022-03-22T14:16:26Z
|
||||
index-state: 2023-03-09T09:15:49Z
|
||||
|
@ -5,6 +5,7 @@ import qualified Control.Concurrent.Async as Async
|
||||
import qualified Control.Exception as E
|
||||
import Control.Monad (void)
|
||||
import Data.Char (isSpace)
|
||||
import Data.List (intercalate)
|
||||
import Main.Utf8 (withUtf8)
|
||||
import System.Environment (getArgs)
|
||||
import Wasp.Cli.Command (runCommand)
|
||||
@ -14,13 +15,16 @@ import qualified Wasp.Cli.Command.Call as Command.Call
|
||||
import Wasp.Cli.Command.Clean (clean)
|
||||
import Wasp.Cli.Command.Compile (compile)
|
||||
import Wasp.Cli.Command.CreateNewProject (createNewProject)
|
||||
import Wasp.Cli.Command.Db (runDbCommand, studio)
|
||||
import Wasp.Cli.Command.Db (runDbCommand)
|
||||
import qualified Wasp.Cli.Command.Db.Migrate as Command.Db.Migrate
|
||||
import qualified Wasp.Cli.Command.Db.Reset as Command.Db.Reset
|
||||
import qualified Wasp.Cli.Command.Db.Studio as Command.Db.Studio
|
||||
import Wasp.Cli.Command.Deploy (deploy)
|
||||
import Wasp.Cli.Command.Deps (deps)
|
||||
import Wasp.Cli.Command.Dockerfile (printDockerfile)
|
||||
import Wasp.Cli.Command.Info (info)
|
||||
import Wasp.Cli.Command.Start (start)
|
||||
import qualified Wasp.Cli.Command.Start.Db as Command.Start.Db
|
||||
import qualified Wasp.Cli.Command.Telemetry as Telemetry
|
||||
import Wasp.Cli.Command.Uninstall (uninstall)
|
||||
import Wasp.Cli.Command.WaspLS (runWaspLS)
|
||||
@ -35,6 +39,7 @@ main = withUtf8 . (`E.catch` handleInternalErrors) $ do
|
||||
let commandCall = case args of
|
||||
["new", projectName] -> Command.Call.New projectName
|
||||
["start"] -> Command.Call.Start
|
||||
["start", "db"] -> Command.Call.StartDb
|
||||
["clean"] -> Command.Call.Clean
|
||||
["compile"] -> Command.Call.Compile
|
||||
("db" : dbArgs) -> Command.Call.Db dbArgs
|
||||
@ -57,6 +62,7 @@ main = withUtf8 . (`E.catch` handleInternalErrors) $ do
|
||||
case commandCall of
|
||||
Command.Call.New projectName -> runCommand $ createNewProject projectName
|
||||
Command.Call.Start -> runCommand start
|
||||
Command.Call.StartDb -> runCommand Command.Start.Db.start
|
||||
Command.Call.Clean -> runCommand clean
|
||||
Command.Call.Compile -> runCommand compile
|
||||
Command.Call.Db dbArgs -> dbCli dbArgs
|
||||
@ -89,36 +95,40 @@ printUsage :: IO ()
|
||||
printUsage =
|
||||
putStrLn $
|
||||
unlines
|
||||
{- ORMOLU_DISABLE -}
|
||||
[ title "USAGE",
|
||||
" wasp <command> [command-args]",
|
||||
"",
|
||||
" wasp <command> [command-args]",
|
||||
"",
|
||||
title "COMMANDS",
|
||||
title " GENERAL",
|
||||
cmd " new <project-name> Creates new Wasp project.",
|
||||
cmd " version Prints current version of CLI.",
|
||||
cmd " waspls Run Wasp Language Server. Add --help to get more info.",
|
||||
cmd " completion Prints help on bash completion.",
|
||||
cmd " uninstall Removes Wasp from your system.",
|
||||
cmd " new <project-name> Creates new Wasp project.",
|
||||
cmd " version Prints current version of CLI.",
|
||||
cmd " waspls Run Wasp Language Server. Add --help to get more info.",
|
||||
cmd " completion Prints help on bash completion.",
|
||||
cmd " uninstall Removes Wasp from your system.",
|
||||
title " IN PROJECT",
|
||||
cmd " start Runs Wasp app in development mode, watching for file changes.",
|
||||
cmd " db <db-cmd> [args] Executes a database command. Run 'wasp db' for more info.",
|
||||
cmd " clean Deletes all generated code and other cached artifacts. 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 " 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.",
|
||||
cmd " dockerfile Prints the contents of the Wasp generated Dockerfile.",
|
||||
cmd " info Prints basic information about current Wasp project.",
|
||||
"",
|
||||
cmd " start Runs Wasp app in development mode, watching for file changes.",
|
||||
cmd " start db Starts managed development database for you.",
|
||||
cmd " db <db-cmd> [args] Executes a database command. Run 'wasp db' for more info.",
|
||||
cmd $ " clean Deletes all generated code and other cached artifacts.",
|
||||
" 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 " 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.",
|
||||
cmd " dockerfile Prints the contents of the Wasp generated Dockerfile.",
|
||||
cmd " info Prints basic information about current Wasp project.",
|
||||
"",
|
||||
title "EXAMPLES",
|
||||
" wasp new MyApp",
|
||||
" wasp start",
|
||||
" wasp db migrate-dev",
|
||||
"",
|
||||
Term.applyStyles [Term.Green] "Docs:" ++ " https://wasp-lang.dev/docs",
|
||||
" wasp new MyApp",
|
||||
" wasp start",
|
||||
" wasp db migrate-dev",
|
||||
"",
|
||||
Term.applyStyles [Term.Green] "Docs:" ++ " https://wasp-lang.dev/docs",
|
||||
Term.applyStyles [Term.Magenta] "Discord (chat):" ++ " https://discord.gg/rzdnErX",
|
||||
Term.applyStyles [Term.Cyan] "Newsletter:" ++ " https://wasp-lang.dev/#signup"
|
||||
Term.applyStyles [Term.Cyan] "Newsletter:" ++ " https://wasp-lang.dev/#signup"
|
||||
]
|
||||
{- ORMOLU_ENABLE -}
|
||||
|
||||
printVersion :: IO ()
|
||||
printVersion = do
|
||||
@ -128,44 +138,49 @@ printVersion = do
|
||||
"",
|
||||
"If you wish to install/switch to the latest version of Wasp, do:",
|
||||
" curl -sSL https://get.wasp-lang.dev/installer.sh | sh -s",
|
||||
"or do",
|
||||
" curl -sSL https://get.wasp-lang.dev/installer.sh | sh -s -- -v x.y.z",
|
||||
"if you want specific x.y.z version of Wasp.",
|
||||
"",
|
||||
"Check https://github.com/wasp-lang/wasp/releases for the list of valid versions, include the latest one."
|
||||
"If you want specific x.y.z version of Wasp, do:",
|
||||
" curl -sSL https://get.wasp-lang.dev/installer.sh | sh -s -- -v x.y.z",
|
||||
"",
|
||||
"Check https://github.com/wasp-lang/wasp/releases for the list of valid versions, including the latest one."
|
||||
]
|
||||
|
||||
-- TODO(matija): maybe extract to a separate module, e.g. DbCli.hs?
|
||||
dbCli :: [String] -> IO ()
|
||||
dbCli args = case args of
|
||||
"migrate-dev" : optionalMigrateArgs -> runDbCommand $ Command.Db.Migrate.migrateDev optionalMigrateArgs
|
||||
["studio"] -> runDbCommand studio
|
||||
["reset"] -> runDbCommand Command.Db.Reset.reset
|
||||
["studio"] -> runDbCommand Command.Db.Studio.studio
|
||||
_ -> printDbUsage
|
||||
|
||||
printDbUsage :: IO ()
|
||||
printDbUsage =
|
||||
putStrLn $
|
||||
unlines
|
||||
{- ORMOLU_DISABLE -}
|
||||
[ title "USAGE",
|
||||
" wasp db <command> [command-args]",
|
||||
"",
|
||||
" wasp db <command> [command-args]",
|
||||
"",
|
||||
title "COMMANDS",
|
||||
cmd
|
||||
( " migrate-dev Ensures dev database corresponds to the current state of schema(entities):\n"
|
||||
<> " - Generates a new migration if there are changes in the schema.\n"
|
||||
<> " - Applies any pending migrations to the database either using the supplied migration name or asking for one.\n"
|
||||
<> "\nOPTIONS:\n"
|
||||
<> " --name [migration-name]\n"
|
||||
<> " --create-only\n"
|
||||
),
|
||||
cmd " studio GUI for inspecting your database.",
|
||||
"",
|
||||
cmd " reset Drops all data and tables from development database and re-applies all migrations.",
|
||||
cmd $ intercalate "\n" [
|
||||
" migrate-dev Ensures dev database corresponds to the current state of schema(entities):",
|
||||
" - Generates a new migration if there are changes in the schema.",
|
||||
" - Applies any pending migrations to the database either using the",
|
||||
" supplied migration name or asking for one.",
|
||||
" OPTIONS:",
|
||||
" --name [migration-name]",
|
||||
" --create-only"
|
||||
],
|
||||
cmd " studio GUI for inspecting your database.",
|
||||
"",
|
||||
title "EXAMPLES",
|
||||
" wasp db migrate-dev",
|
||||
" wasp db migrate-dev --name \"Added User entity\"",
|
||||
" wasp db migrate-dev --create-only",
|
||||
" wasp db studio"
|
||||
" wasp db migrate-dev",
|
||||
" wasp db migrate-dev --name \"Added User entity\"",
|
||||
" wasp db migrate-dev --create-only",
|
||||
" wasp db studio"
|
||||
]
|
||||
{- ORMOLU_ENABLE -}
|
||||
|
||||
cmd :: String -> String
|
||||
cmd = mapFirstWord (Term.applyStyles [Term.Yellow, Term.Bold])
|
||||
|
@ -21,10 +21,10 @@ import Wasp.Cli.Command.Message (cliSendMessageC)
|
||||
import qualified Wasp.Cli.Common as Common
|
||||
import Wasp.Cli.Message (cliSendMessage)
|
||||
import Wasp.CompileOptions (CompileOptions (..))
|
||||
import qualified Wasp.Generator
|
||||
import Wasp.Generator.Monad (GeneratorWarning (GeneratorNeedsMigrationWarning))
|
||||
import Wasp.Lib (CompileError, CompileWarning)
|
||||
import qualified Wasp.Lib
|
||||
import qualified Wasp.Message as Msg
|
||||
import Wasp.Project (CompileError, CompileWarning)
|
||||
|
||||
-- | Builds Wasp project that the current working directory is part of.
|
||||
-- Does all the steps, from analysis to generation, and at the end writes generated code
|
||||
@ -61,7 +61,7 @@ build = do
|
||||
|
||||
buildIO ::
|
||||
Path' Abs (Dir Common.WaspProjectDir) ->
|
||||
Path' Abs (Dir Wasp.Lib.ProjectRootDir) ->
|
||||
Path' Abs (Dir Wasp.Generator.ProjectRootDir) ->
|
||||
IO ([CompileWarning], [CompileError])
|
||||
buildIO waspProjectDir buildDir = compileIOWithOptions options waspProjectDir buildDir
|
||||
where
|
||||
|
@ -3,6 +3,7 @@ module Wasp.Cli.Command.Call where
|
||||
data Call
|
||||
= New String -- project name
|
||||
| Start
|
||||
| StartDb
|
||||
| Clean
|
||||
| Uninstall
|
||||
| Compile
|
||||
|
@ -1,24 +1,25 @@
|
||||
module Wasp.Cli.Command.Common
|
||||
( findWaspProjectRootDirFromCwd,
|
||||
findWaspProjectRoot,
|
||||
readWaspCompileInfo,
|
||||
throwIfExeIsNotAvailable,
|
||||
)
|
||||
where
|
||||
|
||||
import Control.Monad (unless, when)
|
||||
import Control.Monad.Except (throwError)
|
||||
import Control.Monad.IO.Class (liftIO)
|
||||
import Control.Monad.Except
|
||||
import qualified Control.Monad.Except as E
|
||||
import Data.Maybe (fromJust)
|
||||
import StrongPath (Abs, Dir, Path')
|
||||
import qualified StrongPath as SP
|
||||
import System.Directory
|
||||
( doesFileExist,
|
||||
doesPathExist,
|
||||
getCurrentDirectory,
|
||||
)
|
||||
import StrongPath.Operations
|
||||
import System.Directory (doesFileExist, doesPathExist, findExecutable, getCurrentDirectory)
|
||||
import qualified System.FilePath as FP
|
||||
import Wasp.Cli.Command (Command, CommandError (..))
|
||||
import Wasp.Cli.Common (dotWaspRootFileInWaspProjectDir)
|
||||
import Wasp.Common (WaspProjectDir)
|
||||
import qualified Wasp.Cli.Common as Cli.Common
|
||||
import Wasp.Project (WaspProjectDir)
|
||||
import Wasp.Util (ifM)
|
||||
import qualified Wasp.Util.IO as IOUtil
|
||||
|
||||
findWaspProjectRoot :: Path' Abs (Dir ()) -> Command (Path' Abs (Dir WaspProjectDir))
|
||||
findWaspProjectRoot currentDir = do
|
||||
@ -45,3 +46,23 @@ findWaspProjectRootDirFromCwd :: Command (Path' Abs (Dir WaspProjectDir))
|
||||
findWaspProjectRootDirFromCwd = do
|
||||
absCurrentDir <- liftIO getCurrentDirectory
|
||||
findWaspProjectRoot (fromJust $ SP.parseAbsDir absCurrentDir)
|
||||
|
||||
readWaspCompileInfo :: Path' Abs (Dir WaspProjectDir) -> IO String
|
||||
readWaspCompileInfo waspDir =
|
||||
ifM
|
||||
(IOUtil.doesFileExist dotWaspInfoFile)
|
||||
(IOUtil.readFile dotWaspInfoFile)
|
||||
(return "No compile information found")
|
||||
where
|
||||
dotWaspInfoFile =
|
||||
waspDir </> Cli.Common.dotWaspDirInWaspProjectDir
|
||||
</> Cli.Common.generatedCodeDirInDotWaspDir
|
||||
</> Cli.Common.dotWaspInfoFileInGeneratedCodeDir
|
||||
|
||||
throwIfExeIsNotAvailable :: String -> String -> Command ()
|
||||
throwIfExeIsNotAvailable exeName explanationMsg = do
|
||||
liftIO (findExecutable exeName) >>= \case
|
||||
Just _ -> return ()
|
||||
Nothing ->
|
||||
E.throwError $
|
||||
CommandError ("Couldn't find `" <> exeName <> "` executable") explanationMsg
|
||||
|
@ -6,6 +6,8 @@ module Wasp.Cli.Command.Compile
|
||||
defaultCompileOptions,
|
||||
printCompilationResult,
|
||||
printWarningsAndErrorsIfAny,
|
||||
analyze,
|
||||
analyzeWithOptions,
|
||||
)
|
||||
where
|
||||
|
||||
@ -14,6 +16,7 @@ import Control.Monad.Except (throwError)
|
||||
import Control.Monad.IO.Class (liftIO)
|
||||
import Data.List (intercalate)
|
||||
import StrongPath (Abs, Dir, Path', (</>))
|
||||
import qualified Wasp.AppSpec as AS
|
||||
import Wasp.Cli.Command (Command, CommandError (..))
|
||||
import Wasp.Cli.Command.Common
|
||||
( findWaspProjectRootDirFromCwd,
|
||||
@ -21,11 +24,11 @@ import Wasp.Cli.Command.Common
|
||||
import Wasp.Cli.Command.Message (cliSendMessageC)
|
||||
import qualified Wasp.Cli.Common as Common
|
||||
import Wasp.Cli.Message (cliSendMessage)
|
||||
import Wasp.Common (WaspProjectDir)
|
||||
import Wasp.CompileOptions (CompileOptions (..))
|
||||
import Wasp.Lib (CompileError, CompileWarning)
|
||||
import qualified Wasp.Lib
|
||||
import qualified Wasp.Generator
|
||||
import qualified Wasp.Message as Msg
|
||||
import Wasp.Project (CompileError, CompileWarning, WaspProjectDir)
|
||||
import qualified Wasp.Project
|
||||
|
||||
-- | Same like 'compileWithOptions', but with default compile options.
|
||||
compile :: Command [CompileWarning]
|
||||
@ -98,18 +101,18 @@ formatErrorOrWarningMessages = intercalate "\n" . map ("- " ++)
|
||||
-- in given outDir directory.
|
||||
compileIO ::
|
||||
Path' Abs (Dir WaspProjectDir) ->
|
||||
Path' Abs (Dir Wasp.Lib.ProjectRootDir) ->
|
||||
Path' Abs (Dir Wasp.Generator.ProjectRootDir) ->
|
||||
IO ([CompileWarning], [CompileError])
|
||||
compileIO waspProjectDir outDir =
|
||||
compileIOWithOptions (defaultCompileOptions waspProjectDir) waspProjectDir outDir
|
||||
|
||||
compileIOWithOptions ::
|
||||
CompileOptions ->
|
||||
Path' Abs (Dir Common.WaspProjectDir) ->
|
||||
Path' Abs (Dir Wasp.Lib.ProjectRootDir) ->
|
||||
Path' Abs (Dir WaspProjectDir) ->
|
||||
Path' Abs (Dir Wasp.Generator.ProjectRootDir) ->
|
||||
IO ([CompileWarning], [CompileError])
|
||||
compileIOWithOptions options waspProjectDir outDir =
|
||||
Wasp.Lib.compile waspProjectDir outDir options
|
||||
Wasp.Project.compile waspProjectDir outDir options
|
||||
|
||||
defaultCompileOptions :: Path' Abs (Dir WaspProjectDir) -> CompileOptions
|
||||
defaultCompileOptions waspProjectDir =
|
||||
@ -121,3 +124,19 @@ defaultCompileOptions waspProjectDir =
|
||||
sendMessage = cliSendMessage,
|
||||
generatorWarningsFilter = id
|
||||
}
|
||||
|
||||
analyze :: Path' Abs (Dir WaspProjectDir) -> Command AS.AppSpec
|
||||
analyze waspProjectDir = do
|
||||
analyzeWithOptions waspProjectDir $ defaultCompileOptions waspProjectDir
|
||||
|
||||
-- | Analyzes Wasp project that the current working directory is a part of and returns
|
||||
-- AppSpec. So same like compilation, but it stops before any code generation.
|
||||
-- Throws if there were any compilation errors.
|
||||
analyzeWithOptions :: Path' Abs (Dir WaspProjectDir) -> CompileOptions -> Command AS.AppSpec
|
||||
analyzeWithOptions waspProjectDir options = do
|
||||
liftIO (Wasp.Project.analyzeWaspProject waspProjectDir options) >>= \case
|
||||
Left errors ->
|
||||
throwError $
|
||||
CommandError "Analyzing wasp project failed" $
|
||||
show (length errors) <> " errors found:\n" <> formatErrorOrWarningMessages errors
|
||||
Right spec -> return spec
|
||||
|
@ -14,9 +14,8 @@ import qualified System.FilePath as FP
|
||||
import Text.Printf (printf)
|
||||
import Wasp.Analyzer.Parser (isValidWaspIdentifier)
|
||||
import Wasp.Cli.Command (Command, CommandError (..))
|
||||
import Wasp.Common (WaspProjectDir)
|
||||
import qualified Wasp.Common as Common (WaspProjectDir)
|
||||
import qualified Wasp.Data as Data
|
||||
import Wasp.Project (WaspProjectDir)
|
||||
import Wasp.Util (indent, kebabToCamelCase)
|
||||
import qualified Wasp.Util.IO as IOUtil
|
||||
import qualified Wasp.Util.Terminal as Term
|
||||
@ -79,7 +78,7 @@ getAbsoluteWaspProjectDir (ProjectInfo projectName _) = do
|
||||
"Failed to parse absolute path to wasp project dir: " ++ show err
|
||||
|
||||
-- Copies prepared files to the new project directory.
|
||||
initializeProjectFromSkeleton :: Path' Abs (Dir Common.WaspProjectDir) -> IO ()
|
||||
initializeProjectFromSkeleton :: Path' Abs (Dir WaspProjectDir) -> IO ()
|
||||
initializeProjectFromSkeleton absWaspProjectDir = do
|
||||
dataDir <- Data.getAbsDataDirPath
|
||||
let absSkeletonDir = dataDir </> [reldir|Cli/templates/new|]
|
||||
|
@ -1,23 +1,13 @@
|
||||
module Wasp.Cli.Command.Db
|
||||
( runDbCommand,
|
||||
studio,
|
||||
)
|
||||
where
|
||||
|
||||
import Control.Concurrent (newChan)
|
||||
import Control.Concurrent.Async (concurrently)
|
||||
import Control.Monad.IO.Class (liftIO)
|
||||
import StrongPath ((</>))
|
||||
import Wasp.Cli.Command (Command, runCommand)
|
||||
import Wasp.Cli.Command.Common (findWaspProjectRootDirFromCwd)
|
||||
import Wasp.Cli.Command.Compile (compileWithOptions, defaultCompileOptions)
|
||||
import Wasp.Cli.Command.Message (cliSendMessageC)
|
||||
import qualified Wasp.Cli.Common as Common
|
||||
import Wasp.CompileOptions (CompileOptions (generatorWarningsFilter))
|
||||
import Wasp.Generator.DbGenerator.Jobs (runStudio)
|
||||
import Wasp.Generator.Job.IO (readJobMessagesAndPrintThemPrefixed)
|
||||
import Wasp.Generator.Monad (GeneratorWarning (GeneratorNeedsMigrationWarning))
|
||||
import qualified Wasp.Message as Msg
|
||||
|
||||
runDbCommand :: Command a -> IO ()
|
||||
runDbCommand = runCommand . makeDbCommand
|
||||
@ -44,17 +34,3 @@ makeDbCommand cmd = do
|
||||
_ -> True
|
||||
)
|
||||
}
|
||||
|
||||
-- TODO(matija): should we extract this into a separate file, like we did for migrate?
|
||||
studio :: Command ()
|
||||
studio = do
|
||||
waspProjectDir <- findWaspProjectRootDirFromCwd
|
||||
let genProjectDir =
|
||||
waspProjectDir </> Common.dotWaspDirInWaspProjectDir
|
||||
</> Common.generatedCodeDirInDotWaspDir
|
||||
|
||||
cliSendMessageC $ Msg.Start "Running studio..."
|
||||
chan <- liftIO newChan
|
||||
|
||||
_ <- liftIO $ concurrently (readJobMessagesAndPrintThemPrefixed chan) (runStudio genProjectDir chan)
|
||||
error "This should never happen, studio should never stop."
|
||||
|
@ -13,12 +13,11 @@ import Wasp.Cli.Command.Common
|
||||
)
|
||||
import Wasp.Cli.Command.Message (cliSendMessageC)
|
||||
import qualified Wasp.Cli.Common as Cli.Common
|
||||
import Wasp.Common (DbMigrationsDir)
|
||||
import qualified Wasp.Common
|
||||
import Wasp.Generator.Common (ProjectRootDir)
|
||||
import Wasp.Generator.DbGenerator.Common (MigrateArgs (..), defaultMigrateArgs)
|
||||
import qualified Wasp.Generator.DbGenerator.Operations as DbOps
|
||||
import qualified Wasp.Message as Msg
|
||||
import Wasp.Project.Db.Migrations (DbMigrationsDir, dbMigrationsDirInWaspProjectDir)
|
||||
|
||||
-- | NOTE(shayne): Performs database schema migration (based on current schema) in the generated project.
|
||||
-- This assumes the wasp project migrations dir was copied from wasp source project by a previous compile.
|
||||
@ -26,14 +25,13 @@ import qualified Wasp.Message as Msg
|
||||
migrateDev :: [String] -> Command ()
|
||||
migrateDev optionalMigrateArgs = do
|
||||
waspProjectDir <- findWaspProjectRootDirFromCwd
|
||||
let waspDbMigrationsDir = waspProjectDir </> Wasp.Common.dbMigrationsDirInWaspProjectDir
|
||||
let waspDbMigrationsDir = waspProjectDir </> dbMigrationsDirInWaspProjectDir
|
||||
let projectRootDir =
|
||||
waspProjectDir
|
||||
</> Cli.Common.dotWaspDirInWaspProjectDir
|
||||
</> Cli.Common.generatedCodeDirInDotWaspDir
|
||||
|
||||
migrateDatabase optionalMigrateArgs projectRootDir waspDbMigrationsDir
|
||||
generatePrismaClients projectRootDir
|
||||
|
||||
migrateDatabase :: [String] -> Path' Abs (Dir ProjectRootDir) -> Path' Abs (Dir DbMigrationsDir) -> Command ()
|
||||
migrateDatabase optionalMigrateArgs projectRootDir dbMigrationsDir = do
|
||||
@ -46,14 +44,6 @@ migrateDatabase optionalMigrateArgs projectRootDir dbMigrationsDir = do
|
||||
migrateArgs <- liftEither $ parseMigrateArgs optionalMigrateArgs
|
||||
ExceptT $ DbOps.migrateDevAndCopyToSource dbMigrationsDir projectRootDir migrateArgs
|
||||
|
||||
generatePrismaClients :: Path' Abs (Dir ProjectRootDir) -> Command ()
|
||||
generatePrismaClients projectRootDir = do
|
||||
cliSendMessageC $ Msg.Start "Generating prisma clients..."
|
||||
generatePrismaClientsResult <- liftIO $ DbOps.generatePrismaClients projectRootDir
|
||||
case generatePrismaClientsResult of
|
||||
Left err -> throwError $ CommandError "Could not generate Prisma clients" err
|
||||
Right () -> cliSendMessageC $ Msg.Success "Prisma clients successfully generated."
|
||||
|
||||
-- | Basic parsing of db-migrate args. In the future, we could use a smarter parser
|
||||
-- for this (and all other CLI arg parsing).
|
||||
parseMigrateArgs :: [String] -> Either String MigrateArgs
|
||||
|
25
waspc/cli/src/Wasp/Cli/Command/Db/Reset.hs
Normal file
25
waspc/cli/src/Wasp/Cli/Command/Db/Reset.hs
Normal file
@ -0,0 +1,25 @@
|
||||
module Wasp.Cli.Command.Db.Reset
|
||||
( reset,
|
||||
)
|
||||
where
|
||||
|
||||
import Control.Monad.IO.Class (liftIO)
|
||||
import StrongPath ((</>))
|
||||
import Wasp.Cli.Command (Command)
|
||||
import Wasp.Cli.Command.Common (findWaspProjectRootDirFromCwd)
|
||||
import Wasp.Cli.Command.Message (cliSendMessageC)
|
||||
import qualified Wasp.Cli.Common as Common
|
||||
import Wasp.Generator.DbGenerator.Operations (dbReset)
|
||||
import qualified Wasp.Message as Msg
|
||||
|
||||
reset :: Command ()
|
||||
reset = do
|
||||
waspProjectDir <- findWaspProjectRootDirFromCwd
|
||||
let genProjectDir =
|
||||
waspProjectDir </> Common.dotWaspDirInWaspProjectDir </> Common.generatedCodeDirInDotWaspDir
|
||||
|
||||
cliSendMessageC $ Msg.Start "Resetting the database..."
|
||||
|
||||
liftIO (dbReset genProjectDir) >>= \case
|
||||
Left errorMsg -> cliSendMessageC $ Msg.Failure "Database reset failed" errorMsg
|
||||
Right () -> cliSendMessageC $ Msg.Success "Database reset successfully!"
|
29
waspc/cli/src/Wasp/Cli/Command/Db/Studio.hs
Normal file
29
waspc/cli/src/Wasp/Cli/Command/Db/Studio.hs
Normal file
@ -0,0 +1,29 @@
|
||||
module Wasp.Cli.Command.Db.Studio
|
||||
( studio,
|
||||
)
|
||||
where
|
||||
|
||||
import Control.Concurrent (newChan)
|
||||
import Control.Concurrent.Async (concurrently)
|
||||
import Control.Monad.IO.Class (liftIO)
|
||||
import StrongPath ((</>))
|
||||
import Wasp.Cli.Command (Command)
|
||||
import Wasp.Cli.Command.Common (findWaspProjectRootDirFromCwd)
|
||||
import Wasp.Cli.Command.Message (cliSendMessageC)
|
||||
import qualified Wasp.Cli.Common as Common
|
||||
import Wasp.Generator.DbGenerator.Jobs (runStudio)
|
||||
import Wasp.Generator.Job.IO (readJobMessagesAndPrintThemPrefixed)
|
||||
import qualified Wasp.Message as Msg
|
||||
|
||||
studio :: Command ()
|
||||
studio = do
|
||||
waspProjectDir <- findWaspProjectRootDirFromCwd
|
||||
let genProjectDir =
|
||||
waspProjectDir </> Common.dotWaspDirInWaspProjectDir </> Common.generatedCodeDirInDotWaspDir
|
||||
|
||||
cliSendMessageC $ Msg.Start "Running studio..."
|
||||
|
||||
chan <- liftIO newChan
|
||||
_ <- liftIO $ readJobMessagesAndPrintThemPrefixed chan `concurrently` runStudio genProjectDir chan
|
||||
|
||||
error "This should never happen, studio should never stop."
|
@ -7,13 +7,15 @@ import Control.Monad.IO.Class (liftIO)
|
||||
import System.Environment (getExecutablePath)
|
||||
import Wasp.Cli.Command (Command)
|
||||
import Wasp.Cli.Command.Common (findWaspProjectRootDirFromCwd)
|
||||
import qualified Wasp.Lib as Lib
|
||||
import qualified Wasp.Project.Deployment
|
||||
|
||||
deploy :: [String] -> Command ()
|
||||
deploy cmdArgs = do
|
||||
waspProjectDir <- findWaspProjectRootDirFromCwd
|
||||
liftIO $ do
|
||||
-- `getExecutablePath` has some caveats: https://frasertweedale.github.io/blog-fp/posts/2022-05-10-improved-executable-path-queries.html
|
||||
-- Once we upgrade to GHC 9.4 we should change to `executablePath`, but this should be ok for our purposes.
|
||||
-- `getExecutablePath` has some caveats:
|
||||
-- https://frasertweedale.github.io/blog-fp/posts/2022-05-10-improved-executable-path-queries.html
|
||||
-- Once we upgrade to GHC 9.4 we should change to `executablePath`, but this should be ok for
|
||||
-- our purposes.
|
||||
waspExePath <- getExecutablePath
|
||||
Lib.deploy waspExePath waspProjectDir cmdArgs
|
||||
Wasp.Project.Deployment.deploy waspExePath waspProjectDir cmdArgs
|
||||
|
@ -14,7 +14,7 @@ import Wasp.Cli.Terminal (title)
|
||||
import qualified Wasp.Generator.NpmDependencies as N
|
||||
import qualified Wasp.Generator.ServerGenerator as ServerGenerator
|
||||
import qualified Wasp.Generator.WebAppGenerator as WebAppGenerator
|
||||
import Wasp.Lib (analyzeWaspProject)
|
||||
import Wasp.Project (analyzeWaspProject)
|
||||
import qualified Wasp.Util.Terminal as Term
|
||||
|
||||
deps :: Command ()
|
||||
|
@ -9,7 +9,7 @@ import qualified Data.Text.IO as T.IO
|
||||
import Wasp.Cli.Command (Command, CommandError (..))
|
||||
import Wasp.Cli.Command.Common (findWaspProjectRootDirFromCwd)
|
||||
import Wasp.Cli.Command.Compile (defaultCompileOptions)
|
||||
import Wasp.Lib (compileAndRenderDockerfile)
|
||||
import Wasp.Project (compileAndRenderDockerfile)
|
||||
|
||||
printDockerfile :: Command ()
|
||||
printDockerfile = do
|
||||
|
@ -5,45 +5,43 @@ module Wasp.Cli.Command.Info
|
||||
)
|
||||
where
|
||||
|
||||
import Control.Arrow
|
||||
import Control.Monad.Except
|
||||
import Control.Arrow ()
|
||||
import Control.Monad.Except (MonadIO (liftIO))
|
||||
import StrongPath (Abs, Dir, Path', fromRelFile)
|
||||
import StrongPath.Operations
|
||||
import StrongPath.Operations ()
|
||||
import System.Directory (getFileSize)
|
||||
import qualified Wasp.Analyzer as Analyzer
|
||||
import qualified Wasp.AppSpec.App as AS.App
|
||||
import qualified Wasp.AppSpec.Core.Decl as AS (Decl, takeDecls)
|
||||
import qualified Wasp.AppSpec.Valid as ASV
|
||||
import Wasp.Cli.Command (Command)
|
||||
import Wasp.Cli.Command.Common (findWaspProjectRootDirFromCwd)
|
||||
import Wasp.Cli.Command.Common (findWaspProjectRootDirFromCwd, readWaspCompileInfo)
|
||||
import Wasp.Cli.Command.Compile (analyze)
|
||||
import Wasp.Cli.Command.Message (cliSendMessageC)
|
||||
import qualified Wasp.Cli.Common as Cli.Common
|
||||
import Wasp.Cli.Command.Start.Db (getDbSystem)
|
||||
import Wasp.Cli.Terminal (title)
|
||||
import Wasp.Common (WaspProjectDir)
|
||||
import Wasp.Error (showCompilerErrorForTerminal)
|
||||
import Wasp.Lib (findWaspFile)
|
||||
import qualified Wasp.Message as Msg
|
||||
import Wasp.Util (ifM)
|
||||
import Wasp.Project (WaspProjectDir)
|
||||
import qualified Wasp.Util.IO as IOUtil
|
||||
import qualified Wasp.Util.Terminal as Term
|
||||
|
||||
info :: Command ()
|
||||
info = do
|
||||
waspDir <- findWaspProjectRootDirFromCwd
|
||||
compileInfo <- liftIO $ readCompileInformation waspDir
|
||||
|
||||
compileInfo <- liftIO $ readWaspCompileInfo waspDir
|
||||
projectSize <- liftIO $ readDirectorySizeMB waspDir
|
||||
declsOrError <- liftIO $ parseWaspFile waspDir
|
||||
case declsOrError of
|
||||
Left err -> cliSendMessageC $ Msg.Failure "Info failed" err
|
||||
Right decls -> do
|
||||
cliSendMessageC $
|
||||
Msg.Info $
|
||||
unlines
|
||||
[ "",
|
||||
title "Project information",
|
||||
printInfo "Name" (fst $ head $ AS.takeDecls @AS.App.App decls),
|
||||
printInfo "Last compile" compileInfo,
|
||||
printInfo "Project size" projectSize
|
||||
]
|
||||
|
||||
appSpec <- analyze waspDir
|
||||
let (appName, app) = ASV.getApp appSpec
|
||||
|
||||
cliSendMessageC $
|
||||
Msg.Info $
|
||||
unlines
|
||||
[ "",
|
||||
title "Project information",
|
||||
printInfo "Name" appName,
|
||||
printInfo "Database system" $ show $ getDbSystem app,
|
||||
printInfo "Last compile" compileInfo,
|
||||
printInfo "Project dir size" projectSize
|
||||
]
|
||||
|
||||
printInfo :: String -> String -> String
|
||||
printInfo key value = Term.applyStyles [Term.Cyan] key ++ ": " <> Term.applyStyles [Term.White] value
|
||||
@ -52,26 +50,3 @@ readDirectorySizeMB :: Path' Abs (Dir WaspProjectDir) -> IO String
|
||||
readDirectorySizeMB path = (++ " MB") . show . (`div` 1000000) . sum <$> allFileSizes
|
||||
where
|
||||
allFileSizes = IOUtil.listDirectoryDeep path >>= mapM (getFileSize . fromRelFile)
|
||||
|
||||
readCompileInformation :: Path' Abs (Dir WaspProjectDir) -> IO String
|
||||
readCompileInformation waspDir =
|
||||
ifM
|
||||
(IOUtil.doesFileExist dotWaspInfoFile)
|
||||
(IOUtil.readFile dotWaspInfoFile)
|
||||
(return "No compile information found")
|
||||
where
|
||||
dotWaspInfoFile =
|
||||
waspDir </> Cli.Common.dotWaspDirInWaspProjectDir
|
||||
</> Cli.Common.generatedCodeDirInDotWaspDir
|
||||
</> Cli.Common.dotWaspInfoFileInGeneratedCodeDir
|
||||
|
||||
parseWaspFile :: Path' Abs (Dir WaspProjectDir) -> IO (Either String [AS.Decl])
|
||||
parseWaspFile waspDir = runExceptT $ do
|
||||
waspFile <- ExceptT $ findWaspFile waspDir
|
||||
waspStr <- liftIO $ IOUtil.readFile waspFile
|
||||
liftEither $ left (annotateErrorForCli waspFile waspStr) $ Analyzer.analyze waspStr
|
||||
where
|
||||
annotateErrorForCli waspFile waspStr =
|
||||
("Couldn't parse .wasp file:\n" ++)
|
||||
. showCompilerErrorForTerminal (waspFile, waspStr)
|
||||
. Analyzer.getErrorMessageAndCtx
|
||||
|
@ -15,9 +15,9 @@ import Wasp.Cli.Command.Compile (compile, printWarningsAndErrorsIfAny)
|
||||
import Wasp.Cli.Command.Message (cliSendMessageC)
|
||||
import Wasp.Cli.Command.Watch (watch)
|
||||
import qualified Wasp.Cli.Common as Common
|
||||
import Wasp.Lib (CompileError, CompileWarning)
|
||||
import qualified Wasp.Lib
|
||||
import qualified Wasp.Generator
|
||||
import qualified Wasp.Message as Msg
|
||||
import Wasp.Project (CompileError, CompileWarning)
|
||||
|
||||
-- | Does initial compile of wasp code and then runs the generated project.
|
||||
-- It also listens for any file changes and recompiles and restarts generated project accordingly.
|
||||
@ -41,7 +41,7 @@ start = do
|
||||
-- 'watch') once jobs from 'start' quiet down a bit.
|
||||
ongoingCompilationResultMVar <- newMVar (warnings, [])
|
||||
let watchWaspProjectSource = watch waspRoot outDir ongoingCompilationResultMVar
|
||||
let startGeneratedWebApp = Wasp.Lib.start outDir (onJobsQuietDown ongoingCompilationResultMVar)
|
||||
let startGeneratedWebApp = Wasp.Generator.start outDir (onJobsQuietDown ongoingCompilationResultMVar)
|
||||
-- In parallel:
|
||||
-- 1. watch for any changes in the Wasp project, be it users wasp code or users JS/HTML/...
|
||||
-- code. On any change, Wasp is recompiled (and generated code is re-generated).
|
||||
|
175
waspc/cli/src/Wasp/Cli/Command/Start/Db.hs
Normal file
175
waspc/cli/src/Wasp/Cli/Command/Start/Db.hs
Normal file
@ -0,0 +1,175 @@
|
||||
module Wasp.Cli.Command.Start.Db
|
||||
( start,
|
||||
getDbSystem,
|
||||
waspDevDbDockerVolumePrefix,
|
||||
)
|
||||
where
|
||||
|
||||
import Control.Monad (when)
|
||||
import qualified Control.Monad.Except as E
|
||||
import Control.Monad.IO.Class (liftIO)
|
||||
import Data.Maybe (fromMaybe, isJust)
|
||||
import StrongPath (Abs, Dir, File', Path', Rel, fromRelFile)
|
||||
import System.Environment (lookupEnv)
|
||||
import System.Process (callCommand)
|
||||
import Text.Printf (printf)
|
||||
import qualified Wasp.AppSpec as AS
|
||||
import qualified Wasp.AppSpec.App as AS.App
|
||||
import qualified Wasp.AppSpec.App.Db as AS.App.Db
|
||||
import qualified Wasp.AppSpec.Valid as ASV
|
||||
import Wasp.Cli.Command (Command, CommandError (CommandError))
|
||||
import Wasp.Cli.Command.Common (findWaspProjectRootDirFromCwd, throwIfExeIsNotAvailable)
|
||||
import Wasp.Cli.Command.Compile (analyze)
|
||||
import Wasp.Cli.Command.Message (cliSendMessageC)
|
||||
import Wasp.Cli.Common (WaspProjectDir)
|
||||
import qualified Wasp.Message as Msg
|
||||
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)
|
||||
import qualified Wasp.Util.Network.Socket as Socket
|
||||
|
||||
-- | Starts a "managed" dev database, where "managed" means that
|
||||
-- Wasp creates it and connects the Wasp app with it.
|
||||
-- Wasp is smart while doing this so it checks which database is specified
|
||||
-- in Wasp configuration and spins up a database of appropriate type.
|
||||
start :: Command ()
|
||||
start = do
|
||||
waspProjectDir <- findWaspProjectRootDirFromCwd
|
||||
appSpec <- analyze waspProjectDir
|
||||
|
||||
throwIfCustomDbAlreadyInUse appSpec
|
||||
|
||||
let (appName, app) = ASV.getApp appSpec
|
||||
case getDbSystem app of
|
||||
AS.App.Db.SQLite -> noteSQLiteDoesntNeedStart
|
||||
AS.App.Db.PostgreSQL -> startPostgreDevDb waspProjectDir appName
|
||||
where
|
||||
noteSQLiteDoesntNeedStart =
|
||||
cliSendMessageC . Msg.Info $
|
||||
"Nothing to do! You are all good, you are using SQLite which doesn't need to be started."
|
||||
|
||||
throwIfCustomDbAlreadyInUse :: AS.AppSpec -> Command ()
|
||||
throwIfCustomDbAlreadyInUse spec = do
|
||||
throwIfDbUrlInEnv
|
||||
throwIfDbUrlInServerDotEnv spec
|
||||
where
|
||||
throwIfDbUrlInEnv :: Command ()
|
||||
throwIfDbUrlInEnv = do
|
||||
dbUrl <- liftIO $ lookupEnv databaseUrlEnvVarName
|
||||
when (isJust dbUrl) $
|
||||
throwCustomDbAlreadyInUseError
|
||||
( "Wasp has detected existing " <> databaseUrlEnvVarName <> " var in your environment.\n"
|
||||
<> "To have Wasp run the dev database for you, make sure you remove that env var first."
|
||||
)
|
||||
|
||||
throwIfDbUrlInServerDotEnv :: AS.AppSpec -> Command ()
|
||||
throwIfDbUrlInServerDotEnv appSpec =
|
||||
when (isThereDbUrlInServerDotEnv appSpec) $
|
||||
throwCustomDbAlreadyInUseError
|
||||
( printf
|
||||
( "Wasp has detected that you have defined %s env var in your %s file.\n"
|
||||
<> "To have Wasp run the dev database for you, make sure you remove that env var first."
|
||||
)
|
||||
databaseUrlEnvVarName
|
||||
(fromRelFile (dotEnvServer :: Path' (Rel WaspProjectDir) File'))
|
||||
)
|
||||
where
|
||||
isThereDbUrlInServerDotEnv = any ((== databaseUrlEnvVarName) . fst) . AS.devEnvVarsServer
|
||||
|
||||
throwCustomDbAlreadyInUseError :: String -> Command ()
|
||||
throwCustomDbAlreadyInUseError msg =
|
||||
E.throwError $ CommandError "You are using custom database already" msg
|
||||
|
||||
getDbSystem :: AS.App.App -> AS.App.Db.DbSystem
|
||||
getDbSystem app =
|
||||
fromMaybe AS.App.Db.SQLite (AS.App.db app >>= AS.App.Db.system)
|
||||
|
||||
startPostgreDevDb :: Path' Abs (Dir WaspProjectDir) -> String -> Command ()
|
||||
startPostgreDevDb waspProjectDir appName = do
|
||||
throwIfExeIsNotAvailable
|
||||
"docker"
|
||||
"To run PostgreSQL dev database, Wasp needs `docker` installed and in PATH."
|
||||
throwIfDevDbPortIsAlreadyInUse
|
||||
|
||||
cliSendMessageC . Msg.Info $
|
||||
unlines
|
||||
[ "✨ Starting a PostgreSQL dev database (based on your Wasp config) ✨",
|
||||
"",
|
||||
"Additional info:",
|
||||
" ℹ Connection URL, in case you might want to connect with external tools:",
|
||||
" " <> connectionUrl,
|
||||
" ℹ Database data is persisted in a docker volume with the following name"
|
||||
<> " (useful to know if you will want to delete it at some point):",
|
||||
" " <> dockerVolumeName
|
||||
]
|
||||
|
||||
-- NOTE: POSTGRES_PASSWORD, POSTGRES_USER, POSTGRES_DB below are really used by the docker image
|
||||
-- only when initializing the database -> if it already exists, they will be ignored.
|
||||
-- This is how the postgres Docker image works.
|
||||
let command =
|
||||
printf
|
||||
( unwords
|
||||
[ "docker run",
|
||||
"--name %s",
|
||||
"--rm",
|
||||
"--publish %d:5432",
|
||||
"-v %s:/var/lib/postgresql/data",
|
||||
"--env POSTGRES_PASSWORD=%s",
|
||||
"--env POSTGRES_USER=%s",
|
||||
"--env POSTGRES_DB=%s",
|
||||
"postgres"
|
||||
]
|
||||
)
|
||||
dockerContainerName
|
||||
Dev.Postgres.defaultDevPort
|
||||
dockerVolumeName
|
||||
Dev.Postgres.defaultDevPass
|
||||
Dev.Postgres.defaultDevUser
|
||||
dbName
|
||||
liftIO $ callCommand command
|
||||
where
|
||||
dockerVolumeName = makeWaspDevDbDockerVolumeName waspProjectDir appName
|
||||
dockerContainerName = makeWaspDevDbDockerContainerName waspProjectDir appName
|
||||
dbName = Dev.Postgres.makeDevDbName waspProjectDir appName
|
||||
connectionUrl = Dev.Postgres.makeDevConnectionUrl waspProjectDir appName
|
||||
|
||||
throwIfDevDbPortIsAlreadyInUse :: Command ()
|
||||
throwIfDevDbPortIsAlreadyInUse = do
|
||||
-- I am checking both conditions because of Docker having virtual network on Mac which
|
||||
-- always gives precedence to native ports so checking only if we can open the port is
|
||||
-- not enough because we can open it even if Docker container is already bound to that port.
|
||||
whenM (liftIO $ Socket.checkIfPortIsInUse devDbSocketAddress) throwPortAlreadyInUseError
|
||||
whenM (liftIO $ Socket.checkIfPortIsAcceptingConnections devDbSocketAddress) throwPortAlreadyInUseError
|
||||
where
|
||||
devDbSocketAddress = Socket.makeLocalHostSocketAddress $ fromIntegral Dev.Postgres.defaultDevPort
|
||||
throwPortAlreadyInUseError =
|
||||
E.throwError $
|
||||
CommandError
|
||||
"Port already in use"
|
||||
( printf
|
||||
"Wasp can't run PostgreSQL dev database for you since port %d is already in use."
|
||||
Dev.Postgres.defaultDevPort
|
||||
)
|
||||
|
||||
-- | Docker volume name unique for the Wasp project with specified path and name.
|
||||
makeWaspDevDbDockerVolumeName :: Path' Abs (Dir WaspProjectDir) -> String -> String
|
||||
makeWaspDevDbDockerVolumeName waspProjectDir appName =
|
||||
take maxDockerVolumeNameLength $
|
||||
waspDevDbDockerVolumePrefix <> "-" <> makeDevDbUniqueId waspProjectDir appName
|
||||
|
||||
waspDevDbDockerVolumePrefix :: String
|
||||
waspDevDbDockerVolumePrefix = "wasp-dev-db"
|
||||
|
||||
maxDockerVolumeNameLength :: Int
|
||||
maxDockerVolumeNameLength = 255
|
||||
|
||||
-- | Docker container name unique for the Wasp project with specified path and name.
|
||||
makeWaspDevDbDockerContainerName :: Path' Abs (Dir WaspProjectDir) -> String -> String
|
||||
makeWaspDevDbDockerContainerName waspProjectDir appName =
|
||||
take maxDockerContainerNameLength $
|
||||
waspDevDbDockerVolumePrefix <> "-" <> makeDevDbUniqueId waspProjectDir appName
|
||||
|
||||
maxDockerContainerNameLength :: Int
|
||||
maxDockerContainerNameLength = 63
|
@ -9,6 +9,7 @@ import StrongPath (fromAbsDir, fromAbsFile, (</>))
|
||||
import System.Exit (die)
|
||||
import Wasp.Cli.Command (Command)
|
||||
import Wasp.Cli.Command.Message (cliSendMessageC)
|
||||
import Wasp.Cli.Command.Start.Db (waspDevDbDockerVolumePrefix)
|
||||
import Wasp.Cli.FileSystem
|
||||
( getHomeDir,
|
||||
getUserCacheDir,
|
||||
@ -24,7 +25,12 @@ uninstall :: Command ()
|
||||
uninstall = do
|
||||
cliSendMessageC $ Msg.Start "Uninstalling Wasp ..."
|
||||
liftIO removeWaspFiles
|
||||
cliSendMessageC $ Msg.Success "Uninstalled Wasp"
|
||||
cliSendMessageC $ Msg.Success "Uninstalled Wasp."
|
||||
cliSendMessageC $
|
||||
Msg.Info $
|
||||
"If you have used Wasp to run dev database for you, you might want to make sure you also"
|
||||
<> " deleted all the docker volumes it might have created."
|
||||
<> (" You can easily list them by doing `docker volume ls | grep " <> waspDevDbDockerVolumePrefix <> "`.")
|
||||
|
||||
removeWaspFiles :: IO ()
|
||||
removeWaspFiles = do
|
||||
|
@ -17,9 +17,9 @@ import qualified System.FilePath as FP
|
||||
import Wasp.Cli.Command.Compile (compileIO, printCompilationResult)
|
||||
import qualified Wasp.Cli.Common as Common
|
||||
import Wasp.Cli.Message (cliSendMessage)
|
||||
import Wasp.Lib (CompileError, CompileWarning)
|
||||
import qualified Wasp.Lib
|
||||
import qualified Wasp.Generator.Common as Wasp.Generator
|
||||
import qualified Wasp.Message as Msg
|
||||
import Wasp.Project (CompileError, CompileWarning, WaspProjectDir)
|
||||
|
||||
-- TODO: Idea: Read .gitignore file, and ignore everything from it. This will then also cover the
|
||||
-- .wasp dir, and users can easily add any custom stuff they want ignored. But, we also have to
|
||||
@ -32,8 +32,8 @@ import qualified Wasp.Message as Msg
|
||||
-- (warnings, errors) of the latest (re)compile whenever it happens. If there is already
|
||||
-- something in the MVar, it will get overwritten.
|
||||
watch ::
|
||||
Path' Abs (Dir Common.WaspProjectDir) ->
|
||||
Path' Abs (Dir Wasp.Lib.ProjectRootDir) ->
|
||||
Path' Abs (Dir WaspProjectDir) ->
|
||||
Path' Abs (Dir Wasp.Generator.ProjectRootDir) ->
|
||||
MVar ([CompileWarning], [CompileError]) ->
|
||||
IO ()
|
||||
watch waspProjectDir outDir ongoingCompilationResultMVar = FSN.withManager $ \mgr -> do
|
||||
|
@ -18,8 +18,8 @@ where
|
||||
|
||||
import StrongPath (Dir, File', Path', Rel, reldir, relfile)
|
||||
import Wasp.AppSpec.ExternalCode (SourceExternalCodeDir)
|
||||
import Wasp.Common (WaspProjectDir)
|
||||
import qualified Wasp.Generator.Common
|
||||
import Wasp.Project (WaspProjectDir)
|
||||
import qualified Wasp.Util.Terminal as Term
|
||||
|
||||
data DotWaspDir -- Here we put everything that wasp generates.
|
||||
|
@ -14,7 +14,7 @@ const config = {
|
||||
all: {
|
||||
env,
|
||||
port: parseInt(process.env.PORT) || 3001,
|
||||
databaseUrl: process.env.{= databaseUrlEnvVar =},
|
||||
databaseUrl: process.env.{= databaseUrlEnvVarName =},
|
||||
frontendUrl: undefined,
|
||||
allowedCORSOrigins: [],
|
||||
{=# isAuthEnabled =}
|
||||
|
@ -12,4 +12,4 @@ constraints:
|
||||
prune-juice ==0.7
|
||||
|
||||
-- See cabal.project.
|
||||
index-state: 2022-03-22T14:16:26Z
|
||||
index-state: 2023-03-09T09:15:49Z
|
||||
|
@ -89,7 +89,14 @@ runGoldenTest goldenTest = do
|
||||
|
||||
isTestOutputFileTestable :: FilePath -> Bool
|
||||
isTestOutputFileTestable fp =
|
||||
takeFileName fp `notElem` [".waspinfo", "node_modules", "dev.db", "dev.db-journal", "package-lock.json", ".gitignore"]
|
||||
takeFileName fp
|
||||
`notElem` [ ".waspinfo",
|
||||
"node_modules",
|
||||
"dev.db",
|
||||
"dev.db-journal",
|
||||
"package-lock.json",
|
||||
".gitignore"
|
||||
]
|
||||
|
||||
writeFileManifest :: String -> [FilePath] -> FilePath -> IO ()
|
||||
writeFileManifest baseAbsFp filePaths manifestAbsFp = do
|
||||
|
@ -13,6 +13,7 @@ import ShellCommands
|
||||
waspCliNew,
|
||||
)
|
||||
import Util ((<++>))
|
||||
import Wasp.Project.Db (databaseUrlEnvVarName)
|
||||
|
||||
waspComplexTest :: GoldenTest
|
||||
waspComplexTest = do
|
||||
@ -120,7 +121,15 @@ addServerEnvFile = do
|
||||
envFileContents =
|
||||
unlines
|
||||
[ "GOOGLE_CLIENT_ID=google_client_id",
|
||||
"GOOGLE_CLIENT_SECRET=google_client_secret"
|
||||
"GOOGLE_CLIENT_SECRET=google_client_secret",
|
||||
-- NOTE: Since we are using PSQL in this test, if we don't set custom
|
||||
-- database url in server/.env, Wasp will set its own, for managed dev db.
|
||||
-- That is problematic because Wasp's db url depends on project's abs path,
|
||||
-- which is not something we have constant during e2e tests, it depends
|
||||
-- on the location where the tests are being run.
|
||||
-- Therefore, we make sure to set custom database url here, to avoid .env
|
||||
-- changing between different machines / setups.
|
||||
databaseUrlEnvVarName <> "=" <> "mock-database-url"
|
||||
]
|
||||
|
||||
addGoogleAuth :: ShellCommandBuilder [ShellCommand]
|
||||
|
@ -2,13 +2,17 @@ module Tests.WaspJobTest (waspJob) where
|
||||
|
||||
import GoldenTest (GoldenTest, makeGoldenTest)
|
||||
import ShellCommands
|
||||
( appendToWaspFile,
|
||||
( ShellCommand,
|
||||
ShellCommandBuilder,
|
||||
appendToWaspFile,
|
||||
cdIntoCurrentProject,
|
||||
createFile,
|
||||
setDbToPSQL,
|
||||
waspCliCompile,
|
||||
waspCliNew,
|
||||
)
|
||||
import Util ((<++>))
|
||||
import Wasp.Project.Db (databaseUrlEnvVarName)
|
||||
|
||||
waspJob :: GoldenTest
|
||||
waspJob = do
|
||||
@ -31,6 +35,25 @@ waspJob = do
|
||||
cdIntoCurrentProject,
|
||||
setDbToPSQL,
|
||||
appendToWaspFile jobDecl,
|
||||
createFile jobFile "./src/server/jobs" "bar.js",
|
||||
waspCliCompile
|
||||
createFile jobFile "./src/server/jobs" "bar.js"
|
||||
]
|
||||
<++> addServerEnvFile
|
||||
<++> sequence
|
||||
[ waspCliCompile
|
||||
]
|
||||
|
||||
addServerEnvFile :: ShellCommandBuilder [ShellCommand]
|
||||
addServerEnvFile = do
|
||||
sequence [createFile envFileContents "./" ".env.server"]
|
||||
where
|
||||
envFileContents =
|
||||
unlines
|
||||
[ -- NOTE: Since we are using PSQL in this test, if we don't set custom
|
||||
-- database url in server/.env, Wasp will set its own, for managed dev db.
|
||||
-- That is problematic because Wasp's db url depends on project's abs path,
|
||||
-- which is not something we have constant during e2e tests, it depends
|
||||
-- on the location where the tests are being run.
|
||||
-- Therefore, we make sure to set custom database url here, to avoid .env
|
||||
-- changing between different machines / setups.
|
||||
databaseUrlEnvVarName <> "=" <> "mock-database-url"
|
||||
]
|
||||
|
@ -1,7 +1,7 @@
|
||||
app waspBuild {
|
||||
db: { system: PostgreSQL },
|
||||
wasp: {
|
||||
version: "^0.9.0"
|
||||
version: "^0.9.1"
|
||||
},
|
||||
title: "waspBuild"
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ waspCompile/.wasp/out/.waspchecksums
|
||||
waspCompile/.wasp/out/Dockerfile
|
||||
waspCompile/.wasp/out/db/schema.prisma
|
||||
waspCompile/.wasp/out/installedFullStackNpmDependencies.json
|
||||
waspCompile/.wasp/out/server/.env
|
||||
waspCompile/.wasp/out/server/.npmrc
|
||||
waspCompile/.wasp/out/server/README.md
|
||||
waspCompile/.wasp/out/server/nodemon.json
|
||||
@ -32,6 +33,7 @@ waspCompile/.wasp/out/server/src/types/index.ts
|
||||
waspCompile/.wasp/out/server/src/universal/url.ts
|
||||
waspCompile/.wasp/out/server/src/utils.js
|
||||
waspCompile/.wasp/out/server/tsconfig.json
|
||||
waspCompile/.wasp/out/web-app/.env
|
||||
waspCompile/.wasp/out/web-app/.npmrc
|
||||
waspCompile/.wasp/out/web-app/README.md
|
||||
waspCompile/.wasp/out/web-app/index.html
|
||||
|
@ -20,6 +20,13 @@
|
||||
],
|
||||
"93f3b154b04fce7819e24aeb1691cd1c78f731f41a2f9e0213d54ef783f2bc38"
|
||||
],
|
||||
[
|
||||
[
|
||||
"file",
|
||||
"server/.env"
|
||||
],
|
||||
"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||
],
|
||||
[
|
||||
[
|
||||
"file",
|
||||
@ -230,6 +237,13 @@
|
||||
],
|
||||
"f2632965c1e3678fcc0e63b83d7e33fea1a9008ef5fd5a2f5e7bf278337c3e02"
|
||||
],
|
||||
[
|
||||
[
|
||||
"file",
|
||||
"web-app/.env"
|
||||
],
|
||||
"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||
],
|
||||
[
|
||||
[
|
||||
"file",
|
||||
|
0
waspc/e2e-test/test-outputs/waspCompile-golden/waspCompile/.wasp/out/server/.env
generated
Normal file
0
waspc/e2e-test/test-outputs/waspCompile-golden/waspCompile/.wasp/out/server/.env
generated
Normal file
0
waspc/e2e-test/test-outputs/waspCompile-golden/waspCompile/.wasp/out/web-app/.env
generated
Normal file
0
waspc/e2e-test/test-outputs/waspCompile-golden/waspCompile/.wasp/out/web-app/.env
generated
Normal file
@ -1,6 +1,6 @@
|
||||
app waspCompile {
|
||||
wasp: {
|
||||
version: "^0.9.0"
|
||||
version: "^0.9.1"
|
||||
},
|
||||
title: "waspCompile"
|
||||
}
|
||||
|
@ -61,6 +61,7 @@ waspComplexTest/.wasp/out/server/src/types/index.ts
|
||||
waspComplexTest/.wasp/out/server/src/universal/url.ts
|
||||
waspComplexTest/.wasp/out/server/src/utils.js
|
||||
waspComplexTest/.wasp/out/server/tsconfig.json
|
||||
waspComplexTest/.wasp/out/web-app/.env
|
||||
waspComplexTest/.wasp/out/web-app/.npmrc
|
||||
waspComplexTest/.wasp/out/web-app/README.md
|
||||
waspComplexTest/.wasp/out/web-app/index.html
|
||||
|
@ -1,3 +1,4 @@
|
||||
GOOGLE_CLIENT_ID=google_client_id
|
||||
GOOGLE_CLIENT_SECRET=google_client_secret
|
||||
DATABASE_URL=mock-database-url
|
||||
|
||||
|
@ -25,7 +25,7 @@
|
||||
"file",
|
||||
"server/.env"
|
||||
],
|
||||
"7868f4dd9dc579bb21167f4e4cd5caa2960bf44ebe7d3f9f27a2f9380491d31f"
|
||||
"368bf0f88f43e6cc3083d535f348905e51a1d3747abb479749dc250253a0c042"
|
||||
],
|
||||
[
|
||||
[
|
||||
@ -412,6 +412,13 @@
|
||||
],
|
||||
"f2632965c1e3678fcc0e63b83d7e33fea1a9008ef5fd5a2f5e7bf278337c3e02"
|
||||
],
|
||||
[
|
||||
[
|
||||
"file",
|
||||
"web-app/.env"
|
||||
],
|
||||
"368bf0f88f43e6cc3083d535f348905e51a1d3747abb479749dc250253a0c042"
|
||||
],
|
||||
[
|
||||
[
|
||||
"file",
|
||||
|
@ -1,3 +1,3 @@
|
||||
GOOGLE_CLIENT_ID=google_client_id
|
||||
GOOGLE_CLIENT_SECRET=google_client_secret
|
||||
|
||||
GOOGLE_CLIENT_ID="google_client_id"
|
||||
GOOGLE_CLIENT_SECRET="google_client_secret"
|
||||
DATABASE_URL="mock-database-url"
|
3
waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/web-app/.env
generated
Normal file
3
waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/web-app/.env
generated
Normal file
@ -0,0 +1,3 @@
|
||||
GOOGLE_CLIENT_ID="google_client_id"
|
||||
GOOGLE_CLIENT_SECRET="google_client_secret"
|
||||
DATABASE_URL="mock-database-url"
|
@ -1,7 +1,7 @@
|
||||
app waspComplexTest {
|
||||
db: { system: PostgreSQL },
|
||||
wasp: {
|
||||
version: "^0.9.0"
|
||||
version: "^0.9.1"
|
||||
},
|
||||
auth: {
|
||||
userEntity: User,
|
||||
|
@ -1,8 +1,10 @@
|
||||
waspJob/.env.server
|
||||
waspJob/.wasp/out/.dockerignore
|
||||
waspJob/.wasp/out/.waspchecksums
|
||||
waspJob/.wasp/out/Dockerfile
|
||||
waspJob/.wasp/out/db/schema.prisma
|
||||
waspJob/.wasp/out/installedFullStackNpmDependencies.json
|
||||
waspJob/.wasp/out/server/.env
|
||||
waspJob/.wasp/out/server/.npmrc
|
||||
waspJob/.wasp/out/server/README.md
|
||||
waspJob/.wasp/out/server/nodemon.json
|
||||
@ -34,6 +36,7 @@ waspJob/.wasp/out/server/src/types/index.ts
|
||||
waspJob/.wasp/out/server/src/universal/url.ts
|
||||
waspJob/.wasp/out/server/src/utils.js
|
||||
waspJob/.wasp/out/server/tsconfig.json
|
||||
waspJob/.wasp/out/web-app/.env
|
||||
waspJob/.wasp/out/web-app/.npmrc
|
||||
waspJob/.wasp/out/web-app/README.md
|
||||
waspJob/.wasp/out/web-app/index.html
|
||||
|
2
waspc/e2e-test/test-outputs/waspJob-golden/waspJob/.env.server
generated
Normal file
2
waspc/e2e-test/test-outputs/waspJob-golden/waspJob/.env.server
generated
Normal file
@ -0,0 +1,2 @@
|
||||
DATABASE_URL=mock-database-url
|
||||
|
@ -20,6 +20,13 @@
|
||||
],
|
||||
"6f7b1b109e332bad9eb3cda4a2caf4963f4918c91b546c06fa42d8986c0b94a2"
|
||||
],
|
||||
[
|
||||
[
|
||||
"file",
|
||||
"server/.env"
|
||||
],
|
||||
"b267ea48e5ff257c153b4f84102104eec7af8a46827aae6e76fc42c83934cfc0"
|
||||
],
|
||||
[
|
||||
[
|
||||
"file",
|
||||
@ -244,6 +251,13 @@
|
||||
],
|
||||
"f2632965c1e3678fcc0e63b83d7e33fea1a9008ef5fd5a2f5e7bf278337c3e02"
|
||||
],
|
||||
[
|
||||
[
|
||||
"file",
|
||||
"web-app/.env"
|
||||
],
|
||||
"b267ea48e5ff257c153b4f84102104eec7af8a46827aae6e76fc42c83934cfc0"
|
||||
],
|
||||
[
|
||||
[
|
||||
"file",
|
||||
|
1
waspc/e2e-test/test-outputs/waspJob-golden/waspJob/.wasp/out/server/.env
generated
Normal file
1
waspc/e2e-test/test-outputs/waspJob-golden/waspJob/.wasp/out/server/.env
generated
Normal file
@ -0,0 +1 @@
|
||||
DATABASE_URL="mock-database-url"
|
1
waspc/e2e-test/test-outputs/waspJob-golden/waspJob/.wasp/out/web-app/.env
generated
Normal file
1
waspc/e2e-test/test-outputs/waspJob-golden/waspJob/.wasp/out/web-app/.env
generated
Normal file
@ -0,0 +1 @@
|
||||
DATABASE_URL="mock-database-url"
|
@ -1,7 +1,7 @@
|
||||
app waspJob {
|
||||
db: { system: PostgreSQL },
|
||||
wasp: {
|
||||
version: "^0.9.0"
|
||||
version: "^0.9.1"
|
||||
},
|
||||
title: "waspJob"
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ waspMigrate/.wasp/out/db/schema.prisma
|
||||
waspMigrate/.wasp/out/db/schema.prisma.wasp-generate-checksum
|
||||
waspMigrate/.wasp/out/db/schema.prisma.wasp-last-db-concurrence-checksum
|
||||
waspMigrate/.wasp/out/installedFullStackNpmDependencies.json
|
||||
waspMigrate/.wasp/out/server/.env
|
||||
waspMigrate/.wasp/out/server/.npmrc
|
||||
waspMigrate/.wasp/out/server/README.md
|
||||
waspMigrate/.wasp/out/server/nodemon.json
|
||||
@ -37,6 +38,7 @@ waspMigrate/.wasp/out/server/src/types/index.ts
|
||||
waspMigrate/.wasp/out/server/src/universal/url.ts
|
||||
waspMigrate/.wasp/out/server/src/utils.js
|
||||
waspMigrate/.wasp/out/server/tsconfig.json
|
||||
waspMigrate/.wasp/out/web-app/.env
|
||||
waspMigrate/.wasp/out/web-app/.npmrc
|
||||
waspMigrate/.wasp/out/web-app/README.md
|
||||
waspMigrate/.wasp/out/web-app/index.html
|
||||
|
@ -20,6 +20,13 @@
|
||||
],
|
||||
"8d017edd849a861ae086850270a9f817bb4b75d9ee9ac27c08b0e9c29a16f6fe"
|
||||
],
|
||||
[
|
||||
[
|
||||
"file",
|
||||
"server/.env"
|
||||
],
|
||||
"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||
],
|
||||
[
|
||||
[
|
||||
"file",
|
||||
@ -230,6 +237,13 @@
|
||||
],
|
||||
"f2632965c1e3678fcc0e63b83d7e33fea1a9008ef5fd5a2f5e7bf278337c3e02"
|
||||
],
|
||||
[
|
||||
[
|
||||
"file",
|
||||
"web-app/.env"
|
||||
],
|
||||
"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||
],
|
||||
[
|
||||
[
|
||||
"file",
|
||||
|
0
waspc/e2e-test/test-outputs/waspMigrate-golden/waspMigrate/.wasp/out/server/.env
generated
Normal file
0
waspc/e2e-test/test-outputs/waspMigrate-golden/waspMigrate/.wasp/out/server/.env
generated
Normal file
0
waspc/e2e-test/test-outputs/waspMigrate-golden/waspMigrate/.wasp/out/web-app/.env
generated
Normal file
0
waspc/e2e-test/test-outputs/waspMigrate-golden/waspMigrate/.wasp/out/web-app/.env
generated
Normal file
@ -1,6 +1,6 @@
|
||||
app waspMigrate {
|
||||
wasp: {
|
||||
version: "^0.9.0"
|
||||
version: "^0.9.1"
|
||||
},
|
||||
title: "waspMigrate"
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
app waspNew {
|
||||
wasp: {
|
||||
version: "^0.9.0"
|
||||
version: "^0.9.1"
|
||||
},
|
||||
title: "waspNew"
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
app todoApp {
|
||||
wasp: {
|
||||
version: "^0.9.0"
|
||||
version: "^0.9.1"
|
||||
},
|
||||
title: "ToDo App",
|
||||
// head: [],
|
||||
|
@ -1,3 +1,5 @@
|
||||
{-# LANGUAGE TypeApplications #-}
|
||||
|
||||
module Wasp.AppSpec
|
||||
( AppSpec (..),
|
||||
Decl,
|
||||
@ -15,6 +17,7 @@ module Wasp.AppSpec
|
||||
resolveRef,
|
||||
doesConfigFileExist,
|
||||
asAbsWaspProjectDirFile,
|
||||
getApp,
|
||||
)
|
||||
where
|
||||
|
||||
@ -24,6 +27,7 @@ import Data.Text (Text)
|
||||
import StrongPath (Abs, Dir, File', Path', Rel, (</>))
|
||||
import Wasp.AppSpec.Action (Action)
|
||||
import Wasp.AppSpec.Api (Api)
|
||||
import Wasp.AppSpec.App (App)
|
||||
import Wasp.AppSpec.ConfigFile (ConfigFileRelocator (..))
|
||||
import Wasp.AppSpec.Core.Decl (Decl, IsDecl, takeDecls)
|
||||
import Wasp.AppSpec.Core.Ref (Ref, refName)
|
||||
@ -33,7 +37,9 @@ import Wasp.AppSpec.Job (Job)
|
||||
import Wasp.AppSpec.Page (Page)
|
||||
import Wasp.AppSpec.Query (Query)
|
||||
import Wasp.AppSpec.Route (Route)
|
||||
import Wasp.Common (DbMigrationsDir, WaspProjectDir)
|
||||
import Wasp.Env (EnvVar)
|
||||
import Wasp.Project.Common (WaspProjectDir)
|
||||
import Wasp.Project.Db.Migrations (DbMigrationsDir)
|
||||
|
||||
-- | AppSpec is the main/central intermediate representation (IR) of the whole Wasp compiler,
|
||||
-- describing the web app specification with all the details needed to generate it.
|
||||
@ -52,19 +58,20 @@ data AppSpec = AppSpec
|
||||
externalSharedFiles :: [ExternalCode.File],
|
||||
-- | Absolute path to the directory in wasp project source that contains external code files.
|
||||
migrationsDir :: Maybe (Path' Abs (Dir DbMigrationsDir)),
|
||||
-- | Absolute path to the .env.server file in wasp project source. It contains env variables to be
|
||||
-- provided to the server only during the development.
|
||||
dotEnvServerFile :: Maybe (Path' Abs File'),
|
||||
-- | Absolute path to the .env.client file in wasp project source. It contains env variables to be
|
||||
-- provided to the client only during the development.
|
||||
dotEnvClientFile :: Maybe (Path' Abs File'),
|
||||
-- | Env variables to be provided to the server only during the development.
|
||||
devEnvVarsServer :: [EnvVar],
|
||||
-- | Env variables to be provided to the client only during the development.
|
||||
devEnvVarsClient :: [EnvVar],
|
||||
-- | If true, it means project is being compiled for production/deployment -> it is being "built".
|
||||
-- If false, it means project is being compiled for development purposes (e.g. "wasp start").
|
||||
isBuild :: Bool,
|
||||
-- | The contents of the optional user Dockerfile found in the root of the wasp project source.
|
||||
userDockerfileContents :: Maybe Text,
|
||||
-- | A list of paths to any config files found (e.g., tailwind.config.cjs) and where to copy them.
|
||||
configFiles :: [ConfigFileRelocator]
|
||||
configFiles :: [ConfigFileRelocator],
|
||||
-- | Connection URL for a database used during development. If provided, generated app will
|
||||
-- make sure to use it when run in development mode.
|
||||
devDatabaseUrl :: Maybe String
|
||||
}
|
||||
|
||||
-- TODO: Make this return "Named" declarations?
|
||||
@ -96,6 +103,11 @@ getRoutes = getDecls
|
||||
getJobs :: AppSpec -> [(String, Job)]
|
||||
getJobs = getDecls
|
||||
|
||||
getApp :: [Decl] -> Maybe (String, App)
|
||||
getApp dcls = case takeDecls @App dcls of
|
||||
[] -> Nothing
|
||||
apps -> Just $ head apps
|
||||
|
||||
resolveRef :: (IsDecl d) => AppSpec -> Ref d -> (String, d)
|
||||
resolveRef spec ref =
|
||||
fromMaybe
|
||||
|
@ -4,8 +4,8 @@ module Wasp.AppSpec.ConfigFile
|
||||
where
|
||||
|
||||
import StrongPath (File', Path', Rel)
|
||||
import Wasp.Common (WaspProjectDir)
|
||||
import Wasp.Generator.Common (ProjectRootDir)
|
||||
import Wasp.Project.Common (WaspProjectDir)
|
||||
|
||||
-- | A type for establishing the mapping of where to copy config files from/to.
|
||||
data ConfigFileRelocator = ConfigFileRelocator
|
||||
|
@ -186,6 +186,10 @@ getApp spec = case takeDecls @App (AS.decls spec) of
|
||||
isAuthEnabled :: AppSpec -> Bool
|
||||
isAuthEnabled spec = isJust (App.auth $ snd $ getApp spec)
|
||||
|
||||
-- | This function assumes that @AppSpec@ it operates on was validated beforehand (with @validateAppSpec@ function).
|
||||
getDbSystem :: AppSpec -> Maybe AS.Db.DbSystem
|
||||
getDbSystem spec = AS.Db.system =<< AS.App.db (snd $ getApp spec)
|
||||
|
||||
-- | This function assumes that @AppSpec@ it operates on was validated beforehand (with @validateAppSpec@ function).
|
||||
isPostgresUsed :: AppSpec -> Bool
|
||||
isPostgresUsed spec = Just AS.Db.PostgreSQL == (AS.Db.system =<< AS.App.db (snd $ getApp spec))
|
||||
isPostgresUsed = (Just AS.Db.PostgreSQL ==) . getDbSystem
|
||||
|
@ -1,15 +0,0 @@
|
||||
module Wasp.Common
|
||||
( DbMigrationsDir,
|
||||
WaspProjectDir,
|
||||
dbMigrationsDirInWaspProjectDir,
|
||||
)
|
||||
where
|
||||
|
||||
import StrongPath (Dir, Path', Rel, reldir)
|
||||
|
||||
data WaspProjectDir -- Root dir of Wasp project, containing source files.
|
||||
|
||||
data DbMigrationsDir
|
||||
|
||||
dbMigrationsDirInWaspProjectDir :: Path' (Rel WaspProjectDir) (Dir DbMigrationsDir)
|
||||
dbMigrationsDirInWaspProjectDir = [reldir|migrations|]
|
@ -8,8 +8,8 @@ import qualified Data.Map as Data
|
||||
import Data.Maybe (mapMaybe)
|
||||
import StrongPath (Abs, Dir, File', Path', Rel)
|
||||
import Wasp.AppSpec.ConfigFile (ConfigFileRelocator (..))
|
||||
import Wasp.Common (WaspProjectDir)
|
||||
import Wasp.Generator.Common (ProjectRootDir)
|
||||
import Wasp.Project.Common (WaspProjectDir)
|
||||
import qualified Wasp.Util.IO as Util.IO
|
||||
|
||||
type ConfigFileRelocationMap = Data.Map (Path' (Rel WaspProjectDir) File') (Path' (Rel ProjectRootDir) File')
|
||||
|
15
waspc/src/Wasp/Db/Postgres.hs
Normal file
15
waspc/src/Wasp/Db/Postgres.hs
Normal file
@ -0,0 +1,15 @@
|
||||
module Wasp.Db.Postgres
|
||||
( makeConnectionUrl,
|
||||
postgresMaxDbNameLength,
|
||||
)
|
||||
where
|
||||
|
||||
import Text.Printf (printf)
|
||||
|
||||
makeConnectionUrl :: String -> String -> Int -> String -> String
|
||||
makeConnectionUrl user pass port dbName =
|
||||
printf "postgresql://%s:%s@localhost:%d/%s" user pass port dbName
|
||||
|
||||
-- As specified by PostgreSQL documentation.
|
||||
postgresMaxDbNameLength :: Int
|
||||
postgresMaxDbNameLength = 63
|
36
waspc/src/Wasp/Env.hs
Normal file
36
waspc/src/Wasp/Env.hs
Normal file
@ -0,0 +1,36 @@
|
||||
-- | This modules implements general concepts regarding env vars.
|
||||
-- It is not specific to Wasp in any way.
|
||||
module Wasp.Env
|
||||
( EnvVar,
|
||||
EnvVarName,
|
||||
EnvVarValue,
|
||||
parseDotEnvFile,
|
||||
envVarsToDotEnvContent,
|
||||
)
|
||||
where
|
||||
|
||||
import qualified Configuration.Dotenv as Dotenv
|
||||
import Control.Exception (ErrorCall (ErrorCall))
|
||||
import Data.List (intercalate)
|
||||
import qualified Data.Text as T
|
||||
import StrongPath (Abs, File, Path', fromAbsFile)
|
||||
import UnliftIO.Exception (catch, throwIO)
|
||||
|
||||
type EnvVar = (EnvVarName, EnvVarValue)
|
||||
|
||||
type EnvVarName = String
|
||||
|
||||
type EnvVarValue = String
|
||||
|
||||
-- Reads specified dotenv file and returns its values.
|
||||
-- Crashes if file doesn't exist or it can't parse it.
|
||||
parseDotEnvFile :: Path' Abs (File ()) -> IO [EnvVar]
|
||||
parseDotEnvFile envFile =
|
||||
Dotenv.parseFile (fromAbsFile envFile)
|
||||
-- Parse errors are returned from Dotenv.parseFile as ErrorCall, which Wasp compiler would
|
||||
-- report as a bug in compiler, so we instead convert these to IOExceptions.
|
||||
`catch` \(ErrorCall msg) -> throwIO $ userError $ "Failed to parse dot env file: " <> msg
|
||||
|
||||
envVarsToDotEnvContent :: [EnvVar] -> T.Text
|
||||
envVarsToDotEnvContent vars =
|
||||
T.pack $ intercalate "\n" $ map (\(name, value) -> name <> "=" <> show value) vars
|
@ -1,6 +1,7 @@
|
||||
module Wasp.Generator
|
||||
( writeWebAppCode,
|
||||
Wasp.Generator.Start.start,
|
||||
ProjectRootDir,
|
||||
)
|
||||
where
|
||||
|
||||
|
@ -7,10 +7,10 @@ where
|
||||
import Data.Map (fromList)
|
||||
import StrongPath (File', Path', Rel, castRel, relfile, (</>))
|
||||
import Wasp.AppSpec (AppSpec, doesConfigFileExist)
|
||||
import Wasp.Common (WaspProjectDir)
|
||||
import Wasp.ConfigFile (ConfigFileRelocationMap)
|
||||
import Wasp.Generator.Common (ProjectRootDir)
|
||||
import Wasp.Generator.WebAppGenerator.Common (webAppRootDirInProjectRootDir)
|
||||
import Wasp.Project.Common (WaspProjectDir)
|
||||
|
||||
tailwindConfigFile :: Path' (Rel WaspProjectDir) File'
|
||||
tailwindConfigFile = [relfile|tailwind.config.cjs|]
|
||||
|
@ -23,7 +23,6 @@ import Wasp.Generator.DbGenerator.Common
|
||||
( DbSchemaChecksumFile,
|
||||
DbSchemaChecksumOnLastDbConcurrenceFile,
|
||||
PrismaDbSchema,
|
||||
databaseUrlEnvVar,
|
||||
dbMigrationsDirInDbRootDir,
|
||||
dbRootDirInProjectRootDir,
|
||||
dbSchemaChecksumOnLastDbConcurrenceFileProjectRootDir,
|
||||
@ -41,6 +40,7 @@ import Wasp.Generator.Monad
|
||||
GeneratorWarning (GeneratorNeedsMigrationWarning),
|
||||
logAndThrowGeneratorError,
|
||||
)
|
||||
import Wasp.Project.Db (databaseUrlEnvVarName)
|
||||
import qualified Wasp.Psl.Ast.Model as Psl.Ast.Model
|
||||
import qualified Wasp.Psl.Generator.Model as Psl.Generator.Model
|
||||
import Wasp.Util (checksumFromFilePath, hexToString, ifM, (<:>))
|
||||
@ -56,7 +56,7 @@ genPrismaSchema ::
|
||||
Generator FileDraft
|
||||
genPrismaSchema spec = do
|
||||
(datasourceProvider :: String, datasourceUrl) <- case dbSystem of
|
||||
AS.Db.PostgreSQL -> return ("postgresql", makeEnvVarField databaseUrlEnvVar)
|
||||
AS.Db.PostgreSQL -> return ("postgresql", makeEnvVarField databaseUrlEnvVarName)
|
||||
AS.Db.SQLite ->
|
||||
if AS.isBuild spec
|
||||
then logAndThrowGeneratorError $ GenericGeneratorError "SQLite (a default database) is not supported in production. To build your Wasp app for production, switch to a different database. Switching to PostgreSQL: https://wasp-lang.dev/docs/language/features#migrating-from-sqlite-to-postgresql ."
|
||||
@ -67,12 +67,12 @@ genPrismaSchema spec = do
|
||||
[ "modelSchemas" .= map entityToPslModelSchema (AS.getDecls @AS.Entity.Entity spec),
|
||||
"datasourceProvider" .= datasourceProvider,
|
||||
"datasourceUrl" .= datasourceUrl,
|
||||
"prismaClientOutputDir" .= makeEnvVarField prismaClientOutputDirEnvVar
|
||||
"prismaClientOutputDir" .= makeEnvVarField Wasp.Generator.DbGenerator.Common.prismaClientOutputDirEnvVar
|
||||
]
|
||||
|
||||
return $ createTemplateFileDraft dbSchemaFileInProjectRootDir tmplSrcPath (Just templateData)
|
||||
return $ createTemplateFileDraft Wasp.Generator.DbGenerator.Common.dbSchemaFileInProjectRootDir tmplSrcPath (Just templateData)
|
||||
where
|
||||
tmplSrcPath = dbTemplatesDirInTemplatesDir </> dbSchemaFileInDbTemplatesDir
|
||||
tmplSrcPath = Wasp.Generator.DbGenerator.Common.dbTemplatesDirInTemplatesDir </> Wasp.Generator.DbGenerator.Common.dbSchemaFileInDbTemplatesDir
|
||||
dbSystem = fromMaybe AS.Db.SQLite $ AS.Db.system =<< AS.App.db (snd $ getApp spec)
|
||||
makeEnvVarField envVarName = "env(\"" ++ envVarName ++ "\")"
|
||||
|
||||
@ -87,7 +87,7 @@ genMigrationsDir spec =
|
||||
AS.migrationsDir spec >>= \waspMigrationsDir ->
|
||||
Just $ createCopyDirFileDraft (SP.castDir genProjectMigrationsDir) (SP.castDir waspMigrationsDir)
|
||||
where
|
||||
genProjectMigrationsDir = dbRootDirInProjectRootDir </> dbMigrationsDirInDbRootDir
|
||||
genProjectMigrationsDir = Wasp.Generator.DbGenerator.Common.dbRootDirInProjectRootDir </> Wasp.Generator.DbGenerator.Common.dbMigrationsDirInDbRootDir
|
||||
|
||||
-- | This function operates on generated code, and thus assumes the file drafts were written to disk
|
||||
postWriteDbGeneratorActions :: AppSpec -> Path' Abs (Dir ProjectRootDir) -> IO ([GeneratorWarning], [GeneratorError])
|
||||
@ -125,19 +125,22 @@ warnIfDbNeedsMigration spec projectRootDir = do
|
||||
then warnProjectDiffersFromDb projectRootDir
|
||||
else return Nothing
|
||||
where
|
||||
dbSchemaFp = projectRootDir </> dbSchemaFileInProjectRootDir
|
||||
dbSchemaChecksumFp = projectRootDir </> dbSchemaChecksumOnLastDbConcurrenceFileProjectRootDir
|
||||
dbSchemaFp = projectRootDir </> Wasp.Generator.DbGenerator.Common.dbSchemaFileInProjectRootDir
|
||||
dbSchemaChecksumFp = projectRootDir </> Wasp.Generator.DbGenerator.Common.dbSchemaChecksumOnLastDbConcurrenceFileProjectRootDir
|
||||
entitiesExist = not . null $ getEntities spec
|
||||
|
||||
warnIfSchemaDiffersFromChecksum ::
|
||||
Path' Abs (File PrismaDbSchema) ->
|
||||
Path' Abs (File DbSchemaChecksumOnLastDbConcurrenceFile) ->
|
||||
Path' Abs (File Wasp.Generator.DbGenerator.Common.PrismaDbSchema) ->
|
||||
Path' Abs (File Wasp.Generator.DbGenerator.Common.DbSchemaChecksumOnLastDbConcurrenceFile) ->
|
||||
IO (Maybe GeneratorWarning)
|
||||
warnIfSchemaDiffersFromChecksum dbSchemaFileAbs dbschemachecksumfile =
|
||||
ifM
|
||||
(checksumFileMatchesSchema dbSchemaFileAbs dbschemachecksumfile)
|
||||
(return Nothing)
|
||||
(return . Just $ GeneratorNeedsMigrationWarning "Your Prisma schema has changed, please run `wasp db migrate-dev` when ready.")
|
||||
( return . Just $
|
||||
GeneratorNeedsMigrationWarning
|
||||
"Your Prisma schema has changed, please run `wasp db migrate-dev` when ready."
|
||||
)
|
||||
|
||||
-- | Checks if the project's Prisma schema file and migrations dir matches the DB state.
|
||||
-- Issues a warning if it cannot connect, or if either check fails.
|
||||
@ -151,14 +154,22 @@ warnProjectDiffersFromDb projectRootDir = do
|
||||
then do
|
||||
-- NOTE: Since we know schema == db and all migrations are applied,
|
||||
-- we can write this file to prevent future redundant Prisma checks.
|
||||
DbOps.writeDbSchemaChecksumToFile projectRootDir dbSchemaChecksumOnLastDbConcurrenceFileProjectRootDir
|
||||
DbOps.writeDbSchemaChecksumToFile projectRootDir Wasp.Generator.DbGenerator.Common.dbSchemaChecksumOnLastDbConcurrenceFileProjectRootDir
|
||||
return Nothing
|
||||
else return . Just $ GeneratorNeedsMigrationWarning "You have unapplied migrations. Please run `wasp db migrate-dev` when ready."
|
||||
Just False -> return . Just $ GeneratorNeedsMigrationWarning "Your Prisma schema does not match your database, please run `wasp db migrate-dev`."
|
||||
-- NOTE: If there was an error, it could mean we could not connect to the SQLite db, since it does not exist.
|
||||
-- Or it could mean their databaseUrlEnvVar is wrong, or database is down, or any other number of causes.
|
||||
-- In any case, migrating will either solve it (in the SQLite case), or allow Prisma to give them enough info to troubleshoot.
|
||||
Nothing -> return . Just $ GeneratorNeedsMigrationWarning "Wasp was unable to verify your database is up to date. Running `wasp db migrate-dev` may fix this and will provide more info."
|
||||
else
|
||||
return . Just . GeneratorNeedsMigrationWarning $
|
||||
"You have unapplied migrations. Please run `wasp db migrate-dev` when ready."
|
||||
Just False ->
|
||||
return . Just . GeneratorNeedsMigrationWarning $
|
||||
"Your Prisma schema does not match your database, please run `wasp db migrate-dev`."
|
||||
-- NOTE: If there was an error, it could mean we could not connect to the SQLite db, since it
|
||||
-- does not exist. Or it could mean their databaseUrlEnvVar is wrong, or database is down, or
|
||||
-- any other number of causes. In any case, migrating will either solve it (in the SQLite case),
|
||||
-- or allow Prisma to give them enough info to troubleshoot.
|
||||
Nothing ->
|
||||
return . Just . GeneratorNeedsMigrationWarning $
|
||||
"Wasp was unable to verify your database is up to date."
|
||||
<> " Running `wasp db migrate-dev` may fix this and will provide more info."
|
||||
|
||||
genPrismaClients :: AppSpec -> Path' Abs (Dir ProjectRootDir) -> IO (Maybe GeneratorError)
|
||||
genPrismaClients spec projectRootDir =
|
||||
@ -169,7 +180,9 @@ genPrismaClients spec projectRootDir =
|
||||
where
|
||||
wasCurrentSchemaAlreadyGenerated :: IO Bool
|
||||
wasCurrentSchemaAlreadyGenerated =
|
||||
checksumFileExistsAndMatchesSchema projectRootDir dbSchemaChecksumOnLastGenerateFileProjectRootDir
|
||||
checksumFileExistsAndMatchesSchema
|
||||
projectRootDir
|
||||
Wasp.Generator.DbGenerator.Common.dbSchemaChecksumOnLastGenerateFileProjectRootDir
|
||||
|
||||
generatePrismaClientsIfEntitiesExist :: IO (Maybe GeneratorError)
|
||||
generatePrismaClientsIfEntitiesExist
|
||||
@ -180,7 +193,7 @@ genPrismaClients spec projectRootDir =
|
||||
entitiesExist = not . null $ getEntities spec
|
||||
|
||||
checksumFileExistsAndMatchesSchema ::
|
||||
DbSchemaChecksumFile f =>
|
||||
Wasp.Generator.DbGenerator.Common.DbSchemaChecksumFile f =>
|
||||
Path' Abs (Dir ProjectRootDir) ->
|
||||
Path' (Rel ProjectRootDir) (File f) ->
|
||||
IO Bool
|
||||
@ -190,16 +203,16 @@ checksumFileExistsAndMatchesSchema projectRootDir dbSchemaChecksumInProjectDir =
|
||||
(checksumFileMatchesSchema dbSchemaFileAbs checksumFileAbs)
|
||||
(return False)
|
||||
where
|
||||
dbSchemaFileAbs = projectRootDir </> dbSchemaFileInProjectRootDir
|
||||
dbSchemaFileAbs = projectRootDir </> Wasp.Generator.DbGenerator.Common.dbSchemaFileInProjectRootDir
|
||||
checksumFileAbs = projectRootDir </> dbSchemaChecksumInProjectDir
|
||||
|
||||
checksumFileMatchesSchema :: DbSchemaChecksumFile f => Path' Abs (File PrismaDbSchema) -> Path' Abs (File f) -> IO Bool
|
||||
checksumFileMatchesSchema :: Wasp.Generator.DbGenerator.Common.DbSchemaChecksumFile f => Path' Abs (File Wasp.Generator.DbGenerator.Common.PrismaDbSchema) -> Path' Abs (File f) -> IO Bool
|
||||
checksumFileMatchesSchema dbSchemaFileAbs dbSchemaChecksumFileAbs = do
|
||||
-- Read file strictly as the checksum may be later overwritten.
|
||||
dbChecksumFileContents <- IOUtil.readFileStrict dbSchemaChecksumFileAbs
|
||||
schemaFileHasChecksum dbSchemaFileAbs dbChecksumFileContents
|
||||
where
|
||||
schemaFileHasChecksum :: Path' Abs (File PrismaDbSchema) -> Text -> IO Bool
|
||||
schemaFileHasChecksum :: Path' Abs (File Wasp.Generator.DbGenerator.Common.PrismaDbSchema) -> Text -> IO Bool
|
||||
schemaFileHasChecksum schemaFile checksum = do
|
||||
dbSchemaFileChecksum <- pack . hexToString <$> checksumFromFilePath schemaFile
|
||||
return $ dbSchemaFileChecksum == checksum
|
||||
|
@ -20,16 +20,15 @@ module Wasp.Generator.DbGenerator.Common
|
||||
webAppRootDirFromDbRootDir,
|
||||
dbSchemaFileInProjectRootDir,
|
||||
prismaClientOutputDirEnvVar,
|
||||
databaseUrlEnvVar,
|
||||
DbSchemaChecksumFile,
|
||||
)
|
||||
where
|
||||
|
||||
import StrongPath (Dir, File, File', Path', Rel, reldir, relfile, (</>))
|
||||
import qualified StrongPath as SP
|
||||
import Wasp.Common (DbMigrationsDir)
|
||||
import Wasp.Generator.Common (AppComponentRootDir, DbRootDir, ProjectRootDir, ServerRootDir)
|
||||
import Wasp.Generator.Templates (TemplatesDir)
|
||||
import Wasp.Project.Db.Migrations (DbMigrationsDir)
|
||||
|
||||
data DbTemplatesDir
|
||||
|
||||
@ -97,9 +96,6 @@ dbSchemaChecksumOnLastGenerateFileProjectRootDir = dbRootDirInProjectRootDir </>
|
||||
prismaClientOutputDirEnvVar :: String
|
||||
prismaClientOutputDirEnvVar = "PRISMA_CLIENT_OUTPUT_DIR"
|
||||
|
||||
databaseUrlEnvVar :: String
|
||||
databaseUrlEnvVar = "DATABASE_URL"
|
||||
|
||||
prismaClientOutputDirInAppComponentDir :: AppComponentRootDir d => Path' (Rel d) (Dir ServerRootDir)
|
||||
prismaClientOutputDirInAppComponentDir = [reldir|node_modules/.prisma/client|]
|
||||
|
||||
|
@ -3,18 +3,20 @@ module Wasp.Generator.DbGenerator.Jobs
|
||||
migrateDiff,
|
||||
generatePrismaClient,
|
||||
runStudio,
|
||||
reset,
|
||||
migrateStatus,
|
||||
asPrismaCliArgs,
|
||||
)
|
||||
where
|
||||
|
||||
import StrongPath (Abs, Dir, File', Path', (</>))
|
||||
import StrongPath (Abs, Dir, File, File', Path', (</>))
|
||||
import qualified StrongPath as SP
|
||||
import StrongPath.TH (relfile)
|
||||
import qualified System.Info
|
||||
import Wasp.Generator.Common (ProjectRootDir)
|
||||
import Wasp.Generator.DbGenerator.Common
|
||||
( MigrateArgs (..),
|
||||
PrismaDbSchema,
|
||||
dbSchemaFileInProjectRootDir,
|
||||
)
|
||||
import Wasp.Generator.Job (JobType)
|
||||
@ -23,10 +25,7 @@ import Wasp.Generator.Job.Process (runNodeCommandAsJob, runNodeCommandAsJobWithE
|
||||
import Wasp.Generator.ServerGenerator.Common (serverRootDirInProjectRootDir)
|
||||
|
||||
migrateDev :: Path' Abs (Dir ProjectRootDir) -> MigrateArgs -> J.Job
|
||||
migrateDev projectDir migrateArgs = do
|
||||
let serverDir = projectDir </> serverRootDirInProjectRootDir
|
||||
let schemaFile = projectDir </> dbSchemaFileInProjectRootDir
|
||||
|
||||
migrateDev projectDir migrateArgs =
|
||||
-- NOTE(matija): We are running this command from server's root dir since that is where
|
||||
-- Prisma packages (cli and client) are currently installed.
|
||||
-- NOTE(martin): `prisma migrate dev` refuses to execute when interactivity is needed if stdout is being piped,
|
||||
@ -34,25 +33,29 @@ migrateDev projectDir migrateArgs = do
|
||||
-- so we do have interactivity, but Prisma doesn't know that.
|
||||
-- I opened an issue with Prisma https://github.com/prisma/prisma/issues/7113, but in the meantime
|
||||
-- we are using `script` to trick Prisma into thinking it is running in TTY (interactively).
|
||||
|
||||
-- NOTE(martin): For this to work on Mac, filepath in the list below must be as it is now - not wrapped in any quotes.
|
||||
let prismaMigrateCmd =
|
||||
[ absPrismaExecutableFp projectDir,
|
||||
"migrate",
|
||||
"dev",
|
||||
"--schema",
|
||||
SP.toFilePath schemaFile,
|
||||
"--skip-generate"
|
||||
]
|
||||
++ asPrismaCliArgs migrateArgs
|
||||
let scriptArgs =
|
||||
if System.Info.os == "darwin"
|
||||
then -- NOTE(martin): On MacOS, command that `script` should execute is treated as multiple arguments.
|
||||
["-Fq", "/dev/null"] ++ prismaMigrateCmd
|
||||
else -- NOTE(martin): On Linux, command that `script` should execute is treated as one argument.
|
||||
["-feqc", unwords prismaMigrateCmd, "/dev/null"]
|
||||
|
||||
runNodeCommandAsJob serverDir "script" scriptArgs J.Db
|
||||
where
|
||||
serverDir = projectDir </> serverRootDirInProjectRootDir
|
||||
schemaFile = projectDir </> dbSchemaFileInProjectRootDir
|
||||
|
||||
scriptArgs =
|
||||
if System.Info.os == "darwin"
|
||||
then -- NOTE(martin): On MacOS, command that `script` should execute is treated as multiple arguments.
|
||||
["-Fq", "/dev/null"] ++ prismaMigrateCmd
|
||||
else -- NOTE(martin): On Linux, command that `script` should execute is treated as one argument.
|
||||
["-feqc", unwords prismaMigrateCmd, "/dev/null"]
|
||||
|
||||
-- NOTE(martin): For this to work on Mac, filepath in the list below must be as it is now - not
|
||||
-- wrapped in any quotes.
|
||||
prismaMigrateCmd =
|
||||
[ absPrismaExecutableFp projectDir,
|
||||
"migrate",
|
||||
"dev",
|
||||
"--schema",
|
||||
SP.fromAbsFile schemaFile,
|
||||
"--skip-generate"
|
||||
]
|
||||
++ asPrismaCliArgs migrateArgs
|
||||
|
||||
asPrismaCliArgs :: MigrateArgs -> [String]
|
||||
asPrismaCliArgs migrateArgs = do
|
||||
@ -65,20 +68,15 @@ asPrismaCliArgs migrateArgs = do
|
||||
-- Because of the --exit-code flag, it changes the exit code behavior
|
||||
-- to signal if the diff is empty or not (Empty: 0, Error: 1, Not empty: 2)
|
||||
migrateDiff :: Path' Abs (Dir ProjectRootDir) -> J.Job
|
||||
migrateDiff projectDir = do
|
||||
let serverDir = projectDir </> serverRootDirInProjectRootDir
|
||||
let schemaFileFp = SP.toFilePath $ projectDir </> dbSchemaFileInProjectRootDir
|
||||
let prismaMigrateDiffCmdArgs =
|
||||
[ "migrate",
|
||||
"diff",
|
||||
"--from-schema-datamodel",
|
||||
schemaFileFp,
|
||||
"--to-schema-datasource",
|
||||
schemaFileFp,
|
||||
"--exit-code"
|
||||
]
|
||||
|
||||
runNodeCommandAsJob serverDir (absPrismaExecutableFp projectDir) prismaMigrateDiffCmdArgs J.Db
|
||||
migrateDiff projectDir = runPrismaCommandAsJob projectDir $ \schema ->
|
||||
[ "migrate",
|
||||
"diff",
|
||||
"--from-schema-datamodel",
|
||||
SP.fromAbsFile schema,
|
||||
"--to-schema-datasource",
|
||||
SP.fromAbsFile schema,
|
||||
"--exit-code"
|
||||
]
|
||||
|
||||
-- | Checks to see if all migrations are applied to the DB.
|
||||
-- An exit code of 0 means we successfully verified all migrations are applied.
|
||||
@ -86,26 +84,19 @@ migrateDiff projectDir = do
|
||||
-- or (b) there are pending migrations to apply.
|
||||
-- Therefore, this should be checked **after** a command that ensures connectivity.
|
||||
migrateStatus :: Path' Abs (Dir ProjectRootDir) -> J.Job
|
||||
migrateStatus projectDir = do
|
||||
let serverDir = projectDir </> serverRootDirInProjectRootDir
|
||||
let schemaFileFp = SP.toFilePath $ projectDir </> dbSchemaFileInProjectRootDir
|
||||
let prismaMigrateDiffCmdArgs =
|
||||
[ "migrate",
|
||||
"status",
|
||||
"--schema",
|
||||
schemaFileFp
|
||||
]
|
||||
migrateStatus projectDir = runPrismaCommandAsJob projectDir $ \schema ->
|
||||
["migrate", "status", "--schema", SP.fromAbsFile schema]
|
||||
|
||||
runNodeCommandAsJob serverDir (absPrismaExecutableFp projectDir) prismaMigrateDiffCmdArgs J.Db
|
||||
-- | Runs `prisma migrate reset`, which drops the tables (so schemas and data is lost) and then
|
||||
-- reapplies all the migrations.
|
||||
reset :: Path' Abs (Dir ProjectRootDir) -> J.Job
|
||||
reset projectDir = runPrismaCommandAsJob projectDir $ \schema ->
|
||||
["migrate", "reset", "--skip-generate", "--schema", SP.fromAbsFile schema]
|
||||
|
||||
-- | Runs `prisma studio` - Prisma's db inspector.
|
||||
runStudio :: Path' Abs (Dir ProjectRootDir) -> J.Job
|
||||
runStudio projectDir =
|
||||
runNodeCommandAsJob serverDir (absPrismaExecutableFp projectDir) prismaStudioCmdArgs J.Db
|
||||
where
|
||||
serverDir = projectDir </> serverRootDirInProjectRootDir
|
||||
schemaFile = projectDir </> dbSchemaFileInProjectRootDir
|
||||
prismaStudioCmdArgs = ["studio", "--schema", SP.toFilePath schemaFile]
|
||||
runStudio projectDir = runPrismaCommandAsJob projectDir $ \schema ->
|
||||
["studio", "--schema", SP.fromAbsFile schema]
|
||||
|
||||
generatePrismaClient :: Path' Abs (Dir ProjectRootDir) -> (String, String) -> JobType -> J.Job
|
||||
generatePrismaClient projectDir prismaClientOutputDirEnv jobType =
|
||||
@ -114,8 +105,18 @@ generatePrismaClient projectDir prismaClientOutputDirEnv jobType =
|
||||
envVars = [prismaClientOutputDirEnv]
|
||||
serverRootDir = projectDir </> serverRootDirInProjectRootDir
|
||||
prismaExecutable = absPrismaExecutableFp projectDir
|
||||
prismaGenerateCmdArgs = ["generate", "--schema", schemaFile]
|
||||
schemaFile = SP.fromAbsFile $ projectDir </> dbSchemaFileInProjectRootDir
|
||||
prismaGenerateCmdArgs = ["generate", "--schema", SP.fromAbsFile schemaFile]
|
||||
schemaFile = projectDir </> dbSchemaFileInProjectRootDir
|
||||
|
||||
runPrismaCommandAsJob ::
|
||||
Path' Abs (Dir ProjectRootDir) ->
|
||||
(Path' Abs (File PrismaDbSchema) -> [String]) ->
|
||||
J.Job
|
||||
runPrismaCommandAsJob projectDir makeCmdArgs =
|
||||
runNodeCommandAsJob serverDir (absPrismaExecutableFp projectDir) (makeCmdArgs schemaFile) J.Db
|
||||
where
|
||||
serverDir = projectDir </> serverRootDirInProjectRootDir
|
||||
schemaFile = projectDir </> dbSchemaFileInProjectRootDir
|
||||
|
||||
-- | NOTE: The expectation is that `npm install` was already executed
|
||||
-- such that we can use the locally installed package.
|
||||
|
@ -4,6 +4,7 @@ module Wasp.Generator.DbGenerator.Operations
|
||||
doesSchemaMatchDb,
|
||||
writeDbSchemaChecksumToFile,
|
||||
areAllMigrationsAppliedToDb,
|
||||
dbReset,
|
||||
)
|
||||
where
|
||||
|
||||
@ -18,7 +19,6 @@ import qualified Path as P
|
||||
import StrongPath (Abs, Dir, File, Path', Rel, (</>))
|
||||
import qualified StrongPath as SP
|
||||
import System.Exit (ExitCode (..))
|
||||
import Wasp.Common (DbMigrationsDir)
|
||||
import Wasp.Generator.Common (ProjectRootDir)
|
||||
import Wasp.Generator.DbGenerator.Common
|
||||
( DbSchemaChecksumFile,
|
||||
@ -38,8 +38,9 @@ import Wasp.Generator.FileDraft.WriteableMonad (WriteableMonad (copyDirectoryRec
|
||||
import qualified Wasp.Generator.Job as J
|
||||
import Wasp.Generator.Job.IO (printJobMsgsUntilExitReceived, readJobMessagesAndPrintThemPrefixed)
|
||||
import qualified Wasp.Generator.WriteFileDrafts as Generator.WriteFileDrafts
|
||||
import Wasp.Project.Db.Migrations (DbMigrationsDir)
|
||||
import Wasp.Util (checksumFromFilePath, hexToString)
|
||||
import Wasp.Util.IO (doesFileExist, removeFile)
|
||||
import Wasp.Util.IO (deleteFileIfExists, doesFileExist)
|
||||
import qualified Wasp.Util.IO as IOUtil
|
||||
|
||||
-- | Migrates in the generated project context and then copies the migrations dir back
|
||||
@ -101,10 +102,25 @@ removeDbSchemaChecksumFile ::
|
||||
Path' Abs (Dir ProjectRootDir) ->
|
||||
Path' (Rel ProjectRootDir) (File f) ->
|
||||
IO ()
|
||||
removeDbSchemaChecksumFile genProjectRootDirAbs dbSchemaChecksumInProjectRootDir = removeFile dbSchemaChecksumFp
|
||||
removeDbSchemaChecksumFile genProjectRootDirAbs dbSchemaChecksumInProjectRootDir = deleteFileIfExists dbSchemaChecksumFp
|
||||
where
|
||||
dbSchemaChecksumFp = genProjectRootDirAbs </> dbSchemaChecksumInProjectRootDir
|
||||
|
||||
-- Resets the database: drops all data and applies all migrations from scratch.
|
||||
dbReset ::
|
||||
Path' Abs (Dir ProjectRootDir) ->
|
||||
IO (Either String ())
|
||||
dbReset genProjectDir = do
|
||||
-- We are doing quite a move here, resetting the whole db, so best to delete the checksum file,
|
||||
-- which will force Wasp to do a deep check of migrations next time, just to be sure.
|
||||
removeDbSchemaChecksumFile genProjectDir dbSchemaChecksumOnLastDbConcurrenceFileProjectRootDir
|
||||
chan <- newChan
|
||||
((), exitCode) <-
|
||||
readJobMessagesAndPrintThemPrefixed chan `concurrently` DbJobs.reset genProjectDir chan
|
||||
return $ case exitCode of
|
||||
ExitSuccess -> Right ()
|
||||
ExitFailure c -> Left $ "Failed with exit code " <> show c
|
||||
|
||||
generatePrismaClients :: Path' Abs (Dir ProjectRootDir) -> IO (Either String ())
|
||||
generatePrismaClients projectRootDir = do
|
||||
generateResult <- liftA2 (>>) generatePrismaClientForServer generatePrismaClientForWebApp projectRootDir
|
||||
|
@ -6,6 +6,7 @@ module Wasp.Generator.FileDraft
|
||||
createCopyFileDraftIfExists,
|
||||
createTextFileDraft,
|
||||
createCopyDirFileDraft,
|
||||
createCopyAndModifyTextFileDraft,
|
||||
)
|
||||
where
|
||||
|
||||
@ -14,6 +15,7 @@ import Data.Text (Text)
|
||||
import StrongPath (Abs, Dir, File, Path', Rel)
|
||||
import qualified StrongPath as SP
|
||||
import Wasp.Generator.Common (ProjectRootDir)
|
||||
import qualified Wasp.Generator.FileDraft.CopyAndModifyTextFileDraft as CMTextFD
|
||||
import qualified Wasp.Generator.FileDraft.CopyDirFileDraft as CopyDirFD
|
||||
import qualified Wasp.Generator.FileDraft.CopyFileDraft as CopyFD
|
||||
import qualified Wasp.Generator.FileDraft.TemplateFileDraft as TmplFD
|
||||
@ -33,23 +35,26 @@ data FileDraft
|
||||
| FileDraftCopyFd CopyFD.CopyFileDraft
|
||||
| FileDraftCopyDirFd CopyDirFD.CopyDirFileDraft
|
||||
| FileDraftTextFd TextFD.TextFileDraft
|
||||
deriving (Show, Eq)
|
||||
| FileDraftCopyAndModifyTextFd CMTextFD.CopyAndModifyTextFileDraft
|
||||
|
||||
instance Writeable FileDraft where
|
||||
write dstDir (FileDraftTemplateFd draft) = write dstDir draft
|
||||
write dstDir (FileDraftCopyFd draft) = write dstDir draft
|
||||
write dstDir (FileDraftCopyDirFd draft) = write dstDir draft
|
||||
write dstDir (FileDraftTextFd draft) = write dstDir draft
|
||||
write dstDir (FileDraftCopyAndModifyTextFd draft) = write dstDir draft
|
||||
|
||||
getChecksum (FileDraftTemplateFd draft) = getChecksum draft
|
||||
getChecksum (FileDraftCopyFd draft) = getChecksum draft
|
||||
getChecksum (FileDraftCopyDirFd draft) = getChecksum draft
|
||||
getChecksum (FileDraftTextFd draft) = getChecksum draft
|
||||
getChecksum (FileDraftCopyAndModifyTextFd draft) = getChecksum draft
|
||||
|
||||
getDstPath (FileDraftTemplateFd draft) = getDstPath draft
|
||||
getDstPath (FileDraftCopyFd draft) = getDstPath draft
|
||||
getDstPath (FileDraftCopyDirFd draft) = getDstPath draft
|
||||
getDstPath (FileDraftTextFd draft) = getDstPath draft
|
||||
getDstPath (FileDraftCopyAndModifyTextFd draft) = getDstPath draft
|
||||
|
||||
createTemplateFileDraft ::
|
||||
Path' (Rel ProjectRootDir) (File a) ->
|
||||
@ -73,6 +78,17 @@ createCopyFileDraft dstPath srcPath =
|
||||
CopyFD._failIfSrcDoesNotExist = True
|
||||
}
|
||||
|
||||
createCopyAndModifyTextFileDraft ::
|
||||
Path' (Rel ProjectRootDir) (File a) -> Path' Abs (File b) -> (Text -> Text) -> FileDraft
|
||||
createCopyAndModifyTextFileDraft dstPath srcPath modify =
|
||||
FileDraftCopyAndModifyTextFd $
|
||||
CMTextFD.CopyAndModifyTextFileDraft
|
||||
{ CMTextFD._dstPath = SP.castFile dstPath,
|
||||
CMTextFD._srcPath = SP.castFile srcPath,
|
||||
CMTextFD._modify = modify,
|
||||
CMTextFD._failIfSrcDoesNotExist = True
|
||||
}
|
||||
|
||||
createCopyFileDraftIfExists :: Path' (Rel ProjectRootDir) (File a) -> Path' Abs (File b) -> FileDraft
|
||||
createCopyFileDraftIfExists dstPath srcPath =
|
||||
FileDraftCopyFd $
|
||||
|
@ -0,0 +1,54 @@
|
||||
module Wasp.Generator.FileDraft.CopyAndModifyTextFileDraft
|
||||
( CopyAndModifyTextFileDraft (..),
|
||||
)
|
||||
where
|
||||
|
||||
import Control.Monad (when)
|
||||
import qualified Data.Text as T
|
||||
import qualified Data.Text.IO as T.IO
|
||||
import StrongPath (Abs, File', Path', Rel, (</>))
|
||||
import qualified StrongPath as SP
|
||||
import System.IO.Error (doesNotExistErrorType, mkIOError)
|
||||
import Wasp.Generator.Common (ProjectRootDir)
|
||||
import Wasp.Generator.FileDraft.Writeable
|
||||
import Wasp.Generator.FileDraft.WriteableMonad
|
||||
import Wasp.Util (checksumFromText)
|
||||
|
||||
-- | File draft based on existing text file + some modifications.
|
||||
data CopyAndModifyTextFileDraft = CopyAndModifyTextFileDraft
|
||||
{ -- | Path where the file will be copied to.
|
||||
_dstPath :: !(Path' (Rel ProjectRootDir) File'),
|
||||
-- | Absolute path of source file to copy.
|
||||
_srcPath :: !(Path' Abs File'),
|
||||
_failIfSrcDoesNotExist :: !Bool,
|
||||
_modify :: !(T.Text -> T.Text)
|
||||
}
|
||||
|
||||
instance Writeable CopyAndModifyTextFileDraft where
|
||||
write absDstDirPath draft = do
|
||||
srcFileExists <- doesFileExist (SP.fromAbsFile $ _srcPath draft)
|
||||
if srcFileExists
|
||||
then do
|
||||
createDirectoryIfMissing True (SP.fromAbsDir $ SP.parent absDraftDstPath)
|
||||
readFileAsText (SP.fromAbsFile $ _srcPath draft)
|
||||
>>= writeFileFromText (SP.fromAbsFile absDraftDstPath) . _modify draft
|
||||
else
|
||||
when
|
||||
(_failIfSrcDoesNotExist draft)
|
||||
( throwIO $
|
||||
mkIOError
|
||||
doesNotExistErrorType
|
||||
"Source file of CopyTextFileDraft does not exist."
|
||||
Nothing
|
||||
(Just $ SP.fromAbsFile $ _srcPath draft)
|
||||
)
|
||||
where
|
||||
absDraftDstPath = absDstDirPath </> _dstPath draft
|
||||
|
||||
-- NOTE: We are reading file here, but then also again later in the "write", so
|
||||
-- we are reading file at least twice! Any way to avoid it?
|
||||
-- Idea: We could cache it in an mvar in the CopyTextFileDraft.
|
||||
getChecksum draft =
|
||||
checksumFromText . _modify draft <$> T.IO.readFile (SP.fromAbsFile $ _srcPath draft)
|
||||
|
||||
getDstPath draft = Left $ _dstPath draft
|
@ -19,7 +19,7 @@ data CopyFileDraft = CopyFileDraft
|
||||
_dstPath :: !(Path' (Rel ProjectRootDir) File'),
|
||||
-- | Absolute path of source file to copy.
|
||||
_srcPath :: !(Path' Abs File'),
|
||||
_failIfSrcDoesNotExist :: Bool
|
||||
_failIfSrcDoesNotExist :: !Bool
|
||||
}
|
||||
deriving (Show, Eq)
|
||||
|
||||
|
@ -54,6 +54,8 @@ class (MonadIO m) => WriteableMonad m where
|
||||
|
||||
writeFileFromText :: FilePath -> Text -> m ()
|
||||
|
||||
readFileAsText :: FilePath -> m Text
|
||||
|
||||
getTemplateFileAbsPath ::
|
||||
-- | Template file path.
|
||||
Path' (Rel Templates.TemplatesDir) (File a) ->
|
||||
@ -94,6 +96,7 @@ instance WriteableMonad IO where
|
||||
doesFileExist = System.Directory.doesFileExist
|
||||
doesDirectoryExist = System.Directory.doesDirectoryExist
|
||||
writeFileFromText = Data.Text.IO.writeFile
|
||||
readFileAsText = Data.Text.IO.readFile
|
||||
getTemplateFileAbsPath = Templates.getTemplateFileAbsPath
|
||||
getTemplatesDirAbsPath = Templates.getTemplatesDirAbsPath
|
||||
compileAndRenderTemplate = Templates.compileAndRenderTemplate
|
||||
|
@ -1,4 +1,7 @@
|
||||
{-# LANGUAGE TypeApplications #-}
|
||||
{-# OPTIONS_GHC -Wno-unrecognised-pragmas #-}
|
||||
|
||||
{-# HLINT ignore "Replace case with maybe" #-}
|
||||
|
||||
module Wasp.Generator.ServerGenerator
|
||||
( genServer,
|
||||
@ -36,6 +39,7 @@ import qualified Wasp.AppSpec.App.Server as AS.App.Server
|
||||
import qualified Wasp.AppSpec.Entity as AS.Entity
|
||||
import Wasp.AppSpec.Util (isPgBossJobExecutorUsed)
|
||||
import Wasp.AppSpec.Valid (getApp, isAuthEnabled)
|
||||
import Wasp.Env (envVarsToDotEnvContent)
|
||||
import Wasp.Generator.Common
|
||||
( ServerRootDir,
|
||||
latestMajorNodeVersion,
|
||||
@ -44,7 +48,7 @@ import Wasp.Generator.Common
|
||||
prismaVersion,
|
||||
)
|
||||
import Wasp.Generator.ExternalCodeGenerator (genExternalCodeDir)
|
||||
import Wasp.Generator.FileDraft (FileDraft, createCopyFileDraft)
|
||||
import Wasp.Generator.FileDraft (FileDraft, createTextFileDraft)
|
||||
import Wasp.Generator.Monad (Generator)
|
||||
import qualified Wasp.Generator.NpmDependencies as N
|
||||
import Wasp.Generator.ServerGenerator.ApiRoutesG (genApis)
|
||||
@ -57,6 +61,7 @@ import Wasp.Generator.ServerGenerator.JobGenerator (depsRequiredByJobs, genJobEx
|
||||
import Wasp.Generator.ServerGenerator.JsImport (extImportToImportJson)
|
||||
import Wasp.Generator.ServerGenerator.OperationsG (genOperations)
|
||||
import Wasp.Generator.ServerGenerator.OperationsRoutesG (genOperationsRoutes)
|
||||
import Wasp.Project.Db (databaseUrlEnvVarName)
|
||||
import Wasp.SemanticVersion (major)
|
||||
import Wasp.Util (toLowerFirst, (<++>))
|
||||
|
||||
@ -85,15 +90,22 @@ genServer spec =
|
||||
genFileCopy = return . C.mkTmplFd
|
||||
|
||||
genDotEnv :: AppSpec -> Generator [FileDraft]
|
||||
genDotEnv spec = return $
|
||||
case AS.dotEnvServerFile spec of
|
||||
Just srcFilePath
|
||||
| not $ AS.isBuild spec ->
|
||||
[ createCopyFileDraft
|
||||
(C.serverRootDirInProjectRootDir </> dotEnvInServerRootDir)
|
||||
srcFilePath
|
||||
]
|
||||
_ -> []
|
||||
-- Don't generate .env if we are building for production, since .env is to be used only for
|
||||
-- development.
|
||||
genDotEnv spec | AS.isBuild spec = return []
|
||||
genDotEnv spec =
|
||||
return
|
||||
[ createTextFileDraft
|
||||
(C.serverRootDirInProjectRootDir </> dotEnvInServerRootDir)
|
||||
(envVarsToDotEnvContent envVars)
|
||||
]
|
||||
where
|
||||
envVars = waspEnvVars ++ userEnvVars
|
||||
userEnvVars = AS.devEnvVarsServer spec
|
||||
waspEnvVars = case AS.devDatabaseUrl spec of
|
||||
Just url | not isThereCustomDbUrl -> [(databaseUrlEnvVarName, url)]
|
||||
_ -> []
|
||||
isThereCustomDbUrl = any ((== databaseUrlEnvVarName) . fst) userEnvVars
|
||||
|
||||
dotEnvInServerRootDir :: Path' (Rel ServerRootDir) File'
|
||||
dotEnvInServerRootDir = [relfile|.env|]
|
||||
|
@ -5,7 +5,6 @@ module Wasp.Generator.ServerGenerator.Common
|
||||
mkTmplFd,
|
||||
mkTmplFdWithDstAndData,
|
||||
mkSrcTmplFd,
|
||||
dotEnvServer,
|
||||
srcDirInServerTemplatesDir,
|
||||
asTmplFile,
|
||||
asTmplSrcFile,
|
||||
@ -22,11 +21,16 @@ module Wasp.Generator.ServerGenerator.Common
|
||||
where
|
||||
|
||||
import qualified Data.Aeson as Aeson
|
||||
import StrongPath (Dir, File', Path', Rel, reldir, relfile, (</>))
|
||||
import StrongPath (Dir, File', Path', Rel, reldir, (</>))
|
||||
import qualified StrongPath as SP
|
||||
import System.FilePath (splitExtension)
|
||||
import Wasp.Common (WaspProjectDir)
|
||||
import Wasp.Generator.Common (GeneratedSrcDir, ProjectRootDir, ServerRootDir, UniversalTemplatesDir, universalTemplatesDirInTemplatesDir)
|
||||
import Wasp.Generator.Common
|
||||
( GeneratedSrcDir,
|
||||
ProjectRootDir,
|
||||
ServerRootDir,
|
||||
UniversalTemplatesDir,
|
||||
universalTemplatesDirInTemplatesDir,
|
||||
)
|
||||
import Wasp.Generator.FileDraft (FileDraft, createTemplateFileDraft)
|
||||
import Wasp.Generator.Templates (TemplatesDir)
|
||||
|
||||
@ -110,9 +114,6 @@ serverTemplatesDirInTemplatesDir = [reldir|server|]
|
||||
srcDirInServerTemplatesDir :: Path' (Rel ServerTemplatesDir) (Dir ServerTemplatesSrcDir)
|
||||
srcDirInServerTemplatesDir = [reldir|src|]
|
||||
|
||||
dotEnvServer :: Path' (SP.Rel WaspProjectDir) File'
|
||||
dotEnvServer = [relfile|.env.server|]
|
||||
|
||||
-- Converts the real name of the source file (i.e., name on disk) into a name
|
||||
-- that can be used in an ESNext import.
|
||||
-- Specifically, when using the ESNext module system, all source files must be
|
||||
|
@ -9,10 +9,10 @@ import StrongPath (File', Path', Rel, relfile, (</>))
|
||||
import qualified StrongPath as SP
|
||||
import Wasp.AppSpec (AppSpec)
|
||||
import Wasp.AppSpec.Valid (isAuthEnabled)
|
||||
import Wasp.Generator.DbGenerator.Common (databaseUrlEnvVar)
|
||||
import Wasp.Generator.FileDraft (FileDraft)
|
||||
import Wasp.Generator.Monad (Generator)
|
||||
import qualified Wasp.Generator.ServerGenerator.Common as C
|
||||
import Wasp.Project.Db (databaseUrlEnvVarName)
|
||||
|
||||
genConfigFile :: AppSpec -> Generator FileDraft
|
||||
genConfigFile spec = return $ C.mkTmplFdWithDstAndData tmplFile dstFile (Just tmplData)
|
||||
@ -22,7 +22,7 @@ genConfigFile spec = return $ C.mkTmplFdWithDstAndData tmplFile dstFile (Just tm
|
||||
tmplData =
|
||||
object
|
||||
[ "isAuthEnabled" .= isAuthEnabled spec,
|
||||
"databaseUrlEnvVar" .= databaseUrlEnvVar
|
||||
"databaseUrlEnvVarName" .= databaseUrlEnvVarName
|
||||
]
|
||||
|
||||
configFileInSrcDir :: Path' (Rel C.ServerSrcDir) File'
|
||||
|
@ -24,10 +24,11 @@ import Wasp.AppSpec (AppSpec)
|
||||
import qualified Wasp.AppSpec as AS
|
||||
import qualified Wasp.AppSpec.App as AS.App
|
||||
import qualified Wasp.AppSpec.App.Auth as AS.App.Auth
|
||||
import Wasp.AppSpec.App.Client as AS.App.Client
|
||||
import qualified Wasp.AppSpec.App.Client as AS.App.Client
|
||||
import qualified Wasp.AppSpec.App.Dependency as AS.Dependency
|
||||
import qualified Wasp.AppSpec.Entity as AS.Entity
|
||||
import Wasp.AppSpec.Valid (getApp)
|
||||
import Wasp.Env (envVarsToDotEnvContent)
|
||||
import Wasp.Generator.AuthProviders (gitHubAuthProvider, googleAuthProvider)
|
||||
import qualified Wasp.Generator.AuthProviders.OAuth as OAuth
|
||||
import Wasp.Generator.Common
|
||||
@ -75,15 +76,15 @@ genWebApp spec = do
|
||||
genFileCopy = return . C.mkTmplFd
|
||||
|
||||
genDotEnv :: AppSpec -> Generator [FileDraft]
|
||||
genDotEnv spec = return $
|
||||
case AS.dotEnvClientFile spec of
|
||||
Just srcFilePath
|
||||
| not $ AS.isBuild spec ->
|
||||
[ createCopyFileDraft
|
||||
(C.webAppRootDirInProjectRootDir </> dotEnvInWebAppRootDir)
|
||||
srcFilePath
|
||||
]
|
||||
_ -> []
|
||||
-- Don't generate .env if we are building for production, since .env is to be used only for
|
||||
-- development.
|
||||
genDotEnv spec | AS.isBuild spec = return []
|
||||
genDotEnv spec =
|
||||
return
|
||||
[ createTextFileDraft
|
||||
(C.webAppRootDirInProjectRootDir </> dotEnvInWebAppRootDir)
|
||||
(envVarsToDotEnvContent $ AS.devEnvVarsClient spec)
|
||||
]
|
||||
|
||||
dotEnvInWebAppRootDir :: Path' (Rel C.WebAppRootDir) File'
|
||||
dotEnvInWebAppRootDir = [relfile|.env|]
|
||||
|
@ -1,7 +1,6 @@
|
||||
module Wasp.Generator.WebAppGenerator.Common
|
||||
( webAppRootDirInProjectRootDir,
|
||||
webAppSrcDirInWebAppRootDir,
|
||||
dotEnvClient,
|
||||
mkSrcTmplFd,
|
||||
mkTmplFd,
|
||||
mkTmplFdWithDst,
|
||||
@ -21,9 +20,8 @@ module Wasp.Generator.WebAppGenerator.Common
|
||||
where
|
||||
|
||||
import qualified Data.Aeson as Aeson
|
||||
import StrongPath (Dir, File', Path', Rel, reldir, relfile, (</>))
|
||||
import StrongPath (Dir, File', Path', Rel, reldir, (</>))
|
||||
import qualified StrongPath as SP
|
||||
import Wasp.Common (WaspProjectDir)
|
||||
import Wasp.Generator.Common (GeneratedSrcDir, ProjectRootDir, UniversalTemplatesDir, WebAppRootDir, universalTemplatesDirInTemplatesDir)
|
||||
import Wasp.Generator.FileDraft (FileDraft, createTemplateFileDraft)
|
||||
import Wasp.Generator.Templates (TemplatesDir)
|
||||
@ -65,9 +63,6 @@ webAppTemplatesDirInTemplatesDir = [reldir|react-app|]
|
||||
srcDirInWebAppTemplatesDir :: Path' (Rel WebAppTemplatesDir) (Dir WebAppTemplatesSrcDir)
|
||||
srcDirInWebAppTemplatesDir = [reldir|src|]
|
||||
|
||||
dotEnvClient :: Path' (SP.Rel WaspProjectDir) File'
|
||||
dotEnvClient = [relfile|.env.client|]
|
||||
|
||||
mkSrcTmplFd :: Path' (Rel WebAppTemplatesSrcDir) File' -> FileDraft
|
||||
mkSrcTmplFd pathInTemplatesSrcDir = mkTmplFdWithDst srcPath dstPath
|
||||
where
|
||||
|
@ -1,201 +0,0 @@
|
||||
module Wasp.Lib
|
||||
( compile,
|
||||
Generator.start,
|
||||
ProjectRootDir,
|
||||
findWaspFile,
|
||||
analyzeWaspProject,
|
||||
compileAndRenderDockerfile,
|
||||
CompileError,
|
||||
CompileWarning,
|
||||
deploy,
|
||||
)
|
||||
where
|
||||
|
||||
import Control.Arrow
|
||||
import Control.Concurrent (newChan)
|
||||
import Control.Concurrent.Async (concurrently)
|
||||
import Control.Monad.Except
|
||||
import Control.Monad.Extra (whenMaybeM)
|
||||
import Data.List (find, isSuffixOf)
|
||||
import Data.List.NonEmpty (toList)
|
||||
import Data.Maybe (maybeToList)
|
||||
import Data.Text (Text)
|
||||
import qualified Data.Text.IO as T.IO
|
||||
import StrongPath (Abs, Dir, File', Path', Rel, fromAbsDir, reldir, relfile, toFilePath, (</>))
|
||||
import System.Directory (doesDirectoryExist, doesFileExist)
|
||||
import qualified Wasp.Analyzer as Analyzer
|
||||
import Wasp.Analyzer.AnalyzeError (getErrorMessageAndCtx)
|
||||
import qualified Wasp.AppSpec as AS
|
||||
import Wasp.AppSpec.Valid (validateAppSpec)
|
||||
import Wasp.Common (DbMigrationsDir, WaspProjectDir, dbMigrationsDirInWaspProjectDir)
|
||||
import Wasp.CompileOptions (CompileOptions (generatorWarningsFilter), sendMessage)
|
||||
import qualified Wasp.CompileOptions as CompileOptions
|
||||
import qualified Wasp.ConfigFile as CF
|
||||
import qualified Wasp.Data as Data
|
||||
import Wasp.Error (showCompilerErrorForTerminal)
|
||||
import qualified Wasp.ExternalCode as ExternalCode
|
||||
import qualified Wasp.Generator as Generator
|
||||
import Wasp.Generator.Common (ProjectRootDir)
|
||||
import qualified Wasp.Generator.ConfigFile as G.CF
|
||||
import qualified Wasp.Generator.DockerGenerator as DockerGenerator
|
||||
import qualified Wasp.Generator.Job as J
|
||||
import Wasp.Generator.Job.IO (printJobMsgsUntilExitReceived)
|
||||
import Wasp.Generator.Job.Process (runNodeCommandAsJob)
|
||||
import Wasp.Generator.ServerGenerator.Common (dotEnvServer)
|
||||
import Wasp.Generator.WebAppGenerator.Common (dotEnvClient)
|
||||
import Wasp.Util (maybeToEither, unlessM)
|
||||
import qualified Wasp.Util.IO as IOUtil
|
||||
|
||||
type CompileError = String
|
||||
|
||||
type CompileWarning = String
|
||||
|
||||
compile ::
|
||||
Path' Abs (Dir WaspProjectDir) ->
|
||||
Path' Abs (Dir ProjectRootDir) ->
|
||||
CompileOptions ->
|
||||
IO ([CompileWarning], [CompileError])
|
||||
compile waspDir outDir options = do
|
||||
compileWarningsAndErrors <-
|
||||
analyzeWaspProject waspDir options >>= \case
|
||||
Left analyzerErrors -> return ([], analyzerErrors)
|
||||
Right appSpec -> generateCode appSpec outDir options
|
||||
dotEnvWarnings <- maybeToList <$> warnIfDotEnvPresent waspDir
|
||||
return $ (dotEnvWarnings, []) <> compileWarningsAndErrors
|
||||
|
||||
analyzeWaspProject ::
|
||||
Path' Abs (Dir WaspProjectDir) ->
|
||||
CompileOptions ->
|
||||
IO (Either [CompileError] AS.AppSpec)
|
||||
analyzeWaspProject waspDir options = runExceptT $ do
|
||||
waspFilePath <- ExceptT $ left pure <$> findWaspFile waspDir
|
||||
declarations <- ExceptT $ left pure <$> analyzeWaspFileContent waspFilePath
|
||||
ExceptT $ constructAppSpec waspDir options declarations
|
||||
|
||||
generateCode ::
|
||||
AS.AppSpec ->
|
||||
Path' Abs (Dir ProjectRootDir) ->
|
||||
CompileOptions ->
|
||||
IO ([CompileError], [CompileWarning])
|
||||
generateCode appSpec outDir options = do
|
||||
(generatorWarnings, generatorErrors) <- Generator.writeWebAppCode appSpec outDir (sendMessage options)
|
||||
let filteredWarnings = generatorWarningsFilter options generatorWarnings
|
||||
return (show <$> filteredWarnings, show <$> generatorErrors)
|
||||
|
||||
-- | Checks the wasp directory for potential problems, and issues warnings if any are found.
|
||||
warnIfDotEnvPresent :: Path' Abs (Dir WaspProjectDir) -> IO (Maybe CompileWarning)
|
||||
warnIfDotEnvPresent waspDir = (warningMessage <$) <$> findDotEnv waspDir
|
||||
where
|
||||
warningMessage = "Wasp .env files should be named .env.server or .env.client, depending on their use."
|
||||
|
||||
analyzeWaspFileContent :: Path' Abs File' -> IO (Either CompileError [AS.Decl])
|
||||
analyzeWaspFileContent waspFilePath = do
|
||||
waspFileContent <- IOUtil.readFile waspFilePath
|
||||
let declsOrAnalyzeError = Analyzer.analyze waspFileContent
|
||||
return $
|
||||
left
|
||||
(showCompilerErrorForTerminal (waspFilePath, waspFileContent) . getErrorMessageAndCtx)
|
||||
declsOrAnalyzeError
|
||||
|
||||
constructAppSpec ::
|
||||
Path' Abs (Dir WaspProjectDir) ->
|
||||
CompileOptions ->
|
||||
[AS.Decl] ->
|
||||
IO (Either [CompileError] AS.AppSpec)
|
||||
constructAppSpec waspDir options decls = do
|
||||
externalServerCodeFiles <-
|
||||
ExternalCode.readFiles (CompileOptions.externalServerCodeDirPath options)
|
||||
externalClientCodeFiles <-
|
||||
ExternalCode.readFiles (CompileOptions.externalClientCodeDirPath options)
|
||||
externalSharedCodeFiles <-
|
||||
ExternalCode.readFiles (CompileOptions.externalSharedCodeDirPath options)
|
||||
maybeDotEnvServerFile <- findDotEnvServer waspDir
|
||||
maybeDotEnvClientFile <- findDotEnvClient waspDir
|
||||
maybeMigrationsDir <- findMigrationsDir waspDir
|
||||
maybeUserDockerfileContents <- loadUserDockerfileContents waspDir
|
||||
configFiles <- CF.discoverConfigFiles waspDir G.CF.configFileRelocationMap
|
||||
let appSpec =
|
||||
AS.AppSpec
|
||||
{ AS.decls = decls,
|
||||
AS.waspProjectDir = waspDir,
|
||||
AS.externalClientFiles = externalClientCodeFiles,
|
||||
AS.externalServerFiles = externalServerCodeFiles,
|
||||
AS.externalSharedFiles = externalSharedCodeFiles,
|
||||
AS.migrationsDir = maybeMigrationsDir,
|
||||
AS.dotEnvServerFile = maybeDotEnvServerFile,
|
||||
AS.dotEnvClientFile = maybeDotEnvClientFile,
|
||||
AS.isBuild = CompileOptions.isBuild options,
|
||||
AS.userDockerfileContents = maybeUserDockerfileContents,
|
||||
AS.configFiles = configFiles
|
||||
}
|
||||
return $ case validateAppSpec appSpec of
|
||||
[] -> Right appSpec
|
||||
validationErrors -> Left $ map show validationErrors
|
||||
|
||||
findWaspFile :: Path' Abs (Dir WaspProjectDir) -> IO (Either String (Path' Abs File'))
|
||||
findWaspFile waspDir = do
|
||||
files <- fst <$> IOUtil.listDirectory waspDir
|
||||
return $ maybeToEither "Couldn't find a single *.wasp file." $ (waspDir </>) <$> find isWaspFile files
|
||||
where
|
||||
isWaspFile path =
|
||||
".wasp" `isSuffixOf` toFilePath path
|
||||
&& (length (toFilePath path) > length (".wasp" :: String))
|
||||
|
||||
findDotEnvServer :: Path' Abs (Dir WaspProjectDir) -> IO (Maybe (Path' Abs File'))
|
||||
findDotEnvServer waspDir = findFileInWaspProjectDir waspDir dotEnvServer
|
||||
|
||||
findDotEnvClient :: Path' Abs (Dir WaspProjectDir) -> IO (Maybe (Path' Abs File'))
|
||||
findDotEnvClient waspDir = findFileInWaspProjectDir waspDir dotEnvClient
|
||||
|
||||
findDotEnv :: Path' Abs (Dir WaspProjectDir) -> IO (Maybe (Path' Abs File'))
|
||||
findDotEnv waspDir = findFileInWaspProjectDir waspDir [relfile|.env|]
|
||||
|
||||
findFileInWaspProjectDir ::
|
||||
Path' Abs (Dir WaspProjectDir) ->
|
||||
Path' (Rel WaspProjectDir) File' ->
|
||||
IO (Maybe (Path' Abs File'))
|
||||
findFileInWaspProjectDir waspDir file = do
|
||||
let fileAbsFp = waspDir </> file
|
||||
fileExists <- doesFileExist $ toFilePath fileAbsFp
|
||||
return $ if fileExists then Just fileAbsFp else Nothing
|
||||
|
||||
findMigrationsDir ::
|
||||
Path' Abs (Dir WaspProjectDir) ->
|
||||
IO (Maybe (Path' Abs (Dir DbMigrationsDir)))
|
||||
findMigrationsDir waspDir = do
|
||||
let migrationsAbsPath = waspDir </> dbMigrationsDirInWaspProjectDir
|
||||
migrationsExists <- doesDirectoryExist $ fromAbsDir migrationsAbsPath
|
||||
return $ if migrationsExists then Just migrationsAbsPath else Nothing
|
||||
|
||||
loadUserDockerfileContents :: Path' Abs (Dir WaspProjectDir) -> IO (Maybe Text)
|
||||
loadUserDockerfileContents waspDir = do
|
||||
let dockerfileAbsPath = toFilePath $ waspDir </> [relfile|Dockerfile|]
|
||||
whenMaybeM (doesFileExist dockerfileAbsPath) $ T.IO.readFile dockerfileAbsPath
|
||||
|
||||
compileAndRenderDockerfile :: Path' Abs (Dir WaspProjectDir) -> CompileOptions -> IO (Either [CompileError] Text)
|
||||
compileAndRenderDockerfile waspDir compileOptions = do
|
||||
appSpecOrAnalyzerErrors <- analyzeWaspProject waspDir compileOptions
|
||||
case appSpecOrAnalyzerErrors of
|
||||
Left errors -> return $ Left errors
|
||||
Right appSpec -> do
|
||||
dockerfileOrGeneratorErrors <- DockerGenerator.compileAndRenderDockerfile appSpec
|
||||
return $ left (map show . toList) dockerfileOrGeneratorErrors
|
||||
|
||||
-- | This will run our TS deploy project by passing all args from the Wasp CLI straight through.
|
||||
-- The TS project is compiled to JS in CI and included in the data dir for the release archive.
|
||||
-- If the project was not yet built locally (i.e. after they just installed a Wasp version), we do so.
|
||||
deploy :: FilePath -> Path' Abs (Dir WaspProjectDir) -> [String] -> IO ()
|
||||
deploy waspExe waspDir cmdArgs = do
|
||||
waspDataDir <- Data.getAbsDataDirPath
|
||||
let deployDir = waspDataDir </> [reldir|packages/deploy|]
|
||||
let nodeModulesDirExists = doesDirectoryExist . toFilePath $ deployDir </> [reldir|node_modules|]
|
||||
unlessM nodeModulesDirExists $
|
||||
runCommandAndPrintOutput $ runNodeCommandAsJob deployDir "npm" ["install"] J.Server
|
||||
let deployScriptArgs = ["dist/index.js"] ++ cmdArgs ++ ["--wasp-exe", waspExe, "--wasp-project-dir", toFilePath waspDir]
|
||||
-- NOTE: Here we are lying by saying we are running in the J.Server context.
|
||||
-- TODO: Consider adding a new context for these types of things, like J.Other or J.External.
|
||||
runCommandAndPrintOutput $ runNodeCommandAsJob deployDir "node" deployScriptArgs J.Server
|
||||
where
|
||||
runCommandAndPrintOutput job = do
|
||||
chan <- newChan
|
||||
void $ concurrently (printJobMsgsUntilExitReceived chan) (job chan)
|
56
waspc/src/Wasp/Project.hs
Normal file
56
waspc/src/Wasp/Project.hs
Normal file
@ -0,0 +1,56 @@
|
||||
-- | "Project" here stands for a Wasp source project, and this module offers
|
||||
-- logic for operating on and processing a Wasp source project, as a whole.
|
||||
module Wasp.Project
|
||||
( WaspProjectDir,
|
||||
compile,
|
||||
CompileError,
|
||||
CompileWarning,
|
||||
analyzeWaspProject,
|
||||
compileAndRenderDockerfile,
|
||||
)
|
||||
where
|
||||
|
||||
import Control.Arrow (ArrowChoice (left))
|
||||
import Data.List.NonEmpty (toList)
|
||||
import Data.Maybe (maybeToList)
|
||||
import Data.Text (Text)
|
||||
import StrongPath (Abs, Dir, Path')
|
||||
import qualified Wasp.AppSpec as AS
|
||||
import Wasp.CompileOptions (CompileOptions (generatorWarningsFilter), sendMessage)
|
||||
import qualified Wasp.Generator as Generator
|
||||
import qualified Wasp.Generator.DockerGenerator as DockerGenerator
|
||||
import Wasp.Project.Analyze (analyzeWaspProject)
|
||||
import Wasp.Project.Common (CompileError, CompileWarning, WaspProjectDir)
|
||||
import qualified Wasp.Project.Env as Project.Env
|
||||
|
||||
compile ::
|
||||
Path' Abs (Dir WaspProjectDir) ->
|
||||
Path' Abs (Dir Generator.ProjectRootDir) ->
|
||||
CompileOptions ->
|
||||
IO ([CompileWarning], [CompileError])
|
||||
compile waspDir outDir options = do
|
||||
compileWarningsAndErrors <-
|
||||
analyzeWaspProject waspDir options >>= \case
|
||||
Left analyzerErrors -> return ([], analyzerErrors)
|
||||
Right appSpec -> generateCode appSpec outDir options
|
||||
dotEnvWarnings <- maybeToList <$> Project.Env.warnIfTheDotEnvPresent waspDir
|
||||
return $ (dotEnvWarnings, []) <> compileWarningsAndErrors
|
||||
|
||||
generateCode ::
|
||||
AS.AppSpec ->
|
||||
Path' Abs (Dir Generator.ProjectRootDir) ->
|
||||
CompileOptions ->
|
||||
IO ([CompileError], [CompileWarning])
|
||||
generateCode appSpec outDir options = do
|
||||
(generatorWarnings, generatorErrors) <- Generator.writeWebAppCode appSpec outDir (sendMessage options)
|
||||
let filteredWarnings = generatorWarningsFilter options generatorWarnings
|
||||
return (show <$> filteredWarnings, show <$> generatorErrors)
|
||||
|
||||
compileAndRenderDockerfile :: Path' Abs (Dir WaspProjectDir) -> CompileOptions -> IO (Either [CompileError] Text)
|
||||
compileAndRenderDockerfile waspDir compileOptions = do
|
||||
appSpecOrAnalyzerErrors <- analyzeWaspProject waspDir compileOptions
|
||||
case appSpecOrAnalyzerErrors of
|
||||
Left errors -> return $ Left errors
|
||||
Right appSpec -> do
|
||||
dockerfileOrGeneratorErrors <- DockerGenerator.compileAndRenderDockerfile appSpec
|
||||
return $ Control.Arrow.left (map show . toList) dockerfileOrGeneratorErrors
|
90
waspc/src/Wasp/Project/Analyze.hs
Normal file
90
waspc/src/Wasp/Project/Analyze.hs
Normal file
@ -0,0 +1,90 @@
|
||||
module Wasp.Project.Analyze
|
||||
( analyzeWaspProject,
|
||||
)
|
||||
where
|
||||
|
||||
import Control.Arrow (ArrowChoice (left))
|
||||
import Control.Monad.Except (ExceptT (ExceptT), runExceptT)
|
||||
import Data.List (find, isSuffixOf)
|
||||
import StrongPath (Abs, Dir, File', Path', toFilePath, (</>))
|
||||
import qualified Wasp.Analyzer as Analyzer
|
||||
import Wasp.Analyzer.AnalyzeError (getErrorMessageAndCtx)
|
||||
import qualified Wasp.AppSpec as AS
|
||||
import Wasp.AppSpec.Valid (validateAppSpec)
|
||||
import Wasp.CompileOptions (CompileOptions)
|
||||
import qualified Wasp.CompileOptions as CompileOptions
|
||||
import qualified Wasp.ConfigFile as CF
|
||||
import Wasp.Error (showCompilerErrorForTerminal)
|
||||
import qualified Wasp.ExternalCode as ExternalCode
|
||||
import qualified Wasp.Generator.ConfigFile as G.CF
|
||||
import Wasp.Project.Common (CompileError, WaspProjectDir)
|
||||
import Wasp.Project.Db (makeDevDatabaseUrl)
|
||||
import Wasp.Project.Db.Migrations (findMigrationsDir)
|
||||
import Wasp.Project.Deployment (loadUserDockerfileContents)
|
||||
import Wasp.Project.Env (readDotEnvClient, readDotEnvServer)
|
||||
import Wasp.Util (maybeToEither)
|
||||
import qualified Wasp.Util.IO as IOUtil
|
||||
|
||||
analyzeWaspProject ::
|
||||
Path' Abs (Dir WaspProjectDir) ->
|
||||
CompileOptions ->
|
||||
IO (Either [CompileError] AS.AppSpec)
|
||||
analyzeWaspProject waspDir options = runExceptT $ do
|
||||
waspFilePath <- ExceptT $ Control.Arrow.left pure <$> findWaspFile waspDir
|
||||
declarations <- ExceptT $ Control.Arrow.left pure <$> analyzeWaspFileContent waspFilePath
|
||||
ExceptT $ constructAppSpec waspDir options declarations
|
||||
|
||||
analyzeWaspFileContent :: Path' Abs File' -> IO (Either CompileError [AS.Decl])
|
||||
analyzeWaspFileContent waspFilePath = do
|
||||
waspFileContent <- IOUtil.readFile waspFilePath
|
||||
let declsOrAnalyzeError = Analyzer.analyze waspFileContent
|
||||
return $
|
||||
Control.Arrow.left
|
||||
(showCompilerErrorForTerminal (waspFilePath, waspFileContent) . getErrorMessageAndCtx)
|
||||
declsOrAnalyzeError
|
||||
|
||||
constructAppSpec ::
|
||||
Path' Abs (Dir WaspProjectDir) ->
|
||||
CompileOptions ->
|
||||
[AS.Decl] ->
|
||||
IO (Either [CompileError] AS.AppSpec)
|
||||
constructAppSpec waspDir options decls = do
|
||||
externalServerCodeFiles <-
|
||||
ExternalCode.readFiles (CompileOptions.externalServerCodeDirPath options)
|
||||
externalClientCodeFiles <-
|
||||
ExternalCode.readFiles (CompileOptions.externalClientCodeDirPath options)
|
||||
externalSharedCodeFiles <-
|
||||
ExternalCode.readFiles (CompileOptions.externalSharedCodeDirPath options)
|
||||
maybeMigrationsDir <- findMigrationsDir waspDir
|
||||
maybeUserDockerfileContents <- loadUserDockerfileContents waspDir
|
||||
configFiles <- CF.discoverConfigFiles waspDir G.CF.configFileRelocationMap
|
||||
let devDbUrl = makeDevDatabaseUrl waspDir decls
|
||||
serverEnvVars <- readDotEnvServer waspDir
|
||||
clientEnvVars <- readDotEnvClient waspDir
|
||||
let appSpec =
|
||||
AS.AppSpec
|
||||
{ AS.decls = decls,
|
||||
AS.waspProjectDir = waspDir,
|
||||
AS.externalClientFiles = externalClientCodeFiles,
|
||||
AS.externalServerFiles = externalServerCodeFiles,
|
||||
AS.externalSharedFiles = externalSharedCodeFiles,
|
||||
AS.migrationsDir = maybeMigrationsDir,
|
||||
AS.devEnvVarsServer = serverEnvVars,
|
||||
AS.devEnvVarsClient = clientEnvVars,
|
||||
AS.isBuild = CompileOptions.isBuild options,
|
||||
AS.userDockerfileContents = maybeUserDockerfileContents,
|
||||
AS.configFiles = configFiles,
|
||||
AS.devDatabaseUrl = devDbUrl
|
||||
}
|
||||
return $ case validateAppSpec appSpec of
|
||||
[] -> Right appSpec
|
||||
validationErrors -> Left $ map show validationErrors
|
||||
|
||||
findWaspFile :: Path' Abs (Dir WaspProjectDir) -> IO (Either String (Path' Abs File'))
|
||||
findWaspFile waspDir = do
|
||||
files <- fst <$> IOUtil.listDirectory waspDir
|
||||
return $ maybeToEither "Couldn't find a single *.wasp file." $ (waspDir </>) <$> find isWaspFile files
|
||||
where
|
||||
isWaspFile path =
|
||||
".wasp" `isSuffixOf` toFilePath path
|
||||
&& (length (toFilePath path) > length (".wasp" :: String))
|
25
waspc/src/Wasp/Project/Common.hs
Normal file
25
waspc/src/Wasp/Project/Common.hs
Normal file
@ -0,0 +1,25 @@
|
||||
module Wasp.Project.Common
|
||||
( findFileInWaspProjectDir,
|
||||
CompileError,
|
||||
CompileWarning,
|
||||
WaspProjectDir,
|
||||
)
|
||||
where
|
||||
|
||||
import StrongPath (Abs, Dir, File', Path', Rel, toFilePath, (</>))
|
||||
import System.Directory (doesFileExist)
|
||||
|
||||
data WaspProjectDir -- Root dir of Wasp project, containing source files.
|
||||
|
||||
type CompileError = String
|
||||
|
||||
type CompileWarning = String
|
||||
|
||||
findFileInWaspProjectDir ::
|
||||
Path' Abs (Dir WaspProjectDir) ->
|
||||
Path' (Rel WaspProjectDir) File' ->
|
||||
IO (Maybe (Path' Abs File'))
|
||||
findFileInWaspProjectDir waspDir file = do
|
||||
let fileAbsFp = waspDir </> file
|
||||
fileExists <- doesFileExist $ toFilePath fileAbsFp
|
||||
return $ if fileExists then Just fileAbsFp else Nothing
|
23
waspc/src/Wasp/Project/Db.hs
Normal file
23
waspc/src/Wasp/Project/Db.hs
Normal file
@ -0,0 +1,23 @@
|
||||
module Wasp.Project.Db
|
||||
( makeDevDatabaseUrl,
|
||||
databaseUrlEnvVarName,
|
||||
)
|
||||
where
|
||||
|
||||
import StrongPath (Abs, Dir, Path')
|
||||
import qualified Wasp.AppSpec as AS
|
||||
import qualified Wasp.AppSpec.App as AS.App
|
||||
import qualified Wasp.AppSpec.App.Db as AS.App.Db
|
||||
import Wasp.Project.Common (WaspProjectDir)
|
||||
import qualified Wasp.Project.Db.Dev.Postgres as DevPostgres
|
||||
|
||||
makeDevDatabaseUrl :: Path' Abs (Dir WaspProjectDir) -> [AS.Decl] -> Maybe String
|
||||
makeDevDatabaseUrl waspProjectDir decls = do
|
||||
(appName, app) <- AS.getApp decls
|
||||
dbSystem <- AS.App.Db.system =<< AS.App.db app
|
||||
case dbSystem of
|
||||
AS.App.Db.SQLite -> Nothing
|
||||
AS.App.Db.PostgreSQL -> Just $ DevPostgres.makeDevConnectionUrl waspProjectDir appName
|
||||
|
||||
databaseUrlEnvVarName :: String
|
||||
databaseUrlEnvVarName = "DATABASE_URL"
|
25
waspc/src/Wasp/Project/Db/Dev.hs
Normal file
25
waspc/src/Wasp/Project/Db/Dev.hs
Normal file
@ -0,0 +1,25 @@
|
||||
-- | This module and modules under it capture what Wasp knows about its dev database and running it.
|
||||
module Wasp.Project.Db.Dev
|
||||
( makeDevDbUniqueId,
|
||||
)
|
||||
where
|
||||
|
||||
import Data.Char (isAsciiLower, isAsciiUpper, isDigit)
|
||||
import StrongPath (Abs, Dir, Path', fromAbsDir)
|
||||
import Wasp.Project.Common (WaspProjectDir)
|
||||
import qualified Wasp.Util as U
|
||||
|
||||
-- Returns a unique id that can be used for global identification of dev db tied to
|
||||
-- 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.
|
||||
-- TODO: Consider making this more general, make it a unique id of a project that can be used to
|
||||
-- track (global) resources. So it would be not just for the db, but for anything.
|
||||
makeDevDbUniqueId :: Path' Abs (Dir WaspProjectDir) -> String -> String
|
||||
makeDevDbUniqueId 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 == '-'
|
38
waspc/src/Wasp/Project/Db/Dev/Postgres.hs
Normal file
38
waspc/src/Wasp/Project/Db/Dev/Postgres.hs
Normal file
@ -0,0 +1,38 @@
|
||||
-- | This module captures how Wasp runs a PostgreSQL dev database.
|
||||
module Wasp.Project.Db.Dev.Postgres
|
||||
( defaultDevUser,
|
||||
makeDevDbName,
|
||||
defaultDevPass,
|
||||
defaultDevPort,
|
||||
makeDevConnectionUrl,
|
||||
)
|
||||
where
|
||||
|
||||
import StrongPath (Abs, Dir, Path')
|
||||
import Wasp.Db.Postgres (makeConnectionUrl, postgresMaxDbNameLength)
|
||||
import Wasp.Project.Common (WaspProjectDir)
|
||||
import Wasp.Project.Db.Dev (makeDevDbUniqueId)
|
||||
|
||||
defaultDevUser :: String
|
||||
defaultDevUser = "postgresWaspDevUser"
|
||||
|
||||
defaultDevPass :: String
|
||||
defaultDevPass = "postgresWaspDevPass"
|
||||
|
||||
-- | Returns a db name that is unique for this Wasp project.
|
||||
-- It depends on projects path and name, so if any of those change,
|
||||
-- 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"
|
||||
-- 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
|
||||
|
||||
defaultDevPort :: Int
|
||||
defaultDevPort = 5432 -- 5432 is default port for PostgreSQL db.
|
||||
|
||||
makeDevConnectionUrl :: Path' Abs (Dir WaspProjectDir) -> String -> String
|
||||
makeDevConnectionUrl waspProjectDir appName =
|
||||
makeConnectionUrl defaultDevUser defaultDevPass defaultDevPort $ makeDevDbName waspProjectDir appName
|
23
waspc/src/Wasp/Project/Db/Migrations.hs
Normal file
23
waspc/src/Wasp/Project/Db/Migrations.hs
Normal file
@ -0,0 +1,23 @@
|
||||
module Wasp.Project.Db.Migrations
|
||||
( DbMigrationsDir,
|
||||
dbMigrationsDirInWaspProjectDir,
|
||||
findMigrationsDir,
|
||||
)
|
||||
where
|
||||
|
||||
import StrongPath (Abs, Dir, Path', Rel, fromAbsDir, reldir, (</>))
|
||||
import System.Directory (doesDirectoryExist)
|
||||
import Wasp.Project.Common (WaspProjectDir)
|
||||
|
||||
data DbMigrationsDir
|
||||
|
||||
dbMigrationsDirInWaspProjectDir :: Path' (Rel WaspProjectDir) (Dir DbMigrationsDir)
|
||||
dbMigrationsDirInWaspProjectDir = [reldir|migrations|]
|
||||
|
||||
findMigrationsDir ::
|
||||
Path' Abs (Dir WaspProjectDir) ->
|
||||
IO (Maybe (Path' Abs (Dir DbMigrationsDir)))
|
||||
findMigrationsDir waspDir = do
|
||||
let migrationsAbsPath = waspDir </> dbMigrationsDirInWaspProjectDir
|
||||
migrationsExists <- doesDirectoryExist $ fromAbsDir migrationsAbsPath
|
||||
return $ if migrationsExists then Just migrationsAbsPath else Nothing
|
44
waspc/src/Wasp/Project/Deployment.hs
Normal file
44
waspc/src/Wasp/Project/Deployment.hs
Normal file
@ -0,0 +1,44 @@
|
||||
module Wasp.Project.Deployment
|
||||
( loadUserDockerfileContents,
|
||||
deploy,
|
||||
)
|
||||
where
|
||||
|
||||
import Control.Concurrent (newChan)
|
||||
import Control.Concurrent.Async (concurrently)
|
||||
import Control.Monad.Except (void)
|
||||
import Control.Monad.Extra (whenMaybeM)
|
||||
import Data.Text (Text)
|
||||
import qualified Data.Text.IO as T.IO
|
||||
import StrongPath (Abs, Dir, Path', reldir, relfile, toFilePath, (</>))
|
||||
import System.Directory (doesDirectoryExist, doesFileExist)
|
||||
import qualified Wasp.Data as Data
|
||||
import qualified Wasp.Generator.Job as J
|
||||
import Wasp.Generator.Job.IO (printJobMsgsUntilExitReceived)
|
||||
import Wasp.Generator.Job.Process (runNodeCommandAsJob)
|
||||
import Wasp.Project.Common (WaspProjectDir)
|
||||
import Wasp.Util (unlessM)
|
||||
|
||||
loadUserDockerfileContents :: Path' Abs (Dir WaspProjectDir) -> IO (Maybe Text)
|
||||
loadUserDockerfileContents waspDir = do
|
||||
let dockerfileAbsPath = toFilePath $ waspDir </> [relfile|Dockerfile|]
|
||||
whenMaybeM (doesFileExist dockerfileAbsPath) $ T.IO.readFile dockerfileAbsPath
|
||||
|
||||
-- | This will run our TS deploy project by passing all args from the Wasp CLI straight through.
|
||||
-- The TS project is compiled to JS in CI and included in the data dir for the release archive.
|
||||
-- If the project was not yet built locally (i.e. after they just installed a Wasp version), we do so.
|
||||
deploy :: FilePath -> Path' Abs (Dir WaspProjectDir) -> [String] -> IO ()
|
||||
deploy waspExe waspDir cmdArgs = do
|
||||
waspDataDir <- Data.getAbsDataDirPath
|
||||
let deployDir = waspDataDir </> [reldir|packages/deploy|]
|
||||
let nodeModulesDirExists = doesDirectoryExist . toFilePath $ deployDir </> [reldir|node_modules|]
|
||||
unlessM nodeModulesDirExists $
|
||||
runCommandAndPrintOutput $ runNodeCommandAsJob deployDir "npm" ["install"] J.Server
|
||||
let deployScriptArgs = ["dist/index.js"] ++ cmdArgs ++ ["--wasp-exe", waspExe, "--wasp-project-dir", toFilePath waspDir]
|
||||
-- NOTE: Here we are lying by saying we are running in the J.Server context.
|
||||
-- TODO: Consider adding a new context for these types of things, like J.Other or J.External.
|
||||
runCommandAndPrintOutput $ runNodeCommandAsJob deployDir "node" deployScriptArgs J.Server
|
||||
where
|
||||
runCommandAndPrintOutput job = do
|
||||
chan <- newChan
|
||||
void $ concurrently (printJobMsgsUntilExitReceived chan) (job chan)
|
45
waspc/src/Wasp/Project/Env.hs
Normal file
45
waspc/src/Wasp/Project/Env.hs
Normal file
@ -0,0 +1,45 @@
|
||||
module Wasp.Project.Env
|
||||
( readDotEnvServer,
|
||||
readDotEnvClient,
|
||||
warnIfTheDotEnvPresent,
|
||||
dotEnvServer,
|
||||
dotEnvClient,
|
||||
)
|
||||
where
|
||||
|
||||
import StrongPath (Abs, Dir, File', Path', Rel, relfile)
|
||||
import Wasp.Env (EnvVar, parseDotEnvFile)
|
||||
import Wasp.Project.Common (CompileWarning, WaspProjectDir, findFileInWaspProjectDir)
|
||||
|
||||
dotEnvServer :: Path' (Rel WaspProjectDir) File'
|
||||
dotEnvServer = [relfile|.env.server|]
|
||||
|
||||
dotEnvClient :: Path' (Rel WaspProjectDir) File'
|
||||
dotEnvClient = [relfile|.env.client|]
|
||||
|
||||
readDotEnvServer :: Path' Abs (Dir WaspProjectDir) -> IO [EnvVar]
|
||||
readDotEnvServer waspDir = readDotEnvFileInWaspProjectDir waspDir dotEnvServer
|
||||
|
||||
readDotEnvClient :: Path' Abs (Dir WaspProjectDir) -> IO [EnvVar]
|
||||
readDotEnvClient waspDir = readDotEnvFileInWaspProjectDir waspDir dotEnvServer
|
||||
|
||||
-- | Checks if .env exists in wasp dir, and produces a warning if so.
|
||||
-- We have this function because Wasp doesn't use ".env", but still user
|
||||
-- might assume it does and then bother quite a bit trying to figure it out,
|
||||
-- so this way we warn if they assume so.
|
||||
warnIfTheDotEnvPresent :: Path' Abs (Dir WaspProjectDir) -> IO (Maybe CompileWarning)
|
||||
warnIfTheDotEnvPresent waspDir = (warningMessage <$) <$> findFileInWaspProjectDir waspDir [relfile|.env|]
|
||||
where
|
||||
warningMessage = "Wasp .env files should be named .env.server or .env.client, depending on their use."
|
||||
|
||||
-- Reads specified dotenv file and returns its values.
|
||||
-- If file doesn't exist, returns an empty list.
|
||||
-- If file can't be parsed, it will crash with an error.
|
||||
readDotEnvFileInWaspProjectDir ::
|
||||
Path' Abs (Dir WaspProjectDir) ->
|
||||
Path' (Rel WaspProjectDir) File' ->
|
||||
IO [(String, String)]
|
||||
readDotEnvFileInWaspProjectDir waspDir envFileInWaspDir = do
|
||||
findFileInWaspProjectDir waspDir envFileInWaspDir >>= \case
|
||||
Nothing -> return []
|
||||
Just envFile -> parseDotEnvFile envFile
|
@ -32,11 +32,12 @@ module Wasp.Util
|
||||
orIfNothingM,
|
||||
kebabToCamelCase,
|
||||
maybeToEither,
|
||||
whenM,
|
||||
)
|
||||
where
|
||||
|
||||
import Control.Applicative (liftA2)
|
||||
import Control.Monad (unless)
|
||||
import Control.Monad (unless, when)
|
||||
import qualified Crypto.Hash.SHA256 as SHA256
|
||||
import qualified Data.Aeson as Aeson
|
||||
import qualified Data.ByteString as B
|
||||
@ -180,6 +181,9 @@ infixr 5 <:>
|
||||
ifM :: Monad m => m Bool -> m a -> m a -> m a
|
||||
ifM p x y = p >>= \b -> if b then x else y
|
||||
|
||||
whenM :: Monad m => m Bool -> m () -> m ()
|
||||
whenM ma mb = ma >>= (`when` mb)
|
||||
|
||||
unlessM :: Monad m => m Bool -> m () -> m ()
|
||||
unlessM ma mb = ma >>= (`unless` mb)
|
||||
|
||||
|
81
waspc/src/Wasp/Util/Network/Socket.hs
Normal file
81
waspc/src/Wasp/Util/Network/Socket.hs
Normal file
@ -0,0 +1,81 @@
|
||||
module Wasp.Util.Network.Socket
|
||||
( checkIfPortIsAcceptingConnections,
|
||||
checkIfPortIsInUse,
|
||||
checkIfPortCanBeOpened,
|
||||
makeSocketAddress,
|
||||
makeLocalHostSocketAddress,
|
||||
IPv4,
|
||||
)
|
||||
where
|
||||
|
||||
import Data.Word (Word8)
|
||||
import Foreign.C.Error (Errno (..), eADDRINUSE, eCONNREFUSED)
|
||||
import GHC.IO.Exception (IOException (..))
|
||||
import qualified Network.Socket as S
|
||||
import UnliftIO.Exception (bracket, throwIO, try)
|
||||
|
||||
-- | Tests if port is accepting connections.
|
||||
-- Does so by trying to connect via socket to it (connection is closed immediately).
|
||||
-- It returns True if connection succeeds, or False if connection is refused
|
||||
-- (because port is not opened, nobody is listening on it).
|
||||
-- Rethrows connection exceptions in all other cases (e.g. when the host
|
||||
-- is unroutable).
|
||||
checkIfPortIsAcceptingConnections :: S.SockAddr -> IO Bool
|
||||
checkIfPortIsAcceptingConnections sockAddr = do
|
||||
bracket createSocket S.close' $ \sock ->
|
||||
try
|
||||
( do
|
||||
S.connect sock sockAddr
|
||||
)
|
||||
>>= \case
|
||||
Right () -> return True
|
||||
Left e ->
|
||||
if isConnRefusedException e
|
||||
then return False
|
||||
else throwIO e
|
||||
where
|
||||
createSocket = createIPv4TCPSocket
|
||||
isConnRefusedException e = (Errno <$> ioe_errno e) == Just eCONNREFUSED
|
||||
|
||||
-- | True if port is in use, False if it is free, exception in all other cases.
|
||||
checkIfPortIsInUse :: S.SockAddr -> IO Bool
|
||||
checkIfPortIsInUse = (not <$>) . checkIfPortCanBeOpened
|
||||
|
||||
-- | Tests if port can be opened.
|
||||
-- Does so by trying to bind a socket to it (and then closing it immediately).
|
||||
-- Returns True if it can be opened, False if it is already in use, and throws
|
||||
-- an exception in all other cases (e.g. when the host is unroutable).
|
||||
checkIfPortCanBeOpened :: S.SockAddr -> IO Bool
|
||||
checkIfPortCanBeOpened sockAddr = do
|
||||
bracket createSocket S.close' $ \sock ->
|
||||
try
|
||||
( do
|
||||
S.bind sock sockAddr
|
||||
S.listen sock queueLength
|
||||
)
|
||||
>>= \case
|
||||
Right () -> return True
|
||||
Left e ->
|
||||
if isAddrInUseException e
|
||||
then return False
|
||||
else throwIO e
|
||||
where
|
||||
createSocket = do
|
||||
sock <- createIPv4TCPSocket
|
||||
S.setSocketOption sock S.ReuseAddr 1 -- Connect even if port is in TIME_WAIT state.
|
||||
return sock
|
||||
queueLength = 1
|
||||
isAddrInUseException e = (Errno <$> ioe_errno e) == Just eADDRINUSE
|
||||
|
||||
createIPv4TCPSocket :: IO S.Socket
|
||||
createIPv4TCPSocket = S.socket S.AF_INET S.Stream S.defaultProtocol
|
||||
|
||||
-- | Creates a socket address from host IP and port number.
|
||||
-- > makeSocketAddress (127,0,0,1) 8000
|
||||
makeSocketAddress :: IPv4 -> S.PortNumber -> S.SockAddr
|
||||
makeSocketAddress hostIp port = S.SockAddrInet port $ S.tupleToHostAddress hostIp
|
||||
|
||||
makeLocalHostSocketAddress :: S.PortNumber -> S.SockAddr
|
||||
makeLocalHostSocketAddress = makeSocketAddress (127, 0, 0, 1)
|
||||
|
||||
type IPv4 = (Word8, Word8, Word8, Word8)
|
@ -199,10 +199,11 @@ spec_AppSpecValid = do
|
||||
AS.externalSharedFiles = [],
|
||||
AS.isBuild = False,
|
||||
AS.migrationsDir = Nothing,
|
||||
AS.dotEnvServerFile = Nothing,
|
||||
AS.dotEnvClientFile = Nothing,
|
||||
AS.devEnvVarsClient = [],
|
||||
AS.devEnvVarsServer = [],
|
||||
AS.userDockerfileContents = Nothing,
|
||||
AS.configFiles = []
|
||||
AS.configFiles = [],
|
||||
AS.devDatabaseUrl = Nothing
|
||||
}
|
||||
|
||||
basicPage =
|
||||
|
@ -0,0 +1,27 @@
|
||||
module Generator.FileDraft.CopyAndModifyTextFileDraftTest where
|
||||
|
||||
import Fixtures (systemSPRoot)
|
||||
import qualified Generator.MockWriteableMonad as Mock
|
||||
import StrongPath (parent, reldir, relfile, toFilePath, (</>))
|
||||
import Test.Tasty.Hspec
|
||||
import Wasp.Generator.FileDraft
|
||||
|
||||
spec_CopyAndModifyTextFileDraft :: Spec
|
||||
spec_CopyAndModifyTextFileDraft = do
|
||||
describe "write" $ do
|
||||
it "Creates new file by copying existing text file and applying modifications to it" $ do
|
||||
let mock = write dstDir fileDraft
|
||||
let mockLogs = Mock.getMockLogs mock Mock.defaultMockConfig
|
||||
Mock.createDirectoryIfMissing_calls mockLogs
|
||||
`shouldBe` [(True, toFilePath $ parent expectedDstPath)]
|
||||
Mock.readFileAsText_calls mockLogs
|
||||
`shouldBe` [toFilePath expectedSrcPath]
|
||||
Mock.writeFileFromText_calls mockLogs
|
||||
`shouldBe` [(toFilePath expectedDstPath, "First line\nMock text file content")]
|
||||
where
|
||||
dstDir = systemSPRoot </> [reldir|a/b|]
|
||||
dstPath = [relfile|c/d/dst.txt|]
|
||||
srcPath = systemSPRoot </> [relfile|e/src.txt|]
|
||||
fileDraft = createCopyAndModifyTextFileDraft dstPath srcPath ("First line\n" <>)
|
||||
expectedSrcPath = srcPath
|
||||
expectedDstPath = dstDir </> dstPath
|
@ -33,18 +33,24 @@ defaultMockConfig =
|
||||
getTemplateFileAbsPath_impl = \path -> systemSPRoot </> [reldir|mock/templates/dir|] </> path,
|
||||
compileAndRenderTemplate_impl = \_ _ -> pack "Mock template content",
|
||||
doesFileExist_impl = const True,
|
||||
doesDirectoryExist_impl = const True
|
||||
doesDirectoryExist_impl = const True,
|
||||
readFileAsText_impl = \_ -> pack "Mock text file content"
|
||||
}
|
||||
|
||||
getMockLogs :: MockWriteableMonad a -> MockWriteableMonadConfig -> MockWriteableMonadLogs
|
||||
getMockLogs mock config = fst $ execState (unMockWriteableMonad mock) (emptyLogs, config)
|
||||
where
|
||||
emptyLogs = MockWriteableMonadLogs [] [] [] [] [] [] [] []
|
||||
emptyLogs = MockWriteableMonadLogs [] [] [] [] [] [] [] [] []
|
||||
|
||||
instance WriteableMonad MockWriteableMonad where
|
||||
writeFileFromText dstPath text = MockWriteableMonad $ do
|
||||
modifyLogs (writeFileFromText_addCall dstPath text)
|
||||
|
||||
readFileAsText srcPath = MockWriteableMonad $ do
|
||||
modifyLogs (readFileAsText_addCall srcPath)
|
||||
(_, config) <- get
|
||||
return $ readFileAsText_impl config srcPath
|
||||
|
||||
getTemplatesDirAbsPath = MockWriteableMonad $ do
|
||||
modifyLogs getTemplatesDirAbsPath_addCall
|
||||
(_, config) <- get
|
||||
@ -95,6 +101,7 @@ newtype MockWriteableMonad a = MockWriteableMonad
|
||||
|
||||
data MockWriteableMonadLogs = MockWriteableMonadLogs
|
||||
{ writeFileFromText_calls :: [(FilePath, Text)],
|
||||
readFileAsText_calls :: [FilePath],
|
||||
getTemplatesDirAbsPath_calls :: [()],
|
||||
createDirectoryIfMissing_calls :: [(Bool, FilePath)],
|
||||
copyFile_calls :: [(FilePath, FilePath)],
|
||||
@ -109,13 +116,18 @@ data MockWriteableMonadConfig = MockWriteableMonadConfig
|
||||
getTemplateFileAbsPath_impl :: forall a. Path' (Rel TemplatesDir) (File a) -> Path' Abs (File a),
|
||||
compileAndRenderTemplate_impl :: forall a. Path' (Rel TemplatesDir) (File a) -> Aeson.Value -> Text,
|
||||
doesFileExist_impl :: FilePath -> Bool,
|
||||
doesDirectoryExist_impl :: FilePath -> Bool
|
||||
doesDirectoryExist_impl :: FilePath -> Bool,
|
||||
readFileAsText_impl :: FilePath -> Text
|
||||
}
|
||||
|
||||
writeFileFromText_addCall :: FilePath -> Text -> MockWriteableMonadLogs -> MockWriteableMonadLogs
|
||||
writeFileFromText_addCall path text logs =
|
||||
logs {writeFileFromText_calls = (path, text) : writeFileFromText_calls logs}
|
||||
|
||||
readFileAsText_addCall :: FilePath -> MockWriteableMonadLogs -> MockWriteableMonadLogs
|
||||
readFileAsText_addCall path logs =
|
||||
logs {readFileAsText_calls = path : readFileAsText_calls logs}
|
||||
|
||||
getTemplatesDirAbsPath_addCall :: MockWriteableMonadLogs -> MockWriteableMonadLogs
|
||||
getTemplatesDirAbsPath_addCall logs =
|
||||
logs {getTemplatesDirAbsPath_calls = () : getTemplatesDirAbsPath_calls logs}
|
||||
|
@ -9,6 +9,7 @@ import qualified Wasp.AppSpec.App as AS.App
|
||||
import qualified Wasp.AppSpec.App.Wasp as AS.Wasp
|
||||
import qualified Wasp.AppSpec.Core.Decl as AS.Decl
|
||||
import Wasp.Generator.FileDraft
|
||||
import qualified Wasp.Generator.FileDraft.CopyAndModifyTextFileDraft as CMTextFD
|
||||
import qualified Wasp.Generator.FileDraft.CopyDirFileDraft as CopyDirFD
|
||||
import qualified Wasp.Generator.FileDraft.CopyFileDraft as CopyFD
|
||||
import qualified Wasp.Generator.FileDraft.TemplateFileDraft as TmplFD
|
||||
@ -48,10 +49,11 @@ spec_WebAppGenerator = do
|
||||
AS.externalSharedFiles = [],
|
||||
AS.isBuild = False,
|
||||
AS.migrationsDir = Nothing,
|
||||
AS.dotEnvServerFile = Nothing,
|
||||
AS.dotEnvClientFile = Nothing,
|
||||
AS.devEnvVarsServer = [],
|
||||
AS.devEnvVarsClient = [],
|
||||
AS.userDockerfileContents = Nothing,
|
||||
AS.configFiles = []
|
||||
AS.configFiles = [],
|
||||
AS.devDatabaseUrl = Nothing
|
||||
}
|
||||
|
||||
describe "genWebApp" $ do
|
||||
@ -101,3 +103,4 @@ getFileDraftDstPath (FileDraftTemplateFd fd) = SP.toFilePath $ TmplFD._dstPath f
|
||||
getFileDraftDstPath (FileDraftCopyFd fd) = SP.toFilePath $ CopyFD._dstPath fd
|
||||
getFileDraftDstPath (FileDraftCopyDirFd fd) = SP.toFilePath $ CopyDirFD._dstPath fd
|
||||
getFileDraftDstPath (FileDraftTextFd fd) = SP.toFilePath $ TextFD._dstPath fd
|
||||
getFileDraftDstPath (FileDraftCopyAndModifyTextFd fd) = SP.toFilePath $ CMTextFD._dstPath fd
|
||||
|
@ -1,3 +1,5 @@
|
||||
{-# OPTIONS_GHC -Wno-orphans #-}
|
||||
|
||||
module Generator.WriteFileDraftsTest where
|
||||
|
||||
import Data.Bifunctor (Bifunctor (first))
|
||||
@ -34,6 +36,14 @@ spec_WriteDuplicatedDstFileDrafts =
|
||||
let fileDrafts = map FileDraftTextFd (genMockTextFileDrafts 2)
|
||||
in (return $! assertDstPathsAreUnique fileDrafts) `shouldReturn` ()
|
||||
|
||||
-- NOTE: Very weak show function, but it is good enough for the tests below.
|
||||
instance Show FileDraft where
|
||||
show fd = "FileDraft {dstPath = " ++ show (getDstPath fd) ++ "}"
|
||||
|
||||
-- NOTE: Very weak eq function, but it is good enough for the tests below.
|
||||
instance Eq FileDraft where
|
||||
fd1 == fd2 = getDstPath fd1 == getDstPath fd2
|
||||
|
||||
spec_WriteFileDrafts :: Spec
|
||||
spec_WriteFileDrafts =
|
||||
describe "fileDraftsToWriteAndFilesToDelete" $ do
|
||||
|
@ -1,7 +1,7 @@
|
||||
module WaspignoreFileTest where
|
||||
|
||||
import Test.Tasty.Hspec
|
||||
import Test.Tasty.QuickCheck (property)
|
||||
import Test.Tasty.QuickCheck (arbitraryPrintableChar, forAll, listOf, property)
|
||||
import Wasp.WaspignoreFile (ignores, parseWaspignoreFile)
|
||||
|
||||
spec_IgnoreFile :: Spec
|
||||
@ -15,26 +15,31 @@ spec_IgnoreFile = do
|
||||
|
||||
it "When given a blank input, should match only '.waspignore'" $ do
|
||||
let ignoreFile = parseWaspignoreFile ""
|
||||
property $ \fp ->
|
||||
if fp == ".waspignore"
|
||||
then ignoreFile `ignores` fp
|
||||
else not $ ignoreFile `ignores` fp
|
||||
property $
|
||||
forAll (listOf arbitraryPrintableChar) $ \fp ->
|
||||
if fp == ".waspignore"
|
||||
then ignoreFile `ignores` fp
|
||||
else not $ ignoreFile `ignores` fp
|
||||
|
||||
it "When given a comment as the only line, should match only '.waspignore'" $ do
|
||||
let ignoreFile = parseWaspignoreFile "# test comment"
|
||||
property $ \fp ->
|
||||
if fp == ".waspignore"
|
||||
then ignoreFile `ignores` fp
|
||||
else not $ ignoreFile `ignores` fp
|
||||
property $
|
||||
forAll (listOf arbitraryPrintableChar) $ \fp ->
|
||||
if fp == ".waspignore"
|
||||
then ignoreFile `ignores` fp
|
||||
else not $ ignoreFile `ignores` fp
|
||||
|
||||
it "When the only difference between two files is a comment, the files should match the same strings" $ do
|
||||
let comment = "\n# test comment"
|
||||
property $ \pat fp ->
|
||||
(parseWaspignoreFile pat `ignores` fp)
|
||||
== (parseWaspignoreFile (pat ++ comment) `ignores` fp)
|
||||
property $
|
||||
forAll (listOf arbitraryPrintableChar) $ \pat fp ->
|
||||
(parseWaspignoreFile pat `ignores` fp)
|
||||
== (parseWaspignoreFile (pat ++ comment) `ignores` fp)
|
||||
|
||||
it "When given 2 patterns, should match the path if either of the patterns match" $ do
|
||||
let pat1 = parseWaspignoreFile "a"
|
||||
let pat2 = parseWaspignoreFile "b"
|
||||
let patBoth = parseWaspignoreFile "a\nb"
|
||||
property $ \fp -> patBoth `ignores` fp == (pat1 `ignores` fp || pat2 `ignores` fp)
|
||||
property $
|
||||
forAll (listOf arbitraryPrintableChar) $ \fp ->
|
||||
patBoth `ignores` fp == (pat1 `ignores` fp || pat2 `ignores` fp)
|
||||
|
@ -6,7 +6,7 @@ cabal-version: 2.4
|
||||
-- Consider using hpack, or maybe even hpack-dhall.
|
||||
|
||||
name: waspc
|
||||
version: 0.9.0
|
||||
version: 0.9.1
|
||||
description: Please see the README on GitHub at <https://github.com/wasp-lang/wasp/waspc#readme>
|
||||
homepage: https://github.com/wasp-lang/wasp/waspc#readme
|
||||
bug-reports: https://github.com/wasp-lang/wasp/issues
|
||||
@ -124,6 +124,8 @@ library
|
||||
, array ^>= 0.5.4
|
||||
, deepseq ^>= 1.4.4
|
||||
, extra ^>= 1.7.10
|
||||
, dotenv ^>= 0.10.0
|
||||
, network ^>= 3.1.2
|
||||
other-modules: Paths_waspc
|
||||
exposed-modules:
|
||||
FilePath.Extra
|
||||
@ -205,11 +207,12 @@ library
|
||||
Wasp.AppSpec.Route
|
||||
Wasp.AppSpec.Valid
|
||||
Wasp.AppSpec.Util
|
||||
Wasp.Common
|
||||
Wasp.CompileOptions
|
||||
Wasp.ConfigFile
|
||||
Wasp.Data
|
||||
Wasp.Db.Postgres
|
||||
Wasp.Error
|
||||
Wasp.Env
|
||||
Wasp.ExternalCode
|
||||
Wasp.JsImport
|
||||
Wasp.Generator
|
||||
@ -229,6 +232,7 @@ library
|
||||
Wasp.Generator.FileDraft.CopyFileDraft
|
||||
Wasp.Generator.FileDraft.TemplateFileDraft
|
||||
Wasp.Generator.FileDraft.TextFileDraft
|
||||
Wasp.Generator.FileDraft.CopyAndModifyTextFileDraft
|
||||
Wasp.Generator.FileDraft.Writeable
|
||||
Wasp.Generator.FileDraft.WriteableMonad
|
||||
Wasp.Generator.Job
|
||||
@ -273,13 +277,22 @@ library
|
||||
Wasp.Generator.WebAppGenerator.Setup
|
||||
Wasp.Generator.WebAppGenerator.Start
|
||||
Wasp.Generator.WriteFileDrafts
|
||||
Wasp.Lib
|
||||
Wasp.Project
|
||||
Wasp.Project.Analyze
|
||||
Wasp.Project.Common
|
||||
Wasp.Project.Db
|
||||
Wasp.Project.Db.Migrations
|
||||
Wasp.Project.Db.Dev
|
||||
Wasp.Project.Db.Dev.Postgres
|
||||
Wasp.Project.Deployment
|
||||
Wasp.Project.Env
|
||||
Wasp.NpmDependency
|
||||
Wasp.Psl.Ast.Model
|
||||
Wasp.Psl.Generator.Model
|
||||
Wasp.Psl.Parser.Model
|
||||
Wasp.SemanticVersion
|
||||
Wasp.Util
|
||||
Wasp.Util.Network.Socket
|
||||
Wasp.Util.Control.Monad
|
||||
Wasp.Util.Fib
|
||||
Wasp.Util.IO
|
||||
@ -359,11 +372,14 @@ library cli-lib
|
||||
Wasp.Cli.Command.CreateNewProject
|
||||
Wasp.Cli.Command.Db
|
||||
Wasp.Cli.Command.Db.Migrate
|
||||
Wasp.Cli.Command.Db.Reset
|
||||
Wasp.Cli.Command.Db.Studio
|
||||
Wasp.Cli.Command.Deps
|
||||
Wasp.Cli.Command.Deploy
|
||||
Wasp.Cli.Command.Dockerfile
|
||||
Wasp.Cli.Command.Info
|
||||
Wasp.Cli.Command.Start
|
||||
Wasp.Cli.Command.Start.Db
|
||||
Wasp.Cli.Command.Telemetry
|
||||
Wasp.Cli.Command.Telemetry.Common
|
||||
Wasp.Cli.Command.Telemetry.Project
|
||||
@ -435,6 +451,7 @@ test-suite waspc-test
|
||||
Generator.DbGeneratorTest
|
||||
Generator.ExternalCodeGenerator.JsTest
|
||||
Generator.FileDraft.CopyFileDraftTest
|
||||
Generator.FileDraft.CopyAndModifyTextFileDraftTest
|
||||
Generator.FileDraft.TemplateFileDraftTest
|
||||
Generator.MockWriteableMonad
|
||||
Generator.WebAppGeneratorTest
|
||||
@ -483,6 +500,7 @@ test-suite e2e-test
|
||||
main-is: Main.hs
|
||||
build-tool-depends: waspc:wasp-cli
|
||||
build-depends:
|
||||
, waspc
|
||||
, aeson
|
||||
, directory
|
||||
, base
|
||||
|
@ -21,8 +21,9 @@ COMMANDS
|
||||
uninstall Removes Wasp from your system.
|
||||
IN PROJECT
|
||||
start Runs Wasp app in development mode, watching for file changes.
|
||||
start db Starts managed development database for you.
|
||||
db <db-cmd> [args] Executes a database command. Run 'wasp db' for more info.
|
||||
clean Deletes all generated code and other cached artifacts. Wasp equivalent of 'have you tried closing and opening it again?'.
|
||||
clean Deletes all generated code and other cached artifacts. Wasp equivalent of 'have you tried closing and opening it again?'.
|
||||
build Generates full web app code, ready for deployment. Use when deploying or ejecting.
|
||||
deploy Deploys your Wasp app to cloud hosting providers.
|
||||
telemetry Prints telemetry status.
|
||||
@ -80,7 +81,7 @@ To setup Bash completion, execute `wasp completion` and follow the instructions.
|
||||
|
||||
### In project
|
||||
- `wasp start` runs Wasp app in development mode. It opens a browser tab with your application running, and watches for any changes to .wasp or files in `src/` to automatically reflect in the browser. It also shows messages from the web app, the server and the database on stdout/stderr.
|
||||
|
||||
- `wasp start db` starts the database for you. This can be very handy, since you don't need to spin up your own database or provide its connection URL to the Wasp app!
|
||||
- `wasp clean` deletes all generated code and other cached artifacts. If using SQlite, it also deletes the SQlite database. It is the Wasp equivalent to "try shutting it down and turning back on".
|
||||
|
||||
```
|
||||
|
@ -1717,7 +1717,7 @@ Since environmental variables are usually different for server-side and client a
|
||||
|
||||
`.env.server` and `.env.client` files should not be commited to the version control - we already ignore it by default in the .gitignore file we generate when you create a new Wasp project via `wasp new` cli command.
|
||||
|
||||
Variables are defined in `.env.server` or `env.client` files in the form of `NAME=VALUE`, for example:
|
||||
Variables are defined in `.env.server` or `.env.client` files in the form of `NAME=VALUE`, for example:
|
||||
```
|
||||
DATABASE_URL=postgresql://localhost:5432
|
||||
MY_VAR=somevalue
|
||||
@ -1754,29 +1754,20 @@ Default database is `SQLite`, since it is great for getting started with a new p
|
||||
Check below for more details on how to migrate from SQLite to PostgreSQL.
|
||||
|
||||
### PostgreSQL
|
||||
When using `PostgreSQL` as your database (`app: { db: { system: PostgreSQL } }`), you will need to spin up a postgres database on your own so it runs during development (when running `wasp start` or doing `wasp db ...` commands) and you will need to provide Wasp with `DATABASE_URL` environment variable that Wasp will use to connect to it.
|
||||
When using `PostgreSQL` as your database (`app: { db: { system: PostgreSQL } }`), you will need to make sure you have a postgres database running during development (when running `wasp start` or doing `wasp db ...` commands).
|
||||
|
||||
One of the easiest ways to run a PostgreSQL database on your own is by spinning up [postgres docker](https://hub.docker.com/_/postgres) container when you need it with the following shell command:
|
||||
```
|
||||
docker run \
|
||||
--rm \
|
||||
--publish 5432:5432 \
|
||||
-v my-app-data:/var/lib/postgresql/data \
|
||||
--env POSTGRES_PASSWORD=devpass1234 \
|
||||
postgres
|
||||
```
|
||||
To help with this, Wasp provides `wasp start db` that starts the default db for you. Your Wasp app will automatically connect to it once you have it running via `wasp start db`, no additional configuration is needed. This command relies on Docker being installed on your machine.
|
||||
|
||||
:::note
|
||||
The password you provide via `POSTGRES_PASSWORD` is relevant only for the first time when you run that docker command, when database is set up for the first time. Consequent runs will ignore the value of `POSTGRES_PASSWORD` and will just use the password that was initially set. This is just how postgres docker works.
|
||||
:::
|
||||
#### Custom database
|
||||
|
||||
The easiest way to provide the needed `DATABASE_URL` environment variable is by adding the following line to the [.env.server](https://wasp-lang.dev/docs/language/features#env) file in the root dir of your Wasp project (if that file doesn't yet exist, create it):
|
||||
```
|
||||
DATABASE_URL=postgresql://postgres:devpass1234@localhost:5432/postgres
|
||||
```
|
||||
If instead of using `wasp start db` you would rather connect to some other database, you will need to provide Wasp with `DATABASE_URL` environment variable that Wasp will use to connect to it.
|
||||
|
||||
The easiest way to provide the needed `DATABASE_URL` environment variable is by adding it to the [.env.server](https://wasp-lang.dev/docs/language/features#env) file in the root dir of your Wasp project (if that file doesn't yet exist, create it).
|
||||
|
||||
### Migrating from SQLite to PostgreSQL
|
||||
To run Wasp app in production, you will need to switch from `SQLite` to `PostgreSQL`.
|
||||
1. Set `app.db.system` to `PostgreSQL` and set `DATABASE_URL` env var accordingly (as described [above](/docs/language/features#postgresql)).
|
||||
2. Delete old migrations, since they are SQLite migrations and can't be used with PostgreSQL: `rm -r migrations/`.
|
||||
3. Run `wasp db migrate-dev` to apply new changes and create new, initial migration. You will need to have your postgres database running while doing this (check [above](/docs/language/features#postgresql) for easy way to get it running).
|
||||
1. Set `app.db.system` to `PostgreSQL`.
|
||||
3. Delete old migrations, since they are SQLite migrations and can't be used with PostgreSQL: `rm -r migrations/`.
|
||||
3. Run `wasp start db` to start your new db running (or check instructions above if you prefer using your custom db). Leave it running, since we need it for the next step.
|
||||
4. In a different terminal, run `wasp db migrate-dev` to apply new changes and create new, initial migration.
|
||||
5. That is it, you are all done!
|
||||
|
Loading…
Reference in New Issue
Block a user