Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

TS Sdk #2276

Draft
wants to merge 9 commits into
base: main
Choose a base branch
from
Draft

TS Sdk #2276

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ 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 (WaspFile (..), findPackageJsonFile, findWaspFile)
import Wasp.Project.Common (WaspProjectDir)
import qualified Wasp.Util.IO as IOUtil

Expand All @@ -26,7 +26,9 @@ replaceTemplatePlaceholdersInWaspFile ::
replaceTemplatePlaceholdersInWaspFile appName projectName projectDir =
findWaspFile projectDir >>= \case
Nothing -> return ()
Just absMainWaspFile -> replaceTemplatePlaceholdersInFileOnDisk appName projectName absMainWaspFile
-- TODO: Remove duplication?
Just (WaspLang absMainWaspFile) -> replaceTemplatePlaceholdersInFileOnDisk appName projectName absMainWaspFile
Just (WaspTs absMainTsFile) -> replaceTemplatePlaceholdersInFileOnDisk appName projectName absMainTsFile

-- | Template file for package.json file has placeholders in it that we want to replace
-- in the package.json file we have written to the disk.
Expand Down
5 changes: 3 additions & 2 deletions waspc/src/Wasp/Error.hs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
module Wasp.Error (showCompilerErrorForTerminal) where

import Data.List (intercalate)
import StrongPath (Abs, File', Path')
import StrongPath (Abs, Path')
import qualified StrongPath as SP
import StrongPath.Types (File)
import Wasp.Analyzer.Parser.Ctx (Ctx, getCtxRgn)
import Wasp.Analyzer.Parser.SourcePosition (SourcePosition (..))
import Wasp.Analyzer.Parser.SourceRegion (SourceRegion (..))
Expand All @@ -12,7 +13,7 @@ import qualified Wasp.Util.Terminal as T
-- | Transforms compiler error (error with parse context) into an informative, pretty String that
-- can be printed directly into the terminal. It uses terminal features like escape codes
-- (colors, styling, ...).
showCompilerErrorForTerminal :: (Path' Abs File', String) -> (String, Ctx) -> String
showCompilerErrorForTerminal :: (Path' Abs (File f), String) -> (String, Ctx) -> String
showCompilerErrorForTerminal (waspFilePath, waspFileContent) (errMsg, errCtx) =
let srcRegion = getCtxRgn errCtx
in intercalate
Expand Down
153 changes: 142 additions & 11 deletions waspc/src/Wasp/Project/Analyze.hs
Original file line number Diff line number Diff line change
@@ -1,17 +1,26 @@
module Wasp.Project.Analyze
( analyzeWaspProject,
readPackageJsonFile,
analyzeWaspFileContent,
findWaspFile,
findPackageJsonFile,
analyzePrismaSchema,
WaspFile (..),
)
where

import Control.Applicative ((<|>))
import Control.Arrow (ArrowChoice (left))
import Control.Concurrent (newChan)
import Control.Concurrent.Async (concurrently)
import Control.Monad.Except (ExceptT (..), runExceptT)
import Control.Monad.IO.Class (liftIO)
import qualified Data.Aeson as Aeson
import Data.Conduit.Process.Typed (ExitCode (..))
import Data.List (find, isSuffixOf)
import StrongPath (Abs, Dir, File', Path', toFilePath, (</>))
import StrongPath (Abs, Dir, File', Path', Rel, toFilePath, (</>))
import qualified StrongPath as SP
import StrongPath.TH (relfile)
import StrongPath.Types (File)
import qualified Wasp.Analyzer as Analyzer
import Wasp.Analyzer.AnalyzeError (getErrorMessageAndCtx)
import Wasp.Analyzer.Parser.Ctx (Ctx)
Expand All @@ -23,10 +32,14 @@ import qualified Wasp.CompileOptions as CompileOptions
import qualified Wasp.ConfigFile as CF
import Wasp.Error (showCompilerErrorForTerminal)
import qualified Wasp.Generator.ConfigFile as G.CF
import qualified Wasp.Generator.Job as J
import Wasp.Generator.Job.IO (readJobMessagesAndPrintThemPrefixed)
import Wasp.Generator.Job.Process (runNodeCommandAsJob)
import Wasp.Project.Common
( CompileError,
CompileWarning,
WaspProjectDir,
dotWaspDirInWaspProjectDir,
findFileInWaspProjectDir,
packageJsonInWaspProjectDir,
prismaSchemaFileInWaspProjectDir,
Expand All @@ -46,6 +59,27 @@ import qualified Wasp.Util.IO as IOUtil
import Wasp.Valid (ValidationError)
import qualified Wasp.Valid as Valid

data WaspFile
= WaspLang !(Path' Abs (File WaspLangFile))
| WaspTs !(Path' Abs (File WaspTsFile))

data WaspLangFile

data WaspTsFile

data CompiledWaspJsFile

data SpecJsonFile

-- TODO: Not yet sure where this is going to come from because we also need that knowledge to generate a TS SDK project.
--
-- BEGIN SHARED STUFF

tsconfigNodeFileInWaspProjectDir :: Path' (Rel WaspProjectDir) File'
tsconfigNodeFileInWaspProjectDir = [relfile|tsconfig.node.json|]

-- END SHARED STUFF

analyzeWaspProject ::
Path' Abs (Dir WaspProjectDir) ->
CompileOptions ->
Expand All @@ -60,7 +94,7 @@ analyzeWaspProject waspDir options = do
(Left prismaSchemaErrors, prismaSchemaWarnings) -> return (Left prismaSchemaErrors, prismaSchemaWarnings)
-- NOTE: we are ignoring prismaSchemaWarnings if the schema was parsed successfully
(Right prismaSchemaAst, _) ->
analyzeWaspFile prismaSchemaAst waspFilePath >>= \case
analyzeWaspFile waspDir prismaSchemaAst waspFilePath >>= \case
Left errors -> return (Left errors, [])
Right declarations ->
analyzePackageJsonContent waspDir >>= \case
Expand All @@ -69,8 +103,98 @@ analyzeWaspProject waspDir options = do
where
fileNotFoundMessage = "Couldn't find the *.wasp file in the " ++ toFilePath waspDir ++ " directory"

analyzeWaspFile :: Psl.Schema.Schema -> Path' Abs File' -> IO (Either [CompileError] [AS.Decl])
analyzeWaspFile prismaSchemaAst waspFilePath = do
analyzeWaspFile :: Path' Abs (Dir WaspProjectDir) -> Psl.Schema.Schema -> WaspFile -> IO (Either [CompileError] [AS.Decl])
analyzeWaspFile waspDir prismaSchemaAst = \case
WaspLang waspFilePath -> analyzeWaspLangFile prismaSchemaAst waspFilePath
WaspTs waspFilePath -> analyzeWaspTsFile waspDir prismaSchemaAst waspFilePath

readDeclsJsonFile :: Path' Abs (File SpecJsonFile) -> IO (Either [CompileError] Aeson.Value)
readDeclsJsonFile declsJsonFile = do
byteString <- IOUtil.readFile declsJsonFile
return $ Right $ Aeson.toJSON byteString

analyzeWaspTsFile :: Path' Abs (Dir WaspProjectDir) -> Psl.Schema.Schema -> Path' Abs (File WaspTsFile) -> IO (Either [CompileError] [AS.Decl])
analyzeWaspTsFile waspProjectDir _prismaSchemaAst _waspFilePath = runExceptT $ do
-- TODO: The function currently doesn't require the path to main.wasp.ts
-- because it reads it from the tsconfig
-- Should we ensure that the tsconfig indeed points to the name we expect? Probably.
compiledWaspJsFile <- ExceptT $ compileWaspTsFile waspProjectDir
specJsonFile <- ExceptT $ executeMainWaspJsFile waspProjectDir compiledWaspJsFile
contents <- ExceptT $ readDeclsJsonFile specJsonFile
liftIO $ putStrLn "Here are the contents of the spec file:"
liftIO $ print contents
return []

executeMainWaspJsFile :: Path' Abs (Dir WaspProjectDir) -> Path' Abs (File CompiledWaspJsFile) -> IO (Either [CompileError] (Path' Abs (File SpecJsonFile)))
executeMainWaspJsFile waspProjectDir absCompiledMainWaspJsFile = do
chan <- newChan
(_, runExitCode) <- do
concurrently
(readJobMessagesAndPrintThemPrefixed chan)
( runNodeCommandAsJob
waspProjectDir
"npx"
-- TODO: Figure out how to keep running instructions in a single place
-- (e.g., this is the same as the package name, but it's repeated in two places).
-- Before this, I had the entrypoint file hardcoded, which was bad
-- too: waspProjectDir </> [relfile|node_modules/wasp-config/dist/run.js|]
[ "wasp-config",
SP.fromAbsFile absCompiledMainWaspJsFile,
SP.fromAbsFile absSpecOutputFile
]
J.Wasp
chan
)
case runExitCode of
ExitFailure _status -> return $ Left ["Error while running the compiled *.wasp.mts file."]
ExitSuccess -> return $ Right absSpecOutputFile
where
-- TODO: The config part of the path is problematic because it relies on TSC to create it during compilation,
-- see notes in compileWaspFile.
absSpecOutputFile = waspProjectDir </> dotWaspDirInWaspProjectDir </> [relfile|config/spec.json|]

-- TODO: Reconsider the return value. Can I write the function in such a way
-- that it's impossible to get the absolute path to the compiled file without
-- calling the function that compiles it?
-- To do that, I'd have to craete a private module that knows where the file is
-- and not expose the constant for creating the absoltue path (like I did with config/spec.json).
-- Normally, I could just put the constant in the where clause like I did there, but I'm hesitant
-- to do that since the path comes from the tsconfig.
-- That is what I did currently, but I'll have to figure out the long-term solution.
-- The ideal solution is reading the TS file, and passing its config to tsc
-- manually (and getting the output file path in the process).
compileWaspTsFile :: Path' Abs (Dir WaspProjectDir) -> IO (Either [CompileError] (Path' Abs (File CompiledWaspJsFile)))
compileWaspTsFile waspProjectDir = do
-- TODO: The function should also receive the tsconfig.node.json file (not the main.wasp.ts file),
-- because the source of truth (the name of the file, where it's compiled) comes from the typescript config.
-- However, we might want to keep this information in haskell and then verify that the tsconfig is correct.
chan <- newChan
(_, tscExitCode) <-
concurrently
(readJobMessagesAndPrintThemPrefixed chan)
( runNodeCommandAsJob
waspProjectDir
"npx"
[ "tsc",
"-p",
toFilePath (waspProjectDir </> tsconfigNodeFileInWaspProjectDir),
"--noEmit",
"false",
"--outDir",
toFilePath $ SP.parent absCompiledWaspJsFile
]
J.Wasp
chan
)
case tscExitCode of
ExitFailure _status -> return $ Left ["Error while running TypeScript compiler on the *.wasp.mts file."]
ExitSuccess -> return $ Right absCompiledWaspJsFile
where
-- TODO: I should be getting the compiled file path from the tsconfig.node.file
absCompiledWaspJsFile = waspProjectDir </> dotWaspDirInWaspProjectDir </> [relfile|config/main.wasp.mjs|]

analyzeWaspLangFile :: Psl.Schema.Schema -> Path' Abs (File WaspLangFile) -> IO (Either [CompileError] [AS.Decl])
analyzeWaspLangFile prismaSchemaAst waspFilePath = do
waspFileContent <- IOUtil.readFile waspFilePath
left (map $ showCompilerErrorForTerminal (waspFilePath, waspFileContent))
<$> analyzeWaspFileContent prismaSchemaAst waspFileContent
Expand Down Expand Up @@ -118,15 +242,22 @@ constructAppSpec waspDir options packageJson parsedPrismaSchema decls = do

return $ runValidation ASV.validateAppSpec appSpec

findWaspFile :: Path' Abs (Dir WaspProjectDir) -> IO (Maybe (Path' Abs File'))
findWaspFile :: Path' Abs (Dir WaspProjectDir) -> IO (Maybe WaspFile)
findWaspFile waspDir = do
files <- fst <$> IOUtil.listDirectory waspDir
return $ (waspDir </>) <$> find isWaspFile files
return $ findWaspTsFile files <|> findWaspLangFile files
where
isWaspFile path =
".wasp"
`isSuffixOf` toFilePath path
&& (length (toFilePath path) > length (".wasp" :: String))
findWaspTsFile files = WaspTs <$> findFileThatEndsWith ".wasp.mts" files
findWaspLangFile files = WaspLang <$> findFileThatEndsWith ".wasp" files
-- TODO: We used to have a check that made sure not to misidentify the .wasp
-- dir as a wasp file, but that's not true (fst <$>
-- IOUtil.listDirectory takes care of that).
-- A bigger problem is if the user has a file with the same name as the wasp dir,
-- but that's a problem that should be solved in a different way (it's
-- still possible to have both main.wasp and .wasp files and cause that
-- error).
-- TODO: Try out what happens when Wasp finds this file, but the tsconfing setup and package are missing
findFileThatEndsWith suffix files = SP.castFile . (waspDir </>) <$> find ((suffix `isSuffixOf`) . toFilePath) files

analyzePackageJsonContent :: Path' Abs (Dir WaspProjectDir) -> IO (Either [CompileError] PackageJson)
analyzePackageJsonContent waspProjectDir =
Expand Down
5 changes: 3 additions & 2 deletions waspc/src/Wasp/Project/Common.hs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ module Wasp.Project.Common
where

import StrongPath (Abs, Dir, File', Path', Rel, reldir, relfile, toFilePath, (</>))
import StrongPath.Types (File)
import System.Directory (doesFileExist)
import Wasp.AppSpec.ExternalFiles (SourceExternalCodeDir, SourceExternalPublicDir)
import qualified Wasp.Generator.Common
Expand Down Expand Up @@ -86,8 +87,8 @@ tsconfigInWaspProjectDir = [relfile|tsconfig.json|]

findFileInWaspProjectDir ::
Path' Abs (Dir WaspProjectDir) ->
Path' (Rel WaspProjectDir) File' ->
IO (Maybe (Path' Abs File'))
Path' (Rel WaspProjectDir) (File f) ->
IO (Maybe (Path' Abs (File f)))
findFileInWaspProjectDir waspDir file = do
let fileAbsFp = waspDir </> file
fileExists <- doesFileExist $ toFilePath fileAbsFp
Expand Down
Loading