From 8975e16a6e5a03e8b7bc27f7e0adbaa04b61831f Mon Sep 17 00:00:00 2001 From: Mihovil Ilakovac Date: Wed, 18 Sep 2024 15:43:46 +0200 Subject: [PATCH 01/24] WIP: userland files validation --- .../StarterTemplates/Templating.hs | 3 +- waspc/src/Wasp/AppSpec.hs | 2 +- waspc/src/Wasp/AppSpec/PackageJson.hs | 25 ------ waspc/src/Wasp/ExternalConfig.hs | 31 +++++++ waspc/src/Wasp/ExternalConfig/PackageJson.hs | 88 +++++++++++++++++++ waspc/src/Wasp/ExternalConfig/TsConfig.hs | 62 +++++++++++++ waspc/src/Wasp/Generator/NpmDependencies.hs | 6 +- waspc/src/Wasp/Project/Analyze.hs | 34 ++----- waspc/src/Wasp/Project/Common.hs | 4 + waspc/src/Wasp/Util/Json.hs | 16 ++++ waspc/test/AppSpec/ValidTest.hs | 10 +-- waspc/test/Generator/WebAppGeneratorTest.hs | 10 +-- waspc/waspc.cabal | 5 +- 13 files changed, 229 insertions(+), 67 deletions(-) delete mode 100644 waspc/src/Wasp/AppSpec/PackageJson.hs create mode 100644 waspc/src/Wasp/ExternalConfig.hs create mode 100644 waspc/src/Wasp/ExternalConfig/PackageJson.hs create mode 100644 waspc/src/Wasp/ExternalConfig/TsConfig.hs create mode 100644 waspc/src/Wasp/Util/Json.hs diff --git a/waspc/cli/src/Wasp/Cli/Command/CreateNewProject/StarterTemplates/Templating.hs b/waspc/cli/src/Wasp/Cli/Command/CreateNewProject/StarterTemplates/Templating.hs index 146f30660e..13a6ec5061 100644 --- a/waspc/cli/src/Wasp/Cli/Command/CreateNewProject/StarterTemplates/Templating.hs +++ b/waspc/cli/src/Wasp/Cli/Command/CreateNewProject/StarterTemplates/Templating.hs @@ -9,7 +9,8 @@ import qualified Data.Text as T import StrongPath (Abs, Dir, File, Path') import Wasp.Cli.Command.CreateNewProject.Common (defaultWaspVersionBounds) import Wasp.Cli.Command.CreateNewProject.ProjectDescription (NewProjectAppName, NewProjectName) -import Wasp.Project.Analyze (findPackageJsonFile, findWaspFile) +import Wasp.ExternalConfig.PackageJson (findPackageJsonFile) +import Wasp.Project.Analyze (findWaspFile) import Wasp.Project.Common (WaspProjectDir) import qualified Wasp.Util.IO as IOUtil diff --git a/waspc/src/Wasp/AppSpec.hs b/waspc/src/Wasp/AppSpec.hs index f1438fde28..6f5a2d683c 100644 --- a/waspc/src/Wasp/AppSpec.hs +++ b/waspc/src/Wasp/AppSpec.hs @@ -42,11 +42,11 @@ import qualified Wasp.AppSpec.ExternalFiles as ExternalFiles import Wasp.AppSpec.Job (Job) import Wasp.AppSpec.Operation (Operation) import qualified Wasp.AppSpec.Operation as AS.Operation -import Wasp.AppSpec.PackageJson (PackageJson) import Wasp.AppSpec.Page (Page) import Wasp.AppSpec.Query (Query) import Wasp.AppSpec.Route (Route) import Wasp.Env (EnvVar) +import Wasp.ExternalConfig.PackageJson (PackageJson) import Wasp.Node.Version (oldestWaspSupportedNodeVersion) import Wasp.Project.Common (WaspProjectDir) import Wasp.Project.Db.Migrations (DbMigrationsDir) diff --git a/waspc/src/Wasp/AppSpec/PackageJson.hs b/waspc/src/Wasp/AppSpec/PackageJson.hs deleted file mode 100644 index cc90def775..0000000000 --- a/waspc/src/Wasp/AppSpec/PackageJson.hs +++ /dev/null @@ -1,25 +0,0 @@ -{-# LANGUAGE DeriveGeneric #-} - -module Wasp.AppSpec.PackageJson where - -import Data.Aeson (FromJSON) -import Data.Map (Map) -import qualified Data.Map as M -import GHC.Generics (Generic) -import Wasp.AppSpec.App.Dependency (Dependency) -import qualified Wasp.AppSpec.App.Dependency as D - -data PackageJson = PackageJson - { name :: !String, - dependencies :: !(Map String String), - devDependencies :: !(Map String String) - } - deriving (Show, Generic) - -instance FromJSON PackageJson - -getDependencies :: PackageJson -> [Dependency] -getDependencies packageJson = D.fromList $ M.toList $ dependencies packageJson - -getDevDependencies :: PackageJson -> [Dependency] -getDevDependencies packageJson = D.fromList $ M.toList $ devDependencies packageJson diff --git a/waspc/src/Wasp/ExternalConfig.hs b/waspc/src/Wasp/ExternalConfig.hs new file mode 100644 index 0000000000..b426b5cbd1 --- /dev/null +++ b/waspc/src/Wasp/ExternalConfig.hs @@ -0,0 +1,31 @@ +module Wasp.ExternalConfig + ( ExternalConfigs (..), + analyzeExternalConfigs, + ) +where + +import Control.Monad.Except (ExceptT (ExceptT), runExceptT) +import StrongPath (Abs, Dir, Path') +import Wasp.ExternalConfig.PackageJson (PackageJson, analyzePackageJsonContent) +import Wasp.ExternalConfig.TsConfig (TsConfig, analyzeTsConfigContent) +import Wasp.Project.Common + ( CompileError, + WaspProjectDir, + ) + +data ExternalConfigs = ExternalConfigs + { packageJson :: PackageJson, + tsConfig :: TsConfig + } + deriving (Show) + +analyzeExternalConfigs :: Path' Abs (Dir WaspProjectDir) -> IO (Either [CompileError] ExternalConfigs) +analyzeExternalConfigs waspDir = runExceptT $ do + packageJsonContent <- ExceptT $ analyzePackageJsonContent waspDir + tsConfigContent <- ExceptT $ analyzeTsConfigContent waspDir + + return $ + ExternalConfigs + { packageJson = packageJsonContent, + tsConfig = tsConfigContent + } diff --git a/waspc/src/Wasp/ExternalConfig/PackageJson.hs b/waspc/src/Wasp/ExternalConfig/PackageJson.hs new file mode 100644 index 0000000000..6aef01865a --- /dev/null +++ b/waspc/src/Wasp/ExternalConfig/PackageJson.hs @@ -0,0 +1,88 @@ +{-# LANGUAGE DeriveGeneric #-} + +module Wasp.ExternalConfig.PackageJson + ( PackageJson (..), + getDependencies, + getDevDependencies, + analyzePackageJsonContent, + findPackageJsonFile, + ) +where + +import Control.Monad.Except (ExceptT (ExceptT), runExceptT) +import Data.Aeson (FromJSON) +import qualified Data.Aeson as Aeson +import Data.Map (Map) +import qualified Data.Map as M +import GHC.Generics (Generic) +import StrongPath (Abs, Dir, File', Path', toFilePath) +import Wasp.AppSpec.App.Dependency (Dependency) +import qualified Wasp.AppSpec.App.Dependency as D +import Wasp.Project.Common + ( CompileError, + WaspProjectDir, + findFileInWaspProjectDir, + packageJsonInWaspProjectDir, + ) +import Wasp.Util (maybeToEither) +import qualified Wasp.Util.IO as IOUtil + +data PackageJson = PackageJson + { name :: !String, + dependencies :: !DependenciesMap, + devDependencies :: !DependenciesMap + } + deriving (Show, Generic) + +type DependenciesMap = Map PackageName PackageVersion + +type PackageName = String + +type PackageVersion = String + +instance FromJSON PackageJson + +analyzePackageJsonContent :: Path' Abs (Dir WaspProjectDir) -> IO (Either [CompileError] PackageJson) +analyzePackageJsonContent waspProjectDir = runExceptT $ do + packageJsonFile <- ExceptT findPackageJsonFileOrError + packageJson <- ExceptT $ readPackageJsonFile packageJsonFile + ExceptT $ validatePackageJson packageJson + where + findPackageJsonFileOrError = maybeToEither [fileNotFoundMessage] <$> findPackageJsonFile waspProjectDir + fileNotFoundMessage = "Couldn't find the package.json file in the " ++ toFilePath waspProjectDir ++ " directory" + +validatePackageJson :: PackageJson -> IO (Either [CompileError] PackageJson) +validatePackageJson packageJson = + return $ + if null packageJsonErrors + then Right packageJson + else Left packageJsonErrors + where + packageJsonErrors = + validatePackageInDeps (dependencies packageJson) ("wasp", "file:.wasp/out/sdk/wasp") + +findPackageJsonFile :: Path' Abs (Dir WaspProjectDir) -> IO (Maybe (Path' Abs File')) +findPackageJsonFile waspProjectDir = findFileInWaspProjectDir waspProjectDir packageJsonInWaspProjectDir + +readPackageJsonFile :: Path' Abs File' -> IO (Either [CompileError] PackageJson) +readPackageJsonFile packageJsonFile = do + byteString <- IOUtil.readFileBytes packageJsonFile + return $ maybeToEither ["Error parsing the package.json file"] $ Aeson.decode byteString + +validatePackageInDeps :: DependenciesMap -> (PackageName, PackageVersion) -> [CompileError] +validatePackageInDeps deps (packageName, expectedPackageVersion) = + case M.lookup packageName deps of + Just actualPackageVersion -> + if actualPackageVersion == expectedPackageVersion + then [] + else [packageVersionMismatchMessage] + Nothing -> [packageNotFoundMessage] + where + packageVersionMismatchMessage = "The package \"" ++ packageName ++ "\" is not the expected version \"" ++ expectedPackageVersion ++ "\"." + packageNotFoundMessage = "The package \"" ++ packageName ++ "\" is not found in the dependencies." + +getDependencies :: PackageJson -> [Dependency] +getDependencies packageJson = D.fromList $ M.toList $ dependencies packageJson + +getDevDependencies :: PackageJson -> [Dependency] +getDevDependencies packageJson = D.fromList $ M.toList $ devDependencies packageJson diff --git a/waspc/src/Wasp/ExternalConfig/TsConfig.hs b/waspc/src/Wasp/ExternalConfig/TsConfig.hs new file mode 100644 index 0000000000..89d73d190a --- /dev/null +++ b/waspc/src/Wasp/ExternalConfig/TsConfig.hs @@ -0,0 +1,62 @@ +{-# LANGUAGE DeriveGeneric #-} + +module Wasp.ExternalConfig.TsConfig + ( TsConfig (..), + analyzeTsConfigContent, + ) +where + +import Control.Monad.Except +import Data.Aeson (FromJSON) +import qualified Data.ByteString.Lazy.UTF8 as BS +import Data.Either.Extra (maybeToEither) +import GHC.Generics (Generic) +import StrongPath (Abs, Dir, File', Path', toFilePath) +import Wasp.Project.Common + ( CompileError, + WaspProjectDir, + findFileInWaspProjectDir, + tsConfigInWaspProjectDir, + ) +import qualified Wasp.Util.IO as IOUtil +import Wasp.Util.Json (parseJsonWithComments) + +data TsConfig = TsConfig + { compilerOptions :: !CompilerOptions + } + deriving (Show, Generic) + +instance FromJSON TsConfig + +data CompilerOptions = CompilerOptions + { target :: !String + } + deriving (Show, Generic) + +instance FromJSON CompilerOptions + +analyzeTsConfigContent :: Path' Abs (Dir WaspProjectDir) -> IO (Either [CompileError] TsConfig) +analyzeTsConfigContent waspDir = runExceptT $ do + tsConfigFile <- ExceptT findTsConfigOrError + tsConfig <- ExceptT $ showTsConfig tsConfigFile + ExceptT $ validateTsConfig tsConfig + where + findTsConfigOrError = maybeToEither [fileNotFoundMessage] <$> findTsConfigFile waspDir + fileNotFoundMessage = "Couldn't find the tsconfig.json file in the " ++ toFilePath waspDir ++ " directory" + +findTsConfigFile :: Path' Abs (Dir WaspProjectDir) -> IO (Maybe (Path' Abs File')) +findTsConfigFile waspProjectDir = findFileInWaspProjectDir waspProjectDir tsConfigInWaspProjectDir + +showTsConfig :: Path' Abs File' -> IO (Either [CompileError] TsConfig) +showTsConfig tsConfigFile = do + tsConfigContent <- BS.toString <$> IOUtil.readFileBytes tsConfigFile + + parseResult <- parseJsonWithComments tsConfigContent + + case parseResult of + Right tsConfig -> return $ Right tsConfig + Left err -> return $ Left [err] + +-- TODO: validate config fields +validateTsConfig :: TsConfig -> IO (Either [CompileError] TsConfig) +validateTsConfig tsConfig = return $ Right tsConfig diff --git a/waspc/src/Wasp/Generator/NpmDependencies.hs b/waspc/src/Wasp/Generator/NpmDependencies.hs index c341ee2ed4..7d937c7d03 100644 --- a/waspc/src/Wasp/Generator/NpmDependencies.hs +++ b/waspc/src/Wasp/Generator/NpmDependencies.hs @@ -25,7 +25,7 @@ import GHC.Generics import Wasp.AppSpec (AppSpec) import qualified Wasp.AppSpec as AS import qualified Wasp.AppSpec.App.Dependency as D -import qualified Wasp.AppSpec.PackageJson as AS.PackageJson +import qualified Wasp.ExternalConfig.PackageJson as EC.PackageJson import Wasp.Generator.Monad (Generator, GeneratorError (..), logAndThrowGeneratorError) data NpmDepsForFramework = NpmDepsForFramework @@ -110,9 +110,9 @@ buildWaspFrameworkNpmDeps spec forServer forWebApp = getUserNpmDepsForPackage :: AppSpec -> NpmDepsForUser getUserNpmDepsForPackage spec = NpmDepsForUser - { userDependencies = AS.PackageJson.getDependencies $ AS.packageJson spec, + { userDependencies = EC.PackageJson.getDependencies $ AS.packageJson spec, -- Should we allow user devDependencies? https://github.com/wasp-lang/wasp/issues/456 - userDevDependencies = AS.PackageJson.getDevDependencies $ AS.packageJson spec + userDevDependencies = EC.PackageJson.getDevDependencies $ AS.packageJson spec } conflictErrorToMessage :: DependencyConflictError -> String diff --git a/waspc/src/Wasp/Project/Analyze.hs b/waspc/src/Wasp/Project/Analyze.hs index 3cccb0856b..e7e5ece230 100644 --- a/waspc/src/Wasp/Project/Analyze.hs +++ b/waspc/src/Wasp/Project/Analyze.hs @@ -1,34 +1,30 @@ module Wasp.Project.Analyze ( analyzeWaspProject, - readPackageJsonFile, analyzeWaspFileContent, findWaspFile, - findPackageJsonFile, analyzePrismaSchema, ) where import Control.Arrow (ArrowChoice (left)) -import qualified Data.Aeson as Aeson import Data.List (find, isSuffixOf) import StrongPath (Abs, Dir, File', Path', toFilePath, ()) import qualified Wasp.Analyzer as Analyzer import Wasp.Analyzer.AnalyzeError (getErrorMessageAndCtx) import Wasp.Analyzer.Parser.Ctx (Ctx) import qualified Wasp.AppSpec as AS -import Wasp.AppSpec.PackageJson (PackageJson) import qualified Wasp.AppSpec.Valid as ASV import Wasp.CompileOptions (CompileOptions) import qualified Wasp.CompileOptions as CompileOptions import qualified Wasp.ConfigFile as CF import Wasp.Error (showCompilerErrorForTerminal) +import qualified Wasp.ExternalConfig as EC import qualified Wasp.Generator.ConfigFile as G.CF import Wasp.Project.Common ( CompileError, CompileWarning, WaspProjectDir, findFileInWaspProjectDir, - packageJsonInWaspProjectDir, prismaSchemaFileInWaspProjectDir, ) import Wasp.Project.Db (makeDevDatabaseUrl) @@ -63,9 +59,9 @@ analyzeWaspProject waspDir options = do analyzeWaspFile prismaSchemaAst waspFilePath >>= \case Left errors -> return (Left errors, []) Right declarations -> - analyzePackageJsonContent waspDir >>= \case + EC.analyzeExternalConfigs waspDir >>= \case Left errors -> return (Left errors, []) - Right packageJsonContent -> constructAppSpec waspDir options packageJsonContent prismaSchemaAst declarations + Right externalConfigs -> constructAppSpec waspDir options externalConfigs prismaSchemaAst declarations where fileNotFoundMessage = "Couldn't find the *.wasp file in the " ++ toFilePath waspDir ++ " directory" @@ -81,11 +77,11 @@ analyzeWaspFileContent prismaSchemaAst = return . left (map getErrorMessageAndCt constructAppSpec :: Path' Abs (Dir WaspProjectDir) -> CompileOptions -> - PackageJson -> + EC.ExternalConfigs -> Psl.Schema.Schema -> [AS.Decl] -> IO (Either [CompileError] AS.AppSpec, [CompileWarning]) -constructAppSpec waspDir options packageJson parsedPrismaSchema decls = do +constructAppSpec waspDir options externalConfigs parsedPrismaSchema decls = do externalCodeFiles <- ExternalFiles.readCodeFiles waspDir externalPublicFiles <- ExternalFiles.readPublicFiles waspDir customViteConfigPath <- findCustomViteConfigPath waspDir @@ -98,11 +94,13 @@ constructAppSpec waspDir options packageJson parsedPrismaSchema decls = do serverEnvVars <- readDotEnvServer waspDir clientEnvVars <- readDotEnvClient waspDir + let packageJsonContent = EC.packageJson externalConfigs + let appSpec = AS.AppSpec { AS.decls = decls, AS.prismaSchema = parsedPrismaSchema, - AS.packageJson = packageJson, + AS.packageJson = packageJsonContent, AS.waspProjectDir = waspDir, AS.externalCodeFiles = externalCodeFiles, AS.externalPublicFiles = externalPublicFiles, @@ -128,22 +126,6 @@ findWaspFile waspDir = do `isSuffixOf` toFilePath path && (length (toFilePath path) > length (".wasp" :: String)) -analyzePackageJsonContent :: Path' Abs (Dir WaspProjectDir) -> IO (Either [CompileError] PackageJson) -analyzePackageJsonContent waspProjectDir = - findPackageJsonFile waspProjectDir >>= \case - Just packageJsonFile -> readPackageJsonFile packageJsonFile - Nothing -> return $ Left [fileNotFoundMessage] - where - fileNotFoundMessage = "Couldn't find the package.json file in the " ++ toFilePath waspProjectDir ++ " directory" - -findPackageJsonFile :: Path' Abs (Dir WaspProjectDir) -> IO (Maybe (Path' Abs File')) -findPackageJsonFile waspProjectDir = findFileInWaspProjectDir waspProjectDir packageJsonInWaspProjectDir - -readPackageJsonFile :: Path' Abs File' -> IO (Either [CompileError] PackageJson) -readPackageJsonFile packageJsonFile = do - byteString <- IOUtil.readFileBytes packageJsonFile - return $ maybeToEither ["Error parsing the package.json file"] $ Aeson.decode byteString - analyzePrismaSchema :: Path' Abs (Dir WaspProjectDir) -> IO (Either [CompileError] Psl.Schema.Schema, [CompileWarning]) analyzePrismaSchema waspProjectDir = do findPrismaSchemaFile waspProjectDir >>= \case diff --git a/waspc/src/Wasp/Project/Common.hs b/waspc/src/Wasp/Project/Common.hs index 2de89f27a0..993c969634 100644 --- a/waspc/src/Wasp/Project/Common.hs +++ b/waspc/src/Wasp/Project/Common.hs @@ -18,6 +18,7 @@ module Wasp.Project.Common extPublicDirInWaspProjectDir, tsconfigInWaspProjectDir, prismaSchemaFileInWaspProjectDir, + tsConfigInWaspProjectDir, ) where @@ -69,6 +70,9 @@ dotWaspInfoFileInGeneratedCodeDir = [relfile|.waspinfo|] packageJsonInWaspProjectDir :: Path' (Rel WaspProjectDir) File' packageJsonInWaspProjectDir = [relfile|package.json|] +tsConfigInWaspProjectDir :: Path' (Rel WaspProjectDir) File' +tsConfigInWaspProjectDir = [relfile|tsconfig.json|] + packageLockJsonInWaspProjectDir :: Path' (Rel WaspProjectDir) File' packageLockJsonInWaspProjectDir = [relfile|package-lock.json|] diff --git a/waspc/src/Wasp/Util/Json.hs b/waspc/src/Wasp/Util/Json.hs new file mode 100644 index 0000000000..aa43e655c6 --- /dev/null +++ b/waspc/src/Wasp/Util/Json.hs @@ -0,0 +1,16 @@ +module Wasp.Util.Json (parseJsonWithComments) where + +import Data.Aeson (FromJSON) +import System.Exit (ExitCode (..)) +import qualified System.Process as P +import Wasp.Util.Aeson (decodeFromString) + +-- | Uses Node.js to parse JSON with comments by treating it as a JavaScript object. +parseJsonWithComments :: FromJSON a => String -> IO (Either String a) +parseJsonWithComments jsonStr = do + let evalScript = "const v = " ++ jsonStr ++ ";console.log(JSON.stringify(v));" + let cp = P.proc "node" ["-e", evalScript] + (exitCode, response, stderr) <- P.readCreateProcessWithExitCode cp "" + case exitCode of + ExitSuccess -> return $ decodeFromString response + _exitFailure -> return $ Left stderr diff --git a/waspc/test/AppSpec/ValidTest.hs b/waspc/test/AppSpec/ValidTest.hs index 7859f655ce..d61908c6fc 100644 --- a/waspc/test/AppSpec/ValidTest.hs +++ b/waspc/test/AppSpec/ValidTest.hs @@ -26,11 +26,11 @@ import qualified Wasp.AppSpec.Crud as AS.Crud import qualified Wasp.AppSpec.Entity as AS.Entity import qualified Wasp.AppSpec.ExtImport as AS.ExtImport import qualified Wasp.AppSpec.Job as AS.Job -import qualified Wasp.AppSpec.PackageJson as AS.PJS import qualified Wasp.AppSpec.Page as AS.Page import qualified Wasp.AppSpec.Query as AS.Query import qualified Wasp.AppSpec.Route as AS.Route import qualified Wasp.AppSpec.Valid as ASV +import qualified Wasp.ExternalConfig.PackageJson as EC.PackageJson import qualified Wasp.Psl.Ast.Attribute as Psl.Attribute import qualified Wasp.Psl.Ast.Model as Psl.Model import qualified Wasp.SemanticVersion as SV @@ -448,10 +448,10 @@ spec_AppSpecValid = do AS.externalCodeFiles = [], AS.externalPublicFiles = [], AS.packageJson = - AS.PJS.PackageJson - { AS.PJS.name = "testApp", - AS.PJS.dependencies = M.empty, - AS.PJS.devDependencies = M.empty + EC.PackageJson.PackageJson + { EC.PackageJson.name = "testApp", + EC.PackageJson.dependencies = M.empty, + EC.PackageJson.devDependencies = M.empty }, AS.isBuild = False, AS.migrationsDir = Nothing, diff --git a/waspc/test/Generator/WebAppGeneratorTest.hs b/waspc/test/Generator/WebAppGeneratorTest.hs index 65feece08a..72614b05f5 100644 --- a/waspc/test/Generator/WebAppGeneratorTest.hs +++ b/waspc/test/Generator/WebAppGeneratorTest.hs @@ -9,7 +9,7 @@ import qualified Wasp.AppSpec as AS 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 qualified Wasp.AppSpec.PackageJson as AS.PJS +import qualified Wasp.ExternalConfig.PackageJson as EC.PackageJson import Wasp.Generator.FileDraft import qualified Wasp.Generator.FileDraft.CopyAndModifyTextFileDraft as CMTextFD import qualified Wasp.Generator.FileDraft.CopyDirFileDraft as CopyDirFD @@ -52,10 +52,10 @@ spec_WebAppGenerator = do AS.externalCodeFiles = [], AS.externalPublicFiles = [], AS.packageJson = - AS.PJS.PackageJson - { AS.PJS.name = "testApp", - AS.PJS.dependencies = M.empty, - AS.PJS.devDependencies = M.empty + EC.PackageJson.PackageJson + { EC.PackageJson.name = "testApp", + EC.PackageJson.dependencies = M.empty, + EC.PackageJson.devDependencies = M.empty }, AS.isBuild = False, AS.migrationsDir = Nothing, diff --git a/waspc/waspc.cabal b/waspc/waspc.cabal index 244521b37f..33b82e7764 100644 --- a/waspc/waspc.cabal +++ b/waspc/waspc.cabal @@ -248,7 +248,6 @@ library Wasp.AppSpec.Job Wasp.AppSpec.JSON Wasp.AppSpec.Operation - Wasp.AppSpec.PackageJson Wasp.AppSpec.Page Wasp.AppSpec.Query Wasp.AppSpec.Route @@ -260,6 +259,9 @@ library Wasp.Db.Postgres Wasp.Error Wasp.Env + Wasp.ExternalConfig + Wasp.ExternalConfig.PackageJson + Wasp.ExternalConfig.TsConfig Wasp.Generator Wasp.Generator.AuthProviders Wasp.Generator.AuthProviders.Common @@ -408,6 +410,7 @@ library Wasp.Util.HashMap Wasp.Util.IO Wasp.Util.IO.Retry + Wasp.Util.Json Wasp.Util.Network.HTTP Wasp.Util.Network.Socket Wasp.Util.Terminal From 3854e185fc7c105be34a67052a3e4941eef2de26 Mon Sep 17 00:00:00 2001 From: Mihovil Ilakovac Date: Wed, 18 Sep 2024 15:47:48 +0200 Subject: [PATCH 02/24] Cleanup --- waspc/src/Wasp/ExternalConfig/TsConfig.hs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/waspc/src/Wasp/ExternalConfig/TsConfig.hs b/waspc/src/Wasp/ExternalConfig/TsConfig.hs index 89d73d190a..cd382b4d7a 100644 --- a/waspc/src/Wasp/ExternalConfig/TsConfig.hs +++ b/waspc/src/Wasp/ExternalConfig/TsConfig.hs @@ -28,6 +28,7 @@ data TsConfig = TsConfig instance FromJSON TsConfig +-- TODO: define fields we want to validate data CompilerOptions = CompilerOptions { target :: !String } @@ -38,7 +39,7 @@ instance FromJSON CompilerOptions analyzeTsConfigContent :: Path' Abs (Dir WaspProjectDir) -> IO (Either [CompileError] TsConfig) analyzeTsConfigContent waspDir = runExceptT $ do tsConfigFile <- ExceptT findTsConfigOrError - tsConfig <- ExceptT $ showTsConfig tsConfigFile + tsConfig <- ExceptT $ readTsConfigFile tsConfigFile ExceptT $ validateTsConfig tsConfig where findTsConfigOrError = maybeToEither [fileNotFoundMessage] <$> findTsConfigFile waspDir @@ -47,11 +48,11 @@ analyzeTsConfigContent waspDir = runExceptT $ do findTsConfigFile :: Path' Abs (Dir WaspProjectDir) -> IO (Maybe (Path' Abs File')) findTsConfigFile waspProjectDir = findFileInWaspProjectDir waspProjectDir tsConfigInWaspProjectDir -showTsConfig :: Path' Abs File' -> IO (Either [CompileError] TsConfig) -showTsConfig tsConfigFile = do - tsConfigContent <- BS.toString <$> IOUtil.readFileBytes tsConfigFile +readTsConfigFile :: Path' Abs File' -> IO (Either [CompileError] TsConfig) +readTsConfigFile tsConfigFile = do + tsConfigContent <- IOUtil.readFileBytes tsConfigFile - parseResult <- parseJsonWithComments tsConfigContent + parseResult <- parseJsonWithComments . BS.toString $ tsConfigContent case parseResult of Right tsConfig -> return $ Right tsConfig From b0e116f8b5f67329ac1014657bc0fda8dd6f32f1 Mon Sep 17 00:00:00 2001 From: Mihovil Ilakovac Date: Thu, 19 Sep 2024 13:22:27 +0200 Subject: [PATCH 03/24] Upgrade package.json validation --- waspc/src/Wasp/ExternalConfig/PackageJson.hs | 65 ++++++++++++++++---- waspc/src/Wasp/Generator/Common.hs | 4 ++ waspc/src/Wasp/Generator/SdkGenerator.hs | 6 +- waspc/src/Wasp/Generator/WebAppGenerator.hs | 5 +- 4 files changed, 62 insertions(+), 18 deletions(-) diff --git a/waspc/src/Wasp/ExternalConfig/PackageJson.hs b/waspc/src/Wasp/ExternalConfig/PackageJson.hs index 6aef01865a..e41771dccd 100644 --- a/waspc/src/Wasp/ExternalConfig/PackageJson.hs +++ b/waspc/src/Wasp/ExternalConfig/PackageJson.hs @@ -18,6 +18,10 @@ import GHC.Generics (Generic) import StrongPath (Abs, Dir, File', Path', toFilePath) import Wasp.AppSpec.App.Dependency (Dependency) import qualified Wasp.AppSpec.App.Dependency as D +import Wasp.Generator.Common + ( prismaVersion, + reactRouterVersion, + ) import Wasp.Project.Common ( CompileError, WaspProjectDir, @@ -34,6 +38,12 @@ data PackageJson = PackageJson } deriving (Show, Generic) +getDependencies :: PackageJson -> [Dependency] +getDependencies packageJson = D.fromList $ M.toList $ dependencies packageJson + +getDevDependencies :: PackageJson -> [Dependency] +getDevDependencies packageJson = D.fromList $ M.toList $ devDependencies packageJson + type DependenciesMap = Map PackageName PackageVersion type PackageName = String @@ -59,7 +69,16 @@ validatePackageJson packageJson = else Left packageJsonErrors where packageJsonErrors = - validatePackageInDeps (dependencies packageJson) ("wasp", "file:.wasp/out/sdk/wasp") + concat + [ -- Wasp needs the Wasp SDK to be installed in the project. + validate ("wasp", "file:.wasp/out/sdk/wasp", RequiredPackage), + -- Wrong version of Prisma will break the generated code. + validate ("prisma", show prismaVersion, RequiredDevPackage), + -- Installing the wrong version of "react-router-dom" can make users believe that they + -- can use features that are not available in the version that Wasp supports. + validate ("react-router-dom", show reactRouterVersion, OptionalPackage) + ] + validate = validatePackageInDeps packageJson findPackageJsonFile :: Path' Abs (Dir WaspProjectDir) -> IO (Maybe (Path' Abs File')) findPackageJsonFile waspProjectDir = findFileInWaspProjectDir waspProjectDir packageJsonInWaspProjectDir @@ -69,20 +88,40 @@ readPackageJsonFile packageJsonFile = do byteString <- IOUtil.readFileBytes packageJsonFile return $ maybeToEither ["Error parsing the package.json file"] $ Aeson.decode byteString -validatePackageInDeps :: DependenciesMap -> (PackageName, PackageVersion) -> [CompileError] -validatePackageInDeps deps (packageName, expectedPackageVersion) = - case M.lookup packageName deps of - Just actualPackageVersion -> +data PackageValidationType = RequiredPackage | RequiredDevPackage | OptionalPackage + +validatePackageInDeps :: PackageJson -> (PackageName, PackageVersion, PackageValidationType) -> [CompileError] +validatePackageInDeps packageJson (packageName, expectedPackageVersion, validationType) = + case map (M.lookup packageName) depsToCheck of + (Just actualPackageVersion : _) -> if actualPackageVersion == expectedPackageVersion then [] - else [packageVersionMismatchMessage] - Nothing -> [packageNotFoundMessage] + else [incorrectVersionMessage] + _rest -> case validationType of + RequiredPackage -> [requiredPackageMessage "dependencies"] + RequiredDevPackage -> [requiredPackageMessage "devDependencies"] + OptionalPackage -> [] where - packageVersionMismatchMessage = "The package \"" ++ packageName ++ "\" is not the expected version \"" ++ expectedPackageVersion ++ "\"." - packageNotFoundMessage = "The package \"" ++ packageName ++ "\" is not found in the dependencies." + depsToCheck = case validationType of + RequiredPackage -> [dependencies packageJson] + RequiredDevPackage -> [devDependencies packageJson] + -- Users can install packages that don't need to be strictly in dependencies or devDependencies + -- which means Wasp needs to check both to validate the correct version of the package. + OptionalPackage -> [dependencies packageJson, devDependencies packageJson] -getDependencies :: PackageJson -> [Dependency] -getDependencies packageJson = D.fromList $ M.toList $ dependencies packageJson + incorrectVersionMessage :: String + incorrectVersionMessage = + unwords + ["The", show packageName, "package must have version", show expectedPackageVersion, "in package.json."] -getDevDependencies :: PackageJson -> [Dependency] -getDevDependencies packageJson = D.fromList $ M.toList $ devDependencies packageJson + requiredPackageMessage :: String -> String + requiredPackageMessage packageJsonLocation = + unwords + [ "The", + show packageName, + "package with version", + show expectedPackageVersion, + "must be defined in", + show packageJsonLocation, + "in package.json." + ] diff --git a/waspc/src/Wasp/Generator/Common.hs b/waspc/src/Wasp/Generator/Common.hs index 77523b8348..290abcd386 100644 --- a/waspc/src/Wasp/Generator/Common.hs +++ b/waspc/src/Wasp/Generator/Common.hs @@ -11,6 +11,7 @@ module Wasp.Generator.Common GeneratedSrcDir, makeJsArrayFromHaskellList, dropExtensionFromImportPath, + reactRouterVersion, ) where @@ -57,6 +58,9 @@ prismaVersion :: SV.Version -- Then, make sure `data/Generator/templates/sdk/wasp/prisma-runtime-library.d.ts` is up to date. prismaVersion = SV.Version 5 19 1 +reactRouterVersion :: SV.ComparatorSet +reactRouterVersion = SV.backwardsCompatibleWith $ SV.Version 5 3 3 + makeJsonWithEntityData :: String -> Aeson.Value makeJsonWithEntityData name = object diff --git a/waspc/src/Wasp/Generator/SdkGenerator.hs b/waspc/src/Wasp/Generator/SdkGenerator.hs index 81a5f1f090..c003fa7359 100644 --- a/waspc/src/Wasp/Generator/SdkGenerator.hs +++ b/waspc/src/Wasp/Generator/SdkGenerator.hs @@ -26,7 +26,7 @@ import qualified Wasp.AppSpec.App.Dependency as AS.Dependency import qualified Wasp.AppSpec.ExternalFiles as EC import Wasp.AppSpec.Valid (getLowestNodeVersionUserAllows, isAuthEnabled) import qualified Wasp.AppSpec.Valid as AS.Valid -import Wasp.Generator.Common (ProjectRootDir, makeJsonWithEntityData, prismaVersion) +import Wasp.Generator.Common (ProjectRootDir, makeJsonWithEntityData, prismaVersion, reactRouterVersion) import qualified Wasp.Generator.ConfigFile as G.CF import Wasp.Generator.DbGenerator (getEntitiesForPrismaSchema) import qualified Wasp.Generator.DbGenerator.Auth as DbAuth @@ -188,13 +188,13 @@ npmDepsForSdk spec = ("mitt", "3.0.0"), ("react", "^18.2.0"), ("lodash.merge", "^4.6.2"), - ("react-router-dom", "^5.3.3"), + ("react-router-dom", show reactRouterVersion), ("react-hook-form", "^7.45.4"), ("superjson", "^1.12.2"), -- Todo: why is this in dependencies, should it be in dev dependencies? -- Should it go into their package.json ("@types/express-serve-static-core", "^4.17.13"), - ("@types/react-router-dom", "^5.3.3") + ("@types/react-router-dom", show reactRouterVersion) ] ++ depsRequiredForAuth spec ++ depsRequiredByOAuth spec diff --git a/waspc/src/Wasp/Generator/WebAppGenerator.hs b/waspc/src/Wasp/Generator/WebAppGenerator.hs index 427e3b905e..9281b50405 100644 --- a/waspc/src/Wasp/Generator/WebAppGenerator.hs +++ b/waspc/src/Wasp/Generator/WebAppGenerator.hs @@ -32,6 +32,7 @@ import Wasp.AppSpec.Valid (getApp) import Wasp.Env (envVarsToDotEnvContent) import Wasp.Generator.Common ( makeJsArrayFromHaskellList, + reactRouterVersion, ) import Wasp.Generator.FileDraft (FileDraft, createTextFileDraft) import qualified Wasp.Generator.FileDraft as FD @@ -122,7 +123,7 @@ npmDepsForWasp _spec = ("react", "^18.2.0"), ("react-dom", "^18.2.0"), ("@tanstack/react-query", "^4.29.0"), - ("react-router-dom", "^5.3.3"), + ("react-router-dom", show reactRouterVersion), ("superjson", "^1.12.2"), ("mitt", "3.0.0"), -- Used for Auth UI @@ -135,7 +136,7 @@ npmDepsForWasp _spec = ("typescript", "^5.1.0"), ("@types/react", "^18.0.37"), ("@types/react-dom", "^18.0.11"), - ("@types/react-router-dom", "^5.3.3"), + ("@types/react-router-dom", show reactRouterVersion), ("@vitejs/plugin-react", "^4.2.1"), ("dotenv", "^16.0.3"), -- NOTE: Make sure to bump the version of the tsconfig From 9b21447bb2a6fccefe7993273cc52b0b0f0a67d1 Mon Sep 17 00:00:00 2001 From: Mihovil Ilakovac Date: Thu, 19 Sep 2024 14:07:21 +0200 Subject: [PATCH 04/24] Tsconfig validation WIP --- waspc/examples/todoApp/tsconfig.json | 10 +- waspc/src/Wasp/ExternalConfig/TsConfig.hs | 111 ++++++++++++++++++++-- waspc/src/Wasp/Generator/SdkGenerator.hs | 5 +- 3 files changed, 111 insertions(+), 15 deletions(-) diff --git a/waspc/examples/todoApp/tsconfig.json b/waspc/examples/todoApp/tsconfig.json index 4932b229a0..75adcc205d 100644 --- a/waspc/examples/todoApp/tsconfig.json +++ b/waspc/examples/todoApp/tsconfig.json @@ -15,11 +15,11 @@ "strict": true, // Allow default imports. "esModuleInterop": true, - "lib": [ - "dom", - "dom.iterable", - "esnext" - ], + // "lib": [ + // "dom", + // "dom.iterable", + // "esnext" + // ], "allowJs": true, "typeRoots": [ // This is needed to properly support Vitest testing with jest-dom matchers. diff --git a/waspc/src/Wasp/ExternalConfig/TsConfig.hs b/waspc/src/Wasp/ExternalConfig/TsConfig.hs index cd382b4d7a..c289b30b1d 100644 --- a/waspc/src/Wasp/ExternalConfig/TsConfig.hs +++ b/waspc/src/Wasp/ExternalConfig/TsConfig.hs @@ -1,4 +1,7 @@ {-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE InstanceSigs #-} +{-# LANGUAGE TypeSynonymInstances #-} module Wasp.ExternalConfig.TsConfig ( TsConfig (..), @@ -7,9 +10,10 @@ module Wasp.ExternalConfig.TsConfig where import Control.Monad.Except -import Data.Aeson (FromJSON) +import Data.Aeson (FromJSON, parseJSON, withObject, (.:?)) import qualified Data.ByteString.Lazy.UTF8 as BS import Data.Either.Extra (maybeToEither) +import qualified Data.Map as M import GHC.Generics (Generic) import StrongPath (Abs, Dir, File', Path', toFilePath) import Wasp.Project.Common @@ -28,18 +32,40 @@ data TsConfig = TsConfig instance FromJSON TsConfig --- TODO: define fields we want to validate data CompilerOptions = CompilerOptions - { target :: !String + { _module :: !(Maybe String), + target :: !(Maybe String), + moduleResolution :: !(Maybe String), + jsx :: !(Maybe String), + strict :: !(Maybe Bool), + esModuleInterop :: !(Maybe Bool), + lib :: !(Maybe [String]), + allowJs :: !(Maybe Bool), + typeRoots :: !(Maybe [String]), + outDir :: !(Maybe String) } - deriving (Show, Generic) + deriving (Show) -instance FromJSON CompilerOptions +instance FromJSON CompilerOptions where + parseJSON = withObject "CompilerOptions" $ \v -> + CompilerOptions + -- We couldn't use the Generic deriving for this because of the _ prefix in the field name. + <$> v .:? "module" + <*> v .:? "target" + <*> v .:? "moduleResolution" + <*> v .:? "jsx" + <*> v .:? "strict" + <*> v .:? "esModuleInterop" + <*> v .:? "lib" + <*> v .:? "allowJs" + <*> v .:? "typeRoots" + <*> v .:? "outDir" analyzeTsConfigContent :: Path' Abs (Dir WaspProjectDir) -> IO (Either [CompileError] TsConfig) analyzeTsConfigContent waspDir = runExceptT $ do tsConfigFile <- ExceptT findTsConfigOrError tsConfig <- ExceptT $ readTsConfigFile tsConfigFile + lift $ print tsConfig ExceptT $ validateTsConfig tsConfig where findTsConfigOrError = maybeToEither [fileNotFoundMessage] <$> findTsConfigFile waspDir @@ -56,8 +82,77 @@ readTsConfigFile tsConfigFile = do case parseResult of Right tsConfig -> return $ Right tsConfig - Left err -> return $ Left [err] + Left err -> return $ Left ["Failed to parse tsconfig.json file: " ++ err] --- TODO: validate config fields validateTsConfig :: TsConfig -> IO (Either [CompileError] TsConfig) -validateTsConfig tsConfig = return $ Right tsConfig +validateTsConfig tsConfig = + return $ + if null tsConfigErrors + then Right tsConfig + else Left tsConfigErrors + where + tsConfigErrors = + concat + [ validateTsConfigField "module" (_module compilerOptionsValues) "esnext", + validateTsConfigField "target" (target compilerOptionsValues) "esnext", + validateTsConfigField "moduleResolution" (moduleResolution compilerOptionsValues) "bundler", + validateTsConfigField "jsx" (jsx compilerOptionsValues) "preserve", + validateTsConfigField "strict" (strict compilerOptionsValues) True, + validateTsConfigField "esModuleInterop" (esModuleInterop compilerOptionsValues) True, + validateTsConfigField "lib" (lib compilerOptionsValues) ["dom", "dom.iterable", "esnext"], + validateTsConfigField "allowJs" (allowJs compilerOptionsValues) True, + validateTsConfigField "typeRoots" (typeRoots compilerOptionsValues) ["node_modules/@testing-library", "node_modules/@types"], + validateTsConfigField "outDir" (outDir compilerOptionsValues) ".wasp/phantom" + ] + compilerOptionsValues = compilerOptions tsConfig + +class ShowJs a where + showJs :: a -> String + +instance ShowJs String where + showJs = show + +instance ShowJs [String] where + showJs = show + +instance ShowJs Bool where + showJs True = "true" + showJs False = "false" + +-- TODO: maybe use a more structured way of defining expected values, so that we can easily add new fields. +-- expectedValues :: M.Map FieldName TsConfigFieldType +-- expectedValues = +-- M.fromList +-- [ ("module", Str "esnext"), +-- ("target", Str "esnext"), +-- ("moduleResolution", Str "bundler"), +-- ("jsx", Str "preserve"), +-- ("strict", Bool True), +-- ("esModuleInterop", Bool True), +-- ("lib", StrList ["dom", "dom.iterable", "esnext"]), +-- ("allowJs", Bool True), +-- ("typeRoots", StrList ["node_modules/@testing-library", "node_modules/@types"]), +-- ("outDir", Str ".wasp/phantom") +-- ] + +-- data TsConfigFieldType = Str String | Bool Bool | StrList [String] + +-- instance Show TsConfigFieldType where +-- show :: TsConfigFieldType -> String +-- show (Str s) = s +-- show (Bool True) = show ("true" :: String) +-- show (Bool False) = show ("false" :: String) +-- show (StrList l) = show l + +type FieldName = String + +validateTsConfigField :: (Eq value, ShowJs value) => FieldName -> Maybe value -> value -> [CompileError] +validateTsConfigField fieldName Nothing expectedValue = [missingFieldErrorMessage] + where + missingFieldErrorMessage = unwords ["The", show fieldName, "field is missing in tsconfig.json. Expected value:", showJs expectedValue ++ "."] +validateTsConfigField fieldName (Just userProvidedValue) expectedValue = + if userProvidedValue /= expectedValue + then [invalidValueErrorMessage] + else [] + where + invalidValueErrorMessage = unwords ["Invalid value for the", show fieldName, "field in tsconfig.json file, expected:", showJs expectedValue ++ "."] diff --git a/waspc/src/Wasp/Generator/SdkGenerator.hs b/waspc/src/Wasp/Generator/SdkGenerator.hs index c003fa7359..e58ea9c723 100644 --- a/waspc/src/Wasp/Generator/SdkGenerator.hs +++ b/waspc/src/Wasp/Generator/SdkGenerator.hs @@ -198,10 +198,11 @@ npmDepsForSdk spec = ] ++ depsRequiredForAuth spec ++ depsRequiredByOAuth spec - -- This must be installed in the SDK because it lists prisma/client as a dependency. + -- Server auth deps must be installed in the SDK because "@lucia-auth/adapter-prisma" + -- lists prisma/client as a dependency. -- Installing it inside .wasp/out/server/node_modules would also -- install prisma/client in the same folder, which would cause our - -- runtime to load the wrong (uninitialized prisma/client) + -- runtime to load the wrong (uninitialized prisma/client). -- TODO(filip): Find a better way to handle duplicate -- dependencies: https://github.com/wasp-lang/wasp/issues/1640 ++ ServerAuthG.depsRequiredByAuth spec From c515bd45335a456acfd227a5b296e47d71a6d5c5 Mon Sep 17 00:00:00 2001 From: Mihovil Ilakovac Date: Thu, 19 Sep 2024 14:07:27 +0200 Subject: [PATCH 05/24] Cleanup --- waspc/examples/todoApp/tsconfig.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/waspc/examples/todoApp/tsconfig.json b/waspc/examples/todoApp/tsconfig.json index 75adcc205d..4932b229a0 100644 --- a/waspc/examples/todoApp/tsconfig.json +++ b/waspc/examples/todoApp/tsconfig.json @@ -15,11 +15,11 @@ "strict": true, // Allow default imports. "esModuleInterop": true, - // "lib": [ - // "dom", - // "dom.iterable", - // "esnext" - // ], + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], "allowJs": true, "typeRoots": [ // This is needed to properly support Vitest testing with jest-dom matchers. From 3ebe67abbdb2af6234df47b25adf24dee7cdb2f4 Mon Sep 17 00:00:00 2001 From: Mihovil Ilakovac Date: Thu, 19 Sep 2024 23:45:32 +0200 Subject: [PATCH 06/24] Update tsconfig validation --- waspc/src/Wasp/ExternalConfig/TsConfig.hs | 65 +++++++---------------- 1 file changed, 19 insertions(+), 46 deletions(-) diff --git a/waspc/src/Wasp/ExternalConfig/TsConfig.hs b/waspc/src/Wasp/ExternalConfig/TsConfig.hs index c289b30b1d..13685a9339 100644 --- a/waspc/src/Wasp/ExternalConfig/TsConfig.hs +++ b/waspc/src/Wasp/ExternalConfig/TsConfig.hs @@ -1,7 +1,5 @@ {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE FlexibleInstances #-} -{-# LANGUAGE InstanceSigs #-} -{-# LANGUAGE TypeSynonymInstances #-} module Wasp.ExternalConfig.TsConfig ( TsConfig (..), @@ -13,7 +11,6 @@ import Control.Monad.Except import Data.Aeson (FromJSON, parseJSON, withObject, (.:?)) import qualified Data.ByteString.Lazy.UTF8 as BS import Data.Either.Extra (maybeToEither) -import qualified Data.Map as M import GHC.Generics (Generic) import StrongPath (Abs, Dir, File', Path', toFilePath) import Wasp.Project.Common @@ -93,19 +90,20 @@ validateTsConfig tsConfig = where tsConfigErrors = concat - [ validateTsConfigField "module" (_module compilerOptionsValues) "esnext", - validateTsConfigField "target" (target compilerOptionsValues) "esnext", - validateTsConfigField "moduleResolution" (moduleResolution compilerOptionsValues) "bundler", - validateTsConfigField "jsx" (jsx compilerOptionsValues) "preserve", - validateTsConfigField "strict" (strict compilerOptionsValues) True, - validateTsConfigField "esModuleInterop" (esModuleInterop compilerOptionsValues) True, - validateTsConfigField "lib" (lib compilerOptionsValues) ["dom", "dom.iterable", "esnext"], - validateTsConfigField "allowJs" (allowJs compilerOptionsValues) True, - validateTsConfigField "typeRoots" (typeRoots compilerOptionsValues) ["node_modules/@testing-library", "node_modules/@types"], - validateTsConfigField "outDir" (outDir compilerOptionsValues) ".wasp/phantom" + [ validateRequiredField "module" "esnext" (_module compilerOptionsValues), + validateRequiredField "target" "esnext" (target compilerOptionsValues), + validateRequiredField "moduleResolution" "bundler" (moduleResolution compilerOptionsValues), + validateRequiredField "jsx" "preserve" (jsx compilerOptionsValues), + validateRequiredField "strict" True (strict compilerOptionsValues), + validateRequiredField "esModuleInterop" True (esModuleInterop compilerOptionsValues), + validateRequiredField "lib" ["dom", "dom.iterable", "esnext"] (lib compilerOptionsValues), + validateRequiredField "allowJs" True (allowJs compilerOptionsValues), + validateRequiredField "typeRoots" ["node_modules/@testing-library", "node_modules/@types"] (typeRoots compilerOptionsValues), + validateRequiredField "outDir" ".wasp/phantom" (outDir compilerOptionsValues) ] compilerOptionsValues = compilerOptions tsConfig +-- | Used to show expected values in error messages. class ShowJs a where showJs :: a -> String @@ -119,40 +117,15 @@ instance ShowJs Bool where showJs True = "true" showJs False = "false" --- TODO: maybe use a more structured way of defining expected values, so that we can easily add new fields. --- expectedValues :: M.Map FieldName TsConfigFieldType --- expectedValues = --- M.fromList --- [ ("module", Str "esnext"), --- ("target", Str "esnext"), --- ("moduleResolution", Str "bundler"), --- ("jsx", Str "preserve"), --- ("strict", Bool True), --- ("esModuleInterop", Bool True), --- ("lib", StrList ["dom", "dom.iterable", "esnext"]), --- ("allowJs", Bool True), --- ("typeRoots", StrList ["node_modules/@testing-library", "node_modules/@types"]), --- ("outDir", Str ".wasp/phantom") --- ] - --- data TsConfigFieldType = Str String | Bool Bool | StrList [String] - --- instance Show TsConfigFieldType where --- show :: TsConfigFieldType -> String --- show (Str s) = s --- show (Bool True) = show ("true" :: String) --- show (Bool False) = show ("false" :: String) --- show (StrList l) = show l - type FieldName = String -validateTsConfigField :: (Eq value, ShowJs value) => FieldName -> Maybe value -> value -> [CompileError] -validateTsConfigField fieldName Nothing expectedValue = [missingFieldErrorMessage] - where - missingFieldErrorMessage = unwords ["The", show fieldName, "field is missing in tsconfig.json. Expected value:", showJs expectedValue ++ "."] -validateTsConfigField fieldName (Just userProvidedValue) expectedValue = - if userProvidedValue /= expectedValue - then [invalidValueErrorMessage] - else [] +validateRequiredField :: (Eq value, ShowJs value) => FieldName -> value -> Maybe value -> [CompileError] +validateRequiredField fieldName expectedValue maybeUserProvidedValue = case maybeUserProvidedValue of + Nothing -> [missingFieldErrorMessage] + Just userProvidedValue -> + if userProvidedValue /= expectedValue + then [invalidValueErrorMessage] + else [] where invalidValueErrorMessage = unwords ["Invalid value for the", show fieldName, "field in tsconfig.json file, expected:", showJs expectedValue ++ "."] + missingFieldErrorMessage = unwords ["The", show fieldName, "field is missing in tsconfig.json. Expected value:", showJs expectedValue ++ "."] From 78fa56994abb5e9087406e9ed1a618e26a70859e Mon Sep 17 00:00:00 2001 From: Mihovil Ilakovac Date: Fri, 20 Sep 2024 14:57:06 +0200 Subject: [PATCH 07/24] Cleanup --- waspc/src/Wasp/ExternalConfig/TsConfig.hs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/waspc/src/Wasp/ExternalConfig/TsConfig.hs b/waspc/src/Wasp/ExternalConfig/TsConfig.hs index 13685a9339..19b1abe41a 100644 --- a/waspc/src/Wasp/ExternalConfig/TsConfig.hs +++ b/waspc/src/Wasp/ExternalConfig/TsConfig.hs @@ -46,7 +46,7 @@ data CompilerOptions = CompilerOptions instance FromJSON CompilerOptions where parseJSON = withObject "CompilerOptions" $ \v -> CompilerOptions - -- We couldn't use the Generic deriving for this because of the _ prefix in the field name. + -- We couldn't use the Generic deriving for this because of the _ prefix in the "module" field name. <$> v .:? "module" <*> v .:? "target" <*> v .:? "moduleResolution" @@ -62,7 +62,6 @@ analyzeTsConfigContent :: Path' Abs (Dir WaspProjectDir) -> IO (Either [CompileE analyzeTsConfigContent waspDir = runExceptT $ do tsConfigFile <- ExceptT findTsConfigOrError tsConfig <- ExceptT $ readTsConfigFile tsConfigFile - lift $ print tsConfig ExceptT $ validateTsConfig tsConfig where findTsConfigOrError = maybeToEither [fileNotFoundMessage] <$> findTsConfigFile waspDir @@ -127,5 +126,5 @@ validateRequiredField fieldName expectedValue maybeUserProvidedValue = case mayb then [invalidValueErrorMessage] else [] where - invalidValueErrorMessage = unwords ["Invalid value for the", show fieldName, "field in tsconfig.json file, expected:", showJs expectedValue ++ "."] + invalidValueErrorMessage = unwords ["Invalid value for the", show fieldName, "field in tsconfig.json file, expected value:", showJs expectedValue ++ "."] missingFieldErrorMessage = unwords ["The", show fieldName, "field is missing in tsconfig.json. Expected value:", showJs expectedValue ++ "."] From f1245ba5395d4ed3228d468c1a468f9fe35b3d2c Mon Sep 17 00:00:00 2001 From: Mihovil Ilakovac Date: Fri, 20 Sep 2024 15:13:43 +0200 Subject: [PATCH 08/24] Cleanup --- waspc/src/Wasp/ExternalConfig/TsConfig.hs | 32 +++++++++++++---------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/waspc/src/Wasp/ExternalConfig/TsConfig.hs b/waspc/src/Wasp/ExternalConfig/TsConfig.hs index 19b1abe41a..b888d707aa 100644 --- a/waspc/src/Wasp/ExternalConfig/TsConfig.hs +++ b/waspc/src/Wasp/ExternalConfig/TsConfig.hs @@ -89,18 +89,22 @@ validateTsConfig tsConfig = where tsConfigErrors = concat - [ validateRequiredField "module" "esnext" (_module compilerOptionsValues), - validateRequiredField "target" "esnext" (target compilerOptionsValues), - validateRequiredField "moduleResolution" "bundler" (moduleResolution compilerOptionsValues), - validateRequiredField "jsx" "preserve" (jsx compilerOptionsValues), - validateRequiredField "strict" True (strict compilerOptionsValues), - validateRequiredField "esModuleInterop" True (esModuleInterop compilerOptionsValues), - validateRequiredField "lib" ["dom", "dom.iterable", "esnext"] (lib compilerOptionsValues), - validateRequiredField "allowJs" True (allowJs compilerOptionsValues), - validateRequiredField "typeRoots" ["node_modules/@testing-library", "node_modules/@types"] (typeRoots compilerOptionsValues), - validateRequiredField "outDir" ".wasp/phantom" (outDir compilerOptionsValues) + [ validateRequiredFieldInCompilerOptions "module" "esnext" _module, + validateRequiredFieldInCompilerOptions "target" "esnext" target, + validateRequiredFieldInCompilerOptions "moduleResolution" "bundler" moduleResolution, + validateRequiredFieldInCompilerOptions "jsx" "preserve" jsx, + validateRequiredFieldInCompilerOptions "strict" True strict, + validateRequiredFieldInCompilerOptions "esModuleInterop" True esModuleInterop, + validateRequiredFieldInCompilerOptions "lib" ["dom", "dom.iterable", "esnext"] lib, + validateRequiredFieldInCompilerOptions "allowJs" True allowJs, + validateRequiredFieldInCompilerOptions "typeRoots" ["node_modules/@testing-library", "node_modules/@types"] typeRoots, + validateRequiredFieldInCompilerOptions "outDir" ".wasp/phantom" outDir ] - compilerOptionsValues = compilerOptions tsConfig + + validateRequiredFieldInCompilerOptions fieldName expectedValue getField = + validateRequiredField fieldName expectedValue $ getField compilerOptionsFields + + compilerOptionsFields = compilerOptions tsConfig -- | Used to show expected values in error messages. class ShowJs a where @@ -122,9 +126,9 @@ validateRequiredField :: (Eq value, ShowJs value) => FieldName -> value -> Maybe validateRequiredField fieldName expectedValue maybeUserProvidedValue = case maybeUserProvidedValue of Nothing -> [missingFieldErrorMessage] Just userProvidedValue -> - if userProvidedValue /= expectedValue - then [invalidValueErrorMessage] - else [] + if userProvidedValue == expectedValue + then [] + else [invalidValueErrorMessage] where invalidValueErrorMessage = unwords ["Invalid value for the", show fieldName, "field in tsconfig.json file, expected value:", showJs expectedValue ++ "."] missingFieldErrorMessage = unwords ["The", show fieldName, "field is missing in tsconfig.json. Expected value:", showJs expectedValue ++ "."] From b2594ce176023b44e90b5dd39664e8b0688e642a Mon Sep 17 00:00:00 2001 From: Mihovil Ilakovac Date: Fri, 20 Sep 2024 17:41:58 +0200 Subject: [PATCH 09/24] Update headless tests. Update field names. --- .../examples/todoApp/tsconfig.json | 23 ++++++++++++------- waspc/src/Wasp/ExternalConfig/TsConfig.hs | 2 +- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/waspc/headless-test/examples/todoApp/tsconfig.json b/waspc/headless-test/examples/todoApp/tsconfig.json index a38370e8ee..4932b229a0 100644 --- a/waspc/headless-test/examples/todoApp/tsconfig.json +++ b/waspc/headless-test/examples/todoApp/tsconfig.json @@ -5,6 +5,11 @@ // compiler. Proper TS compiler configuration in Wasp is coming soon :) { "compilerOptions": { + "module": "esnext", + "target": "esnext", + // We're bundling all code in the end so this is the most appropriate option, + // it's also important for autocomplete to work properly. + "moduleResolution": "bundler", // JSX support "jsx": "preserve", "strict": true, @@ -16,19 +21,21 @@ "esnext" ], "allowJs": true, - "types": [ + "typeRoots": [ // This is needed to properly support Vitest testing with jest-dom matchers. // Types for jest-dom are not recognized automatically and Typescript complains // about missing types e.g. when using `toBeInTheDocument` and other matchers. - "@testing-library/jest-dom" + "node_modules/@testing-library", + // Specifying type roots overrides the default behavior of looking at the + // node_modules/@types folder so we had to list it explicitly. + // Source 1: https://www.typescriptlang.org/tsconfig#typeRoots + // Source 2: https://github.com/testing-library/jest-dom/issues/546#issuecomment-1889884843 + "node_modules/@types" ], // Since this TS config is used only for IDE support and not for // compilation, the following directory doesn't exist. We need to specify // it to prevent this error: // https://stackoverflow.com/questions/42609768/typescript-error-cannot-write-file-because-it-would-overwrite-input-file - "outDir": "phantom", - }, - "exclude": [ - "phantom" - ], -} \ No newline at end of file + "outDir": ".wasp/phantom" + } +} diff --git a/waspc/src/Wasp/ExternalConfig/TsConfig.hs b/waspc/src/Wasp/ExternalConfig/TsConfig.hs index b888d707aa..1407fa44ad 100644 --- a/waspc/src/Wasp/ExternalConfig/TsConfig.hs +++ b/waspc/src/Wasp/ExternalConfig/TsConfig.hs @@ -102,7 +102,7 @@ validateTsConfig tsConfig = ] validateRequiredFieldInCompilerOptions fieldName expectedValue getField = - validateRequiredField fieldName expectedValue $ getField compilerOptionsFields + validateRequiredField ("compilerOptions." ++ fieldName) expectedValue $ getField compilerOptionsFields compilerOptionsFields = compilerOptions tsConfig From 5db75db9eb138d78f1afe6f7a55299a0e76f1449 Mon Sep 17 00:00:00 2001 From: Mihovil Ilakovac Date: Wed, 25 Sep 2024 18:53:57 +0200 Subject: [PATCH 10/24] Update waspc/src/Wasp/ExternalConfig/TsConfig.hs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Martin Šošić --- waspc/src/Wasp/ExternalConfig/TsConfig.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/waspc/src/Wasp/ExternalConfig/TsConfig.hs b/waspc/src/Wasp/ExternalConfig/TsConfig.hs index 1407fa44ad..83874dd02e 100644 --- a/waspc/src/Wasp/ExternalConfig/TsConfig.hs +++ b/waspc/src/Wasp/ExternalConfig/TsConfig.hs @@ -106,7 +106,7 @@ validateTsConfig tsConfig = compilerOptionsFields = compilerOptions tsConfig --- | Used to show expected values in error messages. +-- | Haskell type that implements ShowJS is a type whose values can be mapped to Javascript values (their string representation). class ShowJs a where showJs :: a -> String From 31cf936e9051995accb40b8613a1958bafd00309 Mon Sep 17 00:00:00 2001 From: Mihovil Ilakovac Date: Wed, 25 Sep 2024 19:02:01 +0200 Subject: [PATCH 11/24] Update comment --- waspc/src/Wasp/Util/Json.hs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/waspc/src/Wasp/Util/Json.hs b/waspc/src/Wasp/Util/Json.hs index aa43e655c6..fce6b959d9 100644 --- a/waspc/src/Wasp/Util/Json.hs +++ b/waspc/src/Wasp/Util/Json.hs @@ -6,6 +6,8 @@ import qualified System.Process as P import Wasp.Util.Aeson (decodeFromString) -- | Uses Node.js to parse JSON with comments by treating it as a JavaScript object. +-- We used this technique because Aeson can't handle it and we didn't want to write +-- a custom parser. parseJsonWithComments :: FromJSON a => String -> IO (Either String a) parseJsonWithComments jsonStr = do let evalScript = "const v = " ++ jsonStr ++ ";console.log(JSON.stringify(v));" From aad26e36345999fb02cf9c02c1cc338635686183 Mon Sep 17 00:00:00 2001 From: Mihovil Ilakovac Date: Wed, 25 Sep 2024 19:12:18 +0200 Subject: [PATCH 12/24] Update package.json validation --- waspc/src/Wasp/ExternalConfig/PackageJson.hs | 34 ++++++++------------ 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/waspc/src/Wasp/ExternalConfig/PackageJson.hs b/waspc/src/Wasp/ExternalConfig/PackageJson.hs index e41771dccd..97ce7452f1 100644 --- a/waspc/src/Wasp/ExternalConfig/PackageJson.hs +++ b/waspc/src/Wasp/ExternalConfig/PackageJson.hs @@ -71,12 +71,12 @@ validatePackageJson packageJson = packageJsonErrors = concat [ -- Wasp needs the Wasp SDK to be installed in the project. - validate ("wasp", "file:.wasp/out/sdk/wasp", RequiredPackage), + validate ("wasp", "file:.wasp/out/sdk/wasp", IsListedWithExactVersion), -- Wrong version of Prisma will break the generated code. - validate ("prisma", show prismaVersion, RequiredDevPackage), + validate ("prisma", show prismaVersion, IsListedAsDevWithExactVersion), -- Installing the wrong version of "react-router-dom" can make users believe that they -- can use features that are not available in the version that Wasp supports. - validate ("react-router-dom", show reactRouterVersion, OptionalPackage) + validate ("react-router-dom", show reactRouterVersion, HasExactVersionIfListed) ] validate = validatePackageInDeps packageJson @@ -88,26 +88,20 @@ readPackageJsonFile packageJsonFile = do byteString <- IOUtil.readFileBytes packageJsonFile return $ maybeToEither ["Error parsing the package.json file"] $ Aeson.decode byteString -data PackageValidationType = RequiredPackage | RequiredDevPackage | OptionalPackage +data PackageValidationType = IsListedWithExactVersion | IsListedAsDevWithExactVersion | HasExactVersionIfListed validatePackageInDeps :: PackageJson -> (PackageName, PackageVersion, PackageValidationType) -> [CompileError] -validatePackageInDeps packageJson (packageName, expectedPackageVersion, validationType) = - case map (M.lookup packageName) depsToCheck of - (Just actualPackageVersion : _) -> - if actualPackageVersion == expectedPackageVersion - then [] - else [incorrectVersionMessage] - _rest -> case validationType of - RequiredPackage -> [requiredPackageMessage "dependencies"] - RequiredDevPackage -> [requiredPackageMessage "devDependencies"] - OptionalPackage -> [] +validatePackageInDeps packageJson (packageName, expectedPackageVersion, validationType) = case validationType of + IsListedWithExactVersion -> checkDeps [dependencies packageJson] [requiredPackageMessage "dependencies"] + IsListedAsDevWithExactVersion -> checkDeps [devDependencies packageJson] [requiredPackageMessage "devDependencies"] + HasExactVersionIfListed -> checkDeps [dependencies packageJson, devDependencies packageJson] [] where - depsToCheck = case validationType of - RequiredPackage -> [dependencies packageJson] - RequiredDevPackage -> [devDependencies packageJson] - -- Users can install packages that don't need to be strictly in dependencies or devDependencies - -- which means Wasp needs to check both to validate the correct version of the package. - OptionalPackage -> [dependencies packageJson, devDependencies packageJson] + checkDeps depsToCheck errorMessagesIfPackageNotListed = case map (M.lookup packageName) depsToCheck of + (Just actualPackageVersion : _) -> + if actualPackageVersion == expectedPackageVersion + then [] + else [incorrectVersionMessage] + _notListed -> errorMessagesIfPackageNotListed incorrectVersionMessage :: String incorrectVersionMessage = From 6789b0e03aa9785f15e668b4e6dcba76c655a864 Mon Sep 17 00:00:00 2001 From: Mihovil Ilakovac Date: Wed, 25 Sep 2024 19:22:53 +0200 Subject: [PATCH 13/24] Update messages --- waspc/src/Wasp/ExternalConfig/PackageJson.hs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/waspc/src/Wasp/ExternalConfig/PackageJson.hs b/waspc/src/Wasp/ExternalConfig/PackageJson.hs index 97ce7452f1..478ae4f219 100644 --- a/waspc/src/Wasp/ExternalConfig/PackageJson.hs +++ b/waspc/src/Wasp/ExternalConfig/PackageJson.hs @@ -106,16 +106,16 @@ validatePackageInDeps packageJson (packageName, expectedPackageVersion, validati incorrectVersionMessage :: String incorrectVersionMessage = unwords - ["The", show packageName, "package must have version", show expectedPackageVersion, "in package.json."] + ["Wasp requires package", show packageName, "to be version", show expectedPackageVersion, "in package.json."] requiredPackageMessage :: String -> String requiredPackageMessage packageJsonLocation = unwords - [ "The", + [ "Wasp requires package", show packageName, - "package with version", + "with version", show expectedPackageVersion, - "must be defined in", + "in", show packageJsonLocation, "in package.json." ] From f16ee595e4dd493a583a09374e9cf32485e46ab8 Mon Sep 17 00:00:00 2001 From: Mihovil Ilakovac Date: Wed, 25 Sep 2024 19:31:30 +0200 Subject: [PATCH 14/24] Update FromJSON instance for TsConfig --- waspc/src/Wasp/ExternalConfig/TsConfig.hs | 29 +++++++++++------------ 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/waspc/src/Wasp/ExternalConfig/TsConfig.hs b/waspc/src/Wasp/ExternalConfig/TsConfig.hs index 83874dd02e..2297a7f5c0 100644 --- a/waspc/src/Wasp/ExternalConfig/TsConfig.hs +++ b/waspc/src/Wasp/ExternalConfig/TsConfig.hs @@ -8,7 +8,12 @@ module Wasp.ExternalConfig.TsConfig where import Control.Monad.Except -import Data.Aeson (FromJSON, parseJSON, withObject, (.:?)) +import Data.Aeson + ( FromJSON, + genericParseJSON, + parseJSON, + ) +import qualified Data.Aeson as Aeson import qualified Data.ByteString.Lazy.UTF8 as BS import Data.Either.Extra (maybeToEither) import GHC.Generics (Generic) @@ -41,22 +46,16 @@ data CompilerOptions = CompilerOptions typeRoots :: !(Maybe [String]), outDir :: !(Maybe String) } - deriving (Show) + deriving (Show, Generic) instance FromJSON CompilerOptions where - parseJSON = withObject "CompilerOptions" $ \v -> - CompilerOptions - -- We couldn't use the Generic deriving for this because of the _ prefix in the "module" field name. - <$> v .:? "module" - <*> v .:? "target" - <*> v .:? "moduleResolution" - <*> v .:? "jsx" - <*> v .:? "strict" - <*> v .:? "esModuleInterop" - <*> v .:? "lib" - <*> v .:? "allowJs" - <*> v .:? "typeRoots" - <*> v .:? "outDir" + parseJSON = + genericParseJSON $ + Aeson.defaultOptions {Aeson.fieldLabelModifier = modifyFieldLabel} + where + -- "module" is a reserved keyword in Haskell, so we use "_module" instead. + modifyFieldLabel "_module" = "module" + modifyFieldLabel other = other analyzeTsConfigContent :: Path' Abs (Dir WaspProjectDir) -> IO (Either [CompileError] TsConfig) analyzeTsConfigContent waspDir = runExceptT $ do From 1335462a4297e50a513a842e08a2b45045e9f743 Mon Sep 17 00:00:00 2001 From: Mihovil Ilakovac Date: Wed, 25 Sep 2024 19:39:31 +0200 Subject: [PATCH 15/24] Update TsConfig validation --- waspc/src/Wasp/ExternalConfig/TsConfig.hs | 40 +++++++++++------------ 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/waspc/src/Wasp/ExternalConfig/TsConfig.hs b/waspc/src/Wasp/ExternalConfig/TsConfig.hs index 2297a7f5c0..b618a41c31 100644 --- a/waspc/src/Wasp/ExternalConfig/TsConfig.hs +++ b/waspc/src/Wasp/ExternalConfig/TsConfig.hs @@ -100,34 +100,34 @@ validateTsConfig tsConfig = validateRequiredFieldInCompilerOptions "outDir" ".wasp/phantom" outDir ] - validateRequiredFieldInCompilerOptions fieldName expectedValue getField = - validateRequiredField ("compilerOptions." ++ fieldName) expectedValue $ getField compilerOptionsFields + validateRequiredFieldInCompilerOptions fieldName expectedValue getFieldValue = case getFieldValue compilerOptionsFields of + Just actualValue -> validateFieldValue ("compilerOptions." ++ fieldName) expectedValue actualValue + Nothing -> [missingFieldErrorMessage] + where + missingFieldErrorMessage = unwords ["The", show fieldName, "field is missing in tsconfig.json. Expected value:", showAsJsValue expectedValue ++ "."] compilerOptionsFields = compilerOptions tsConfig -- | Haskell type that implements ShowJS is a type whose values can be mapped to Javascript values (their string representation). -class ShowJs a where - showJs :: a -> String +class IsJavascriptValue a where + showAsJsValue :: a -> String -instance ShowJs String where - showJs = show +instance IsJavascriptValue String where + showAsJsValue = show -instance ShowJs [String] where - showJs = show +instance IsJavascriptValue [String] where + showAsJsValue = show -instance ShowJs Bool where - showJs True = "true" - showJs False = "false" +instance IsJavascriptValue Bool where + showAsJsValue True = "true" + showAsJsValue False = "false" type FieldName = String -validateRequiredField :: (Eq value, ShowJs value) => FieldName -> value -> Maybe value -> [CompileError] -validateRequiredField fieldName expectedValue maybeUserProvidedValue = case maybeUserProvidedValue of - Nothing -> [missingFieldErrorMessage] - Just userProvidedValue -> - if userProvidedValue == expectedValue - then [] - else [invalidValueErrorMessage] +validateFieldValue :: (Eq value, IsJavascriptValue value) => FieldName -> value -> value -> [CompileError] +validateFieldValue fieldName expectedValue actualValue = + if actualValue == expectedValue + then [] + else [invalidValueErrorMessage] where - invalidValueErrorMessage = unwords ["Invalid value for the", show fieldName, "field in tsconfig.json file, expected value:", showJs expectedValue ++ "."] - missingFieldErrorMessage = unwords ["The", show fieldName, "field is missing in tsconfig.json. Expected value:", showJs expectedValue ++ "."] + invalidValueErrorMessage = unwords ["Invalid value for the", show fieldName, "field in tsconfig.json file, expected value:", showAsJsValue expectedValue ++ "."] From 016443edbed1ef93a8e4c233a0c990af56c82a6f Mon Sep 17 00:00:00 2001 From: Mihovil Ilakovac Date: Thu, 26 Sep 2024 13:23:17 +0200 Subject: [PATCH 16/24] Moves modules into Project and Generator --- .../StarterTemplates/Templating.hs | 2 +- waspc/src/Wasp/ExternalConfig.hs | 31 ------- waspc/src/Wasp/ExternalConfig/PackageJson.hs | 88 +----------------- waspc/src/Wasp/ExternalConfig/TsConfig.hs | 89 +------------------ waspc/src/Wasp/Generator/Common.hs | 4 - .../Generator/ExternalConfig/PackageJson.hs | 63 +++++++++++++ .../Wasp/Generator/ExternalConfig/TsConfig.hs | 64 +++++++++++++ waspc/src/Wasp/Generator/SdkGenerator.hs | 3 +- waspc/src/Wasp/Generator/WebAppGenerator.hs | 7 +- .../Wasp/Generator/WebAppGenerator/Common.hs | 5 ++ waspc/src/Wasp/Project/Analyze.hs | 4 +- waspc/src/Wasp/Project/Common.hs | 16 ++-- waspc/src/Wasp/Project/ExternalConfig.hs | 83 +++++++++++++++++ waspc/waspc.cabal | 4 +- 14 files changed, 240 insertions(+), 223 deletions(-) delete mode 100644 waspc/src/Wasp/ExternalConfig.hs create mode 100644 waspc/src/Wasp/Generator/ExternalConfig/PackageJson.hs create mode 100644 waspc/src/Wasp/Generator/ExternalConfig/TsConfig.hs create mode 100644 waspc/src/Wasp/Project/ExternalConfig.hs diff --git a/waspc/cli/src/Wasp/Cli/Command/CreateNewProject/StarterTemplates/Templating.hs b/waspc/cli/src/Wasp/Cli/Command/CreateNewProject/StarterTemplates/Templating.hs index 13a6ec5061..f541431f14 100644 --- a/waspc/cli/src/Wasp/Cli/Command/CreateNewProject/StarterTemplates/Templating.hs +++ b/waspc/cli/src/Wasp/Cli/Command/CreateNewProject/StarterTemplates/Templating.hs @@ -9,9 +9,9 @@ import qualified Data.Text as T import StrongPath (Abs, Dir, File, Path') import Wasp.Cli.Command.CreateNewProject.Common (defaultWaspVersionBounds) import Wasp.Cli.Command.CreateNewProject.ProjectDescription (NewProjectAppName, NewProjectName) -import Wasp.ExternalConfig.PackageJson (findPackageJsonFile) import Wasp.Project.Analyze (findWaspFile) import Wasp.Project.Common (WaspProjectDir) +import Wasp.Project.ExternalConfig (findPackageJsonFile) import qualified Wasp.Util.IO as IOUtil replaceTemplatePlaceholdersInTemplateFiles :: NewProjectAppName -> NewProjectName -> Path' Abs (Dir WaspProjectDir) -> IO () diff --git a/waspc/src/Wasp/ExternalConfig.hs b/waspc/src/Wasp/ExternalConfig.hs deleted file mode 100644 index b426b5cbd1..0000000000 --- a/waspc/src/Wasp/ExternalConfig.hs +++ /dev/null @@ -1,31 +0,0 @@ -module Wasp.ExternalConfig - ( ExternalConfigs (..), - analyzeExternalConfigs, - ) -where - -import Control.Monad.Except (ExceptT (ExceptT), runExceptT) -import StrongPath (Abs, Dir, Path') -import Wasp.ExternalConfig.PackageJson (PackageJson, analyzePackageJsonContent) -import Wasp.ExternalConfig.TsConfig (TsConfig, analyzeTsConfigContent) -import Wasp.Project.Common - ( CompileError, - WaspProjectDir, - ) - -data ExternalConfigs = ExternalConfigs - { packageJson :: PackageJson, - tsConfig :: TsConfig - } - deriving (Show) - -analyzeExternalConfigs :: Path' Abs (Dir WaspProjectDir) -> IO (Either [CompileError] ExternalConfigs) -analyzeExternalConfigs waspDir = runExceptT $ do - packageJsonContent <- ExceptT $ analyzePackageJsonContent waspDir - tsConfigContent <- ExceptT $ analyzeTsConfigContent waspDir - - return $ - ExternalConfigs - { packageJson = packageJsonContent, - tsConfig = tsConfigContent - } diff --git a/waspc/src/Wasp/ExternalConfig/PackageJson.hs b/waspc/src/Wasp/ExternalConfig/PackageJson.hs index 478ae4f219..8e1f1fd033 100644 --- a/waspc/src/Wasp/ExternalConfig/PackageJson.hs +++ b/waspc/src/Wasp/ExternalConfig/PackageJson.hs @@ -2,34 +2,20 @@ module Wasp.ExternalConfig.PackageJson ( PackageJson (..), + DependenciesMap, + PackageName, + PackageVersion, getDependencies, getDevDependencies, - analyzePackageJsonContent, - findPackageJsonFile, ) where -import Control.Monad.Except (ExceptT (ExceptT), runExceptT) import Data.Aeson (FromJSON) -import qualified Data.Aeson as Aeson import Data.Map (Map) import qualified Data.Map as M import GHC.Generics (Generic) -import StrongPath (Abs, Dir, File', Path', toFilePath) import Wasp.AppSpec.App.Dependency (Dependency) import qualified Wasp.AppSpec.App.Dependency as D -import Wasp.Generator.Common - ( prismaVersion, - reactRouterVersion, - ) -import Wasp.Project.Common - ( CompileError, - WaspProjectDir, - findFileInWaspProjectDir, - packageJsonInWaspProjectDir, - ) -import Wasp.Util (maybeToEither) -import qualified Wasp.Util.IO as IOUtil data PackageJson = PackageJson { name :: !String, @@ -51,71 +37,3 @@ type PackageName = String type PackageVersion = String instance FromJSON PackageJson - -analyzePackageJsonContent :: Path' Abs (Dir WaspProjectDir) -> IO (Either [CompileError] PackageJson) -analyzePackageJsonContent waspProjectDir = runExceptT $ do - packageJsonFile <- ExceptT findPackageJsonFileOrError - packageJson <- ExceptT $ readPackageJsonFile packageJsonFile - ExceptT $ validatePackageJson packageJson - where - findPackageJsonFileOrError = maybeToEither [fileNotFoundMessage] <$> findPackageJsonFile waspProjectDir - fileNotFoundMessage = "Couldn't find the package.json file in the " ++ toFilePath waspProjectDir ++ " directory" - -validatePackageJson :: PackageJson -> IO (Either [CompileError] PackageJson) -validatePackageJson packageJson = - return $ - if null packageJsonErrors - then Right packageJson - else Left packageJsonErrors - where - packageJsonErrors = - concat - [ -- Wasp needs the Wasp SDK to be installed in the project. - validate ("wasp", "file:.wasp/out/sdk/wasp", IsListedWithExactVersion), - -- Wrong version of Prisma will break the generated code. - validate ("prisma", show prismaVersion, IsListedAsDevWithExactVersion), - -- Installing the wrong version of "react-router-dom" can make users believe that they - -- can use features that are not available in the version that Wasp supports. - validate ("react-router-dom", show reactRouterVersion, HasExactVersionIfListed) - ] - validate = validatePackageInDeps packageJson - -findPackageJsonFile :: Path' Abs (Dir WaspProjectDir) -> IO (Maybe (Path' Abs File')) -findPackageJsonFile waspProjectDir = findFileInWaspProjectDir waspProjectDir packageJsonInWaspProjectDir - -readPackageJsonFile :: Path' Abs File' -> IO (Either [CompileError] PackageJson) -readPackageJsonFile packageJsonFile = do - byteString <- IOUtil.readFileBytes packageJsonFile - return $ maybeToEither ["Error parsing the package.json file"] $ Aeson.decode byteString - -data PackageValidationType = IsListedWithExactVersion | IsListedAsDevWithExactVersion | HasExactVersionIfListed - -validatePackageInDeps :: PackageJson -> (PackageName, PackageVersion, PackageValidationType) -> [CompileError] -validatePackageInDeps packageJson (packageName, expectedPackageVersion, validationType) = case validationType of - IsListedWithExactVersion -> checkDeps [dependencies packageJson] [requiredPackageMessage "dependencies"] - IsListedAsDevWithExactVersion -> checkDeps [devDependencies packageJson] [requiredPackageMessage "devDependencies"] - HasExactVersionIfListed -> checkDeps [dependencies packageJson, devDependencies packageJson] [] - where - checkDeps depsToCheck errorMessagesIfPackageNotListed = case map (M.lookup packageName) depsToCheck of - (Just actualPackageVersion : _) -> - if actualPackageVersion == expectedPackageVersion - then [] - else [incorrectVersionMessage] - _notListed -> errorMessagesIfPackageNotListed - - incorrectVersionMessage :: String - incorrectVersionMessage = - unwords - ["Wasp requires package", show packageName, "to be version", show expectedPackageVersion, "in package.json."] - - requiredPackageMessage :: String -> String - requiredPackageMessage packageJsonLocation = - unwords - [ "Wasp requires package", - show packageName, - "with version", - show expectedPackageVersion, - "in", - show packageJsonLocation, - "in package.json." - ] diff --git a/waspc/src/Wasp/ExternalConfig/TsConfig.hs b/waspc/src/Wasp/ExternalConfig/TsConfig.hs index b618a41c31..d4d10176f5 100644 --- a/waspc/src/Wasp/ExternalConfig/TsConfig.hs +++ b/waspc/src/Wasp/ExternalConfig/TsConfig.hs @@ -3,29 +3,17 @@ module Wasp.ExternalConfig.TsConfig ( TsConfig (..), - analyzeTsConfigContent, + CompilerOptions (..), ) where -import Control.Monad.Except import Data.Aeson ( FromJSON, genericParseJSON, parseJSON, ) import qualified Data.Aeson as Aeson -import qualified Data.ByteString.Lazy.UTF8 as BS -import Data.Either.Extra (maybeToEither) import GHC.Generics (Generic) -import StrongPath (Abs, Dir, File', Path', toFilePath) -import Wasp.Project.Common - ( CompileError, - WaspProjectDir, - findFileInWaspProjectDir, - tsConfigInWaspProjectDir, - ) -import qualified Wasp.Util.IO as IOUtil -import Wasp.Util.Json (parseJsonWithComments) data TsConfig = TsConfig { compilerOptions :: !CompilerOptions @@ -56,78 +44,3 @@ instance FromJSON CompilerOptions where -- "module" is a reserved keyword in Haskell, so we use "_module" instead. modifyFieldLabel "_module" = "module" modifyFieldLabel other = other - -analyzeTsConfigContent :: Path' Abs (Dir WaspProjectDir) -> IO (Either [CompileError] TsConfig) -analyzeTsConfigContent waspDir = runExceptT $ do - tsConfigFile <- ExceptT findTsConfigOrError - tsConfig <- ExceptT $ readTsConfigFile tsConfigFile - ExceptT $ validateTsConfig tsConfig - where - findTsConfigOrError = maybeToEither [fileNotFoundMessage] <$> findTsConfigFile waspDir - fileNotFoundMessage = "Couldn't find the tsconfig.json file in the " ++ toFilePath waspDir ++ " directory" - -findTsConfigFile :: Path' Abs (Dir WaspProjectDir) -> IO (Maybe (Path' Abs File')) -findTsConfigFile waspProjectDir = findFileInWaspProjectDir waspProjectDir tsConfigInWaspProjectDir - -readTsConfigFile :: Path' Abs File' -> IO (Either [CompileError] TsConfig) -readTsConfigFile tsConfigFile = do - tsConfigContent <- IOUtil.readFileBytes tsConfigFile - - parseResult <- parseJsonWithComments . BS.toString $ tsConfigContent - - case parseResult of - Right tsConfig -> return $ Right tsConfig - Left err -> return $ Left ["Failed to parse tsconfig.json file: " ++ err] - -validateTsConfig :: TsConfig -> IO (Either [CompileError] TsConfig) -validateTsConfig tsConfig = - return $ - if null tsConfigErrors - then Right tsConfig - else Left tsConfigErrors - where - tsConfigErrors = - concat - [ validateRequiredFieldInCompilerOptions "module" "esnext" _module, - validateRequiredFieldInCompilerOptions "target" "esnext" target, - validateRequiredFieldInCompilerOptions "moduleResolution" "bundler" moduleResolution, - validateRequiredFieldInCompilerOptions "jsx" "preserve" jsx, - validateRequiredFieldInCompilerOptions "strict" True strict, - validateRequiredFieldInCompilerOptions "esModuleInterop" True esModuleInterop, - validateRequiredFieldInCompilerOptions "lib" ["dom", "dom.iterable", "esnext"] lib, - validateRequiredFieldInCompilerOptions "allowJs" True allowJs, - validateRequiredFieldInCompilerOptions "typeRoots" ["node_modules/@testing-library", "node_modules/@types"] typeRoots, - validateRequiredFieldInCompilerOptions "outDir" ".wasp/phantom" outDir - ] - - validateRequiredFieldInCompilerOptions fieldName expectedValue getFieldValue = case getFieldValue compilerOptionsFields of - Just actualValue -> validateFieldValue ("compilerOptions." ++ fieldName) expectedValue actualValue - Nothing -> [missingFieldErrorMessage] - where - missingFieldErrorMessage = unwords ["The", show fieldName, "field is missing in tsconfig.json. Expected value:", showAsJsValue expectedValue ++ "."] - - compilerOptionsFields = compilerOptions tsConfig - --- | Haskell type that implements ShowJS is a type whose values can be mapped to Javascript values (their string representation). -class IsJavascriptValue a where - showAsJsValue :: a -> String - -instance IsJavascriptValue String where - showAsJsValue = show - -instance IsJavascriptValue [String] where - showAsJsValue = show - -instance IsJavascriptValue Bool where - showAsJsValue True = "true" - showAsJsValue False = "false" - -type FieldName = String - -validateFieldValue :: (Eq value, IsJavascriptValue value) => FieldName -> value -> value -> [CompileError] -validateFieldValue fieldName expectedValue actualValue = - if actualValue == expectedValue - then [] - else [invalidValueErrorMessage] - where - invalidValueErrorMessage = unwords ["Invalid value for the", show fieldName, "field in tsconfig.json file, expected value:", showAsJsValue expectedValue ++ "."] diff --git a/waspc/src/Wasp/Generator/Common.hs b/waspc/src/Wasp/Generator/Common.hs index 290abcd386..77523b8348 100644 --- a/waspc/src/Wasp/Generator/Common.hs +++ b/waspc/src/Wasp/Generator/Common.hs @@ -11,7 +11,6 @@ module Wasp.Generator.Common GeneratedSrcDir, makeJsArrayFromHaskellList, dropExtensionFromImportPath, - reactRouterVersion, ) where @@ -58,9 +57,6 @@ prismaVersion :: SV.Version -- Then, make sure `data/Generator/templates/sdk/wasp/prisma-runtime-library.d.ts` is up to date. prismaVersion = SV.Version 5 19 1 -reactRouterVersion :: SV.ComparatorSet -reactRouterVersion = SV.backwardsCompatibleWith $ SV.Version 5 3 3 - makeJsonWithEntityData :: String -> Aeson.Value makeJsonWithEntityData name = object diff --git a/waspc/src/Wasp/Generator/ExternalConfig/PackageJson.hs b/waspc/src/Wasp/Generator/ExternalConfig/PackageJson.hs new file mode 100644 index 0000000000..613bab6c38 --- /dev/null +++ b/waspc/src/Wasp/Generator/ExternalConfig/PackageJson.hs @@ -0,0 +1,63 @@ +module Wasp.Generator.ExternalConfig.PackageJson + ( validatePackageJson, + ) +where + +import qualified Data.Map as M +import qualified Wasp.ExternalConfig.PackageJson as P +import Wasp.Generator.Common (prismaVersion) +import Wasp.Generator.WebAppGenerator.Common (reactRouterVersion) +import Wasp.Project.Common + ( CompileError, + ) + +validatePackageJson :: P.PackageJson -> IO (Either [CompileError] P.PackageJson) +validatePackageJson packageJson = + return $ + if null packageJsonErrors + then Right packageJson + else Left packageJsonErrors + where + packageJsonErrors = + concat + [ -- Wasp needs the Wasp SDK to be installed in the project. + validate ("wasp", "file:.wasp/out/sdk/wasp", IsListedWithExactVersion), + -- Wrong version of Prisma will break the generated code. + validate ("prisma", show prismaVersion, IsListedAsDevWithExactVersion), + -- Installing the wrong version of "react-router-dom" can make users believe that they + -- can use features that are not available in the version that Wasp supports. + validate ("react-router-dom", show reactRouterVersion, HasExactVersionIfListed) + ] + validate = validatePackageInDeps packageJson + +data PackageValidationType = IsListedWithExactVersion | IsListedAsDevWithExactVersion | HasExactVersionIfListed + +validatePackageInDeps :: P.PackageJson -> (P.PackageName, P.PackageVersion, PackageValidationType) -> [CompileError] +validatePackageInDeps packageJson (packageName, expectedPackageVersion, validationType) = case validationType of + IsListedWithExactVersion -> checkDeps [P.dependencies packageJson] [requiredPackageMessage "dependencies"] + IsListedAsDevWithExactVersion -> checkDeps [P.devDependencies packageJson] [requiredPackageMessage "devDependencies"] + HasExactVersionIfListed -> checkDeps [P.dependencies packageJson, P.devDependencies packageJson] [] + where + checkDeps depsToCheck errorMessagesIfPackageNotListed = case map (M.lookup packageName) depsToCheck of + (Just actualPackageVersion : _) -> + if actualPackageVersion == expectedPackageVersion + then [] + else [incorrectVersionMessage] + _notListed -> errorMessagesIfPackageNotListed + + incorrectVersionMessage :: String + incorrectVersionMessage = + unwords + ["Wasp requires package", show packageName, "to be version", show expectedPackageVersion, "in package.json."] + + requiredPackageMessage :: String -> String + requiredPackageMessage packageJsonLocation = + unwords + [ "Wasp requires package", + show packageName, + "with version", + show expectedPackageVersion, + "in", + show packageJsonLocation, + "in package.json." + ] diff --git a/waspc/src/Wasp/Generator/ExternalConfig/TsConfig.hs b/waspc/src/Wasp/Generator/ExternalConfig/TsConfig.hs new file mode 100644 index 0000000000..9dfb8d4655 --- /dev/null +++ b/waspc/src/Wasp/Generator/ExternalConfig/TsConfig.hs @@ -0,0 +1,64 @@ +{-# LANGUAGE FlexibleInstances #-} + +module Wasp.Generator.ExternalConfig.TsConfig + ( validateTsConfig, + ) +where + +import qualified Wasp.ExternalConfig.TsConfig as T +import Wasp.Project.Common + ( CompileError, + ) + +validateTsConfig :: T.TsConfig -> IO (Either [CompileError] T.TsConfig) +validateTsConfig tsConfig = + return $ + if null tsConfigErrors + then Right tsConfig + else Left tsConfigErrors + where + tsConfigErrors = + concat + [ validateRequiredFieldInCompilerOptions "module" "esnext" T._module, + validateRequiredFieldInCompilerOptions "target" "esnext" T.target, + validateRequiredFieldInCompilerOptions "moduleResolution" "bundler" T.moduleResolution, + validateRequiredFieldInCompilerOptions "jsx" "preserve" T.jsx, + validateRequiredFieldInCompilerOptions "strict" True T.strict, + validateRequiredFieldInCompilerOptions "esModuleInterop" True T.esModuleInterop, + validateRequiredFieldInCompilerOptions "lib" ["dom", "dom.iterable", "esnext"] T.lib, + validateRequiredFieldInCompilerOptions "allowJs" True T.allowJs, + validateRequiredFieldInCompilerOptions "typeRoots" ["node_modules/@testing-library", "node_modules/@types"] T.typeRoots, + validateRequiredFieldInCompilerOptions "outDir" ".wasp/phantom" T.outDir + ] + + validateRequiredFieldInCompilerOptions fieldName expectedValue getFieldValue = case getFieldValue compilerOptionsFields of + Just actualValue -> validateFieldValue ("compilerOptions." ++ fieldName) expectedValue actualValue + Nothing -> [missingFieldErrorMessage] + where + missingFieldErrorMessage = unwords ["The", show fieldName, "field is missing in tsconfig.json. Expected value:", showAsJsValue expectedValue ++ "."] + + compilerOptionsFields = T.compilerOptions tsConfig + +-- | Haskell type that implements ShowJS is a type whose values can be mapped to Javascript values (their string representation). +class IsJavascriptValue a where + showAsJsValue :: a -> String + +instance IsJavascriptValue String where + showAsJsValue = show + +instance IsJavascriptValue [String] where + showAsJsValue = show + +instance IsJavascriptValue Bool where + showAsJsValue True = "true" + showAsJsValue False = "false" + +type FieldName = String + +validateFieldValue :: (Eq value, IsJavascriptValue value) => FieldName -> value -> value -> [CompileError] +validateFieldValue fieldName expectedValue actualValue = + if actualValue == expectedValue + then [] + else [invalidValueErrorMessage] + where + invalidValueErrorMessage = unwords ["Invalid value for the", show fieldName, "field in tsconfig.json file, expected value:", showAsJsValue expectedValue ++ "."] diff --git a/waspc/src/Wasp/Generator/SdkGenerator.hs b/waspc/src/Wasp/Generator/SdkGenerator.hs index e58ea9c723..35c01a7ed4 100644 --- a/waspc/src/Wasp/Generator/SdkGenerator.hs +++ b/waspc/src/Wasp/Generator/SdkGenerator.hs @@ -26,7 +26,7 @@ import qualified Wasp.AppSpec.App.Dependency as AS.Dependency import qualified Wasp.AppSpec.ExternalFiles as EC import Wasp.AppSpec.Valid (getLowestNodeVersionUserAllows, isAuthEnabled) import qualified Wasp.AppSpec.Valid as AS.Valid -import Wasp.Generator.Common (ProjectRootDir, makeJsonWithEntityData, prismaVersion, reactRouterVersion) +import Wasp.Generator.Common (ProjectRootDir, makeJsonWithEntityData, prismaVersion) import qualified Wasp.Generator.ConfigFile as G.CF import Wasp.Generator.DbGenerator (getEntitiesForPrismaSchema) import qualified Wasp.Generator.DbGenerator.Auth as DbAuth @@ -54,6 +54,7 @@ import Wasp.Generator.SdkGenerator.ServerApiG (genServerApi) import Wasp.Generator.SdkGenerator.WebSocketGenerator (depsRequiredByWebSockets, genWebSockets) import qualified Wasp.Generator.ServerGenerator.AuthG as ServerAuthG import qualified Wasp.Generator.ServerGenerator.Common as Server +import Wasp.Generator.WebAppGenerator.Common (reactRouterVersion) import qualified Wasp.Generator.WebAppGenerator.Common as WebApp import qualified Wasp.Node.Version as NodeVersion import Wasp.Project.Common (WaspProjectDir) diff --git a/waspc/src/Wasp/Generator/WebAppGenerator.hs b/waspc/src/Wasp/Generator/WebAppGenerator.hs index 9281b50405..7721304d28 100644 --- a/waspc/src/Wasp/Generator/WebAppGenerator.hs +++ b/waspc/src/Wasp/Generator/WebAppGenerator.hs @@ -30,17 +30,14 @@ import qualified Wasp.AppSpec.App.Client as AS.App.Client import qualified Wasp.AppSpec.App.Dependency as AS.Dependency import Wasp.AppSpec.Valid (getApp) import Wasp.Env (envVarsToDotEnvContent) -import Wasp.Generator.Common - ( makeJsArrayFromHaskellList, - reactRouterVersion, - ) +import Wasp.Generator.Common (makeJsArrayFromHaskellList) import Wasp.Generator.FileDraft (FileDraft, createTextFileDraft) import qualified Wasp.Generator.FileDraft as FD import Wasp.Generator.JsImport (jsImportToImportJson) import Wasp.Generator.Monad (Generator) import qualified Wasp.Generator.NpmDependencies as N import Wasp.Generator.WebAppGenerator.AuthG (genAuth) -import Wasp.Generator.WebAppGenerator.Common (webAppRootDirInProjectRootDir, webAppSrcDirInWebAppRootDir) +import Wasp.Generator.WebAppGenerator.Common (reactRouterVersion, webAppRootDirInProjectRootDir, webAppSrcDirInWebAppRootDir) import qualified Wasp.Generator.WebAppGenerator.Common as C import Wasp.Generator.WebAppGenerator.JsImport (extImportToImportJson) import Wasp.Generator.WebAppGenerator.RouterGenerator (genRouter) diff --git a/waspc/src/Wasp/Generator/WebAppGenerator/Common.hs b/waspc/src/Wasp/Generator/WebAppGenerator/Common.hs index 3e3c6d623b..ff58b06678 100644 --- a/waspc/src/Wasp/Generator/WebAppGenerator/Common.hs +++ b/waspc/src/Wasp/Generator/WebAppGenerator/Common.hs @@ -24,6 +24,7 @@ module Wasp.Generator.WebAppGenerator.Common getBaseDir, getDefaultDevClientUrl, defaultClientPort, + reactRouterVersion, ) where @@ -48,6 +49,7 @@ import Wasp.Generator.Common ) import Wasp.Generator.FileDraft (FileDraft, createCopyFileDraft, createTemplateFileDraft) import Wasp.Generator.Templates (TemplatesDir) +import qualified Wasp.SemanticVersion as SV data WebAppSrcDir @@ -150,3 +152,6 @@ defaultClientPort = 3000 getDefaultDevClientUrl :: AppSpec -> String getDefaultDevClientUrl spec = "http://localhost:" ++ show defaultClientPort ++ SP.fromAbsDirP (getBaseDir spec) + +reactRouterVersion :: SV.ComparatorSet +reactRouterVersion = SV.backwardsCompatibleWith $ SV.Version 5 3 3 diff --git a/waspc/src/Wasp/Project/Analyze.hs b/waspc/src/Wasp/Project/Analyze.hs index e7e5ece230..5cd19cd96a 100644 --- a/waspc/src/Wasp/Project/Analyze.hs +++ b/waspc/src/Wasp/Project/Analyze.hs @@ -18,7 +18,6 @@ import Wasp.CompileOptions (CompileOptions) import qualified Wasp.CompileOptions as CompileOptions import qualified Wasp.ConfigFile as CF import Wasp.Error (showCompilerErrorForTerminal) -import qualified Wasp.ExternalConfig as EC import qualified Wasp.Generator.ConfigFile as G.CF import Wasp.Project.Common ( CompileError, @@ -31,6 +30,7 @@ import Wasp.Project.Db (makeDevDatabaseUrl) import Wasp.Project.Db.Migrations (findMigrationsDir) import Wasp.Project.Deployment (loadUserDockerfileContents) import Wasp.Project.Env (readDotEnvClient, readDotEnvServer) +import qualified Wasp.Project.ExternalConfig as EC import qualified Wasp.Project.ExternalFiles as ExternalFiles import Wasp.Project.Vite (findCustomViteConfigPath) import qualified Wasp.Psl.Ast.Schema as Psl.Schema @@ -94,7 +94,7 @@ constructAppSpec waspDir options externalConfigs parsedPrismaSchema decls = do serverEnvVars <- readDotEnvServer waspDir clientEnvVars <- readDotEnvClient waspDir - let packageJsonContent = EC.packageJson externalConfigs + let packageJsonContent = EC._packageJson externalConfigs let appSpec = AS.AppSpec diff --git a/waspc/src/Wasp/Project/Common.hs b/waspc/src/Wasp/Project/Common.hs index 993c969634..f6541cd725 100644 --- a/waspc/src/Wasp/Project/Common.hs +++ b/waspc/src/Wasp/Project/Common.hs @@ -4,6 +4,8 @@ module Wasp.Project.Common NodeModulesDir, CompileError, CompileWarning, + PackageJsonFile, + TsConfigFile, findFileInWaspProjectDir, dotWaspDirInWaspProjectDir, generatedCodeDirInDotWaspDir, @@ -22,7 +24,7 @@ module Wasp.Project.Common ) where -import StrongPath (Abs, Dir, File', Path', Rel, reldir, relfile, toFilePath, ()) +import StrongPath (Abs, Dir, File, File', Path', Rel, reldir, relfile, toFilePath, ()) import System.Directory (doesFileExist) import Wasp.AppSpec.ExternalFiles (SourceExternalCodeDir, SourceExternalPublicDir) import qualified Wasp.Generator.Common @@ -37,6 +39,10 @@ data NodeModulesDir data DotWaspDir -- Here we put everything that wasp generates. +data PackageJsonFile + +data TsConfigFile + -- | NOTE: If you change the depth of this path, also update @waspProjectDirFromProjectRootDir@ below. -- TODO: SHould this be renamed to include word "root"? dotWaspDirInWaspProjectDir :: Path' (Rel WaspProjectDir) (Dir DotWaspDir) @@ -67,10 +73,10 @@ dotWaspRootFileInWaspProjectDir = [relfile|.wasproot|] dotWaspInfoFileInGeneratedCodeDir :: Path' (Rel Wasp.Generator.Common.ProjectRootDir) File' dotWaspInfoFileInGeneratedCodeDir = [relfile|.waspinfo|] -packageJsonInWaspProjectDir :: Path' (Rel WaspProjectDir) File' +packageJsonInWaspProjectDir :: Path' (Rel WaspProjectDir) (File PackageJsonFile) packageJsonInWaspProjectDir = [relfile|package.json|] -tsConfigInWaspProjectDir :: Path' (Rel WaspProjectDir) File' +tsConfigInWaspProjectDir :: Path' (Rel WaspProjectDir) (File TsConfigFile) tsConfigInWaspProjectDir = [relfile|tsconfig.json|] packageLockJsonInWaspProjectDir :: Path' (Rel WaspProjectDir) File' @@ -90,8 +96,8 @@ tsconfigInWaspProjectDir = [relfile|tsconfig.json|] findFileInWaspProjectDir :: Path' Abs (Dir WaspProjectDir) -> - Path' (Rel WaspProjectDir) File' -> - IO (Maybe (Path' Abs File')) + Path' (Rel WaspProjectDir) (File file) -> + IO (Maybe (Path' Abs (File file))) findFileInWaspProjectDir waspDir file = do let fileAbsFp = waspDir file fileExists <- doesFileExist $ toFilePath fileAbsFp diff --git a/waspc/src/Wasp/Project/ExternalConfig.hs b/waspc/src/Wasp/Project/ExternalConfig.hs new file mode 100644 index 0000000000..1329f717e0 --- /dev/null +++ b/waspc/src/Wasp/Project/ExternalConfig.hs @@ -0,0 +1,83 @@ +module Wasp.Project.ExternalConfig + ( analyzeExternalConfigs, + findPackageJsonFile, + ExternalConfigs (..), + ) +where + +import Control.Monad.Except (ExceptT (ExceptT), runExceptT) +import qualified Data.Aeson as Aeson +import qualified Data.ByteString.Lazy.UTF8 as BS +import Data.Either.Extra (maybeToEither) +import StrongPath (Abs, Dir, File, Path', toFilePath) +import qualified Wasp.ExternalConfig.PackageJson as P +import qualified Wasp.ExternalConfig.TsConfig as T +import Wasp.Generator.ExternalConfig.PackageJson (validatePackageJson) +import Wasp.Generator.ExternalConfig.TsConfig (validateTsConfig) +import Wasp.Project.Common + ( CompileError, + PackageJsonFile, + TsConfigFile, + WaspProjectDir, + findFileInWaspProjectDir, + packageJsonInWaspProjectDir, + tsConfigInWaspProjectDir, + ) +import qualified Wasp.Util.IO as IOUtil +import Wasp.Util.Json (parseJsonWithComments) + +data ExternalConfigs = ExternalConfigs + { _packageJson :: P.PackageJson, + _tsConfig :: T.TsConfig + } + deriving (Show) + +analyzeExternalConfigs :: Path' Abs (Dir WaspProjectDir) -> IO (Either [CompileError] ExternalConfigs) +analyzeExternalConfigs waspDir = runExceptT $ do + packageJsonContent <- ExceptT $ analyzePackageJsonContent waspDir + tsConfigContent <- ExceptT $ analyzeTsConfigContent waspDir + + return $ + ExternalConfigs + { _packageJson = packageJsonContent, + _tsConfig = tsConfigContent + } + +analyzePackageJsonContent :: Path' Abs (Dir WaspProjectDir) -> IO (Either [CompileError] P.PackageJson) +analyzePackageJsonContent waspProjectDir = runExceptT $ do + packageJsonFile <- ExceptT findPackageJsonFileOrError + packageJson <- ExceptT $ readPackageJsonFile packageJsonFile + ExceptT $ validatePackageJson packageJson + where + findPackageJsonFileOrError = maybeToEither [fileNotFoundMessage] <$> findPackageJsonFile waspProjectDir + fileNotFoundMessage = "Couldn't find the package.json file in the " ++ toFilePath waspProjectDir ++ " directory" + +findPackageJsonFile :: Path' Abs (Dir WaspProjectDir) -> IO (Maybe (Path' Abs (File PackageJsonFile))) +findPackageJsonFile waspProjectDir = findFileInWaspProjectDir waspProjectDir packageJsonInWaspProjectDir + +readPackageJsonFile :: Path' Abs (File PackageJsonFile) -> IO (Either [CompileError] P.PackageJson) +readPackageJsonFile packageJsonFile = do + byteString <- IOUtil.readFileBytes packageJsonFile + return $ maybeToEither ["Error parsing the package.json file"] $ Aeson.decode byteString + +analyzeTsConfigContent :: Path' Abs (Dir WaspProjectDir) -> IO (Either [CompileError] T.TsConfig) +analyzeTsConfigContent waspDir = runExceptT $ do + tsConfigFile <- ExceptT findTsConfigOrError + tsConfig <- ExceptT $ readTsConfigFile tsConfigFile + ExceptT $ validateTsConfig tsConfig + where + findTsConfigOrError = maybeToEither [fileNotFoundMessage] <$> findTsConfigFile waspDir + fileNotFoundMessage = "Couldn't find the tsconfig.json file in the " ++ toFilePath waspDir ++ " directory" + +findTsConfigFile :: Path' Abs (Dir WaspProjectDir) -> IO (Maybe (Path' Abs (File TsConfigFile))) +findTsConfigFile waspProjectDir = findFileInWaspProjectDir waspProjectDir tsConfigInWaspProjectDir + +readTsConfigFile :: Path' Abs (File TsConfigFile) -> IO (Either [CompileError] T.TsConfig) +readTsConfigFile tsConfigFile = do + tsConfigContent <- IOUtil.readFileBytes tsConfigFile + + parseResult <- parseJsonWithComments . BS.toString $ tsConfigContent + + case parseResult of + Right tsConfig -> return $ Right tsConfig + Left err -> return $ Left ["Failed to parse tsconfig.json file: " ++ err] diff --git a/waspc/waspc.cabal b/waspc/waspc.cabal index 33b82e7764..d85c42ce36 100644 --- a/waspc/waspc.cabal +++ b/waspc/waspc.cabal @@ -259,7 +259,6 @@ library Wasp.Db.Postgres Wasp.Error Wasp.Env - Wasp.ExternalConfig Wasp.ExternalConfig.PackageJson Wasp.ExternalConfig.TsConfig Wasp.Generator @@ -280,6 +279,8 @@ library Wasp.Generator.DbGenerator.Operations Wasp.Generator.DockerGenerator Wasp.Generator.ExternalCodeGenerator.Common + Wasp.Generator.ExternalConfig.PackageJson + Wasp.Generator.ExternalConfig.TsConfig Wasp.Generator.FileDraft Wasp.Generator.FileDraft.CopyDirFileDraft Wasp.Generator.FileDraft.CopyFileDraft @@ -366,6 +367,7 @@ library Wasp.Project.Db.Dev.Sqlite Wasp.Project.Deployment Wasp.Project.Env + Wasp.Project.ExternalConfig Wasp.Project.ExternalFiles Wasp.Project.Studio Wasp.Project.Vite From e26238d2dfde0e434abc08c780ff9f873d751fc3 Mon Sep 17 00:00:00 2001 From: Mihovil Ilakovac Date: Thu, 26 Sep 2024 14:14:18 +0200 Subject: [PATCH 17/24] Extract PackageJson and TsConfig in Project modules --- .../StarterTemplates/Templating.hs | 2 +- waspc/src/Wasp/Project/ExternalConfig.hs | 60 ++----------------- .../Project/ExternalConfig/PackageJson.hs | 37 ++++++++++++ .../Wasp/Project/ExternalConfig/TsConfig.hs | 42 +++++++++++++ waspc/waspc.cabal | 2 + 5 files changed, 87 insertions(+), 56 deletions(-) create mode 100644 waspc/src/Wasp/Project/ExternalConfig/PackageJson.hs create mode 100644 waspc/src/Wasp/Project/ExternalConfig/TsConfig.hs diff --git a/waspc/cli/src/Wasp/Cli/Command/CreateNewProject/StarterTemplates/Templating.hs b/waspc/cli/src/Wasp/Cli/Command/CreateNewProject/StarterTemplates/Templating.hs index f541431f14..7c3cb58570 100644 --- a/waspc/cli/src/Wasp/Cli/Command/CreateNewProject/StarterTemplates/Templating.hs +++ b/waspc/cli/src/Wasp/Cli/Command/CreateNewProject/StarterTemplates/Templating.hs @@ -11,7 +11,7 @@ import Wasp.Cli.Command.CreateNewProject.Common (defaultWaspVersionBounds) import Wasp.Cli.Command.CreateNewProject.ProjectDescription (NewProjectAppName, NewProjectName) import Wasp.Project.Analyze (findWaspFile) import Wasp.Project.Common (WaspProjectDir) -import Wasp.Project.ExternalConfig (findPackageJsonFile) +import Wasp.Project.ExternalConfig.PackageJson (findPackageJsonFile) import qualified Wasp.Util.IO as IOUtil replaceTemplatePlaceholdersInTemplateFiles :: NewProjectAppName -> NewProjectName -> Path' Abs (Dir WaspProjectDir) -> IO () diff --git a/waspc/src/Wasp/Project/ExternalConfig.hs b/waspc/src/Wasp/Project/ExternalConfig.hs index 1329f717e0..c03a2732de 100644 --- a/waspc/src/Wasp/Project/ExternalConfig.hs +++ b/waspc/src/Wasp/Project/ExternalConfig.hs @@ -1,30 +1,19 @@ module Wasp.Project.ExternalConfig ( analyzeExternalConfigs, - findPackageJsonFile, ExternalConfigs (..), ) where import Control.Monad.Except (ExceptT (ExceptT), runExceptT) -import qualified Data.Aeson as Aeson -import qualified Data.ByteString.Lazy.UTF8 as BS -import Data.Either.Extra (maybeToEither) -import StrongPath (Abs, Dir, File, Path', toFilePath) +import StrongPath (Abs, Dir, Path') import qualified Wasp.ExternalConfig.PackageJson as P import qualified Wasp.ExternalConfig.TsConfig as T -import Wasp.Generator.ExternalConfig.PackageJson (validatePackageJson) -import Wasp.Generator.ExternalConfig.TsConfig (validateTsConfig) import Wasp.Project.Common ( CompileError, - PackageJsonFile, - TsConfigFile, WaspProjectDir, - findFileInWaspProjectDir, - packageJsonInWaspProjectDir, - tsConfigInWaspProjectDir, ) -import qualified Wasp.Util.IO as IOUtil -import Wasp.Util.Json (parseJsonWithComments) +import Wasp.Project.ExternalConfig.PackageJson (analyzePackageJsonFile) +import Wasp.Project.ExternalConfig.TsConfig (analyzeTsConfigFile) data ExternalConfigs = ExternalConfigs { _packageJson :: P.PackageJson, @@ -34,50 +23,11 @@ data ExternalConfigs = ExternalConfigs analyzeExternalConfigs :: Path' Abs (Dir WaspProjectDir) -> IO (Either [CompileError] ExternalConfigs) analyzeExternalConfigs waspDir = runExceptT $ do - packageJsonContent <- ExceptT $ analyzePackageJsonContent waspDir - tsConfigContent <- ExceptT $ analyzeTsConfigContent waspDir + packageJsonContent <- ExceptT $ analyzePackageJsonFile waspDir + tsConfigContent <- ExceptT $ analyzeTsConfigFile waspDir return $ ExternalConfigs { _packageJson = packageJsonContent, _tsConfig = tsConfigContent } - -analyzePackageJsonContent :: Path' Abs (Dir WaspProjectDir) -> IO (Either [CompileError] P.PackageJson) -analyzePackageJsonContent waspProjectDir = runExceptT $ do - packageJsonFile <- ExceptT findPackageJsonFileOrError - packageJson <- ExceptT $ readPackageJsonFile packageJsonFile - ExceptT $ validatePackageJson packageJson - where - findPackageJsonFileOrError = maybeToEither [fileNotFoundMessage] <$> findPackageJsonFile waspProjectDir - fileNotFoundMessage = "Couldn't find the package.json file in the " ++ toFilePath waspProjectDir ++ " directory" - -findPackageJsonFile :: Path' Abs (Dir WaspProjectDir) -> IO (Maybe (Path' Abs (File PackageJsonFile))) -findPackageJsonFile waspProjectDir = findFileInWaspProjectDir waspProjectDir packageJsonInWaspProjectDir - -readPackageJsonFile :: Path' Abs (File PackageJsonFile) -> IO (Either [CompileError] P.PackageJson) -readPackageJsonFile packageJsonFile = do - byteString <- IOUtil.readFileBytes packageJsonFile - return $ maybeToEither ["Error parsing the package.json file"] $ Aeson.decode byteString - -analyzeTsConfigContent :: Path' Abs (Dir WaspProjectDir) -> IO (Either [CompileError] T.TsConfig) -analyzeTsConfigContent waspDir = runExceptT $ do - tsConfigFile <- ExceptT findTsConfigOrError - tsConfig <- ExceptT $ readTsConfigFile tsConfigFile - ExceptT $ validateTsConfig tsConfig - where - findTsConfigOrError = maybeToEither [fileNotFoundMessage] <$> findTsConfigFile waspDir - fileNotFoundMessage = "Couldn't find the tsconfig.json file in the " ++ toFilePath waspDir ++ " directory" - -findTsConfigFile :: Path' Abs (Dir WaspProjectDir) -> IO (Maybe (Path' Abs (File TsConfigFile))) -findTsConfigFile waspProjectDir = findFileInWaspProjectDir waspProjectDir tsConfigInWaspProjectDir - -readTsConfigFile :: Path' Abs (File TsConfigFile) -> IO (Either [CompileError] T.TsConfig) -readTsConfigFile tsConfigFile = do - tsConfigContent <- IOUtil.readFileBytes tsConfigFile - - parseResult <- parseJsonWithComments . BS.toString $ tsConfigContent - - case parseResult of - Right tsConfig -> return $ Right tsConfig - Left err -> return $ Left ["Failed to parse tsconfig.json file: " ++ err] diff --git a/waspc/src/Wasp/Project/ExternalConfig/PackageJson.hs b/waspc/src/Wasp/Project/ExternalConfig/PackageJson.hs new file mode 100644 index 0000000000..8aa3509465 --- /dev/null +++ b/waspc/src/Wasp/Project/ExternalConfig/PackageJson.hs @@ -0,0 +1,37 @@ +module Wasp.Project.ExternalConfig.PackageJson + ( analyzePackageJsonFile, + findPackageJsonFile, + ) +where + +import Control.Monad.Except (ExceptT (ExceptT), runExceptT) +import qualified Data.Aeson as Aeson +import Data.Either.Extra (maybeToEither) +import StrongPath (Abs, Dir, File, Path', toFilePath) +import qualified Wasp.ExternalConfig.PackageJson as P +import Wasp.Generator.ExternalConfig.PackageJson (validatePackageJson) +import Wasp.Project.Common + ( CompileError, + PackageJsonFile, + WaspProjectDir, + findFileInWaspProjectDir, + packageJsonInWaspProjectDir, + ) +import qualified Wasp.Util.IO as IOUtil + +analyzePackageJsonFile :: Path' Abs (Dir WaspProjectDir) -> IO (Either [CompileError] P.PackageJson) +analyzePackageJsonFile waspProjectDir = runExceptT $ do + packageJsonFile <- ExceptT findPackageJsonFileOrError + packageJson <- ExceptT $ readPackageJsonFile packageJsonFile + ExceptT $ validatePackageJson packageJson + where + findPackageJsonFileOrError = maybeToEither [fileNotFoundMessage] <$> findPackageJsonFile waspProjectDir + fileNotFoundMessage = "Couldn't find the package.json file in the " ++ toFilePath waspProjectDir ++ " directory" + +findPackageJsonFile :: Path' Abs (Dir WaspProjectDir) -> IO (Maybe (Path' Abs (File PackageJsonFile))) +findPackageJsonFile waspProjectDir = findFileInWaspProjectDir waspProjectDir packageJsonInWaspProjectDir + +readPackageJsonFile :: Path' Abs (File PackageJsonFile) -> IO (Either [CompileError] P.PackageJson) +readPackageJsonFile packageJsonFile = do + byteString <- IOUtil.readFileBytes packageJsonFile + return $ maybeToEither ["Error parsing the package.json file"] $ Aeson.decode byteString diff --git a/waspc/src/Wasp/Project/ExternalConfig/TsConfig.hs b/waspc/src/Wasp/Project/ExternalConfig/TsConfig.hs new file mode 100644 index 0000000000..47c85fcc16 --- /dev/null +++ b/waspc/src/Wasp/Project/ExternalConfig/TsConfig.hs @@ -0,0 +1,42 @@ +module Wasp.Project.ExternalConfig.TsConfig + ( analyzeTsConfigFile, + ) +where + +import Control.Monad.Except (ExceptT (ExceptT), runExceptT) +import qualified Data.ByteString.Lazy.UTF8 as BS +import Data.Either.Extra (maybeToEither) +import StrongPath (Abs, Dir, File, Path', toFilePath) +import qualified Wasp.ExternalConfig.TsConfig as T +import Wasp.Generator.ExternalConfig.TsConfig (validateTsConfig) +import Wasp.Project.Common + ( CompileError, + TsConfigFile, + WaspProjectDir, + findFileInWaspProjectDir, + tsConfigInWaspProjectDir, + ) +import qualified Wasp.Util.IO as IOUtil +import Wasp.Util.Json (parseJsonWithComments) + +analyzeTsConfigFile :: Path' Abs (Dir WaspProjectDir) -> IO (Either [CompileError] T.TsConfig) +analyzeTsConfigFile waspDir = runExceptT $ do + tsConfigFile <- ExceptT findTsConfigOrError + tsConfig <- ExceptT $ readTsConfigFile tsConfigFile + ExceptT $ validateTsConfig tsConfig + where + findTsConfigOrError = maybeToEither [fileNotFoundMessage] <$> findTsConfigFile waspDir + fileNotFoundMessage = "Couldn't find the tsconfig.json file in the " ++ toFilePath waspDir ++ " directory" + +findTsConfigFile :: Path' Abs (Dir WaspProjectDir) -> IO (Maybe (Path' Abs (File TsConfigFile))) +findTsConfigFile waspProjectDir = findFileInWaspProjectDir waspProjectDir tsConfigInWaspProjectDir + +readTsConfigFile :: Path' Abs (File TsConfigFile) -> IO (Either [CompileError] T.TsConfig) +readTsConfigFile tsConfigFile = do + tsConfigContent <- IOUtil.readFileBytes tsConfigFile + + parseResult <- parseJsonWithComments . BS.toString $ tsConfigContent + + case parseResult of + Right tsConfig -> return $ Right tsConfig + Left err -> return $ Left ["Failed to parse tsconfig.json file: " ++ err] diff --git a/waspc/waspc.cabal b/waspc/waspc.cabal index d85c42ce36..b8e1b353d7 100644 --- a/waspc/waspc.cabal +++ b/waspc/waspc.cabal @@ -368,6 +368,8 @@ library Wasp.Project.Deployment Wasp.Project.Env Wasp.Project.ExternalConfig + Wasp.Project.ExternalConfig.PackageJson + Wasp.Project.ExternalConfig.TsConfig Wasp.Project.ExternalFiles Wasp.Project.Studio Wasp.Project.Vite From dbafdcea5e7a810410b4d59a2ebe16c6e4e64fc8 Mon Sep 17 00:00:00 2001 From: Mihovil Ilakovac Date: Thu, 26 Sep 2024 14:15:40 +0200 Subject: [PATCH 18/24] Update naming --- waspc/src/Wasp/Generator/ExternalConfig/PackageJson.hs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/waspc/src/Wasp/Generator/ExternalConfig/PackageJson.hs b/waspc/src/Wasp/Generator/ExternalConfig/PackageJson.hs index 613bab6c38..2fdd868fe1 100644 --- a/waspc/src/Wasp/Generator/ExternalConfig/PackageJson.hs +++ b/waspc/src/Wasp/Generator/ExternalConfig/PackageJson.hs @@ -28,12 +28,12 @@ validatePackageJson packageJson = -- can use features that are not available in the version that Wasp supports. validate ("react-router-dom", show reactRouterVersion, HasExactVersionIfListed) ] - validate = validatePackageInDeps packageJson + validate = validateDep packageJson data PackageValidationType = IsListedWithExactVersion | IsListedAsDevWithExactVersion | HasExactVersionIfListed -validatePackageInDeps :: P.PackageJson -> (P.PackageName, P.PackageVersion, PackageValidationType) -> [CompileError] -validatePackageInDeps packageJson (packageName, expectedPackageVersion, validationType) = case validationType of +validateDep :: P.PackageJson -> (P.PackageName, P.PackageVersion, PackageValidationType) -> [CompileError] +validateDep packageJson (packageName, expectedPackageVersion, validationType) = case validationType of IsListedWithExactVersion -> checkDeps [P.dependencies packageJson] [requiredPackageMessage "dependencies"] IsListedAsDevWithExactVersion -> checkDeps [P.devDependencies packageJson] [requiredPackageMessage "devDependencies"] HasExactVersionIfListed -> checkDeps [P.dependencies packageJson, P.devDependencies packageJson] [] From d3fd09f94c679dea0c3047dbb55f107c1bf008f5 Mon Sep 17 00:00:00 2001 From: Mihovil Ilakovac Date: Thu, 26 Sep 2024 14:36:27 +0200 Subject: [PATCH 19/24] Update validation fns --- .../Generator/ExternalConfig/PackageJson.hs | 29 ++++++--------- .../Wasp/Generator/ExternalConfig/TsConfig.hs | 36 ++++++++----------- .../Project/ExternalConfig/PackageJson.hs | 10 +++--- .../Wasp/Project/ExternalConfig/TsConfig.hs | 10 +++--- waspc/src/Wasp/Util.hs | 7 ++++ 5 files changed, 42 insertions(+), 50 deletions(-) diff --git a/waspc/src/Wasp/Generator/ExternalConfig/PackageJson.hs b/waspc/src/Wasp/Generator/ExternalConfig/PackageJson.hs index 2fdd868fe1..0473d3b5d0 100644 --- a/waspc/src/Wasp/Generator/ExternalConfig/PackageJson.hs +++ b/waspc/src/Wasp/Generator/ExternalConfig/PackageJson.hs @@ -7,32 +7,25 @@ import qualified Data.Map as M import qualified Wasp.ExternalConfig.PackageJson as P import Wasp.Generator.Common (prismaVersion) import Wasp.Generator.WebAppGenerator.Common (reactRouterVersion) -import Wasp.Project.Common - ( CompileError, - ) -validatePackageJson :: P.PackageJson -> IO (Either [CompileError] P.PackageJson) +validatePackageJson :: P.PackageJson -> IO [String] validatePackageJson packageJson = return $ - if null packageJsonErrors - then Right packageJson - else Left packageJsonErrors + concat + [ -- Wasp needs the Wasp SDK to be installed in the project. + validate ("wasp", "file:.wasp/out/sdk/wasp", IsListedWithExactVersion), + -- Wrong version of Prisma will break the generated code. + validate ("prisma", show prismaVersion, IsListedAsDevWithExactVersion), + -- Installing the wrong version of "react-router-dom" can make users believe that they + -- can use features that are not available in the version that Wasp supports. + validate ("react-router-dom", show reactRouterVersion, HasExactVersionIfListed) + ] where - packageJsonErrors = - concat - [ -- Wasp needs the Wasp SDK to be installed in the project. - validate ("wasp", "file:.wasp/out/sdk/wasp", IsListedWithExactVersion), - -- Wrong version of Prisma will break the generated code. - validate ("prisma", show prismaVersion, IsListedAsDevWithExactVersion), - -- Installing the wrong version of "react-router-dom" can make users believe that they - -- can use features that are not available in the version that Wasp supports. - validate ("react-router-dom", show reactRouterVersion, HasExactVersionIfListed) - ] validate = validateDep packageJson data PackageValidationType = IsListedWithExactVersion | IsListedAsDevWithExactVersion | HasExactVersionIfListed -validateDep :: P.PackageJson -> (P.PackageName, P.PackageVersion, PackageValidationType) -> [CompileError] +validateDep :: P.PackageJson -> (P.PackageName, P.PackageVersion, PackageValidationType) -> [String] validateDep packageJson (packageName, expectedPackageVersion, validationType) = case validationType of IsListedWithExactVersion -> checkDeps [P.dependencies packageJson] [requiredPackageMessage "dependencies"] IsListedAsDevWithExactVersion -> checkDeps [P.devDependencies packageJson] [requiredPackageMessage "devDependencies"] diff --git a/waspc/src/Wasp/Generator/ExternalConfig/TsConfig.hs b/waspc/src/Wasp/Generator/ExternalConfig/TsConfig.hs index 9dfb8d4655..f3f9a04c13 100644 --- a/waspc/src/Wasp/Generator/ExternalConfig/TsConfig.hs +++ b/waspc/src/Wasp/Generator/ExternalConfig/TsConfig.hs @@ -6,31 +6,23 @@ module Wasp.Generator.ExternalConfig.TsConfig where import qualified Wasp.ExternalConfig.TsConfig as T -import Wasp.Project.Common - ( CompileError, - ) -validateTsConfig :: T.TsConfig -> IO (Either [CompileError] T.TsConfig) +validateTsConfig :: T.TsConfig -> IO [String] validateTsConfig tsConfig = return $ - if null tsConfigErrors - then Right tsConfig - else Left tsConfigErrors + concat + [ validateRequiredFieldInCompilerOptions "module" "esnext" T._module, + validateRequiredFieldInCompilerOptions "target" "esnext" T.target, + validateRequiredFieldInCompilerOptions "moduleResolution" "bundler" T.moduleResolution, + validateRequiredFieldInCompilerOptions "jsx" "preserve" T.jsx, + validateRequiredFieldInCompilerOptions "strict" True T.strict, + validateRequiredFieldInCompilerOptions "esModuleInterop" True T.esModuleInterop, + validateRequiredFieldInCompilerOptions "lib" ["dom", "dom.iterable", "esnext"] T.lib, + validateRequiredFieldInCompilerOptions "allowJs" True T.allowJs, + validateRequiredFieldInCompilerOptions "typeRoots" ["node_modules/@testing-library", "node_modules/@types"] T.typeRoots, + validateRequiredFieldInCompilerOptions "outDir" ".wasp/phantom" T.outDir + ] where - tsConfigErrors = - concat - [ validateRequiredFieldInCompilerOptions "module" "esnext" T._module, - validateRequiredFieldInCompilerOptions "target" "esnext" T.target, - validateRequiredFieldInCompilerOptions "moduleResolution" "bundler" T.moduleResolution, - validateRequiredFieldInCompilerOptions "jsx" "preserve" T.jsx, - validateRequiredFieldInCompilerOptions "strict" True T.strict, - validateRequiredFieldInCompilerOptions "esModuleInterop" True T.esModuleInterop, - validateRequiredFieldInCompilerOptions "lib" ["dom", "dom.iterable", "esnext"] T.lib, - validateRequiredFieldInCompilerOptions "allowJs" True T.allowJs, - validateRequiredFieldInCompilerOptions "typeRoots" ["node_modules/@testing-library", "node_modules/@types"] T.typeRoots, - validateRequiredFieldInCompilerOptions "outDir" ".wasp/phantom" T.outDir - ] - validateRequiredFieldInCompilerOptions fieldName expectedValue getFieldValue = case getFieldValue compilerOptionsFields of Just actualValue -> validateFieldValue ("compilerOptions." ++ fieldName) expectedValue actualValue Nothing -> [missingFieldErrorMessage] @@ -55,7 +47,7 @@ instance IsJavascriptValue Bool where type FieldName = String -validateFieldValue :: (Eq value, IsJavascriptValue value) => FieldName -> value -> value -> [CompileError] +validateFieldValue :: (Eq value, IsJavascriptValue value) => FieldName -> value -> value -> [String] validateFieldValue fieldName expectedValue actualValue = if actualValue == expectedValue then [] diff --git a/waspc/src/Wasp/Project/ExternalConfig/PackageJson.hs b/waspc/src/Wasp/Project/ExternalConfig/PackageJson.hs index 8aa3509465..a46fd9e800 100644 --- a/waspc/src/Wasp/Project/ExternalConfig/PackageJson.hs +++ b/waspc/src/Wasp/Project/ExternalConfig/PackageJson.hs @@ -11,19 +11,19 @@ import StrongPath (Abs, Dir, File, Path', toFilePath) import qualified Wasp.ExternalConfig.PackageJson as P import Wasp.Generator.ExternalConfig.PackageJson (validatePackageJson) import Wasp.Project.Common - ( CompileError, - PackageJsonFile, + ( PackageJsonFile, WaspProjectDir, findFileInWaspProjectDir, packageJsonInWaspProjectDir, ) +import Wasp.Util (validateToEither) import qualified Wasp.Util.IO as IOUtil -analyzePackageJsonFile :: Path' Abs (Dir WaspProjectDir) -> IO (Either [CompileError] P.PackageJson) +analyzePackageJsonFile :: Path' Abs (Dir WaspProjectDir) -> IO (Either [String] P.PackageJson) analyzePackageJsonFile waspProjectDir = runExceptT $ do packageJsonFile <- ExceptT findPackageJsonFileOrError packageJson <- ExceptT $ readPackageJsonFile packageJsonFile - ExceptT $ validatePackageJson packageJson + ExceptT $ validateToEither validatePackageJson packageJson where findPackageJsonFileOrError = maybeToEither [fileNotFoundMessage] <$> findPackageJsonFile waspProjectDir fileNotFoundMessage = "Couldn't find the package.json file in the " ++ toFilePath waspProjectDir ++ " directory" @@ -31,7 +31,7 @@ analyzePackageJsonFile waspProjectDir = runExceptT $ do findPackageJsonFile :: Path' Abs (Dir WaspProjectDir) -> IO (Maybe (Path' Abs (File PackageJsonFile))) findPackageJsonFile waspProjectDir = findFileInWaspProjectDir waspProjectDir packageJsonInWaspProjectDir -readPackageJsonFile :: Path' Abs (File PackageJsonFile) -> IO (Either [CompileError] P.PackageJson) +readPackageJsonFile :: Path' Abs (File PackageJsonFile) -> IO (Either [String] P.PackageJson) readPackageJsonFile packageJsonFile = do byteString <- IOUtil.readFileBytes packageJsonFile return $ maybeToEither ["Error parsing the package.json file"] $ Aeson.decode byteString diff --git a/waspc/src/Wasp/Project/ExternalConfig/TsConfig.hs b/waspc/src/Wasp/Project/ExternalConfig/TsConfig.hs index 47c85fcc16..2ca9027d31 100644 --- a/waspc/src/Wasp/Project/ExternalConfig/TsConfig.hs +++ b/waspc/src/Wasp/Project/ExternalConfig/TsConfig.hs @@ -10,20 +10,20 @@ import StrongPath (Abs, Dir, File, Path', toFilePath) import qualified Wasp.ExternalConfig.TsConfig as T import Wasp.Generator.ExternalConfig.TsConfig (validateTsConfig) import Wasp.Project.Common - ( CompileError, - TsConfigFile, + ( TsConfigFile, WaspProjectDir, findFileInWaspProjectDir, tsConfigInWaspProjectDir, ) +import Wasp.Util (validateToEither) import qualified Wasp.Util.IO as IOUtil import Wasp.Util.Json (parseJsonWithComments) -analyzeTsConfigFile :: Path' Abs (Dir WaspProjectDir) -> IO (Either [CompileError] T.TsConfig) +analyzeTsConfigFile :: Path' Abs (Dir WaspProjectDir) -> IO (Either [String] T.TsConfig) analyzeTsConfigFile waspDir = runExceptT $ do tsConfigFile <- ExceptT findTsConfigOrError tsConfig <- ExceptT $ readTsConfigFile tsConfigFile - ExceptT $ validateTsConfig tsConfig + ExceptT $ validateToEither validateTsConfig tsConfig where findTsConfigOrError = maybeToEither [fileNotFoundMessage] <$> findTsConfigFile waspDir fileNotFoundMessage = "Couldn't find the tsconfig.json file in the " ++ toFilePath waspDir ++ " directory" @@ -31,7 +31,7 @@ analyzeTsConfigFile waspDir = runExceptT $ do findTsConfigFile :: Path' Abs (Dir WaspProjectDir) -> IO (Maybe (Path' Abs (File TsConfigFile))) findTsConfigFile waspProjectDir = findFileInWaspProjectDir waspProjectDir tsConfigInWaspProjectDir -readTsConfigFile :: Path' Abs (File TsConfigFile) -> IO (Either [CompileError] T.TsConfig) +readTsConfigFile :: Path' Abs (File TsConfigFile) -> IO (Either [String] T.TsConfig) readTsConfigFile tsConfigFile = do tsConfigContent <- IOUtil.readFileBytes tsConfigFile diff --git a/waspc/src/Wasp/Util.hs b/waspc/src/Wasp/Util.hs index 9cedb4e0d7..1907da3ec4 100644 --- a/waspc/src/Wasp/Util.hs +++ b/waspc/src/Wasp/Util.hs @@ -40,6 +40,7 @@ module Wasp.Util trim, secondsToMicroSeconds, findDuplicateElems, + validateToEither, ) where @@ -270,3 +271,9 @@ secondsToMicroSeconds = (* 1000000) findDuplicateElems :: Ord a => [a] -> [a] findDuplicateElems = map head . filter ((> 1) . length) . group . sort + +validateToEither :: (Monad m) => (a -> m [String]) -> a -> m (Either [String] a) +validateToEither validateFn input = + validateFn input >>= \case + [] -> return $ Right input + errors -> return $ Left errors From bcb33db3fb34e03a5ca8b72801a44ca7c024849d Mon Sep 17 00:00:00 2001 From: Mihovil Ilakovac Date: Fri, 27 Sep 2024 12:23:23 +0200 Subject: [PATCH 20/24] Update waspc/src/Wasp/Util/Json.hs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Martin Šošić --- waspc/src/Wasp/Util/Json.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/waspc/src/Wasp/Util/Json.hs b/waspc/src/Wasp/Util/Json.hs index fce6b959d9..dc7128ec1b 100644 --- a/waspc/src/Wasp/Util/Json.hs +++ b/waspc/src/Wasp/Util/Json.hs @@ -6,7 +6,7 @@ import qualified System.Process as P import Wasp.Util.Aeson (decodeFromString) -- | Uses Node.js to parse JSON with comments by treating it as a JavaScript object. --- We used this technique because Aeson can't handle it and we didn't want to write +-- We use this technique because Aeson can't read JSON with comments and we didn't want to write -- a custom parser. parseJsonWithComments :: FromJSON a => String -> IO (Either String a) parseJsonWithComments jsonStr = do From 3086ed14afd650c24c251da9b8b61abc7d27add2 Mon Sep 17 00:00:00 2001 From: Mihovil Ilakovac Date: Fri, 27 Sep 2024 12:26:53 +0200 Subject: [PATCH 21/24] Update validation --- .../Wasp/Generator/ExternalConfig/Common.hs | 3 ++ .../Generator/ExternalConfig/PackageJson.hs | 20 ++++----- .../Wasp/Generator/ExternalConfig/TsConfig.hs | 45 ++++++++++++------- waspc/waspc.cabal | 1 + 4 files changed, 41 insertions(+), 28 deletions(-) create mode 100644 waspc/src/Wasp/Generator/ExternalConfig/Common.hs diff --git a/waspc/src/Wasp/Generator/ExternalConfig/Common.hs b/waspc/src/Wasp/Generator/ExternalConfig/Common.hs new file mode 100644 index 0000000000..3f49f1782b --- /dev/null +++ b/waspc/src/Wasp/Generator/ExternalConfig/Common.hs @@ -0,0 +1,3 @@ +module Wasp.Generator.ExternalConfig.Common (ErrorMsg) where + +type ErrorMsg = String diff --git a/waspc/src/Wasp/Generator/ExternalConfig/PackageJson.hs b/waspc/src/Wasp/Generator/ExternalConfig/PackageJson.hs index 0473d3b5d0..304a035fa5 100644 --- a/waspc/src/Wasp/Generator/ExternalConfig/PackageJson.hs +++ b/waspc/src/Wasp/Generator/ExternalConfig/PackageJson.hs @@ -6,20 +6,18 @@ where import qualified Data.Map as M import qualified Wasp.ExternalConfig.PackageJson as P import Wasp.Generator.Common (prismaVersion) +import Wasp.Generator.ExternalConfig.Common (ErrorMsg) import Wasp.Generator.WebAppGenerator.Common (reactRouterVersion) -validatePackageJson :: P.PackageJson -> IO [String] +validatePackageJson :: P.PackageJson -> [ErrorMsg] validatePackageJson packageJson = - return $ - concat - [ -- Wasp needs the Wasp SDK to be installed in the project. - validate ("wasp", "file:.wasp/out/sdk/wasp", IsListedWithExactVersion), - -- Wrong version of Prisma will break the generated code. - validate ("prisma", show prismaVersion, IsListedAsDevWithExactVersion), - -- Installing the wrong version of "react-router-dom" can make users believe that they - -- can use features that are not available in the version that Wasp supports. - validate ("react-router-dom", show reactRouterVersion, HasExactVersionIfListed) - ] + concat + [ validate ("wasp", "file:.wasp/out/sdk/wasp", IsListedWithExactVersion), + validate ("prisma", show prismaVersion, IsListedAsDevWithExactVersion), + -- Installing the wrong version of "react-router-dom" can make users believe that they + -- can use features that are not available in the version that Wasp supports. + validate ("react-router-dom", show reactRouterVersion, HasExactVersionIfListed) + ] where validate = validateDep packageJson diff --git a/waspc/src/Wasp/Generator/ExternalConfig/TsConfig.hs b/waspc/src/Wasp/Generator/ExternalConfig/TsConfig.hs index f3f9a04c13..d41d051872 100644 --- a/waspc/src/Wasp/Generator/ExternalConfig/TsConfig.hs +++ b/waspc/src/Wasp/Generator/ExternalConfig/TsConfig.hs @@ -6,32 +6,37 @@ module Wasp.Generator.ExternalConfig.TsConfig where import qualified Wasp.ExternalConfig.TsConfig as T +import Wasp.Generator.ExternalConfig.Common (ErrorMsg) -validateTsConfig :: T.TsConfig -> IO [String] +validateTsConfig :: T.TsConfig -> [ErrorMsg] validateTsConfig tsConfig = - return $ - concat - [ validateRequiredFieldInCompilerOptions "module" "esnext" T._module, - validateRequiredFieldInCompilerOptions "target" "esnext" T.target, - validateRequiredFieldInCompilerOptions "moduleResolution" "bundler" T.moduleResolution, - validateRequiredFieldInCompilerOptions "jsx" "preserve" T.jsx, - validateRequiredFieldInCompilerOptions "strict" True T.strict, - validateRequiredFieldInCompilerOptions "esModuleInterop" True T.esModuleInterop, - validateRequiredFieldInCompilerOptions "lib" ["dom", "dom.iterable", "esnext"] T.lib, - validateRequiredFieldInCompilerOptions "allowJs" True T.allowJs, - validateRequiredFieldInCompilerOptions "typeRoots" ["node_modules/@testing-library", "node_modules/@types"] T.typeRoots, - validateRequiredFieldInCompilerOptions "outDir" ".wasp/phantom" T.outDir - ] + concat + [ validateRequiredFieldInCompilerOptions "module" "esnext" T._module, + validateRequiredFieldInCompilerOptions "target" "esnext" T.target, + validateRequiredFieldInCompilerOptions "moduleResolution" "bundler" T.moduleResolution, + validateRequiredFieldInCompilerOptions "jsx" "preserve" T.jsx, + validateRequiredFieldInCompilerOptions "strict" True T.strict, + validateRequiredFieldInCompilerOptions "esModuleInterop" True T.esModuleInterop, + validateRequiredFieldInCompilerOptions "lib" ["dom", "dom.iterable", "esnext"] T.lib, + validateRequiredFieldInCompilerOptions "allowJs" True T.allowJs, + validateRequiredFieldInCompilerOptions "typeRoots" ["node_modules/@testing-library", "node_modules/@types"] T.typeRoots, + validateRequiredFieldInCompilerOptions "outDir" ".wasp/phantom" T.outDir + ] where validateRequiredFieldInCompilerOptions fieldName expectedValue getFieldValue = case getFieldValue compilerOptionsFields of Just actualValue -> validateFieldValue ("compilerOptions." ++ fieldName) expectedValue actualValue Nothing -> [missingFieldErrorMessage] where - missingFieldErrorMessage = unwords ["The", show fieldName, "field is missing in tsconfig.json. Expected value:", showAsJsValue expectedValue ++ "."] + missingFieldErrorMessage = + unwords + [ "The", + show fieldName, + "field is missing in tsconfig.json. Expected value:", + showAsJsValue expectedValue ++ "." + ] compilerOptionsFields = T.compilerOptions tsConfig --- | Haskell type that implements ShowJS is a type whose values can be mapped to Javascript values (their string representation). class IsJavascriptValue a where showAsJsValue :: a -> String @@ -53,4 +58,10 @@ validateFieldValue fieldName expectedValue actualValue = then [] else [invalidValueErrorMessage] where - invalidValueErrorMessage = unwords ["Invalid value for the", show fieldName, "field in tsconfig.json file, expected value:", showAsJsValue expectedValue ++ "."] + invalidValueErrorMessage = + unwords + [ "Invalid value for the", + show fieldName, + "field in tsconfig.json file, expected value:", + showAsJsValue expectedValue ++ "." + ] diff --git a/waspc/waspc.cabal b/waspc/waspc.cabal index b8e1b353d7..17559a2b6d 100644 --- a/waspc/waspc.cabal +++ b/waspc/waspc.cabal @@ -279,6 +279,7 @@ library Wasp.Generator.DbGenerator.Operations Wasp.Generator.DockerGenerator Wasp.Generator.ExternalCodeGenerator.Common + Wasp.Generator.ExternalConfig.Common Wasp.Generator.ExternalConfig.PackageJson Wasp.Generator.ExternalConfig.TsConfig Wasp.Generator.FileDraft From 6a7ba87e09298a56aa2c553d217e66adb9a1a224 Mon Sep 17 00:00:00 2001 From: Mihovil Ilakovac Date: Fri, 27 Sep 2024 12:32:33 +0200 Subject: [PATCH 22/24] PR comments --- waspc/src/Wasp/ExternalConfig/PackageJson.hs | 5 +- .../Generator/ExternalConfig/PackageJson.hs | 10 ++-- .../Wasp/Generator/ExternalConfig/TsConfig.hs | 56 +++++++++---------- 3 files changed, 35 insertions(+), 36 deletions(-) diff --git a/waspc/src/Wasp/ExternalConfig/PackageJson.hs b/waspc/src/Wasp/ExternalConfig/PackageJson.hs index 8e1f1fd033..fddb9b9268 100644 --- a/waspc/src/Wasp/ExternalConfig/PackageJson.hs +++ b/waspc/src/Wasp/ExternalConfig/PackageJson.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DeriveGeneric #-} module Wasp.ExternalConfig.PackageJson @@ -22,7 +23,7 @@ data PackageJson = PackageJson dependencies :: !DependenciesMap, devDependencies :: !DependenciesMap } - deriving (Show, Generic) + deriving (Show, Generic, FromJSON) getDependencies :: PackageJson -> [Dependency] getDependencies packageJson = D.fromList $ M.toList $ dependencies packageJson @@ -35,5 +36,3 @@ type DependenciesMap = Map PackageName PackageVersion type PackageName = String type PackageVersion = String - -instance FromJSON PackageJson diff --git a/waspc/src/Wasp/Generator/ExternalConfig/PackageJson.hs b/waspc/src/Wasp/Generator/ExternalConfig/PackageJson.hs index 304a035fa5..c8e8952443 100644 --- a/waspc/src/Wasp/Generator/ExternalConfig/PackageJson.hs +++ b/waspc/src/Wasp/Generator/ExternalConfig/PackageJson.hs @@ -12,19 +12,19 @@ import Wasp.Generator.WebAppGenerator.Common (reactRouterVersion) validatePackageJson :: P.PackageJson -> [ErrorMsg] validatePackageJson packageJson = concat - [ validate ("wasp", "file:.wasp/out/sdk/wasp", IsListedWithExactVersion), - validate ("prisma", show prismaVersion, IsListedAsDevWithExactVersion), + [ validate ("wasp", "file:.wasp/out/sdk/wasp") IsListedWithExactVersion, + validate ("prisma", show prismaVersion) IsListedAsDevWithExactVersion, -- Installing the wrong version of "react-router-dom" can make users believe that they -- can use features that are not available in the version that Wasp supports. - validate ("react-router-dom", show reactRouterVersion, HasExactVersionIfListed) + validate ("react-router-dom", show reactRouterVersion) HasExactVersionIfListed ] where validate = validateDep packageJson data PackageValidationType = IsListedWithExactVersion | IsListedAsDevWithExactVersion | HasExactVersionIfListed -validateDep :: P.PackageJson -> (P.PackageName, P.PackageVersion, PackageValidationType) -> [String] -validateDep packageJson (packageName, expectedPackageVersion, validationType) = case validationType of +validateDep :: P.PackageJson -> (P.PackageName, P.PackageVersion) -> PackageValidationType -> [String] +validateDep packageJson (packageName, expectedPackageVersion) = \case IsListedWithExactVersion -> checkDeps [P.dependencies packageJson] [requiredPackageMessage "dependencies"] IsListedAsDevWithExactVersion -> checkDeps [P.devDependencies packageJson] [requiredPackageMessage "devDependencies"] HasExactVersionIfListed -> checkDeps [P.dependencies packageJson, P.devDependencies packageJson] [] diff --git a/waspc/src/Wasp/Generator/ExternalConfig/TsConfig.hs b/waspc/src/Wasp/Generator/ExternalConfig/TsConfig.hs index d41d051872..ab3d3e4001 100644 --- a/waspc/src/Wasp/Generator/ExternalConfig/TsConfig.hs +++ b/waspc/src/Wasp/Generator/ExternalConfig/TsConfig.hs @@ -8,6 +8,21 @@ where import qualified Wasp.ExternalConfig.TsConfig as T import Wasp.Generator.ExternalConfig.Common (ErrorMsg) +class IsJavascriptValue a where + showAsJsValue :: a -> String + +instance IsJavascriptValue String where + showAsJsValue = show + +instance IsJavascriptValue [String] where + showAsJsValue = show + +instance IsJavascriptValue Bool where + showAsJsValue True = "true" + showAsJsValue False = "false" + +type FieldName = String + validateTsConfig :: T.TsConfig -> [ErrorMsg] validateTsConfig tsConfig = concat @@ -37,31 +52,16 @@ validateTsConfig tsConfig = compilerOptionsFields = T.compilerOptions tsConfig -class IsJavascriptValue a where - showAsJsValue :: a -> String - -instance IsJavascriptValue String where - showAsJsValue = show - -instance IsJavascriptValue [String] where - showAsJsValue = show - -instance IsJavascriptValue Bool where - showAsJsValue True = "true" - showAsJsValue False = "false" - -type FieldName = String - -validateFieldValue :: (Eq value, IsJavascriptValue value) => FieldName -> value -> value -> [String] -validateFieldValue fieldName expectedValue actualValue = - if actualValue == expectedValue - then [] - else [invalidValueErrorMessage] - where - invalidValueErrorMessage = - unwords - [ "Invalid value for the", - show fieldName, - "field in tsconfig.json file, expected value:", - showAsJsValue expectedValue ++ "." - ] + validateFieldValue :: (Eq value, IsJavascriptValue value) => FieldName -> value -> value -> [String] + validateFieldValue fieldName expectedValue actualValue = + if actualValue == expectedValue + then [] + else [invalidValueErrorMessage] + where + invalidValueErrorMessage = + unwords + [ "Invalid value for the", + show fieldName, + "field in tsconfig.json file, expected value:", + showAsJsValue expectedValue ++ "." + ] From 684d19ea6d690a87fa904e67780d18d50df2a595 Mon Sep 17 00:00:00 2001 From: Mihovil Ilakovac Date: Fri, 27 Sep 2024 13:06:18 +0200 Subject: [PATCH 23/24] Inline validateToEither --- waspc/src/Wasp/Project/ExternalConfig/PackageJson.hs | 7 ++++--- waspc/src/Wasp/Project/ExternalConfig/TsConfig.hs | 7 ++++--- waspc/src/Wasp/Util.hs | 7 ------- 3 files changed, 8 insertions(+), 13 deletions(-) diff --git a/waspc/src/Wasp/Project/ExternalConfig/PackageJson.hs b/waspc/src/Wasp/Project/ExternalConfig/PackageJson.hs index a46fd9e800..562285a8b6 100644 --- a/waspc/src/Wasp/Project/ExternalConfig/PackageJson.hs +++ b/waspc/src/Wasp/Project/ExternalConfig/PackageJson.hs @@ -4,7 +4,7 @@ module Wasp.Project.ExternalConfig.PackageJson ) where -import Control.Monad.Except (ExceptT (ExceptT), runExceptT) +import Control.Monad.Except (ExceptT (ExceptT), MonadError (throwError), runExceptT) import qualified Data.Aeson as Aeson import Data.Either.Extra (maybeToEither) import StrongPath (Abs, Dir, File, Path', toFilePath) @@ -16,14 +16,15 @@ import Wasp.Project.Common findFileInWaspProjectDir, packageJsonInWaspProjectDir, ) -import Wasp.Util (validateToEither) import qualified Wasp.Util.IO as IOUtil analyzePackageJsonFile :: Path' Abs (Dir WaspProjectDir) -> IO (Either [String] P.PackageJson) analyzePackageJsonFile waspProjectDir = runExceptT $ do packageJsonFile <- ExceptT findPackageJsonFileOrError packageJson <- ExceptT $ readPackageJsonFile packageJsonFile - ExceptT $ validateToEither validatePackageJson packageJson + case validatePackageJson packageJson of + [] -> return packageJson + errors -> throwError errors where findPackageJsonFileOrError = maybeToEither [fileNotFoundMessage] <$> findPackageJsonFile waspProjectDir fileNotFoundMessage = "Couldn't find the package.json file in the " ++ toFilePath waspProjectDir ++ " directory" diff --git a/waspc/src/Wasp/Project/ExternalConfig/TsConfig.hs b/waspc/src/Wasp/Project/ExternalConfig/TsConfig.hs index 2ca9027d31..8693241a9b 100644 --- a/waspc/src/Wasp/Project/ExternalConfig/TsConfig.hs +++ b/waspc/src/Wasp/Project/ExternalConfig/TsConfig.hs @@ -3,7 +3,7 @@ module Wasp.Project.ExternalConfig.TsConfig ) where -import Control.Monad.Except (ExceptT (ExceptT), runExceptT) +import Control.Monad.Except (ExceptT (ExceptT), runExceptT, throwError) import qualified Data.ByteString.Lazy.UTF8 as BS import Data.Either.Extra (maybeToEither) import StrongPath (Abs, Dir, File, Path', toFilePath) @@ -15,7 +15,6 @@ import Wasp.Project.Common findFileInWaspProjectDir, tsConfigInWaspProjectDir, ) -import Wasp.Util (validateToEither) import qualified Wasp.Util.IO as IOUtil import Wasp.Util.Json (parseJsonWithComments) @@ -23,7 +22,9 @@ analyzeTsConfigFile :: Path' Abs (Dir WaspProjectDir) -> IO (Either [String] T.T analyzeTsConfigFile waspDir = runExceptT $ do tsConfigFile <- ExceptT findTsConfigOrError tsConfig <- ExceptT $ readTsConfigFile tsConfigFile - ExceptT $ validateToEither validateTsConfig tsConfig + case validateTsConfig tsConfig of + [] -> return tsConfig + errors -> throwError errors where findTsConfigOrError = maybeToEither [fileNotFoundMessage] <$> findTsConfigFile waspDir fileNotFoundMessage = "Couldn't find the tsconfig.json file in the " ++ toFilePath waspDir ++ " directory" diff --git a/waspc/src/Wasp/Util.hs b/waspc/src/Wasp/Util.hs index 1907da3ec4..9cedb4e0d7 100644 --- a/waspc/src/Wasp/Util.hs +++ b/waspc/src/Wasp/Util.hs @@ -40,7 +40,6 @@ module Wasp.Util trim, secondsToMicroSeconds, findDuplicateElems, - validateToEither, ) where @@ -271,9 +270,3 @@ secondsToMicroSeconds = (* 1000000) findDuplicateElems :: Ord a => [a] -> [a] findDuplicateElems = map head . filter ((> 1) . length) . group . sort - -validateToEither :: (Monad m) => (a -> m [String]) -> a -> m (Either [String] a) -validateToEither validateFn input = - validateFn input >>= \case - [] -> return $ Right input - errors -> return $ Left errors From a1e68517b1095246103bf8f9614b0ce4ca1e5f6f Mon Sep 17 00:00:00 2001 From: Mihovil Ilakovac Date: Fri, 27 Sep 2024 13:08:24 +0200 Subject: [PATCH 24/24] Cleanup --- waspc/src/Wasp/ExternalConfig/TsConfig.hs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/waspc/src/Wasp/ExternalConfig/TsConfig.hs b/waspc/src/Wasp/ExternalConfig/TsConfig.hs index d4d10176f5..fe443b9647 100644 --- a/waspc/src/Wasp/ExternalConfig/TsConfig.hs +++ b/waspc/src/Wasp/ExternalConfig/TsConfig.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE FlexibleInstances #-} @@ -18,9 +19,7 @@ import GHC.Generics (Generic) data TsConfig = TsConfig { compilerOptions :: !CompilerOptions } - deriving (Show, Generic) - -instance FromJSON TsConfig + deriving (Show, Generic, FromJSON) data CompilerOptions = CompilerOptions { _module :: !(Maybe String),