-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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
Using TS SDK from waspc
#2276
Using TS SDK from waspc
#2276
Changes from 13 commits
e228263
664eafe
1b75ab9
0c7b901
b7c4800
68bfe30
0758747
b7ecab4
e2e802e
2d27900
987243d
15e3e68
7600288
2b0c0ff
7ec9356
1597f4b
b887cd8
1bdc871
52378c4
115dce5
2617b6f
32275e5
b673308
3513b68
e9ef283
0dc7435
3e9efc8
24f65ec
edd7a85
ae76820
7eab0bc
8c8cb4f
d0ba9e5
4eec3c1
1699df2
5a6719e
3f48b98
67f1f9e
522e131
8ab0b3f
d90b235
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This file has a lot of diverse imports, indicating that it might want to be split into multiple files. I think we should split it probably -> we could certainly separate the logic that deals with WaspFile, and maybe then further split that into WaspLang and WaspTs logic, but not yet sure about that. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I thought about extracting a But yeah, if you like it too, I'll do it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Leaving this one for later. |
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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see somebody went wild with accepting import suggestions from LS :D. You can just add those to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Heh. I have had this item on my Haskell list for quite some time. For some reason (and for some SP imports), LSP almost never suggests a top-level import from
This mental overhead drives me crazy, and sometimes I forget to change the imports manually later (like here). I would really like to solve this because I don't want to waste time manually editing the imports. @Martinsos @infomiho Is this specific to my setup, does it happen to you? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am in emacs, so it is a bit different, but first I get a suggestion for autocompletion: This one is correct, but when I accept, for some reason it doesn't also add an import automatically. So I get a compiler error that symbol is missing. I then tell LSP to suggest code actions, and then I get this Which is correct! I am offered also the lower level imports, but the first option I am offered is to add it to existing import list of StrongPath, and if I pick that, it adds import correctly. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok, glad to hear it's fixable because it's been annoying me for ages (particularly with StrongPath imports). I'll bother you in the office to help me figure it out. |
||
import qualified Wasp.Analyzer as Analyzer | ||
import Wasp.Analyzer.AnalyzeError (getErrorMessageAndCtx) | ||
import Wasp.Analyzer.Parser.Ctx (Ctx) | ||
|
@@ -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) | ||
Comment on lines
+43
to
+45
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If this is used here, then it sounds like it maybe shouldn't be in the Generator hm. What do you think? I didn't check the code though to see if there is anything Generator specific about it really or not. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You're right, nice catch. I didn't notice it was there. And there's nothing generator-specific about it. I think it's just there because the generator used to be the only one running jobs. I'll move it somewhere more appropriate. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Leaving this one for later. |
||
import Wasp.Project.Common | ||
( CompileError, | ||
CompileWarning, | ||
WaspProjectDir, | ||
dotWaspDirInWaspProjectDir, | ||
findFileInWaspProjectDir, | ||
packageJsonInWaspProjectDir, | ||
prismaSchemaFileInWaspProjectDir, | ||
|
@@ -60,7 +73,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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm, I prefer keeping the |
||
Left errors -> return (Left errors, []) | ||
Right declarations -> | ||
analyzePackageJsonContent waspDir >>= \case | ||
|
@@ -69,8 +82,114 @@ 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 | ||
data WaspFile | ||
sodic marked this conversation as resolved.
Show resolved
Hide resolved
|
||
= WaspLang !(Path' Abs (File WaspLangFile)) | ||
| WaspTs !(Path' Abs (File WaspTsFile)) | ||
|
||
data WaspLangFile | ||
|
||
data WaspTsFile | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What if you went with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm, can you elaborate how this works with the functions that use these types (compared to what we have now)? I looked into DataKinds, and I don't think there's use for them here, but I might be missing something. |
||
|
||
data CompiledWaspJsFile | ||
|
||
data SpecJsonFile | ||
|
||
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 | ||
|
||
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 [] | ||
|
||
-- 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 | ||
sodic marked this conversation as resolved.
Show resolved
Hide resolved
sodic marked this conversation as resolved.
Show resolved
Hide resolved
|
||
waspProjectDir | ||
"npx" | ||
[ "tsc", | ||
"-p", | ||
toFilePath (waspProjectDir </> tsconfigNodeFileInWaspProjectDir), | ||
sodic marked this conversation as resolved.
Show resolved
Hide resolved
|
||
"--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|] | ||
-- TODO: I'm not yet sure where this is going to come from because we also need | ||
-- that knowledge to generate a TS SDK project. | ||
tsconfigNodeFileInWaspProjectDir :: Path' (Rel WaspProjectDir) File' | ||
tsconfigNodeFileInWaspProjectDir = [relfile|tsconfig.node.json|] | ||
|
||
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, | ||
sodic marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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|] | ||
|
||
readDeclsJsonFile :: Path' Abs (File SpecJsonFile) -> IO (Either [CompileError] Aeson.Value) | ||
readDeclsJsonFile declsJsonFile = do | ||
byteString <- IOUtil.readFile declsJsonFile | ||
return $ Right $ Aeson.toJSON byteString | ||
|
||
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 | ||
|
@@ -118,15 +237,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 | ||
sodic marked this conversation as resolved.
Show resolved
Hide resolved
|
||
where | ||
isWaspFile path = | ||
".wasp" | ||
`isSuffixOf` toFilePath path | ||
&& (length (toFilePath path) > length (".wasp" :: String)) | ||
findWaspTsFile files = WaspTs <$> findFileThatEndsWith ".wasp.mts" files | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I explained the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If that sticks, we will also wnat to have a comment in code somewhere that explains it. |
||
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 needed (fst <$> | ||
-- IOUtil.listDirectory already takes care of that and says so in its signature). | ||
-- 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 | ||
infomiho marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
analyzePackageJsonContent :: Path' Abs (Dir WaspProjectDir) -> IO (Either [CompileError] PackageJson) | ||
analyzePackageJsonContent waspProjectDir = | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't have something like
data WaspFile
, so that we could doPath' Abs (File WaspFile)
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice catch. We didn't before, but we'll have it now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll take care of this later. It requires dealing with cyclic dependencies.