module Wasp.Lib ( compile, Generator.start, ProjectRootDir, findWaspFile, analyzeWaspProject, ) where import Data.List (find, isSuffixOf) import StrongPath (Abs, Dir, File', Path', relfile) import qualified StrongPath as SP import System.Directory (doesDirectoryExist, doesFileExist) import qualified Wasp.Analyzer as Analyzer import Wasp.Analyzer.AnalyzeError (getErrorMessageAndCtx) import qualified Wasp.AppSpec as AS import qualified Wasp.AppSpec.Valid as ASV import Wasp.Common (DbMigrationsDir, WaspProjectDir, dbMigrationsDirInWaspProjectDir) import Wasp.CompileOptions (CompileOptions (generatorWarningsFilter), sendMessage) import qualified Wasp.CompileOptions as CompileOptions import Wasp.Error (showCompilerErrorForTerminal) import qualified Wasp.ExternalCode as ExternalCode import qualified Wasp.Generator as Generator import Wasp.Generator.Common (ProjectRootDir) import qualified Wasp.Util.IO as Util.IO type CompileError = String type CompileWarning = String compile :: Path' Abs (Dir WaspProjectDir) -> Path' Abs (Dir ProjectRootDir) -> CompileOptions -> IO ([CompileWarning], [CompileError]) compile waspDir outDir options = do appSpecOrCompileErrors <- analyzeWaspProject waspDir options case appSpecOrCompileErrors of Left compileErrors -> return ([], compileErrors) Right appSpec -> case ASV.validateAppSpec appSpec of [] -> do (generatorWarnings, generatorErrors) <- Generator.writeWebAppCode appSpec outDir (sendMessage options) return (map show $ generatorWarningsFilter options generatorWarnings, map show generatorErrors) validationErrors -> do return ([], map show validationErrors) analyzeWaspProject :: Path' Abs (Dir WaspProjectDir) -> CompileOptions -> IO (Either [CompileError] AS.AppSpec) analyzeWaspProject waspDir options = do maybeWaspFilePath <- findWaspFile waspDir case maybeWaspFilePath of Nothing -> return $ Left ["Couldn't find a single *.wasp file."] Just waspFilePath -> do waspFileContent <- readFile (SP.fromAbsFile waspFilePath) case Analyzer.analyze waspFileContent of Left analyzeError -> return $ Left [ showCompilerErrorForTerminal (waspFilePath, waspFileContent) (getErrorMessageAndCtx analyzeError) ] Right decls -> do externalCodeFiles <- ExternalCode.readFiles (CompileOptions.externalCodeDirPath options) maybeDotEnvFile <- findDotEnvFile waspDir maybeMigrationsDir <- findMigrationsDir waspDir return $ Right AS.AppSpec { AS.decls = decls, AS.externalCodeFiles = externalCodeFiles, AS.externalCodeDirPath = CompileOptions.externalCodeDirPath options, AS.migrationsDir = maybeMigrationsDir, AS.dotEnvFile = maybeDotEnvFile, AS.isBuild = CompileOptions.isBuild options } findWaspFile :: Path' Abs (Dir WaspProjectDir) -> IO (Maybe (Path' Abs File')) findWaspFile waspDir = do files <- fst <$> Util.IO.listDirectory waspDir return $ (waspDir SP.) <$> find isWaspFile files where isWaspFile path = ".wasp" `isSuffixOf` SP.toFilePath path && (length (SP.toFilePath path) > length (".wasp" :: String)) findDotEnvFile :: Path' Abs (Dir WaspProjectDir) -> IO (Maybe (Path' Abs File')) findDotEnvFile waspDir = do let dotEnvAbsPath = waspDir SP. [relfile|.env|] dotEnvExists <- doesFileExist (SP.toFilePath dotEnvAbsPath) return $ if dotEnvExists then Just dotEnvAbsPath else Nothing findMigrationsDir :: Path' Abs (Dir WaspProjectDir) -> IO (Maybe (Path' Abs (Dir DbMigrationsDir))) findMigrationsDir waspDir = do let migrationsAbsPath = waspDir SP. dbMigrationsDirInWaspProjectDir migrationsExists <- doesDirectoryExist $ SP.fromAbsDir migrationsAbsPath return $ if migrationsExists then Just migrationsAbsPath else Nothing