diff --git a/waspc/src/Wasp/AI/GenerateNewProject.hs b/waspc/src/Wasp/AI/GenerateNewProject.hs index 1ded44866..832cb2930 100644 --- a/waspc/src/Wasp/AI/GenerateNewProject.hs +++ b/waspc/src/Wasp/AI/GenerateNewProject.hs @@ -26,7 +26,7 @@ import Wasp.AI.GenerateNewProject.Operation ) import Wasp.AI.GenerateNewProject.OperationsJsFile (fixOperationsJsFile) import Wasp.AI.GenerateNewProject.Page (generateAndWritePage, getPageComponentPath) -import Wasp.AI.GenerateNewProject.PageComponentFile (fixImportsInPageComponentFile, fixPageComponent) +import Wasp.AI.GenerateNewProject.PageComponentFile (fixPageComponent) import Wasp.AI.GenerateNewProject.Plan (generatePlan) import qualified Wasp.AI.GenerateNewProject.Plan as Plan import Wasp.AI.GenerateNewProject.Skeleton (generateAndWriteProjectSkeletonAndPresetFiles) @@ -99,12 +99,6 @@ generateNewProject newProjectDetails waspProjectSkeletonFiles = do writeToLog $ "Fixed '" <> fromString pageFp <> "' page." writeToLog "Pages fixed." - writeToLogFixing "import mistakes in pages..." - forM_ (getPageComponentPath <$> pages) $ \pageFp -> do - fixImportsInPageComponentFile pageFp queries actions - writeToLog $ "Fixed '" <> fromString pageFp <> "' page." - writeToLog "Imports in pages fixed." - (promptTokensUsed, completionTokensUsed) <- getTotalTokensUsage writeToLog $ "\nTotal tokens usage: " diff --git a/waspc/src/Wasp/AI/GenerateNewProject/Common/Prompts.hs b/waspc/src/Wasp/AI/GenerateNewProject/Common/Prompts.hs index 8e68b599d..5160a9315 100644 --- a/waspc/src/Wasp/AI/GenerateNewProject/Common/Prompts.hs +++ b/waspc/src/Wasp/AI/GenerateNewProject/Common/Prompts.hs @@ -74,7 +74,7 @@ waspFileExample = onAuthFailedRedirectTo: "/login" }, client: { - rootComponent: import { Layout } from "@client/Layout.jsx", + rootComponent: import { Layout } from "@src/Layout.jsx", }, db: { prisma: { @@ -85,24 +85,22 @@ waspFileExample = route SignupRoute { path: "/signup", to: SignupPage } page SignupPage { - component: import Signup from "@client/pages/auth/Signup.jsx" + component: import Signup from "@src/pages/auth/Signup.jsx" } route LoginRoute { path: "/login", to: LoginPage } page LoginPage { - component: import Login from "@client/pages/auth/Login.jsx" + component: import Login from "@src/pages/auth/Login.jsx" } route DashboardRoute { path: "/", to: DashboardPage } page DashboardPage { authRequired: true, - component: import Dashboard from "@client/pages/Dashboard.jsx" + component: import Dashboard from "@src/pages/Dashboard.jsx" } entity User {=psl id Int @id @default(autoincrement()) - username String @unique - password String tasks Task[] psl=} @@ -115,22 +113,22 @@ waspFileExample = psl=} query getUser { - fn: import { getUser } from "@server/queries.js", + fn: import { getUser } from "@src/queries.js", entities: [User] // Entities that this query operates on. } query getTasks { - fn: import { getTasks } from "@server/queries.js", + fn: import { getTasks } from "@src/queries.js", entities: [Task] } action createTask { - fn: import { createTask } from "@server/actions.js", + fn: import { createTask } from "@src/actions.js", entities: [Task] } action updateTask { - fn: import { updateTask } from "@server/actions.js", + fn: import { updateTask } from "@src/actions.js", entities: [Task] } ``` diff --git a/waspc/src/Wasp/AI/GenerateNewProject/Operation.hs b/waspc/src/Wasp/AI/GenerateNewProject/Operation.hs index d135e2c0d..fde71e88f 100644 --- a/waspc/src/Wasp/AI/GenerateNewProject/Operation.hs +++ b/waspc/src/Wasp/AI/GenerateNewProject/Operation.hs @@ -97,7 +97,7 @@ generateOperation operationType newProjectDetails entityPlans operationPlan = do Example of response: { "opWaspDecl": "${operationTypeText} ${operationName} {\n fn: import { ${operationName} } from \"${operationFnPath}\",\n entities: [Task]\n}", "opJsImpl": "export const {$operationName} = async (args, context) => { ... }", - "opJsImports": "import HttpError from '@wasp/core/HttpError.js'" + "opJsImports": "import { HttpError } from 'wasp/server'" } "opWaspDecl" and "opJsImpl" are required, "opJsImports" you can skip if none are needed. There should be no other text in the response, just valid JSON. @@ -108,7 +108,7 @@ generateOperation operationType newProjectDetails entityPlans operationPlan = do Instead, always write real implementation! - Don't import prisma client in the JS imports, it is not needed. - In wasp declaration (`opWaspDecl`), make sure to use ',' before `entities:`. - Also, make sure to use full import statement for `fn:`: `import { getTasks } from "@server/actions.js"`, + Also, make sure to use full import statement for `fn:`: `import { getTasks } from "@src/actions.js"`, don't provide just the file path. - In NodeJS implementation, you will typically want to check if user is authenticated, by doing `if (!context.user) { throw new HttpError(401) }` at the start of the operation. @@ -160,14 +160,14 @@ actionDocPrompt = - Wasp declaration: ```wasp action updateTask { - fn: import { updateTask } from "@server/taskActions.js", + fn: import { updateTask } from "@src/taskActions.js", entities: [Task] // Entities that action mutates. } ``` - NodeJS implementation (with imports): ```js - import HttpError from '@wasp/core/HttpError.js' + import { HttpError } from 'wasp/server' export const updateTask = async (args, context) => { if (!context.user) { throw new HttpError(401) }; // If user needs to be authenticated. @@ -189,14 +189,14 @@ actionDocPrompt = ```wasp action deleteList { - fn: import { deleteList } from "@server/actions.js", + fn: import { deleteList } from "@src/actions.js", entities: [List, Card] } ``` - NodeJS implementation (with imports): ```js - import HttpError from '@wasp/core/HttpError.js' + import { HttpError } from 'wasp/server' export const deleteList = async ({ listId }, context) => { if (!context.user) { throw new HttpError(401) }; @@ -232,14 +232,14 @@ queryDocPrompt = - Wasp declaration: ```wasp query fetchFilteredTasks { - fn: import { getFilteredTasks } from "@server/taskQueries.js", + fn: import { getFilteredTasks } from "@src/taskQueries.js", entities: [Task] // Entities that query uses. } ``` - NodeJS implementation (with imports): ```js - import HttpError from '@wasp/core/HttpError.js' + import { HttpError } from 'wasp/server' export const getFilteredTasks = async (args, context) => { if (!context.user) { throw new HttpError(401) }; // If user needs to be authenticated. @@ -259,29 +259,28 @@ queryDocPrompt = - Wasp declaration: ```wasp query getAuthor { - fn: import { getAuthor } from "@server/author/queries.js", + fn: import { getAuthor } from "@src/author/queries.js", entities: [Author] } ``` - NodeJS implementation (with imports): ```js - import HttpError from '@wasp/core/HttpError.js' + import { HttpError } from 'wasp/server' - export const getAuthor = async ({ username }, context) => { + export const getAuthor = async ({ id }, context) => { // Here we don't check if user is authenticated as this query is public. const author = await context.entities.Author.findUnique({ - where: { username }, + where: { id }, select: { - username: true, id: true, bio: true, profilePictureUrl: true } }); - if (!author) throw new HttpError(404, 'No author with username ' + username); + if (!author) throw new HttpError(404, 'No author with id ' + id); return author; } @@ -355,9 +354,8 @@ writeOperationToJsFile 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 <> "." + resolvePath p | "@src/" `isPrefixOf` p = drop (length ("@" :: String)) p + resolvePath _ = error "path incorrectly formatted, should start with @src/ ." writeOperationToWaspFile :: FilePath -> Operation -> CodeAgent () writeOperationToWaspFile waspFilePath operation = diff --git a/waspc/src/Wasp/AI/GenerateNewProject/Page.hs b/waspc/src/Wasp/AI/GenerateNewProject/Page.hs index ce6219d5e..b1f4adbfb 100644 --- a/waspc/src/Wasp/AI/GenerateNewProject/Page.hs +++ b/waspc/src/Wasp/AI/GenerateNewProject/Page.hs @@ -109,7 +109,7 @@ generatePage newProjectDetails entityPlans queries actions pPlan = do Example of such JSON: { - "pageWaspDecl": "route ExampleRoute { path: \"/\", to: ExamplePage }\npage ExamplePage {\n component: import ExamplePage from \"@client/ExamplePage.jsx\",\n authRequired: true\n}", + "pageWaspDecl": "route ExampleRoute { path: \"/\", to: ExamplePage }\npage ExamplePage {\n component: import ExamplePage from \"@src/ExamplePage.jsx\",\n authRequired: true\n}", "pageJsImpl": "JS imports + React component implementing the page.", } There should be no other text in the response. @@ -134,7 +134,7 @@ makePageDocPrompt = ```wasp route TasksRoute { path: "/", to: ExamplePage } page TasksPage { - component: import Tasks from "@client/Tasks.jsx", + component: import Tasks from "@src/Tasks.jsx", authRequired: true } ``` @@ -143,11 +143,13 @@ makePageDocPrompt = ```jsx import React, { useState } from 'react'; - import { useQuery } from '@wasp/queries'; // A thin wrapper around react-query's useQuery - import { useAction } from '@wasp/actions'; // A thin wrapper around react-query's useMutation - import getTasks from '@wasp/queries/getTasks'; - import createTask from '@wasp/actions/createTask'; - import toggleTask from '@wasp/actions/toggleTask'; + import { + useQuery, // A thin wrapper around react-query's useQuery + useAction, // A thin wrapper around react-query's useMutation + getTasks, // query + createTask, // action + toggleTask // action + } from 'wasp/client/operations'; const Tasks = () => { const { data: tasks, isLoading, error } = useQuery(getTasks); @@ -208,7 +210,7 @@ makePageDocPrompt = ```wasp route DashboardRoute { path: "/dashboard", to: DashboardPage } page DashboardPage { - component: import Dashboard from "@client/Dashboard.jsx", + component: import Dashboard from "@src/Dashboard.jsx", authRequired: true } ``` @@ -218,10 +220,7 @@ makePageDocPrompt = ```jsx import React from 'react'; import { Link } from 'react-router-dom'; - import { useQuery } from '@wasp/queries'; - import { useAction } from '@wasp/actions'; - import getUsers from '@wasp/queries/getUsers'; - import deleteUser from '@wasp/actions/deleteUser'; + import { useQuery, useAction, getUsers, deleteUser } from 'wasp/client/operations'; const DashboardPage = () => { const { data: users, isLoading, error } = useQuery(getUsers); @@ -266,15 +265,11 @@ makePageDocPrompt = Here are the rules for importing actions and queries. - If a query is called "myQuery", its import MUST BE `import myQuery from '@wasp/queries/myQuery';`. - More generally, a query import MUST BE a default import, and name of the file is the same as name of the query. + If a query is called "myQuery", its import MUST BE `import { myQuery } from 'wasp/client/operations';`. The hook for wrapping queries is called `useQuery`. - Use a single import statement per query. - If an action is called "myAction", its import MUST BE `import myAction from '@wasp/actions/myAction';`. - More generally, an action import MUST BE a default import, and name of the file is the same as name of the action. + If an action is called "myAction", its import MUST BE `import { myAction } from 'wasp/client/operations';`. The hook for wrapping actions is called `useAction`. - Use a single import statement per action. Note: There is no `useMutation` hook in Wasp. |] @@ -303,9 +298,8 @@ getPageComponentPath :: Page -> String getPageComponentPath page = path where path = resolvePath $ Plan.componentPath $ pagePlan page - pathPrefix = "@client/" - resolvePath p | pathPrefix `isPrefixOf` p = "src/" <> drop (length ("@" :: String)) p - resolvePath _ = error "path incorrectly formatted, should start with " <> pathPrefix <> "." + resolvePath p | "@src/" `isPrefixOf` p = drop (length ("@" :: String)) p + resolvePath _ = error "path incorrectly formatted, should start with @src/." writePageToWaspFile :: FilePath -> Page -> CodeAgent () writePageToWaspFile waspFilePath page = diff --git a/waspc/src/Wasp/AI/GenerateNewProject/PageComponentFile.hs b/waspc/src/Wasp/AI/GenerateNewProject/PageComponentFile.hs index 7b95a4480..9fdc3baae 100644 --- a/waspc/src/Wasp/AI/GenerateNewProject/PageComponentFile.hs +++ b/waspc/src/Wasp/AI/GenerateNewProject/PageComponentFile.hs @@ -2,27 +2,15 @@ module Wasp.AI.GenerateNewProject.PageComponentFile ( fixPageComponent, - fixImportsInPageComponentFile, - -- NOTE: Exports below are exported only for testing! - getPageComponentFileContentWithFixedImports, - partitionComponentFileByImports, - getImportedNamesFromImport, - getAllPossibleWaspJsClientImports, ) where -import Control.Arrow (first) import Data.Aeson (FromJSON) -import Data.List (intercalate, isInfixOf, isPrefixOf, partition, stripPrefix) -import Data.List.Extra (nub) -import Data.Map (Map) -import qualified Data.Map as M -import Data.Maybe (fromJust, fromMaybe, mapMaybe) +import Data.Maybe (fromMaybe) import Data.Text (Text) import qualified Data.Text as T import GHC.Generics (Generic) import NeatInterpolation (trimming) -import Text.Printf (printf) import Wasp.AI.CodeAgent (getFile, writeToFile) import Wasp.AI.GenerateNewProject.Common ( CodeAgent, @@ -33,93 +21,8 @@ import Wasp.AI.GenerateNewProject.Common ) import Wasp.AI.GenerateNewProject.Common.Prompts (appDescriptionBlock) import qualified Wasp.AI.GenerateNewProject.Common.Prompts as Prompts -import Wasp.AI.GenerateNewProject.Operation (Operation) -import qualified Wasp.AI.GenerateNewProject.Operation as Operation import Wasp.AI.GenerateNewProject.Page (makePageDocPrompt) -import qualified Wasp.AI.GenerateNewProject.Plan as Plan import Wasp.AI.OpenAI.ChatGPT (ChatMessage (..), ChatRole (..)) -import Wasp.Util (trim) - -fixImportsInPageComponentFile :: FilePath -> [Operation] -> [Operation] -> CodeAgent () -fixImportsInPageComponentFile pageComponentPath queries actions = do - currentPageComponentContent <- fromMaybe (error "couldn't find page file to fix") <$> getFile pageComponentPath - let fixedComponentContent = getPageComponentFileContentWithFixedImports currentPageComponentContent allPossibleWaspImports - writeToFile pageComponentPath (const fixedComponentContent) - where - allPossibleWaspImports = getAllPossibleWaspJsClientImports $ queries ++ actions - -getPageComponentFileContentWithFixedImports :: Text -> Map String String -> Text -getPageComponentFileContentWithFixedImports pageComponentContent allPossibleWaspImports = - T.intercalate "\n" [nonWaspImportsText, fixedWaspImportsText, remainingCodeText] - where - fixedWaspImportsText = T.pack $ intercalate "\n" $ mapMaybe (`M.lookup` allPossibleWaspImports) importedNames - nonWaspImportsText = T.pack $ intercalate "\n" nonWaspImports - remainingCodeText = T.pack $ intercalate "\n" remainingCode - importedNames = nub $ concatMap getImportedNamesFromImport waspImports - (waspImports, nonWaspImports, remainingCode) = partitionComponentFileByImports pageComponentContent - --- NOTE: Doesn't work correctly for imports that use `as` keyword! -getImportedNamesFromImport :: String -> [String] -getImportedNamesFromImport = - nub - . words - . map convertSpecialCharToSpace - . trim - . removeSuffix "from" - . trim - . removePrefix "import" - . trim - . takeWhile (not . (`elem` ['"', '\''])) - where - convertSpecialCharToSpace char - | char `elem` [',', '}', '{'] = ' ' - | otherwise = char - - removePrefix prefix = fromJust . stripPrefix prefix - - removeSuffix suffix = reverse . removePrefix (reverse suffix) . reverse - -partitionComponentFileByImports :: Text -> ([String], [String], [String]) -partitionComponentFileByImports componentContent = (waspImportLines, nonWaspImportLines, "" : remainingCodeLines) - where - (waspImportLines, nonWaspImportLines) = partition isWaspImportLine importLines - (importLines, remainingCodeLines) = - first cleanUpImportLines $ - span isImportLineOrEmpty $ lines $ T.unpack componentContent - - isImportLineOrEmpty l = let l' = trim l in "import" `isPrefixOf` l' || null l' - isWaspImportLine = ("@wasp" `isInfixOf`) - cleanUpImportLines = filter (not . null) . fmap trim - --- | Given a list of all operations in the app, it returns a list of all possible @@wasp imports --- that a Page could import. Those are imports for the specified operations, but also some general --- imports like login/logouts, hooks, ... . --- Each entry in the returned map is one possible @@wasp import, where key is imported symbol --- while import statement is the value. -getAllPossibleWaspJsClientImports :: [Operation] -> M.Map String String -getAllPossibleWaspJsClientImports operations = M.fromList $ possibleUnchangingImports ++ map makeOperationImport operations - where - possibleUnchangingImports :: [(String, String)] - possibleUnchangingImports = - [ ("logout", "import logout from '@wasp/auth/logout';"), - ("useAuth", "import useAuth from '@wasp/auth/useAuth';"), - ("useQuery", "import { useQuery } from '@wasp/queries';"), - ("useAction", "import { useAction } from '@wasp/actions';") - ] - - makeOperationImport :: Operation -> (String, String) - makeOperationImport operation = (opName, opImport) - where - opImport :: String - opImport = printf "import %s from '@wasp/%s/%s';" opName opType opName - - opName :: String - opName = Plan.opName $ Operation.opPlan operation - - opType :: String - opType = case Operation.opType operation of - Operation.Action -> "actions" - Operation.Query -> "queries" fixPageComponent :: NewProjectDetails -> FilePath -> FilePath -> CodeAgent () fixPageComponent newProjectDetails waspFilePath pageComponentPath = do @@ -169,6 +72,10 @@ fixPageComponent newProjectDetails waspFilePath pageComponentPath = do - If there are any js imports of local modules (`from "./`, `from "../`), remove them and instead add the needed implementation directly in the file we are fixing right now. - Remove redundant imports, but don't change any of the remaining ones. + - Feel free to merge together imports from the same path. + e.g. if you have `import { useQuery } from 'wasp/client/operations';` and + `import { useAction } from 'wasp/client/operations';`, you can merge those into + `import { useQuery, useAction } from 'wasp/client/operations';`. - Make sure that the component is exported as a default export. With this in mind, generate a new, fixed React component (${pageComponentPathText}). diff --git a/waspc/src/Wasp/AI/GenerateNewProject/Plan.hs b/waspc/src/Wasp/AI/GenerateNewProject/Plan.hs index 07c9001a0..22c1816f3 100644 --- a/waspc/src/Wasp/AI/GenerateNewProject/Plan.hs +++ b/waspc/src/Wasp/AI/GenerateNewProject/Plan.hs @@ -87,21 +87,21 @@ generatePlan newProjectDetails planRules = do { "entities": [{ "entityName": "User", - "entityBodyPsl": " id Int @id @default(autoincrement())\n username String @unique\n password String\n tasks Task[]" + "entityBodyPsl": " id Int @id @default(autoincrement())\n tasks Task[]" }], "actions": [{ "opName": "createTask", - "opFnPath": "@server/actions.js", + "opFnPath": "@src/actions.js", "opDesc": "Checks that user is authenticated and if so, creates new Task belonging to them. Takes description as an argument and by default sets isDone to false. Returns created Task." }], "queries": [{ "opName": "getTask", - "opFnPath": "@server/queries.js", + "opFnPath": "@src/queries.js", "opDesc": "Takes task id as an argument. Checks that user is authenticated, and if so, fetches and returns their task that has specified task id. Throws HttpError(400) if tasks exists but does not belong to them." }], "pages": [{ "pageName": "TaskPage", - "componentPath": "@client/pages/Task.jsx", + "componentPath": "@src/pages/Task.jsx", "routeName: "TaskRoute", "routePath": "/task/:taskId", "pageDesc": "Diplays a Task with the specified taskId. Allows editing of the Task. Uses getTask query and createTask action.", @@ -273,10 +273,10 @@ checkPlanForOperationIssues opType plan = else [] checkOperationFnPath op = - if not ("@server" `isPrefixOf` opFnPath op) + if not ("@src" `isPrefixOf` opFnPath op) then [ "fn path of " <> caseOnOpType "query" "action" <> " '" <> opName op - <> "' must start with '@server'." + <> "' must start with '@src'." ] else [] @@ -308,9 +308,9 @@ checkPlanForPageIssues plan = else [] checkPageComponentPath page = - if not ("@client" `isPrefixOf` componentPath page) + if not ("@src" `isPrefixOf` componentPath page) then - [ "component path of page '" <> pageName page <> "' must start with '@client'." + [ "component path of page '" <> pageName page <> "' must start with '@src'." ] else [] diff --git a/waspc/src/Wasp/AI/GenerateNewProject/Skeleton.hs b/waspc/src/Wasp/AI/GenerateNewProject/Skeleton.hs index 1c30bfa23..00ea4ce65 100644 --- a/waspc/src/Wasp/AI/GenerateNewProject/Skeleton.hs +++ b/waspc/src/Wasp/AI/GenerateNewProject/Skeleton.hs @@ -35,6 +35,8 @@ generateAndWriteProjectSkeletonAndPresetFiles newProjectDetails waspProjectSkele let (waspFile@(waspFilePath, _), planRules) = generateBaseWaspFile newProjectDetails writeNewFile waspFile + writeNewFile $ generatePackageJson newProjectDetails + case getProjectAuth newProjectDetails of UsernameAndPassword -> do writeNewFile generateLoginJsPage @@ -72,10 +74,8 @@ generateBaseWaspFile newProjectDetails = ((path, content), planRules) [ "App uses username and password authentication.", T.unpack [trimming| - App MUST have a 'User' entity, with following fields required: + App MUST have a 'User' entity, with following field(s) required: - `id Int @id @default(autoincrement())` - - `username String @unique` - - `password String` It is also likely to have a field that refers to some other entity that user owns, e.g. `tasks Task[]`. |], "One of the pages in the app must have a route path \"/\"." @@ -92,7 +92,7 @@ generateBaseWaspFile newProjectDetails = ((path, content), planRules) }, title: "${appTitle}", client: { - rootComponent: import { Layout } from "@client/Layout.jsx", + rootComponent: import { Layout } from "@src/Layout.jsx", }, db: { prisma: { @@ -104,21 +104,47 @@ generateBaseWaspFile newProjectDetails = ((path, content), planRules) route LoginRoute { path: "/login", to: LoginPage } page LoginPage { - component: import Login from "@client/pages/auth/Login.jsx" + component: import Login from "@src/pages/auth/Login.jsx" } route SignupRoute { path: "/signup", to: SignupPage } page SignupPage { - component: import Signup from "@client/pages/auth/Signup.jsx" + component: import Signup from "@src/pages/auth/Signup.jsx" } |] +-- TODO: We have duplication here, since package.json is already defined +-- in `basic` templates file. We should find a way to reuse that, so we don't +-- have to keep stuff in sync here. +generatePackageJson :: NewProjectDetails -> File +generatePackageJson newProjectDetails = + ( "package.json", + [trimming| + { + "name": "${appName}", + "dependencies": { + "wasp": "file:.wasp/out/sdk/wasp", + "react": "^18.2.0" + }, + "devDependencies": { + "typescript": "^5.1.0", + "vite": "^4.3.9", + "@types/react": "^18.0.37", + "prisma": "4.16.2" + } + } + + |] + ) + where + appName = T.pack $ _projectAppName newProjectDetails + generateLoginJsPage :: File generateLoginJsPage = - ( "src/client/pages/auth/Login.jsx", + ( "src/pages/auth/Login.jsx", [trimming| import React from "react"; import { Link } from "react-router-dom"; - import { LoginForm } from "@wasp/auth/forms/Login"; + import { LoginForm } from "wasp/client/auth"; export default function Login() { return ( @@ -153,11 +179,11 @@ generateLoginJsPage = generateSignupJsPage :: File generateSignupJsPage = - ( "src/client/pages/auth/Signup.jsx", + ( "src/pages/auth/Signup.jsx", [trimming| import React from "react"; import { Link } from "react-router-dom"; - import { SignupForm } from "@wasp/auth/forms/Signup"; + import { SignupForm } from "wasp/client/auth"; export default function Signup() { return ( @@ -201,7 +227,7 @@ generateDotEnvServerFile = generateMainCssFile :: File generateMainCssFile = - ( "src/client/Main.css", + ( "src/Main.css", [trimming| @tailwind base; @tailwind components; @@ -217,11 +243,11 @@ generateMainCssFile = generateLayoutComponent :: NewProjectDetails -> File generateLayoutComponent newProjectDetails = - ( "src/client/Layout.jsx", + ( "src/Layout.jsx", [trimming| import { Link } from "react-router-dom"; - import useAuth from '@wasp/auth/useAuth'; - import logout from '@wasp/auth/logout'; + import { useAuth, logout } from "wasp/client/auth"; + import { getUsername } from "wasp/auth"; import "./Main.css"; export const Layout = ({ children }) => { @@ -236,7 +262,7 @@ generateLayoutComponent newProjectDetails = { user ? ( - Hi, {user.username}!{' '} + Hi, {getUsername(user)}!{' '} diff --git a/waspc/src/Wasp/AI/GenerateNewProject/WaspFile.hs b/waspc/src/Wasp/AI/GenerateNewProject/WaspFile.hs index 6eb3219e1..67ff9ce37 100644 --- a/waspc/src/Wasp/AI/GenerateNewProject/WaspFile.hs +++ b/waspc/src/Wasp/AI/GenerateNewProject/WaspFile.hs @@ -103,7 +103,7 @@ fixWaspFile newProjectDetails waspFilePath plan = do For example, the following is missing ',' after the component field: ```wasp page ExamplePage { - component: import ExamplePage from "@client/pages/ExamplePage.jsx" // <- missing ',' + component: import ExamplePage from "@src/pages/ExamplePage.jsx" // <- missing ',' authRequired: true } ``` @@ -111,8 +111,8 @@ fixWaspFile newProjectDetails waspFilePath plan = do Fix these by replacing them with actual implementation. - Strings in Wasp must use double quotes, not single quotes. - Value of `fn:` field in `query` or `action` not having correct import syntax, - for example it might have invalid syntax, e.g. `fn: @server/actions.js`. - Fix these by replacing it with correct syntax, e.g. `fn: import { actionName } from "@server/actions.js"`. + for example it might have invalid syntax, e.g. `fn: @src/actions.js`. + Fix these by replacing it with correct syntax, e.g. `fn: import { actionName } from "@src/actions.js"`. - If two entities are in a relation, make sure that they both have a field that references the other entity. - If an entity has a field that references another entity (e.g. location), make sure to include @relation directive on that field. - If an entity references another entity, make sure the ID field (e.g. locationId) of the referenced entity is also included. diff --git a/waspc/test/AI/GenerateNewProject/PageComponentFileTest.hs b/waspc/test/AI/GenerateNewProject/PageComponentFileTest.hs deleted file mode 100644 index 000b8e420..000000000 --- a/waspc/test/AI/GenerateNewProject/PageComponentFileTest.hs +++ /dev/null @@ -1,92 +0,0 @@ -module AI.GenerateNewProject.PageComponentFileTest where - -import qualified Data.Map as M -import NeatInterpolation (trimming) -import Test.Tasty.Hspec -import Wasp.AI.GenerateNewProject.PageComponentFile - -spec_PageComponentFileTest :: Spec -spec_PageComponentFileTest = do - describe "getPageComponentFileContentWithFixedImports" $ do - let mockAllPossibleWaspClientImports = - M.fromList - [ ("useQuery", "import { useQuery } from '@wasp/queries';"), - ("useAction", "import { useAction } from '@wasp/actions';"), - ("useAuth", "import useAuth from '@wasp/auth/useAuth';"), - ("someAction", "import someAction from '@wasp/actions/someAction';"), - ("someQuery", "import someQuery from '@wasp/queries/someQuery';") - ] - it "should fix incorrect @wasp imports while keeping non-@wasp imports and removing made up @wasp ones." $ do - let mockPageComponentFileContent = - [trimming| - import React from 'react'; - import { useAuth } from '@wasp/authorization/useAuth'; - import { useQuery, useAction } from '@wasp/queries_and_actions'; - import { Link } from 'react-router'; - import madeUpThingy from '@wasp/madeup'; - import { someAction } from '@wasp/actions'; - - function HomePage () { - ... - } - |] - getPageComponentFileContentWithFixedImports - mockPageComponentFileContent - mockAllPossibleWaspClientImports - `shouldBe` [trimming| - import React from 'react'; - import { Link } from 'react-router'; - import useAuth from '@wasp/auth/useAuth'; - import { useQuery } from '@wasp/queries'; - import { useAction } from '@wasp/actions'; - import someAction from '@wasp/actions/someAction'; - - function HomePage () { - ... - } - |] - - describe "getImportedNameFromImport" $ do - it "should correctly pick up names from various imports statements" $ do - getImportedNamesFromImport "import { foo, barBar} from 'some/path'" - `shouldBe` ["foo", "barBar"] - getImportedNamesFromImport " import fooFoo from \"../\" " - `shouldBe` ["fooFoo"] - - describe "partitionComponentFileByImports" $ do - it "should partition file content to wasp imports, non-wasp imports, and the rest of the file." $ do - let fileContent = - "\n" - <> [trimming| - import React from 'react'; - import { useAuth } from '@wasp/useAuth'; - import { useQuery } from '@wasp/queries'; - - import { someAction } from '@wasp/actions'; - import { Link } from 'react-router'; - - function importStuff () { - importStuffNow("@wasp"); - } - - const a = 5; - |] - <> "\n\n" - - partitionComponentFileByImports fileContent - `shouldBe` ( [ "import { useAuth } from '@wasp/useAuth';", - "import { useQuery } from '@wasp/queries';", - "import { someAction } from '@wasp/actions';" - ], - [ "import React from 'react';", - "import { Link } from 'react-router';" - ], - [ "", - "function importStuff () {", - " importStuffNow(\"@wasp\");", - "}", - "", - "const a = 5;", - "" - ] - ) diff --git a/waspc/waspc.cabal b/waspc/waspc.cabal index 4d55cd6da..b47c17c82 100644 --- a/waspc/waspc.cabal +++ b/waspc/waspc.cabal @@ -52,6 +52,7 @@ data-files: Cli/templates/basic/main.wasp Cli/templates/skeleton/.gitignore Cli/templates/skeleton/.wasproot + Cli/templates/skeleton/public/.gitignore Cli/templates/skeleton/src/.waspignore Lsp/templates/**/*.js Lsp/templates/**/*.ts @@ -573,7 +574,6 @@ test-suite waspc-test , tasty-quickcheck ^>= 0.10 , tasty-golden ^>= 2.3.5 other-modules: - AI.GenerateNewProject.PageComponentFileTest AI.GenerateNewProject.LogMsgTest Analyzer.Evaluation.EvaluationErrorTest Analyzer.EvaluatorTest