mirror of
https://github.com/wasp-lang/wasp.git
synced 2024-12-27 19:14:52 +03:00
Actions are now generated.
This commit is contained in:
parent
cbb3962508
commit
9ffc115fcd
@ -56,6 +56,7 @@ getFile path = gets $ H.lookup path . _files
|
||||
getAllFiles :: CodeAgent [(FilePath, Text)]
|
||||
getAllFiles = gets $ H.toList . _files
|
||||
|
||||
-- TODO: Make it so that if ChatGPT replies with being too busy, we try again.
|
||||
queryChatGPT :: ChatGPTParams -> [ChatMessage] -> CodeAgent Text
|
||||
queryChatGPT params messages = do
|
||||
key <- asks _openAIApiKey
|
||||
|
@ -10,7 +10,9 @@ import Data.Text (Text)
|
||||
import qualified Data.Text as T
|
||||
import NeatInterpolation (trimming)
|
||||
import Wasp.Cli.Command.AI.CodeAgent (CodeAgent, writeToLog)
|
||||
import Wasp.Cli.Command.AI.GenerateNewProject.Action (Action, generateAndWriteAction)
|
||||
import Wasp.Cli.Command.AI.GenerateNewProject.Common (NewProjectDetails (..))
|
||||
import Wasp.Cli.Command.AI.GenerateNewProject.Entity (writeEntitiesToWaspFile)
|
||||
import Wasp.Cli.Command.AI.GenerateNewProject.Plan (generatePlan)
|
||||
import qualified Wasp.Cli.Command.AI.GenerateNewProject.Plan as Plan
|
||||
import Wasp.Cli.Command.AI.GenerateNewProject.Skeleton (generateAndWriteProjectSkeleton)
|
||||
@ -25,18 +27,18 @@ import Wasp.Cli.Command.AI.GenerateNewProject.Skeleton (generateAndWriteProjectS
|
||||
-- and also contain description of what happened (or maybe that is separate message).
|
||||
generateNewProject :: NewProjectDetails -> CodeAgent ()
|
||||
generateNewProject newProjectDetails = do
|
||||
waspFilePath <- generateAndWriteProjectSkeleton newProjectDetails
|
||||
(waspFilePath, planRules) <- generateAndWriteProjectSkeleton newProjectDetails
|
||||
writeToLog "Generated project skeleton."
|
||||
|
||||
writeToLog "Generating plan..."
|
||||
plan <- generatePlan newProjectDetails
|
||||
plan <- generatePlan newProjectDetails planRules
|
||||
writeToLog $ "Plan generated!\n" <> summarizePlan plan
|
||||
|
||||
writeEntitiesToWaspFile waspFilePath (Plan.entities plan)
|
||||
writeToLog "Added entities to wasp file."
|
||||
|
||||
writeToLog "Generating actions..."
|
||||
actions <- forM (Plan.actions plan) $ generateAndWriteAction waspFilePath plan
|
||||
actions <- forM (Plan.actions plan) $ generateAndWriteAction newProjectDetails waspFilePath plan
|
||||
|
||||
writeToLog "Generating queries..."
|
||||
queries <- forM (Plan.queries plan) $ generateAndWriteQuery waspFilePath plan
|
||||
@ -70,16 +72,9 @@ generateNewProject newProjectDetails = do
|
||||
showT :: Show a => a -> Text
|
||||
showT = T.pack . show
|
||||
|
||||
generateAndWriteAction waspFilePath plan actionPlan = do
|
||||
action <- generateAction newProjectDetails (Plan.entities plan) actionPlan
|
||||
writeActionToFile action
|
||||
writeActionToWaspFile waspFilePath action
|
||||
writeToLog $ "Generated action: " <> T.pack (Plan.actionName actionPlan)
|
||||
return action
|
||||
|
||||
generateAndWriteQuery waspFilePath plan queryPlan = do
|
||||
query <- generateQuery newProjectDetails (Plan.entities plan) queryPlan
|
||||
writeQueryToFile query
|
||||
writeQueryToFile query -- TODO: Do we need to convert @server/ from path into src/server/?
|
||||
writeQueryToWaspFile waspFilePath query
|
||||
writeToLog $ "Generated query: " <> T.pack (Plan.queryName queryPlan)
|
||||
return query
|
||||
@ -91,26 +86,6 @@ generateNewProject newProjectDetails = do
|
||||
writeToLog $ "Generated page: " <> T.pack (Plan.pageName pagePlan)
|
||||
return page
|
||||
|
||||
writeEntitiesToWaspFile :: FilePath -> [Plan.Entity] -> CodeAgent ()
|
||||
writeEntitiesToWaspFile waspFilePath entities = do
|
||||
-- TODO: assemble code for each entity and write it to wasp file.
|
||||
undefined
|
||||
|
||||
generateAction :: NewProjectDetails -> [Plan.Entity] -> Plan.Action -> CodeAgent Action
|
||||
generateAction = undefined
|
||||
|
||||
writeActionToFile :: Action -> CodeAgent ()
|
||||
writeActionToFile = undefined
|
||||
|
||||
writeActionToWaspFile :: FilePath -> Action -> CodeAgent ()
|
||||
writeActionToWaspFile waspFilePath action = undefined
|
||||
|
||||
data Action = Action
|
||||
{ _actionWaspDecl :: String,
|
||||
_actionJsImpl :: String,
|
||||
_actionPlan :: Plan.Action
|
||||
}
|
||||
|
||||
generateQuery :: NewProjectDetails -> [Plan.Entity] -> Plan.Query -> CodeAgent Query
|
||||
generateQuery = undefined
|
||||
|
||||
|
149
waspc/cli/src/Wasp/Cli/Command/AI/GenerateNewProject/Action.hs
Normal file
149
waspc/cli/src/Wasp/Cli/Command/AI/GenerateNewProject/Action.hs
Normal file
@ -0,0 +1,149 @@
|
||||
{-# LANGUAGE DeriveGeneric #-}
|
||||
|
||||
module Wasp.Cli.Command.AI.GenerateNewProject.Action
|
||||
( generateAndWriteAction,
|
||||
Action,
|
||||
)
|
||||
where
|
||||
|
||||
import Data.Aeson (FromJSON)
|
||||
import Data.List (isPrefixOf)
|
||||
import Data.Maybe (fromMaybe)
|
||||
import qualified Data.Text as T
|
||||
import GHC.Generics (Generic)
|
||||
import NeatInterpolation (trimming)
|
||||
import Wasp.Cli.Command.AI.CodeAgent (CodeAgent, writeToFile, writeToLog)
|
||||
import Wasp.Cli.Command.AI.GenerateNewProject.Common
|
||||
( NewProjectDetails (..),
|
||||
defaultChatGPTParams,
|
||||
queryChatGPTForJSON,
|
||||
)
|
||||
import Wasp.Cli.Command.AI.GenerateNewProject.Common.Prompts (appDescriptionStartMarkerLine)
|
||||
import qualified Wasp.Cli.Command.AI.GenerateNewProject.Common.Prompts as Prompts
|
||||
import Wasp.Cli.Command.AI.GenerateNewProject.Entity (entityPlanToWaspDecl)
|
||||
import Wasp.Cli.Command.AI.GenerateNewProject.Plan (Plan)
|
||||
import qualified Wasp.Cli.Command.AI.GenerateNewProject.Plan as Plan
|
||||
import Wasp.OpenAI.ChatGPT (ChatMessage (..), ChatRole (..))
|
||||
|
||||
generateAndWriteAction :: NewProjectDetails -> FilePath -> Plan -> Plan.Action -> CodeAgent Action
|
||||
generateAndWriteAction newProjectDetails waspFilePath plan actPlan = do
|
||||
action <- generateAction newProjectDetails (Plan.entities plan) actPlan
|
||||
writeActionToJsFile action
|
||||
writeActionToWaspFile waspFilePath action
|
||||
writeToLog $ "Generated action: " <> T.pack (Plan.actionName actPlan)
|
||||
return action
|
||||
|
||||
generateAction :: NewProjectDetails -> [Plan.Entity] -> Plan.Action -> CodeAgent Action
|
||||
generateAction newProjectDetails entityPlans plan = do
|
||||
impl <- queryChatGPTForJSON defaultChatGPTParams chatMessages
|
||||
return Action {actionImpl = impl, actionPlan = plan}
|
||||
where
|
||||
chatMessages =
|
||||
[ ChatMessage {role = System, content = Prompts.systemPrompt},
|
||||
ChatMessage {role = User, content = planPrompt}
|
||||
]
|
||||
appName = T.pack $ _projectAppName newProjectDetails
|
||||
appDesc = T.pack $ _projectDescription newProjectDetails
|
||||
basicWaspLangInfoPrompt = Prompts.basicWaspLangInfo
|
||||
actionName = T.pack $ Plan.actionName plan
|
||||
actionFnPath = T.pack $ Plan.actionFnPath plan
|
||||
actionDesc = T.pack $ Plan.actionDesc plan
|
||||
entityDecls = T.intercalate "\n\n" $ entityPlanToWaspDecl <$> entityPlans
|
||||
planPrompt =
|
||||
[trimming|
|
||||
${basicWaspLangInfoPrompt}
|
||||
|
||||
${actionDocPrompt}
|
||||
|
||||
We are implementing a Wasp app (check bottom for description).
|
||||
|
||||
This app has following entities:
|
||||
${entityDecls}
|
||||
|
||||
Let's now implement the following Wasp action:
|
||||
- name: ${actionName}
|
||||
- fn: ${actionFnPath}
|
||||
- description: ${actionDesc}
|
||||
|
||||
Please, respond ONLY with a valid JSON, of following format:
|
||||
{ "actionWaspDecl": "action ${actionName} {\n ... }",
|
||||
"actionJsImpl": "export const {$actionName} ... ",
|
||||
"actionJsImports": "import foo from \"bar.js\"\n ...""
|
||||
}
|
||||
"waspDeclaration" and "jsImplementation" are required, "jsImports" you can skip if none are needed.
|
||||
There should be no other text in the response.
|
||||
|
||||
${appDescriptionStartMarkerLine}
|
||||
|
||||
App name: ${appName}
|
||||
${appDesc}
|
||||
|]
|
||||
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) }
|
||||
|
||||
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.
|
||||
|]
|
||||
|
||||
writeActionToJsFile :: Action -> CodeAgent ()
|
||||
writeActionToJsFile action =
|
||||
-- TODO: An issue we have here is that if other action 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.
|
||||
-- Maybe best to read and pass previous imports (we would have to do that above somewhere).
|
||||
-- Or even the whole file? Hmmmmm.
|
||||
writeToFile path $
|
||||
((jsImports <> "\n") <>) . (<> "\n\n" <> jsImpl) . (fromMaybe "")
|
||||
where
|
||||
path = resolvePath $ Plan.actionFnPath $ actionPlan action
|
||||
jsImpl = T.pack $ actionJsImpl $ actionImpl action
|
||||
jsImports = T.pack $ actionJsImports $ actionImpl action
|
||||
resolvePath p | "@server/" `isPrefixOf` p = "src/server/" <> drop (length ("@server/" :: String)) p
|
||||
resolvePath _ = error "path incorrectly formatted, should start with @server."
|
||||
|
||||
writeActionToWaspFile :: FilePath -> Action -> CodeAgent ()
|
||||
writeActionToWaspFile waspFilePath action =
|
||||
writeToFile waspFilePath $
|
||||
(<> "\n\n" <> waspDeclCode) . fromMaybe (error "wasp file shouldn't be empty")
|
||||
where
|
||||
waspDeclCode = T.pack $ actionWaspDecl $ actionImpl action
|
||||
|
||||
data Action = Action
|
||||
{ actionImpl :: ActionImpl,
|
||||
actionPlan :: Plan.Action
|
||||
}
|
||||
deriving (Show)
|
||||
|
||||
data ActionImpl = ActionImpl
|
||||
{ actionWaspDecl :: String,
|
||||
actionJsImpl :: String,
|
||||
actionJsImports :: String
|
||||
}
|
||||
deriving (Generic, Show)
|
||||
|
||||
instance FromJSON ActionImpl
|
@ -2,10 +2,19 @@ module Wasp.Cli.Command.AI.GenerateNewProject.Common
|
||||
( NewProjectDetails (..),
|
||||
File,
|
||||
AuthProvider (..),
|
||||
queryChatGPTForJSON,
|
||||
defaultChatGPTParams,
|
||||
writeToWaspFileEnd,
|
||||
)
|
||||
where
|
||||
|
||||
import Data.Aeson (FromJSON)
|
||||
import qualified Data.Aeson as Aeson
|
||||
import Data.Maybe (fromMaybe)
|
||||
import Data.Text (Text)
|
||||
import Wasp.Cli.Command.AI.CodeAgent (CodeAgent, queryChatGPT, writeToFile)
|
||||
import Wasp.OpenAI.ChatGPT (ChatGPTParams (..), ChatMessage (..), Model (GPT_3_5_turbo_16k))
|
||||
import Wasp.Util (naiveTrimJSON, textToLazyBS)
|
||||
|
||||
data NewProjectDetails = NewProjectDetails
|
||||
{ _projectAppName :: !String,
|
||||
@ -13,8 +22,29 @@ data NewProjectDetails = NewProjectDetails
|
||||
_projectAuth :: !AuthProvider
|
||||
}
|
||||
|
||||
-- TODO: Make these relative to WaspProjectDir?
|
||||
-- TODO: Make these relative to WaspProjectDir, via StrongPath?
|
||||
type File = (FilePath, Text)
|
||||
|
||||
-- TODO: Support more methods.
|
||||
data AuthProvider = UsernameAndPassword
|
||||
|
||||
queryChatGPTForJSON :: FromJSON a => ChatGPTParams -> [ChatMessage] -> CodeAgent a
|
||||
queryChatGPTForJSON chatGPTParams chatMessages = do
|
||||
responseJSONText <- naiveTrimJSON <$> queryChatGPT chatGPTParams chatMessages
|
||||
case Aeson.eitherDecode $ textToLazyBS responseJSONText of
|
||||
Right plan -> return plan
|
||||
Left _errMsg ->
|
||||
-- TODO: Handle this better.
|
||||
-- Try sending response back to chatGPT and ask it to fix it -> hey it is not valid JSON, fix it.
|
||||
-- Write to log, to let user know.
|
||||
error "Failed to parse plan"
|
||||
|
||||
-- TODO: Test more for the optimal temperature (possibly higher).
|
||||
-- TODO: Should we make sure we have max_tokens set to high enough?
|
||||
defaultChatGPTParams :: ChatGPTParams
|
||||
defaultChatGPTParams = ChatGPTParams {_model = GPT_3_5_turbo_16k, _temperature = Just 1.0}
|
||||
|
||||
writeToWaspFileEnd :: FilePath -> Text -> CodeAgent ()
|
||||
writeToWaspFileEnd waspFilePath text = do
|
||||
writeToFile waspFilePath $
|
||||
(<> "\n" <> text) . fromMaybe (error "wasp file shouldn't be empty")
|
||||
|
@ -0,0 +1,28 @@
|
||||
module Wasp.Cli.Command.AI.GenerateNewProject.Entity
|
||||
( writeEntitiesToWaspFile,
|
||||
entityPlanToWaspDecl,
|
||||
)
|
||||
where
|
||||
|
||||
import Data.Text (Text)
|
||||
import qualified Data.Text as T
|
||||
import NeatInterpolation (trimming)
|
||||
import Wasp.Cli.Command.AI.CodeAgent (CodeAgent)
|
||||
import Wasp.Cli.Command.AI.GenerateNewProject.Common (writeToWaspFileEnd)
|
||||
import qualified Wasp.Cli.Command.AI.GenerateNewProject.Plan as Plan
|
||||
|
||||
writeEntitiesToWaspFile :: FilePath -> [Plan.Entity] -> CodeAgent ()
|
||||
writeEntitiesToWaspFile waspFilePath entityPlans = do
|
||||
writeToWaspFileEnd waspFilePath $ "\n" <> entitiesCode
|
||||
where
|
||||
entitiesCode = T.intercalate "\n\n" $ entityPlanToWaspDecl <$> entityPlans
|
||||
|
||||
entityPlanToWaspDecl :: Plan.Entity -> Text
|
||||
entityPlanToWaspDecl plan =
|
||||
let name = T.pack $ Plan.entityName plan
|
||||
pslBody = T.pack $ Plan.entityBodyPsl plan
|
||||
in [trimming|
|
||||
entity ${name} {=psl
|
||||
${pslBody}
|
||||
psl=}
|
||||
|]
|
@ -7,38 +7,32 @@ module Wasp.Cli.Command.AI.GenerateNewProject.Plan
|
||||
Action (..),
|
||||
Page (..),
|
||||
generatePlan,
|
||||
PlanRule,
|
||||
)
|
||||
where
|
||||
|
||||
import Data.Aeson (FromJSON)
|
||||
import qualified Data.Aeson as Aeson
|
||||
import Data.ByteString.Lazy.UTF8 (ByteString)
|
||||
import Data.Text (Text)
|
||||
import qualified Data.Text as T
|
||||
import qualified Data.Text.Lazy as TL
|
||||
import qualified Data.Text.Lazy.Encoding as TLE
|
||||
import GHC.Generics (Generic)
|
||||
import NeatInterpolation (trimming)
|
||||
import Wasp.Cli.Command.AI.CodeAgent (CodeAgent, queryChatGPT)
|
||||
import Wasp.Cli.Command.AI.GenerateNewProject.Common (NewProjectDetails (_projectAppName, _projectDescription))
|
||||
import Wasp.Cli.Command.AI.CodeAgent (CodeAgent)
|
||||
import Wasp.Cli.Command.AI.GenerateNewProject.Common
|
||||
( AuthProvider (UsernameAndPassword),
|
||||
NewProjectDetails (..),
|
||||
defaultChatGPTParams,
|
||||
queryChatGPTForJSON,
|
||||
)
|
||||
import Wasp.Cli.Command.AI.GenerateNewProject.Common.Prompts (appDescriptionStartMarkerLine)
|
||||
import qualified Wasp.Cli.Command.AI.GenerateNewProject.Common.Prompts as Prompts
|
||||
import Wasp.OpenAI.ChatGPT (ChatGPTParams (..), ChatMessage (..), ChatRole (..), Model (..))
|
||||
import Wasp.OpenAI.ChatGPT (ChatMessage (..), ChatRole (..))
|
||||
|
||||
generatePlan :: NewProjectDetails -> CodeAgent Plan
|
||||
generatePlan newProjectDetails = do
|
||||
responseJSONText <- naiveTrimJSON <$> queryChatGPT chatGPTParams chatMessages
|
||||
case Aeson.eitherDecode $ textToLazyBS responseJSONText of
|
||||
Right plan -> return plan
|
||||
Left _errMsg ->
|
||||
-- TODO: Handle this better.
|
||||
-- Try sending response back to chatGPT and ask it to fix it -> hey it is not valid JSON, fix it.
|
||||
-- Write to log, to let user know.
|
||||
error "Failed to parse plan"
|
||||
-- | Additional rule to follow while generating plan.
|
||||
type PlanRule = String
|
||||
|
||||
generatePlan :: NewProjectDetails -> [PlanRule] -> CodeAgent Plan
|
||||
generatePlan newProjectDetails planRules = do
|
||||
queryChatGPTForJSON defaultChatGPTParams chatMessages
|
||||
where
|
||||
-- TODO: Try configuring temperature.
|
||||
-- TODO: Make sure we have max_tokens set to high enough.
|
||||
chatGPTParams = ChatGPTParams {_model = GPT_3_5_turbo_16k, _temperature = Just 1.0}
|
||||
chatMessages =
|
||||
[ ChatMessage {role = System, content = Prompts.systemPrompt},
|
||||
ChatMessage {role = User, content = planPrompt}
|
||||
@ -47,6 +41,7 @@ generatePlan newProjectDetails = do
|
||||
appDesc = T.pack $ _projectDescription newProjectDetails
|
||||
basicWaspLangInfoPrompt = Prompts.basicWaspLangInfo
|
||||
waspFileExamplePrompt = Prompts.waspFileExample
|
||||
rulesText = T.pack . unlines $ "Rules:" : map (" - " ++) planRules
|
||||
planPrompt =
|
||||
[trimming|
|
||||
${basicWaspLangInfoPrompt}
|
||||
@ -80,9 +75,10 @@ generatePlan newProjectDetails = do
|
||||
}]
|
||||
}
|
||||
|
||||
Make sure to generate at least one page with routePath "/".
|
||||
${rulesText}
|
||||
|
||||
We will later use this plan to implement all of these parts of Wasp app.
|
||||
We will later use this plan to implement all of these parts of Wasp app,
|
||||
so make sure descriptions are detailed enough to guide implementing them.
|
||||
|
||||
Please, respond ONLY with a valid JSON that is a plan.
|
||||
There should be no other text in the response.
|
||||
@ -171,15 +167,3 @@ data Page = Page
|
||||
deriving (Generic, Show)
|
||||
|
||||
instance FromJSON Page
|
||||
|
||||
-- | Given a text containing a single instance of JSON and some text around it but no { or }, trim
|
||||
-- it until just JSON is left.
|
||||
-- Examples
|
||||
-- naiveTrimJson "some text { \"a\": 5 } yay" == "{\"a\": 5 }"
|
||||
-- naiveTrimJson "some {text} { \"a\": 5 }" -> won't work correctly.
|
||||
naiveTrimJSON :: Text -> Text
|
||||
naiveTrimJSON textContainingJson =
|
||||
T.reverse . T.dropWhile (/= '}') . T.reverse . T.dropWhile (/= '{') $ textContainingJson
|
||||
|
||||
textToLazyBS :: Text -> ByteString
|
||||
textToLazyBS = TLE.encodeUtf8 . TL.fromStrict
|
||||
|
@ -10,15 +10,16 @@ import NeatInterpolation (trimming)
|
||||
import qualified StrongPath as SP
|
||||
import Wasp.Cli.Command.AI.CodeAgent (CodeAgent, writeNewFile)
|
||||
import Wasp.Cli.Command.AI.GenerateNewProject.Common (AuthProvider (..), File, NewProjectDetails (..))
|
||||
import Wasp.Cli.Command.AI.GenerateNewProject.Plan (PlanRule)
|
||||
import Wasp.Cli.Command.CreateNewProject (readCoreWaspProjectFiles)
|
||||
import qualified Wasp.Version
|
||||
|
||||
generateAndWriteProjectSkeleton :: NewProjectDetails -> CodeAgent FilePath
|
||||
generateAndWriteProjectSkeleton :: NewProjectDetails -> CodeAgent (FilePath, [PlanRule])
|
||||
generateAndWriteProjectSkeleton newProjectDetails = do
|
||||
coreFiles <- liftIO $ map (first SP.fromRelFile) <$> readCoreWaspProjectFiles
|
||||
mapM_ writeNewFile coreFiles
|
||||
|
||||
let waspFile@(waspFilePath, _) = generateBaseWaspFile newProjectDetails
|
||||
let (waspFile@(waspFilePath, _), planRules) = generateBaseWaspFile newProjectDetails
|
||||
writeNewFile waspFile
|
||||
|
||||
case _projectAuth newProjectDetails of
|
||||
@ -28,19 +29,22 @@ generateAndWriteProjectSkeleton newProjectDetails = do
|
||||
|
||||
writeNewFile generateDotEnvServerFile
|
||||
|
||||
return waspFilePath
|
||||
return (waspFilePath, planRules)
|
||||
|
||||
generateBaseWaspFile :: NewProjectDetails -> File
|
||||
generateBaseWaspFile newProjectDetails = (path, content)
|
||||
generateBaseWaspFile :: NewProjectDetails -> (File, [PlanRule])
|
||||
generateBaseWaspFile newProjectDetails = ((path, content), planRules)
|
||||
where
|
||||
path = "main.wasp"
|
||||
appName = T.pack $ _projectAppName newProjectDetails
|
||||
appTitle = appName
|
||||
waspVersion = T.pack $ show Wasp.Version.waspVersion
|
||||
appAuth = case _projectAuth newProjectDetails of
|
||||
-- NOTE: We assume here that there will be a page with route "/".
|
||||
(appAuth, authPlanRules) = case _projectAuth newProjectDetails of
|
||||
-- NOTE: We assume two things here:
|
||||
-- - that there will be a page with route "/".
|
||||
-- - that there will be a User entity with 'id', 'username' and 'password'.
|
||||
-- We later that those will be added to Plan.
|
||||
UsernameAndPassword ->
|
||||
[trimming|
|
||||
( [trimming|
|
||||
auth: {
|
||||
userEntity: User,
|
||||
methods: {
|
||||
@ -49,7 +53,12 @@ generateBaseWaspFile newProjectDetails = (path, content)
|
||||
onAuthFailedRedirectTo: "/login",
|
||||
onAuthSucceededRedirectTo: "/"
|
||||
},
|
||||
|]
|
||||
|],
|
||||
[ "Generate a User entity, with at least the following fields: 'id', 'username', 'password'.",
|
||||
"There should be a page with route path \"/\"."
|
||||
]
|
||||
)
|
||||
planRules = authPlanRules <> ["Don't generate the Login or Signup page."]
|
||||
content =
|
||||
[trimming|
|
||||
app ${appName} {
|
||||
@ -60,12 +69,6 @@ generateBaseWaspFile newProjectDetails = (path, content)
|
||||
${appAuth}
|
||||
}
|
||||
|
||||
entity User {=psl
|
||||
id Int @id @default(autoincrement())
|
||||
username String @unique
|
||||
password String
|
||||
psl=}
|
||||
|
||||
route LoginRoute { path: "/login", to: LoginPage }
|
||||
page LoginPage {
|
||||
component: import Login from "@client/Login.jsx"
|
||||
|
@ -33,6 +33,8 @@ module Wasp.Util
|
||||
kebabToCamelCase,
|
||||
maybeToEither,
|
||||
whenM,
|
||||
naiveTrimJSON,
|
||||
textToLazyBS,
|
||||
)
|
||||
where
|
||||
|
||||
@ -41,6 +43,7 @@ import Control.Monad (unless, when)
|
||||
import qualified Crypto.Hash.SHA256 as SHA256
|
||||
import qualified Data.Aeson as Aeson
|
||||
import qualified Data.ByteString as B
|
||||
import qualified Data.ByteString.Lazy as BSL
|
||||
import qualified Data.ByteString.UTF8 as BSU
|
||||
import Data.Char (isSpace, isUpper, toLower, toUpper)
|
||||
import qualified Data.HashMap.Strict as M
|
||||
@ -48,8 +51,11 @@ import Data.List (intercalate)
|
||||
import Data.List.Split (splitOn, wordsBy)
|
||||
import Data.Maybe (fromMaybe)
|
||||
import Data.Text (Text)
|
||||
import qualified Data.Text as T
|
||||
import qualified Data.Text as Text
|
||||
import qualified Data.Text.Encoding as TextEncoding
|
||||
import qualified Data.Text.Lazy as TL
|
||||
import qualified Data.Text.Lazy.Encoding as TLE
|
||||
import StrongPath (File, Path')
|
||||
import qualified StrongPath as SP
|
||||
import Text.Printf (printf)
|
||||
@ -230,3 +236,15 @@ maybeToEither leftValue = maybe (Left leftValue) Right
|
||||
|
||||
getEnvVarDefinition :: (String, String) -> String
|
||||
getEnvVarDefinition (name, value) = concat [name, "=", value]
|
||||
|
||||
-- | Given a text containing a single instance of JSON and some text around it but no { or }, trim
|
||||
-- it until just JSON is left.
|
||||
-- Examples
|
||||
-- naiveTrimJson "some text { \"a\": 5 } yay" == "{\"a\": 5 }"
|
||||
-- naiveTrimJson "some {text} { \"a\": 5 }" -> won't work correctly.
|
||||
naiveTrimJSON :: Text -> Text
|
||||
naiveTrimJSON textContainingJson =
|
||||
T.reverse . T.dropWhile (/= '}') . T.reverse . T.dropWhile (/= '{') $ textContainingJson
|
||||
|
||||
textToLazyBS :: Text -> BSL.ByteString
|
||||
textToLazyBS = TLE.encodeUtf8 . TL.fromStrict
|
||||
|
@ -372,8 +372,10 @@ library cli-lib
|
||||
Wasp.Cli.FileSystem
|
||||
Wasp.Cli.Command.AI.CodeAgent
|
||||
Wasp.Cli.Command.AI.GenerateNewProject
|
||||
Wasp.Cli.Command.AI.GenerateNewProject.Action
|
||||
Wasp.Cli.Command.AI.GenerateNewProject.Common
|
||||
Wasp.Cli.Command.AI.GenerateNewProject.Common.Prompts
|
||||
Wasp.Cli.Command.AI.GenerateNewProject.Entity
|
||||
Wasp.Cli.Command.AI.GenerateNewProject.Plan
|
||||
Wasp.Cli.Command.AI.GenerateNewProject.Skeleton
|
||||
Wasp.Cli.Command.AI.New
|
||||
|
Loading…
Reference in New Issue
Block a user