diff --git a/waspc/.gitignore b/waspc/.gitignore index f0b0addec0..e2c6093a0e 100644 --- a/waspc/.gitignore +++ b/waspc/.gitignore @@ -16,3 +16,6 @@ module-graph.png # macOS related .DS_Store + +# apps for testing +examples/ignored diff --git a/waspc/data/Cli/templates/skeleton/tsconfig.json b/waspc/data/Cli/templates/skeleton/tsconfig.json index 506b0e2fc1..8341c73377 100644 --- a/waspc/data/Cli/templates/skeleton/tsconfig.json +++ b/waspc/data/Cli/templates/skeleton/tsconfig.json @@ -40,6 +40,9 @@ // Source 2: https://github.com/testing-library/jest-dom/issues/546#issuecomment-1889884843 "node_modules/@types" ], + // Wasp internally uses TypeScript's project references to compile the + // code. Referenced projects may not disable emit, so we must specify an + // outDir. "outDir": ".wasp/out/user" }, "include": [ diff --git a/waspc/data/Generator/templates/react-app/tsconfig.json b/waspc/data/Generator/templates/react-app/tsconfig.json index a4817e8940..d2b6b1f828 100644 --- a/waspc/data/Generator/templates/react-app/tsconfig.json +++ b/waspc/data/Generator/templates/react-app/tsconfig.json @@ -1,4 +1,7 @@ { + // Wasp's TS config arrangement is based on Vite's tsconfig arrangement We + // have an extra top-level file because the two projects are independent. The + // empty files array is necessary to allow `noEmit` in referenced projects. "files": [], "references": [ { "path": "./tsconfig.app.json" }, diff --git a/waspc/data/Generator/templates/server/tsconfig.json b/waspc/data/Generator/templates/server/tsconfig.json index 6d0f513a17..64133aead4 100644 --- a/waspc/data/Generator/templates/server/tsconfig.json +++ b/waspc/data/Generator/templates/server/tsconfig.json @@ -12,6 +12,10 @@ // make project references work for the TS config), then I believe the // correct configuration is "rootDir": "." (the project reference should // take care of the user code), but we should double-check. + // + // Before changing this property, ensure you're aware of the implications: + // - https://github.com/wasp-lang/wasp/pull/1584#discussion_r1404019301 + // - https://github.com/wasp-lang/wasp/pull/1713/files#diff-58191eecd9cc55f71c73de89420df8b1866dce38ad35f4ef0f6f8874616eda77R32 "rootDir": ".", // Overriding this because we want to use top-level await "module": "esnext", @@ -30,6 +34,6 @@ "src" ], "references": [ - { "path": "../../../tsconfig.json" } + { "path": "{= srcTsConfigPath =}" } ] } diff --git a/waspc/src/Wasp/Generator/ExternalConfig/TsConfig.hs b/waspc/src/Wasp/Generator/ExternalConfig/TsConfig.hs index 4790700385..cb62318327 100644 --- a/waspc/src/Wasp/Generator/ExternalConfig/TsConfig.hs +++ b/waspc/src/Wasp/Generator/ExternalConfig/TsConfig.hs @@ -73,7 +73,7 @@ validateFieldValue fullyQualifiedFieldName expectedValue actualValue = unwords [ "Invalid value for the", "\"" ++ show fullyQualifiedFieldName ++ "\"", - "field in tsconfig.json, you must set it to:", + "field in TS config, you must set it to:", showAsJsValue expected ++ "." ] @@ -81,13 +81,13 @@ validateFieldValue fullyQualifiedFieldName expectedValue actualValue = unwords [ "The", "\"" ++ show fullyQualifiedFieldName ++ "\"", - "field in tsconfig.json must be unset." + "field in TS Config must be left unspecified." ] makeMissingFieldErrorMessage expected = unwords [ "The", "\"" ++ show fullyQualifiedFieldName ++ "\"", - "field is missing in tsconfig.json, you must set it to:", + "field is missing in TS config, you must set it to:", showAsJsValue expected ++ "." ] diff --git a/waspc/src/Wasp/Generator/SdkGenerator/CrudG.hs b/waspc/src/Wasp/Generator/SdkGenerator/CrudG.hs index 5bf7354974..dfa6407166 100644 --- a/waspc/src/Wasp/Generator/SdkGenerator/CrudG.hs +++ b/waspc/src/Wasp/Generator/SdkGenerator/CrudG.hs @@ -21,7 +21,7 @@ import Wasp.Generator.FileDraft (FileDraft) import qualified Wasp.Generator.JsImport as GJI import Wasp.Generator.Monad (Generator) import qualified Wasp.Generator.SdkGenerator.Common as C -import Wasp.Generator.SdkGenerator.Server.OperationsGenerator (extImportToJsImport) +import Wasp.Generator.SdkGenerator.JsImport (extImportToJsImport) genCrud :: AppSpec -> Generator [FileDraft] genCrud spec = diff --git a/waspc/src/Wasp/Generator/SdkGenerator/EnvValidation.hs b/waspc/src/Wasp/Generator/SdkGenerator/EnvValidation.hs index d6d5c163d8..5125a6cac7 100644 --- a/waspc/src/Wasp/Generator/SdkGenerator/EnvValidation.hs +++ b/waspc/src/Wasp/Generator/SdkGenerator/EnvValidation.hs @@ -19,7 +19,7 @@ import Wasp.Generator.FileDraft (FileDraft) import qualified Wasp.Generator.JsImport as GJI import Wasp.Generator.Monad (Generator) import qualified Wasp.Generator.SdkGenerator.Common as C -import Wasp.Generator.SdkGenerator.Server.OperationsGenerator (extImportToJsImport) +import Wasp.Generator.SdkGenerator.JsImport (extImportToJsImport) import qualified Wasp.Generator.ServerGenerator.Common as Server import qualified Wasp.Generator.WebAppGenerator.Common as WebApp import qualified Wasp.Project.Db as Db diff --git a/waspc/src/Wasp/Generator/SdkGenerator/JsImport.hs b/waspc/src/Wasp/Generator/SdkGenerator/JsImport.hs new file mode 100644 index 0000000000..944886b9dd --- /dev/null +++ b/waspc/src/Wasp/Generator/SdkGenerator/JsImport.hs @@ -0,0 +1,38 @@ +module Wasp.Generator.SdkGenerator.JsImport + ( extImportToJsImport, + extOperationImportToImportJson, + ) +where + +import qualified Data.Aeson as Aeson +import Data.Maybe (fromJust) +import StrongPath (()) +import qualified StrongPath as SP +import qualified Wasp.AppSpec.ExtImport as EI +import Wasp.Generator.Common (dropExtensionFromImportPath) +import qualified Wasp.Generator.JsImport as GJI +import qualified Wasp.Generator.SdkGenerator.Common as C +import Wasp.JsImport (JsImport (..), JsImportPath (..)) +import qualified Wasp.JsImport as JI + +extImportToJsImport :: EI.ExtImport -> JsImport +extImportToJsImport extImport@(EI.ExtImport extImportName extImportPath) = + JsImport + { _path = ModuleImportPath importPath, + _name = importName, + _importAlias = Just $ EI.importIdentifier extImport ++ "_ext" + } + where + importPath = C.makeSdkImportPath $ dropExtensionFromImportPath $ extCodeDirP SP.castRel extImportPath + extCodeDirP = fromJust $ SP.relDirToPosix C.extSrcDirInSdkRootDir + importName = GJI.extImportNameToJsImportName extImportName + +extOperationImportToImportJson :: EI.ExtImport -> Aeson.Value +extOperationImportToImportJson = + GJI.jsImportToImportJson + . Just + . applyExtImportAlias + . extImportToJsImport + where + applyExtImportAlias jsImport = + jsImport {_importAlias = Just $ JI.getImportIdentifier jsImport ++ "_ext"} diff --git a/waspc/src/Wasp/Generator/SdkGenerator/Server/OperationsGenerator.hs b/waspc/src/Wasp/Generator/SdkGenerator/Server/OperationsGenerator.hs index e22ca0141f..6652f54389 100644 --- a/waspc/src/Wasp/Generator/SdkGenerator/Server/OperationsGenerator.hs +++ b/waspc/src/Wasp/Generator/SdkGenerator/Server/OperationsGenerator.hs @@ -1,8 +1,7 @@ {-# LANGUAGE TypeApplications #-} module Wasp.Generator.SdkGenerator.Server.OperationsGenerator - ( extImportToJsImport, - serverOperationsDirInSdkRootDir, + ( serverOperationsDirInSdkRootDir, genOperations, ) where @@ -10,25 +9,22 @@ where import Data.Aeson (object, (.=)) import qualified Data.Aeson as Aeson import Data.List (nub) -import Data.Maybe (fromJust, fromMaybe) +import Data.Maybe (fromMaybe) import StrongPath (Dir, Dir', File', Path', Rel, reldir, relfile, ()) import qualified StrongPath as SP import Wasp.AppSpec (AppSpec) import qualified Wasp.AppSpec as AS import qualified Wasp.AppSpec.Action as AS.Action -import qualified Wasp.AppSpec.ExtImport as EI import Wasp.AppSpec.Operation (getName) import qualified Wasp.AppSpec.Operation as AS.Operation import qualified Wasp.AppSpec.Query as AS.Query import Wasp.AppSpec.Valid (isAuthEnabled) -import Wasp.Generator.Common (dropExtensionFromImportPath, makeJsonWithEntityData) +import Wasp.Generator.Common (makeJsonWithEntityData) import Wasp.Generator.FileDraft (FileDraft) -import qualified Wasp.Generator.JsImport as GJI import Wasp.Generator.Monad (Generator) import Wasp.Generator.SdkGenerator.Common (SdkTemplatesDir, getOperationTypeName, mkTmplFdWithData, serverTemplatesDirInSdkTemplatesDir) import qualified Wasp.Generator.SdkGenerator.Common as C -import Wasp.JsImport (JsImport (..), JsImportPath (..)) -import qualified Wasp.JsImport as JI +import Wasp.Generator.SdkGenerator.JsImport (extOperationImportToImportJson) import Wasp.Util (toUpperFirst) data ServerOpsTemplatesDir @@ -153,26 +149,3 @@ getOperationTmplData isAuthEnabledGlobally operation = .= maybe [] (map (makeJsonWithEntityData . AS.refName)) (AS.Operation.getEntities operation), "usesAuth" .= fromMaybe isAuthEnabledGlobally (AS.Operation.getAuth operation) ] - -extOperationImportToImportJson :: EI.ExtImport -> Aeson.Value -extOperationImportToImportJson = - GJI.jsImportToImportJson - . Just - . applyExtImportAlias - . extImportToJsImport - -applyExtImportAlias :: JsImport -> JsImport -applyExtImportAlias jsImport = - jsImport {_importAlias = Just $ JI.getImportIdentifier jsImport ++ "_ext"} - -extImportToJsImport :: EI.ExtImport -> JsImport -extImportToJsImport extImport@(EI.ExtImport extImportName extImportPath) = - JsImport - { _path = ModuleImportPath importPath, - _name = importName, - _importAlias = Just $ EI.importIdentifier extImport ++ "_ext" - } - where - importPath = C.makeSdkImportPath $ dropExtensionFromImportPath $ extCodeDirP SP.castRel extImportPath - extCodeDirP = fromJust $ SP.relDirToPosix C.extSrcDirInSdkRootDir - importName = GJI.extImportNameToJsImportName extImportName diff --git a/waspc/src/Wasp/Generator/SdkGenerator/WebSocketGenerator.hs b/waspc/src/Wasp/Generator/SdkGenerator/WebSocketGenerator.hs index 8feeed8e02..2603cc8700 100644 --- a/waspc/src/Wasp/Generator/SdkGenerator/WebSocketGenerator.hs +++ b/waspc/src/Wasp/Generator/SdkGenerator/WebSocketGenerator.hs @@ -18,7 +18,7 @@ import Wasp.Generator.FileDraft (FileDraft) import qualified Wasp.Generator.JsImport as GJI import Wasp.Generator.Monad (Generator) import qualified Wasp.Generator.SdkGenerator.Common as C -import Wasp.Generator.SdkGenerator.Server.OperationsGenerator (extImportToJsImport) +import Wasp.Generator.SdkGenerator.JsImport (extImportToJsImport) import qualified Wasp.Generator.WebSocket as AS.WS genWebSockets :: AppSpec -> Generator [FileDraft] diff --git a/waspc/src/Wasp/Generator/ServerGenerator.hs b/waspc/src/Wasp/Generator/ServerGenerator.hs index 58261acd10..3f760f739c 100644 --- a/waspc/src/Wasp/Generator/ServerGenerator.hs +++ b/waspc/src/Wasp/Generator/ServerGenerator.hs @@ -19,6 +19,7 @@ import Data.Maybe ) import StrongPath ( Dir, + File, File', Path, Path', @@ -28,6 +29,7 @@ import StrongPath relfile, (), ) +import qualified StrongPath as SP import Wasp.AppSpec (AppSpec) import qualified Wasp.AppSpec as AS import qualified Wasp.AppSpec.App as AS.App @@ -57,6 +59,7 @@ import Wasp.Generator.ServerGenerator.OperationsG (genOperations) import Wasp.Generator.ServerGenerator.OperationsRoutesG (genOperationsRoutes) import Wasp.Generator.ServerGenerator.WebSocketG (depsRequiredByWebSockets, genWebSockets, mkWebSocketFnImport) import qualified Wasp.Node.Version as NodeVersion +import Wasp.Project.Common (SrcTsConfigFile, waspProjectDirFromAppComponentDir) import Wasp.Project.Db (databaseUrlEnvVarName) import qualified Wasp.SemanticVersion as SV import Wasp.Util ((<++>)) @@ -67,7 +70,7 @@ genServer spec = [ genFileCopy [relfile|README.md|], genFileCopy [relfile|nodemon.json|], genRollupConfigJs spec, - genTsConfigJson, + genTsConfigJson spec, genPackageJson spec (npmDepsForWasp spec), genNpmrc, genGitignore @@ -101,17 +104,21 @@ genDotEnv spec = dotEnvInServerRootDir :: Path' (Rel ServerRootDir) File' dotEnvInServerRootDir = [relfile|.env|] -genTsConfigJson :: Generator FileDraft -genTsConfigJson = do +genTsConfigJson :: AppSpec -> Generator FileDraft +genTsConfigJson spec = do return $ C.mkTmplFdWithDstAndData (C.asTmplFile [relfile|tsconfig.json|]) (C.asServerFile [relfile|tsconfig.json|]) ( Just $ object - [ "majorNodeVersion" .= show (SV.major NodeVersion.oldestWaspSupportedNodeVersion) + [ "majorNodeVersion" .= show (SV.major NodeVersion.oldestWaspSupportedNodeVersion), + "srcTsConfigPath" .= SP.fromRelFile srcTsConfigPath ] ) + where + srcTsConfigPath :: Path' (Rel C.ServerRootDir) (File SrcTsConfigFile) = + waspProjectDirFromAppComponentDir AS.srcTsConfigPath spec genPackageJson :: AppSpec -> N.NpmDepsForWasp -> Generator FileDraft genPackageJson spec waspDependencies = do diff --git a/waspc/src/Wasp/Project/ExternalConfig/TsConfig.hs b/waspc/src/Wasp/Project/ExternalConfig/TsConfig.hs index 3423f9ce35..072c372042 100644 --- a/waspc/src/Wasp/Project/ExternalConfig/TsConfig.hs +++ b/waspc/src/Wasp/Project/ExternalConfig/TsConfig.hs @@ -7,7 +7,7 @@ import Control.Arrow (left) 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', Rel, toFilePath) +import StrongPath (Abs, Dir, File, Path', Rel, basename, fromRelFile, toFilePath) import qualified Wasp.ExternalConfig.TsConfig as T import Wasp.Generator.ExternalConfig.TsConfig (validateSrcTsConfig) import Wasp.Project.Common @@ -15,6 +15,7 @@ import Wasp.Project.Common WaspProjectDir, findFileInWaspProjectDir, ) +import Wasp.Util (indent) import qualified Wasp.Util.IO as IOUtil import Wasp.Util.Json (parseJsonWithComments) @@ -37,4 +38,7 @@ readTsConfigFile :: Path' Abs (File f) -> IO (Either String T.TsConfig) readTsConfigFile tsConfigFile = do tsConfigContent <- IOUtil.readFileBytes tsConfigFile parseResult <- parseJsonWithComments . BS.toString $ tsConfigContent - return $ left ("Failed to parse tsconfig file: " ++) parseResult + return $ left ((errorMessagePrefix ++) . indent 2) parseResult + where + errorMessagePrefix = "Failed to parse '" ++ baseTsConfigFilePath ++ "':\n" + baseTsConfigFilePath = fromRelFile (basename tsConfigFile) diff --git a/waspc/src/Wasp/Util/IO.hs b/waspc/src/Wasp/Util/IO.hs index f91aaf8e0e..079994f576 100644 --- a/waspc/src/Wasp/Util/IO.hs +++ b/waspc/src/Wasp/Util/IO.hs @@ -29,7 +29,7 @@ import Data.Text (Text) import qualified Data.Text.IO as T.IO import qualified Data.Text.IO as Text.IO import qualified Path.IO as PathIO -import StrongPath (Abs, Dir, Dir', File, Path', Rel, basename, parseRelDir, parseRelFile, toFilePath, ()) +import StrongPath (Abs, Dir, File, Path', Rel, basename, parseRelDir, parseRelFile, toFilePath, ()) import qualified StrongPath as SP import qualified StrongPath.Path as SP.Path import qualified System.Directory as SD @@ -66,7 +66,7 @@ listDirectoryDeep absDirPath = do -- TODO: write tests. -- | Lists files and directories at top lvl of the directory. -listDirectory :: forall d f. Path' Abs (Dir d) -> IO ([Path' (Rel d) (File f)], [Path' (Rel d) Dir']) +listDirectory :: forall r d f. Path' Abs (Dir r) -> IO ([Path' (Rel r) (File f)], [Path' (Rel r) (Dir d)]) listDirectory absDirPath = do fpRelItemPaths <- SD.listDirectory fpAbsDirPath relFilePaths <- filterFiles fpAbsDirPath fpRelItemPaths @@ -76,12 +76,12 @@ listDirectory absDirPath = do fpAbsDirPath :: FilePath fpAbsDirPath = toFilePath absDirPath - filterFiles :: FilePath -> [FilePath] -> IO [Path' (Rel d) (File f)] + filterFiles :: FilePath -> [FilePath] -> IO [Path' (Rel r) (File f)] filterFiles absDir relItems = filterM (SD.doesFileExist . (absDir FilePath.)) relItems >>= mapM parseRelFile - filterDirs :: FilePath -> [FilePath] -> IO [Path' (Rel d) Dir'] + filterDirs :: FilePath -> [FilePath] -> IO [Path' (Rel r) (Dir d)] filterDirs absDir relItems = filterM (SD.doesDirectoryExist . (absDir FilePath.)) relItems >>= mapM parseRelDir diff --git a/waspc/test/AppSpec/ValidTest.hs b/waspc/test/AppSpec/ValidTest.hs index 7de28824ab..5fbc092722 100644 --- a/waspc/test/AppSpec/ValidTest.hs +++ b/waspc/test/AppSpec/ValidTest.hs @@ -6,6 +6,7 @@ import qualified Data.Map as M import Data.Maybe (fromJust) import Fixtures (systemSPRoot) import NeatInterpolation (trimming) +import StrongPath (relfile) import qualified StrongPath as SP import Test.Tasty.Hspec import qualified Util.Prisma as Util @@ -489,7 +490,8 @@ spec_AppSpecValid = do AS.userDockerfileContents = Nothing, AS.configFiles = [], AS.devDatabaseUrl = Nothing, - AS.customViteConfigPath = Nothing + AS.customViteConfigPath = Nothing, + AS.srcTsConfigPath = [relfile|tsconfig.json|] } getPrismaSchemaWithConfig restOfPrismaSource = diff --git a/waspc/test/Generator/WebAppGeneratorTest.hs b/waspc/test/Generator/WebAppGeneratorTest.hs index 72614b05f5..fee58c56d3 100644 --- a/waspc/test/Generator/WebAppGeneratorTest.hs +++ b/waspc/test/Generator/WebAppGeneratorTest.hs @@ -2,6 +2,7 @@ module Generator.WebAppGeneratorTest where import qualified Data.Map as M import Fixtures +import StrongPath (relfile) import qualified StrongPath as SP import System.FilePath (()) import Test.Tasty.Hspec @@ -64,7 +65,8 @@ spec_WebAppGenerator = do AS.userDockerfileContents = Nothing, AS.configFiles = [], AS.devDatabaseUrl = Nothing, - AS.customViteConfigPath = Nothing + AS.customViteConfigPath = Nothing, + AS.srcTsConfigPath = [relfile|tsconfig.json|] } describe "genWebApp" $ do diff --git a/waspc/waspc.cabal b/waspc/waspc.cabal index 006ae96a66..1e7c95660a 100644 --- a/waspc/waspc.cabal +++ b/waspc/waspc.cabal @@ -319,6 +319,7 @@ library Wasp.Generator.SdkGenerator.CrudG Wasp.Generator.SdkGenerator.EmailSender.Providers Wasp.Generator.SdkGenerator.EnvValidation + Wasp.Generator.SdkGenerator.JsImport Wasp.Generator.SdkGenerator.Server.AuthG Wasp.Generator.SdkGenerator.Server.OAuthG Wasp.Generator.SdkGenerator.Server.CrudG