Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Separate user code into client and server directories #753

Merged
merged 41 commits into from
Nov 11, 2022
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
4e09b34
Separate ext code to client and server
sodic Oct 12, 2022
86e70fe
Use skeleton in createNewProject and refactor
sodic Oct 14, 2022
bb4107e
Refactor Lib.hs to use ExceptT
sodic Oct 14, 2022
cc7f353
Fix formatting
sodic Oct 14, 2022
492f247
Pop up returns
sodic Oct 14, 2022
d88e204
Extract liftIO and add a do block
sodic Oct 17, 2022
51b426a
Address some review comments
sodic Oct 24, 2022
789dc8f
Add skeleton comment
sodic Oct 24, 2022
6d29601
Extract common CommandError message
sodic Oct 24, 2022
11846e7
Separate skeleton comment into two rows
sodic Oct 24, 2022
4f834ec
Move server and client dirs into src
sodic Oct 24, 2022
385b35c
Simplify maybeToEither
sodic Oct 25, 2022
a7753d2
Further refactor Lib.hs
sodic Oct 25, 2022
53dc889
Further simplify skeleton comment
sodic Oct 25, 2022
8388b0f
Merge branch 'main' into filip-change-project-structure
sodic Oct 25, 2022
40f56d2
Add shared code directory to project structure
sodic Oct 31, 2022
936116b
Update e2e test inputs
sodic Oct 31, 2022
1d4e0d8
Update e2e test outputs
sodic Oct 31, 2022
dc753f4
Fix formatting
sodic Oct 31, 2022
281c3cf
Fix bug in compile function
sodic Oct 31, 2022
a64832c
Change map to fmap in compile function
sodic Oct 31, 2022
b30e3de
Fix formatting
sodic Oct 31, 2022
f40a8ce
Force git to include empty directories
sodic Oct 31, 2022
ddc8682
Remove extra empty line from .gitkeep files
sodic Oct 31, 2022
eb29d50
Watch shared directory for changes
sodic Oct 31, 2022
9fa7e9a
Fix regular and e2e tests
sodic Oct 31, 2022
8674887
Fix cli template packaging and update todoApp
sodic Nov 4, 2022
997fe72
Merge remote-tracking branch 'origin/main' into filip-change-project-…
sodic Nov 4, 2022
0cb6369
Add a shared function demo to todoApp
sodic Nov 4, 2022
8bb94cc
Update waspc and e2e tests
sodic Nov 4, 2022
5f47855
Fix compiler warnings and rename function
sodic Nov 4, 2022
54e0b02
Rename mkError to mkParserError
sodic Nov 4, 2022
0ae2631
Remove redundant empty line
sodic Nov 4, 2022
c5c326e
Merge remote-tracking branch 'origin/main' into filip-change-project-…
sodic Nov 4, 2022
fc76fb6
Fix test warnings
sodic Nov 4, 2022
dc29140
Fix formatting
sodic Nov 4, 2022
36e5766
Merge remote-tracking branch 'origin/main' into filip-change-project-…
sodic Nov 7, 2022
cd101ab
Merge branch 'main' into filip-change-project-structure
sodic Nov 7, 2022
bed02f1
Merge branch 'main' into filip-change-project-structure
sodic Nov 7, 2022
f384d14
Fix directory tree watching on wasp start
sodic Nov 9, 2022
d4a4c07
Implement review feedback
sodic Nov 9, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion waspc/cli/src/Wasp/Cli/Command/Build.hs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@ buildIO waspProjectDir buildDir = compileIOWithOptions options waspProjectDir bu
where
options =
CompileOptions
{ externalCodeDirPath = waspProjectDir </> Common.extCodeDirInWaspProjectDir,
{ externalClientCodeDirPath = waspProjectDir </> Common.extClientCodeDirInWaspProjectDir,
externalServerCodeDirPath = waspProjectDir </> Common.extServerCodeDirInWaspProjectDir,
isBuild = True,
sendMessage = cliSendMessage,
-- Ignore "DB needs migration warnings" during build, as that is not a required step.
Expand Down
3 changes: 2 additions & 1 deletion waspc/cli/src/Wasp/Cli/Command/Compile.hs
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,8 @@ compileIOWithOptions options waspProjectDir outDir = do
defaultCompileOptions :: Path' Abs (Dir WaspProjectDir) -> CompileOptions
defaultCompileOptions waspProjectDir =
CompileOptions
{ externalCodeDirPath = waspProjectDir </> Common.extCodeDirInWaspProjectDir,
{ externalServerCodeDirPath = waspProjectDir </> Common.extServerCodeDirInWaspProjectDir,
externalClientCodeDirPath = waspProjectDir </> Common.extClientCodeDirInWaspProjectDir,
isBuild = False,
sendMessage = cliSendMessage,
generatorWarningsFilter = id
Expand Down
182 changes: 62 additions & 120 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)
Copy link
Contributor Author

@sodic sodic Oct 14, 2022

Choose a reason for hiding this comment

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

I introduced a lot of changes to this file. The best way to review it is probably to avoid looking at the diff altogether.

The main (non-refactor) changes are:

  • We now create two separate directories for user code: client and server
  • Since most of the project scaffolding doesn't depend on anything, we now create a new project mainly by copying a skeleton from our templates and dynamically generate files only when necessary (i.e., for main.wasp file which depends on ProjectInfo).

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

Expand All @@ -26,99 +26,70 @@ data ProjectInfo = ProjectInfo
_appName :: String
}

createNewProject :: String -> Command ()
createNewProject projectName = do
projectInfo <- parseProjectInfo projectName
createWaspProjectDir projectInfo
liftIO printGettingStartedInstructions
where
printGettingStartedInstructions = do
putStrLn $ Term.applyStyles [Term.Green] ("Created new Wasp app in ./" ++ projectName ++ " directory!")
Copy link
Contributor

Choose a reason for hiding this comment

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

I think it is fine to use projectName because the contract of parseProjectInfo means it will fail if invalid, but since we use projectInfo to create, it may also be nice to pull the project name off that struct so everything downstream uses the "validated" thing. But I don't think it warrants a change, just thinking aloud. Only reason it could matter in future is if we did any normalization for them or whatnot.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, I know what you mean. I'll change it.

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
Martinsos marked this conversation as resolved.
Show resolved Hide resolved
parseProjectInfo name
| isValidWaspIdentifier appName = Right (ProjectInfo name appName)
| isValidWaspIdentifier appName = return $ ProjectInfo name appName
| otherwise =
Left $
intercalate
"\n"
[ "The project's name is not in the valid format!",
indent 2 "- It can start with a letter or an underscore.",
indent 2 "- It can contain only letters, numbers, dashes, or underscores.",
indent 2 "- It can't be a Wasp keyword."
]
throwProjectCreationError $
intercalate
"\n"
[ "The project's name is not in the valid format!",
indent 2 "- It can start with a letter or an underscore.",
indent 2 "- It can contain only letters, numbers, dashes, or underscores.",
indent 2 "- It can't be a Wasp keyword."
]
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))
shayneczyzewski marked this conversation as resolved.
Show resolved Hide resolved
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

-- To avoid creating the initial files for a new project dynamically,
Martinsos marked this conversation as resolved.
Show resolved Hide resolved
-- we copy the project directory skeleton from our templates.
initializeProjectFromSkeleton :: Path' Abs (Dir Common.WaspProjectDir) -> IO ()
Martinsos marked this conversation as resolved.
Show resolved Hide resolved
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,34 +102,5 @@ createNewProject' (ProjectInfo projectName appName) = do
"}"
]

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 =
sodic marked this conversation as resolved.
Show resolved Hide resolved
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"
6 changes: 3 additions & 3 deletions waspc/cli/src/Wasp/Cli/Command/Deps.hs
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,20 @@ 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.Lib (findAndAnalyzeWaspFile)
import qualified Wasp.Util.Terminal as Term

deps :: Command ()
deps = do
waspProjectDir <- findWaspProjectRootDirFromCwd
(_, appSpecOrAnalyzerErrors) <- liftIO $ analyzeWaspProject waspProjectDir (defaultCompileOptions waspProjectDir)
appSpecOrAnalyzerErrors <- liftIO $ findAndAnalyzeWaspFile waspProjectDir (defaultCompileOptions waspProjectDir)
appSpec <-
either
(throwError . CommandError "Determining dependencies failed due to a compilation error in your Wasp project" . unwords . toList)
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
3 changes: 2 additions & 1 deletion waspc/cli/src/Wasp/Cli/Command/Watch.hs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ watch waspProjectDir outDir = 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.watchTreeChan mgr (SP.fromAbsDir $ waspProjectDir </> Common.extClientCodeDirInWaspProjectDir) eventFilter chan
_ <- FSN.watchTreeChan mgr (SP.fromAbsDir $ waspProjectDir </> Common.extServerCodeDirInWaspProjectDir) eventFilter chan
listenForEvents chan currentTime
where
listenForEvents :: Chan FSN.Event -> UTCTime -> IO ()
Expand Down
10 changes: 7 additions & 3 deletions waspc/cli/src/Wasp/Cli/Common.hs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ module Wasp.Cli.Common
dotWaspDirInWaspProjectDir,
dotWaspRootFileInWaspProjectDir,
dotWaspInfoFileInGeneratedCodeDir,
extCodeDirInWaspProjectDir,
extClientCodeDirInWaspProjectDir,
extServerCodeDirInWaspProjectDir,
generatedCodeDirInDotWaspDir,
buildDirInDotWaspDir,
waspSays,
Expand Down Expand Up @@ -41,8 +42,11 @@ dotWaspRootFileInWaspProjectDir = [relfile|.wasproot|]
dotWaspInfoFileInGeneratedCodeDir :: Path' (Rel Wasp.Generator.Common.ProjectRootDir) File'
dotWaspInfoFileInGeneratedCodeDir = [relfile|.waspinfo|]

extCodeDirInWaspProjectDir :: Path' (Rel WaspProjectDir) (Dir SourceExternalCodeDir)
extCodeDirInWaspProjectDir = [reldir|ext|]
extServerCodeDirInWaspProjectDir :: Path' (Rel WaspProjectDir) (Dir SourceExternalCodeDir)
extServerCodeDirInWaspProjectDir = [reldir|src/server|]

extClientCodeDirInWaspProjectDir :: Path' (Rel WaspProjectDir) (Dir SourceExternalCodeDir)
extClientCodeDirInWaspProjectDir = [reldir|src/client|]

waspSays :: String -> IO ()
waspSays what = putStrLn $ Term.applyStyles [Term.Yellow] what
Expand Down
3 changes: 3 additions & 0 deletions waspc/data/Cli/templates/new/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/.wasp/
/.env.server
/.env.client
1 change: 1 addition & 0 deletions waspc/data/Cli/templates/new/.wasproot
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
File marking the root of Wasp project.
3 changes: 3 additions & 0 deletions waspc/data/Cli/templates/new/src/client/.waspignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Ignore editor tmp files
**/*~
**/#*#
Loading