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

Using TS SDK from waspc #2276

Open
wants to merge 37 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
e228263
Successfully get spec from stdout
sodic Sep 6, 2024
664eafe
Successfully read spec from file
sodic Sep 6, 2024
1b75ab9
Clean up code in Analyze.hs
sodic Sep 10, 2024
0c7b901
Move outDir source of truth into Haskell
sodic Sep 10, 2024
b7c4800
Add extra context to Analyze.hs
sodic Sep 11, 2024
68bfe30
Improve TS SDK analysis
sodic Sep 11, 2024
0758747
Put config dir back in .wasp dir
sodic Sep 11, 2024
b7ecab4
Add more comments
sodic Sep 12, 2024
e2e802e
Update Analyze.hs
sodic Sep 18, 2024
2d27900
Merge branch 'main' into filip-ts-sdk
sodic Sep 25, 2024
987243d
Remove todo for duplication
sodic Sep 25, 2024
15e3e68
Remove the duplication in templating
sodic Sep 25, 2024
7600288
Rearrange analyze.hs
sodic Sep 25, 2024
2b0c0ff
Remove todo after adding it to GitHub
sodic Sep 25, 2024
7ec9356
Remove one more todo
sodic Sep 25, 2024
1597f4b
Remove todo after adding it to GitHub
sodic Sep 25, 2024
b887cd8
Resolve some more todos
sodic Sep 25, 2024
1bdc871
Remove another todo
sodic Sep 25, 2024
52378c4
Send prisma models to JS runtime
sodic Sep 26, 2024
115dce5
Merge branch 'main' into filip-ts-sdk
sodic Sep 26, 2024
2617b6f
Properly parse decls.json
sodic Sep 26, 2024
32275e5
Improve error messages
sodic Sep 26, 2024
b673308
Merge branch 'main' into filip-ts-sdk
sodic Sep 30, 2024
3513b68
Fix ext import parsing and inject entities
sodic Oct 5, 2024
e9ef283
Fail if you find both wasp files
sodic Oct 7, 2024
0dc7435
Change tsconfig file name
sodic Oct 7, 2024
3e9efc8
Clean up imports
sodic Oct 7, 2024
24f65ec
Fix some more imports
sodic Oct 7, 2024
edd7a85
Change type name
sodic Oct 7, 2024
ae76820
Implement review changes
sodic Oct 7, 2024
7eab0bc
Fix failing tests
sodic Oct 7, 2024
8c8cb4f
Remove duplication when parsing ext imports
sodic Oct 7, 2024
d0ba9e5
Remove redundant variable
sodic Oct 7, 2024
4eec3c1
Remove trailling space
sodic Oct 7, 2024
1699df2
Revert Show instance for Decl
sodic Oct 7, 2024
5a6719e
Add module to package.json and remove mts
sodic Oct 8, 2024
3f48b98
Merge branch 'main' into filip-ts-sdk
sodic Oct 8, 2024
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
2 changes: 1 addition & 1 deletion waspc/cli/exe/Main.hs
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ main = withUtf8 . (`E.catch` handleInternalErrors) $ do

handleInternalErrors :: E.ErrorCall -> IO ()
handleInternalErrors e = do
putStrLn $ "\nInternal Wasp error (bug in compiler):\n" ++ indent 2 (show e)
putStrLn $ "\nInternal Wasp error (bug in the compiler):\n" ++ indent 2 (show e)
exitFailure

-- | Sets env variables that are visible to the commands run by the CLI.
Expand Down
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,10 @@ replaceTemplatePlaceholdersInWaspFile ::
replaceTemplatePlaceholdersInWaspFile appName projectName projectDir =
findWaspFile projectDir >>= \case
Nothing -> return ()
Just absMainWaspFile -> replaceTemplatePlaceholdersInFileOnDisk appName projectName absMainWaspFile
Just (WaspLang absMainWaspFile) -> replaceTemplatePlaceholders absMainWaspFile
Just (WaspTs absMainTsFile) -> replaceTemplatePlaceholders absMainTsFile
where
replaceTemplatePlaceholders = replaceTemplatePlaceholdersInFileOnDisk appName projectName

-- | 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
Copy link
Member

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 do Path' Abs (File WaspFile)?

Copy link
Contributor Author

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.

Copy link
Contributor Author

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.

showCompilerErrorForTerminal (waspFilePath, waspFileContent) (errMsg, errCtx) =
let srcRegion = getCtxRgn errCtx
in intercalate
Expand Down
130 changes: 119 additions & 11 deletions waspc/src/Wasp/Project/Analyze.hs
Copy link
Member

Choose a reason for hiding this comment

The 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.

Copy link
Contributor Author

@sodic sodic Sep 30, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought about extracting a Project.WaspFile module but wasn't sure if it's necessary (thought I mentioned it in a comment somewhere, but looks like I didn't).

But yeah, if you like it too, I'll do it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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, basename, toFilePath, (</>))
import qualified StrongPath as SP
import StrongPath.TH (relfile)
import StrongPath.Types (File)
Copy link
Member

Choose a reason for hiding this comment

The 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 StrongPath import above. I also wonder if you need separate qualified as SP line.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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 StrongPath. Whenever I look at LSP's import suggestions, this is my protocol:

  1. Check if there is an Add to import option.
  2. Check if there is a top-level import option (in this case, StrongPath).
  3. Use a lower-level import option and fix the import manually later (sometimes it works, sometimes it doesn't).

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?
Please try with reldirP in Wasp.Project.Common. I just reproduced it there:

image

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Screenshot 2024-09-30 at 13 13 41

Top level for me

Copy link
Member

Choose a reason for hiding this comment

The 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:

image

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

image

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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)
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)
Comment on lines +43 to +45
Copy link
Member

Choose a reason for hiding this comment

The 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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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,
Expand All @@ -43,6 +56,7 @@ import Wasp.Psl.Valid (getValidDbSystemFromPrismaSchema)
import qualified Wasp.Psl.Valid as PslV
import Wasp.Util (maybeToEither)
import qualified Wasp.Util.IO as IOUtil
import Wasp.Util.StrongPath (replaceRelExtension)
import Wasp.Valid (ValidationError)
import qualified Wasp.Valid as Valid

Expand All @@ -60,7 +74,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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

waspFilePath -> waspFile?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I prefer keeping the Path. I think it's our general convention too.

Left errors -> return (Left errors, [])
Right declarations ->
analyzePackageJsonContent waspDir >>= \case
Expand All @@ -69,8 +83,103 @@ 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
Comment on lines +96 to +98
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if you went with data WaspFile = WaspLangFile | WaspTsFile and used DataKinds lang extension, assuming you previously renamed data WaspFile to data WaspFilePath which is any way a more correct name?
I believe that will allow you to then also do stuff like specify that a function takes any of these, because now you can use WaspLangFile as a kind also. I am not 100% sure because I haven't tried it and I am still a bit newer to type level programming but it might work nicely.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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: I'm not yet sure where tsconfig.node.json location will come from
-- because we also need that knowledge to generate a TS SDK project.
compiledWaspJsFile <- ExceptT $ compileWaspTsFile waspProjectDir [relfile|tsconfig.node.json|] waspFilePath
sodic marked this conversation as resolved.
Show resolved Hide resolved
specJsonFile <- ExceptT $ executeMainWaspJsFile waspProjectDir compiledWaspJsFile
contents <- ExceptT $ readDeclsJsonFile specJsonFile
liftIO $ putStrLn "Here are the contents of the spec file:"
liftIO $ print contents
return []

compileWaspTsFile ::
Path' Abs (Dir WaspProjectDir) ->
Path' (Rel WaspProjectDir) File' ->
Path' Abs (File WaspTsFile) ->
IO (Either [CompileError] (Path' Abs (File CompiledWaspJsFile)))
sodic marked this conversation as resolved.
Show resolved Hide resolved
compileWaspTsFile waspProjectDir tsconfigNodeFileInWaspProjectDir waspFilePath = do
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 outDir
sodic marked this conversation as resolved.
Show resolved Hide resolved
]
Copy link
Contributor Author

@sodic sodic Sep 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function does its best to control tsc and emulate its behavior.

The compiled main.wasp.js file's full file path depends on three factors:

  1. The include argument for tsc (e.g., "include": ["main.wasp.ts"]).
  2. The file's name (which must match the include argument).
  3. The outDir argument for tsc.

We want to control all of them, but it's a little tricky.

Controlling the outDir is simple. We configured the tsconfig.node.json to --noEmit (meaning it provides only IDE support and does not specify an outDir). Wasp then overrides this by specifying --noEmit false and --outDir <path> when calling tsc.

Controlling the file name is also simple. The compiler requires the file's name to be <something>.wasp.ts, and then searches for that file. We should also probably ensure there aren't more .wasp.ts files, but that's a different story (we don't do it for .wasp files either).

Controlling the include argument is the tricky part. It must be the same as the file name (e.g., main.wasp.ts). I'm hoping that @infomiho's validation can help here. To control include, this function must parse the tsconfig.node.json file, validate what's necessary, and either:

  1. Pass the parsed contents to tsc explicitly, without relying on tsc reading the tsconfig.node.json file.
  2. Tell tsc to read the tsconfig.node.json file after validation.

Note

If we kept the contents of tsconfig.node.json in Haskell (as opposed to a file on disk), we wouldn't have this problem, but our users would suffer bad DX.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aren't the file names main.wasp.mts and main.wasp.mjs?

The validation PR will help here since it enables to required certain values in tsconfig.json (which we will need to expand to three different tsconfig.json files).

Copy link
Contributor Author

@sodic sodic Sep 27, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aren't the file names main.wasp.mts and main.wasp.mjs?

You can ignore that, it's a minor detail. I mostly use ts and js as canonical names for these files when I'm writing about other ideas.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems to me like you put effor tinto explaining the situation here, but I have to admit I still didn't get most of it. I am certainly missing some context + have less knowledge of the ts toolchain, but I still feel like I should have been able to understand it.
Main thoughts:

  1. If you had to write all these comments for us, they should likely also go into the code, at least portion of it that has any assumptions we did or reasons why we did something and similar.
  2. Seems to me like I am just missing context to understand the crux of it. For example you say this command controls and emulates tsc. I can see it call tsc, but I don't get what you mean by controling and emulating it. I get that we have tsconfig.node.json, and I think I undrestand that tsconfig.node.json is here primarily for IDE, but here, in this call to tsc, we want to use settings from tsconfig.node.json but with some tweaks that we need to actually do the compliation here? And then you are tring to explain those tweaks and assumptions behind them? You continue the use of word control through the comment, and I feel like that should give me context I need to understand the rest, but it doesn't.
    I also don't get the part with keeping contents of tsconfig.node.js in Haskell -> so we wouldn't have it on disk at all? How would you use them then, you would feed them to this command here via stdin? Can that even work and I would suspect then it makes more sense to jsut provide ocmpiler flags? I feel like I am again missing something here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I may be in too deep here.

I think it's best if I explain this to you in person when you come back to the office (tomorrow or Wednesday), and you can help me write a comment that makes the situation clearer to the uninitiated.

@infomiho Did you understand it? If so, can you help out?

Copy link
Contributor

@infomiho infomiho Sep 30, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Martinsos I guess what Filip is trying to say by "emulate and control" - make sure that the compilation step happens in the same say the TS LSP interprets the tsconfig.node.json in the IDE + modifying it to actually emit the file.

This command runs tsc with tsconfig.node.json + adding some options so the JS file is outputted.

Motivation: in order for us to know where the JS file will end up (so we can call it with node) we have to make sure that the user didn't modify the tsconfig.node.json in way that would mess up our pipeline. We do this by adding the noEmit and outDir options + we'll validate the tsconfig.node.json file.

Copy link
Member

@Martinsos Martinsos Oct 1, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok tnx both -> @infomiho's explanation here helped + he explained it to me a bit more, so I think I get it now @sodic ! Here is what I understand, and how I would put it.

Here, in this function, we are using tsc to compile the main .wasp.ts file into JS file so we can execute it.
While tsconfig.wasp.json is the main config for compilation of .wasp.ts files (both for our purposes here and for their IDE (language server)), there are a couple of settings we want to ensure for here that are not in the tsconfig.json -> we want to specify that JS file has to be emitted, and we want to specify where it should be emitted, so that we can then later execute that JS file. So we add those as additional cmd args to tsc, overriding whatever is in tsconfig.json. Why didn't we write such tsconfig.json from the start? Well, because LS doesn't need it, and we can easily set these arguments here, so why enforce them earlier then we have to.
So TLDR: we set noEmit and outDir to make sure that JS file gets produced and in expected place, so we can further consume it.

That is it for that part, but there is another issue, which I think is a general issue, not specific to are we compiling with tsc or if LS is doing the compilation: How do we know what to put in include in tsconfig, when we don't konw the name of the .wasp.ts file? It is not fixed to main.wasp.ts, it can be anything instead, as long as it ends with .wasp.ts. So you are asking here if we should, at in this place in haskell code, where we already identified the .wasp.ts file, check if it indeed matches what is in include field in tsconfig.wasp.json and complain if that is not a match.
As I said, I would treat this as a separate issue, as it is a problem for both LS and for our tsc usage here.
One solution I see for it is indeed what you said -> check if these names match and complain otherwise.
Other solution I see is us hardcoding the file: we can say it has to be main.wasp.ts and that is it. Or spec.wasp.ts? Probalby main.wasp.ts is good enough name.

I actually like the idea of hardcoded name. We dont' need the flexibility + then we open the opportunity for multiple .wasp.ts files which we will want anyway, and you don't have to bother with validation or anything. We can just tell them it has to be main.wasp.ts and that is it. I know this is different behaviour than what we have now with .wasp files, but I think that is fine, this is a good opportunity actually to fix that.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll leave this for after the release if that's alright @Martinsos.

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
outDir = waspProjectDir </> dotWaspDirInWaspProjectDir
absCompiledWaspJsFile = outDir </> compiledWaspJsFileInDotWaspDir
compiledWaspJsFileInDotWaspDir = SP.castFile $ case replaceRelExtension (basename waspFilePath) ".mjs" of
sodic marked this conversation as resolved.
Show resolved Hide resolved
Just path -> path
Nothing -> error $ "Couldn't calculate the compiled JS file path for " ++ show waspFilePath
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should I just go with fromJust?

Copy link
Member

@Martinsos Martinsos Sep 27, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think providing an error message is nice, and yeah this casing here is boilerplatish, but better option than fromJust is fromMaybe because it will just pass the Just value through. It is like fromJust using id for positive case.

What is ugly is that error is not the last thing to read, although you could probably achieve that by doing a flip hm.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Weird that the LSP didn't suggest fromMaybe. It always suggests it in these cases, so I got used to not thinking about it. I'll switch it up.

Copy link
Contributor Author

@sodic sodic Oct 7, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I know why the LSP didn't suggest it.

fromMaybe won't work here because I'm crashing the process with an error call. Just to be clear, this isn't a user compile error, if the nothing branch executes, it's an error in our compiler.

Should I leave it like this then?


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|spec.json|]
sodic marked this conversation as resolved.
Show resolved Hide resolved

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
Expand Down Expand Up @@ -118,15 +227,14 @@ 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
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I explained the mjs and mts story in Discord, you can ignore that for now.

Copy link
Member

Choose a reason for hiding this comment

The 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
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 =
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) ->
Martinsos marked this conversation as resolved.
Show resolved Hide resolved
IO (Maybe (Path' Abs (File f)))
findFileInWaspProjectDir waspDir file = do
let fileAbsFp = waspDir </> file
fileExists <- doesFileExist $ toFilePath fileAbsFp
Expand Down
Loading