mirror of
https://github.com/wasp-lang/wasp.git
synced 2024-09-11 07:16:09 +03:00
Updated Wasp AI to work with wasp 0.12. (#1726)
This commit is contained in:
parent
814b82bd58
commit
03d234fd7d
@ -26,7 +26,7 @@ import Wasp.AI.GenerateNewProject.Operation
|
|||||||
)
|
)
|
||||||
import Wasp.AI.GenerateNewProject.OperationsJsFile (fixOperationsJsFile)
|
import Wasp.AI.GenerateNewProject.OperationsJsFile (fixOperationsJsFile)
|
||||||
import Wasp.AI.GenerateNewProject.Page (generateAndWritePage, getPageComponentPath)
|
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 Wasp.AI.GenerateNewProject.Plan (generatePlan)
|
||||||
import qualified Wasp.AI.GenerateNewProject.Plan as Plan
|
import qualified Wasp.AI.GenerateNewProject.Plan as Plan
|
||||||
import Wasp.AI.GenerateNewProject.Skeleton (generateAndWriteProjectSkeletonAndPresetFiles)
|
import Wasp.AI.GenerateNewProject.Skeleton (generateAndWriteProjectSkeletonAndPresetFiles)
|
||||||
@ -99,12 +99,6 @@ generateNewProject newProjectDetails waspProjectSkeletonFiles = do
|
|||||||
writeToLog $ "Fixed '" <> fromString pageFp <> "' page."
|
writeToLog $ "Fixed '" <> fromString pageFp <> "' page."
|
||||||
writeToLog "Pages fixed."
|
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
|
(promptTokensUsed, completionTokensUsed) <- getTotalTokensUsage
|
||||||
writeToLog $
|
writeToLog $
|
||||||
"\nTotal tokens usage: "
|
"\nTotal tokens usage: "
|
||||||
|
@ -74,7 +74,7 @@ waspFileExample =
|
|||||||
onAuthFailedRedirectTo: "/login"
|
onAuthFailedRedirectTo: "/login"
|
||||||
},
|
},
|
||||||
client: {
|
client: {
|
||||||
rootComponent: import { Layout } from "@client/Layout.jsx",
|
rootComponent: import { Layout } from "@src/Layout.jsx",
|
||||||
},
|
},
|
||||||
db: {
|
db: {
|
||||||
prisma: {
|
prisma: {
|
||||||
@ -85,24 +85,22 @@ waspFileExample =
|
|||||||
|
|
||||||
route SignupRoute { path: "/signup", to: SignupPage }
|
route SignupRoute { path: "/signup", to: SignupPage }
|
||||||
page 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 }
|
route LoginRoute { path: "/login", to: LoginPage }
|
||||||
page 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 }
|
route DashboardRoute { path: "/", to: DashboardPage }
|
||||||
page DashboardPage {
|
page DashboardPage {
|
||||||
authRequired: true,
|
authRequired: true,
|
||||||
component: import Dashboard from "@client/pages/Dashboard.jsx"
|
component: import Dashboard from "@src/pages/Dashboard.jsx"
|
||||||
}
|
}
|
||||||
|
|
||||||
entity User {=psl
|
entity User {=psl
|
||||||
id Int @id @default(autoincrement())
|
id Int @id @default(autoincrement())
|
||||||
username String @unique
|
|
||||||
password String
|
|
||||||
tasks Task[]
|
tasks Task[]
|
||||||
psl=}
|
psl=}
|
||||||
|
|
||||||
@ -115,22 +113,22 @@ waspFileExample =
|
|||||||
psl=}
|
psl=}
|
||||||
|
|
||||||
query getUser {
|
query getUser {
|
||||||
fn: import { getUser } from "@server/queries.js",
|
fn: import { getUser } from "@src/queries.js",
|
||||||
entities: [User] // Entities that this query operates on.
|
entities: [User] // Entities that this query operates on.
|
||||||
}
|
}
|
||||||
|
|
||||||
query getTasks {
|
query getTasks {
|
||||||
fn: import { getTasks } from "@server/queries.js",
|
fn: import { getTasks } from "@src/queries.js",
|
||||||
entities: [Task]
|
entities: [Task]
|
||||||
}
|
}
|
||||||
|
|
||||||
action createTask {
|
action createTask {
|
||||||
fn: import { createTask } from "@server/actions.js",
|
fn: import { createTask } from "@src/actions.js",
|
||||||
entities: [Task]
|
entities: [Task]
|
||||||
}
|
}
|
||||||
|
|
||||||
action updateTask {
|
action updateTask {
|
||||||
fn: import { updateTask } from "@server/actions.js",
|
fn: import { updateTask } from "@src/actions.js",
|
||||||
entities: [Task]
|
entities: [Task]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
@ -97,7 +97,7 @@ generateOperation operationType newProjectDetails entityPlans operationPlan = do
|
|||||||
Example of response:
|
Example of response:
|
||||||
{ "opWaspDecl": "${operationTypeText} ${operationName} {\n fn: import { ${operationName} } from \"${operationFnPath}\",\n entities: [Task]\n}",
|
{ "opWaspDecl": "${operationTypeText} ${operationName} {\n fn: import { ${operationName} } from \"${operationFnPath}\",\n entities: [Task]\n}",
|
||||||
"opJsImpl": "export const {$operationName} = async (args, context) => { ... }",
|
"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.
|
"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.
|
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!
|
Instead, always write real implementation!
|
||||||
- Don't import prisma client in the JS imports, it is not needed.
|
- Don't import prisma client in the JS imports, it is not needed.
|
||||||
- In wasp declaration (`opWaspDecl`), make sure to use ',' before `entities:`.
|
- 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.
|
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.
|
- 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 declaration:
|
||||||
```wasp
|
```wasp
|
||||||
action updateTask {
|
action updateTask {
|
||||||
fn: import { updateTask } from "@server/taskActions.js",
|
fn: import { updateTask } from "@src/taskActions.js",
|
||||||
entities: [Task] // Entities that action mutates.
|
entities: [Task] // Entities that action mutates.
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
- NodeJS implementation (with imports):
|
- NodeJS implementation (with imports):
|
||||||
```js
|
```js
|
||||||
import HttpError from '@wasp/core/HttpError.js'
|
import { HttpError } from 'wasp/server'
|
||||||
|
|
||||||
export const updateTask = async (args, context) => {
|
export const updateTask = async (args, context) => {
|
||||||
if (!context.user) { throw new HttpError(401) }; // If user needs to be authenticated.
|
if (!context.user) { throw new HttpError(401) }; // If user needs to be authenticated.
|
||||||
@ -189,14 +189,14 @@ actionDocPrompt =
|
|||||||
|
|
||||||
```wasp
|
```wasp
|
||||||
action deleteList {
|
action deleteList {
|
||||||
fn: import { deleteList } from "@server/actions.js",
|
fn: import { deleteList } from "@src/actions.js",
|
||||||
entities: [List, Card]
|
entities: [List, Card]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
- NodeJS implementation (with imports):
|
- NodeJS implementation (with imports):
|
||||||
```js
|
```js
|
||||||
import HttpError from '@wasp/core/HttpError.js'
|
import { HttpError } from 'wasp/server'
|
||||||
|
|
||||||
export const deleteList = async ({ listId }, context) => {
|
export const deleteList = async ({ listId }, context) => {
|
||||||
if (!context.user) { throw new HttpError(401) };
|
if (!context.user) { throw new HttpError(401) };
|
||||||
@ -232,14 +232,14 @@ queryDocPrompt =
|
|||||||
- Wasp declaration:
|
- Wasp declaration:
|
||||||
```wasp
|
```wasp
|
||||||
query fetchFilteredTasks {
|
query fetchFilteredTasks {
|
||||||
fn: import { getFilteredTasks } from "@server/taskQueries.js",
|
fn: import { getFilteredTasks } from "@src/taskQueries.js",
|
||||||
entities: [Task] // Entities that query uses.
|
entities: [Task] // Entities that query uses.
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
- NodeJS implementation (with imports):
|
- NodeJS implementation (with imports):
|
||||||
```js
|
```js
|
||||||
import HttpError from '@wasp/core/HttpError.js'
|
import { HttpError } from 'wasp/server'
|
||||||
|
|
||||||
export const getFilteredTasks = async (args, context) => {
|
export const getFilteredTasks = async (args, context) => {
|
||||||
if (!context.user) { throw new HttpError(401) }; // If user needs to be authenticated.
|
if (!context.user) { throw new HttpError(401) }; // If user needs to be authenticated.
|
||||||
@ -259,29 +259,28 @@ queryDocPrompt =
|
|||||||
- Wasp declaration:
|
- Wasp declaration:
|
||||||
```wasp
|
```wasp
|
||||||
query getAuthor {
|
query getAuthor {
|
||||||
fn: import { getAuthor } from "@server/author/queries.js",
|
fn: import { getAuthor } from "@src/author/queries.js",
|
||||||
entities: [Author]
|
entities: [Author]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
- NodeJS implementation (with imports):
|
- NodeJS implementation (with imports):
|
||||||
```js
|
```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.
|
// Here we don't check if user is authenticated as this query is public.
|
||||||
|
|
||||||
const author = await context.entities.Author.findUnique({
|
const author = await context.entities.Author.findUnique({
|
||||||
where: { username },
|
where: { id },
|
||||||
select: {
|
select: {
|
||||||
username: true,
|
|
||||||
id: true,
|
id: true,
|
||||||
bio: true,
|
bio: true,
|
||||||
profilePictureUrl: 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;
|
return author;
|
||||||
}
|
}
|
||||||
@ -355,9 +354,8 @@ writeOperationToJsFile operation =
|
|||||||
getOperationJsFilePath :: Operation -> FilePath
|
getOperationJsFilePath :: Operation -> FilePath
|
||||||
getOperationJsFilePath operation = resolvePath $ Plan.opFnPath $ opPlan operation
|
getOperationJsFilePath operation = resolvePath $ Plan.opFnPath $ opPlan operation
|
||||||
where
|
where
|
||||||
pathPrefix = "@server/"
|
resolvePath p | "@src/" `isPrefixOf` p = drop (length ("@" :: String)) p
|
||||||
resolvePath p | pathPrefix `isPrefixOf` p = "src/" <> drop (length ("@" :: String)) p
|
resolvePath _ = error "path incorrectly formatted, should start with @src/ ."
|
||||||
resolvePath _ = error "path incorrectly formatted, should start with " <> pathPrefix <> "."
|
|
||||||
|
|
||||||
writeOperationToWaspFile :: FilePath -> Operation -> CodeAgent ()
|
writeOperationToWaspFile :: FilePath -> Operation -> CodeAgent ()
|
||||||
writeOperationToWaspFile waspFilePath operation =
|
writeOperationToWaspFile waspFilePath operation =
|
||||||
|
@ -109,7 +109,7 @@ generatePage newProjectDetails entityPlans queries actions pPlan = do
|
|||||||
|
|
||||||
Example of such JSON:
|
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.",
|
"pageJsImpl": "JS imports + React component implementing the page.",
|
||||||
}
|
}
|
||||||
There should be no other text in the response.
|
There should be no other text in the response.
|
||||||
@ -134,7 +134,7 @@ makePageDocPrompt =
|
|||||||
```wasp
|
```wasp
|
||||||
route TasksRoute { path: "/", to: ExamplePage }
|
route TasksRoute { path: "/", to: ExamplePage }
|
||||||
page TasksPage {
|
page TasksPage {
|
||||||
component: import Tasks from "@client/Tasks.jsx",
|
component: import Tasks from "@src/Tasks.jsx",
|
||||||
authRequired: true
|
authRequired: true
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@ -143,11 +143,13 @@ makePageDocPrompt =
|
|||||||
|
|
||||||
```jsx
|
```jsx
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { useQuery } from '@wasp/queries'; // A thin wrapper around react-query's useQuery
|
import {
|
||||||
import { useAction } from '@wasp/actions'; // A thin wrapper around react-query's useMutation
|
useQuery, // A thin wrapper around react-query's useQuery
|
||||||
import getTasks from '@wasp/queries/getTasks';
|
useAction, // A thin wrapper around react-query's useMutation
|
||||||
import createTask from '@wasp/actions/createTask';
|
getTasks, // query
|
||||||
import toggleTask from '@wasp/actions/toggleTask';
|
createTask, // action
|
||||||
|
toggleTask // action
|
||||||
|
} from 'wasp/client/operations';
|
||||||
|
|
||||||
const Tasks = () => {
|
const Tasks = () => {
|
||||||
const { data: tasks, isLoading, error } = useQuery(getTasks);
|
const { data: tasks, isLoading, error } = useQuery(getTasks);
|
||||||
@ -208,7 +210,7 @@ makePageDocPrompt =
|
|||||||
```wasp
|
```wasp
|
||||||
route DashboardRoute { path: "/dashboard", to: DashboardPage }
|
route DashboardRoute { path: "/dashboard", to: DashboardPage }
|
||||||
page DashboardPage {
|
page DashboardPage {
|
||||||
component: import Dashboard from "@client/Dashboard.jsx",
|
component: import Dashboard from "@src/Dashboard.jsx",
|
||||||
authRequired: true
|
authRequired: true
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@ -218,10 +220,7 @@ makePageDocPrompt =
|
|||||||
```jsx
|
```jsx
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { useQuery } from '@wasp/queries';
|
import { useQuery, useAction, getUsers, deleteUser } from 'wasp/client/operations';
|
||||||
import { useAction } from '@wasp/actions';
|
|
||||||
import getUsers from '@wasp/queries/getUsers';
|
|
||||||
import deleteUser from '@wasp/actions/deleteUser';
|
|
||||||
|
|
||||||
const DashboardPage = () => {
|
const DashboardPage = () => {
|
||||||
const { data: users, isLoading, error } = useQuery(getUsers);
|
const { data: users, isLoading, error } = useQuery(getUsers);
|
||||||
@ -266,15 +265,11 @@ makePageDocPrompt =
|
|||||||
|
|
||||||
Here are the rules for importing actions and queries.
|
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';`.
|
If a query is called "myQuery", its import MUST BE `import { myQuery } from 'wasp/client/operations';`.
|
||||||
More generally, a query import MUST BE a default import, and name of the file is the same as name of the query.
|
|
||||||
The hook for wrapping queries is called `useQuery`.
|
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';`.
|
If an action is called "myAction", its import MUST BE `import { myAction } from 'wasp/client/operations';`.
|
||||||
More generally, an action import MUST BE a default import, and name of the file is the same as name of the action.
|
|
||||||
The hook for wrapping actions is called `useAction`.
|
The hook for wrapping actions is called `useAction`.
|
||||||
Use a single import statement per action.
|
|
||||||
|
|
||||||
Note: There is no `useMutation` hook in Wasp.
|
Note: There is no `useMutation` hook in Wasp.
|
||||||
|]
|
|]
|
||||||
@ -303,9 +298,8 @@ getPageComponentPath :: Page -> String
|
|||||||
getPageComponentPath page = path
|
getPageComponentPath page = path
|
||||||
where
|
where
|
||||||
path = resolvePath $ Plan.componentPath $ pagePlan page
|
path = resolvePath $ Plan.componentPath $ pagePlan page
|
||||||
pathPrefix = "@client/"
|
resolvePath p | "@src/" `isPrefixOf` p = drop (length ("@" :: String)) p
|
||||||
resolvePath p | pathPrefix `isPrefixOf` p = "src/" <> drop (length ("@" :: String)) p
|
resolvePath _ = error "path incorrectly formatted, should start with @src/."
|
||||||
resolvePath _ = error "path incorrectly formatted, should start with " <> pathPrefix <> "."
|
|
||||||
|
|
||||||
writePageToWaspFile :: FilePath -> Page -> CodeAgent ()
|
writePageToWaspFile :: FilePath -> Page -> CodeAgent ()
|
||||||
writePageToWaspFile waspFilePath page =
|
writePageToWaspFile waspFilePath page =
|
||||||
|
@ -2,27 +2,15 @@
|
|||||||
|
|
||||||
module Wasp.AI.GenerateNewProject.PageComponentFile
|
module Wasp.AI.GenerateNewProject.PageComponentFile
|
||||||
( fixPageComponent,
|
( fixPageComponent,
|
||||||
fixImportsInPageComponentFile,
|
|
||||||
-- NOTE: Exports below are exported only for testing!
|
|
||||||
getPageComponentFileContentWithFixedImports,
|
|
||||||
partitionComponentFileByImports,
|
|
||||||
getImportedNamesFromImport,
|
|
||||||
getAllPossibleWaspJsClientImports,
|
|
||||||
)
|
)
|
||||||
where
|
where
|
||||||
|
|
||||||
import Control.Arrow (first)
|
|
||||||
import Data.Aeson (FromJSON)
|
import Data.Aeson (FromJSON)
|
||||||
import Data.List (intercalate, isInfixOf, isPrefixOf, partition, stripPrefix)
|
import Data.Maybe (fromMaybe)
|
||||||
import Data.List.Extra (nub)
|
|
||||||
import Data.Map (Map)
|
|
||||||
import qualified Data.Map as M
|
|
||||||
import Data.Maybe (fromJust, fromMaybe, mapMaybe)
|
|
||||||
import Data.Text (Text)
|
import Data.Text (Text)
|
||||||
import qualified Data.Text as T
|
import qualified Data.Text as T
|
||||||
import GHC.Generics (Generic)
|
import GHC.Generics (Generic)
|
||||||
import NeatInterpolation (trimming)
|
import NeatInterpolation (trimming)
|
||||||
import Text.Printf (printf)
|
|
||||||
import Wasp.AI.CodeAgent (getFile, writeToFile)
|
import Wasp.AI.CodeAgent (getFile, writeToFile)
|
||||||
import Wasp.AI.GenerateNewProject.Common
|
import Wasp.AI.GenerateNewProject.Common
|
||||||
( CodeAgent,
|
( CodeAgent,
|
||||||
@ -33,93 +21,8 @@ import Wasp.AI.GenerateNewProject.Common
|
|||||||
)
|
)
|
||||||
import Wasp.AI.GenerateNewProject.Common.Prompts (appDescriptionBlock)
|
import Wasp.AI.GenerateNewProject.Common.Prompts (appDescriptionBlock)
|
||||||
import qualified Wasp.AI.GenerateNewProject.Common.Prompts as Prompts
|
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 Wasp.AI.GenerateNewProject.Page (makePageDocPrompt)
|
||||||
import qualified Wasp.AI.GenerateNewProject.Plan as Plan
|
|
||||||
import Wasp.AI.OpenAI.ChatGPT (ChatMessage (..), ChatRole (..))
|
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 -> FilePath -> FilePath -> CodeAgent ()
|
||||||
fixPageComponent newProjectDetails waspFilePath pageComponentPath = do
|
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 "../`),
|
- 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 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.
|
- 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.
|
- Make sure that the component is exported as a default export.
|
||||||
|
|
||||||
With this in mind, generate a new, fixed React component (${pageComponentPathText}).
|
With this in mind, generate a new, fixed React component (${pageComponentPathText}).
|
||||||
|
@ -87,21 +87,21 @@ generatePlan newProjectDetails planRules = do
|
|||||||
{
|
{
|
||||||
"entities": [{
|
"entities": [{
|
||||||
"entityName": "User",
|
"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": [{
|
"actions": [{
|
||||||
"opName": "createTask",
|
"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."
|
"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": [{
|
"queries": [{
|
||||||
"opName": "getTask",
|
"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."
|
"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": [{
|
"pages": [{
|
||||||
"pageName": "TaskPage",
|
"pageName": "TaskPage",
|
||||||
"componentPath": "@client/pages/Task.jsx",
|
"componentPath": "@src/pages/Task.jsx",
|
||||||
"routeName: "TaskRoute",
|
"routeName: "TaskRoute",
|
||||||
"routePath": "/task/:taskId",
|
"routePath": "/task/:taskId",
|
||||||
"pageDesc": "Diplays a Task with the specified taskId. Allows editing of the Task. Uses getTask query and createTask action.",
|
"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 []
|
else []
|
||||||
|
|
||||||
checkOperationFnPath op =
|
checkOperationFnPath op =
|
||||||
if not ("@server" `isPrefixOf` opFnPath op)
|
if not ("@src" `isPrefixOf` opFnPath op)
|
||||||
then
|
then
|
||||||
[ "fn path of " <> caseOnOpType "query" "action" <> " '" <> opName op
|
[ "fn path of " <> caseOnOpType "query" "action" <> " '" <> opName op
|
||||||
<> "' must start with '@server'."
|
<> "' must start with '@src'."
|
||||||
]
|
]
|
||||||
else []
|
else []
|
||||||
|
|
||||||
@ -308,9 +308,9 @@ checkPlanForPageIssues plan =
|
|||||||
else []
|
else []
|
||||||
|
|
||||||
checkPageComponentPath page =
|
checkPageComponentPath page =
|
||||||
if not ("@client" `isPrefixOf` componentPath page)
|
if not ("@src" `isPrefixOf` componentPath page)
|
||||||
then
|
then
|
||||||
[ "component path of page '" <> pageName page <> "' must start with '@client'."
|
[ "component path of page '" <> pageName page <> "' must start with '@src'."
|
||||||
]
|
]
|
||||||
else []
|
else []
|
||||||
|
|
||||||
|
@ -35,6 +35,8 @@ generateAndWriteProjectSkeletonAndPresetFiles newProjectDetails waspProjectSkele
|
|||||||
let (waspFile@(waspFilePath, _), planRules) = generateBaseWaspFile newProjectDetails
|
let (waspFile@(waspFilePath, _), planRules) = generateBaseWaspFile newProjectDetails
|
||||||
writeNewFile waspFile
|
writeNewFile waspFile
|
||||||
|
|
||||||
|
writeNewFile $ generatePackageJson newProjectDetails
|
||||||
|
|
||||||
case getProjectAuth newProjectDetails of
|
case getProjectAuth newProjectDetails of
|
||||||
UsernameAndPassword -> do
|
UsernameAndPassword -> do
|
||||||
writeNewFile generateLoginJsPage
|
writeNewFile generateLoginJsPage
|
||||||
@ -72,10 +74,8 @@ generateBaseWaspFile newProjectDetails = ((path, content), planRules)
|
|||||||
[ "App uses username and password authentication.",
|
[ "App uses username and password authentication.",
|
||||||
T.unpack
|
T.unpack
|
||||||
[trimming|
|
[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())`
|
- `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[]`.
|
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 \"/\"."
|
"One of the pages in the app must have a route path \"/\"."
|
||||||
@ -92,7 +92,7 @@ generateBaseWaspFile newProjectDetails = ((path, content), planRules)
|
|||||||
},
|
},
|
||||||
title: "${appTitle}",
|
title: "${appTitle}",
|
||||||
client: {
|
client: {
|
||||||
rootComponent: import { Layout } from "@client/Layout.jsx",
|
rootComponent: import { Layout } from "@src/Layout.jsx",
|
||||||
},
|
},
|
||||||
db: {
|
db: {
|
||||||
prisma: {
|
prisma: {
|
||||||
@ -104,21 +104,47 @@ generateBaseWaspFile newProjectDetails = ((path, content), planRules)
|
|||||||
|
|
||||||
route LoginRoute { path: "/login", to: LoginPage }
|
route LoginRoute { path: "/login", to: LoginPage }
|
||||||
page 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 }
|
route SignupRoute { path: "/signup", to: SignupPage }
|
||||||
page 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 :: File
|
||||||
generateLoginJsPage =
|
generateLoginJsPage =
|
||||||
( "src/client/pages/auth/Login.jsx",
|
( "src/pages/auth/Login.jsx",
|
||||||
[trimming|
|
[trimming|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { LoginForm } from "@wasp/auth/forms/Login";
|
import { LoginForm } from "wasp/client/auth";
|
||||||
|
|
||||||
export default function Login() {
|
export default function Login() {
|
||||||
return (
|
return (
|
||||||
@ -153,11 +179,11 @@ generateLoginJsPage =
|
|||||||
|
|
||||||
generateSignupJsPage :: File
|
generateSignupJsPage :: File
|
||||||
generateSignupJsPage =
|
generateSignupJsPage =
|
||||||
( "src/client/pages/auth/Signup.jsx",
|
( "src/pages/auth/Signup.jsx",
|
||||||
[trimming|
|
[trimming|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { SignupForm } from "@wasp/auth/forms/Signup";
|
import { SignupForm } from "wasp/client/auth";
|
||||||
|
|
||||||
export default function Signup() {
|
export default function Signup() {
|
||||||
return (
|
return (
|
||||||
@ -201,7 +227,7 @@ generateDotEnvServerFile =
|
|||||||
|
|
||||||
generateMainCssFile :: File
|
generateMainCssFile :: File
|
||||||
generateMainCssFile =
|
generateMainCssFile =
|
||||||
( "src/client/Main.css",
|
( "src/Main.css",
|
||||||
[trimming|
|
[trimming|
|
||||||
@tailwind base;
|
@tailwind base;
|
||||||
@tailwind components;
|
@tailwind components;
|
||||||
@ -217,11 +243,11 @@ generateMainCssFile =
|
|||||||
|
|
||||||
generateLayoutComponent :: NewProjectDetails -> File
|
generateLayoutComponent :: NewProjectDetails -> File
|
||||||
generateLayoutComponent newProjectDetails =
|
generateLayoutComponent newProjectDetails =
|
||||||
( "src/client/Layout.jsx",
|
( "src/Layout.jsx",
|
||||||
[trimming|
|
[trimming|
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import useAuth from '@wasp/auth/useAuth';
|
import { useAuth, logout } from "wasp/client/auth";
|
||||||
import logout from '@wasp/auth/logout';
|
import { getUsername } from "wasp/auth";
|
||||||
import "./Main.css";
|
import "./Main.css";
|
||||||
|
|
||||||
export const Layout = ({ children }) => {
|
export const Layout = ({ children }) => {
|
||||||
@ -236,7 +262,7 @@ generateLayoutComponent newProjectDetails =
|
|||||||
</Link>
|
</Link>
|
||||||
{ user ? (
|
{ user ? (
|
||||||
<span>
|
<span>
|
||||||
Hi, {user.username}!{' '}
|
Hi, {getUsername(user)}!{' '}
|
||||||
<button onClick={logout} className="text-xl2 underline">
|
<button onClick={logout} className="text-xl2 underline">
|
||||||
(Log out)
|
(Log out)
|
||||||
</button>
|
</button>
|
||||||
|
@ -103,7 +103,7 @@ fixWaspFile newProjectDetails waspFilePath plan = do
|
|||||||
For example, the following is missing ',' after the component field:
|
For example, the following is missing ',' after the component field:
|
||||||
```wasp
|
```wasp
|
||||||
page ExamplePage {
|
page ExamplePage {
|
||||||
component: import ExamplePage from "@client/pages/ExamplePage.jsx" // <- missing ','
|
component: import ExamplePage from "@src/pages/ExamplePage.jsx" // <- missing ','
|
||||||
authRequired: true
|
authRequired: true
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@ -111,8 +111,8 @@ fixWaspFile newProjectDetails waspFilePath plan = do
|
|||||||
Fix these by replacing them with actual implementation.
|
Fix these by replacing them with actual implementation.
|
||||||
- Strings in Wasp must use double quotes, not single quotes.
|
- Strings in Wasp must use double quotes, not single quotes.
|
||||||
- Value of `fn:` field in `query` or `action` not having correct import syntax,
|
- 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`.
|
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 "@server/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 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 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.
|
- If an entity references another entity, make sure the ID field (e.g. locationId) of the referenced entity is also included.
|
||||||
|
@ -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;",
|
|
||||||
""
|
|
||||||
]
|
|
||||||
)
|
|
@ -52,6 +52,7 @@ data-files:
|
|||||||
Cli/templates/basic/main.wasp
|
Cli/templates/basic/main.wasp
|
||||||
Cli/templates/skeleton/.gitignore
|
Cli/templates/skeleton/.gitignore
|
||||||
Cli/templates/skeleton/.wasproot
|
Cli/templates/skeleton/.wasproot
|
||||||
|
Cli/templates/skeleton/public/.gitignore
|
||||||
Cli/templates/skeleton/src/.waspignore
|
Cli/templates/skeleton/src/.waspignore
|
||||||
Lsp/templates/**/*.js
|
Lsp/templates/**/*.js
|
||||||
Lsp/templates/**/*.ts
|
Lsp/templates/**/*.ts
|
||||||
@ -573,7 +574,6 @@ test-suite waspc-test
|
|||||||
, tasty-quickcheck ^>= 0.10
|
, tasty-quickcheck ^>= 0.10
|
||||||
, tasty-golden ^>= 2.3.5
|
, tasty-golden ^>= 2.3.5
|
||||||
other-modules:
|
other-modules:
|
||||||
AI.GenerateNewProject.PageComponentFileTest
|
|
||||||
AI.GenerateNewProject.LogMsgTest
|
AI.GenerateNewProject.LogMsgTest
|
||||||
Analyzer.Evaluation.EvaluationErrorTest
|
Analyzer.Evaluation.EvaluationErrorTest
|
||||||
Analyzer.EvaluatorTest
|
Analyzer.EvaluatorTest
|
||||||
|
Loading…
Reference in New Issue
Block a user