Added fixing of NodeJS operations files.

This commit is contained in:
Martin Sosic 2023-06-28 22:04:57 +02:00
parent 286117b404
commit 08c235d12e
5 changed files with 183 additions and 67 deletions

0
waspc/Operation.js Normal file
View File

View File

@ -3,7 +3,8 @@ module Wasp.AI.GenerateNewProject
)
where
import Control.Monad (forM)
import Control.Monad (forM, forM_)
import Data.List (nub)
import Data.Text (Text)
import qualified Data.Text as T
import NeatInterpolation (trimming)
@ -11,7 +12,8 @@ import StrongPath (File', Path, Rel, System)
import Wasp.AI.CodeAgent (CodeAgent, writeToLog)
import Wasp.AI.GenerateNewProject.Common (NewProjectDetails (..))
import Wasp.AI.GenerateNewProject.Entity (writeEntitiesToWaspFile)
import Wasp.AI.GenerateNewProject.Operation (OperationType (..), generateAndWriteOperation)
import Wasp.AI.GenerateNewProject.Operation (OperationType (..), generateAndWriteOperation, getOperationJsFilePath)
import Wasp.AI.GenerateNewProject.OperationsJsFile (fixOperationsJsFile)
import Wasp.AI.GenerateNewProject.Page (generateAndWritePage)
import Wasp.AI.GenerateNewProject.Plan (generatePlan)
import qualified Wasp.AI.GenerateNewProject.Plan as Plan
@ -64,6 +66,12 @@ generateNewProject newProjectDetails waspProjectSkeletonFiles = do
fixWaspFile newProjectDetails waspFilePath
writeToLog "Wasp file fixed."
writeToLog "Fixing any mistakes in NodeJS operations files..."
forM_ (nub $ getOperationJsFilePath <$> (queries <> actions)) $ \opFp -> do
fixOperationsJsFile newProjectDetails waspFilePath opFp
writeToLog $ T.pack $ "Fixed NodeJS operations file '" <> opFp <> "'."
writeToLog "NodeJS operations files fixed."
-- TODO: what about having additional step here that goes through all the files once again and
-- fixes any stuff in them (Wasp, JS files)? REPL?

View File

@ -5,12 +5,16 @@ module Wasp.AI.GenerateNewProject.Operation
Operation (..),
OperationType (..),
OperationImpl (..),
actionDocPrompt,
queryDocPrompt,
getOperationJsFilePath,
)
where
import Data.Aeson (FromJSON)
import Data.Aeson.Types (ToJSON)
import Data.List (find, intercalate, isInfixOf, isPrefixOf)
import Data.Text (Text)
import qualified Data.Text as T
import GHC.Generics (Generic)
import NeatInterpolation (trimming)
@ -106,68 +110,6 @@ generateOperation operationType newProjectDetails entityPlans operationPlan = do
Action -> actionDocPrompt
Query -> queryDocPrompt
actionDocPrompt =
[trimming|
Action is implemented via Wasp declaration and corresponding NodeJS implementation.
Example of Wasp declaration:
```wasp
action updateTaskIsDone {
fn: import { updateTaskIsDone } from "@server/taskActions.js",
entities: [Task] // Entities that action uses.
}
```
Example of NodeJS implementation:
```js
import HttpError from '@wasp/core/HttpError.js'
export const updateTaskIsDone = (args, context) => {
if (!context.user) { throw new HttpError(403) } // If user needs to be authenticated.
return context.entities.Task.update({ // prisma object
where: { args.id },
data: { args.isDone }
})
}
```
Action can then be easily called from the client, via Wasp's RPC mechanism.
|]
queryDocPrompt =
[trimming|
Query is implemented via Wasp declaration and corresponding NodeJS implementation.
It is important that Query doesn't do any mutations, be it on the server or external world.
Example of Wasp declaration:
```wasp
query fetchFilteredTasks {
fn: import { getFilteredTasks } from "@server/taskQueries.js",
entities: [Task] // Entities that query uses.
}
```
Example of NodeJS implementation:
```js
import HttpError from '@wasp/core/HttpError.js'
export const getFilteredTasks = async (args, context) => {
if (!context.user) { throw new HttpError(403) } // If user needs to be authenticated.
return context.entities.Task.findMany({
where: { isDone: args.isDone }
})
}
```
Query can then be easily called from the client, via Wasp's RPC mechanism.
|]
fixOperationImplIfNeeded :: OperationImpl -> CodeAgent OperationImpl
fixOperationImplIfNeeded operationImpl = do
let issues = checkWaspDecl operationImpl <> checkJsImpl operationImpl
@ -194,6 +136,70 @@ generateOperation operationType newProjectDetails entityPlans operationPlan = do
}
]
actionDocPrompt :: Text
actionDocPrompt =
[trimming|
Action is implemented via Wasp declaration and corresponding NodeJS implementation.
Example of Wasp declaration:
```wasp
action updateTaskIsDone {
fn: import { updateTaskIsDone } from "@server/taskActions.js",
entities: [Task] // Entities that action uses.
}
```
Example of NodeJS implementation:
```js
import HttpError from '@wasp/core/HttpError.js'
export const updateTaskIsDone = (args, context) => {
if (!context.user) { throw new HttpError(403) } // If user needs to be authenticated.
return context.entities.Task.update({ // prisma object
where: { args.id },
data: { args.isDone }
})
}
```
Action can then be easily called from the client, via Wasp's RPC mechanism.
|]
queryDocPrompt :: Text
queryDocPrompt =
[trimming|
Query is implemented via Wasp declaration and corresponding NodeJS implementation.
It is important that Query doesn't do any mutations, be it on the server or external world.
Example of Wasp declaration:
```wasp
query fetchFilteredTasks {
fn: import { getFilteredTasks } from "@server/taskQueries.js",
entities: [Task] // Entities that query uses.
}
```
Example of NodeJS implementation:
```js
import HttpError from '@wasp/core/HttpError.js'
export const getFilteredTasks = async (args, context) => {
if (!context.user) { throw new HttpError(403) } // If user needs to be authenticated.
return context.entities.Task.findMany({
where: { isDone: args.isDone }
})
}
```
Query can then be easily called from the client, via Wasp's RPC mechanism.
|]
-- TODO: This is quite manual here, checking the AST!
-- Consider instead generating entities during assembling Plan,
-- and then using those to manually construct the wasp decl, so we are
@ -240,16 +246,23 @@ writeOperationToJsFile :: Operation -> CodeAgent ()
writeOperationToJsFile operation =
-- TODO: An issue we have here is that if other operation already did the same import,
-- we don't know and we import it again.
-- One thing we can do it supply chatGPT with a list of imports that are already there.
-- Second thing we can do is to look for same lines at the start of the file, but that sounds fragile.
-- One thing we can do is supply chatGPT with a list of imports that are already there.
-- Second thing we can do is to look for same lines at the start of the file, but that sounds
-- fragile.
-- Maybe best to read and pass previous imports (we would have to do that above somewhere).
-- Or even the whole file? Hmmmmm.
-- Right now we fix this later, while fixing the whole operations file, but we could try to fix
-- it here, earlier.
writeToFile path $
(jsImportsBlock <>) . (<> jsImpl) . maybe "" (<> "\n\n")
where
path = resolvePath $ Plan.opFnPath $ opPlan operation
path = getOperationJsFilePath operation
jsImpl = T.pack $ opJsImpl $ opImpl operation
jsImportsBlock = T.pack $ maybe "" (<> "\n") $ opJsImports $ opImpl operation
getOperationJsFilePath :: Operation -> FilePath
getOperationJsFilePath operation = resolvePath $ Plan.opFnPath $ opPlan operation
where
pathPrefix = "@server/"
resolvePath p | pathPrefix `isPrefixOf` p = "src/" <> drop (length ("@" :: String)) p
resolvePath _ = error "path incorrectly formatted, should start with " <> pathPrefix <> "."

View File

@ -0,0 +1,94 @@
{-# LANGUAGE DeriveGeneric #-}
module Wasp.AI.GenerateNewProject.OperationsJsFile
( fixOperationsJsFile,
)
where
import Data.Aeson (FromJSON)
import Data.Aeson.Types (ToJSON)
import Data.Maybe (fromMaybe)
import Data.Text (Text)
import qualified Data.Text as T
import GHC.Generics (Generic)
import NeatInterpolation (trimming)
import Wasp.AI.CodeAgent (CodeAgent, getFile, writeToFile)
import Wasp.AI.GenerateNewProject.Common
( NewProjectDetails,
defaultChatGPTParams,
queryChatGPTForJSON,
)
import Wasp.AI.GenerateNewProject.Common.Prompts (appDescriptionBlock)
import qualified Wasp.AI.GenerateNewProject.Common.Prompts as Prompts
import Wasp.AI.GenerateNewProject.Operation (actionDocPrompt, queryDocPrompt)
import Wasp.AI.OpenAI.ChatGPT (ChatMessage (..), ChatRole (..))
fixOperationsJsFile :: NewProjectDetails -> FilePath -> FilePath -> CodeAgent ()
fixOperationsJsFile newProjectDetails waspFilePath opJsFilePath = do
currentWaspFileContent <- fromMaybe (error "couldn't find wasp file") <$> getFile waspFilePath
currentOpJsFileContent <- fromMaybe (error "couldn't find operation js file to fix") <$> getFile opJsFilePath
-- TODO: Would be great if we could compile it, check for compiler errors and then also provide those.
-- For that however, we would likely need the whole Wasp file generated on the disk,
-- with npm dependencies installed, so we skipped it for now.
fixedOpJsFile <-
queryChatGPTForJSON
defaultChatGPTParams
[ ChatMessage {role = System, content = Prompts.systemPrompt},
ChatMessage {role = User, content = fixOpJsFilePrompt currentWaspFileContent currentOpJsFileContent}
]
writeToFile opJsFilePath (const $ opJsFileContent fixedOpJsFile)
where
fixOpJsFilePrompt currentWaspFileContent currentOpJsFileContent =
[trimming|
${basicWaspLangInfoPrompt}
${queryDocPrompt}
${actionDocPrompt}
===============
We are together building a new Wasp app (description at the end of prompt).
Here is a wasp file that we generated together so far:
```wasp
${currentWaspFileContent}
```
Here is a NodeJS file (${opJsFilePathText}) containing some Wasp Operations
(Queries and Actions) that we generated together earlier:
```js
${currentOpJsFileContent}
```
===============
The NodeJS file with operations likely has some mistakes: let's fix it!
Some common mistakes to look for:
- "TODO" comments or "..." that should be replaced with actual implementation.
Fix these by replacing them with actual implementation.
- Duplicate imports. If there are any, make sure to remove them.
- Redundant imports of prisma client or of prisma entities. Those imports are not needed -> remove them!
- There might be some invalid JS syntax -> fix it if there is any.
- If there is some obvious refactoring that could improve code quality, go for it.
With this in mind, generate a new, fixed NodeJS file with operations (${opJsFilePathText}).
Do actual fixes, don't leave comments with "TODO"!
Please respond ONLY with a valid JSON of the format { opJsFileContent: string }.
There should be no other text in your response. Don't wrap content with the "```" code delimiters.
${appDescriptionBlockText}
|]
appDescriptionBlockText = appDescriptionBlock newProjectDetails
basicWaspLangInfoPrompt = Prompts.basicWaspLangInfo
opJsFilePathText = T.pack opJsFilePath
data OperationsJsFile = OperationsJsFile
{ opJsFileContent :: Text
}
deriving (Generic, Show)
instance FromJSON OperationsJsFile
instance ToJSON OperationsJsFile

View File

@ -144,6 +144,7 @@ library
Wasp.AI.GenerateNewProject.Common.Prompts
Wasp.AI.GenerateNewProject.Entity
Wasp.AI.GenerateNewProject.Operation
Wasp.AI.GenerateNewProject.OperationsJsFile
Wasp.AI.GenerateNewProject.Page
Wasp.AI.GenerateNewProject.Plan
Wasp.AI.GenerateNewProject.Skeleton