Skip to content

Commit

Permalink
Merge branch 'main' into miho-react-router-dom-6
Browse files Browse the repository at this point in the history
  • Loading branch information
infomiho committed Sep 27, 2024
2 parents f6d2d8d + b755293 commit 2314809
Show file tree
Hide file tree
Showing 25 changed files with 407 additions and 83 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +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.Project.Analyze (findPackageJsonFile, findWaspFile)
import Wasp.Project.Analyze (findWaspFile)
import Wasp.Project.Common (WaspProjectDir)
import Wasp.Project.ExternalConfig.PackageJson (findPackageJsonFile)
import qualified Wasp.Util.IO as IOUtil

replaceTemplatePlaceholdersInTemplateFiles :: NewProjectAppName -> NewProjectName -> Path' Abs (Dir WaspProjectDir) -> IO ()
Expand Down
23 changes: 15 additions & 8 deletions waspc/headless-test/examples/todoApp/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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"
],
}
"outDir": ".wasp/phantom"
}
}
2 changes: 1 addition & 1 deletion waspc/src/Wasp/AppSpec.hs
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,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)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}

module Wasp.AppSpec.PackageJson where
module Wasp.ExternalConfig.PackageJson
( PackageJson (..),
DependenciesMap,
PackageName,
PackageVersion,
getDependencies,
getDevDependencies,
)
where

import Data.Aeson (FromJSON)
import Data.Map (Map)
Expand All @@ -11,15 +20,19 @@ import qualified Wasp.AppSpec.App.Dependency as D

data PackageJson = PackageJson
{ name :: !String,
dependencies :: !(Map String String),
devDependencies :: !(Map String String)
dependencies :: !DependenciesMap,
devDependencies :: !DependenciesMap
}
deriving (Show, Generic)

instance FromJSON PackageJson
deriving (Show, Generic, FromJSON)

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

type PackageVersion = String
45 changes: 45 additions & 0 deletions waspc/src/Wasp/ExternalConfig/TsConfig.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleInstances #-}

module Wasp.ExternalConfig.TsConfig
( TsConfig (..),
CompilerOptions (..),
)
where

import Data.Aeson
( FromJSON,
genericParseJSON,
parseJSON,
)
import qualified Data.Aeson as Aeson
import GHC.Generics (Generic)

data TsConfig = TsConfig
{ compilerOptions :: !CompilerOptions
}
deriving (Show, Generic, FromJSON)

data CompilerOptions = CompilerOptions
{ _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)

instance FromJSON CompilerOptions where
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
3 changes: 3 additions & 0 deletions waspc/src/Wasp/Generator/ExternalConfig/Common.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module Wasp.Generator.ExternalConfig.Common (ErrorMsg) where

type ErrorMsg = String
54 changes: 54 additions & 0 deletions waspc/src/Wasp/Generator/ExternalConfig/PackageJson.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
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.ExternalConfig.Common (ErrorMsg)
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,
-- 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

data PackageValidationType = IsListedWithExactVersion | IsListedAsDevWithExactVersion | HasExactVersionIfListed

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] []
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."
]
67 changes: 67 additions & 0 deletions waspc/src/Wasp/Generator/ExternalConfig/TsConfig.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
{-# LANGUAGE FlexibleInstances #-}

module Wasp.Generator.ExternalConfig.TsConfig
( validateTsConfig,
)
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
[ 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 ++ "."
]

compilerOptionsFields = T.compilerOptions tsConfig

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 ++ "."
]
4 changes: 2 additions & 2 deletions waspc/src/Wasp/Generator/Monad.hs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ where
import Control.Monad.Except (ExceptT, MonadError (throwError), runExceptT)
import qualified Control.Monad.Except as MonadExcept
import Control.Monad.Identity (Identity (runIdentity))
import Control.Monad.State (MonadState, StateT (runStateT), modify)
import Control.Monad.State (MonadState, State, modify, runStateT)
import Data.List.NonEmpty (NonEmpty, fromList)

-- | Generator is a monad transformer stack where we abstract away the underlying
Expand All @@ -25,7 +25,7 @@ import Data.List.NonEmpty (NonEmpty, fromList)
-- The mechanism to catch errors is only there to assist in collecting more errors, not recover.
-- There may optionally be additional errors or non-fatal warnings logged in the State.
newtype Generator a = Generator
{ _runGenerator :: ExceptT GeneratorError (StateT GeneratorState Identity) a
{ _runGenerator :: ExceptT GeneratorError (State GeneratorState) a
}
deriving
( Functor,
Expand Down
6 changes: 3 additions & 3 deletions waspc/src/Wasp/Generator/NpmDependencies.hs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
15 changes: 7 additions & 8 deletions waspc/src/Wasp/Generator/SdkGenerator.hs
Original file line number Diff line number Diff line change
Expand Up @@ -54,16 +54,14 @@ 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)
import qualified Wasp.Project.Db as Db
import qualified Wasp.SemanticVersion as SV
import Wasp.Util ((<++>))

genSdk :: AppSpec -> Generator [FileDraft]
genSdk spec = genSdkReal spec

buildSdk :: Path' Abs (Dir ProjectRootDir) -> IO (Either String ())
buildSdk projectRootDir = do
chan <- newChan
Expand All @@ -77,8 +75,8 @@ buildSdk projectRootDir = do
where
dstDir = projectRootDir </> C.sdkRootDirInProjectRootDir

genSdkReal :: AppSpec -> Generator [FileDraft]
genSdkReal spec =
genSdk :: AppSpec -> Generator [FileDraft]
genSdk spec =
sequence
[ genFileCopy [relfile|vite-env.d.ts|],
genFileCopy [relfile|prisma-runtime-library.d.ts|],
Expand Down Expand Up @@ -188,16 +186,17 @@ npmDepsForSdk spec =
("mitt", "3.0.0"),
("react", "^18.2.0"),
("lodash.merge", "^4.6.2"),
("react-router-dom", "^6.26.2"),
("react-router-dom", show reactRouterVersion),
("react-hook-form", "^7.45.4"),
("superjson", "^1.12.2")
]
++ 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
Expand Down
4 changes: 2 additions & 2 deletions waspc/src/Wasp/Generator/Setup.hs
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ runSetup spec projectRootDir sendMessage = do
setUpDatabaseResults@(_warnings, _errors@[]) -> do
-- todo(filip): Should we consider building SDK as part of code generation?
-- todo(filip): Avoid building on each setup if we don't need to.
buildsSdkResults <- buildSdk projectRootDir sendMessage
return $ setUpDatabaseResults <> buildsSdkResults
buildSdkResults <- buildSdk projectRootDir sendMessage
return $ setUpDatabaseResults <> buildSdkResults
setUpDatabaseResults -> return setUpDatabaseResults
Left npmInstallError -> return ([], [npmInstallError])

Expand Down
Loading

0 comments on commit 2314809

Please sign in to comment.