Skip to content

Commit

Permalink
Separate user code into client, server, shared (#753)
Browse files Browse the repository at this point in the history
* Separate ext code to client and server

* Use skeleton in createNewProject and refactor

* Refactor Lib.hs to use ExceptT

* Fix formatting

* Pop up returns

* Extract liftIO and add a do block

Co-authored-by: Shayne Czyzewski <[email protected]>

* Address some review comments

* Add skeleton comment

* Extract common CommandError message

* Separate skeleton comment into two rows

* Move server and client dirs into src

* Simplify maybeToEither

* Further refactor Lib.hs

* Further simplify skeleton comment

* Add shared code directory to project structure

* Update e2e test inputs

* Update e2e test outputs

* Fix formatting

* Fix bug in compile function

Co-authored-by: Martin Šošić <[email protected]>

* Change map to fmap in compile function

* Fix formatting

* Force git to include empty directories

* Remove extra empty line from .gitkeep files

* Watch shared directory for changes

* Fix regular and e2e tests

* Fix cli template packaging and update todoApp

* Add a shared function demo to todoApp

* Update waspc and e2e tests

* Fix compiler warnings and rename function

* Rename mkError to mkParserError

* Remove redundant empty line

* Fix test warnings

* Fix formatting

* Fix directory tree watching on wasp start

* Implement review feedback

Co-authored-by: Shayne Czyzewski <[email protected]>
Co-authored-by: Martin Šošić <[email protected]>
  • Loading branch information
3 people authored Nov 11, 2022
1 parent 6d0e81e commit 86e0893
Show file tree
Hide file tree
Showing 143 changed files with 546 additions and 986 deletions.
4 changes: 3 additions & 1 deletion waspc/cli/src/Wasp/Cli/Command/Build.hs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,9 @@ buildIO waspProjectDir buildDir = compileIOWithOptions options waspProjectDir bu
where
options =
CompileOptions
{ externalCodeDirPath = waspProjectDir </> Common.extCodeDirInWaspProjectDir,
{ externalClientCodeDirPath = waspProjectDir </> Common.extClientCodeDirInWaspProjectDir,
externalServerCodeDirPath = waspProjectDir </> Common.extServerCodeDirInWaspProjectDir,
externalSharedCodeDirPath = waspProjectDir </> Common.extSharedCodeDirInWaspProjectDir,
isBuild = True,
sendMessage = cliSendMessage,
-- Ignore "DB needs migration warnings" during build, as that is not a required step.
Expand Down
4 changes: 3 additions & 1 deletion waspc/cli/src/Wasp/Cli/Command/Compile.hs
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,9 @@ compileIOWithOptions options waspProjectDir outDir =
defaultCompileOptions :: Path' Abs (Dir WaspProjectDir) -> CompileOptions
defaultCompileOptions waspProjectDir =
CompileOptions
{ externalCodeDirPath = waspProjectDir </> Common.extCodeDirInWaspProjectDir,
{ externalServerCodeDirPath = waspProjectDir </> Common.extServerCodeDirInWaspProjectDir,
externalClientCodeDirPath = waspProjectDir </> Common.extClientCodeDirInWaspProjectDir,
externalSharedCodeDirPath = waspProjectDir </> Common.extSharedCodeDirInWaspProjectDir,
isBuild = False,
sendMessage = cliSendMessage,
generatorWarningsFilter = id
Expand Down
170 changes: 56 additions & 114 deletions waspc/cli/src/Wasp/Cli/Command/CreateNewProject.hs
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,18 @@ where
import Control.Monad.Except (throwError)
import Control.Monad.IO.Class (liftIO)
import Data.List (intercalate)
import StrongPath (Abs, Dir, File', Path', Rel, reldir, relfile, (</>))
import qualified StrongPath as SP
import System.Directory (createDirectory, getCurrentDirectory)
import qualified System.Directory
import Path.IO (copyDirRecur, doesDirExist)
import StrongPath (Abs, Dir, Path, Path', System, fromAbsFile, parseAbsDir, reldir, relfile, (</>))
import StrongPath.Path (toPathAbsDir)
import System.Directory (getCurrentDirectory)
import qualified System.FilePath as FP
import Text.Printf (printf)
import Wasp.Analyzer.Parser (isValidWaspIdentifier)
import Wasp.AppSpec.ExternalCode (SourceExternalCodeDir)
import Wasp.Cli.Command (Command, CommandError (..))
import qualified Wasp.Cli.Command.Common as Command.Common
import qualified Wasp.Cli.Common as Common
import qualified Wasp.Data
import Wasp.Common (WaspProjectDir)
import qualified Wasp.Common as Common (WaspProjectDir)
import qualified Wasp.Data as Data
import Wasp.Util (indent, kebabToCamelCase)
import qualified Wasp.Util.Terminal as Term
import qualified Wasp.Version as WV
Expand All @@ -27,14 +27,30 @@ data ProjectInfo = ProjectInfo
_appName :: String
}

createNewProject :: String -> Command ()
createNewProject projectNameCandidate = do
projectInfo <- parseProjectInfo projectNameCandidate
createWaspProjectDir projectInfo
liftIO $ printGettingStartedInstructions $ _projectName projectInfo
where
printGettingStartedInstructions :: String -> IO ()
printGettingStartedInstructions projectName = do
putStrLn $ Term.applyStyles [Term.Green] ("Created new Wasp app in ./" ++ projectName ++ " directory!")
putStrLn "To run it, do:"
putStrLn ""
putStrLn $ Term.applyStyles [Term.Bold] (" cd " ++ projectName)
putStrLn $ Term.applyStyles [Term.Bold] " wasp start"
putStrLn ""
putStrLn Command.Common.alphaWarningMessage

-- Takes a project name String
-- Returns either the ProjectInfo type that contains both the Project name
-- and the App name (which might be the same), or an error describing why the name is invalid
parseProjectInfo :: String -> Either String ProjectInfo
parseProjectInfo :: String -> Command ProjectInfo
parseProjectInfo name
| isValidWaspIdentifier appName = Right (ProjectInfo name appName)
| isValidWaspIdentifier appName = return $ ProjectInfo name appName
| otherwise =
Left $
throwProjectCreationError $
intercalate
"\n"
[ "The project's name is not in the valid format!",
Expand All @@ -45,81 +61,36 @@ parseProjectInfo name
where
appName = kebabToCamelCase name

createNewProject :: String -> Command ()
createNewProject name =
case parseProjectInfo name of
Right projectInfo -> createNewProject' projectInfo
Left parsedError ->
throwError $
CommandError "Project creation failed" parsedError

createNewProject' :: ProjectInfo -> Command ()
createNewProject' (ProjectInfo projectName appName) = do
createWaspProjectDir :: ProjectInfo -> Command ()
createWaspProjectDir projectInfo = do
absWaspProjectDir <- getAbsoluteWaspProjectDir projectInfo
dirExists <- doesDirExist $ toPathAbsDir absWaspProjectDir
if dirExists
then throwProjectCreationError $ show absWaspProjectDir ++ " is an existing directory"
else liftIO $ do
initializeProjectFromSkeleton absWaspProjectDir
writeMainWaspFile absWaspProjectDir projectInfo

getAbsoluteWaspProjectDir :: ProjectInfo -> Command (Path System Abs (Dir WaspProjectDir))
getAbsoluteWaspProjectDir (ProjectInfo projectName _) = do
absCwd <- liftIO getCurrentDirectory
waspProjectDir <- case SP.parseAbsDir $ absCwd FP.</> projectName of
Left err ->
throwError $
CommandError
"Project creation failed"
( "Failed to parse absolute path to wasp project dir: "
++ show err
)
case parseAbsDir $ absCwd FP.</> projectName of
Right sp -> return sp
liftIO $ do
createDirectorySP waspProjectDir
writeFileSP (waspProjectDir </> mainWaspFileInWaspProjectDir) mainWaspFileContent
writeFileSP (waspProjectDir </> gitignoreFileInWaspProjectDir) gitignoreFileContent
writeFileSP
(waspProjectDir </> Common.dotWaspRootFileInWaspProjectDir)
"File marking the root of Wasp project."

let extCodeDir = waspProjectDir </> Common.extCodeDirInWaspProjectDir
liftIO $ do
createDirectorySP extCodeDir
dataDir <- Wasp.Data.getAbsDataDirPath

let copyTemplateFile' = copyTemplateFile dataDir extCodeDir

writeFileSP (extCodeDir </> waspignoreFileInExtCodeDir) waspignoreFileContent

copyTemplateFile'
[relfile|new/ext/MainPage.js|]
mainPageJsFileInExtCodeDir

copyTemplateFile'
[relfile|new/ext/Main.css|]
mainCssFileInExtCodeDir

copyTemplateFile'
[relfile|new/ext/waspLogo.png|]
waspLogoFileInExtCodeDir

liftIO $ do
putStrLn $ Term.applyStyles [Term.Green] ("Created new Wasp app in ./" ++ projectName ++ " directory!")
putStrLn "To run it, do:"
putStrLn ""
putStrLn $ Term.applyStyles [Term.Bold] (" cd " ++ projectName)
putStrLn $ Term.applyStyles [Term.Bold] " wasp start"
putStrLn ""
putStrLn Command.Common.alphaWarningMessage
Left err ->
throwProjectCreationError $
"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 absWaspProjectDir = do
dataDir <- Data.getAbsDataDirPath
let absSkeletonDir = dataDir </> [reldir|Cli/templates/new|]
copyDirRecur (toPathAbsDir absSkeletonDir) (toPathAbsDir absWaspProjectDir)

writeMainWaspFile :: Path System Abs (Dir WaspProjectDir) -> ProjectInfo -> IO ()
writeMainWaspFile waspProjectDir (ProjectInfo projectName appName) = writeFile absMainWaspFile mainWaspFileContent
where
copyTemplateFile ::
Path' Abs (Dir Wasp.Data.DataDir) ->
Path' Abs (Dir SourceExternalCodeDir) ->
Path' (Rel Common.CliTemplatesDir) File' ->
Path' (Rel SourceExternalCodeDir) File' ->
IO ()
copyTemplateFile dataDir extCodeDir srcTmplFile dstExtDirFile =
System.Directory.copyFile
(SP.fromAbsFile (dataDir </> cliTemplatesDirInDataDir </> srcTmplFile))
(SP.fromAbsFile (extCodeDir </> dstExtDirFile))

cliTemplatesDirInDataDir :: Path' (Rel Wasp.Data.DataDir) (Dir Common.CliTemplatesDir)
cliTemplatesDirInDataDir = [reldir|Cli/templates|]

mainWaspFileInWaspProjectDir :: Path' (Rel Common.WaspProjectDir) File'
mainWaspFileInWaspProjectDir = [relfile|main.wasp|]

absMainWaspFile = fromAbsFile $ waspProjectDir </> [relfile|main.wasp|]
mainWaspFileContent =
unlines
[ "app %s {" `printf` appName,
Expand All @@ -131,38 +102,9 @@ createNewProject' (ProjectInfo projectName appName) = do
"",
"route RootRoute { path: \"/\", to: MainPage }",
"page MainPage {",
" component: import Main from \"@ext/MainPage.js\"",
" component: import Main from \"@client/MainPage.js\"",
"}"
]

gitignoreFileInWaspProjectDir :: Path' (Rel Common.WaspProjectDir) File'
gitignoreFileInWaspProjectDir = [relfile|.gitignore|]

gitignoreFileContent =
unlines
[ "/.wasp/",
"/.env.server",
"/.env.client"
]

waspignoreFileInExtCodeDir :: Path' (Rel SourceExternalCodeDir) File'
waspignoreFileInExtCodeDir = [relfile|.waspignore|]

waspignoreFileContent =
unlines
[ "# Ignore editor tmp files",
"**/*~",
"**/#*#"
]

mainPageJsFileInExtCodeDir :: Path' (Rel SourceExternalCodeDir) File'
mainPageJsFileInExtCodeDir = [relfile|MainPage.js|]

mainCssFileInExtCodeDir :: Path' (Rel SourceExternalCodeDir) File'
mainCssFileInExtCodeDir = [relfile|Main.css|]

waspLogoFileInExtCodeDir :: Path' (Rel SourceExternalCodeDir) File'
waspLogoFileInExtCodeDir = [relfile|waspLogo.png|]

writeFileSP = writeFile . SP.fromAbsFile
createDirectorySP = createDirectory . SP.fromAbsDir
throwProjectCreationError :: String -> Command a
throwProjectCreationError = throwError . CommandError "Project creation failed"
7 changes: 3 additions & 4 deletions waspc/cli/src/Wasp/Cli/Command/Deps.hs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ where

import Control.Monad.Except (throwError)
import Control.Monad.IO.Class (liftIO)
import Data.List.NonEmpty (toList)
import Wasp.AppSpec (AppSpec)
import qualified Wasp.AppSpec.App.Dependency as AS.Dependency
import Wasp.Cli.Command (Command, CommandError (..))
Expand All @@ -21,14 +20,14 @@ import qualified Wasp.Util.Terminal as Term
deps :: Command ()
deps = do
waspProjectDir <- findWaspProjectRootDirFromCwd
(_, appSpecOrAnalyzerErrors) <- liftIO $ analyzeWaspProject waspProjectDir (defaultCompileOptions waspProjectDir)
appSpecOrAnalyzerErrors <- liftIO $ analyzeWaspProject waspProjectDir (defaultCompileOptions waspProjectDir)
appSpec <-
either
(throwError . CommandError "Determining dependencies failed due to a compilation error in your Wasp project" . unwords . toList)
(throwError . CommandError "Determining dependencies failed due to a compilation error in your Wasp project" . unwords)
return
appSpecOrAnalyzerErrors

liftIO . putStrLn $ depsMessage appSpec
liftIO $ putStrLn $ depsMessage appSpec

depsMessage :: AppSpec -> String
depsMessage appSpec =
Expand Down
70 changes: 29 additions & 41 deletions waspc/cli/src/Wasp/Cli/Command/Info.hs
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ module Wasp.Cli.Command.Info
)
where

import Control.Arrow (ArrowChoice (left))
import Control.Monad.IO.Class (liftIO)
import Control.Arrow
import Control.Monad.Except
import StrongPath (Abs, Dir, Path', fromAbsFile, fromRelFile, toFilePath)
import StrongPath.Operations
import System.Directory (doesFileExist, getFileSize)
import qualified Wasp.Analyzer as Analyzer
import qualified Wasp.AppSpec as AS
import qualified Wasp.AppSpec.App as AS.App
import qualified Wasp.AppSpec.Core.Decl as AS (Decl, takeDecls)
import Wasp.Cli.Command (Command)
import Wasp.Cli.Command.Common (findWaspProjectRootDirFromCwd)
import Wasp.Cli.Command.Message (cliSendMessageC)
Expand All @@ -26,30 +26,23 @@ import Wasp.Util.IO (listDirectoryDeep)
import qualified Wasp.Util.Terminal as Term

info :: Command ()
info =
do
waspDir <- findWaspProjectRootDirFromCwd
compileInfo <- liftIO $ readCompileInformation 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
]
info = do
waspDir <- findWaspProjectRootDirFromCwd
compileInfo <- liftIO $ readCompileInformation 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
]

printInfo :: String -> String -> String
printInfo key value = Term.applyStyles [Term.Cyan] key ++ ": " <> Term.applyStyles [Term.White] value
Expand All @@ -70,17 +63,12 @@ readCompileInformation waspDir = do
else return "No compile information found"

parseWaspFile :: Path' Abs (Dir WaspProjectDir) -> IO (Either String [AS.Decl])
parseWaspFile waspDir = do
maybeWaspFile <- findWaspFile waspDir
case maybeWaspFile of
Nothing -> return (Left "Couldn't find a single *.wasp file.")
Just waspFile ->
do
waspStr <- readFile (toFilePath waspFile)
return $
left
( ("Couldn't parse .wasp file:\n" <>)
. showCompilerErrorForTerminal (waspFile, waspStr)
. Analyzer.getErrorMessageAndCtx
)
$ Analyzer.analyze waspStr
parseWaspFile waspDir = runExceptT $ do
waspFile <- ExceptT $ findWaspFile waspDir
waspStr <- liftIO $ readFile $ toFilePath waspFile
liftEither $ left (annotateErrorForCli waspFile waspStr) $ Analyzer.analyze waspStr
where
annotateErrorForCli waspFile waspStr =
("Couldn't parse .wasp file:\n" ++)
. showCompilerErrorForTerminal (waspFile, waspStr)
. Analyzer.getErrorMessageAndCtx
19 changes: 6 additions & 13 deletions waspc/cli/src/Wasp/Cli/Command/Watch.hs
Original file line number Diff line number Diff line change
Expand Up @@ -39,18 +39,11 @@ watch ::
watch waspProjectDir outDir ongoingCompilationResultMVar = FSN.withManager $ \mgr -> do
currentTime <- getCurrentTime
chan <- newChan
_ <-
FSN.watchDirChan
mgr
(SP.fromAbsDir waspProjectDir)
eventFilter
chan
_ <-
FSN.watchTreeChan
mgr
(SP.fromAbsDir $ waspProjectDir </> Common.extCodeDirInWaspProjectDir)
eventFilter
chan
_ <- FSN.watchDirChan mgr (SP.fromAbsDir waspProjectDir) eventFilter chan
let watchProjectSubdirTree path = FSN.watchTreeChan mgr (SP.fromAbsDir $ waspProjectDir </> path) eventFilter chan
_ <- watchProjectSubdirTree Common.extClientCodeDirInWaspProjectDir
_ <- watchProjectSubdirTree Common.extServerCodeDirInWaspProjectDir
_ <- watchProjectSubdirTree Common.extSharedCodeDirInWaspProjectDir
listenForEvents chan currentTime
where
listenForEvents :: Chan FSN.Event -> UTCTime -> IO ()
Expand Down Expand Up @@ -87,7 +80,7 @@ watch waspProjectDir outDir ongoingCompilationResultMVar = FSN.withManager $ \mg
waitUntilNoNewEvents chan lastCompileTime secondsToDelay = do
eventOrDelay <- race (readChan chan) (threadDelaySeconds secondsToDelay)
case eventOrDelay of
Left event -> do
Left event ->
unless (isStaleEvent event lastCompileTime) $
-- We have a new event, restart waiting process.
waitUntilNoNewEvents chan lastCompileTime secondsToDelay
Expand Down
Loading

0 comments on commit 86e0893

Please sign in to comment.