diff --git a/README.md b/README.md
index 31d9d1f60..0dcb6a679 100644
--- a/README.md
+++ b/README.md
@@ -38,9 +38,9 @@ app TodoApp {
title: "Todo App"
}
-route "/" -> page Main
-page Main {
- component: import Main from "@ext/pages/Main.js" // Importing React component.
+route RootRoute { path: "/", to: MainPage }
+page MainPage {
+ component: import Main from "@ext/pages/Main.js" // Importing React component.
}
query getTasks {
diff --git a/examples/realworld/main.wasp b/examples/realworld/main.wasp
index ccb2c6ef6..e0388ac14 100644
--- a/examples/realworld/main.wasp
+++ b/examples/realworld/main.wasp
@@ -3,55 +3,64 @@ app Conduit {
head: [
""
+ ],
+
+ auth: {
+ userEntity: User,
+ methods: [ EmailAndPassword ],
+ onAuthFailedRedirectTo: "/login"
+ },
+
+ db: { system: PostgreSQL },
+
+ dependencies: [
+ ("prop-types", "15.7.2"),
+ ("react-markdown", "5.0.3"),
+ ("moment", "2.29.1"),
+ ("@material-ui/core", "4.11.3"),
+ ("@material-ui/icons", "4.11.2"),
+ ("slug", "4.0.2")
]
}
-auth {
- userEntity: User,
- methods: [ EmailAndPassword ],
- onAuthFailedRedirectTo: "/login"
-}
-db {
- system: PostgreSQL
-}
// ----------------- Pages ------------------ //
-route "/" -> page Main
-page Main {
+route RootRoute { path: "/", to: MainPage }
+page MainPage {
component: import Main from "@ext/MainPage.js"
}
-route "/login" -> page LogIn
-page LogIn {
+route LogInRoute { path: "/login", to: LogInPage }
+page LogInPage {
component: import LogIn from "@ext/auth/LoginPage.js"
}
-route "/register" -> page SignUp
-page SignUp {
+route RegisterRoute { path: "/register", to: SignUpPage }
+page SignUpPage {
component: import SignUp from "@ext/auth/SignupPage.js"
}
-route "/settings" -> page UserSettings
-page UserSettings {
+route UserSettingsRoute { path: "/settings", to: UserSettingsPage }
+page UserSettingsPage {
authRequired: true,
component: import UserSettings from "@ext/user/components/UserSettingsPage.js"
}
-route "/@:username" -> page UserProfile
-page UserProfile {
+route UserProfileRoute { path: "/@:username", to: UserProfilePage }
+page UserProfilePage {
component: import UserProfile from "@ext/user/components/UserProfilePage.js"
}
-route "/editor/:articleSlug?" -> page ArticleEditor
-page ArticleEditor {
+route ArticleEditorRoute { path: "/editor/:articleSlug?", to: ArticleEditorPage }
+page ArticleEditorPage {
authRequired: true,
component: import ArticleEditor from "@ext/article/components/ArticleEditorPage.js"
}
-route "/article/:articleSlug" -> page ArticleView
-page ArticleView {
+route ArticleViewRoute { path: "/article/:articleSlug", to: ArticleViewPage }
+page ArticleViewPage {
component: import ArticleView from "@ext/article/components/ArticleViewPage.js"
}
@@ -190,12 +199,3 @@ query getTags {
}
// -------------------------------------------- //
-
-dependencies {=json
- "prop-types": "15.7.2",
- "react-markdown": "5.0.3",
- "moment": "2.29.1",
- "@material-ui/core": "4.11.3",
- "@material-ui/icons": "4.11.2",
- "slug": "4.0.2"
-json=}
diff --git a/examples/thoughts/main.wasp b/examples/thoughts/main.wasp
index 1e3080701..d9a1453b7 100644
--- a/examples/thoughts/main.wasp
+++ b/examples/thoughts/main.wasp
@@ -1,36 +1,36 @@
app Thoughts {
- title: "Thoughts"
+ title: "Thoughts",
+ db: { system: PostgreSQL },
+ auth: {
+ userEntity: User,
+ methods: [ EmailAndPassword ],
+ onAuthFailedRedirectTo: "/login"
+ },
+ dependencies: [
+ ("react-markdown", "6.0.1"),
+ ("color-hash", "2.0.1")
+ ]
}
-db {
- system: PostgreSQL
-}
-
-auth {
- userEntity: User,
- methods: [ EmailAndPassword ],
- onAuthFailedRedirectTo: "/login"
-}
-
-route "/" -> page Main
-page Main {
+route MainRoute { path: "/", to: MainPage }
+page MainPage {
component: import Main from "@ext/MainPage.js",
authRequired: true
}
-route "/thoughts" -> page Thoughts
-page Thoughts {
+route ThoughtsRoute { path: "/thoughts", to: ThoughtsPage }
+page ThoughtsPage {
component: import Thoughts from "@ext/ThoughtsPage.js",
authRequired: true
}
-route "/login" -> page Login
-page Login {
+route LoginRoute { path: "/login", to: LoginPage }
+page LoginPage {
component: import Login from "@ext/LoginPage.js"
}
-route "/signup" -> page Signup
-page Signup {
+route SignupRoute { path: "/signup", to: SignupPage }
+page SignupPage {
component: import Signup from "@ext/SignupPage"
}
@@ -79,9 +79,4 @@ entity User {=psl
thoughts Thought[]
tags Tag[]
-psl=}
-
-dependencies {=json
- "react-markdown": "6.0.1",
- "color-hash": "2.0.1"
-json=}
\ No newline at end of file
+psl=}
\ No newline at end of file
diff --git a/examples/tutorials/TodoApp/main.wasp b/examples/tutorials/TodoApp/main.wasp
index f630e0a21..c90f882c5 100644
--- a/examples/tutorials/TodoApp/main.wasp
+++ b/examples/tutorials/TodoApp/main.wasp
@@ -1,27 +1,31 @@
app TodoApp {
- title: "Todo app"
-}
+ title: "Todo app",
-auth {
+ auth: {
userEntity: User,
methods: [ EmailAndPassword ],
onAuthFailedRedirectTo: "/login"
+ },
+
+ dependencies: [
+ ("react-clock", "3.0.0")
+ ]
}
-route "/" -> page Main
-page Main {
+route RootRoute { path: "/", to: MainPage }
+page MainPage {
authRequired: true,
component: import Main from "@ext/MainPage.js"
}
-route "/signup" -> page Signup
-page Signup {
- component: import Signup from "@ext/SignupPage"
+route SignupRoute { path: "/signup", to: SignupPage }
+page SignupPage {
+ component: import Signup from "@ext/SignupPage"
}
-route "/login" -> page Login
-page Login {
- component: import Login from "@ext/LoginPage"
+route LoginRoute { path: "/login", to: LoginPage }
+page LoginPage {
+ component: import Login from "@ext/LoginPage"
}
entity User {=psl
@@ -53,7 +57,3 @@ action updateTask {
fn: import { updateTask } from "@ext/actions.js",
entities: [Task]
}
-
-dependencies {=json
- "react-clock": "3.0.0"
-json=}
diff --git a/examples/tutorials/TodoApp/migrations/20201023121126-a/README.md b/examples/tutorials/TodoApp/migrations/20201023121126-a/README.md
deleted file mode 100644
index 7548e4f81..000000000
--- a/examples/tutorials/TodoApp/migrations/20201023121126-a/README.md
+++ /dev/null
@@ -1,57 +0,0 @@
-# Migration `20201023121126-a`
-
-This migration has been generated by Martin Sosic at 10/23/2020, 2:11:26 PM.
-You can check out the [state of the schema](./schema.prisma) after the migration.
-
-## Database Steps
-
-```sql
-CREATE TABLE "User" (
- "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
- "email" TEXT NOT NULL,
- "password" TEXT NOT NULL
-)
-
-CREATE TABLE "Task" (
- "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
- "description" TEXT NOT NULL,
- "isDone" BOOLEAN NOT NULL DEFAULT false
-)
-
-CREATE UNIQUE INDEX "User.email_unique" ON "User"("email")
-```
-
-## Changes
-
-```diff
-diff --git schema.prisma schema.prisma
-migration ..20201023121126-a
---- datamodel.dml
-+++ datamodel.dml
-@@ -1,0 +1,23 @@
-+
-+datasource db {
-+ provider = "sqlite"
-+ url = "***"
-+}
-+
-+generator client {
-+ provider = "prisma-client-js"
-+ output = "../server/node_modules/.prisma/client"
-+}
-+
-+model User {
-+ id Int @id @default(autoincrement())
-+ email String @unique
-+ password String
-+}
-+
-+model Task {
-+ id Int @id @default(autoincrement())
-+ description String
-+ isDone Boolean @default(false)
-+}
-+
-```
-
-
diff --git a/examples/tutorials/TodoApp/migrations/20201023121126-a/schema.prisma b/examples/tutorials/TodoApp/migrations/20201023121126-a/schema.prisma
deleted file mode 100644
index 1a7817e65..000000000
--- a/examples/tutorials/TodoApp/migrations/20201023121126-a/schema.prisma
+++ /dev/null
@@ -1,23 +0,0 @@
-
-datasource db {
- provider = "sqlite"
- url = "***"
-}
-
-generator client {
- provider = "prisma-client-js"
- output = "../server/node_modules/.prisma/client"
-}
-
-model User {
- id Int @id @default(autoincrement())
- email String @unique
- password String
-}
-
-model Task {
- id Int @id @default(autoincrement())
- description String
- isDone Boolean @default(false)
-}
-
diff --git a/examples/tutorials/TodoApp/migrations/20201023121126-a/steps.json b/examples/tutorials/TodoApp/migrations/20201023121126-a/steps.json
deleted file mode 100644
index 93812f939..000000000
--- a/examples/tutorials/TodoApp/migrations/20201023121126-a/steps.json
+++ /dev/null
@@ -1,185 +0,0 @@
-{
- "version": "0.3.14-fixed",
- "steps": [
- {
- "tag": "CreateSource",
- "source": "db"
- },
- {
- "tag": "CreateArgument",
- "location": {
- "tag": "Source",
- "source": "db"
- },
- "argument": "provider",
- "value": "\"sqlite\""
- },
- {
- "tag": "CreateArgument",
- "location": {
- "tag": "Source",
- "source": "db"
- },
- "argument": "url",
- "value": "\"***\""
- },
- {
- "tag": "CreateModel",
- "model": "User"
- },
- {
- "tag": "CreateField",
- "model": "User",
- "field": "id",
- "type": "Int",
- "arity": "Required"
- },
- {
- "tag": "CreateDirective",
- "location": {
- "path": {
- "tag": "Field",
- "model": "User",
- "field": "id"
- },
- "directive": "id"
- }
- },
- {
- "tag": "CreateDirective",
- "location": {
- "path": {
- "tag": "Field",
- "model": "User",
- "field": "id"
- },
- "directive": "default"
- }
- },
- {
- "tag": "CreateArgument",
- "location": {
- "tag": "Directive",
- "path": {
- "tag": "Field",
- "model": "User",
- "field": "id"
- },
- "directive": "default"
- },
- "argument": "",
- "value": "autoincrement()"
- },
- {
- "tag": "CreateField",
- "model": "User",
- "field": "email",
- "type": "String",
- "arity": "Required"
- },
- {
- "tag": "CreateDirective",
- "location": {
- "path": {
- "tag": "Field",
- "model": "User",
- "field": "email"
- },
- "directive": "unique"
- }
- },
- {
- "tag": "CreateField",
- "model": "User",
- "field": "password",
- "type": "String",
- "arity": "Required"
- },
- {
- "tag": "CreateModel",
- "model": "Task"
- },
- {
- "tag": "CreateField",
- "model": "Task",
- "field": "id",
- "type": "Int",
- "arity": "Required"
- },
- {
- "tag": "CreateDirective",
- "location": {
- "path": {
- "tag": "Field",
- "model": "Task",
- "field": "id"
- },
- "directive": "id"
- }
- },
- {
- "tag": "CreateDirective",
- "location": {
- "path": {
- "tag": "Field",
- "model": "Task",
- "field": "id"
- },
- "directive": "default"
- }
- },
- {
- "tag": "CreateArgument",
- "location": {
- "tag": "Directive",
- "path": {
- "tag": "Field",
- "model": "Task",
- "field": "id"
- },
- "directive": "default"
- },
- "argument": "",
- "value": "autoincrement()"
- },
- {
- "tag": "CreateField",
- "model": "Task",
- "field": "description",
- "type": "String",
- "arity": "Required"
- },
- {
- "tag": "CreateField",
- "model": "Task",
- "field": "isDone",
- "type": "Boolean",
- "arity": "Required"
- },
- {
- "tag": "CreateDirective",
- "location": {
- "path": {
- "tag": "Field",
- "model": "Task",
- "field": "isDone"
- },
- "directive": "default"
- }
- },
- {
- "tag": "CreateArgument",
- "location": {
- "tag": "Directive",
- "path": {
- "tag": "Field",
- "model": "Task",
- "field": "isDone"
- },
- "directive": "default"
- },
- "argument": "",
- "value": "false"
- }
- ]
-}
\ No newline at end of file
diff --git a/examples/tutorials/TodoApp/migrations/20201023121536-b/README.md b/examples/tutorials/TodoApp/migrations/20201023121536-b/README.md
deleted file mode 100644
index 146a148e9..000000000
--- a/examples/tutorials/TodoApp/migrations/20201023121536-b/README.md
+++ /dev/null
@@ -1,56 +0,0 @@
-# Migration `20201023121536-b`
-
-This migration has been generated by Martin Sosic at 10/23/2020, 2:15:36 PM.
-You can check out the [state of the schema](./schema.prisma) after the migration.
-
-## Database Steps
-
-```sql
-PRAGMA foreign_keys=OFF;
-CREATE TABLE "new_Task" (
- "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
- "description" TEXT NOT NULL,
- "isDone" BOOLEAN NOT NULL DEFAULT false,
- "userId" INTEGER,
-
- FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE
-);
-INSERT INTO "new_Task" ("id", "description", "isDone") SELECT "id", "description", "isDone" FROM "Task";
-DROP TABLE "Task";
-ALTER TABLE "new_Task" RENAME TO "Task";
-PRAGMA foreign_key_check;
-PRAGMA foreign_keys=ON
-```
-
-## Changes
-
-```diff
-diff --git schema.prisma schema.prisma
-migration 20201023121126-a..20201023121536-b
---- datamodel.dml
-+++ datamodel.dml
-@@ -1,8 +1,8 @@
- datasource db {
- provider = "sqlite"
-- url = "***"
-+ url = "***"
- }
- generator client {
- provider = "prisma-client-js"
-@@ -12,12 +12,15 @@
- model User {
- id Int @id @default(autoincrement())
- email String @unique
- password String
-+ tasks Task[]
- }
- model Task {
- id Int @id @default(autoincrement())
- description String
- isDone Boolean @default(false)
-+ user User? @relation(fields: [userId], references: [id])
-+ userId Int?
- }
-```
-
-
diff --git a/examples/tutorials/TodoApp/migrations/20201023121536-b/schema.prisma b/examples/tutorials/TodoApp/migrations/20201023121536-b/schema.prisma
deleted file mode 100644
index 2c344a06b..000000000
--- a/examples/tutorials/TodoApp/migrations/20201023121536-b/schema.prisma
+++ /dev/null
@@ -1,26 +0,0 @@
-
-datasource db {
- provider = "sqlite"
- url = "***"
-}
-
-generator client {
- provider = "prisma-client-js"
- output = "../server/node_modules/.prisma/client"
-}
-
-model User {
- id Int @id @default(autoincrement())
- email String @unique
- password String
- tasks Task[]
-}
-
-model Task {
- id Int @id @default(autoincrement())
- description String
- isDone Boolean @default(false)
- user User? @relation(fields: [userId], references: [id])
- userId Int?
-}
-
diff --git a/examples/tutorials/TodoApp/migrations/20201023121536-b/steps.json b/examples/tutorials/TodoApp/migrations/20201023121536-b/steps.json
deleted file mode 100644
index 30da01335..000000000
--- a/examples/tutorials/TodoApp/migrations/20201023121536-b/steps.json
+++ /dev/null
@@ -1,65 +0,0 @@
-{
- "version": "0.3.14-fixed",
- "steps": [
- {
- "tag": "CreateField",
- "model": "User",
- "field": "tasks",
- "type": "Task",
- "arity": "List"
- },
- {
- "tag": "CreateField",
- "model": "Task",
- "field": "user",
- "type": "User",
- "arity": "Optional"
- },
- {
- "tag": "CreateDirective",
- "location": {
- "path": {
- "tag": "Field",
- "model": "Task",
- "field": "user"
- },
- "directive": "relation"
- }
- },
- {
- "tag": "CreateArgument",
- "location": {
- "tag": "Directive",
- "path": {
- "tag": "Field",
- "model": "Task",
- "field": "user"
- },
- "directive": "relation"
- },
- "argument": "fields",
- "value": "[userId]"
- },
- {
- "tag": "CreateArgument",
- "location": {
- "tag": "Directive",
- "path": {
- "tag": "Field",
- "model": "Task",
- "field": "user"
- },
- "directive": "relation"
- },
- "argument": "references",
- "value": "[id]"
- },
- {
- "tag": "CreateField",
- "model": "Task",
- "field": "userId",
- "type": "Int",
- "arity": "Optional"
- }
- ]
-}
\ No newline at end of file
diff --git a/examples/tutorials/TodoApp/migrations/20220113204751_init/migration.sql b/examples/tutorials/TodoApp/migrations/20220113204751_init/migration.sql
new file mode 100644
index 000000000..7a12b3624
--- /dev/null
+++ b/examples/tutorials/TodoApp/migrations/20220113204751_init/migration.sql
@@ -0,0 +1,18 @@
+-- CreateTable
+CREATE TABLE "User" (
+ "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
+ "email" TEXT NOT NULL,
+ "password" TEXT NOT NULL
+);
+
+-- CreateTable
+CREATE TABLE "Task" (
+ "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
+ "description" TEXT NOT NULL,
+ "isDone" BOOLEAN NOT NULL DEFAULT false,
+ "userId" INTEGER,
+ FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE SET NULL ON UPDATE CASCADE
+);
+
+-- CreateIndex
+CREATE UNIQUE INDEX "User.email_unique" ON "User"("email");
diff --git a/examples/tutorials/TodoApp/migrations/migrate.lock b/examples/tutorials/TodoApp/migrations/migrate.lock
deleted file mode 100644
index 30e77881e..000000000
--- a/examples/tutorials/TodoApp/migrations/migrate.lock
+++ /dev/null
@@ -1,4 +0,0 @@
-# Prisma Migrate lockfile v1
-
-20201023121126-a
-20201023121536-b
\ No newline at end of file
diff --git a/examples/tutorials/TodoApp/migrations/migration_lock.toml b/examples/tutorials/TodoApp/migrations/migration_lock.toml
new file mode 100644
index 000000000..e5e5c4705
--- /dev/null
+++ b/examples/tutorials/TodoApp/migrations/migration_lock.toml
@@ -0,0 +1,3 @@
+# Please do not edit this file manually
+# It should be added in your version-control system (i.e. Git)
+provider = "sqlite"
\ No newline at end of file
diff --git a/waspc/README.md b/waspc/README.md
index 6eb3cc27d..0a4cf4e40 100644
--- a/waspc/README.md
+++ b/waspc/README.md
@@ -136,12 +136,12 @@ CLI is actually `wasp` executable, and it uses the library, where most of the lo
Wasp compiler takes .wasp files + everything in the `ext/` dir (JS, HTML, ...) and generates a web app that consists of client, server and database.
-Wasp compiler code is split into 2 basic layers: Parser and Generator.
+Wasp compiler code is split into 2 basic layers: Analyzer (frontend) and Generator (backend).
-Wasp file(s) are parsed by Parser into an AST (Abstract Syntax Tree) described in `src/Wasp.hs`.
-Parser is implemented via parser combinators and has no distinct tokenization, syntax and semantic analysis steps, currently it is all one big step.
+Wasp file(s) are analyzed by Analyzer, where they are first parsed, then typechecked, and then evaluated into a central IR (Intermediate Representation), which is `AppSpec` (`src/Wasp/AppSpec.hs`).
+Check `src/Wasp/Analyzer.hs` for more details.
-AST generated by Parser is passed to the Generator, which based on it decides how to generate a web app.
+AppSpec is passed to the Generator, which based on it decides how to generate a web app.
Output of Generator is a list of FileDrafts, where each FileDraft explains how to create a file on the disk.
Therefore, Generator doesn't generate anything itself, instead it provides instructions (FileDrafts) on how to generate the web app.
FileDrafts are using mustache templates a lot (they can be found in `data/Generator/templates`).
diff --git a/waspc/cli/Wasp/Cli/Command/CreateNewProject.hs b/waspc/cli/Wasp/Cli/Command/CreateNewProject.hs
index 51eda62b8..16b92cfa9 100644
--- a/waspc/cli/Wasp/Cli/Command/CreateNewProject.hs
+++ b/waspc/cli/Wasp/Cli/Command/CreateNewProject.hs
@@ -106,8 +106,8 @@ createNewProject' (ProjectName projectName) = do
" title: \"%s\"" `printf` projectName,
"}",
"",
- "route \"/\" -> page Main",
- "page Main {",
+ "route RootRoute { path: \"/\", to: MainPage }",
+ "page MainPage {",
" component: import Main from \"@ext/MainPage.js\"",
"}"
]
diff --git a/waspc/cli/Wasp/Cli/Command/Deps.hs b/waspc/cli/Wasp/Cli/Command/Deps.hs
index 99833ba4b..591b1b64b 100644
--- a/waspc/cli/Wasp/Cli/Command/Deps.hs
+++ b/waspc/cli/Wasp/Cli/Command/Deps.hs
@@ -4,11 +4,12 @@ module Wasp.Cli.Command.Deps
where
import Control.Monad.IO.Class (liftIO)
+import qualified Wasp.AppSpec.App.Dependency as AS.Dependency
import Wasp.Cli.Command (Command)
import Wasp.Cli.Terminal (title)
import qualified Wasp.Generator.ServerGenerator as ServerGenerator
import qualified Wasp.Generator.WebAppGenerator as WebAppGenerator
-import Wasp.NpmDependency (printDep)
+import qualified Wasp.Util.Terminal as Term
deps :: Command ()
deps =
@@ -25,3 +26,9 @@ deps =
title "Webapp dependencies:"
]
++ map printDep WebAppGenerator.waspNpmDeps
+
+printDep :: AS.Dependency.Dependency -> String
+printDep dep =
+ Term.applyStyles [Term.Cyan] (AS.Dependency.name dep)
+ ++ "@"
+ ++ Term.applyStyles [Term.Yellow] (AS.Dependency.version dep)
diff --git a/waspc/cli/Wasp/Cli/Command/Info.hs b/waspc/cli/Wasp/Cli/Command/Info.hs
index 750dd569e..87a706e1e 100644
--- a/waspc/cli/Wasp/Cli/Command/Info.hs
+++ b/waspc/cli/Wasp/Cli/Command/Info.hs
@@ -1,3 +1,5 @@
+{-# LANGUAGE TypeApplications #-}
+
module Wasp.Cli.Command.Info
( info,
)
@@ -8,19 +10,18 @@ import Control.Monad.IO.Class (liftIO)
import StrongPath (Abs, Dir, Path', fromAbsFile, fromRelFile, toFilePath)
import StrongPath.Operations
import System.Directory (doesFileExist, getFileSize)
+import qualified Wasp.Analyzer as Analyzer
+import qualified Wasp.AppSpec as AS
+import qualified Wasp.AppSpec.App as AS.App
import Wasp.Cli.Command (Command)
-import Wasp.Cli.Command.Common
- ( findWaspProjectRootDirFromCwd,
- waspSaysC,
- )
+import Wasp.Cli.Command.Common (findWaspProjectRootDirFromCwd, waspSaysC)
import qualified Wasp.Cli.Common as Cli.Common
import Wasp.Cli.Terminal (title)
import Wasp.Common (WaspProjectDir)
+import Wasp.Error (showCompilerErrorForTerminal)
import Wasp.Lib (findWaspFile)
-import qualified Wasp.Parser
import Wasp.Util.IO (listDirectoryDeep)
import qualified Wasp.Util.Terminal as Term
-import Wasp.Wasp (Wasp, appName, getApp)
info :: Command ()
info =
@@ -28,17 +29,17 @@ info =
waspDir <- findWaspProjectRootDirFromCwd
compileInfo <- liftIO $ readCompileInformation waspDir
projectSize <- liftIO $ readDirectorySizeMB waspDir
- waspAstOrError <- liftIO $ parseWaspFile waspDir
- case waspAstOrError of
+ declsOrError <- liftIO $ parseWaspFile waspDir
+ case declsOrError of
Left err -> waspSaysC err
- Right wasp -> do
+ Right decls -> do
waspSaysC $
unlines
[ "",
title "Project information",
printInfo
"Name"
- (appName $ getApp wasp),
+ (fst $ head $ AS.takeDecls @AS.App.App decls),
printInfo
"Last compile"
compileInfo,
@@ -55,17 +56,28 @@ readDirectorySizeMB path = (++ " MB") . show . (`div` 1000000) . sum <$> (listDi
readCompileInformation :: Path' Abs (Dir WaspProjectDir) -> IO String
readCompileInformation waspDir = do
- let dotWaspInfoFile = fromAbsFile $ waspDir > Cli.Common.dotWaspDirInWaspProjectDir > Cli.Common.generatedCodeDirInDotWaspDir > Cli.Common.dotWaspInfoFileInGeneratedCodeDir
+ let dotWaspInfoFile =
+ fromAbsFile $
+ waspDir > Cli.Common.dotWaspDirInWaspProjectDir
+ > Cli.Common.generatedCodeDirInDotWaspDir
+ > Cli.Common.dotWaspInfoFileInGeneratedCodeDir
dotWaspInfoFileExists <- doesFileExist dotWaspInfoFile
if dotWaspInfoFileExists
then do readFile dotWaspInfoFile
else return "No compile information found"
-parseWaspFile :: Path' Abs (Dir WaspProjectDir) -> IO (Either String Wasp)
+parseWaspFile :: Path' Abs (Dir WaspProjectDir) -> IO (Either String [AS.Decl])
parseWaspFile waspDir = do
maybeWaspFile <- findWaspFile waspDir
case maybeWaspFile of
Nothing -> return (Left "Couldn't find a single *.wasp file.")
- Just waspFile -> do
- waspStr <- readFile (toFilePath waspFile)
- return $ left (("Couldn't parse .wasp file: " <>) . show) $ Wasp.Parser.parseWasp waspStr
+ Just waspFile ->
+ do
+ waspStr <- readFile (toFilePath waspFile)
+ return $
+ left
+ ( ("Couldn't parse .wasp file:\n" <>)
+ . showCompilerErrorForTerminal (waspFile, waspStr)
+ . Analyzer.getErrorMessageAndCtx
+ )
+ $ Analyzer.analyze waspStr
diff --git a/waspc/data/Cli/templates/new/ext/MainPage.js b/waspc/data/Cli/templates/new/ext/MainPage.js
index 56395e6c0..529a4f286 100644
--- a/waspc/data/Cli/templates/new/ext/MainPage.js
+++ b/waspc/data/Cli/templates/new/ext/MainPage.js
@@ -12,7 +12,7 @@ const MainPage = () => {
Welcome to Wasp - you just started a new app!
- This is page Main located at route /.
+ This is page MainPage located at route /.
Open ext/MainPage.js to edit it.
diff --git a/waspc/data/Generator/templates/react-app/README.md b/waspc/data/Generator/templates/react-app/README.md
index 304158a25..9d9614c4f 100644
--- a/waspc/data/Generator/templates/react-app/README.md
+++ b/waspc/data/Generator/templates/react-app/README.md
@@ -1,4 +1,3 @@
-{{={{> <}}=}}
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
## Available Scripts
diff --git a/waspc/data/Generator/templates/react-app/gitignore b/waspc/data/Generator/templates/react-app/gitignore
index 21e71a8d8..4d29575de 100644
--- a/waspc/data/Generator/templates/react-app/gitignore
+++ b/waspc/data/Generator/templates/react-app/gitignore
@@ -1,4 +1,3 @@
-{{={{> <}}=}}
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
diff --git a/waspc/data/Generator/templates/react-app/package.json b/waspc/data/Generator/templates/react-app/package.json
index 97137e638..f0e086df5 100644
--- a/waspc/data/Generator/templates/react-app/package.json
+++ b/waspc/data/Generator/templates/react-app/package.json
@@ -1,6 +1,6 @@
{{={= =}=}}
{
- "name": "{= wasp.app.name =}",
+ "name": "{= appName =}",
"version": "0.0.0",
"private": true,
{=& depsChunk =},
diff --git a/waspc/data/Generator/templates/react-app/public/manifest.json b/waspc/data/Generator/templates/react-app/public/manifest.json
index b1942a294..302b5fccd 100644
--- a/waspc/data/Generator/templates/react-app/public/manifest.json
+++ b/waspc/data/Generator/templates/react-app/public/manifest.json
@@ -1,6 +1,6 @@
-{{={{> <}}=}}
+{{={= =}=}}
{
- "name": "{{> app.name <}}",
+ "name": "{= appName =}",
"icons": [
{
"src": "favicon.ico",
diff --git a/waspc/data/Generator/templates/react-app/src/index.css b/waspc/data/Generator/templates/react-app/src/index.css
index 30e3447ab..cee5f348f 100644
--- a/waspc/data/Generator/templates/react-app/src/index.css
+++ b/waspc/data/Generator/templates/react-app/src/index.css
@@ -1,4 +1,3 @@
-{{={{> <}}=}}
body {
margin: 0;
padding: 0;
diff --git a/waspc/data/Generator/templates/react-app/src/index.js b/waspc/data/Generator/templates/react-app/src/index.js
index 9c6afda1d..5ad5730d4 100644
--- a/waspc/data/Generator/templates/react-app/src/index.js
+++ b/waspc/data/Generator/templates/react-app/src/index.js
@@ -1,4 +1,3 @@
-{{={= =}=}}
import React from 'react'
import ReactDOM from 'react-dom'
import { ReactQueryCacheProvider } from 'react-query'
diff --git a/waspc/data/Generator/templates/react-app/src/operations/index.js b/waspc/data/Generator/templates/react-app/src/operations/index.js
index e38aa06ae..647398dc4 100644
--- a/waspc/data/Generator/templates/react-app/src/operations/index.js
+++ b/waspc/data/Generator/templates/react-app/src/operations/index.js
@@ -1,4 +1,3 @@
-{{={= =}=}}
import api, { handleApiError } from '../api.js'
import config from '../config.js'
diff --git a/waspc/data/Generator/templates/react-app/src/serviceWorker.js b/waspc/data/Generator/templates/react-app/src/serviceWorker.js
index ff8bab1dd..f8c7e50c2 100644
--- a/waspc/data/Generator/templates/react-app/src/serviceWorker.js
+++ b/waspc/data/Generator/templates/react-app/src/serviceWorker.js
@@ -1,4 +1,3 @@
-{{={{> <}}=}}
// This optional code is used to register a service worker.
// register() is not called by default.
diff --git a/waspc/examples/todoApp/todoApp.wasp b/waspc/examples/todoApp/todoApp.wasp
index 613bf6d69..121a6000e 100644
--- a/waspc/examples/todoApp/todoApp.wasp
+++ b/waspc/examples/todoApp/todoApp.wasp
@@ -1,19 +1,20 @@
app todoApp {
- title: "ToDo App",
- head: [
- ""
- ]
-}
-
-dependencies {=json
- "@material-ui/core": "4.11.3"
-json=}
-
-auth {
+ title: "ToDo App",
+ head: [
+ ""
+ ],
+ dependencies: [
+ ("@material-ui/core", "4.11.3")
+ ],
+ auth: {
userEntity: User,
methods: [ EmailAndPassword ],
onAuthFailedRedirectTo: "/login",
onAuthSucceededRedirectTo: "/profile"
+ },
+ server: {
+ setupFn: import setup from "@ext/serverSetup.js"
+ }
}
entity User {=psl
@@ -31,43 +32,40 @@ entity Task {=psl
userId Int
psl=}
-server {
- setupFn: import setup from "@ext/serverSetup.js"
+
+route SignupRoute { path: "/signup", to: SignupPage }
+page SignupPage {
+ component: import Signup from "@ext/pages/auth/Signup"
}
-route "/signup" -> page Signup
-page Signup {
- component: import Signup from "@ext/pages/auth/Signup"
+route LoginRoute { path: "/login", to: LoginPage }
+page LoginPage {
+ component: import Login from "@ext/pages/auth/Login"
}
-route "/login" -> page Login
-page Login {
- component: import Login from "@ext/pages/auth/Login"
+route HomeRoute { path: "/", to: MainPage }
+page MainPage {
+ authRequired: true,
+ component: import Main from "@ext/pages/Main"
}
-route "/" -> page Main
-page Main {
- authRequired: true,
- component: import Main from "@ext/pages/Main"
+route AboutRoute { path: "/about", to: AboutPage }
+page AboutPage {
+ component: import About from "@ext/pages/About"
}
-route "/about" -> page About
-page About {
- component: import About from "@ext/pages/About"
-}
-
-route "/profile" -> page Profile
-page Profile {
- authRequired: true,
- component: import { ProfilePage } from "@ext/pages/ProfilePage"
+route ProfileRoute { path: "/profile", to: ProfilePage }
+page ProfilePage {
+ authRequired: true,
+ component: import { ProfilePage } from "@ext/pages/ProfilePage"
}
// Page for viewing a specific task
//
-route "/task/:id" -> page Task
-page Task {
- authRequired: true,
- component: import Task from "@ext/pages/Task"
+route TaskRoute { path: "/task/:id", to: TaskPage }
+page TaskPage {
+ authRequired: true,
+ component: import Task from "@ext/pages/Task"
}
// --------- Queries --------- //
@@ -79,7 +77,7 @@ query getTasks {
query getNumTasks {
fn: import { getNumTasks } from "@ext/queries.js",
- entities: [Task],
+
auth: false
}
diff --git a/waspc/images/waspc-implementation-diagram.png b/waspc/images/waspc-implementation-diagram.png
index ac5fae6c4..3efc2cecd 100644
Binary files a/waspc/images/waspc-implementation-diagram.png and b/waspc/images/waspc-implementation-diagram.png differ
diff --git a/waspc/package.yaml b/waspc/package.yaml
index 940262e37..390bec117 100644
--- a/waspc/package.yaml
+++ b/waspc/package.yaml
@@ -76,7 +76,7 @@ library:
- unliftio
- array # Used by code generated by Alex for src/Analyzer/Parser/Lexer.x
- mtl
- - strong-path
+ - strong-path >= 1.1.2.0
- template-haskell
- path-io
diff --git a/waspc/src/Wasp/Analyzer/Evaluator/Evaluation/TypedExpr/Combinators.hs b/waspc/src/Wasp/Analyzer/Evaluator/Evaluation/TypedExpr/Combinators.hs
index bf4b6559e..abf406c20 100644
--- a/waspc/src/Wasp/Analyzer/Evaluator/Evaluation/TypedExpr/Combinators.hs
+++ b/waspc/src/Wasp/Analyzer/Evaluator/Evaluation/TypedExpr/Combinators.hs
@@ -19,6 +19,8 @@ module Wasp.Analyzer.Evaluator.Evaluation.TypedExpr.Combinators
where
import Control.Arrow (left)
+import Data.List (stripPrefix)
+import qualified StrongPath as SP
import Wasp.Analyzer.Evaluator.Evaluation.Internal (evaluation, evaluation', runEvaluation)
import Wasp.Analyzer.Evaluator.Evaluation.TypedExpr (TypedExprEvaluation)
import qualified Wasp.Analyzer.Evaluator.EvaluationError as ER
@@ -152,8 +154,24 @@ tuple4 eval1 eval2 eval3 eval4 = evaluation $ \(typeDefs, bindings) -> withCtx $
-- | An evaluation that expects an "ExtImport".
extImport :: TypedExprEvaluation AppSpec.ExtImport.ExtImport
extImport = evaluation' . withCtx $ \ctx -> \case
- TypedAST.ExtImport name file -> pure $ AppSpec.ExtImport.ExtImport name file
+ TypedAST.ExtImport name extFileFP ->
+ -- NOTE(martin): This parsing here could instead be done in Parser.
+ -- I don't have a very good reason for doing it here instead of Parser, except
+ -- for being somewhat simpler to implement.
+ -- So we might want to move it to Parser at some point in the future, if we
+ -- figure out that is better (it sounds/feels like it could be).
+ case stripPrefix extPrefix extFileFP of
+ Just relFileFP -> case SP.parseRelFileP relFileFP of
+ Left err -> Left $ ER.mkEvaluationError ctx $ ER.ParseError $ ER.EvaluationParseError $ show err
+ Right relFileSP -> pure $ AppSpec.ExtImport.ExtImport name relFileSP
+ Nothing ->
+ Left $
+ ER.mkEvaluationError ctx $
+ ER.ParseError $
+ ER.EvaluationParseError $ "Path in external import must start with \"" ++ extPrefix ++ "\"!"
expr -> Left $ ER.mkEvaluationError ctx $ ER.ExpectedType T.ExtImportType (TypedAST.exprType expr)
+ where
+ extPrefix = "@ext/"
-- | An evaluation that expects a "JSON".
json :: TypedExprEvaluation AppSpec.JSON.JSON
diff --git a/waspc/src/Wasp/Analyzer/Parser/Lexer.x b/waspc/src/Wasp/Analyzer/Parser/Lexer.x
index 27076d474..c5f0fc60b 100644
--- a/waspc/src/Wasp/Analyzer/Parser/Lexer.x
+++ b/waspc/src/Wasp/Analyzer/Parser/Lexer.x
@@ -29,12 +29,16 @@ $any = [.$white]
@double = "-"? $digit+ "." $digit+
@integer = "-"? $digit+
@ident = $identstart $ident* "'"*
+@linecomment = "//" [^\n\r]*
+@blockcomment = "/*" (("*"[^\/]) | [^\*] | $white)* "*/" -- Based on https://stackoverflow.com/a/16165598/1509394 .
-- Tokenization rules (regex -> token)
tokens :-
--- Skips whitespace
+-- Skips whitespace and comments
<0> $white+ ;
+<0> @linecomment ;
+<0> @blockcomment ;
-- Quoter rules:
-- Uses Alex start codes to lex quoted characters with different rules:
diff --git a/waspc/src/Wasp/Analyzer/Parser/ParseError.hs b/waspc/src/Wasp/Analyzer/Parser/ParseError.hs
index 3258a5134..92130f967 100644
--- a/waspc/src/Wasp/Analyzer/Parser/ParseError.hs
+++ b/waspc/src/Wasp/Analyzer/Parser/ParseError.hs
@@ -7,7 +7,7 @@ module Wasp.Analyzer.Parser.ParseError
where
import Wasp.Analyzer.Parser.Ctx (Ctx, WithCtx (..), ctxFromPos, ctxFromRgn, getCtxRgn)
-import Wasp.Analyzer.Parser.SourcePosition (SourcePosition)
+import Wasp.Analyzer.Parser.SourcePosition (SourcePosition (..))
import Wasp.Analyzer.Parser.SourceRegion (getRgnEnd, getRgnStart)
import Wasp.Analyzer.Parser.Token (Token (..))
@@ -40,7 +40,9 @@ getErrorMessageAndCtx = \case
"Expected one of the following tokens instead: "
++ unwords expectedTokens
in unexpectedTokenMessage ++ if not (null expectedTokens) then "\n" ++ expectedTokensMessage else "",
- ctxFromPos $ tokenStartPosition unexpectedToken
+ let tokenStartPos@(SourcePosition sl sc) = tokenStartPosition unexpectedToken
+ tokenEndPos = SourcePosition sl (sc + length (tokenLexeme unexpectedToken) - 1)
+ in ctxFromRgn tokenStartPos tokenEndPos
)
QuoterDifferentTags (WithCtx lctx ltag) (WithCtx rctx rtag) ->
let ctx = ctxFromRgn (getRgnStart $ getCtxRgn lctx) (getRgnEnd $ getCtxRgn rctx)
diff --git a/waspc/src/Wasp/Analyzer/TypeDefinitions/TH/Decl.hs b/waspc/src/Wasp/Analyzer/TypeDefinitions/TH/Decl.hs
index 9d29263b3..224f2ae97 100644
--- a/waspc/src/Wasp/Analyzer/TypeDefinitions/TH/Decl.hs
+++ b/waspc/src/Wasp/Analyzer/TypeDefinitions/TH/Decl.hs
@@ -275,12 +275,11 @@ waspKindOfHaskellType typ = do
maybeCustomEvaluationKind <- tryCastingToCustomEvaluationKind typ
maybeRecordKind <- tryCastingToRecordKind typ
maybe (fail $ "No translation to wasp type for type " ++ show typ) return $
- maybeDeclRefKind
+ -- NOTE: It is important to have @maybeCustomEvaluationKind@ first, since we want it to override
+ -- any of the hardcoded kind assignments below.
+ maybeCustomEvaluationKind
+ <|> maybeDeclRefKind
<|> maybeEnumKind
- -- NOTE: It is important that @maybeCustomEvaluationKind@ is before @maybeRecordKind@,
- -- since having a custom evaluation should override typical record evalution, if type is a record.
- <|> maybeCustomEvaluationKind
- <|> maybeRecordKind
<|> case typ of
ConT name
| name == ''String -> pure KString
@@ -295,6 +294,10 @@ waspKindOfHaskellType typ = do
TupleT 3 `AppT` t1 `AppT` t2 `AppT` t3 -> pure (KTuple (t1, t2, [t3]))
TupleT 4 `AppT` t1 `AppT` t2 `AppT` t3 `AppT` t4 -> pure (KTuple (t1, t2, [t3, t4]))
_ -> Nothing
+ -- NOTE: It is important that @maybeRecordKind@ is last, in case there are some other custom/specialized
+ -- kind assignments for a specific record type -> in that case we want those to be used,
+ -- instead of a generic record kind assignment.
+ <|> maybeRecordKind
where
tryCastingToDeclRefKind :: Type -> Q (Maybe WaspKind)
tryCastingToDeclRefKind (ConT name `AppT` subType) | name == ''Ref = do
diff --git a/waspc/src/Wasp/AppSpec.hs b/waspc/src/Wasp/AppSpec.hs
index dc0fa1baf..2b1a85d18 100644
--- a/waspc/src/Wasp/AppSpec.hs
+++ b/waspc/src/Wasp/AppSpec.hs
@@ -1,11 +1,34 @@
+{-# LANGUAGE TypeApplications #-}
+
module Wasp.AppSpec
( AppSpec (..),
+ Decl,
+ getDecls,
+ takeDecls,
+ Ref,
+ refName,
+ getApp,
+ getActions,
+ getQueries,
+ getEntities,
+ getPages,
+ getRoutes,
+ isAuthEnabled,
)
where
+import Data.Maybe (isJust)
import StrongPath (Abs, Dir, File', Path')
-import Wasp.AppSpec.Core.Decl (Decl)
+import Wasp.AppSpec.Action (Action)
+import Wasp.AppSpec.App (App)
+import qualified Wasp.AppSpec.App as App
+import Wasp.AppSpec.Core.Decl (Decl, IsDecl, takeDecls)
+import Wasp.AppSpec.Core.Ref (Ref, refName)
+import Wasp.AppSpec.Entity (Entity)
import qualified Wasp.AppSpec.ExternalCode as ExternalCode
+import Wasp.AppSpec.Page (Page)
+import Wasp.AppSpec.Query (Query)
+import Wasp.AppSpec.Route (Route)
import Wasp.Common (DbMigrationsDir)
-- | AppSpec is the main/central intermediate representation (IR) of the whole Wasp compiler,
@@ -21,6 +44,46 @@ data AppSpec = AppSpec
externalCodeDirPath :: !(Path' Abs (Dir ExternalCode.SourceExternalCodeDir)),
-- | Absolute path to the directory in wasp project source that contains database migrations.
migrationsDir :: Maybe (Path' Abs (Dir DbMigrationsDir)),
+ -- | Absolute path to the .env file in wasp project source. It contains env variables to be
+ -- provided to the server only during the development.
dotEnvFile :: Maybe (Path' Abs File'),
+ -- | If true, it means project is being compiled for production/deployment -> it is being "built".
+ -- If false, it means project is being compiled for development purposes (e.g. "wasp start").
isBuild :: Bool
}
+
+-- TODO: Make this return "Named" declarations?
+-- We would have something like NamedDecl or smth like that. Or at least have a @type Named@ or smth like that.
+-- Or @WithName@ or just @Named@.
+-- I like the best: `newtype Named a = Named (String, a)`
+-- I created a github issue for it: https://github.com/wasp-lang/wasp/issues/426 .
+getDecls :: IsDecl a => AppSpec -> [(String, a)]
+getDecls = takeDecls . decls
+
+-- TODO: This will fail with an error if there is no `app` declaration (because of `head`)!
+-- However, returning a Maybe here would be PITA later in the code.
+-- It would be cool instead if we had an extra step that somehow ensures that app exists and
+-- throws nice error if it doesn't. Some step that validated AppSpec. Maybe we could
+-- have a function that returns `Validated AppSpec` -> so basically smart constructor,
+-- validates AppSpec and returns it wrapped with `Validated`,
+-- I created a github issue for it: https://github.com/wasp-lang/wasp/issues/425 .
+getApp :: AppSpec -> (String, App)
+getApp spec = head $ takeDecls @App (decls spec)
+
+getQueries :: AppSpec -> [(String, Query)]
+getQueries spec = takeDecls @Query (decls spec)
+
+getActions :: AppSpec -> [(String, Action)]
+getActions spec = takeDecls @Action (decls spec)
+
+getEntities :: AppSpec -> [(String, Entity)]
+getEntities spec = takeDecls @Entity (decls spec)
+
+getPages :: AppSpec -> [(String, Page)]
+getPages spec = takeDecls @Page (decls spec)
+
+getRoutes :: AppSpec -> [(String, Route)]
+getRoutes spec = takeDecls @Route (decls spec)
+
+isAuthEnabled :: AppSpec -> Bool
+isAuthEnabled spec = isJust (App.auth $ snd $ getApp spec)
diff --git a/waspc/src/Wasp/AppSpec/App.hs b/waspc/src/Wasp/AppSpec/App.hs
index bc3c7cc4e..a05be3174 100644
--- a/waspc/src/Wasp/AppSpec/App.hs
+++ b/waspc/src/Wasp/AppSpec/App.hs
@@ -12,12 +12,9 @@ import Wasp.AppSpec.Core.Decl (IsDecl)
data App = App
{ title :: String,
head :: Maybe [String],
- auth :: Maybe Auth, -- NOTE: This is new. Before, `auth` was a standalone declaration.
- server :: Maybe Server, -- NOTE: This is new. Before, `server` was a standalone declaration.
- db :: Maybe Db, -- NOTE: This is new. Before, `db` was a standalone declaration.
-
- -- | NOTE: This is new. Before, `dependencies` was a standalone declaration and it was a {=json json=},
- -- while now it is a [{ name :: String, version :: String }].
+ auth :: Maybe Auth,
+ server :: Maybe Server,
+ db :: Maybe Db,
dependencies :: Maybe [Dependency]
}
deriving (Show, Eq, Data)
diff --git a/waspc/src/Wasp/AppSpec/App/Dependency.hs b/waspc/src/Wasp/AppSpec/App/Dependency.hs
index 8f068619a..3dbd8b2c6 100644
--- a/waspc/src/Wasp/AppSpec/App/Dependency.hs
+++ b/waspc/src/Wasp/AppSpec/App/Dependency.hs
@@ -2,6 +2,7 @@
module Wasp.AppSpec.App.Dependency
( Dependency (..),
+ fromList,
)
where
@@ -12,3 +13,6 @@ data Dependency = Dependency
version :: String
}
deriving (Show, Eq, Data)
+
+fromList :: [(String, String)] -> [Dependency]
+fromList = map (\(n, v) -> Dependency {name = n, version = v})
diff --git a/waspc/src/Wasp/AppSpec/Core/Ref.hs b/waspc/src/Wasp/AppSpec/Core/Ref.hs
index f9c97cc0e..8a20768cd 100644
--- a/waspc/src/Wasp/AppSpec/Core/Ref.hs
+++ b/waspc/src/Wasp/AppSpec/Core/Ref.hs
@@ -4,6 +4,7 @@
module Wasp.AppSpec.Core.Ref
( Ref (..),
+ refName,
)
where
@@ -21,3 +22,6 @@ deriving instance Eq a => Eq (Ref a)
deriving instance Show a => Show (Ref a)
deriving instance (IsDecl a, Data a) => Data (Ref a)
+
+refName :: Ref a -> String
+refName (Ref name) = name
diff --git a/waspc/src/Wasp/AppSpec/ExtImport.hs b/waspc/src/Wasp/AppSpec/ExtImport.hs
index d5809cf7c..22e3cbcd1 100644
--- a/waspc/src/Wasp/AppSpec/ExtImport.hs
+++ b/waspc/src/Wasp/AppSpec/ExtImport.hs
@@ -7,11 +7,18 @@ module Wasp.AppSpec.ExtImport
where
import Data.Data (Data)
+import StrongPath (File', Path, Posix, Rel)
+import Wasp.AppSpec.ExternalCode (SourceExternalCodeDir)
-data ExtImport = ExtImport ExtImportName ExtImportPath
+data ExtImport = ExtImport
+ { -- | What is being imported.
+ name :: ExtImportName,
+ -- | Path from which we are importing.
+ path :: ExtImportPath
+ }
deriving (Show, Eq, Data)
-type ExtImportPath = String
+type ExtImportPath = Path Posix (Rel SourceExternalCodeDir) File'
type Identifier = String
diff --git a/waspc/src/Wasp/AppSpec/ExternalCode.hs b/waspc/src/Wasp/AppSpec/ExternalCode.hs
index e3f73b7f7..7f83db731 100644
--- a/waspc/src/Wasp/AppSpec/ExternalCode.hs
+++ b/waspc/src/Wasp/AppSpec/ExternalCode.hs
@@ -1,3 +1,5 @@
+{-# LANGUAGE DeriveDataTypeable #-}
+
module Wasp.AppSpec.ExternalCode
( -- | Wasp project consists of Wasp code (.wasp files) and external code (e.g. .js files) that is
-- used/referenced by the Wasp code.
@@ -14,13 +16,14 @@ module Wasp.AppSpec.ExternalCode
)
where
+import Data.Data (Data)
import Data.Text (Text)
import qualified Data.Text.Lazy as TextL
import StrongPath (Abs, Dir, File', Path', Rel, (>))
-- | Directory in Wasp source that contains external code.
-- External code files are obtained from it.
-data SourceExternalCodeDir
+data SourceExternalCodeDir deriving (Data)
data File = File
{ _pathInExtCodeDir :: !(Path' (Rel SourceExternalCodeDir) File'),
diff --git a/waspc/src/Wasp/AppSpec/Operation.hs b/waspc/src/Wasp/AppSpec/Operation.hs
new file mode 100644
index 000000000..e062d8013
--- /dev/null
+++ b/waspc/src/Wasp/AppSpec/Operation.hs
@@ -0,0 +1,38 @@
+module Wasp.AppSpec.Operation
+ ( Operation (..),
+ getName,
+ getFn,
+ getEntities,
+ getAuth,
+ )
+where
+
+import Wasp.AppSpec.Action (Action)
+import qualified Wasp.AppSpec.Action as Action
+import Wasp.AppSpec.Core.Ref (Ref)
+import Wasp.AppSpec.Entity (Entity)
+import Wasp.AppSpec.ExtImport (ExtImport)
+import Wasp.AppSpec.Query (Query)
+import qualified Wasp.AppSpec.Query as Query
+
+-- | Common "interface" for queries and actions.
+data Operation
+ = QueryOp String Query
+ | ActionOp String Action
+ deriving (Show)
+
+getName :: Operation -> String
+getName (QueryOp name _) = name
+getName (ActionOp name _) = name
+
+getFn :: Operation -> ExtImport
+getFn (QueryOp _ query) = Query.fn query
+getFn (ActionOp _ action) = Action.fn action
+
+getEntities :: Operation -> Maybe [Ref Entity]
+getEntities (QueryOp _ query) = Query.entities query
+getEntities (ActionOp _ action) = Action.entities action
+
+getAuth :: Operation -> Maybe Bool
+getAuth (QueryOp _ query) = Query.auth query
+getAuth (ActionOp _ action) = Action.auth action
diff --git a/waspc/src/Wasp/AppSpec/Route.hs b/waspc/src/Wasp/AppSpec/Route.hs
index a2731beff..8f46eac26 100644
--- a/waspc/src/Wasp/AppSpec/Route.hs
+++ b/waspc/src/Wasp/AppSpec/Route.hs
@@ -10,7 +10,6 @@ import Wasp.AppSpec.Core.Decl (IsDecl)
import Wasp.AppSpec.Core.Ref (Ref)
import Wasp.AppSpec.Page
--- | NOTE: We have new syntax for route, before it was `route "/task" -> page Task`, now it is a dictionary.
data Route = Route
{ path :: String,
-- TODO: In the future we might want to add other types of targets, for example another Route.
diff --git a/waspc/src/Wasp/Error.hs b/waspc/src/Wasp/Error.hs
new file mode 100644
index 000000000..947d82fb3
--- /dev/null
+++ b/waspc/src/Wasp/Error.hs
@@ -0,0 +1,76 @@
+module Wasp.Error (showCompilerErrorForTerminal) where
+
+import Data.List (intercalate)
+import StrongPath (Abs, File', Path')
+import qualified StrongPath as SP
+import Wasp.Analyzer.Parser.Ctx (Ctx, getCtxRgn)
+import Wasp.Analyzer.Parser.SourcePosition (SourcePosition (..))
+import Wasp.Analyzer.Parser.SourceRegion (SourceRegion (..))
+import Wasp.Util (indent, insertAt, leftPad)
+import qualified Wasp.Util.Terminal as T
+
+-- | Transforms compiler error (error with parse context) into an informative, pretty String that
+-- can be printed directly into the terminal. It uses terminal features like escape codes
+-- (colors, styling, ...).
+showCompilerErrorForTerminal :: (Path' Abs File', String) -> (String, Ctx) -> String
+showCompilerErrorForTerminal (waspFilePath, waspFileContent) (errMsg, errCtx) =
+ let srcRegion = getCtxRgn errCtx
+ in intercalate
+ "\n"
+ [ SP.fromAbsFile waspFilePath ++ " @ " ++ showRgn srcRegion,
+ indent 2 errMsg,
+ "",
+ indent 2 $ prettyShowSrcLinesOfErrorRgn waspFileContent srcRegion
+ ]
+
+showRgn :: SourceRegion -> String
+showRgn (SourceRegion (SourcePosition l1 c1) (SourcePosition l2 c2))
+ | l1 == l2 && c1 == c2 = showPos l1 c1
+ | l1 == l2 && c1 /= c2 = show l1 ++ ":" ++ show c1 ++ "-" ++ show c2
+ | otherwise = showPos l1 c1 ++ " - " ++ showPos l2 c2
+ where
+ showPos l c = show l ++ ":" ++ show c
+
+-- | Given wasp source and error region in it, extracts source lines
+-- that are in the given error region and then nicely displays them,
+-- by coloring in red the exact error region part of the code and also
+-- by prefixing all the lines with their line number (colored yellow).
+-- Uses terminal features for styling, like escape codes and similar.
+prettyShowSrcLinesOfErrorRgn :: String -> SourceRegion -> String
+prettyShowSrcLinesOfErrorRgn
+ waspFileContent
+ ( SourceRegion
+ (SourcePosition startLineNum startColNum)
+ (SourcePosition endLineNum endColNum)
+ ) =
+ let srcLines =
+ zip [max 1 (startLineNum - numCtxLines) ..] $
+ take (endLineNum - startLineNum + 1 + numCtxLines * 2) $
+ drop (startLineNum - 1 - numCtxLines) $
+ lines waspFileContent
+ srcLinesWithMarkedErrorRgn =
+ map
+ ( \(lineNum, line) ->
+ let lineContainsError = lineNum >= startLineNum && lineNum <= endLineNum
+ lineWithStylingStartAndEnd =
+ if lineNum == startLineNum
+ then insertAt stylingStart (startColNum - 1) lineWithStylingEnd
+ else stylingStart ++ lineWithStylingEnd
+ lineWithStylingEnd =
+ if lineNum == endLineNum
+ then insertAt stylingEnd endColNum line
+ else line ++ stylingEnd
+ stylingStart = T.escapeCode ++ T.styleCode T.Red
+ stylingEnd = T.escapeCode ++ T.resetCode
+ in (lineNum, if lineContainsError then lineWithStylingStartAndEnd else line)
+ )
+ srcLines
+ srcLinesWithMarkedErrorRgnAndLineNumber =
+ map
+ (\(lineNum, line) -> T.applyStyles [T.Yellow] (leftPad ' ' 6 (show lineNum) ++ " | ") ++ line)
+ srcLinesWithMarkedErrorRgn
+ in intercalate "\n" srcLinesWithMarkedErrorRgnAndLineNumber
+ where
+ -- Number of lines to show before and after the source region containing error.
+ numCtxLines :: Int
+ numCtxLines = 1
diff --git a/waspc/src/Wasp/Generator.hs b/waspc/src/Wasp/Generator.hs
index ce26594c1..7720acc31 100644
--- a/waspc/src/Wasp/Generator.hs
+++ b/waspc/src/Wasp/Generator.hs
@@ -12,7 +12,7 @@ import qualified Data.Version
import qualified Paths_waspc
import StrongPath (Abs, Dir, Path', relfile, (>))
import qualified StrongPath as SP
-import Wasp.CompileOptions (CompileOptions)
+import Wasp.AppSpec (AppSpec)
import Wasp.Generator.Common (ProjectRootDir)
import Wasp.Generator.DbGenerator (genDb)
import qualified Wasp.Generator.DbGenerator as DbGenerator
@@ -23,21 +23,20 @@ import qualified Wasp.Generator.ServerGenerator as ServerGenerator
import qualified Wasp.Generator.Setup
import qualified Wasp.Generator.Start
import Wasp.Generator.WebAppGenerator (generateWebApp)
-import Wasp.Wasp (Wasp)
-- | Generates web app code from given Wasp and writes it to given destination directory.
-- If dstDir does not exist yet, it will be created.
-- NOTE(martin): What if there is already smth in the dstDir? It is probably best
-- if we clean it up first? But we don't want this to end up with us deleting stuff
-- from user's machine. Maybe we just overwrite and we are good?
-writeWebAppCode :: Wasp -> Path' Abs (Dir ProjectRootDir) -> CompileOptions -> IO ()
-writeWebAppCode wasp dstDir compileOptions = do
- writeFileDrafts dstDir (generateWebApp wasp compileOptions)
- ServerGenerator.preCleanup wasp dstDir compileOptions
- writeFileDrafts dstDir (genServer wasp compileOptions)
- DbGenerator.preCleanup wasp dstDir compileOptions
- writeFileDrafts dstDir (genDb wasp compileOptions)
- writeFileDrafts dstDir (genDockerFiles wasp compileOptions)
+writeWebAppCode :: AppSpec -> Path' Abs (Dir ProjectRootDir) -> IO ()
+writeWebAppCode spec dstDir = do
+ writeFileDrafts dstDir (generateWebApp spec)
+ ServerGenerator.preCleanup spec dstDir
+ writeFileDrafts dstDir (genServer spec)
+ DbGenerator.preCleanup spec dstDir
+ writeFileDrafts dstDir (genDb spec)
+ writeFileDrafts dstDir (genDockerFiles spec)
writeDotWaspInfo dstDir
-- | Writes file drafts while using given destination dir as root dir.
diff --git a/waspc/src/Wasp/Generator/DbGenerator.hs b/waspc/src/Wasp/Generator/DbGenerator.hs
index 4bd54e59f..2f7b10e72 100644
--- a/waspc/src/Wasp/Generator/DbGenerator.hs
+++ b/waspc/src/Wasp/Generator/DbGenerator.hs
@@ -1,3 +1,5 @@
+{-# LANGUAGE TypeApplications #-}
+
module Wasp.Generator.DbGenerator
( genDb,
preCleanup,
@@ -9,22 +11,21 @@ where
import Control.Monad (when)
import Data.Aeson (object, (.=))
-import Data.Maybe (isNothing, maybeToList)
+import Data.Maybe (fromMaybe, isNothing, maybeToList)
import StrongPath (Abs, Dir, File', Path', Rel, reldir, relfile, (>))
import qualified StrongPath as SP
import System.Directory (doesDirectoryExist, removeDirectoryRecursive)
+import Wasp.AppSpec (AppSpec)
+import qualified Wasp.AppSpec as AS
+import qualified Wasp.AppSpec.App as AS.App
+import qualified Wasp.AppSpec.App.Db as AS.Db
+import qualified Wasp.AppSpec.Entity as AS.Entity
import Wasp.Common (DbMigrationsDir)
-import Wasp.CompileOptions (CompileOptions)
import Wasp.Generator.Common (ProjectRootDir)
import Wasp.Generator.FileDraft (FileDraft, createCopyDirFileDraft, createTemplateFileDraft)
import Wasp.Generator.Templates (TemplatesDir)
import qualified Wasp.Psl.Ast.Model as Psl.Ast.Model
import qualified Wasp.Psl.Generator.Model as Psl.Generator.Model
-import Wasp.Wasp (Wasp, getMigrationsDir)
-import qualified Wasp.Wasp as Wasp
-import qualified Wasp.Wasp.Db as Wasp.Db
-import Wasp.Wasp.Entity (Entity)
-import qualified Wasp.Wasp.Entity as Wasp.Entity
data DbRootDir
@@ -50,13 +51,18 @@ dbSchemaFileInProjectRootDir = dbRootDirInProjectRootDir > dbSchemaFileInDbRoo
dbMigrationsDirInDbRootDir :: Path' (Rel DbRootDir) (Dir DbMigrationsDir)
dbMigrationsDirInDbRootDir = [reldir|migrations|]
-preCleanup :: Wasp -> Path' Abs (Dir ProjectRootDir) -> CompileOptions -> IO ()
-preCleanup wasp projectRootDir _ = do
- deleteGeneratedMigrationsDirIfRedundant wasp projectRootDir
+preCleanup :: AppSpec -> Path' Abs (Dir ProjectRootDir) -> IO ()
+preCleanup spec projectRootDir = do
+ deleteGeneratedMigrationsDirIfRedundant spec projectRootDir
-deleteGeneratedMigrationsDirIfRedundant :: Wasp -> Path' Abs (Dir ProjectRootDir) -> IO ()
-deleteGeneratedMigrationsDirIfRedundant wasp projectRootDir = do
- let waspMigrationsDirMissing = isNothing $ getMigrationsDir wasp
+-- * Db generator
+
+genDb :: AppSpec -> [FileDraft]
+genDb spec = genPrismaSchema spec : maybeToList (genMigrationsDir spec)
+
+deleteGeneratedMigrationsDirIfRedundant :: AppSpec -> Path' Abs (Dir ProjectRootDir) -> IO ()
+deleteGeneratedMigrationsDirIfRedundant spec projectRootDir = do
+ let waspMigrationsDirMissing = isNothing $ AS.migrationsDir spec
projectMigrationsDirExists <- doesDirectoryExist projectMigrationsDirAbsFilePath
when (waspMigrationsDirMissing && projectMigrationsDirExists) $ do
putStrLn "A migrations directory does not exist in this Wasp root directory, but does in the generated project output directory."
@@ -66,40 +72,36 @@ deleteGeneratedMigrationsDirIfRedundant wasp projectRootDir = do
where
projectMigrationsDirAbsFilePath = SP.fromAbsDir $ projectRootDir > dbRootDirInProjectRootDir > dbMigrationsDirInDbRootDir
-genDb :: Wasp -> CompileOptions -> [FileDraft]
-genDb wasp _ =
- genPrismaSchema wasp : maybeToList (genMigrationsDir wasp)
-
-genPrismaSchema :: Wasp -> FileDraft
-genPrismaSchema wasp = createTemplateFileDraft dstPath tmplSrcPath (Just templateData)
+genPrismaSchema :: AppSpec -> FileDraft
+genPrismaSchema spec = createTemplateFileDraft dstPath tmplSrcPath (Just templateData)
where
dstPath = dbSchemaFileInProjectRootDir
tmplSrcPath = dbTemplatesDirInTemplatesDir > dbSchemaFileInDbTemplatesDir
templateData =
object
- [ "modelSchemas" .= map entityToPslModelSchema (Wasp.getPSLEntities wasp),
+ [ "modelSchemas" .= map entityToPslModelSchema (AS.getDecls @AS.Entity.Entity spec),
"datasourceProvider" .= (datasourceProvider :: String),
"datasourceUrl" .= (datasourceUrl :: String)
]
- dbSystem = maybe Wasp.Db.SQLite Wasp.Db._system (Wasp.getDb wasp)
+ dbSystem = fromMaybe AS.Db.SQLite (AS.Db.system =<< AS.App.db (snd $ AS.getApp spec))
(datasourceProvider, datasourceUrl) = case dbSystem of
- Wasp.Db.PostgreSQL -> ("postgresql", "env(\"DATABASE_URL\")")
+ AS.Db.PostgreSQL -> ("postgresql", "env(\"DATABASE_URL\")")
-- TODO: Report this error with some better mechanism, not `error`.
- Wasp.Db.SQLite ->
- if Wasp.getIsBuild wasp
- then error "SQLite (a default database) is not supported in production. To build your Wasp app for production, switch to a different database. Switching to PostgreSQL: https://wasp-lang.dev/docs/language/basic-elements/#migrating-from-sqlite-to-postgresql ."
+ AS.Db.SQLite ->
+ if AS.isBuild spec
+ then error "SQLite (a default database) is not supported in production. To build your Wasp app for production, switch to a different database. Switching to PostgreSQL: https://wasp-lang.dev/docs/language/features/#migrating-from-sqlite-to-postgresql ."
else ("sqlite", "\"file:./dev.db\"")
- entityToPslModelSchema :: Entity -> String
- entityToPslModelSchema entity =
+ entityToPslModelSchema :: (String, AS.Entity.Entity) -> String
+ entityToPslModelSchema (entityName, entity) =
Psl.Generator.Model.generateModel $
- Psl.Ast.Model.Model (Wasp.Entity._name entity) (Wasp.Entity._pslModelBody entity)
+ Psl.Ast.Model.Model entityName (AS.Entity.getPslModelBody entity)
-genMigrationsDir :: Wasp -> Maybe FileDraft
-genMigrationsDir wasp =
- (getMigrationsDir wasp) >>= \waspMigrationsDir ->
+genMigrationsDir :: AppSpec -> Maybe FileDraft
+genMigrationsDir spec =
+ AS.migrationsDir spec >>= \waspMigrationsDir ->
Just $ createCopyDirFileDraft (SP.castDir genProjectMigrationsDir) (SP.castDir waspMigrationsDir)
where
genProjectMigrationsDir = dbRootDirInProjectRootDir > dbMigrationsDirInDbRootDir
diff --git a/waspc/src/Wasp/Generator/DockerGenerator.hs b/waspc/src/Wasp/Generator/DockerGenerator.hs
index 47b429b49..5af27dc1a 100644
--- a/waspc/src/Wasp/Generator/DockerGenerator.hs
+++ b/waspc/src/Wasp/Generator/DockerGenerator.hs
@@ -1,3 +1,5 @@
+{-# LANGUAGE TypeApplications #-}
+
module Wasp.Generator.DockerGenerator
( genDockerFiles,
)
@@ -5,29 +7,29 @@ where
import Data.Aeson (object, (.=))
import StrongPath (File', Path', Rel, relfile)
-import Wasp.CompileOptions (CompileOptions)
+import Wasp.AppSpec (AppSpec)
+import qualified Wasp.AppSpec as AS
+import qualified Wasp.AppSpec.Entity as AS.Entity
import Wasp.Generator.Common (ProjectRootDir)
import Wasp.Generator.FileDraft (FileDraft, createTemplateFileDraft)
import Wasp.Generator.Templates (TemplatesDir)
-import Wasp.Wasp (Wasp)
-import qualified Wasp.Wasp as Wasp
-genDockerFiles :: Wasp -> CompileOptions -> [FileDraft]
-genDockerFiles wasp _ = genDockerfile wasp : [genDockerignore wasp]
+genDockerFiles :: AppSpec -> [FileDraft]
+genDockerFiles spec = genDockerfile spec : [genDockerignore spec]
-- TODO: Inject paths to server and db files/dirs, right now they are hardcoded in the templates.
-genDockerfile :: Wasp -> FileDraft
-genDockerfile wasp =
+genDockerfile :: AppSpec -> FileDraft
+genDockerfile spec =
createTemplateFileDraft
([relfile|Dockerfile|] :: Path' (Rel ProjectRootDir) File')
([relfile|Dockerfile|] :: Path' (Rel TemplatesDir) File')
( Just $
object
- [ "usingPrisma" .= not (null $ Wasp.getPSLEntities wasp)
+ [ "usingPrisma" .= not (null $ AS.getDecls @AS.Entity.Entity spec)
]
)
-genDockerignore :: Wasp -> FileDraft
+genDockerignore :: AppSpec -> FileDraft
genDockerignore _ =
createTemplateFileDraft
([relfile|.dockerignore|] :: Path' (Rel ProjectRootDir) File')
diff --git a/waspc/src/Wasp/Generator/ExternalCodeGenerator.hs b/waspc/src/Wasp/Generator/ExternalCodeGenerator.hs
index 08ba40896..7a82f589f 100644
--- a/waspc/src/Wasp/Generator/ExternalCodeGenerator.hs
+++ b/waspc/src/Wasp/Generator/ExternalCodeGenerator.hs
@@ -10,17 +10,14 @@ import qualified Wasp.AppSpec.ExternalCode as EC
import qualified Wasp.Generator.ExternalCodeGenerator.Common as C
import Wasp.Generator.ExternalCodeGenerator.Js (generateJsFile)
import qualified Wasp.Generator.FileDraft as FD
-import Wasp.Wasp (Wasp)
-import qualified Wasp.Wasp as Wasp
-- | Takes external code files from Wasp and generates them in new location as part of the generated project.
-- It might not just copy them but also do some changes on them, as needed.
generateExternalCodeDir ::
C.ExternalCodeGeneratorStrategy ->
- Wasp ->
+ [EC.File] ->
[FD.FileDraft]
-generateExternalCodeDir strategy wasp =
- map (generateFile strategy) (Wasp.getExternalCodeFiles wasp)
+generateExternalCodeDir strategy = map (generateFile strategy)
generateFile :: C.ExternalCodeGeneratorStrategy -> EC.File -> FD.FileDraft
generateFile strategy file
diff --git a/waspc/src/Wasp/Generator/JsImport.hs b/waspc/src/Wasp/Generator/JsImport.hs
index ab34724dc..260ed878c 100644
--- a/waspc/src/Wasp/Generator/JsImport.hs
+++ b/waspc/src/Wasp/Generator/JsImport.hs
@@ -1,27 +1,26 @@
module Wasp.Generator.JsImport
- ( getImportDetailsForJsFnImport,
+ ( getJsImportDetailsForExtFnImport,
)
where
import StrongPath (Dir, Path, Posix, Rel, (>))
import qualified StrongPath as SP
+import qualified Wasp.AppSpec.ExtImport as AS.ExtImport
import Wasp.Generator.ExternalCodeGenerator.Common (GeneratedExternalCodeDir)
-import qualified Wasp.Wasp.JsImport as Wasp.JsImport
-getImportDetailsForJsFnImport ::
+getJsImportDetailsForExtFnImport ::
-- | Path to generated external code directory, relative to the directory in which file doing the importing is.
Path Posix (Rel (Dir a)) (Dir GeneratedExternalCodeDir) ->
- Wasp.JsImport.JsImport ->
+ AS.ExtImport.ExtImport ->
-- | (importIdentifier, importStmt)
- -- - importIdentifier -> Identifier via which you can access js function after you import it with importStmt.
- -- - importStmt -> Import statement via which you should do the import.
+ -- - importIdentifier -> Identifier via which you can access ext js function after you import it with importStmt.
+ -- - importStmt -> Javascript import statement via which you should do the import.
(String, String)
-getImportDetailsForJsFnImport relPosixPathToExtCodeDir jsImport = (importIdentifier, importStmt)
+getJsImportDetailsForExtFnImport relPosixPathToExtCodeDir extImport = (importIdentifier, importStmt)
where
importStmt = "import " ++ importWhat ++ " from '" ++ importFrom ++ "'"
- importFrom = "./" ++ SP.fromRelFileP (relPosixPathToExtCodeDir > SP.castRel (Wasp.JsImport._from jsImport))
+ importFrom = "./" ++ SP.fromRelFileP (relPosixPathToExtCodeDir > SP.castRel (AS.ExtImport.path extImport))
(importIdentifier, importWhat) =
- case (Wasp.JsImport._defaultImport jsImport, Wasp.JsImport._namedImports jsImport) of
- (Just defaultImport, []) -> (defaultImport, defaultImport)
- (Nothing, [namedImport]) -> (namedImport, "{ " ++ namedImport ++ " }")
- _ -> error $ "Expected from " ++ show jsImport ++ " to be either default import or single named import, due to it being used to import a single js function."
+ case AS.ExtImport.name extImport of
+ AS.ExtImport.ExtImportModule defaultImport -> (defaultImport, defaultImport)
+ AS.ExtImport.ExtImportField namedImport -> (namedImport, "{ " ++ namedImport ++ " }")
diff --git a/waspc/src/Wasp/Generator/PackageJsonGenerator.hs b/waspc/src/Wasp/Generator/PackageJsonGenerator.hs
index 654ed5489..68bdef3df 100644
--- a/waspc/src/Wasp/Generator/PackageJsonGenerator.hs
+++ b/waspc/src/Wasp/Generator/PackageJsonGenerator.hs
@@ -8,7 +8,7 @@ where
import Data.Bifunctor (second)
import Data.List (find, intercalate)
import Data.Maybe (fromJust, isJust)
-import qualified Wasp.NpmDependency as ND
+import qualified Wasp.AppSpec.App.Dependency as D
type NpmDependenciesConflictError = String
@@ -20,56 +20,56 @@ type NpmDependenciesConflictError = String
-- On error (Left), returns list of conflicting user deps together with the error message
-- explaining what the error is.
resolveNpmDeps ::
- [ND.NpmDependency] ->
- [ND.NpmDependency] ->
+ [D.Dependency] ->
+ [D.Dependency] ->
Either
- [(ND.NpmDependency, NpmDependenciesConflictError)]
- ([ND.NpmDependency], [ND.NpmDependency])
+ [(D.Dependency, NpmDependenciesConflictError)]
+ ([D.Dependency], [D.Dependency])
resolveNpmDeps waspDeps userDeps =
if null conflictingUserDeps
then Right (waspDeps, userDepsNotInWaspDeps)
else Left conflictingUserDeps
where
- conflictingUserDeps :: [(ND.NpmDependency, NpmDependenciesConflictError)]
+ conflictingUserDeps :: [(D.Dependency, NpmDependenciesConflictError)]
conflictingUserDeps =
map (second fromJust) $
filter (isJust . snd) $
map (\dep -> (dep, checkIfConflictingUserDep dep)) userDeps
- checkIfConflictingUserDep :: ND.NpmDependency -> Maybe NpmDependenciesConflictError
+ checkIfConflictingUserDep :: D.Dependency -> Maybe NpmDependenciesConflictError
checkIfConflictingUserDep userDep =
let attachErrorMessage dep =
- "Error: Dependency conflict for user npm dependency ("
- ++ ND._name dep
+ "Error: Dependency conflict for user dependency ("
+ ++ D.name dep
++ ", "
- ++ ND._version dep
+ ++ D.version dep
++ "): "
++ "Version must be set to the exactly the same version as"
++ " the one wasp is using: "
- ++ ND._version dep
+ ++ D.version dep
in attachErrorMessage <$> find (areTwoDepsInConflict userDep) waspDeps
- areTwoDepsInConflict :: ND.NpmDependency -> ND.NpmDependency -> Bool
+ areTwoDepsInConflict :: D.Dependency -> D.Dependency -> Bool
areTwoDepsInConflict d1 d2 =
- ND._name d1 == ND._name d2
- && ND._version d1 /= ND._version d2
+ D.name d1 == D.name d2
+ && D.version d1 /= D.version d2
- userDepsNotInWaspDeps :: [ND.NpmDependency]
- userDepsNotInWaspDeps = filter (not . isDepWithNameInWaspDeps . ND._name) userDeps
+ userDepsNotInWaspDeps :: [D.Dependency]
+ userDepsNotInWaspDeps = filter (not . isDepWithNameInWaspDeps . D.name) userDeps
isDepWithNameInWaspDeps :: String -> Bool
- isDepWithNameInWaspDeps name = any ((name ==) . ND._name) waspDeps
+ isDepWithNameInWaspDeps name = any ((name ==) . D.name) waspDeps
-npmDepsToPackageJsonEntryWithKey :: [ND.NpmDependency] -> String -> String
+npmDepsToPackageJsonEntryWithKey :: [D.Dependency] -> String -> String
npmDepsToPackageJsonEntryWithKey deps key =
"\""
++ key
++ "\": {"
- ++ intercalate ",\n " (map (\dep -> "\"" ++ ND._name dep ++ "\": \"" ++ ND._version dep ++ "\"") deps)
+ ++ intercalate ",\n " (map (\dep -> "\"" ++ D.name dep ++ "\": \"" ++ D.version dep ++ "\"") deps)
++ "\n}"
-npmDepsToPackageJsonEntry :: [ND.NpmDependency] -> String
+npmDepsToPackageJsonEntry :: [D.Dependency] -> String
npmDepsToPackageJsonEntry deps = npmDepsToPackageJsonEntryWithKey deps "dependencies"
-npmDevDepsToPackageJsonEntry :: [ND.NpmDependency] -> String
+npmDevDepsToPackageJsonEntry :: [D.Dependency] -> String
npmDevDepsToPackageJsonEntry deps = npmDepsToPackageJsonEntryWithKey deps "devDependencies"
diff --git a/waspc/src/Wasp/Generator/ServerGenerator.hs b/waspc/src/Wasp/Generator/ServerGenerator.hs
index bc209aaf7..5a6a07f13 100644
--- a/waspc/src/Wasp/Generator/ServerGenerator.hs
+++ b/waspc/src/Wasp/Generator/ServerGenerator.hs
@@ -1,3 +1,5 @@
+{-# LANGUAGE TypeApplications #-}
+
module Wasp.Generator.ServerGenerator
( genServer,
preCleanup,
@@ -20,12 +22,18 @@ import qualified StrongPath as SP
import System.Directory (removeFile)
import System.IO.Error (isDoesNotExistError)
import UnliftIO.Exception (catch, throwIO)
-import Wasp.CompileOptions (CompileOptions)
+import Wasp.AppSpec (AppSpec)
+import qualified Wasp.AppSpec as AS
+import qualified Wasp.AppSpec.App as AS.App
+import qualified Wasp.AppSpec.App.Auth as AS.App.Auth
+import qualified Wasp.AppSpec.App.Dependency as AS.Dependency
+import qualified Wasp.AppSpec.App.Server as AS.App.Server
+import qualified Wasp.AppSpec.Entity as AS.Entity
import Wasp.Generator.Common (ProjectRootDir, nodeVersionAsText)
import Wasp.Generator.ExternalCodeGenerator (generateExternalCodeDir)
import Wasp.Generator.ExternalCodeGenerator.Common (GeneratedExternalCodeDir)
import Wasp.Generator.FileDraft (FileDraft, createCopyFileDraft)
-import Wasp.Generator.JsImport (getImportDetailsForJsFnImport)
+import Wasp.Generator.JsImport (getJsImportDetailsForExtFnImport)
import Wasp.Generator.PackageJsonGenerator
( npmDepsToPackageJsonEntry,
npmDevDepsToPackageJsonEntry,
@@ -42,24 +50,18 @@ import Wasp.Generator.ServerGenerator.ConfigG (genConfigFile)
import qualified Wasp.Generator.ServerGenerator.ExternalCodeGenerator as ServerExternalCodeGenerator
import Wasp.Generator.ServerGenerator.OperationsG (genOperations)
import Wasp.Generator.ServerGenerator.OperationsRoutesG (genOperationsRoutes)
-import qualified Wasp.NpmDependency as ND
-import Wasp.Wasp (Wasp, getAuth)
-import qualified Wasp.Wasp as Wasp
-import qualified Wasp.Wasp.Auth as Wasp.Auth
-import qualified Wasp.Wasp.NpmDependencies as WND
-import qualified Wasp.Wasp.Server as Wasp.Server
-genServer :: Wasp -> CompileOptions -> [FileDraft]
-genServer wasp _ =
+genServer :: AppSpec -> [FileDraft]
+genServer spec =
concat
- [ [genReadme wasp],
- [genPackageJson wasp waspNpmDeps waspNpmDevDeps],
- [genNpmrc wasp],
- [genNvmrc wasp],
- [genGitignore wasp],
- genSrcDir wasp,
- generateExternalCodeDir ServerExternalCodeGenerator.generatorStrategy wasp,
- genDotEnv wasp
+ [ [genReadme],
+ [genPackageJson spec waspNpmDeps waspNpmDevDeps],
+ [genNpmrc],
+ [genNvmrc],
+ [genGitignore],
+ genSrcDir spec,
+ generateExternalCodeDir ServerExternalCodeGenerator.generatorStrategy (AS.externalCodeFiles spec),
+ genDotEnv spec
]
-- Cleanup to be performed before generating new server code.
@@ -67,8 +69,8 @@ genServer wasp _ =
-- TODO: Once we implement a fancier method of removing old/redundant files in outDir,
-- we will not need this method any more. Check https://github.com/wasp-lang/wasp/issues/209
-- for progress of this.
-preCleanup :: Wasp -> Path' Abs (Dir ProjectRootDir) -> CompileOptions -> IO ()
-preCleanup _ outDir _ = do
+preCleanup :: AppSpec -> Path' Abs (Dir ProjectRootDir) -> IO ()
+preCleanup _ outDir = do
-- If .env gets removed but there is old .env file in generated project from previous attempts,
-- we need to make sure we remove it.
removeFile dotEnvAbsFilePath
@@ -76,9 +78,9 @@ preCleanup _ outDir _ = do
where
dotEnvAbsFilePath = SP.toFilePath $ outDir > C.serverRootDirInProjectRootDir > dotEnvInServerRootDir
-genDotEnv :: Wasp -> [FileDraft]
-genDotEnv wasp =
- case Wasp.getDotEnvFile wasp of
+genDotEnv :: AppSpec -> [FileDraft]
+genDotEnv spec =
+ case AS.dotEnvFile spec of
Just srcFilePath ->
[ createCopyFileDraft
(C.serverRootDirInProjectRootDir > dotEnvInServerRootDir)
@@ -89,22 +91,21 @@ genDotEnv wasp =
dotEnvInServerRootDir :: Path' (Rel C.ServerRootDir) File'
dotEnvInServerRootDir = [relfile|.env|]
-genReadme :: Wasp -> FileDraft
-genReadme _ = C.copyTmplAsIs (asTmplFile [relfile|README.md|])
+genReadme :: FileDraft
+genReadme = C.mkTmplFd (asTmplFile [relfile|README.md|])
-genPackageJson :: Wasp -> [ND.NpmDependency] -> [ND.NpmDependency] -> FileDraft
-genPackageJson wasp waspDeps waspDevDeps =
- C.makeTemplateFD
+genPackageJson :: AppSpec -> [AS.Dependency.Dependency] -> [AS.Dependency.Dependency] -> FileDraft
+genPackageJson spec waspDeps waspDevDeps =
+ C.mkTmplFdWithDstAndData
(asTmplFile [relfile|package.json|])
(asServerFile [relfile|package.json|])
( Just $
object
- [ "wasp" .= wasp,
- "depsChunk" .= npmDepsToPackageJsonEntry (resolvedWaspDeps ++ resolvedUserDeps),
+ [ "depsChunk" .= npmDepsToPackageJsonEntry (resolvedWaspDeps ++ resolvedUserDeps),
"devDepsChunk" .= npmDevDepsToPackageJsonEntry waspDevDeps,
"nodeVersion" .= nodeVersionAsText,
"startProductionScript"
- .= if not (null $ Wasp.getPSLEntities wasp)
+ .= if not (null $ AS.getDecls @AS.Entity.Entity spec)
then "npm run db-migrate-prod && "
else
""
@@ -117,12 +118,12 @@ genPackageJson wasp waspDeps waspDevDeps =
Right deps -> deps
Left depsAndErrors -> error $ intercalate " ; " $ map snd depsAndErrors
- userDeps :: [ND.NpmDependency]
- userDeps = WND._dependencies $ Wasp.getNpmDependencies wasp
+ userDeps :: [AS.Dependency.Dependency]
+ userDeps = fromMaybe [] $ AS.App.dependencies $ snd $ AS.getApp spec
-waspNpmDeps :: [ND.NpmDependency]
+waspNpmDeps :: [AS.Dependency.Dependency]
waspNpmDeps =
- ND.fromList
+ AS.Dependency.fromList
[ ("cookie-parser", "~1.4.4"),
("cors", "^2.8.5"),
("debug", "~2.6.9"),
@@ -135,56 +136,56 @@ waspNpmDeps =
("helmet", "^4.6.0")
]
-waspNpmDevDeps :: [ND.NpmDependency]
+waspNpmDevDeps :: [AS.Dependency.Dependency]
waspNpmDevDeps =
- ND.fromList
+ AS.Dependency.fromList
[ ("nodemon", "^2.0.4"),
("standard", "^14.3.4"),
("prisma", "2.22.1")
]
-genNpmrc :: Wasp -> FileDraft
-genNpmrc _ =
- C.makeTemplateFD
+genNpmrc :: FileDraft
+genNpmrc =
+ C.mkTmplFdWithDstAndData
(asTmplFile [relfile|npmrc|])
(asServerFile [relfile|.npmrc|])
Nothing
-genNvmrc :: Wasp -> FileDraft
-genNvmrc _ =
- C.makeTemplateFD
+genNvmrc :: FileDraft
+genNvmrc =
+ C.mkTmplFdWithDstAndData
(asTmplFile [relfile|nvmrc|])
(asServerFile [relfile|.nvmrc|])
(Just (object ["nodeVersion" .= ('v' : nodeVersionAsText)]))
-genGitignore :: Wasp -> FileDraft
-genGitignore _ =
- C.makeTemplateFD
+genGitignore :: FileDraft
+genGitignore =
+ C.mkTmplFdWithDstAndData
(asTmplFile [relfile|gitignore|])
(asServerFile [relfile|.gitignore|])
Nothing
-genSrcDir :: Wasp -> [FileDraft]
-genSrcDir wasp =
+genSrcDir :: AppSpec -> [FileDraft]
+genSrcDir spec =
concat
- [ [C.copySrcTmplAsIs $ C.asTmplSrcFile [relfile|app.js|]],
- [C.copySrcTmplAsIs $ C.asTmplSrcFile [relfile|server.js|]],
- [C.copySrcTmplAsIs $ C.asTmplSrcFile [relfile|utils.js|]],
- [C.copySrcTmplAsIs $ C.asTmplSrcFile [relfile|core/AuthError.js|]],
- [C.copySrcTmplAsIs $ C.asTmplSrcFile [relfile|core/HttpError.js|]],
- [genDbClient wasp],
- [genConfigFile wasp],
- genRoutesDir wasp,
- genOperationsRoutes wasp,
- genOperations wasp,
- genAuth wasp,
- [genServerJs wasp]
+ [ [C.mkSrcTmplFd $ C.asTmplSrcFile [relfile|app.js|]],
+ [C.mkSrcTmplFd $ C.asTmplSrcFile [relfile|server.js|]],
+ [C.mkSrcTmplFd $ C.asTmplSrcFile [relfile|utils.js|]],
+ [C.mkSrcTmplFd $ C.asTmplSrcFile [relfile|core/AuthError.js|]],
+ [C.mkSrcTmplFd $ C.asTmplSrcFile [relfile|core/HttpError.js|]],
+ [genDbClient spec],
+ [genConfigFile spec],
+ genRoutesDir spec,
+ genOperationsRoutes spec,
+ genOperations spec,
+ genAuth spec,
+ [genServerJs spec]
]
-genDbClient :: Wasp -> FileDraft
-genDbClient wasp = C.makeTemplateFD tmplFile dstFile (Just tmplData)
+genDbClient :: AppSpec -> FileDraft
+genDbClient spec = C.mkTmplFdWithDstAndData tmplFile dstFile (Just tmplData)
where
- maybeAuth = getAuth wasp
+ maybeAuth = AS.App.auth $ snd $ AS.getApp spec
dbClientRelToSrcP = [relfile|dbClient.js|]
tmplFile = C.asTmplFile $ [reldir|src|] > dbClientRelToSrcP
@@ -195,13 +196,13 @@ genDbClient wasp = C.makeTemplateFD tmplFile dstFile (Just tmplData)
then
object
[ "isAuthEnabled" .= True,
- "userEntityUpper" .= Wasp.Auth._userEntity (fromJust maybeAuth)
+ "userEntityUpper" .= (AS.refName (AS.App.Auth.userEntity $ fromJust maybeAuth) :: String)
]
else object []
-genServerJs :: Wasp -> FileDraft
-genServerJs wasp =
- C.makeTemplateFD
+genServerJs :: AppSpec -> FileDraft
+genServerJs spec =
+ C.mkTmplFdWithDstAndData
(asTmplFile [relfile|src/server.js|])
(asServerFile [relfile|src/server.js|])
( Just $
@@ -212,8 +213,8 @@ genServerJs wasp =
]
)
where
- maybeSetupJsFunction = Wasp.Server._setupJsFunction <$> Wasp.getServer wasp
- maybeSetupJsFnImportDetails = getImportDetailsForJsFnImport relPosixPathFromSrcDirToExtSrcDir <$> maybeSetupJsFunction
+ maybeSetupJsFunction = AS.App.Server.setupFn =<< AS.App.server (snd $ AS.getApp spec)
+ maybeSetupJsFnImportDetails = getJsImportDetailsForExtFnImport relPosixPathFromSrcDirToExtSrcDir <$> maybeSetupJsFunction
(maybeSetupJsFnImportIdentifier, maybeSetupJsFnImportStmt) =
(fst <$> maybeSetupJsFnImportDetails, snd <$> maybeSetupJsFnImportDetails)
@@ -221,17 +222,17 @@ genServerJs wasp =
relPosixPathFromSrcDirToExtSrcDir :: Path Posix (Rel (Dir ServerSrcDir)) (Dir GeneratedExternalCodeDir)
relPosixPathFromSrcDirToExtSrcDir = [reldirP|./ext-src|]
-genRoutesDir :: Wasp -> [FileDraft]
-genRoutesDir wasp =
+genRoutesDir :: AppSpec -> [FileDraft]
+genRoutesDir spec =
-- TODO(martin): We will probably want to extract "routes" path here same as we did with "src", to avoid hardcoding,
-- but I did not bother with it yet since it is used only here for now.
- [ C.makeTemplateFD
+ [ C.mkTmplFdWithDstAndData
(asTmplFile [relfile|src/routes/index.js|])
(asServerFile [relfile|src/routes/index.js|])
( Just $
object
- [ "operationsRouteInRootRouter" .= operationsRouteInRootRouter,
- "isAuthEnabled" .= isJust (getAuth wasp)
+ [ "operationsRouteInRootRouter" .= (operationsRouteInRootRouter :: String),
+ "isAuthEnabled" .= (AS.isAuthEnabled spec :: Bool)
]
)
]
diff --git a/waspc/src/Wasp/Generator/ServerGenerator/AuthG.hs b/waspc/src/Wasp/Generator/ServerGenerator/AuthG.hs
index d50ff66b5..7e47d9155 100644
--- a/waspc/src/Wasp/Generator/ServerGenerator/AuthG.hs
+++ b/waspc/src/Wasp/Generator/ServerGenerator/AuthG.hs
@@ -5,14 +5,16 @@ where
import Data.Aeson (object, (.=))
import StrongPath (reldir, relfile, (>))
+import Wasp.AppSpec (AppSpec)
+import qualified Wasp.AppSpec as AS
+import qualified Wasp.AppSpec.App as AS.App
+import qualified Wasp.AppSpec.App.Auth as AS.Auth
import Wasp.Generator.FileDraft (FileDraft)
import qualified Wasp.Generator.ServerGenerator.Common as C
import qualified Wasp.Util as Util
-import Wasp.Wasp (Wasp, getAuth)
-import qualified Wasp.Wasp.Auth as Wasp.Auth
-genAuth :: Wasp -> [FileDraft]
-genAuth wasp = case maybeAuth of
+genAuth :: AppSpec -> [FileDraft]
+genAuth spec = case maybeAuth of
Just auth ->
[ genCoreAuth auth,
genAuthMiddleware auth,
@@ -24,55 +26,55 @@ genAuth wasp = case maybeAuth of
]
Nothing -> []
where
- maybeAuth = getAuth wasp
+ maybeAuth = AS.App.auth $ snd $ AS.getApp spec
-- | Generates core/auth file which contains auth middleware and createUser() function.
-genCoreAuth :: Wasp.Auth.Auth -> FileDraft
-genCoreAuth auth = C.makeTemplateFD tmplFile dstFile (Just tmplData)
+genCoreAuth :: AS.Auth.Auth -> FileDraft
+genCoreAuth auth = C.mkTmplFdWithDstAndData tmplFile dstFile (Just tmplData)
where
coreAuthRelToSrc = [relfile|core/auth.js|]
tmplFile = C.asTmplFile $ [reldir|src|] > coreAuthRelToSrc
dstFile = C.serverSrcDirInServerRootDir > C.asServerSrcFile coreAuthRelToSrc
tmplData =
- let userEntity = Wasp.Auth._userEntity auth
+ let userEntityName = AS.refName $ AS.Auth.userEntity auth
in object
- [ "userEntityUpper" .= userEntity,
- "userEntityLower" .= Util.toLowerFirst userEntity
+ [ "userEntityUpper" .= (userEntityName :: String),
+ "userEntityLower" .= (Util.toLowerFirst userEntityName :: String)
]
-genAuthMiddleware :: Wasp.Auth.Auth -> FileDraft
-genAuthMiddleware auth = C.makeTemplateFD tmplFile dstFile (Just tmplData)
+genAuthMiddleware :: AS.Auth.Auth -> FileDraft
+genAuthMiddleware auth = C.mkTmplFdWithDstAndData tmplFile dstFile (Just tmplData)
where
authMiddlewareRelToSrc = [relfile|core/auth/prismaMiddleware.js|]
tmplFile = C.asTmplFile $ [reldir|src|] > authMiddlewareRelToSrc
dstFile = C.serverSrcDirInServerRootDir > C.asServerSrcFile authMiddlewareRelToSrc
tmplData =
- let userEntity = Wasp.Auth._userEntity auth
+ let userEntityName = AS.refName $ AS.Auth.userEntity auth
in object
- [ "userEntityUpper" .= userEntity
+ [ "userEntityUpper" .= (userEntityName :: String)
]
genAuthRoutesIndex :: FileDraft
-genAuthRoutesIndex = C.copySrcTmplAsIs (C.asTmplSrcFile [relfile|routes/auth/index.js|])
+genAuthRoutesIndex = C.mkSrcTmplFd (C.asTmplSrcFile [relfile|routes/auth/index.js|])
-genLoginRoute :: Wasp.Auth.Auth -> FileDraft
-genLoginRoute auth = C.makeTemplateFD tmplFile dstFile (Just tmplData)
+genLoginRoute :: AS.Auth.Auth -> FileDraft
+genLoginRoute auth = C.mkTmplFdWithDstAndData tmplFile dstFile (Just tmplData)
where
loginRouteRelToSrc = [relfile|routes/auth/login.js|]
tmplFile = C.asTmplFile $ [reldir|src|] > loginRouteRelToSrc
dstFile = C.serverSrcDirInServerRootDir > C.asServerSrcFile loginRouteRelToSrc
tmplData =
- let userEntity = Wasp.Auth._userEntity auth
+ let userEntityName = AS.refName $ AS.Auth.userEntity auth
in object
- [ "userEntityUpper" .= userEntity,
- "userEntityLower" .= Util.toLowerFirst userEntity
+ [ "userEntityUpper" .= (userEntityName :: String),
+ "userEntityLower" .= (Util.toLowerFirst userEntityName :: String)
]
-genSignupRoute :: Wasp.Auth.Auth -> FileDraft
-genSignupRoute auth = C.makeTemplateFD tmplFile dstFile (Just tmplData)
+genSignupRoute :: AS.Auth.Auth -> FileDraft
+genSignupRoute auth = C.mkTmplFdWithDstAndData tmplFile dstFile (Just tmplData)
where
signupRouteRelToSrc = [relfile|routes/auth/signup.js|]
tmplFile = C.asTmplFile $ [reldir|src|] > signupRouteRelToSrc
@@ -80,11 +82,11 @@ genSignupRoute auth = C.makeTemplateFD tmplFile dstFile (Just tmplData)
tmplData =
object
- [ "userEntityLower" .= Util.toLowerFirst (Wasp.Auth._userEntity auth)
+ [ "userEntityLower" .= (Util.toLowerFirst (AS.refName $ AS.Auth.userEntity auth) :: String)
]
-genMeRoute :: Wasp.Auth.Auth -> FileDraft
-genMeRoute auth = C.makeTemplateFD tmplFile dstFile (Just tmplData)
+genMeRoute :: AS.Auth.Auth -> FileDraft
+genMeRoute auth = C.mkTmplFdWithDstAndData tmplFile dstFile (Just tmplData)
where
meRouteRelToSrc = [relfile|routes/auth/me.js|]
tmplFile = C.asTmplFile $ [reldir|src|] > meRouteRelToSrc
@@ -92,5 +94,5 @@ genMeRoute auth = C.makeTemplateFD tmplFile dstFile (Just tmplData)
tmplData =
object
- [ "userEntityLower" .= Util.toLowerFirst (Wasp.Auth._userEntity auth)
+ [ "userEntityLower" .= (Util.toLowerFirst (AS.refName $ AS.Auth.userEntity auth) :: String)
]
diff --git a/waspc/src/Wasp/Generator/ServerGenerator/Common.hs b/waspc/src/Wasp/Generator/ServerGenerator/Common.hs
index a5e3525b5..8ac180a9c 100644
--- a/waspc/src/Wasp/Generator/ServerGenerator/Common.hs
+++ b/waspc/src/Wasp/Generator/ServerGenerator/Common.hs
@@ -2,10 +2,9 @@ module Wasp.Generator.ServerGenerator.Common
( serverRootDirInProjectRootDir,
serverSrcDirInServerRootDir,
serverSrcDirInProjectRootDir,
- copyTmplAsIs,
- makeSimpleTemplateFD,
- makeTemplateFD,
- copySrcTmplAsIs,
+ mkTmplFd,
+ mkTmplFdWithDstAndData,
+ mkSrcTmplFd,
srcDirInServerTemplatesDir,
asTmplFile,
asTmplSrcFile,
@@ -24,7 +23,6 @@ import qualified StrongPath as SP
import Wasp.Generator.Common (ProjectRootDir)
import Wasp.Generator.FileDraft (FileDraft, createTemplateFileDraft)
import Wasp.Generator.Templates (TemplatesDir)
-import Wasp.Wasp (Wasp)
data ServerRootDir
@@ -61,29 +59,24 @@ serverSrcDirInProjectRootDir = serverRootDirInProjectRootDir > serverSrcDirInS
-- * Templates
-copyTmplAsIs :: Path' (Rel ServerTemplatesDir) File' -> FileDraft
-copyTmplAsIs srcPath = makeTemplateFD srcPath dstPath Nothing
+mkTmplFd :: Path' (Rel ServerTemplatesDir) File' -> FileDraft
+mkTmplFd srcPath = mkTmplFdWithDstAndData srcPath dstPath Nothing
where
dstPath = SP.castRel srcPath :: Path' (Rel ServerRootDir) File'
-makeSimpleTemplateFD :: Path' (Rel ServerTemplatesDir) File' -> Wasp -> FileDraft
-makeSimpleTemplateFD srcPath wasp = makeTemplateFD srcPath dstPath (Just $ Aeson.toJSON wasp)
- where
- dstPath = SP.castRel srcPath :: Path' (Rel ServerRootDir) File'
-
-makeTemplateFD ::
+mkTmplFdWithDstAndData ::
Path' (Rel ServerTemplatesDir) File' ->
Path' (Rel ServerRootDir) File' ->
Maybe Aeson.Value ->
FileDraft
-makeTemplateFD relSrcPath relDstPath tmplData =
+mkTmplFdWithDstAndData relSrcPath relDstPath tmplData =
createTemplateFileDraft
(serverRootDirInProjectRootDir > relDstPath)
(serverTemplatesDirInTemplatesDir > relSrcPath)
tmplData
-copySrcTmplAsIs :: Path' (Rel ServerTemplatesSrcDir) File' -> FileDraft
-copySrcTmplAsIs pathInTemplatesSrcDir = makeTemplateFD srcPath dstPath Nothing
+mkSrcTmplFd :: Path' (Rel ServerTemplatesSrcDir) File' -> FileDraft
+mkSrcTmplFd pathInTemplatesSrcDir = mkTmplFdWithDstAndData srcPath dstPath Nothing
where
srcPath = srcDirInServerTemplatesDir > pathInTemplatesSrcDir
dstPath =
diff --git a/waspc/src/Wasp/Generator/ServerGenerator/ConfigG.hs b/waspc/src/Wasp/Generator/ServerGenerator/ConfigG.hs
index 7c159e5ee..425a63a36 100644
--- a/waspc/src/Wasp/Generator/ServerGenerator/ConfigG.hs
+++ b/waspc/src/Wasp/Generator/ServerGenerator/ConfigG.hs
@@ -5,21 +5,21 @@ module Wasp.Generator.ServerGenerator.ConfigG
where
import Data.Aeson (object, (.=))
-import Data.Maybe (isJust)
import StrongPath (File', Path', Rel, relfile, (>))
import qualified StrongPath as SP
+import Wasp.AppSpec (AppSpec)
+import qualified Wasp.AppSpec as AS
import Wasp.Generator.FileDraft (FileDraft)
import qualified Wasp.Generator.ServerGenerator.Common as C
-import Wasp.Wasp (Wasp, getAuth)
-genConfigFile :: Wasp -> FileDraft
-genConfigFile wasp = C.makeTemplateFD tmplFile dstFile (Just tmplData)
+genConfigFile :: AppSpec -> FileDraft
+genConfigFile spec = C.mkTmplFdWithDstAndData tmplFile dstFile (Just tmplData)
where
tmplFile = C.srcDirInServerTemplatesDir > SP.castRel configFileInSrcDir
dstFile = C.serverSrcDirInServerRootDir > configFileInSrcDir
tmplData =
object
- [ "isAuthEnabled" .= isJust (getAuth wasp)
+ [ "isAuthEnabled" .= (AS.isAuthEnabled spec :: Bool)
]
configFileInSrcDir :: Path' (Rel C.ServerSrcDir) File'
diff --git a/waspc/src/Wasp/Generator/ServerGenerator/OperationsG.hs b/waspc/src/Wasp/Generator/ServerGenerator/OperationsG.hs
index 2e0057676..e78c7c9c8 100644
--- a/waspc/src/Wasp/Generator/ServerGenerator/OperationsG.hs
+++ b/waspc/src/Wasp/Generator/ServerGenerator/OperationsG.hs
@@ -1,3 +1,5 @@
+{-# LANGUAGE TypeApplications #-}
+
module Wasp.Generator.ServerGenerator.OperationsG
( genOperations,
queryFileInSrcDir,
@@ -12,80 +14,76 @@ import Data.Char (toLower)
import Data.Maybe (fromJust)
import StrongPath (Dir, Dir', File', Path, Path', Posix, Rel, reldir, reldirP, relfile, (>))
import qualified StrongPath as SP
+import Wasp.AppSpec (AppSpec)
+import qualified Wasp.AppSpec as AS
+import qualified Wasp.AppSpec.Action as AS.Action
+import qualified Wasp.AppSpec.Operation as AS.Operation
+import qualified Wasp.AppSpec.Query as AS.Query
import Wasp.Generator.ExternalCodeGenerator.Common (GeneratedExternalCodeDir)
import Wasp.Generator.FileDraft (FileDraft)
-import Wasp.Generator.JsImport (getImportDetailsForJsFnImport)
+import Wasp.Generator.JsImport (getJsImportDetailsForExtFnImport)
import qualified Wasp.Generator.ServerGenerator.Common as C
-import Wasp.Wasp (Wasp)
-import qualified Wasp.Wasp as Wasp
-import qualified Wasp.Wasp.Action as Wasp.Action
-import qualified Wasp.Wasp.Operation as Wasp.Operation
-import qualified Wasp.Wasp.Query as Wasp.Query
-genOperations :: Wasp -> [FileDraft]
-genOperations wasp =
- genQueries wasp
- ++ genActions wasp
+genOperations :: AppSpec -> [FileDraft]
+genOperations spec = genQueries spec ++ genActions spec
-genQueries :: Wasp -> [FileDraft]
-genQueries wasp =
- map (genQuery wasp) (Wasp.getQueries wasp)
+genQueries :: AppSpec -> [FileDraft]
+genQueries spec = map (genQuery spec) (AS.getQueries spec)
-genActions :: Wasp -> [FileDraft]
-genActions wasp =
- map (genAction wasp) (Wasp.getActions wasp)
+genActions :: AppSpec -> [FileDraft]
+genActions spec = map (genAction spec) (AS.getActions spec)
-- | Here we generate JS file that basically imports JS query function provided by user,
-- decorates it (mostly injects stuff into it) and exports. Idea is that the rest of the server,
-- and user also, should use this new JS function, and not the old one directly.
-genQuery :: Wasp -> Wasp.Query.Query -> FileDraft
-genQuery _ query = C.makeTemplateFD tmplFile dstFile (Just tmplData)
+genQuery :: AppSpec -> (String, AS.Query.Query) -> FileDraft
+genQuery _ (queryName, query) = C.mkTmplFdWithDstAndData tmplFile dstFile (Just tmplData)
where
- operation = Wasp.Operation.QueryOp query
+ operation = AS.Operation.QueryOp queryName query
tmplFile = C.asTmplFile [relfile|src/queries/_query.js|]
- dstFile = C.serverSrcDirInServerRootDir > queryFileInSrcDir query
+ dstFile = C.serverSrcDirInServerRootDir > queryFileInSrcDir queryName
tmplData = operationTmplData operation
-- | Analogous to genQuery.
-genAction :: Wasp -> Wasp.Action.Action -> FileDraft
-genAction _ action = C.makeTemplateFD tmplFile dstFile (Just tmplData)
+genAction :: AppSpec -> (String, AS.Action.Action) -> FileDraft
+genAction _ (actionName, action) = C.mkTmplFdWithDstAndData tmplFile dstFile (Just tmplData)
where
- operation = Wasp.Operation.ActionOp action
+ operation = AS.Operation.ActionOp actionName action
tmplFile = [relfile|src/actions/_action.js|]
- dstFile = C.serverSrcDirInServerRootDir > actionFileInSrcDir action
+ dstFile = C.serverSrcDirInServerRootDir > actionFileInSrcDir actionName
tmplData = operationTmplData operation
-queryFileInSrcDir :: Wasp.Query.Query -> Path' (Rel C.ServerSrcDir) File'
-queryFileInSrcDir query =
+queryFileInSrcDir :: String -> Path' (Rel C.ServerSrcDir) File'
+queryFileInSrcDir queryName =
[reldir|queries|]
-- TODO: fromJust here could fail if there is some problem with the name, we should handle this.
- > fromJust (SP.parseRelFile $ Wasp.Query._name query ++ ".js")
+ > fromJust (SP.parseRelFile $ queryName ++ ".js")
-actionFileInSrcDir :: Wasp.Action.Action -> Path' (Rel C.ServerSrcDir) File'
-actionFileInSrcDir action =
+actionFileInSrcDir :: String -> Path' (Rel C.ServerSrcDir) File'
+actionFileInSrcDir actionName =
[reldir|actions|]
-- TODO: fromJust here could fail if there is some problem with the name, we should handle this.
- > fromJust (SP.parseRelFile $ Wasp.Action._name action ++ ".js")
+ > fromJust (SP.parseRelFile $ actionName ++ ".js")
-operationFileInSrcDir :: Wasp.Operation.Operation -> Path' (Rel C.ServerSrcDir) File'
-operationFileInSrcDir (Wasp.Operation.QueryOp query) = queryFileInSrcDir query
-operationFileInSrcDir (Wasp.Operation.ActionOp action) = actionFileInSrcDir action
+operationFileInSrcDir :: AS.Operation.Operation -> Path' (Rel C.ServerSrcDir) File'
+operationFileInSrcDir (AS.Operation.QueryOp name _) = queryFileInSrcDir name
+operationFileInSrcDir (AS.Operation.ActionOp name _) = actionFileInSrcDir name
-- | TODO: Make this not hardcoded!
relPosixPathFromOperationFileToExtSrcDir :: Path Posix (Rel Dir') (Dir GeneratedExternalCodeDir)
relPosixPathFromOperationFileToExtSrcDir = [reldirP|../ext-src/|]
-operationTmplData :: Wasp.Operation.Operation -> Aeson.Value
+operationTmplData :: AS.Operation.Operation -> Aeson.Value
operationTmplData operation =
object
[ "jsFnImportStatement" .= importStmt,
"jsFnIdentifier" .= importIdentifier,
- "entities" .= maybe [] (map buildEntityData) (Wasp.Operation.getEntities operation)
+ "entities" .= maybe [] (map (buildEntityData . AS.refName)) (AS.Operation.getEntities operation)
]
where
(importIdentifier, importStmt) =
- getImportDetailsForJsFnImport relPosixPathFromOperationFileToExtSrcDir $
- Wasp.Operation.getJsFn operation
+ getJsImportDetailsForExtFnImport relPosixPathFromOperationFileToExtSrcDir $
+ AS.Operation.getFn operation
buildEntityData :: String -> Aeson.Value
buildEntityData entityName =
object
diff --git a/waspc/src/Wasp/Generator/ServerGenerator/OperationsRoutesG.hs b/waspc/src/Wasp/Generator/ServerGenerator/OperationsRoutesG.hs
index 5c2614705..932de8ff9 100644
--- a/waspc/src/Wasp/Generator/ServerGenerator/OperationsRoutesG.hs
+++ b/waspc/src/Wasp/Generator/ServerGenerator/OperationsRoutesG.hs
@@ -1,3 +1,5 @@
+{-# LANGUAGE TypeApplications #-}
+
module Wasp.Generator.ServerGenerator.OperationsRoutesG
( genOperationsRoutes,
operationRouteInOperationsRouter,
@@ -9,54 +11,55 @@ import qualified Data.Aeson as Aeson
import Data.Maybe (fromJust, fromMaybe, isJust)
import StrongPath (Dir, File', Path, Path', Posix, Rel, reldir, reldirP, relfile, (>))
import qualified StrongPath as SP
+import Wasp.AppSpec (AppSpec, getApp)
+import qualified Wasp.AppSpec as AS
+import qualified Wasp.AppSpec.Action as AS.Action
+import qualified Wasp.AppSpec.App as AS.App
+import qualified Wasp.AppSpec.App.Auth as AS.Auth
+import qualified Wasp.AppSpec.Operation as AS.Operation
+import qualified Wasp.AppSpec.Query as AS.Query
import Wasp.Generator.FileDraft (FileDraft)
import qualified Wasp.Generator.ServerGenerator.Common as C
import Wasp.Generator.ServerGenerator.OperationsG (operationFileInSrcDir)
import qualified Wasp.Util as U
-import Wasp.Wasp (Wasp, getAuth)
-import qualified Wasp.Wasp as Wasp
-import qualified Wasp.Wasp.Action as Wasp.Action
-import qualified Wasp.Wasp.Auth as Wasp.Auth
-import qualified Wasp.Wasp.Operation as Wasp.Operation
-import qualified Wasp.Wasp.Query as Wasp.Query
-genOperationsRoutes :: Wasp -> [FileDraft]
-genOperationsRoutes wasp =
+genOperationsRoutes :: AppSpec -> [FileDraft]
+genOperationsRoutes spec =
concat
- [ map (genActionRoute wasp) (Wasp.getActions wasp),
- map (genQueryRoute wasp) (Wasp.getQueries wasp),
- [genOperationsRouter wasp]
+ [ map (genActionRoute spec) (AS.getActions spec),
+ map (genQueryRoute spec) (AS.getQueries spec),
+ [genOperationsRouter spec]
]
-genActionRoute :: Wasp -> Wasp.Action.Action -> FileDraft
-genActionRoute wasp action = genOperationRoute wasp op tmplFile
+genActionRoute :: AppSpec -> (String, AS.Action.Action) -> FileDraft
+genActionRoute spec (actionName, action) = genOperationRoute spec op tmplFile
where
- op = Wasp.Operation.ActionOp action
+ op = AS.Operation.ActionOp actionName action
tmplFile = C.asTmplFile [relfile|src/routes/operations/_action.js|]
-genQueryRoute :: Wasp -> Wasp.Query.Query -> FileDraft
-genQueryRoute wasp query = genOperationRoute wasp op tmplFile
+genQueryRoute :: AppSpec -> (String, AS.Query.Query) -> FileDraft
+genQueryRoute spec (queryName, query) = genOperationRoute spec op tmplFile
where
- op = Wasp.Operation.QueryOp query
+ op = AS.Operation.QueryOp queryName query
tmplFile = C.asTmplFile [relfile|src/routes/operations/_query.js|]
-genOperationRoute :: Wasp -> Wasp.Operation.Operation -> Path' (Rel C.ServerTemplatesDir) File' -> FileDraft
-genOperationRoute wasp operation tmplFile = C.makeTemplateFD tmplFile dstFile (Just tmplData)
+genOperationRoute :: AppSpec -> AS.Operation.Operation -> Path' (Rel C.ServerTemplatesDir) File' -> FileDraft
+genOperationRoute spec operation tmplFile = C.mkTmplFdWithDstAndData tmplFile dstFile (Just tmplData)
where
dstFile = operationsRoutesDirInServerRootDir > operationRouteFileInOperationsRoutesDir operation
baseTmplData =
object
- [ "operationImportPath" .= operationImportPath,
- "operationName" .= Wasp.Operation.getName operation
+ [ "operationImportPath" .= (operationImportPath :: FilePath),
+ "operationName" .= (AS.Operation.getName operation :: String)
]
- tmplData = case Wasp.getAuth wasp of
+ tmplData = case AS.App.auth (snd $ getApp spec) of
Nothing -> baseTmplData
Just auth ->
U.jsonSet
"userEntityLower"
- (Aeson.toJSON (U.toLowerFirst $ Wasp.Auth._userEntity auth))
+ (Aeson.toJSON (U.toLowerFirst $ AS.refName $ AS.Auth.userEntity auth))
baseTmplData
operationImportPath =
@@ -72,40 +75,45 @@ operationsRoutesDirInServerSrcDir = [reldir|routes/operations/|]
operationsRoutesDirInServerRootDir :: Path' (Rel C.ServerRootDir) (Dir OperationsRoutesDir)
operationsRoutesDirInServerRootDir = C.serverSrcDirInServerRootDir > operationsRoutesDirInServerSrcDir
-operationRouteFileInOperationsRoutesDir :: Wasp.Operation.Operation -> Path' (Rel OperationsRoutesDir) File'
-operationRouteFileInOperationsRoutesDir operation = fromJust $ SP.parseRelFile $ Wasp.Operation.getName operation ++ ".js"
+operationRouteFileInOperationsRoutesDir :: AS.Operation.Operation -> Path' (Rel OperationsRoutesDir) File'
+operationRouteFileInOperationsRoutesDir operation = fromJust $ SP.parseRelFile $ AS.Operation.getName operation ++ ".js"
relPosixPathFromOperationsRoutesDirToSrcDir :: Path Posix (Rel OperationsRoutesDir) (Dir C.ServerSrcDir)
relPosixPathFromOperationsRoutesDirToSrcDir = [reldirP|../..|]
-genOperationsRouter :: Wasp -> FileDraft
-genOperationsRouter wasp
+genOperationsRouter :: AppSpec -> FileDraft
+genOperationsRouter spec
-- TODO: Right now we are throwing error here, but we should instead perform this check in parsing/analyzer phase, as a semantic check, since we have all the info we need then already.
| any isAuthSpecifiedForOperation operations && not isAuthEnabledGlobally = error "`auth` cannot be specified for specific operations if it is not enabled for the whole app!"
- | otherwise = C.makeTemplateFD tmplFile dstFile (Just tmplData)
+ | otherwise = C.mkTmplFdWithDstAndData tmplFile dstFile (Just tmplData)
where
tmplFile = C.asTmplFile [relfile|src/routes/operations/index.js|]
dstFile = operationsRoutesDirInServerRootDir > [relfile|index.js|]
operations =
- map Wasp.Operation.ActionOp (Wasp.getActions wasp)
- ++ map Wasp.Operation.QueryOp (Wasp.getQueries wasp)
+ map (uncurry AS.Operation.ActionOp) (AS.getActions spec)
+ ++ map (uncurry AS.Operation.QueryOp) (AS.getQueries spec)
tmplData =
object
[ "operationRoutes" .= map makeOperationRoute operations,
"isAuthEnabled" .= isAuthEnabledGlobally
]
makeOperationRoute operation =
- let operationName = Wasp.Operation.getName operation
+ let operationName = AS.Operation.getName operation
in object
[ "importIdentifier" .= operationName,
- "importPath" .= ("./" ++ SP.fromRelFileP (fromJust $ SP.relFileToPosix $ operationRouteFileInOperationsRoutesDir operation)),
+ "importPath"
+ .= ( "./"
+ ++ SP.fromRelFileP
+ ( fromJust $ SP.relFileToPosix $ operationRouteFileInOperationsRoutesDir operation
+ )
+ ),
"routePath" .= ("/" ++ operationRouteInOperationsRouter operation),
"isUsingAuth" .= isAuthEnabledForOperation operation
]
- isAuthEnabledGlobally = isJust $ getAuth wasp
- isAuthEnabledForOperation operation = fromMaybe isAuthEnabledGlobally (Wasp.Operation.getAuth operation)
- isAuthSpecifiedForOperation operation = isJust $ Wasp.Operation.getAuth operation
+ isAuthEnabledGlobally = AS.isAuthEnabled spec
+ isAuthEnabledForOperation operation = fromMaybe isAuthEnabledGlobally (AS.Operation.getAuth operation)
+ isAuthSpecifiedForOperation operation = isJust $ AS.Operation.getAuth operation
-operationRouteInOperationsRouter :: Wasp.Operation.Operation -> String
-operationRouteInOperationsRouter = U.camelToKebabCase . Wasp.Operation.getName
+operationRouteInOperationsRouter :: AS.Operation.Operation -> String
+operationRouteInOperationsRouter = U.camelToKebabCase . AS.Operation.getName
diff --git a/waspc/src/Wasp/Generator/WebAppGenerator.hs b/waspc/src/Wasp/Generator/WebAppGenerator.hs
index f1356dfb6..615d3328c 100644
--- a/waspc/src/Wasp/Generator/WebAppGenerator.hs
+++ b/waspc/src/Wasp/Generator/WebAppGenerator.hs
@@ -4,12 +4,9 @@ module Wasp.Generator.WebAppGenerator
)
where
-import Data.Aeson
- ( ToJSON (..),
- object,
- (.=),
- )
+import Data.Aeson (object, (.=))
import Data.List (intercalate)
+import Data.Maybe (fromMaybe)
import StrongPath
( Dir,
Path',
@@ -18,7 +15,10 @@ import StrongPath
relfile,
(>),
)
-import Wasp.CompileOptions (CompileOptions)
+import Wasp.AppSpec (AppSpec, getApp)
+import qualified Wasp.AppSpec as AS
+import qualified Wasp.AppSpec.App as AS.App
+import qualified Wasp.AppSpec.App.Dependency as AS.Dependency
import Wasp.Generator.ExternalCodeGenerator (generateExternalCodeDir)
import Wasp.Generator.FileDraft
import Wasp.Generator.PackageJsonGenerator
@@ -35,34 +35,30 @@ import qualified Wasp.Generator.WebAppGenerator.Common as C
import qualified Wasp.Generator.WebAppGenerator.ExternalCodeGenerator as WebAppExternalCodeGenerator
import Wasp.Generator.WebAppGenerator.OperationsGenerator (genOperations)
import qualified Wasp.Generator.WebAppGenerator.RouterGenerator as RouterGenerator
-import qualified Wasp.NpmDependency as ND
-import Wasp.Wasp
-import qualified Wasp.Wasp.App as Wasp.App
-import qualified Wasp.Wasp.NpmDependencies as WND
-generateWebApp :: Wasp -> CompileOptions -> [FileDraft]
-generateWebApp wasp _ =
+generateWebApp :: AppSpec -> [FileDraft]
+generateWebApp spec =
concat
- [ [generateReadme wasp],
- [genPackageJson wasp waspNpmDeps],
- [generateGitignore wasp],
- generatePublicDir wasp,
- generateSrcDir wasp,
- generateExternalCodeDir WebAppExternalCodeGenerator.generatorStrategy wasp,
- [C.makeSimpleTemplateFD (asTmplFile [relfile|netlify.toml|]) wasp]
+ [ [generateReadme],
+ [genPackageJson spec waspNpmDeps],
+ [generateGitignore],
+ generatePublicDir spec,
+ generateSrcDir spec,
+ generateExternalCodeDir WebAppExternalCodeGenerator.generatorStrategy (AS.externalCodeFiles spec),
+ [C.mkTmplFd $ asTmplFile [relfile|netlify.toml|]]
]
-generateReadme :: Wasp -> FileDraft
-generateReadme wasp = C.makeSimpleTemplateFD (asTmplFile [relfile|README.md|]) wasp
+generateReadme :: FileDraft
+generateReadme = C.mkTmplFd $ asTmplFile [relfile|README.md|]
-genPackageJson :: Wasp -> [ND.NpmDependency] -> FileDraft
-genPackageJson wasp waspDeps =
- C.makeTemplateFD
+genPackageJson :: AppSpec -> [AS.Dependency.Dependency] -> FileDraft
+genPackageJson spec waspDeps =
+ C.mkTmplFdWithDstAndData
(C.asTmplFile [relfile|package.json|])
(C.asWebAppFile [relfile|package.json|])
( Just $
object
- [ "wasp" .= wasp,
+ [ "appName" .= (fst (getApp spec) :: String),
"depsChunk" .= npmDepsToPackageJsonEntry (resolvedWaspDeps ++ resolvedUserDeps)
]
)
@@ -72,12 +68,12 @@ genPackageJson wasp waspDeps =
Right deps -> deps
Left depsAndErrors -> error $ intercalate " ; " $ map snd depsAndErrors
- userDeps :: [ND.NpmDependency]
- userDeps = WND._dependencies $ Wasp.Wasp.getNpmDependencies wasp
+ userDeps :: [AS.Dependency.Dependency]
+ userDeps = fromMaybe [] $ AS.App.dependencies $ snd $ getApp spec
-waspNpmDeps :: [ND.NpmDependency]
+waspNpmDeps :: [AS.Dependency.Dependency]
waspNpmDeps =
- ND.fromList
+ AS.Dependency.fromList
[ ("axios", "^0.21.1"),
("lodash", "^4.17.15"),
("react", "^16.12.0"),
@@ -90,25 +86,26 @@ waspNpmDeps =
-- TODO: Also extract devDependencies like we did dependencies (waspNpmDeps).
-generateGitignore :: Wasp -> FileDraft
-generateGitignore wasp =
- C.makeTemplateFD
+generateGitignore :: FileDraft
+generateGitignore =
+ C.mkTmplFdWithDst
(asTmplFile [relfile|gitignore|])
(asWebAppFile [relfile|.gitignore|])
- (Just $ toJSON wasp)
-generatePublicDir :: Wasp -> [FileDraft]
-generatePublicDir wasp =
- C.copyTmplAsIs (asTmplFile [relfile|public/favicon.ico|]) :
- generatePublicIndexHtml wasp :
- map
- (\path -> C.makeSimpleTemplateFD (asTmplFile $ [reldir|public|] > path) wasp)
- [ [relfile|manifest.json|]
- ]
+generatePublicDir :: AppSpec -> [FileDraft]
+generatePublicDir spec =
+ C.mkTmplFd (asTmplFile [relfile|public/favicon.ico|]) :
+ generatePublicIndexHtml spec :
+ ( let tmplData = object ["appName" .= (fst (getApp spec) :: String)]
+ processPublicTmpl path = C.mkTmplFdWithData (asTmplFile $ [reldir|public|] > path) tmplData
+ in processPublicTmpl
+ <$> [ [relfile|manifest.json|]
+ ]
+ )
-generatePublicIndexHtml :: Wasp -> FileDraft
-generatePublicIndexHtml wasp =
- C.makeTemplateFD
+generatePublicIndexHtml :: AppSpec -> FileDraft
+generatePublicIndexHtml spec =
+ C.mkTmplFdWithDstAndData
(asTmplFile [relfile|public/index.html|])
targetPath
(Just templateData)
@@ -116,8 +113,8 @@ generatePublicIndexHtml wasp =
targetPath = [relfile|public/index.html|]
templateData =
object
- [ "title" .= Wasp.App.appTitle (getApp wasp),
- "head" .= maybe "" (intercalate "\n") (Wasp.App.appHead $ getApp wasp)
+ [ "title" .= (AS.App.title (snd $ getApp spec) :: String),
+ "head" .= (maybe "" (intercalate "\n") (AS.App.head $ snd $ getApp spec) :: String)
]
-- * Src dir
@@ -132,15 +129,15 @@ srcDir = C.webAppSrcDirInWebAppRootDir
-- | Generates api.js file which contains token management and configured api (e.g. axios) instance.
genApi :: FileDraft
-genApi = C.copyTmplAsIs (C.asTmplFile [relfile|src/api.js|])
+genApi = C.mkTmplFd (C.asTmplFile [relfile|src/api.js|])
-generateSrcDir :: Wasp -> [FileDraft]
-generateSrcDir wasp =
+generateSrcDir :: AppSpec -> [FileDraft]
+generateSrcDir spec =
generateLogo :
- RouterGenerator.generateRouter wasp :
+ RouterGenerator.generateRouter spec :
genApi :
map
- makeSimpleSrcTemplateFD
+ processSrcTmpl
[ [relfile|index.js|],
[relfile|index.css|],
[relfile|serviceWorker.js|],
@@ -148,16 +145,15 @@ generateSrcDir wasp =
[relfile|queryCache.js|],
[relfile|utils.js|]
]
- ++ genOperations wasp
- ++ AuthG.genAuth wasp
+ ++ genOperations spec
+ ++ AuthG.genAuth spec
where
generateLogo =
- C.makeTemplateFD
+ C.mkTmplFdWithDstAndData
(asTmplFile [relfile|src/logo.png|])
(srcDir > asWebAppSrcFile [relfile|logo.png|])
Nothing
- makeSimpleSrcTemplateFD path =
- C.makeTemplateFD
+ processSrcTmpl path =
+ C.mkTmplFdWithDst
(asTmplFile $ [reldir|src|] > path)
(srcDir > asWebAppSrcFile path)
- (Just $ toJSON wasp)
diff --git a/waspc/src/Wasp/Generator/WebAppGenerator/AuthG.hs b/waspc/src/Wasp/Generator/WebAppGenerator/AuthG.hs
index 50613ddc0..d7686452e 100644
--- a/waspc/src/Wasp/Generator/WebAppGenerator/AuthG.hs
+++ b/waspc/src/Wasp/Generator/WebAppGenerator/AuthG.hs
@@ -5,14 +5,17 @@ where
import Data.Aeson (object, (.=))
import Data.Aeson.Types (Pair)
+import Data.Maybe (fromMaybe)
import StrongPath (File', Path', Rel', reldir, relfile, (>))
+import Wasp.AppSpec (AppSpec)
+import qualified Wasp.AppSpec as AS
+import qualified Wasp.AppSpec.App as AS.App
+import qualified Wasp.AppSpec.App.Auth as AS.Auth
import Wasp.Generator.FileDraft (FileDraft)
import Wasp.Generator.WebAppGenerator.Common as C
-import Wasp.Wasp (Wasp, getAuth)
-import qualified Wasp.Wasp.Auth as Wasp.Auth
-genAuth :: Wasp -> [FileDraft]
-genAuth wasp = case maybeAuth of
+genAuth :: AppSpec -> [FileDraft]
+genAuth spec = case maybeAuth of
Just auth ->
[ genSignup,
genLogin,
@@ -23,54 +26,56 @@ genAuth wasp = case maybeAuth of
++ genAuthForms auth
Nothing -> []
where
- maybeAuth = getAuth wasp
+ maybeAuth = AS.App.auth $ snd $ AS.getApp spec
-- | Generates file with signup function to be used by Wasp developer.
genSignup :: FileDraft
-genSignup = C.copyTmplAsIs (C.asTmplFile [relfile|src/auth/signup.js|])
+genSignup = C.mkTmplFd (C.asTmplFile [relfile|src/auth/signup.js|])
-- | Generates file with login function to be used by Wasp developer.
genLogin :: FileDraft
-genLogin = C.copyTmplAsIs (C.asTmplFile [relfile|src/auth/login.js|])
+genLogin = C.mkTmplFd (C.asTmplFile [relfile|src/auth/login.js|])
-- | Generates file with logout function to be used by Wasp developer.
genLogout :: FileDraft
-genLogout = C.copyTmplAsIs (C.asTmplFile [relfile|src/auth/logout.js|])
+genLogout = C.mkTmplFd (C.asTmplFile [relfile|src/auth/logout.js|])
-- | Generates HOC that handles auth for the given page.
-genCreateAuthRequiredPage :: Wasp.Auth.Auth -> FileDraft
+genCreateAuthRequiredPage :: AS.Auth.Auth -> FileDraft
genCreateAuthRequiredPage auth =
compileTmplToSamePath
[relfile|auth/pages/createAuthRequiredPage.js|]
- ["onAuthFailedRedirectTo" .= Wasp.Auth._onAuthFailedRedirectTo auth]
+ ["onAuthFailedRedirectTo" .= AS.Auth.onAuthFailedRedirectTo auth]
-- | Generates React hook that Wasp developer can use in a component to get
-- access to the currently logged in user (and check whether user is logged in
-- ot not).
genUseAuth :: FileDraft
-genUseAuth = C.copyTmplAsIs (C.asTmplFile [relfile|src/auth/useAuth.js|])
+genUseAuth = C.mkTmplFd (C.asTmplFile [relfile|src/auth/useAuth.js|])
-genAuthForms :: Wasp.Auth.Auth -> [FileDraft]
+genAuthForms :: AS.Auth.Auth -> [FileDraft]
genAuthForms auth =
[ genLoginForm auth,
genSignupForm auth
]
-genLoginForm :: Wasp.Auth.Auth -> FileDraft
+genLoginForm :: AS.Auth.Auth -> FileDraft
genLoginForm auth =
+ -- TODO: Logic that says "/" is a default redirect on success is duplicated here and in the function below.
+ -- We should remove that duplication.
compileTmplToSamePath
[relfile|auth/forms/Login.js|]
- ["onAuthSucceededRedirectTo" .= Wasp.Auth._onAuthSucceededRedirectTo auth]
+ ["onAuthSucceededRedirectTo" .= fromMaybe "/" (AS.Auth.onAuthSucceededRedirectTo auth)]
-genSignupForm :: Wasp.Auth.Auth -> FileDraft
+genSignupForm :: AS.Auth.Auth -> FileDraft
genSignupForm auth =
compileTmplToSamePath
[relfile|auth/forms/Signup.js|]
- ["onAuthSucceededRedirectTo" .= Wasp.Auth._onAuthSucceededRedirectTo auth]
+ ["onAuthSucceededRedirectTo" .= fromMaybe "/" (AS.Auth.onAuthSucceededRedirectTo auth)]
compileTmplToSamePath :: Path' Rel' File' -> [Pair] -> FileDraft
compileTmplToSamePath tmplFileInTmplSrcDir keyValuePairs =
- C.makeTemplateFD
+ C.mkTmplFdWithDstAndData
(asTmplFile $ [reldir|src|] > tmplFileInTmplSrcDir)
targetPath
(Just templateData)
diff --git a/waspc/src/Wasp/Generator/WebAppGenerator/Common.hs b/waspc/src/Wasp/Generator/WebAppGenerator/Common.hs
index 88742946a..758bb967f 100644
--- a/waspc/src/Wasp/Generator/WebAppGenerator/Common.hs
+++ b/waspc/src/Wasp/Generator/WebAppGenerator/Common.hs
@@ -1,9 +1,10 @@
module Wasp.Generator.WebAppGenerator.Common
( webAppRootDirInProjectRootDir,
webAppSrcDirInWebAppRootDir,
- copyTmplAsIs,
- makeSimpleTemplateFD,
- makeTemplateFD,
+ mkTmplFd,
+ mkTmplFdWithDst,
+ mkTmplFdWithData,
+ mkTmplFdWithDstAndData,
webAppSrcDirInProjectRootDir,
webAppTemplatesDirInTemplatesDir,
asTmplFile,
@@ -21,7 +22,6 @@ import qualified StrongPath as SP
import Wasp.Generator.Common (ProjectRootDir)
import Wasp.Generator.FileDraft (FileDraft, createTemplateFileDraft)
import Wasp.Generator.Templates (TemplatesDir)
-import Wasp.Wasp (Wasp)
data WebAppRootDir
@@ -57,18 +57,21 @@ webAppSrcDirInProjectRootDir = webAppRootDirInProjectRootDir > webAppSrcDirInW
webAppTemplatesDirInTemplatesDir :: Path' (Rel TemplatesDir) (Dir WebAppTemplatesDir)
webAppTemplatesDirInTemplatesDir = [reldir|react-app|]
-copyTmplAsIs :: Path' (Rel WebAppTemplatesDir) File' -> FileDraft
-copyTmplAsIs path = makeTemplateFD path (SP.castRel path) Nothing
+mkTmplFd :: Path' (Rel WebAppTemplatesDir) File' -> FileDraft
+mkTmplFd path = mkTmplFdWithDst path (SP.castRel path)
-makeSimpleTemplateFD :: Path' (Rel WebAppTemplatesDir) File' -> Wasp -> FileDraft
-makeSimpleTemplateFD path wasp = makeTemplateFD path (SP.castRel path) (Just $ Aeson.toJSON wasp)
+mkTmplFdWithDst :: Path' (Rel WebAppTemplatesDir) File' -> Path' (Rel WebAppRootDir) File' -> FileDraft
+mkTmplFdWithDst src dst = mkTmplFdWithDstAndData src dst Nothing
-makeTemplateFD ::
+mkTmplFdWithData :: Path' (Rel WebAppTemplatesDir) File' -> Aeson.Value -> FileDraft
+mkTmplFdWithData src tmplData = mkTmplFdWithDstAndData src (SP.castRel src) (Just tmplData)
+
+mkTmplFdWithDstAndData ::
Path' (Rel WebAppTemplatesDir) File' ->
Path' (Rel WebAppRootDir) File' ->
Maybe Aeson.Value ->
FileDraft
-makeTemplateFD srcPathInWebAppTemplatesDir dstPathInWebAppRootDir tmplData =
+mkTmplFdWithDstAndData srcPathInWebAppTemplatesDir dstPathInWebAppRootDir tmplData =
createTemplateFileDraft
(webAppRootDirInProjectRootDir > dstPathInWebAppRootDir)
(webAppTemplatesDirInTemplatesDir > srcPathInWebAppTemplatesDir)
diff --git a/waspc/src/Wasp/Generator/WebAppGenerator/OperationsGenerator.hs b/waspc/src/Wasp/Generator/WebAppGenerator/OperationsGenerator.hs
index b36846a33..2befa149e 100644
--- a/waspc/src/Wasp/Generator/WebAppGenerator/OperationsGenerator.hs
+++ b/waspc/src/Wasp/Generator/WebAppGenerator/OperationsGenerator.hs
@@ -1,3 +1,5 @@
+{-# LANGUAGE TypeApplications #-}
+
module Wasp.Generator.WebAppGenerator.OperationsGenerator
( genOperations,
)
@@ -10,44 +12,44 @@ import Data.Aeson
import Data.List (intercalate)
import Data.Maybe (fromJust)
import StrongPath (File', Path', Rel', parseRelFile, reldir, relfile, (>))
+import Wasp.AppSpec (AppSpec)
+import qualified Wasp.AppSpec as AS
+import qualified Wasp.AppSpec.Action as AS.Action
+import qualified Wasp.AppSpec.Operation as AS.Operation
+import qualified Wasp.AppSpec.Query as AS.Query
import Wasp.Generator.FileDraft (FileDraft)
import qualified Wasp.Generator.ServerGenerator as ServerGenerator
import qualified Wasp.Generator.ServerGenerator.OperationsRoutesG as ServerOperationsRoutesG
import qualified Wasp.Generator.WebAppGenerator.Common as C
import qualified Wasp.Generator.WebAppGenerator.OperationsGenerator.ResourcesG as Resources
-import Wasp.Wasp (Wasp)
-import qualified Wasp.Wasp as Wasp
-import qualified Wasp.Wasp.Action as Wasp.Action
-import qualified Wasp.Wasp.Operation as Wasp.Operation
-import qualified Wasp.Wasp.Query as Wasp.Query
-genOperations :: Wasp -> [FileDraft]
-genOperations wasp =
+genOperations :: AppSpec -> [FileDraft]
+genOperations spec =
concat
- [ genQueries wasp,
- genActions wasp,
- [C.makeSimpleTemplateFD (C.asTmplFile [relfile|src/operations/index.js|]) wasp],
- Resources.genResources wasp
+ [ genQueries spec,
+ genActions spec,
+ [C.mkTmplFd $ C.asTmplFile [relfile|src/operations/index.js|]],
+ Resources.genResources spec
]
-genQueries :: Wasp -> [FileDraft]
-genQueries wasp =
- map (genQuery wasp) (Wasp.getQueries wasp)
- ++ [C.makeSimpleTemplateFD (C.asTmplFile [relfile|src/queries/index.js|]) wasp]
+genQueries :: AppSpec -> [FileDraft]
+genQueries spec =
+ map (genQuery spec) (AS.getQueries spec)
+ ++ [C.mkTmplFd $ C.asTmplFile [relfile|src/queries/index.js|]]
-genActions :: Wasp -> [FileDraft]
-genActions wasp =
- map (genAction wasp) (Wasp.getActions wasp)
+genActions :: AppSpec -> [FileDraft]
+genActions spec =
+ map (genAction spec) (AS.getActions spec)
-genQuery :: Wasp -> Wasp.Query.Query -> FileDraft
-genQuery _ query = C.makeTemplateFD tmplFile dstFile (Just tmplData)
+genQuery :: AppSpec -> (String, AS.Query.Query) -> FileDraft
+genQuery _ (queryName, query) = C.mkTmplFdWithDstAndData tmplFile dstFile (Just tmplData)
where
tmplFile = C.asTmplFile [relfile|src/queries/_query.js|]
dstFile = C.asWebAppFile $ [reldir|src/queries/|] > fromJust (getOperationDstFileName operation)
tmplData =
object
- [ "queryFnName" .= Wasp.Query._name query,
+ [ "queryFnName" .= (queryName :: String),
"queryRoute"
.= ( ServerGenerator.operationsRouteInRootRouter
++ "/"
@@ -55,17 +57,17 @@ genQuery _ query = C.makeTemplateFD tmplFile dstFile (Just tmplData)
),
"entitiesArray" .= makeJsArrayOfEntityNames operation
]
- operation = Wasp.Operation.QueryOp query
+ operation = AS.Operation.QueryOp queryName query
-genAction :: Wasp -> Wasp.Action.Action -> FileDraft
-genAction _ action = C.makeTemplateFD tmplFile dstFile (Just tmplData)
+genAction :: AppSpec -> (String, AS.Action.Action) -> FileDraft
+genAction _ (actionName, action) = C.mkTmplFdWithDstAndData tmplFile dstFile (Just tmplData)
where
tmplFile = C.asTmplFile [relfile|src/actions/_action.js|]
dstFile = C.asWebAppFile $ [reldir|src/actions/|] > fromJust (getOperationDstFileName operation)
tmplData =
object
- [ "actionFnName" .= Wasp.Action._name action,
+ [ "actionFnName" .= (actionName :: String),
"actionRoute"
.= ( ServerGenerator.operationsRouteInRootRouter
++ "/"
@@ -73,14 +75,14 @@ genAction _ action = C.makeTemplateFD tmplFile dstFile (Just tmplData)
),
"entitiesArray" .= makeJsArrayOfEntityNames operation
]
- operation = Wasp.Operation.ActionOp action
+ operation = AS.Operation.ActionOp actionName action
-- | Generates string that is JS array containing names (as strings) of entities being used by given operation.
-- E.g. "['Task', 'Project']"
-makeJsArrayOfEntityNames :: Wasp.Operation.Operation -> String
+makeJsArrayOfEntityNames :: AS.Operation.Operation -> String
makeJsArrayOfEntityNames operation = "[" ++ intercalate ", " entityStrings ++ "]"
where
- entityStrings = maybe [] (map (\x -> "'" ++ x ++ "'")) (Wasp.Operation.getEntities operation)
+ entityStrings = maybe [] (map $ \x -> "'" ++ AS.refName x ++ "'") (AS.Operation.getEntities operation)
-getOperationDstFileName :: Wasp.Operation.Operation -> Maybe (Path' Rel' File')
-getOperationDstFileName operation = parseRelFile (Wasp.Operation.getName operation ++ ".js")
+getOperationDstFileName :: AS.Operation.Operation -> Maybe (Path' Rel' File')
+getOperationDstFileName operation = parseRelFile (AS.Operation.getName operation ++ ".js")
diff --git a/waspc/src/Wasp/Generator/WebAppGenerator/OperationsGenerator/ResourcesG.hs b/waspc/src/Wasp/Generator/WebAppGenerator/OperationsGenerator/ResourcesG.hs
index 3b5ac6706..3fd4bd190 100644
--- a/waspc/src/Wasp/Generator/WebAppGenerator/OperationsGenerator/ResourcesG.hs
+++ b/waspc/src/Wasp/Generator/WebAppGenerator/OperationsGenerator/ResourcesG.hs
@@ -5,12 +5,12 @@ where
import Data.Aeson (object)
import StrongPath (relfile)
+import Wasp.AppSpec (AppSpec)
import Wasp.Generator.FileDraft (FileDraft)
import qualified Wasp.Generator.WebAppGenerator.Common as C
-import Wasp.Wasp (Wasp)
-genResources :: Wasp -> [FileDraft]
-genResources _ = [C.makeTemplateFD tmplFile dstFile (Just tmplData)]
+genResources :: AppSpec -> [FileDraft]
+genResources _ = [C.mkTmplFdWithDstAndData tmplFile dstFile (Just tmplData)]
where
tmplFile = C.asTmplFile [relfile|src/operations/resources.js|]
dstFile = C.asWebAppFile [relfile|src/operations/resources.js|] -- TODO: Un-hardcode this by combining path to operations dir with path to resources file in it.
diff --git a/waspc/src/Wasp/Generator/WebAppGenerator/RouterGenerator.hs b/waspc/src/Wasp/Generator/WebAppGenerator/RouterGenerator.hs
index 957980cec..72c9b8ad1 100644
--- a/waspc/src/Wasp/Generator/WebAppGenerator/RouterGenerator.hs
+++ b/waspc/src/Wasp/Generator/WebAppGenerator/RouterGenerator.hs
@@ -1,3 +1,5 @@
+{-# LANGUAGE TypeApplications #-}
+
module Wasp.Generator.WebAppGenerator.RouterGenerator
( generateRouter,
)
@@ -5,17 +7,18 @@ where
import Data.Aeson (ToJSON (..), object, (.=))
import Data.List (find)
-import Data.Maybe (fromJust, fromMaybe, isJust)
+import Data.Maybe (fromMaybe)
import StrongPath (reldir, relfile, (>))
import qualified StrongPath as SP
+import qualified System.FilePath as FP
+import Wasp.AppSpec (AppSpec)
+import qualified Wasp.AppSpec as AS
+import qualified Wasp.AppSpec.ExtImport as AS.ExtImport
+import qualified Wasp.AppSpec.Page as AS.Page
+import qualified Wasp.AppSpec.Route as AS.Route
import Wasp.Generator.FileDraft (FileDraft)
import Wasp.Generator.WebAppGenerator.Common (asTmplFile, asWebAppSrcFile)
import qualified Wasp.Generator.WebAppGenerator.Common as C
-import Wasp.Wasp (Wasp)
-import qualified Wasp.Wasp as Wasp
-import qualified Wasp.Wasp.JsImport as Wasp.JsImport
-import qualified Wasp.Wasp.Page as Wasp.Page
-import qualified Wasp.Wasp.Route as Wasp.Route
data RouterTemplateData = RouterTemplateData
{ _routes :: ![RouteTemplateData],
@@ -56,47 +59,52 @@ instance ToJSON PageTemplateData where
"importFrom" .= _importFrom pageTD
]
-generateRouter :: Wasp -> FileDraft
-generateRouter wasp =
- C.makeTemplateFD
+generateRouter :: AppSpec -> FileDraft
+generateRouter spec =
+ C.mkTmplFdWithDstAndData
(asTmplFile $ [reldir|src|] > routerPath)
targetPath
(Just $ toJSON templateData)
where
routerPath = [relfile|router.js|]
- templateData = createRouterTemplateData wasp
+ templateData = createRouterTemplateData spec
targetPath = C.webAppSrcDirInWebAppRootDir > asWebAppSrcFile routerPath
-createRouterTemplateData :: Wasp -> RouterTemplateData
-createRouterTemplateData wasp =
+createRouterTemplateData :: AppSpec -> RouterTemplateData
+createRouterTemplateData spec =
RouterTemplateData
{ _routes = routes,
_pagesToImport = pages,
- _isAuthEnabled = isJust $ Wasp.getAuth wasp
+ _isAuthEnabled = AS.isAuthEnabled spec
}
where
- routes = map (createRouteTemplateData wasp) $ Wasp.getRoutes wasp
- pages = map createPageTemplateData $ Wasp.getPages wasp
+ routes = map (createRouteTemplateData spec) $ AS.getRoutes spec
+ pages = map createPageTemplateData $ AS.getPages spec
-createRouteTemplateData :: Wasp -> Wasp.Route.Route -> RouteTemplateData
-createRouteTemplateData wasp route =
+createRouteTemplateData :: AppSpec -> (String, AS.Route.Route) -> RouteTemplateData
+createRouteTemplateData spec namedRoute@(_, route) =
RouteTemplateData
- { _urlPath = Wasp.Route._urlPath route,
- _targetComponent = determineRouteTargetComponent wasp route
+ { _urlPath = AS.Route.path route,
+ _targetComponent = determineRouteTargetComponent spec namedRoute
}
-determineRouteTargetComponent :: Wasp -> Wasp.Route.Route -> String
-determineRouteTargetComponent wasp route =
+determineRouteTargetComponent :: AppSpec -> (String, AS.Route.Route) -> String
+determineRouteTargetComponent spec (_, route) =
maybe
targetPageName
determineRouteTargetComponent'
- (Wasp.Page._authRequired targetPage)
+ (AS.Page.authRequired $ snd targetPage)
where
- targetPageName = Wasp.Route._targetPage route
+ targetPageName = AS.refName (AS.Route.to route :: AS.Ref AS.Page.Page)
targetPage =
fromMaybe
- (error $ "Can't find page with name '" ++ targetPageName ++ "', pointed to by route '" ++ Wasp.Route._urlPath route ++ "'")
- (find ((==) targetPageName . Wasp.Page._name) (Wasp.getPages wasp))
+ ( error $
+ "Can't find page with name '" ++ targetPageName
+ ++ "', pointed to by route '"
+ ++ AS.Route.path route
+ ++ "'"
+ )
+ (find ((==) targetPageName . fst) (AS.getPages spec))
determineRouteTargetComponent' :: Bool -> String
determineRouteTargetComponent' authRequired =
@@ -105,21 +113,20 @@ determineRouteTargetComponent wasp route =
"createAuthRequiredPage(" ++ targetPageName ++ ")"
else targetPageName
-createPageTemplateData :: Wasp.Page.Page -> PageTemplateData
+createPageTemplateData :: (String, AS.Page.Page) -> PageTemplateData
createPageTemplateData page =
PageTemplateData
- { _importFrom =
- relPathToExtSrcDir
- ++ SP.fromRelFileP (fromJust $ SP.relFileToPosix $ Wasp.JsImport._from pageComponent),
- _importWhat = case Wasp.JsImport._namedImports pageComponent of
- -- If no named imports, we go with the default import.
- [] -> pageName
- [namedImport] -> "{ " ++ namedImport ++ " as " ++ pageName ++ " }"
- _ -> error "Only one named import can be provided for a page."
+ { _importFrom = relPathToExtSrcDir FP.> SP.fromRelFileP (AS.ExtImport.path pageComponent),
+ _importWhat = case AS.ExtImport.name pageComponent of
+ AS.ExtImport.ExtImportModule _ -> pageName
+ AS.ExtImport.ExtImportField identifier -> "{ " ++ identifier ++ " as " ++ pageName ++ " }"
}
where
relPathToExtSrcDir :: FilePath
relPathToExtSrcDir = "./ext-src/"
- pageName = Wasp.Page._name page
- pageComponent = Wasp.Page._component page
+ pageName :: String
+ pageName = fst page
+
+ pageComponent :: AS.ExtImport.ExtImport
+ pageComponent = AS.Page.component $ snd page
diff --git a/waspc/src/Wasp/Lib.hs b/waspc/src/Wasp/Lib.hs
index 70fd90a9a..38b64a8e7 100644
--- a/waspc/src/Wasp/Lib.hs
+++ b/waspc/src/Wasp/Lib.hs
@@ -11,16 +11,17 @@ import Data.List (find, isSuffixOf)
import StrongPath (Abs, Dir, File', Path', relfile)
import qualified StrongPath as SP
import System.Directory (doesDirectoryExist, doesFileExist)
+import qualified Wasp.Analyzer as Analyzer
+import Wasp.Analyzer.AnalyzeError (getErrorMessageAndCtx)
+import qualified Wasp.AppSpec as AS
import Wasp.Common (DbMigrationsDir, WaspProjectDir, dbMigrationsDirInWaspProjectDir)
import Wasp.CompileOptions (CompileOptions)
import qualified Wasp.CompileOptions as CompileOptions
+import Wasp.Error (showCompilerErrorForTerminal)
import qualified Wasp.ExternalCode as ExternalCode
import qualified Wasp.Generator as Generator
import Wasp.Generator.Common (ProjectRootDir)
-import qualified Wasp.Parser as Parser
import qualified Wasp.Util.IO as Util.IO
-import Wasp.Wasp (Wasp)
-import qualified Wasp.Wasp as Wasp
type CompileError = String
@@ -30,34 +31,33 @@ compile ::
CompileOptions ->
IO (Either CompileError ())
compile waspDir outDir options = do
- maybeWaspFile <- findWaspFile waspDir
- case maybeWaspFile of
+ maybeWaspFilePath <- findWaspFile waspDir
+ case maybeWaspFilePath of
Nothing -> return $ Left "Couldn't find a single *.wasp file."
- Just waspFile -> do
- waspStr <- readFile (SP.toFilePath waspFile)
-
- case Parser.parseWasp waspStr of
- Left err -> return $ Left (show err)
- Right wasp -> do
+ Just waspFilePath -> do
+ waspFileContent <- readFile (SP.fromAbsFile waspFilePath)
+ case Analyzer.analyze waspFileContent of
+ Left analyzeError ->
+ return $
+ Left $
+ showCompilerErrorForTerminal
+ (waspFilePath, waspFileContent)
+ (getErrorMessageAndCtx analyzeError)
+ Right decls -> do
+ externalCodeFiles <-
+ ExternalCode.readFiles (CompileOptions.externalCodeDirPath options)
maybeDotEnvFile <- findDotEnvFile waspDir
maybeMigrationsDir <- findMigrationsDir waspDir
- ( wasp
- `Wasp.setDotEnvFile` maybeDotEnvFile
- `Wasp.setMigrationsDir` maybeMigrationsDir
- `enrichWaspASTBasedOnCompileOptions` options
- )
- >>= generateCode
- where
- generateCode wasp = Generator.writeWebAppCode wasp outDir options >> return (Right ())
-
-enrichWaspASTBasedOnCompileOptions :: Wasp -> CompileOptions -> IO Wasp
-enrichWaspASTBasedOnCompileOptions wasp options = do
- externalCodeFiles <- ExternalCode.readFiles (CompileOptions.externalCodeDirPath options)
- return
- ( wasp
- `Wasp.setExternalCodeFiles` externalCodeFiles
- `Wasp.setIsBuild` CompileOptions.isBuild options
- )
+ let appSpec =
+ AS.AppSpec
+ { AS.decls = decls,
+ AS.externalCodeFiles = externalCodeFiles,
+ AS.externalCodeDirPath = CompileOptions.externalCodeDirPath options,
+ AS.migrationsDir = maybeMigrationsDir,
+ AS.dotEnvFile = maybeDotEnvFile,
+ AS.isBuild = CompileOptions.isBuild options
+ }
+ Right <$> Generator.writeWebAppCode appSpec outDir
findWaspFile :: Path' Abs (Dir WaspProjectDir) -> IO (Maybe (Path' Abs File'))
findWaspFile waspDir = do
@@ -74,7 +74,9 @@ findDotEnvFile waspDir = do
dotEnvExists <- doesFileExist (SP.toFilePath dotEnvAbsPath)
return $ if dotEnvExists then Just dotEnvAbsPath else Nothing
-findMigrationsDir :: Path' Abs (Dir WaspProjectDir) -> IO (Maybe (Path' Abs (Dir DbMigrationsDir)))
+findMigrationsDir ::
+ Path' Abs (Dir WaspProjectDir) ->
+ IO (Maybe (Path' Abs (Dir DbMigrationsDir)))
findMigrationsDir waspDir = do
let migrationsAbsPath = waspDir SP.> dbMigrationsDirInWaspProjectDir
migrationsExists <- doesDirectoryExist $ SP.fromAbsDir migrationsAbsPath
diff --git a/waspc/src/Wasp/Parser.hs b/waspc/src/Wasp/Parser.hs
deleted file mode 100644
index 95badd146..000000000
--- a/waspc/src/Wasp/Parser.hs
+++ /dev/null
@@ -1,88 +0,0 @@
-module Wasp.Parser
- ( parseWasp,
- )
-where
-
-import Text.Parsec (ParseError, eof, many, many1, (<|>))
-import Text.Parsec.String (Parser)
-import Wasp.Lexer
-import qualified Wasp.Parser.Action as Parser.Action
-import Wasp.Parser.App (app)
-import Wasp.Parser.Auth (auth)
-import Wasp.Parser.Common (runWaspParser)
-import Wasp.Parser.Db (db)
-import Wasp.Parser.Entity (entity)
-import Wasp.Parser.JsImport (jsImport)
-import qualified Wasp.Parser.NpmDependencies as Parser.NpmDependencies
-import Wasp.Parser.Page (page)
-import qualified Wasp.Parser.Query as Parser.Query
-import Wasp.Parser.Route (route)
-import qualified Wasp.Parser.Server as Parser.Server
-import qualified Wasp.Wasp as Wasp
-
-waspElement :: Parser Wasp.WaspElement
-waspElement =
- waspElementApp
- <|> waspElementAuth
- <|> waspElementPage
- <|> waspElementDb
- <|> waspElementRoute
- <|> waspElementEntity
- <|> waspElementQuery
- <|> waspElementAction
- <|> waspElementNpmDependencies
- <|> waspElementServer
-
-waspElementApp :: Parser Wasp.WaspElement
-waspElementApp = Wasp.WaspElementApp <$> app
-
-waspElementAuth :: Parser Wasp.WaspElement
-waspElementAuth = Wasp.WaspElementAuth <$> auth
-
-waspElementDb :: Parser Wasp.WaspElement
-waspElementDb = Wasp.WaspElementDb <$> db
-
-waspElementPage :: Parser Wasp.WaspElement
-waspElementPage = Wasp.WaspElementPage <$> page
-
-waspElementRoute :: Parser Wasp.WaspElement
-waspElementRoute = Wasp.WaspElementRoute <$> route
-
-waspElementEntity :: Parser Wasp.WaspElement
-waspElementEntity = Wasp.WaspElementEntity <$> entity
-
-waspElementQuery :: Parser Wasp.WaspElement
-waspElementQuery = Wasp.WaspElementQuery <$> Parser.Query.query
-
-waspElementAction :: Parser Wasp.WaspElement
-waspElementAction = Wasp.WaspElementAction <$> Parser.Action.action
-
-waspElementServer :: Parser Wasp.WaspElement
-waspElementServer = Wasp.WaspElementServer <$> Parser.Server.server
-
-waspElementNpmDependencies :: Parser Wasp.WaspElement
-waspElementNpmDependencies = Wasp.WaspElementNpmDependencies <$> Parser.NpmDependencies.npmDependencies
-
--- | Top level parser, produces Wasp.
-waspParser :: Parser Wasp.Wasp
-waspParser = do
- -- NOTE(matija): this is the only place we need to use whiteSpace, to skip empty lines
- -- and comments in the beginning of file. All other used parsers are lexeme parsers
- -- so they do it themselves.
- whiteSpace
-
- jsImports <- many jsImport
-
- waspElems <- many1 waspElement
-
- eof
-
- -- TODO(matija): after we parsed everything, we should do semantic analysis
- -- e.g. check there is only 1 title - if not, throw a meaningful error.
- -- Also, check there is at least one Page defined.
-
- return $ Wasp.fromWaspElems waspElems `Wasp.setJsImports` jsImports
-
--- | Top level parser executor.
-parseWasp :: String -> Either ParseError Wasp.Wasp
-parseWasp = runWaspParser waspParser
diff --git a/waspc/src/Wasp/Parser/Action.hs b/waspc/src/Wasp/Parser/Action.hs
deleted file mode 100644
index 025faf48a..000000000
--- a/waspc/src/Wasp/Parser/Action.hs
+++ /dev/null
@@ -1,24 +0,0 @@
-module Wasp.Parser.Action
- ( action,
- )
-where
-
-import Data.Maybe (fromMaybe)
-import Text.Parsec.String (Parser)
-import qualified Wasp.Lexer as L
-import qualified Wasp.Parser.Common as C
-import qualified Wasp.Parser.Operation as Operation
-import Wasp.Wasp.Action (Action)
-import qualified Wasp.Wasp.Action as Action
-
-action :: Parser Action
-action = do
- (name, props) <- C.waspElementNameAndClosureContent L.reservedNameAction Operation.properties
- return
- Action.Action
- { Action._name = name,
- Action._jsFunction =
- fromMaybe (error "Action js function is missing.") (Operation.getJsFunctionFromProps props),
- Action._entities = Operation.getEntitiesFromProps props,
- Action._auth = Operation.getAuthEnabledFromProps props
- }
diff --git a/waspc/src/Wasp/Parser/App.hs b/waspc/src/Wasp/Parser/App.hs
deleted file mode 100644
index a011729be..000000000
--- a/waspc/src/Wasp/Parser/App.hs
+++ /dev/null
@@ -1,57 +0,0 @@
-module Wasp.Parser.App
- ( app,
- )
-where
-
-import Data.Maybe (listToMaybe)
-import Text.Parsec
-import Text.Parsec.String (Parser)
-import Wasp.Lexer
-import qualified Wasp.Lexer as L
-import Wasp.Parser.Common
-import qualified Wasp.Wasp.App as App
-
--- | A type that describes supported app properties.
-data AppProperty
- = Title !String
- | Favicon !String
- | Head [String]
- deriving (Show, Eq)
-
--- | Parses supported app properties, expects format "key1: value1, key2: value2, ..."
-appProperties :: Parser [AppProperty]
-appProperties =
- commaSep1 $
- appPropertyTitle
- <|> appPropertyFavicon
- <|> appPropertyHead
-
-appPropertyTitle :: Parser AppProperty
-appPropertyTitle = Title <$> waspPropertyStringLiteral "title"
-
-appPropertyFavicon :: Parser AppProperty
--- TODO(matija): 'fav.png' currently does not work because of '.'. Support it.
-appPropertyFavicon = Favicon <$> waspPropertyStringLiteral "favicon"
-
-appPropertyHead :: Parser AppProperty
-appPropertyHead = Head <$> waspProperty "head" (L.brackets $ L.commaSep1 L.stringLiteral)
-
--- TODO(matija): unsafe, what if empty list?
-getAppTitle :: [AppProperty] -> String
-getAppTitle ps = head $ [t | Title t <- ps]
-
-getAppHead :: [AppProperty] -> Maybe [String]
-getAppHead ps = listToMaybe [hs | Head hs <- ps]
-
--- | Top level parser, parses App.
-app :: Parser App.App
-app = do
- (appName, appProps) <- waspElementNameAndClosureContent reservedNameApp appProperties
-
- return
- App.App
- { App.appName = appName,
- App.appTitle = getAppTitle appProps,
- App.appHead = getAppHead appProps
- -- TODO(matija): add favicon.
- }
diff --git a/waspc/src/Wasp/Parser/Auth.hs b/waspc/src/Wasp/Parser/Auth.hs
deleted file mode 100644
index e9b5154af..000000000
--- a/waspc/src/Wasp/Parser/Auth.hs
+++ /dev/null
@@ -1,85 +0,0 @@
-module Wasp.Parser.Auth
- ( auth,
- )
-where
-
-import Control.Monad (when)
-import Text.Parsec (try, (<|>))
-import Text.Parsec.String (Parser)
-import qualified Wasp.Lexer as L
-import qualified Wasp.Parser.Common as P
-import qualified Wasp.Wasp.Auth as Wasp.Auth
-
-auth :: Parser Wasp.Auth.Auth
-auth = do
- L.reserved L.reservedNameAuth
- authProperties <- P.waspClosure (L.commaSep1 authProperty)
-
- let userEntityProps = [s | AuthPropertyUserEntity s <- authProperties]
- failIfPropMissing propUserEntityName userEntityProps
-
- let methodsProps = [ms | AuthPropertyMethods ms <- authProperties]
- failIfPropMissing propMethodsName methodsProps
-
- let failRedirectProps = [r | AuthPropertyOnAuthFailedRedirectTo r <- authProperties]
- failIfPropMissing propOnAuthFailedRedirectToName failRedirectProps
-
- let successRedirectProps = [r | AuthPropertyOnAuthSucceededRedirectTo r <- authProperties]
-
- return
- Wasp.Auth.Auth
- { Wasp.Auth._userEntity = head userEntityProps,
- Wasp.Auth._methods = head methodsProps,
- Wasp.Auth._onAuthFailedRedirectTo = head failRedirectProps,
- Wasp.Auth._onAuthSucceededRedirectTo = headWithDefault "/" successRedirectProps
- }
-
-headWithDefault :: a -> [a] -> a
-headWithDefault d ps = if null ps then d else head ps
-
--- TODO(matija): this should be extracted if we want to use in other places too.
-failIfPropMissing :: (Applicative m, MonadFail m) => String -> [p] -> m ()
-failIfPropMissing propName ps = when (null ps) $ fail errorMsg
- where
- errorMsg = propName ++ " is required!"
-
--- Auxiliary data structure used by parser.
-data AuthProperty
- = AuthPropertyUserEntity String
- | AuthPropertyMethods [Wasp.Auth.AuthMethod]
- | AuthPropertyOnAuthFailedRedirectTo String
- | AuthPropertyOnAuthSucceededRedirectTo String
-
-propUserEntityName :: String
-propUserEntityName = "userEntity"
-
-propMethodsName :: String
-propMethodsName = "methods"
-
-propOnAuthFailedRedirectToName :: String
-propOnAuthFailedRedirectToName = "onAuthFailedRedirectTo"
-
--- Sub-parsers
-
-authProperty :: Parser AuthProperty
-authProperty =
- authPropertyUserEntity
- <|> authPropertyMethods
- <|> (try authPropertyOnAuthFailedRedirectTo <|> authPropertyOnAuthSucceededRedirectTo)
-
-authPropertyOnAuthFailedRedirectTo :: Parser AuthProperty
-authPropertyOnAuthFailedRedirectTo =
- AuthPropertyOnAuthFailedRedirectTo <$> P.waspPropertyStringLiteral "onAuthFailedRedirectTo"
-
-authPropertyOnAuthSucceededRedirectTo :: Parser AuthProperty
-authPropertyOnAuthSucceededRedirectTo =
- AuthPropertyOnAuthSucceededRedirectTo <$> P.waspPropertyStringLiteral "onAuthSucceededRedirectTo"
-
-authPropertyUserEntity :: Parser AuthProperty
-authPropertyUserEntity = AuthPropertyUserEntity <$> P.waspProperty "userEntity" L.identifier
-
-authPropertyMethods :: Parser AuthProperty
-authPropertyMethods = AuthPropertyMethods <$> P.waspProperty "methods" (L.brackets $ L.commaSep1 authMethod)
-
-authMethod :: Parser Wasp.Auth.AuthMethod
-authMethod = L.symbol "EmailAndPassword" *> pure Wasp.Auth.EmailAndPassword
diff --git a/waspc/src/Wasp/Parser/Common.hs b/waspc/src/Wasp/Parser/Common.hs
deleted file mode 100644
index 328b311aa..000000000
--- a/waspc/src/Wasp/Parser/Common.hs
+++ /dev/null
@@ -1,180 +0,0 @@
-{-
- Common functions used among Wasp parsers.
--}
-
-module Wasp.Parser.Common where
-
-import qualified Data.Text as T
-import StrongPath (File, Path, Posix, Rel, System)
-import qualified StrongPath as SP
-import Text.Parsec
- ( ParseError,
- anyChar,
- manyTill,
- parse,
- try,
- unexpected,
- )
-import Text.Parsec.String (Parser)
-import qualified Wasp.Lexer as L
-
--- | Runs given wasp parser on a specified input.
-runWaspParser :: Parser a -> String -> Either ParseError a
-runWaspParser waspParser input = parse waspParser sourceName input
- where
- -- NOTE(matija): this is used by Parsec only when reporting errors, but we currently
- -- don't provide source name (e.g. .wasp file name) to this method so leaving it empty
- -- for now.
- sourceName = ""
-
--- TODO(matija): rename to just "waspElement"?
-
--- | Parses declaration of a wasp element (e.g. App or Page) and the closure content.
-waspElementNameAndClosureContent ::
- -- | Type of the wasp element (e.g. "app" or "page").
- String ->
- -- | Parser to be used for parsing closure content of the wasp element.
- Parser a ->
- -- | Name of the element and parsed closure content.
- Parser (String, a)
-waspElementNameAndClosureContent elementType closureContent =
- waspElementNameAndClosure elementType (waspClosure closureContent)
-
--- | Parses declaration of a wasp element (e.g. App or Page) and the belonging closure.
-waspElementNameAndClosure ::
- -- | Element type
- String ->
- -- | Closure parser (needs to parse braces as well, not just the content)
- Parser a ->
- -- | Name of the element and parsed closure content.
- Parser (String, a)
-waspElementNameAndClosure elementType closure =
- -- NOTE(matija): It is important to have `try` here because we don't want to consume the
- -- content intended for other parsers.
- -- E.g. if we tried to parse "entity-form" this parser would have been tried first for
- -- "entity" and would consume "entity", so entity-form parser would also fail.
- -- This way when entity parser fails, it will backtrack and allow
- -- entity-form parser to succeed.
- --
- -- TODO(matija): should I push this try higher, to the specific case of entity parser
- -- which is causing the trouble?
- -- This way try will be executed in more cases where it is not neccessary, this
- -- might not be the best for the performance and the clarity of error messages.
- -- On the other hand, it is safer?
- try $ do
- L.reserved elementType
- elementName <- L.identifier
- closureContent <- closure
-
- return (elementName, closureContent)
-
--- | Parses declaration of a wasp element linked to an entity.
--- E.g. "entity-form ..." or "action ..."
-waspElementLinkedToEntity ::
- -- | Type of the linked wasp element (e.g. "entity-form").
- String ->
- -- | Parser to be used for parsing body of the wasp element.
- Parser a ->
- -- | Name of the linked entity, element name and body.
- Parser (String, String, a)
-waspElementLinkedToEntity elementType bodyParser = do
- L.reserved elementType
- linkedEntityName <- L.angles L.identifier
- elementName <- L.identifier
- body <- bodyParser
- return (linkedEntityName, elementName, body)
-
--- | Parses wasp property along with the key, "key: value".
-waspProperty :: String -> Parser a -> Parser a
-waspProperty key value = L.symbol key <* L.colon *> value
-
--- | Parses wasp property which has a string literal for a value.
--- e.g.: title: "my first app"
-waspPropertyStringLiteral :: String -> Parser String
-waspPropertyStringLiteral key = waspProperty key L.stringLiteral
-
--- | Parses wasp property which has a bool for a value. E.g.: "onEnter: true".
-waspPropertyBool :: String -> Parser Bool
-waspPropertyBool key = waspProperty key L.bool
-
--- | Parses wasp property which has an identifier as a key (E.g. field name or filter name).
--- E.g. within an entity-form {} we can set properties for a specific field with a closure of
--- form "FIELD_NAME: {...}" -> FIELD_NAME is then an identifier we need.
-waspPropertyWithIdentifierAsKey :: Parser a -> Parser (String, a)
-waspPropertyWithIdentifierAsKey valueP = do
- identifier <- L.identifier <* L.colon
- value <- valueP
-
- return (identifier, value)
-
--- | Parses wasp closure, which is {...}. Returns parsed content within the closure.
-waspClosure :: Parser a -> Parser a
-waspClosure = L.braces
-
--- | Parses wasp property closure where property is an identifier whose value we also
--- need to retrieve.
--- E.g. within an entity-form {} we can set properties for a specific field with a closure of
--- form "FIELD_NAME: {...}" -> FIELD_NAME is then an identifier we need.
-waspIdentifierClosure :: Parser a -> Parser (String, a)
-waspIdentifierClosure = waspPropertyWithIdentifierAsKey . waspClosure
-
--- | Parses wasp property which has a closure for a value. Returns parsed content within the
--- closure.
-waspPropertyClosure :: String -> Parser a -> Parser a
-waspPropertyClosure key closureContent = waspProperty key (waspClosure closureContent)
-
--- | Parses wasp property which has a jsx closure for a value. Returns the content
--- within the closure.
-waspPropertyJsxClosure :: String -> Parser String
-waspPropertyJsxClosure key = waspProperty key waspJsxClosure
-
--- | Parses wasp property which has a css closure for a value. Returns the content
--- within the closure.
-waspPropertyCssClosure :: String -> Parser String
-waspPropertyCssClosure key = waspProperty key waspCssClosure
-
--- | Parses wasp jsx closure, which is {=jsx...jsx=}. Returns content within the closure.
-waspJsxClosure :: Parser String
-waspJsxClosure = waspNamedClosure "jsx"
-
--- | Parses wasp css closure, which is {=css...css=}. Returns content within the closure.
-waspCssClosure :: Parser String
-waspCssClosure = waspNamedClosure "css"
-
--- TODO(martin): write tests and comments.
-
--- | Parses named wasp closure, which is {=name...name=}. Returns content within the closure.
-waspNamedClosure :: String -> Parser String
-waspNamedClosure name = do
- _ <- closureStart
- strip <$> manyTill anyChar (try closureEnd)
- where
- closureStart = L.symbol ("{=" ++ name)
- closureEnd = L.symbol (name ++ "=}")
-
--- | Parses a list of items that can be parsed with given parser.
--- For example, `waspList L.identifier` will parse "[foo, bar, t]" into ["foo", "bar", "t"].
-waspList :: Parser a -> Parser [a]
-waspList elementParser = L.brackets $ L.commaSep elementParser
-
--- | Removes leading and trailing spaces from a string.
-strip :: String -> String
-strip = T.unpack . T.strip . T.pack
-
--- | Parses relative file path, e.g. "my/file.txt".
-relFilePathString :: Parser (Path System (Rel d) (File f))
-relFilePathString = do
- path <- L.stringLiteral
- maybe
- (unexpected $ "string \"" ++ path ++ "\": Expected relative file path.")
- return
- (SP.parseRelFile path)
-
--- | Parses relative posix file path, e.g. "my/file.txt".
-relPosixFilePathString :: Parser (Path Posix (Rel d) (File f))
-relPosixFilePathString = do
- path <- L.stringLiteral
- maybe
- (unexpected $ "string \"" ++ path ++ "\": Expected relative file path.")
- return
- (SP.parseRelFileP path)
diff --git a/waspc/src/Wasp/Parser/Db.hs b/waspc/src/Wasp/Parser/Db.hs
deleted file mode 100644
index 2d2bf179b..000000000
--- a/waspc/src/Wasp/Parser/Db.hs
+++ /dev/null
@@ -1,41 +0,0 @@
-module Wasp.Parser.Db
- ( db,
- )
-where
-
-import Data.Maybe (listToMaybe)
-import Text.Parsec (try, (<|>))
-import Text.Parsec.String (Parser)
-import qualified Wasp.Lexer as L
-import qualified Wasp.Parser.Common as P
-import qualified Wasp.Wasp.Db as Wasp.Db
-
-db :: Parser Wasp.Db.Db
-db = do
- L.reserved L.reservedNameDb
- dbProperties <- P.waspClosure (L.commaSep1 dbProperty)
-
- system <-
- maybe
- (fail "'system' property is required!")
- return
- (listToMaybe [p | DbPropertySystem p <- dbProperties])
-
- return
- Wasp.Db.Db
- { Wasp.Db._system = system
- }
-
-data DbProperty
- = DbPropertySystem Wasp.Db.DbSystem
-
-dbProperty :: Parser DbProperty
-dbProperty =
- dbPropertySystem
-
-dbPropertySystem :: Parser DbProperty
-dbPropertySystem = DbPropertySystem <$> P.waspProperty "system" dbPropertySystemValue
- where
- dbPropertySystemValue =
- try (L.symbol "PostgreSQL" >> return Wasp.Db.PostgreSQL)
- <|> try (L.symbol "SQLite" >> return Wasp.Db.SQLite)
diff --git a/waspc/src/Wasp/Parser/Entity.hs b/waspc/src/Wasp/Parser/Entity.hs
deleted file mode 100644
index e46ef2e3b..000000000
--- a/waspc/src/Wasp/Parser/Entity.hs
+++ /dev/null
@@ -1,66 +0,0 @@
-module Wasp.Parser.Entity
- ( entity,
- )
-where
-
-import Text.Parsec.String (Parser)
-import qualified Wasp.Lexer as L
-import qualified Wasp.Psl.Ast.Model as PslModel
-import qualified Wasp.Psl.Parser.Model
-import qualified Wasp.Wasp.Entity as Entity
-
-entity :: Parser Entity.Entity
-entity = do
- _ <- L.reserved L.reservedNameEntity
- name <- L.identifier
- _ <- L.symbol "{=psl"
- pslModelBody <- Wasp.Psl.Parser.Model.body
- _ <- L.symbol "psl=}"
-
- return
- Entity.Entity
- { Entity._name = name,
- Entity._fields = getEntityFields pslModelBody,
- Entity._pslModelBody = pslModelBody
- }
-
-getEntityFields :: PslModel.Body -> [Entity.Field]
-getEntityFields (PslModel.Body pslElements) = map pslFieldToEntityField pslFields
- where
- pslFields = [field | (PslModel.ElementField field) <- pslElements]
-
- pslFieldToEntityField :: PslModel.Field -> Entity.Field
- pslFieldToEntityField pslField =
- Entity.Field
- { Entity._fieldName = PslModel._name pslField,
- Entity._fieldType =
- pslFieldTypeToEntityFieldType
- (PslModel._type pslField)
- (PslModel._typeModifiers pslField)
- }
-
- pslFieldTypeToEntityFieldType ::
- PslModel.FieldType ->
- [PslModel.FieldTypeModifier] ->
- Entity.FieldType
- pslFieldTypeToEntityFieldType fType fTypeModifiers =
- let scalar = pslFieldTypeToScalar fType
- in case fTypeModifiers of
- [] -> Entity.FieldTypeScalar scalar
- [PslModel.List] -> Entity.FieldTypeComposite $ Entity.List scalar
- [PslModel.Optional] -> Entity.FieldTypeComposite $ Entity.Optional scalar
- _ -> error "Not a valid list of modifiers."
-
- pslFieldTypeToScalar :: PslModel.FieldType -> Entity.Scalar
- pslFieldTypeToScalar fType = case fType of
- PslModel.String -> Entity.String
- PslModel.Boolean -> Entity.Boolean
- PslModel.Int -> Entity.Int
- PslModel.BigInt -> Entity.BigInt
- PslModel.Float -> Entity.Float
- PslModel.Decimal -> Entity.Decimal
- PslModel.DateTime -> Entity.DateTime
- PslModel.Json -> Entity.Json
- PslModel.Bytes -> Entity.Bytes
- PslModel.UserType typeName -> Entity.UserType typeName
- PslModel.Unsupported typeName -> Entity.Unsupported typeName
diff --git a/waspc/src/Wasp/Parser/ExternalCode.hs b/waspc/src/Wasp/Parser/ExternalCode.hs
deleted file mode 100644
index ddba55015..000000000
--- a/waspc/src/Wasp/Parser/ExternalCode.hs
+++ /dev/null
@@ -1,24 +0,0 @@
-module Wasp.Parser.ExternalCode
- ( extCodeFilePathString,
- )
-where
-
-import qualified Path.Posix as PPosix
-import StrongPath (File', Path, Posix, Rel)
-import qualified StrongPath.Path as SP.Path
-import Text.Parsec (unexpected)
-import Text.Parsec.String (Parser)
-import Wasp.AppSpec.ExternalCode (SourceExternalCodeDir)
-import qualified Wasp.Parser.Common
-
--- Parses string literal that is file path to file in source external code dir.
--- Returns file path relative to the external code dir.
--- Example of input: "@ext/some/file.txt". Output would be: "some/file.txt".
-extCodeFilePathString :: Parser (Path Posix (Rel SourceExternalCodeDir) File')
-extCodeFilePathString = do
- path <- Wasp.Parser.Common.relPosixFilePathString
- maybe
- (unexpected $ "string \"" ++ show path ++ "\": External code file path should start with \"@ext/\".")
- return
- -- TODO: Once StrongPath supports stripProperPrefix method, use that instead of transforming it to Path and back.
- (SP.Path.fromPathRelFileP <$> PPosix.stripProperPrefix [PPosix.reldir|@ext|] (SP.Path.toPathRelFileP path))
diff --git a/waspc/src/Wasp/Parser/JsCode.hs b/waspc/src/Wasp/Parser/JsCode.hs
deleted file mode 100644
index d18defa5e..000000000
--- a/waspc/src/Wasp/Parser/JsCode.hs
+++ /dev/null
@@ -1,12 +0,0 @@
-module Wasp.Parser.JsCode
- ( jsCode,
- )
-where
-
-import qualified Data.Text as Text
-import Text.Parsec.String (Parser)
-import qualified Wasp.Parser.Common as P
-import qualified Wasp.Wasp.JsCode as WJS
-
-jsCode :: Parser WJS.JsCode
-jsCode = WJS.JsCode . Text.pack <$> P.waspNamedClosure "js"
diff --git a/waspc/src/Wasp/Parser/JsImport.hs b/waspc/src/Wasp/Parser/JsImport.hs
deleted file mode 100644
index 56535aaf5..000000000
--- a/waspc/src/Wasp/Parser/JsImport.hs
+++ /dev/null
@@ -1,32 +0,0 @@
-module Wasp.Parser.JsImport
- ( jsImport,
- )
-where
-
-import Text.Parsec ((<|>))
-import Text.Parsec.String (Parser)
-import qualified Wasp.Lexer as L
-import qualified Wasp.Parser.ExternalCode
-import qualified Wasp.Wasp.JsImport as Wasp.JsImport
-
--- | Parses subset of JS import statement (only default or single named import, and only external code files):
--- import from "@ext/..."
--- import { } from "@ext/..."
-jsImport :: Parser Wasp.JsImport.JsImport
-jsImport = do
- L.whiteSpace
- _ <- L.reserved L.reservedNameImport
- -- For now we support only default import or one named import.
- (defaultImport, namedImports) <-
- ((\i -> (Just i, [])) <$> L.identifier)
- <|> ((\i -> (Nothing, [i])) <$> L.braces L.identifier)
- _ <- L.reserved L.reservedNameFrom
- -- TODO: For now we only support double quotes here, we should also support single quotes.
- -- We would need to write this from scratch, with single quote escaping enabled.
- from <- Wasp.Parser.ExternalCode.extCodeFilePathString
- return
- Wasp.JsImport.JsImport
- { Wasp.JsImport._defaultImport = defaultImport,
- Wasp.JsImport._namedImports = namedImports,
- Wasp.JsImport._from = from
- }
diff --git a/waspc/src/Wasp/Parser/NpmDependencies.hs b/waspc/src/Wasp/Parser/NpmDependencies.hs
deleted file mode 100644
index 47f88af6a..000000000
--- a/waspc/src/Wasp/Parser/NpmDependencies.hs
+++ /dev/null
@@ -1,31 +0,0 @@
-module Wasp.Parser.NpmDependencies
- ( npmDependencies,
- )
-where
-
-import qualified Data.Aeson as Aeson
-import qualified Data.ByteString.Lazy.UTF8 as BLU
-import qualified Data.HashMap.Strict as M
-import Text.Parsec (try)
-import Text.Parsec.String (Parser)
-import qualified Wasp.Lexer as L
-import qualified Wasp.NpmDependency as ND
-import qualified Wasp.Parser.Common as P
-import Wasp.Wasp.NpmDependencies (NpmDependencies)
-import qualified Wasp.Wasp.NpmDependencies as NpmDependencies
-
-npmDependencies :: Parser NpmDependencies
-npmDependencies = try $ do
- L.reserved L.reservedNameDependencies
- closureContent <- P.waspNamedClosure "json"
- let jsonBytestring = BLU.fromString $ "{ " ++ closureContent ++ " }"
- npmDeps <- case Aeson.eitherDecode' jsonBytestring :: Either String (M.HashMap String String) of
- Left errorMessage -> fail $ "Failed to parse dependencies JSON: " ++ errorMessage
- Right rawDeps -> return $ map rawDepToNpmDep (M.toList rawDeps)
- return
- NpmDependencies.NpmDependencies
- { NpmDependencies._dependencies = npmDeps
- }
- where
- rawDepToNpmDep :: (String, String) -> ND.NpmDependency
- rawDepToNpmDep (name, version) = ND.NpmDependency {ND._name = name, ND._version = version}
diff --git a/waspc/src/Wasp/Parser/Operation.hs b/waspc/src/Wasp/Parser/Operation.hs
deleted file mode 100644
index fe2979d69..000000000
--- a/waspc/src/Wasp/Parser/Operation.hs
+++ /dev/null
@@ -1,50 +0,0 @@
-module Wasp.Parser.Operation
- ( jsFunctionPropParser,
- entitiesPropParser,
- getJsFunctionFromProps,
- getEntitiesFromProps,
- getAuthEnabledFromProps,
- properties,
- -- FOR TESTS:
- Property (..),
- )
-where
-
-import Data.Maybe (listToMaybe)
-import Text.Parsec ((<|>))
-import Text.Parsec.String (Parser)
-import qualified Wasp.Lexer as L
-import qualified Wasp.Parser.Common as C
-import qualified Wasp.Parser.JsImport
-import qualified Wasp.Wasp.JsImport as Wasp.JsImport
-
-data Property
- = JsFunction !Wasp.JsImport.JsImport
- | Entities ![String]
- | AuthEnabled !Bool
- deriving (Show, Eq)
-
-properties :: Parser [Property]
-properties =
- L.commaSep1 $
- jsFunctionPropParser
- <|> entitiesPropParser
- <|> authEnabledPropParser
-
-jsFunctionPropParser :: Parser Property
-jsFunctionPropParser = JsFunction <$> C.waspProperty "fn" Wasp.Parser.JsImport.jsImport
-
-getJsFunctionFromProps :: [Property] -> Maybe Wasp.JsImport.JsImport
-getJsFunctionFromProps ps = listToMaybe [f | JsFunction f <- ps]
-
-entitiesPropParser :: Parser Property
-entitiesPropParser = Entities <$> C.waspProperty "entities" (C.waspList L.identifier)
-
-getEntitiesFromProps :: [Property] -> Maybe [String]
-getEntitiesFromProps ps = listToMaybe [es | Entities es <- ps]
-
-authEnabledPropParser :: Parser Property
-authEnabledPropParser = AuthEnabled <$> C.waspProperty "auth" L.bool
-
-getAuthEnabledFromProps :: [Property] -> Maybe Bool
-getAuthEnabledFromProps ps = listToMaybe [aE | AuthEnabled aE <- ps]
diff --git a/waspc/src/Wasp/Parser/Page.hs b/waspc/src/Wasp/Parser/Page.hs
deleted file mode 100644
index 2432c2735..000000000
--- a/waspc/src/Wasp/Parser/Page.hs
+++ /dev/null
@@ -1,55 +0,0 @@
-module Wasp.Parser.Page
- ( page,
- )
-where
-
-import Data.Maybe (fromMaybe, listToMaybe)
-import Text.Parsec
-import Text.Parsec.String (Parser)
-import Wasp.Lexer
-import Wasp.Parser.Common
-import qualified Wasp.Parser.JsImport
-import Wasp.Wasp.JsImport (JsImport)
-import qualified Wasp.Wasp.Page as Page
-
-data PageProperty
- = Title !String
- | Component !JsImport
- | AuthRequired !Bool
- deriving (Show, Eq)
-
--- | Parses Page properties, separated by a comma.
-pageProperties :: Parser [PageProperty]
-pageProperties =
- commaSep1 $
- pagePropertyTitle
- <|> pagePropertyComponent
- <|> pagePropertyAuthRequired
-
--- NOTE(matija): this is currently unused?
-pagePropertyTitle :: Parser PageProperty
-pagePropertyTitle = Title <$> waspPropertyStringLiteral "title"
-
-pagePropertyComponent :: Parser PageProperty
-pagePropertyComponent = Component <$> waspProperty "component" Wasp.Parser.JsImport.jsImport
-
-pagePropertyAuthRequired :: Parser PageProperty
-pagePropertyAuthRequired = AuthRequired <$> waspPropertyBool "authRequired"
-
-getPageAuthRequired :: [PageProperty] -> Maybe Bool
-getPageAuthRequired ps = listToMaybe [a | AuthRequired a <- ps]
-
-getPageComponent :: [PageProperty] -> Maybe JsImport
-getPageComponent ps = listToMaybe [c | Component c <- ps]
-
--- | Top level parser, parses Page.
-page :: Parser Page.Page
-page = do
- (pageName, pageProps) <- waspElementNameAndClosureContent reservedNamePage pageProperties
-
- return
- Page.Page
- { Page._name = pageName,
- Page._component = fromMaybe (error "Page component is missing.") (getPageComponent pageProps),
- Page._authRequired = getPageAuthRequired pageProps
- }
diff --git a/waspc/src/Wasp/Parser/Query.hs b/waspc/src/Wasp/Parser/Query.hs
deleted file mode 100644
index 952422abd..000000000
--- a/waspc/src/Wasp/Parser/Query.hs
+++ /dev/null
@@ -1,24 +0,0 @@
-module Wasp.Parser.Query
- ( query,
- )
-where
-
-import Data.Maybe (fromMaybe)
-import Text.Parsec.String (Parser)
-import qualified Wasp.Lexer as L
-import qualified Wasp.Parser.Common as C
-import qualified Wasp.Parser.Operation as Operation
-import Wasp.Wasp.Query (Query)
-import qualified Wasp.Wasp.Query as Query
-
-query :: Parser Query
-query = do
- (name, props) <- C.waspElementNameAndClosureContent L.reservedNameQuery Operation.properties
- return
- Query.Query
- { Query._name = name,
- Query._jsFunction =
- fromMaybe (error "Query js function is missing.") (Operation.getJsFunctionFromProps props),
- Query._entities = Operation.getEntitiesFromProps props,
- Query._auth = Operation.getAuthEnabledFromProps props
- }
diff --git a/waspc/src/Wasp/Parser/Route.hs b/waspc/src/Wasp/Parser/Route.hs
deleted file mode 100644
index a495af94a..000000000
--- a/waspc/src/Wasp/Parser/Route.hs
+++ /dev/null
@@ -1,26 +0,0 @@
-module Wasp.Parser.Route
- ( route,
- )
-where
-
-import Text.Parsec.String (Parser)
-import qualified Wasp.Lexer as L
-import qualified Wasp.Wasp.Route as Route
-
--- | Top level parser, parses route Wasp element.
-route :: Parser Route.Route
-route = do
- -- route "some/url/path"
- L.reserved L.reservedNameRoute
- urlPath <- L.stringLiteral
-
- -- -> page somePage
- L.reserved "->"
- L.reserved L.reservedNamePage
- targetPage <- L.identifier
-
- return
- Route.Route
- { Route._urlPath = urlPath,
- Route._targetPage = targetPage
- }
diff --git a/waspc/src/Wasp/Parser/Server.hs b/waspc/src/Wasp/Parser/Server.hs
deleted file mode 100644
index eb96429b3..000000000
--- a/waspc/src/Wasp/Parser/Server.hs
+++ /dev/null
@@ -1,38 +0,0 @@
-module Wasp.Parser.Server
- ( server,
- )
-where
-
-import Data.Maybe (fromMaybe, listToMaybe)
-import Text.Parsec.String (Parser)
-import qualified Wasp.Lexer as L
-import qualified Wasp.Parser.Common as C
-import qualified Wasp.Parser.JsImport
-import qualified Wasp.Wasp.JsImport as Wasp.JsImport
-import Wasp.Wasp.Server (Server)
-import qualified Wasp.Wasp.Server as Server
-
-server :: Parser Server
-server = do
- L.reserved L.reservedNameServer
- props <- C.waspClosure properties
-
- return
- Server.Server
- { Server._setupJsFunction =
- fromMaybe (error "Server js function is missing.") (getSetupJsFunctionFromProps props)
- }
-
-data Property
- = SetupJsFunction !Wasp.JsImport.JsImport
- deriving (Show, Eq)
-
-properties :: Parser [Property]
-properties =
- L.commaSep1 setupJsFunctionPropParser
-
-setupJsFunctionPropParser :: Parser Property
-setupJsFunctionPropParser = SetupJsFunction <$> C.waspProperty "setupFn" Wasp.Parser.JsImport.jsImport
-
-getSetupJsFunctionFromProps :: [Property] -> Maybe Wasp.JsImport.JsImport
-getSetupJsFunctionFromProps ps = listToMaybe [f | SetupJsFunction f <- ps]
diff --git a/waspc/src/Wasp/Parser/Style.hs b/waspc/src/Wasp/Parser/Style.hs
deleted file mode 100644
index 5511c8ada..000000000
--- a/waspc/src/Wasp/Parser/Style.hs
+++ /dev/null
@@ -1,20 +0,0 @@
-module Wasp.Parser.Style
- ( style,
- )
-where
-
-import qualified Data.Text as Text
-import Text.Parsec ((<|>))
-import Text.Parsec.String (Parser)
-import qualified Wasp.Parser.Common
-import qualified Wasp.Parser.ExternalCode
-import qualified Wasp.Wasp.Style as Wasp.Style
-
-style :: Parser Wasp.Style.Style
-style = cssFile <|> cssCode
-
-cssFile :: Parser Wasp.Style.Style
-cssFile = Wasp.Style.ExtCodeCssFile <$> Wasp.Parser.ExternalCode.extCodeFilePathString
-
-cssCode :: Parser Wasp.Style.Style
-cssCode = Wasp.Style.CssCode . Text.pack <$> Wasp.Parser.Common.waspNamedClosure "css"
diff --git a/waspc/src/Wasp/Util.hs b/waspc/src/Wasp/Util.hs
index 659c002e7..b98ea0be6 100644
--- a/waspc/src/Wasp/Util.hs
+++ b/waspc/src/Wasp/Util.hs
@@ -8,6 +8,8 @@ module Wasp.Util
indent,
concatShortPrefixAndText,
concatPrefixAndText,
+ insertAt,
+ leftPad,
)
where
@@ -102,3 +104,17 @@ concatShortPrefixAndText prefix text =
concatPrefixAndText :: String -> String -> String
concatPrefixAndText prefix text =
if length (lines text) <= 1 then prefix ++ text else prefix ++ "\n" ++ indent 2 text
+
+-- | Adds given element to the start of the given list until the list is of specified length.
+-- leftPad ' ' 4 "hi" == " hi"
+-- leftPad ' ' 4 "hihihi" == "hihihi"
+leftPad :: a -> Int -> [a] -> [a]
+leftPad padElem n list = replicate (max 0 (n - length list)) padElem ++ list
+
+-- | Inserts a given @theInsert@ list into the given @host@ list so that @theInsert@
+-- starts at index @idx@ in the @host@.
+-- Example: @insertAt "hi" 2 "hoho" == "hohiho"@
+insertAt :: [a] -> Int -> [a] -> [a]
+insertAt theInsert idx host =
+ let (before, after) = splitAt idx host
+ in before ++ theInsert ++ after
diff --git a/waspc/src/Wasp/Util/Terminal.hs b/waspc/src/Wasp/Util/Terminal.hs
index d78f98d52..8373d522d 100644
--- a/waspc/src/Wasp/Util/Terminal.hs
+++ b/waspc/src/Wasp/Util/Terminal.hs
@@ -1,6 +1,9 @@
module Wasp.Util.Terminal
( Style (..),
applyStyles,
+ styleCode,
+ escapeCode,
+ resetCode,
)
where
diff --git a/waspc/src/Wasp/Wasp.hs b/waspc/src/Wasp/Wasp.hs
deleted file mode 100644
index b13a5f56f..000000000
--- a/waspc/src/Wasp/Wasp.hs
+++ /dev/null
@@ -1,259 +0,0 @@
-module Wasp.Wasp
- ( Wasp,
- WaspElement (..),
- fromWaspElems,
- module Wasp.Wasp.JsImport,
- getJsImports,
- setJsImports,
- module Wasp.Wasp.App,
- fromApp,
- getApp,
- setApp,
- getAuth,
- getServer,
- getPSLEntities,
- getDb,
- module Wasp.Wasp.Page,
- getPages,
- addPage,
- getRoutes,
- getQueries,
- addQuery,
- getQueryByName,
- getActions,
- addAction,
- getActionByName,
- setExternalCodeFiles,
- getExternalCodeFiles,
- setDotEnvFile,
- getDotEnvFile,
- setMigrationsDir,
- getMigrationsDir,
- setIsBuild,
- getIsBuild,
- setNpmDependencies,
- getNpmDependencies,
- )
-where
-
-import Data.Aeson (ToJSON (..), object, (.=))
-import StrongPath (Abs, Dir, File', Path')
-import qualified Wasp.AppSpec.ExternalCode as ExternalCode
-import Wasp.Common (DbMigrationsDir)
-import qualified Wasp.Util as U
-import qualified Wasp.Wasp.Action as Wasp.Action
-import Wasp.Wasp.App
-import qualified Wasp.Wasp.Auth as Wasp.Auth
-import qualified Wasp.Wasp.Db as Wasp.Db
-import Wasp.Wasp.Entity
-import Wasp.Wasp.JsImport
-import Wasp.Wasp.NpmDependencies (NpmDependencies)
-import qualified Wasp.Wasp.NpmDependencies as Wasp.NpmDependencies
-import Wasp.Wasp.Page
-import qualified Wasp.Wasp.Query as Wasp.Query
-import Wasp.Wasp.Route
-import qualified Wasp.Wasp.Server as Wasp.Server
-
--- * Wasp
-
-data Wasp = Wasp
- { waspElements :: [WaspElement],
- waspJsImports :: [JsImport],
- externalCodeFiles :: [ExternalCode.File],
- dotEnvFile :: Maybe (Path' Abs File'),
- migrationsDir :: Maybe (Path' Abs (Dir DbMigrationsDir)),
- isBuild :: Bool
- }
- deriving (Show, Eq)
-
-data WaspElement
- = WaspElementApp !App
- | WaspElementAuth !Wasp.Auth.Auth
- | WaspElementDb !Wasp.Db.Db
- | WaspElementPage !Page
- | WaspElementNpmDependencies !NpmDependencies
- | WaspElementRoute !Route
- | WaspElementEntity !Wasp.Wasp.Entity.Entity
- | WaspElementQuery !Wasp.Query.Query
- | WaspElementAction !Wasp.Action.Action
- | WaspElementServer !Wasp.Server.Server
- deriving (Show, Eq)
-
-fromWaspElems :: [WaspElement] -> Wasp
-fromWaspElems elems =
- Wasp
- { waspElements = elems,
- waspJsImports = [],
- externalCodeFiles = [],
- dotEnvFile = Nothing,
- migrationsDir = Nothing,
- isBuild = False
- }
-
--- * Build
-
-getIsBuild :: Wasp -> Bool
-getIsBuild = isBuild
-
-setIsBuild :: Wasp -> Bool -> Wasp
-setIsBuild wasp isBuildNew = wasp {isBuild = isBuildNew}
-
--- * External code files
-
-getExternalCodeFiles :: Wasp -> [ExternalCode.File]
-getExternalCodeFiles = externalCodeFiles
-
-setExternalCodeFiles :: Wasp -> [ExternalCode.File] -> Wasp
-setExternalCodeFiles wasp files = wasp {externalCodeFiles = files}
-
--- * Dot env files
-
-getDotEnvFile :: Wasp -> Maybe (Path' Abs File')
-getDotEnvFile = dotEnvFile
-
-setDotEnvFile :: Wasp -> Maybe (Path' Abs File') -> Wasp
-setDotEnvFile wasp file = wasp {dotEnvFile = file}
-
--- * Migrations dir
-
-getMigrationsDir :: Wasp -> Maybe (Path' Abs (Dir DbMigrationsDir))
-getMigrationsDir = migrationsDir
-
-setMigrationsDir :: Wasp -> Maybe (Path' Abs (Dir DbMigrationsDir)) -> Wasp
-setMigrationsDir wasp dir = wasp {migrationsDir = dir}
-
--- * Js imports
-
-getJsImports :: Wasp -> [JsImport]
-getJsImports = waspJsImports
-
-setJsImports :: Wasp -> [JsImport] -> Wasp
-setJsImports wasp jsImports = wasp {waspJsImports = jsImports}
-
--- * App
-
-getApp :: Wasp -> App
-getApp wasp =
- let apps = getApps wasp
- in if length apps /= 1
- then error "Wasp has to contain exactly one WaspElementApp element!"
- else head apps
-
-isAppElem :: WaspElement -> Bool
-isAppElem WaspElementApp {} = True
-isAppElem _ = False
-
-getApps :: Wasp -> [App]
-getApps wasp = [app | (WaspElementApp app) <- waspElements wasp]
-
-setApp :: Wasp -> App -> Wasp
-setApp wasp app = wasp {waspElements = WaspElementApp app : filter (not . isAppElem) (waspElements wasp)}
-
-fromApp :: App -> Wasp
-fromApp app = fromWaspElems [WaspElementApp app]
-
--- * Auth
-
-getAuth :: Wasp -> Maybe Wasp.Auth.Auth
-getAuth wasp =
- let auths = [a | WaspElementAuth a <- waspElements wasp]
- in case auths of
- [] -> Nothing
- [a] -> Just a
- _ -> error "Wasp can't contain more than one WaspElementAuth element!"
-
--- * Db
-
-getDb :: Wasp -> Maybe Wasp.Db.Db
-getDb wasp =
- let dbs = [db | WaspElementDb db <- waspElements wasp]
- in case dbs of
- [] -> Nothing
- [db] -> Just db
- _ -> error "Wasp can't contain more than one Db element!"
-
--- * NpmDependencies
-
-getNpmDependencies :: Wasp -> NpmDependencies
-getNpmDependencies wasp =
- let depses = [d | (WaspElementNpmDependencies d) <- waspElements wasp]
- in case depses of
- [] -> Wasp.NpmDependencies.empty
- [deps] -> deps
- _ -> error "Wasp can't contain more than one NpmDependencies element!"
-
-isNpmDependenciesElem :: WaspElement -> Bool
-isNpmDependenciesElem WaspElementNpmDependencies {} = True
-isNpmDependenciesElem _ = False
-
-setNpmDependencies :: Wasp -> NpmDependencies -> Wasp
-setNpmDependencies wasp deps =
- wasp
- { waspElements = WaspElementNpmDependencies deps : filter (not . isNpmDependenciesElem) (waspElements wasp)
- }
-
--- * Routes
-
-getRoutes :: Wasp -> [Route]
-getRoutes wasp = [route | (WaspElementRoute route) <- waspElements wasp]
-
--- * Pages
-
-getPages :: Wasp -> [Page]
-getPages wasp = [page | (WaspElementPage page) <- waspElements wasp]
-
-addPage :: Wasp -> Page -> Wasp
-addPage wasp page = wasp {waspElements = WaspElementPage page : waspElements wasp}
-
--- * Query
-
-getQueries :: Wasp -> [Wasp.Query.Query]
-getQueries wasp = [query | (WaspElementQuery query) <- waspElements wasp]
-
-addQuery :: Wasp -> Wasp.Query.Query -> Wasp
-addQuery wasp query = wasp {waspElements = WaspElementQuery query : waspElements wasp}
-
--- | Gets query with a specified name from wasp, if such an action exists.
--- We assume here that there are no two queries with same name.
-getQueryByName :: Wasp -> String -> Maybe Wasp.Query.Query
-getQueryByName wasp name = U.headSafe $ filter (\a -> Wasp.Query._name a == name) (getQueries wasp)
-
--- * Action
-
-getActions :: Wasp -> [Wasp.Action.Action]
-getActions wasp = [action | (WaspElementAction action) <- waspElements wasp]
-
-addAction :: Wasp -> Wasp.Action.Action -> Wasp
-addAction wasp action = wasp {waspElements = WaspElementAction action : waspElements wasp}
-
--- | Gets action with a specified name from wasp, if such an action exists.
--- We assume here that there are no two actions with same name.
-getActionByName :: Wasp -> String -> Maybe Wasp.Action.Action
-getActionByName wasp name = U.headSafe $ filter (\a -> Wasp.Action._name a == name) (getActions wasp)
-
--- * Entities
-
-getPSLEntities :: Wasp -> [Wasp.Wasp.Entity.Entity]
-getPSLEntities wasp = [entity | (WaspElementEntity entity) <- waspElements wasp]
-
--- * Get server
-
-getServer :: Wasp -> Maybe Wasp.Server.Server
-getServer wasp =
- let servers = [s | WaspElementServer s <- waspElements wasp]
- in case servers of
- [] -> Nothing
- [s] -> Just s
- _ -> error "Wasp can't contain more than one WaspElementAuth element!"
-
--- * ToJSON instances.
-
-instance ToJSON Wasp where
- toJSON wasp =
- object
- [ "app" .= getApp wasp,
- "pages" .= getPages wasp,
- "routes" .= getRoutes wasp,
- "jsImports" .= getJsImports wasp,
- "server" .= getServer wasp
- ]
diff --git a/waspc/src/Wasp/Wasp/Action.hs b/waspc/src/Wasp/Wasp/Action.hs
deleted file mode 100644
index 5616e49ad..000000000
--- a/waspc/src/Wasp/Wasp/Action.hs
+++ /dev/null
@@ -1,26 +0,0 @@
-module Wasp.Wasp.Action
- ( Action (..),
- )
-where
-
-import Data.Aeson (ToJSON (..), object, (.=))
-import Wasp.Wasp.JsImport (JsImport)
-
--- TODO: Very similar to Wasp.Query, consider extracting duplication.
-
-data Action = Action
- { _name :: !String,
- _jsFunction :: !JsImport,
- _entities :: !(Maybe [String]),
- _auth :: !(Maybe Bool)
- }
- deriving (Show, Eq)
-
-instance ToJSON Action where
- toJSON action =
- object
- [ "name" .= _name action,
- "jsFunction" .= _jsFunction action,
- "entities" .= _entities action,
- "auth" .= _auth action
- ]
diff --git a/waspc/src/Wasp/Wasp/App.hs b/waspc/src/Wasp/Wasp/App.hs
deleted file mode 100644
index 6e16fcd7b..000000000
--- a/waspc/src/Wasp/Wasp/App.hs
+++ /dev/null
@@ -1,20 +0,0 @@
-module Wasp.Wasp.App
- ( App (..),
- )
-where
-
-import Data.Aeson (ToJSON (..), object, (.=))
-
-data App = App
- { appName :: !String, -- Identifier
- appTitle :: !String,
- appHead :: !(Maybe [String])
- }
- deriving (Show, Eq)
-
-instance ToJSON App where
- toJSON app =
- object
- [ "name" .= appName app,
- "title" .= appTitle app
- ]
diff --git a/waspc/src/Wasp/Wasp/Auth.hs b/waspc/src/Wasp/Wasp/Auth.hs
deleted file mode 100644
index ffdbd964c..000000000
--- a/waspc/src/Wasp/Wasp/Auth.hs
+++ /dev/null
@@ -1,17 +0,0 @@
-module Wasp.Wasp.Auth
- ( Auth (..),
- AuthMethod (..),
- )
-where
-
-data Auth = Auth
- { _userEntity :: !String,
- _methods :: [AuthMethod],
- _onAuthFailedRedirectTo :: !String,
- _onAuthSucceededRedirectTo :: !String
- }
- deriving (Show, Eq)
-
-data AuthMethod
- = EmailAndPassword
- deriving (Show, Eq)
diff --git a/waspc/src/Wasp/Wasp/Db.hs b/waspc/src/Wasp/Wasp/Db.hs
deleted file mode 100644
index dbf67f18a..000000000
--- a/waspc/src/Wasp/Wasp/Db.hs
+++ /dev/null
@@ -1,15 +0,0 @@
-module Wasp.Wasp.Db
- ( Db (..),
- DbSystem (..),
- )
-where
-
-data Db = Db
- { _system :: !DbSystem
- }
- deriving (Show, Eq)
-
-data DbSystem
- = PostgreSQL
- | SQLite
- deriving (Show, Eq)
diff --git a/waspc/src/Wasp/Wasp/Entity.hs b/waspc/src/Wasp/Wasp/Entity.hs
deleted file mode 100644
index 4d12694f7..000000000
--- a/waspc/src/Wasp/Wasp/Entity.hs
+++ /dev/null
@@ -1,55 +0,0 @@
-module Wasp.Wasp.Entity
- ( Entity (..),
- Field (..),
- FieldType (..),
- Scalar (..),
- Composite (..),
- )
-where
-
-import Data.Aeson (ToJSON (..), object, (.=))
-import qualified Wasp.Psl.Ast.Model
-
-data Entity = Entity
- { _name :: !String,
- _fields :: ![Field],
- _pslModelBody :: !Wasp.Psl.Ast.Model.Body
- }
- deriving (Show, Eq)
-
-data Field = Field
- { _fieldName :: !String,
- _fieldType :: !FieldType
- }
- deriving (Show, Eq)
-
-data FieldType = FieldTypeScalar Scalar | FieldTypeComposite Composite
- deriving (Show, Eq)
-
-data Composite = Optional Scalar | List Scalar
- deriving (Show, Eq)
-
-data Scalar
- = String
- | Boolean
- | Int
- | BigInt
- | Float
- | Decimal
- | DateTime
- | Json
- | Bytes
- | -- | Name of the user-defined type.
- -- This could be another entity, or maybe an enum,
- -- we don't know here yet.
- UserType String
- | Unsupported String
- deriving (Show, Eq)
-
-instance ToJSON Entity where
- toJSON entity =
- object
- [ "name" .= _name entity,
- "fields" .= show (_fields entity),
- "pslModelBody" .= show (_pslModelBody entity)
- ]
diff --git a/waspc/src/Wasp/Wasp/JsCode.hs b/waspc/src/Wasp/Wasp/JsCode.hs
deleted file mode 100644
index 77ef3e33b..000000000
--- a/waspc/src/Wasp/Wasp/JsCode.hs
+++ /dev/null
@@ -1,15 +0,0 @@
-module Wasp.Wasp.JsCode
- ( JsCode (..),
- )
-where
-
-import Data.Aeson (ToJSON (..))
-import Data.Text (Text)
-
-data JsCode = JsCode !Text deriving (Show, Eq)
-
--- TODO(matija): Currently generator is relying on this implementation, which is not
--- ideal. Ideally all the generation logic would be in the generator. But for now this was
--- the simplest way to implement it.
-instance ToJSON JsCode where
- toJSON (JsCode code) = toJSON code
diff --git a/waspc/src/Wasp/Wasp/JsImport.hs b/waspc/src/Wasp/Wasp/JsImport.hs
deleted file mode 100644
index cd6d31236..000000000
--- a/waspc/src/Wasp/Wasp/JsImport.hs
+++ /dev/null
@@ -1,25 +0,0 @@
-module Wasp.Wasp.JsImport
- ( JsImport (..),
- )
-where
-
-import Data.Aeson (ToJSON (..), object, (.=))
-import StrongPath (File', Path, Posix, Rel)
-import qualified StrongPath as SP
-import Wasp.AppSpec.ExternalCode (SourceExternalCodeDir)
-
--- | Represents javascript import -> "import from ".
-data JsImport = JsImport
- { _defaultImport :: !(Maybe String),
- _namedImports :: ![String],
- _from :: Path Posix (Rel SourceExternalCodeDir) File'
- }
- deriving (Show, Eq)
-
-instance ToJSON JsImport where
- toJSON jsImport =
- object
- [ "defaultImport" .= _defaultImport jsImport,
- "namedImports" .= _namedImports jsImport,
- "from" .= SP.fromRelFileP (_from jsImport)
- ]
diff --git a/waspc/src/Wasp/Wasp/NpmDependencies.hs b/waspc/src/Wasp/Wasp/NpmDependencies.hs
deleted file mode 100644
index ede1de43a..000000000
--- a/waspc/src/Wasp/Wasp/NpmDependencies.hs
+++ /dev/null
@@ -1,22 +0,0 @@
-module Wasp.Wasp.NpmDependencies
- ( NpmDependencies (..),
- empty,
- )
-where
-
-import Data.Aeson (ToJSON (..), object, (.=))
-import Wasp.NpmDependency
-
-data NpmDependencies = NpmDependencies
- { _dependencies :: ![NpmDependency]
- }
- deriving (Show, Eq)
-
-empty :: NpmDependencies
-empty = NpmDependencies {_dependencies = []}
-
-instance ToJSON NpmDependencies where
- toJSON deps =
- object
- [ "dependencies" .= _dependencies deps
- ]
diff --git a/waspc/src/Wasp/Wasp/Operation.hs b/waspc/src/Wasp/Wasp/Operation.hs
deleted file mode 100644
index b491d5bdd..000000000
--- a/waspc/src/Wasp/Wasp/Operation.hs
+++ /dev/null
@@ -1,38 +0,0 @@
-module Wasp.Wasp.Operation
- ( Operation (..),
- getName,
- getJsFn,
- getEntities,
- getAuth,
- )
-where
-
--- TODO: Is this ok approach, should I instead use typeclass?
--- So far, all usages in the codebase could be easily replaced with the Typeclass.
-
-import Wasp.Wasp.Action (Action)
-import qualified Wasp.Wasp.Action as Action
-import Wasp.Wasp.JsImport (JsImport)
-import Wasp.Wasp.Query (Query)
-import qualified Wasp.Wasp.Query as Query
-
-data Operation
- = QueryOp Query
- | ActionOp Action
- deriving (Show)
-
-getName :: Operation -> String
-getName (QueryOp query) = Query._name query
-getName (ActionOp action) = Action._name action
-
-getJsFn :: Operation -> JsImport
-getJsFn (QueryOp query) = Query._jsFunction query
-getJsFn (ActionOp action) = Action._jsFunction action
-
-getEntities :: Operation -> Maybe [String]
-getEntities (QueryOp query) = Query._entities query
-getEntities (ActionOp action) = Action._entities action
-
-getAuth :: Operation -> Maybe Bool
-getAuth (QueryOp query) = Query._auth query
-getAuth (ActionOp action) = Action._auth action
diff --git a/waspc/src/Wasp/Wasp/Page.hs b/waspc/src/Wasp/Wasp/Page.hs
deleted file mode 100644
index 6d557adbb..000000000
--- a/waspc/src/Wasp/Wasp/Page.hs
+++ /dev/null
@@ -1,21 +0,0 @@
-module Wasp.Wasp.Page
- ( Page (..),
- )
-where
-
-import Data.Aeson (ToJSON (..), object, (.=))
-import Wasp.Wasp.JsImport (JsImport)
-
-data Page = Page
- { _name :: !String,
- _component :: !JsImport,
- _authRequired :: Maybe Bool
- }
- deriving (Show, Eq)
-
-instance ToJSON Page where
- toJSON page =
- object
- [ "name" .= _name page,
- "component" .= _component page
- ]
diff --git a/waspc/src/Wasp/Wasp/Query.hs b/waspc/src/Wasp/Wasp/Query.hs
deleted file mode 100644
index b7b185c8e..000000000
--- a/waspc/src/Wasp/Wasp/Query.hs
+++ /dev/null
@@ -1,26 +0,0 @@
-module Wasp.Wasp.Query
- ( Query (..),
- )
-where
-
-import Data.Aeson (ToJSON (..), object, (.=))
-import Wasp.Wasp.JsImport (JsImport)
-
--- TODO: Very similar to Wasp.Action, consider extracting duplication.
-
-data Query = Query
- { _name :: !String,
- _jsFunction :: !JsImport,
- _entities :: !(Maybe [String]),
- _auth :: !(Maybe Bool)
- }
- deriving (Show, Eq)
-
-instance ToJSON Query where
- toJSON query =
- object
- [ "name" .= _name query,
- "jsFunction" .= _jsFunction query,
- "entities" .= _entities query,
- "auth" .= _auth query
- ]
diff --git a/waspc/src/Wasp/Wasp/Route.hs b/waspc/src/Wasp/Wasp/Route.hs
deleted file mode 100644
index bf67c11ce..000000000
--- a/waspc/src/Wasp/Wasp/Route.hs
+++ /dev/null
@@ -1,21 +0,0 @@
-module Wasp.Wasp.Route
- ( Route (..),
- )
-where
-
-import Data.Aeson (ToJSON (..), object, (.=))
-
-data Route = Route
- { _urlPath :: !String,
- -- NOTE(matija): for now page is the only possible target, but in
- -- the future there might be different types of targets (e.g. another route).
- _targetPage :: !String
- }
- deriving (Show, Eq)
-
-instance ToJSON Route where
- toJSON route =
- object
- [ "urlPath" .= _urlPath route,
- "targetPage" .= _targetPage route
- ]
diff --git a/waspc/src/Wasp/Wasp/Server.hs b/waspc/src/Wasp/Wasp/Server.hs
deleted file mode 100644
index fd009a8ba..000000000
--- a/waspc/src/Wasp/Wasp/Server.hs
+++ /dev/null
@@ -1,18 +0,0 @@
-module Wasp.Wasp.Server
- ( Server (..),
- )
-where
-
-import Data.Aeson (ToJSON (..), object, (.=))
-import Wasp.Wasp.JsImport (JsImport)
-
-data Server = Server
- { _setupJsFunction :: !JsImport
- }
- deriving (Show, Eq)
-
-instance ToJSON Server where
- toJSON server =
- object
- [ "setupJsFunction" .= _setupJsFunction server
- ]
diff --git a/waspc/src/Wasp/Wasp/Style.hs b/waspc/src/Wasp/Wasp/Style.hs
deleted file mode 100644
index 6150cc32e..000000000
--- a/waspc/src/Wasp/Wasp/Style.hs
+++ /dev/null
@@ -1,18 +0,0 @@
-module Wasp.Wasp.Style
- ( Style (..),
- )
-where
-
-import Data.Aeson (ToJSON (..))
-import Data.Text (Text)
-import StrongPath (File', Path, Posix, Rel, toFilePath)
-import Wasp.AppSpec.ExternalCode (SourceExternalCodeDir)
-
-data Style
- = ExtCodeCssFile !(Path Posix (Rel SourceExternalCodeDir) File')
- | CssCode !Text
- deriving (Show, Eq)
-
-instance ToJSON Style where
- toJSON (ExtCodeCssFile path) = toJSON $ toFilePath path
- toJSON (CssCode code) = toJSON code
diff --git a/waspc/stack-snapshot.yaml b/waspc/stack-snapshot.yaml
index 95776d683..be9831c4d 100644
--- a/waspc/stack-snapshot.yaml
+++ b/waspc/stack-snapshot.yaml
@@ -1 +1 @@
-resolver: lts-18.15
\ No newline at end of file
+resolver: lts-18.21
diff --git a/waspc/stack.yaml b/waspc/stack.yaml
index c268b1c62..0be48a15a 100644
--- a/waspc/stack.yaml
+++ b/waspc/stack.yaml
@@ -2,8 +2,8 @@ resolver: ./stack-snapshot.yaml
packages:
- .
extra-deps:
- - strong-path-1.1.0.0
- - path-0.9.0
+ - strong-path-1.1.2.0
+ - path-0.9.2
- path-io-1.6.3
# (Martin): I added this per instructions from haskell-language-server, in order to
# enable type information and documentation on hover for dependencies.
diff --git a/waspc/test/Analyzer/EvaluatorTest.hs b/waspc/test/Analyzer/EvaluatorTest.hs
index bf1811622..5248163d5 100644
--- a/waspc/test/Analyzer/EvaluatorTest.hs
+++ b/waspc/test/Analyzer/EvaluatorTest.hs
@@ -8,6 +8,8 @@ module Analyzer.EvaluatorTest where
import Data.Data (Data)
import Data.List.Split (splitOn)
+import Data.Maybe (fromJust)
+import qualified StrongPath as SP
import Test.Tasty.Hspec
import Text.Read (readMaybe)
import Wasp.Analyzer.Evaluator
@@ -182,7 +184,7 @@ spec_Evaluator = do
let typeDefs = TD.addDeclType @Special $ TD.empty
let source =
[ "special Test {",
- " imps: [import { field } from \"main.js\", import main from \"main.js\"],",
+ " imps: [import { field } from \"@ext/main.js\", import main from \"@ext/main.js\"],",
" json: {=json \"key\": 1 json=}",
"}"
]
@@ -191,8 +193,8 @@ spec_Evaluator = do
`shouldBe` Right
[ ( "Test",
Special
- [ ExtImport (ExtImportField "field") "main.js",
- ExtImport (ExtImportModule "main") "main.js"
+ [ ExtImport (ExtImportField "field") (fromJust $ SP.parseRelFileP "main.js"),
+ ExtImport (ExtImportModule "main") (fromJust $ SP.parseRelFileP "main.js")
]
(JSON " \"key\": 1 ")
)
diff --git a/waspc/test/Analyzer/ParserTest.hs b/waspc/test/Analyzer/ParserTest.hs
index f5a270eaa..ad7eeb6f8 100644
--- a/waspc/test/Analyzer/ParserTest.hs
+++ b/waspc/test/Analyzer/ParserTest.hs
@@ -19,14 +19,15 @@ spec_Parser = do
" yes: true,",
" no: false,",
" ident: Wasp,",
+ " // This is a comment",
" innerDict: { innerDictReal: 2.17 }",
"}"
]
let ast =
AST
- [ wctx (1, 1) (10, 1) $
+ [ wctx (1, 1) (11, 1) $
Decl "test" "Decl" $
- wctx (1, 11) (10, 1) $
+ wctx (1, 11) (11, 1) $
Dict
[ ("string", wctx (2, 11) (2, 25) $ StringLiteral "Hello Wasp =}"),
("escapedString", wctx (3, 18) (3, 29) $ StringLiteral "Look, a \""),
@@ -36,15 +37,32 @@ spec_Parser = do
("no", wctx (7, 7) (7, 11) $ BoolLiteral False),
("ident", wctx (8, 10) (8, 13) $ Var "Wasp"),
( "innerDict",
- wctx (9, 14) (9, 36) $
+ wctx (10, 14) (10, 36) $
Dict
- [ ("innerDictReal", wctx (9, 31) (9, 34) $ DoubleLiteral 2.17)
+ [ ("innerDictReal", wctx (10, 31) (10, 34) $ DoubleLiteral 2.17)
]
)
]
]
parse source `shouldBe` Right ast
+ it "Parses comments" $ do
+ let source =
+ unlines
+ [ " // This is some // comment",
+ "/* comment",
+ " span//ning",
+ "multi/*ple lines */",
+ "test /* *hi* */ Decl 42 // One more comment",
+ "// And here is final comment"
+ ]
+ let ast =
+ AST
+ [ wctx (5, 1) (5, 23) $
+ Decl "test" "Decl" $ wctx (5, 22) (5, 23) $ IntegerLiteral 42
+ ]
+ parse source `shouldBe` Right ast
+
it "Parses external imports" $ do
let source =
unlines
diff --git a/waspc/test/AnalyzerTest.hs b/waspc/test/AnalyzerTest.hs
index a82cb6527..6d573545b 100644
--- a/waspc/test/AnalyzerTest.hs
+++ b/waspc/test/AnalyzerTest.hs
@@ -5,6 +5,8 @@ module AnalyzerTest where
import Analyzer.TestUtil (ctx)
import Data.Either (isRight)
import Data.List (intercalate)
+import Data.Maybe (fromJust)
+import qualified StrongPath as SP
import Test.Tasty.Hspec
import Wasp.Analyzer
import Wasp.Analyzer.Parser (Ctx)
@@ -103,7 +105,11 @@ spec_Analyzer = do
App.server =
Just
Server.Server
- { Server.setupFn = Just $ ExtImport (ExtImportField "setupServer") "@ext/bar.js"
+ { Server.setupFn =
+ Just $
+ ExtImport
+ (ExtImportField "setupServer")
+ (fromJust $ SP.parseRelFileP "bar.js")
},
App.db = Just Db.Db {Db.system = Just Db.PostgreSQL}
}
@@ -114,13 +120,19 @@ spec_Analyzer = do
let expectedPages =
[ ( "HomePage",
Page.Page
- { Page.component = ExtImport (ExtImportModule "Home") "@ext/pages/Main",
+ { Page.component =
+ ExtImport
+ (ExtImportModule "Home")
+ (fromJust $ SP.parseRelFileP "pages/Main"),
Page.authRequired = Nothing
}
),
( "ProfilePage",
Page.Page
- { Page.component = ExtImport (ExtImportField "profilePage") "@ext/pages/Profile",
+ { Page.component =
+ ExtImport
+ (ExtImportField "profilePage")
+ (fromJust $ SP.parseRelFileP "pages/Profile"),
Page.authRequired = Just True
}
)
@@ -153,7 +165,10 @@ spec_Analyzer = do
let expectedQueries =
[ ( "getUsers",
Query.Query
- { Query.fn = ExtImport (ExtImportField "getAllUsers") "@ext/foo.js",
+ { Query.fn =
+ ExtImport
+ (ExtImportField "getAllUsers")
+ (fromJust $ SP.parseRelFileP "foo.js"),
Query.entities = Just [Ref "User"],
Query.auth = Nothing
}
@@ -164,7 +179,10 @@ spec_Analyzer = do
let expectedAction =
[ ( "updateUser",
Action.Action
- { Action.fn = ExtImport (ExtImportField "updateUser") "@ext/foo.js",
+ { Action.fn =
+ ExtImport
+ (ExtImportField "updateUser")
+ (fromJust $ SP.parseRelFileP "foo.js"),
Action.entities = Just [Ref "User"],
Action.auth = Just True
}
diff --git a/waspc/test/ErrorTest.hs b/waspc/test/ErrorTest.hs
new file mode 100644
index 000000000..c3fc70349
--- /dev/null
+++ b/waspc/test/ErrorTest.hs
@@ -0,0 +1,79 @@
+module ErrorTest where
+
+import Data.List (intercalate)
+import Data.Maybe (fromJust)
+import Fixtures (systemSPRoot)
+import qualified StrongPath as SP
+import Test.Tasty.Hspec
+import Wasp.Analyzer.Parser.Ctx (ctxFromRgn)
+import Wasp.Analyzer.Parser.SourcePosition (SourcePosition (..))
+import Wasp.Error
+import qualified Wasp.Util.Terminal as T
+
+spec_WaspError :: Spec
+spec_WaspError = do
+ describe "showCompilerErrorForTerminal" $ do
+ describe "correctly shows simple error" $ do
+ let waspFilePath = systemSPRoot SP.> fromJust (SP.parseRelFile "waspeteer/aproject/main.wasp")
+ let waspFileContent =
+ unlines
+ [ "app TestApp {",
+ " server:",
+ " { db: SQLite",
+ " },",
+ " title: \"Test App\"",
+ "}"
+ ]
+ let errMsg = "Whoops: a test error happened!"
+ it "when error spans multiple lines" $ do
+ let errCtx = ctxFromRgn (SourcePosition 2 3) (SourcePosition 4 5)
+ showCompilerErrorForTerminal (waspFilePath, waspFileContent) (errMsg, errCtx)
+ `shouldBe` intercalate
+ "\n"
+ [ SP.fromAbsFile waspFilePath ++ " @ 2:3 - 4:5",
+ " " ++ errMsg,
+ "",
+ " " ++ T.applyStyles [T.Yellow] " 1 | " ++ "app TestApp {",
+ " " ++ T.applyStyles [T.Yellow] " 2 | " ++ " " ++ T.applyStyles [T.Red] "server:",
+ " " ++ T.applyStyles [T.Yellow] " 3 | " ++ T.applyStyles [T.Red] " { db: SQLite",
+ " " ++ T.applyStyles [T.Yellow] " 4 | " ++ T.applyStyles [T.Red] " }" ++ ",",
+ " " ++ T.applyStyles [T.Yellow] " 5 | " ++ " title: \"Test App\""
+ ]
+ it "when error spans a single line" $ do
+ let errCtx = ctxFromRgn (SourcePosition 5 10) (SourcePosition 5 19)
+ showCompilerErrorForTerminal (waspFilePath, waspFileContent) (errMsg, errCtx)
+ `shouldBe` intercalate
+ "\n"
+ [ SP.fromAbsFile waspFilePath ++ " @ 5:10-19",
+ " " ++ errMsg,
+ "",
+ " " ++ T.applyStyles [T.Yellow] " 4 | " ++ " },",
+ " " ++ T.applyStyles [T.Yellow] " 5 | " ++ " title: " ++ T.applyStyles [T.Red] "\"Test App\"",
+ " " ++ T.applyStyles [T.Yellow] " 6 | " ++ "}"
+ ]
+ it "when error spans a single character" $ do
+ let errCtx = ctxFromRgn (SourcePosition 3 11) (SourcePosition 3 11)
+ showCompilerErrorForTerminal (waspFilePath, waspFileContent) (errMsg, errCtx)
+ `shouldBe` intercalate
+ "\n"
+ [ SP.fromAbsFile waspFilePath ++ " @ 3:11",
+ " " ++ errMsg,
+ "",
+ " " ++ T.applyStyles [T.Yellow] " 2 | " ++ " server:",
+ " " ++ T.applyStyles [T.Yellow] " 3 | " ++ " { db: " ++ T.applyStyles [T.Red] "S" ++ "QLite",
+ " " ++ T.applyStyles [T.Yellow] " 4 | " ++ " },"
+ ]
+ it "when there is no context lines around the line with error" $ do
+ let waspFileContent' = "app $TestApp { title: \"Test App\" }"
+ let errCtx = ctxFromRgn (SourcePosition 1 5) (SourcePosition 1 12)
+ showCompilerErrorForTerminal (waspFilePath, waspFileContent') (errMsg, errCtx)
+ `shouldBe` intercalate
+ "\n"
+ [ SP.fromAbsFile waspFilePath ++ " @ 1:5-12",
+ " " ++ errMsg,
+ "",
+ " " ++ T.applyStyles [T.Yellow] " 1 | "
+ ++ "app "
+ ++ T.applyStyles [T.Red] "$TestApp"
+ ++ " { title: \"Test App\" }"
+ ]
diff --git a/waspc/test/Fixtures.hs b/waspc/test/Fixtures.hs
index 578a53793..c1ae5d180 100644
--- a/waspc/test/Fixtures.hs
+++ b/waspc/test/Fixtures.hs
@@ -4,29 +4,6 @@ import Data.Maybe (fromJust)
import qualified Path as P
import qualified StrongPath as SP
import qualified System.FilePath as FP
-import Wasp.Wasp
-import qualified Wasp.Wasp.Route as RouteAST
-
-app :: App
-app =
- App
- { appName = "test_app",
- appTitle = "Hello World!",
- appHead = Nothing
- }
-
-routeHome :: RouteAST.Route
-routeHome =
- RouteAST.Route
- { RouteAST._urlPath = "/home",
- RouteAST._targetPage = "Home"
- }
-
-wasp :: Wasp
-wasp =
- fromWaspElems
- [ WaspElementApp app
- ]
systemSPRoot :: SP.Path' SP.Abs (SP.Dir d)
systemSPRoot = fromJust $ SP.parseAbsDir systemFpRoot
diff --git a/waspc/test/Generator/PackageJsonGeneratorTest.hs b/waspc/test/Generator/PackageJsonGeneratorTest.hs
index 584a8ddca..754cbfde8 100644
--- a/waspc/test/Generator/PackageJsonGeneratorTest.hs
+++ b/waspc/test/Generator/PackageJsonGeneratorTest.hs
@@ -1,8 +1,8 @@
module Generator.PackageJsonGeneratorTest where
import Test.Tasty.Hspec
+import qualified Wasp.AppSpec.App.Dependency as D
import Wasp.Generator.PackageJsonGenerator (resolveNpmDeps)
-import qualified Wasp.NpmDependency as ND
spec_resolveNpmDeps :: Spec
spec_resolveNpmDeps = do
@@ -16,21 +16,21 @@ spec_resolveNpmDeps = do
[ ("foo", "bar"),
("foo2", "bar2")
]
- resolveNpmDeps (ND.fromList waspDeps) (ND.fromList userDeps)
- `shouldBe` Right (ND.fromList waspDeps, ND.fromList userDeps)
+ resolveNpmDeps (D.fromList waspDeps) (D.fromList userDeps)
+ `shouldBe` Right (D.fromList waspDeps, D.fromList userDeps)
it "Does not repeat dep if it is both user and wasp dep." $ do
let userDeps =
[ ("axios", "^0.20.0"),
("foo", "bar")
]
- resolveNpmDeps (ND.fromList waspDeps) (ND.fromList userDeps)
- `shouldBe` Right (ND.fromList waspDeps, ND.fromList [("foo", "bar")])
+ resolveNpmDeps (D.fromList waspDeps) (D.fromList userDeps)
+ `shouldBe` Right (D.fromList waspDeps, D.fromList [("foo", "bar")])
it "Reports error if user dep version does not match wasp dep version." $ do
let userDeps =
[ ("axios", "^1.20.0"),
("foo", "bar")
]
- let Left conflicts = resolveNpmDeps (ND.fromList waspDeps) (ND.fromList userDeps)
- map fst conflicts `shouldBe` ND.fromList [("axios", "^1.20.0")]
+ let Left conflicts = resolveNpmDeps (D.fromList waspDeps) (D.fromList userDeps)
+ map fst conflicts `shouldBe` D.fromList [("axios", "^1.20.0")]
diff --git a/waspc/test/Generator/WebAppGeneratorTest.hs b/waspc/test/Generator/WebAppGeneratorTest.hs
index 9e5c2e25b..7143997f7 100644
--- a/waspc/test/Generator/WebAppGeneratorTest.hs
+++ b/waspc/test/Generator/WebAppGeneratorTest.hs
@@ -4,7 +4,9 @@ import Fixtures (systemSPRoot)
import qualified StrongPath as SP
import System.FilePath ((>))
import Test.Tasty.Hspec
-import qualified Wasp.CompileOptions as CompileOptions
+import qualified Wasp.AppSpec as AS
+import qualified Wasp.AppSpec.App as AS.App
+import qualified Wasp.AppSpec.Core.Decl as AS.Decl
import Wasp.Generator.FileDraft
import qualified Wasp.Generator.FileDraft.CopyDirFileDraft as CopyDirFD
import qualified Wasp.Generator.FileDraft.CopyFileDraft as CopyFD
@@ -12,27 +14,39 @@ import qualified Wasp.Generator.FileDraft.TemplateFileDraft as TmplFD
import qualified Wasp.Generator.FileDraft.TextFileDraft as TextFD
import Wasp.Generator.WebAppGenerator
import qualified Wasp.Generator.WebAppGenerator.Common as Common
-import Wasp.Wasp
--- TODO(martin): We could define Arbitrary instance for Wasp, define properties over
--- generator functions and then do property testing on them, that would be cool.
+-- TODO(martin): We could maybe define Arbitrary instance for AppSpec, define properties
+-- over generator functions and then do property testing on them, that would be cool.
spec_WebAppGenerator :: Spec
spec_WebAppGenerator = do
- let testApp = App "TestApp" "Test App" Nothing
- let testWasp = fromApp testApp
- let testCompileOptions =
- CompileOptions.CompileOptions
- { CompileOptions.externalCodeDirPath = systemSPRoot SP.> [SP.reldir|test/src|],
- CompileOptions.isBuild = False
+ let testAppSpec =
+ AS.AppSpec
+ { AS.decls =
+ [ AS.Decl.makeDecl
+ "TestApp"
+ AS.App.App
+ { AS.App.title = "Test App",
+ AS.App.db = Nothing,
+ AS.App.server = Nothing,
+ AS.App.auth = Nothing,
+ AS.App.dependencies = Nothing,
+ AS.App.head = Nothing
+ }
+ ],
+ AS.externalCodeDirPath = systemSPRoot SP.> [SP.reldir|test/src|],
+ AS.externalCodeFiles = [],
+ AS.isBuild = False,
+ AS.migrationsDir = Nothing,
+ AS.dotEnvFile = Nothing
}
describe "generateWebApp" $ do
-- NOTE: This test does not (for now) check that content of files is correct or
-- that they will successfully be written, it checks only that their
-- destinations are correct.
- it "Given a simple Wasp, creates file drafts at expected destinations" $ do
- let fileDrafts = generateWebApp testWasp testCompileOptions
+ it "Given a simple AppSpec, creates file drafts at expected destinations" $ do
+ let fileDrafts = generateWebApp testAppSpec
let expectedFileDraftDstPaths =
map (SP.toFilePath Common.webAppRootDirInProjectRootDir >) $
concat
diff --git a/waspc/test/Parser/ActionTest.hs b/waspc/test/Parser/ActionTest.hs
deleted file mode 100644
index 1dfe2dd79..000000000
--- a/waspc/test/Parser/ActionTest.hs
+++ /dev/null
@@ -1,57 +0,0 @@
-module Parser.ActionTest where
-
-import Data.Char (toLower)
-import Data.Either (isLeft)
-import qualified StrongPath as SP
-import Test.Tasty.Hspec
-import Wasp.Parser.Action (action)
-import Wasp.Parser.Common (runWaspParser)
-import qualified Wasp.Wasp.Action as Wasp.Action
-import qualified Wasp.Wasp.JsImport as Wasp.JsImport
-
--- TODO: This file is mostly just duplication of Parser.QueryTest.
--- We might want to look into generalizing the two, to avoid all this
--- duplication -> well, we really shoud start with generalizing
--- Parser.Query and Parser.Action.
-
-spec_parseAction :: Spec
-spec_parseAction =
- describe "Parsing action declaration" $ do
- let parseAction = runWaspParser action
- let testWhenAuth auth =
- it ("When given a valid action declaration, returns correct AST(action.auth = " ++ show auth ++ ")") $
- parseAction (genActionCode auth) `shouldBe` Right (genActionAST auth)
- testWhenAuth (Just True)
- testWhenAuth (Just False)
- testWhenAuth Nothing
- it "When given action wasp declaration without 'fn' property, should return Left" $ do
- isLeft (parseAction "action myAction { }") `shouldBe` True
- where
- genActionAST :: Maybe Bool -> Wasp.Action.Action
- genActionAST aApplyAuth =
- Wasp.Action.Action
- { Wasp.Action._name = testActionName,
- Wasp.Action._jsFunction =
- Wasp.JsImport.JsImport
- { Wasp.JsImport._defaultImport = Nothing,
- Wasp.JsImport._namedImports = [testActionJsFunctionName],
- Wasp.JsImport._from = testActionJsFunctionFrom
- },
- Wasp.Action._entities = Nothing,
- Wasp.Action._auth = aApplyAuth
- }
- genActionCode :: Maybe Bool -> String
- genActionCode aApplyAuth =
- "action " ++ testActionName ++ " {\n"
- ++ " fn: import { "
- ++ testActionJsFunctionName
- ++ " } from \"@ext/some/path\""
- ++ authStr aApplyAuth
- ++ "}"
-
- authStr :: Maybe Bool -> String
- authStr (Just useAuth) = ",\n auth: " ++ map toLower (show useAuth) ++ "\n"
- authStr _ = "\n"
- testActionJsFunctionFrom = [SP.relfileP|some/path|]
- testActionJsFunctionName = "myJsAction"
- testActionName = "myAction"
diff --git a/waspc/test/Parser/CommonTest.hs b/waspc/test/Parser/CommonTest.hs
deleted file mode 100644
index 72fbc2134..000000000
--- a/waspc/test/Parser/CommonTest.hs
+++ /dev/null
@@ -1,112 +0,0 @@
-module Parser.CommonTest where
-
-import Data.Either
-import qualified StrongPath as SP
-import Test.Tasty.Hspec
-import Text.Parsec
-import Wasp.Lexer
-import qualified Wasp.Lexer as L
-import Wasp.Parser.Common
-
-spec_parseWaspCommon :: Spec
-spec_parseWaspCommon = do
- describe "Parsing wasp element linked to an entity" $ do
- it "When given a valid declaration, parses it correctly." $ do
- runWaspParser
- (waspElementLinkedToEntity "entity-form" (waspClosure whiteSpace))
- "entity-form TaskForm { }"
- `shouldBe` Right ("Task", "TaskForm", ())
-
- describe "Parsing wasp element name and properties" $ do
- let parseWaspElementNameAndClosureContent elemKeyword p input =
- runWaspParser (waspElementNameAndClosureContent elemKeyword p) input
-
- it
- "When given valid wasp element declaration along with whitespace parser,\
- \ returns an expected result"
- $ do
- parseWaspElementNameAndClosureContent "app" whiteSpace "app someApp { }"
- `shouldBe` Right ("someApp", ())
-
- it
- "When given valid wasp element declaration along with char parser, returns\
- \ an expected result"
- $ do
- parseWaspElementNameAndClosureContent "app" (char 'a') "app someApp {a}"
- `shouldBe` Right ("someApp", 'a')
-
- it "When given wasp element declaration with invalid name, returns Left" $ do
- isLeft (parseWaspElementNameAndClosureContent "app" whiteSpace "app 1someApp { }")
- `shouldBe` True
-
- describe "Parsing wasp closure" $ do
- it "Parses a closure with braces {}" $ do
- runWaspParser (waspClosure (symbol "content")) "{ content }"
- `shouldBe` Right "content"
-
- it "Does not parse a closure with brackets []" $ do
- isLeft (runWaspParser (waspClosure (symbol "content")) "[ content ]")
- `shouldBe` True
-
- describe "Parsing wasp property with a closure as a value" $ do
- it "When given a string as a key and closure as a value, returns closure content." $ do
- runWaspParser (waspPropertyClosure "someKey" (symbol "content")) "someKey: { content }"
- `shouldBe` Right "content"
-
- describe "Parsing wasp property - string literal" $ do
- let parseWaspPropertyStringLiteral key input =
- runWaspParser (waspPropertyStringLiteral key) input
-
- it "When given key/value with int value, returns Left." $ do
- isLeft (parseWaspPropertyStringLiteral "title" "title: 23")
- `shouldBe` True
-
- it "When given key/value with string value, returns a parsed value." $ do
- let appTitle = "my first app"
- parseWaspPropertyStringLiteral "title" ("title: \"" ++ appTitle ++ "\"")
- `shouldBe` Right appTitle
-
- describe "Parsing wasp property - jsx closure {=jsx...jsx=}" $ do
- let parseWaspPropertyJsxClosure key input =
- runWaspParser (waspPropertyJsxClosure key) input
-
- it "When given unexpected property key, returns Left." $ do
- isLeft (parseWaspPropertyJsxClosure "content" "title: 23")
- `shouldBe` True
-
- it "When given content within jsx closure, returns that content." $ do
- parseWaspPropertyJsxClosure "content" "content: {=jsx some content jsx=}"
- `shouldBe` Right "some content"
-
- describe "Parsing wasp jsx closure" $ do
- let parseWaspJsxClosure input = runWaspParser waspJsxClosure input
- let closureContent = "
hello world
"
-
- it "Returns the content of closure" $ do
- parseWaspJsxClosure ("{=jsx " ++ closureContent ++ " jsx=}")
- `shouldBe` Right closureContent
-
- it "Can parse braces {} within the closure" $ do
- let closureContentWithBraces = "
hello world {task.length}
"
-
- parseWaspJsxClosure ("{=jsx " ++ closureContentWithBraces ++ " jsx=}")
- `shouldBe` Right closureContentWithBraces
-
- it "Removes leading and trailing spaces" $ do
- parseWaspJsxClosure ("{=jsx " ++ closureContent ++ " jsx=}")
- `shouldBe` Right closureContent
-
- describe "Parsing relative file path string" $ do
- it "Correctly parses relative path in double quotes" $ do
- runWaspParser relFilePathString "\"foo/bar.txt\""
- `shouldBe` Right [SP.relfile|foo/bar.txt|]
-
- -- TODO: It is not passing on Windows, due to Path differently parsing paths on Windows.
- -- Check out Path.Posix vs Path.Windows.
- -- it "When path is not relative, returns Left" $ do
- -- isLeft (runWaspParser relFilePathString "\"/foo/bar.txt\"") `shouldBe` True
-
- describe "Parsing wasp array" $ do
- it "Correctly parses array of identifiers" $ do
- runWaspParser (waspList L.identifier) "[ Task, Project ,User]"
- `shouldBe` Right ["Task", "Project", "User"]
diff --git a/waspc/test/Parser/DbTest.hs b/waspc/test/Parser/DbTest.hs
deleted file mode 100644
index f6ea95d62..000000000
--- a/waspc/test/Parser/DbTest.hs
+++ /dev/null
@@ -1,21 +0,0 @@
-module Parser.DbTest where
-
-import Data.Either (isLeft)
-import Test.Tasty.Hspec
-import Wasp.Parser.Common (runWaspParser)
-import Wasp.Parser.Db (db)
-import qualified Wasp.Wasp.Db as Wasp.Db
-
-spec_parseDb :: Spec
-spec_parseDb =
- describe "Parsing db declaration" $ do
- let parseDb input = runWaspParser db input
-
- it "When given a valid db declaration, returns correct AST" $ do
- parseDb "db { system: PostgreSQL }"
- `shouldBe` Right (Wasp.Db.Db {Wasp.Db._system = Wasp.Db.PostgreSQL})
- parseDb "db { system: SQLite }"
- `shouldBe` Right (Wasp.Db.Db {Wasp.Db._system = Wasp.Db.SQLite})
-
- it "When given db wasp declaration without 'db', should return Left" $ do
- isLeft (parseDb "db { }") `shouldBe` True
diff --git a/waspc/test/Parser/ExternalCodeTest.hs b/waspc/test/Parser/ExternalCodeTest.hs
deleted file mode 100644
index af195e06f..000000000
--- a/waspc/test/Parser/ExternalCodeTest.hs
+++ /dev/null
@@ -1,17 +0,0 @@
-module Parser.ExternalCodeTest where
-
-import Data.Either (isLeft)
-import qualified StrongPath as SP
-import Test.Tasty.Hspec
-import Wasp.Parser.Common (runWaspParser)
-import Wasp.Parser.ExternalCode (extCodeFilePathString)
-
-spec_ParserExternalCode :: Spec
-spec_ParserExternalCode = do
- describe "Parsing external code file path string" $ do
- it "Correctly parses external code path in double quotes" $ do
- runWaspParser extCodeFilePathString "\"@ext/foo/bar.txt\""
- `shouldBe` Right [SP.relfileP|foo/bar.txt|]
-
- it "When path does not start with @ext/, returns Left" $ do
- isLeft (runWaspParser extCodeFilePathString "\"@ext2/foo/bar.txt\"") `shouldBe` True
diff --git a/waspc/test/Parser/JsImportTest.hs b/waspc/test/Parser/JsImportTest.hs
deleted file mode 100644
index 71a058283..000000000
--- a/waspc/test/Parser/JsImportTest.hs
+++ /dev/null
@@ -1,44 +0,0 @@
-module Parser.JsImportTest where
-
-import Data.Either (isLeft)
-import qualified StrongPath as SP
-import Test.Tasty.Hspec
-import Wasp.Parser.Common (runWaspParser)
-import Wasp.Parser.JsImport (jsImport)
-import qualified Wasp.Wasp as Wasp
-
-spec_parseJsImport :: Spec
-spec_parseJsImport = do
- let someFilePath = [SP.relfileP|some/file.js|]
-
- it "Parses external code js import with default import correctly" $ do
- runWaspParser jsImport "import something from \"@ext/some/file.js\""
- `shouldBe` Right (Wasp.JsImport (Just "something") [] someFilePath)
-
- it "Parses correctly when there is whitespace up front" $ do
- runWaspParser jsImport " import something from \"@ext/some/file.js\""
- `shouldBe` Right (Wasp.JsImport (Just "something") [] someFilePath)
-
- it "Parses correctly when 'from' is part of WHAT part" $ do
- runWaspParser jsImport "import somethingfrom from \"@ext/some/file.js\""
- `shouldBe` Right (Wasp.JsImport (Just "somethingfrom") [] someFilePath)
-
- it "Parses correctly when 'what' is a single named export" $ do
- runWaspParser jsImport "import { something } from \"@ext/some/file.js\""
- `shouldBe` Right (Wasp.JsImport Nothing ["something"] someFilePath)
-
- it "For now we don't support multiple named exports in WHAT part" $ do
- isLeft (runWaspParser jsImport "import { foo, bar } from \"@ext/some/file.js\"")
- `shouldBe` True
-
- it "Throws error if there is no whitespace after import" $ do
- isLeft (runWaspParser jsImport "importsomething from \"@ext/some/file.js\"")
- `shouldBe` True
-
- it "Throws error if 'from' part is not referring to the external code" $ do
- isLeft (runWaspParser jsImport "import something from \"some/file.js\"")
- `shouldBe` True
-
- it "For now we don't support single quotes in FROM part (TODO: support them in the future!)" $ do
- isLeft (runWaspParser jsImport "import something from '@ext/some/file.js'")
- `shouldBe` True
diff --git a/waspc/test/Parser/NpmDependenciesTest.hs b/waspc/test/Parser/NpmDependenciesTest.hs
deleted file mode 100644
index 1d96629f4..000000000
--- a/waspc/test/Parser/NpmDependenciesTest.hs
+++ /dev/null
@@ -1,24 +0,0 @@
-module Parser.NpmDependenciesTest where
-
-import Data.Either (isLeft)
-import Test.Tasty.Hspec
-import qualified Wasp.NpmDependency as ND
-import Wasp.Parser.Common (runWaspParser)
-import Wasp.Parser.NpmDependencies (npmDependencies)
-import Wasp.Wasp.NpmDependencies
-
-spec_parseNpmDependencies :: Spec
-spec_parseNpmDependencies = do
- describe "Parsing npm dependencies" $ do
- it "When given a valid declaration with valid json, parses it correctly" $ do
- runWaspParser npmDependencies "dependencies {=json \"foo\": \"test1\", \"bar\": \"test2\" json=}"
- `shouldBe` Right
- NpmDependencies
- { _dependencies =
- [ ND.NpmDependency {ND._name = "foo", ND._version = "test1"},
- ND.NpmDependency {ND._name = "bar", ND._version = "test2"}
- ]
- }
- it "When given invalid json, reports error" $ do
- isLeft (runWaspParser npmDependencies "dependencies {=json foo: 42 json=}")
- `shouldBe` True
diff --git a/waspc/test/Parser/OperationTest.hs b/waspc/test/Parser/OperationTest.hs
deleted file mode 100644
index 4c79a0bb2..000000000
--- a/waspc/test/Parser/OperationTest.hs
+++ /dev/null
@@ -1,34 +0,0 @@
-module Parser.OperationTest where
-
-import Data.List (intercalate)
-import qualified StrongPath as SP
-import Test.Tasty.Hspec
-import Wasp.Parser.Common (runWaspParser)
-import Wasp.Parser.Operation
-import qualified Wasp.Wasp.JsImport as Wasp.JsImport
-
-spec_parseOperation :: Spec
-spec_parseOperation =
- describe "Parsing operation properties" $ do
- let parseOperationProperties = runWaspParser properties
-
- it "When given a valid list of properties, correctly parses them" $ do
- let testJsFnName = "myJsFn"
- testJsFnFrom = [SP.relfileP|some/path|]
- let testProps =
- [ JsFunction $
- Wasp.JsImport.JsImport
- { Wasp.JsImport._defaultImport = Nothing,
- Wasp.JsImport._namedImports = [testJsFnName],
- Wasp.JsImport._from = testJsFnFrom
- },
- Entities ["Task", "Project"]
- ]
- parseOperationProperties
- ( intercalate
- ",\n"
- [ "fn: import { " ++ testJsFnName ++ " } from \"@ext/some/path\"",
- "entities: [Task, Project]"
- ]
- )
- `shouldBe` Right testProps
diff --git a/waspc/test/Parser/PageTest.hs b/waspc/test/Parser/PageTest.hs
deleted file mode 100644
index 9cb8dc130..000000000
--- a/waspc/test/Parser/PageTest.hs
+++ /dev/null
@@ -1,57 +0,0 @@
-module Parser.PageTest where
-
-import Data.Either (isLeft)
-import qualified StrongPath as SP
-import Test.Tasty.Hspec
-import Wasp.Parser.Common (runWaspParser)
-import Wasp.Parser.Page (page)
-import qualified Wasp.Wasp.JsImport as Wasp.JsImport
-import qualified Wasp.Wasp.Page as Wasp.Page
-
-spec_parsePage :: Spec
-spec_parsePage =
- describe "Parsing page declaration" $ do
- let parsePage input = runWaspParser page input
-
- let expectedPageComponentImport =
- Wasp.JsImport.JsImport
- { Wasp.JsImport._defaultImport = Just "Main",
- Wasp.JsImport._namedImports = [],
- Wasp.JsImport._from = [SP.relfileP|pages/Main|]
- }
-
- it "When given a valid page declaration, returns correct AST" $ do
- let testPageName = "Landing"
-
- parsePage
- ( "page " ++ testPageName ++ " { "
- ++ "component: import Main from \"@ext/pages/Main\""
- ++ "}"
- )
- `shouldBe` Right
- ( Wasp.Page.Page
- { Wasp.Page._name = testPageName,
- Wasp.Page._component = expectedPageComponentImport,
- Wasp.Page._authRequired = Nothing
- }
- )
-
- it "When given a valid page with authRequired property, returns correct AST" $ do
- let testPageName = "Landing"
-
- parsePage
- ( "page " ++ testPageName ++ " { "
- ++ "component: import Main from \"@ext/pages/Main\","
- ++ "authRequired: true"
- ++ "}"
- )
- `shouldBe` Right
- ( Wasp.Page.Page
- { Wasp.Page._name = testPageName,
- Wasp.Page._component = expectedPageComponentImport,
- Wasp.Page._authRequired = Just True
- }
- )
-
- it "When given page wasp declaration without 'page', should return Left" $ do
- isLeft (parsePage "Landing { component: import Main from \"@ext/pages/Main\" }") `shouldBe` True
diff --git a/waspc/test/Parser/ParserTest.hs b/waspc/test/Parser/ParserTest.hs
deleted file mode 100644
index 53f95f9bc..000000000
--- a/waspc/test/Parser/ParserTest.hs
+++ /dev/null
@@ -1,125 +0,0 @@
-module Parser.ParserTest where
-
-import Data.Either
-import qualified StrongPath as SP
-import Test.Tasty.Hspec
-import Wasp.NpmDependency as ND
-import Wasp.Parser
-import Wasp.Parser.Common (runWaspParser)
-import qualified Wasp.Psl.Parser.Model
-import Wasp.Wasp
-import qualified Wasp.Wasp.Auth as Wasp.Auth
-import qualified Wasp.Wasp.Entity as Wasp.Entity
-import qualified Wasp.Wasp.JsImport as Wasp.JsImport
-import qualified Wasp.Wasp.NpmDependencies as Wasp.NpmDependencies
-import qualified Wasp.Wasp.Page as Wasp.Page
-import qualified Wasp.Wasp.Query as Wasp.Query
-import qualified Wasp.Wasp.Route as R
-
-spec_parseWasp :: Spec
-spec_parseWasp =
- describe "Parsing wasp" $ do
- it "When given wasp without app, should return Left" $ do
- isLeft (parseWasp "hoho") `shouldBe` True
-
- before (readFile "test/Parser/valid.wasp") $ do
- it "When given a valid wasp source, should return correct Wasp" $ \wasp ->
- do
- parseWasp wasp
- `shouldBe` Right
- ( fromWaspElems
- [ WaspElementApp $
- App
- { appName = "test_app",
- appTitle = "Hello World!",
- appHead = Nothing
- },
- WaspElementAuth $
- Wasp.Auth.Auth
- { Wasp.Auth._userEntity = "User",
- Wasp.Auth._methods = [Wasp.Auth.EmailAndPassword],
- Wasp.Auth._onAuthFailedRedirectTo = "/test",
- Wasp.Auth._onAuthSucceededRedirectTo = "/"
- },
- WaspElementRoute $
- R.Route
- { R._urlPath = "/",
- R._targetPage = "Landing"
- },
- WaspElementPage $
- Wasp.Page.Page
- { Wasp.Page._name = "Landing",
- Wasp.Page._component =
- Wasp.JsImport.JsImport
- { Wasp.JsImport._defaultImport = Just "Landing",
- Wasp.JsImport._namedImports = [],
- Wasp.JsImport._from = [SP.relfileP|pages/Landing|]
- },
- Wasp.Page._authRequired = Just False
- },
- WaspElementRoute $
- R.Route
- { R._urlPath = "/test",
- R._targetPage = "TestPage"
- },
- WaspElementPage $
- Wasp.Page.Page
- { Wasp.Page._name = "TestPage",
- Wasp.Page._component =
- Wasp.JsImport.JsImport
- { Wasp.JsImport._defaultImport = Just "Test",
- Wasp.JsImport._namedImports = [],
- Wasp.JsImport._from = [SP.relfileP|pages/Test|]
- },
- Wasp.Page._authRequired = Nothing
- },
- WaspElementEntity $
- Wasp.Entity.Entity
- { Wasp.Entity._name = "Task",
- Wasp.Entity._fields =
- [ Wasp.Entity.Field
- { Wasp.Entity._fieldName = "id",
- Wasp.Entity._fieldType = Wasp.Entity.FieldTypeScalar Wasp.Entity.Int
- },
- Wasp.Entity.Field
- { Wasp.Entity._fieldName = "description",
- Wasp.Entity._fieldType = Wasp.Entity.FieldTypeScalar Wasp.Entity.String
- },
- Wasp.Entity.Field
- { Wasp.Entity._fieldName = "isDone",
- Wasp.Entity._fieldType = Wasp.Entity.FieldTypeScalar Wasp.Entity.Boolean
- }
- ],
- Wasp.Entity._pslModelBody =
- fromRight (error "failed to parse") $
- runWaspParser
- Wasp.Psl.Parser.Model.body
- "\
- \ id Int @id @default(autoincrement())\n\
- \ description String\n\
- \ isDone Boolean @default(false)"
- },
- WaspElementQuery $
- Wasp.Query.Query
- { Wasp.Query._name = "myQuery",
- Wasp.Query._jsFunction =
- Wasp.JsImport.JsImport
- { Wasp.JsImport._defaultImport = Nothing,
- Wasp.JsImport._namedImports = ["myJsQuery"],
- Wasp.JsImport._from = [SP.relfileP|some/path|]
- },
- Wasp.Query._entities = Nothing,
- Wasp.Query._auth = Nothing
- },
- WaspElementNpmDependencies $
- Wasp.NpmDependencies.NpmDependencies
- { Wasp.NpmDependencies._dependencies =
- [ ND.NpmDependency
- { ND._name = "lodash",
- ND._version = "^4.17.15"
- }
- ]
- }
- ]
- `setJsImports` [JsImport (Just "something") [] [SP.relfileP|some/file|]]
- )
diff --git a/waspc/test/Parser/QueryTest.hs b/waspc/test/Parser/QueryTest.hs
deleted file mode 100644
index 6dedb85f6..000000000
--- a/waspc/test/Parser/QueryTest.hs
+++ /dev/null
@@ -1,53 +0,0 @@
-module Parser.QueryTest where
-
-import Data.Char (toLower)
-import Data.Either (isLeft)
-import qualified StrongPath as SP
-import Test.Tasty.Hspec
-import Wasp.Parser.Common (runWaspParser)
-import Wasp.Parser.Query (query)
-import qualified Wasp.Wasp.JsImport as Wasp.JsImport
-import qualified Wasp.Wasp.Query as Wasp.Query
-
-spec_parseQuery :: Spec
-spec_parseQuery =
- describe "Parsing query declaration" $ do
- let parseQuery = runWaspParser query
- let testWhenAuth auth =
- it ("When given a valid query declaration, returns correct AST(query.auth = " ++ show auth ++ ")") $
- parseQuery (genQueryCode auth) `shouldBe` Right (genQueryAST auth)
- testWhenAuth (Just True)
- testWhenAuth (Just False)
- testWhenAuth Nothing
- it "When given query wasp declaration without 'fn' property, should return Left" $ do
- isLeft (parseQuery "query myQuery { }") `shouldBe` True
- where
- genQueryCode :: Maybe Bool -> String
- genQueryCode qApplyAuth =
- "query " ++ testQueryName ++ " {\n"
- ++ " fn: import { "
- ++ testQueryJsFunctionName
- ++ " } from \"@ext/some/path\",\n"
- ++ " entities: [Task, Project]"
- ++ authStr qApplyAuth
- ++ "}"
- genQueryAST :: Maybe Bool -> Wasp.Query.Query
- genQueryAST qApplyAuth =
- Wasp.Query.Query
- { Wasp.Query._name = testQueryName,
- Wasp.Query._jsFunction =
- Wasp.JsImport.JsImport
- { Wasp.JsImport._defaultImport = Nothing,
- Wasp.JsImport._namedImports = [testQueryJsFunctionName],
- Wasp.JsImport._from = testQueryJsFunctionFrom
- },
- Wasp.Query._entities = Just ["Task", "Project"],
- Wasp.Query._auth = qApplyAuth
- }
-
- authStr :: Maybe Bool -> String
- authStr (Just useAuth) = ",\n auth: " ++ map toLower (show useAuth) ++ "\n"
- authStr _ = "\n"
- testQueryName = "myQuery"
- testQueryJsFunctionName = "myJsQuery"
- testQueryJsFunctionFrom = [SP.relfileP|some/path|]
diff --git a/waspc/test/Parser/RouteTest.hs b/waspc/test/Parser/RouteTest.hs
deleted file mode 100644
index 8cabbb89c..000000000
--- a/waspc/test/Parser/RouteTest.hs
+++ /dev/null
@@ -1,29 +0,0 @@
-module Parser.RouteTest where
-
-import Data.Either (isLeft)
-import Test.Tasty.Hspec
-import Wasp.Parser.Common (runWaspParser)
-import Wasp.Parser.Route (route)
-import qualified Wasp.Wasp.Route as RouteAST
-
-spec_parseRoute :: Spec
-spec_parseRoute =
- describe "Parsing route declaration" $ do
- let parseRoute = runWaspParser route
-
- it "When given a valid route declaration, returns correct AST." $ do
- let inputUrlPath = "/some/url/path"
- let inputTargetPage = "somePage"
-
- parseRoute
- ( "route " ++ "\"" ++ inputUrlPath ++ "\"" ++ " -> page " ++ inputTargetPage
- )
- `shouldBe` Right
- ( RouteAST.Route
- { RouteAST._urlPath = inputUrlPath,
- RouteAST._targetPage = inputTargetPage
- }
- )
-
- it "When given a route declaration without 'page', should return Left" $ do
- isLeft (parseRoute "route \"/url\" -> Home") `shouldBe` True
diff --git a/waspc/test/Parser/ServerTest.hs b/waspc/test/Parser/ServerTest.hs
deleted file mode 100644
index 0efda47a9..000000000
--- a/waspc/test/Parser/ServerTest.hs
+++ /dev/null
@@ -1,40 +0,0 @@
-module Parser.ServerTest where
-
-import Data.Either (isLeft)
-import qualified StrongPath as SP
-import Test.Tasty.Hspec
-import Wasp.Parser.Common (runWaspParser)
-import Wasp.Parser.Server (server)
-import qualified Wasp.Wasp.JsImport as Wasp.JsImport
-import qualified Wasp.Wasp.Server as Wasp.Server
-
-spec_parseServer :: Spec
-spec_parseServer =
- describe "Parsing server declaration" $ do
- let parseServer = runWaspParser server
-
- it "When given a valid server declaration, returns correct AST" $ do
- let setupFnName = "myServerSetup"
- setupFnFrom = [SP.relfileP|some/path|]
- let ast =
- Wasp.Server.Server
- { Wasp.Server._setupJsFunction =
- Wasp.JsImport.JsImport
- { Wasp.JsImport._defaultImport = Nothing,
- Wasp.JsImport._namedImports = [setupFnName],
- Wasp.JsImport._from = setupFnFrom
- }
- }
- parseServer
- ( "server {\n"
- ++ " setupFn: import { "
- ++ setupFnName
- ++ " } from \"@ext/"
- ++ SP.fromRelFileP setupFnFrom
- ++ "\"\n"
- ++ "}"
- )
- `shouldBe` Right ast
-
- it "When given server wasp declaration without 'serverFn' property, should return Left" $ do
- isLeft (parseServer "server { }") `shouldBe` True
diff --git a/waspc/test/Parser/StyleTest.hs b/waspc/test/Parser/StyleTest.hs
deleted file mode 100644
index 2934f1407..000000000
--- a/waspc/test/Parser/StyleTest.hs
+++ /dev/null
@@ -1,22 +0,0 @@
-module Parser.StyleTest where
-
-import Data.Either (isLeft)
-import qualified StrongPath as SP
-import Test.Tasty.Hspec
-import Wasp.Parser.Common (runWaspParser)
-import Wasp.Parser.Style (style)
-import qualified Wasp.Wasp.Style as Wasp.Style
-
-spec_parseStyle :: Spec
-spec_parseStyle = do
- it "Parses external code file path correctly" $ do
- runWaspParser style "\"@ext/some/file.css\""
- `shouldBe` Right (Wasp.Style.ExtCodeCssFile [SP.relfileP|some/file.css|])
-
- it "Parses css closure correctly" $ do
- runWaspParser style "{=css Some css code css=}"
- `shouldBe` Right (Wasp.Style.CssCode "Some css code")
-
- it "Throws error if path is not external code path." $ do
- isLeft (runWaspParser style "\"some/file.css\"")
- `shouldBe` True
diff --git a/waspc/test/Parser/valid.wasp b/waspc/test/Parser/valid.wasp
deleted file mode 100644
index e359ace38..000000000
--- a/waspc/test/Parser/valid.wasp
+++ /dev/null
@@ -1,40 +0,0 @@
-// Test .wasp file.
-
-import something from "@ext/some/file"
-
-// App definition.
-app test_app {
- // Title of the app.
- title: "Hello World!"
-}
-
-auth {
- onAuthFailedRedirectTo: "/test",
- userEntity: User,
- methods: [ EmailAndPassword ]
-}
-
-route "/" -> page Landing
-page Landing {
- component: import Landing from "@ext/pages/Landing",
- authRequired: false
-}
-
-route "/test" -> page TestPage
-page TestPage {
- component: import Test from "@ext/pages/Test"
-}
-
-entity Task {=psl
- id Int @id @default(autoincrement())
- description String
- isDone Boolean @default(false)
-psl=}
-
-query myQuery {
- fn: import { myJsQuery } from "@ext/some/path"
-}
-
-dependencies {=json
- "lodash": "^4.17.15"
-json=}
diff --git a/waspc/test/Psl/Generator/ModelTest.hs b/waspc/test/Psl/Generator/ModelTest.hs
index 80a509b30..fca350a31 100644
--- a/waspc/test/Psl/Generator/ModelTest.hs
+++ b/waspc/test/Psl/Generator/ModelTest.hs
@@ -6,7 +6,7 @@ module Psl.Generator.ModelTest where
import Psl.Common.ModelTest (sampleBodyAst)
import Test.Tasty.Hspec
import Test.Tasty.QuickCheck
-import Wasp.Parser.Common (runWaspParser)
+import qualified Text.Parsec as Parsec
import qualified Wasp.Psl.Ast.Model as AST
import Wasp.Psl.Generator.Model (generateModel)
import qualified Wasp.Psl.Parser.Model
@@ -17,12 +17,12 @@ spec_generatePslModel = do
let pslModelAst = AST.Model "User" sampleBodyAst
it "parse(generate(sampleBodyAst)) == sampleBodyAst" $ do
- runWaspParser Wasp.Psl.Parser.Model.model (generateModel pslModelAst) `shouldBe` Right pslModelAst
+ Parsec.parse Wasp.Psl.Parser.Model.model "" (generateModel pslModelAst) `shouldBe` Right pslModelAst
prop_generatePslModel :: Property
prop_generatePslModel = mapSize (const 100) $ \modelAst ->
within 1000000 $
- runWaspParser Wasp.Psl.Parser.Model.model (generateModel modelAst) `shouldBe` Right modelAst
+ Parsec.parse Wasp.Psl.Parser.Model.model "" (generateModel modelAst) `shouldBe` Right modelAst
instance Arbitrary AST.Model where
arbitrary = AST.Model <$> arbitraryIdentifier <*> arbitrary
diff --git a/waspc/test/Psl/Parser/ModelTest.hs b/waspc/test/Psl/Parser/ModelTest.hs
index 043e72dbc..cf998aba5 100644
--- a/waspc/test/Psl/Parser/ModelTest.hs
+++ b/waspc/test/Psl/Parser/ModelTest.hs
@@ -3,7 +3,7 @@ module Psl.Parser.ModelTest where
import Data.Either (isLeft)
import Psl.Common.ModelTest (sampleBodyAst, sampleBodySchema)
import Test.Tasty.Hspec
-import Wasp.Parser.Common (runWaspParser)
+import qualified Text.Parsec as Parsec
import qualified Wasp.Psl.Ast.Model as AST
import Wasp.Psl.Parser.Model (attrArgument, body, model)
@@ -14,14 +14,14 @@ spec_parsePslModel = do
expectedModelAst = AST.Model "User" sampleBodyAst
it "Body parser correctly parses" $ do
- runWaspParser body sampleBodySchema `shouldBe` Right sampleBodyAst
+ Parsec.parse body "" sampleBodySchema `shouldBe` Right sampleBodyAst
it "Model parser correctly parses" $ do
- runWaspParser model pslModel `shouldBe` Right expectedModelAst
+ Parsec.parse model "" pslModel `shouldBe` Right expectedModelAst
describe "Body parser" $ do
describe "Fails if input is not valid PSL" $ do
- let runTest psl = it psl $ isLeft (runWaspParser body psl) `shouldBe` True
+ let runTest psl = it psl $ isLeft (Parsec.parse body "" psl) `shouldBe` True
mapM_
runTest
[ " noType",
@@ -51,5 +51,5 @@ spec_parsePslModel = do
)
]
let runTest (psl, expected) =
- it ("correctly parses " ++ psl) $ runWaspParser attrArgument psl `shouldBe` Right expected
+ it ("correctly parses " ++ psl) $ Parsec.parse attrArgument "" psl `shouldBe` Right expected
mapM_ runTest tests
diff --git a/waspc/test/UtilTest.hs b/waspc/test/UtilTest.hs
index 75ddf5bba..3eb92de9f 100644
--- a/waspc/test/UtilTest.hs
+++ b/waspc/test/UtilTest.hs
@@ -91,3 +91,25 @@ spec_concatPrefixAndText = do
concatPrefixAndText "prefix: " "foo" `shouldBe` "prefix: foo"
it "put all the text below the prefix, indented for 2 spaces, if text has multiple lines" $ do
concatPrefixAndText "prefix: " "foo\nbar" `shouldBe` "prefix: \n foo\n bar"
+
+spec_leftPad :: Spec
+spec_leftPad = do
+ describe "leftPad should" $ do
+ it "pad the list if it is shorter than desired length" $ do
+ leftPad ' ' 5 "hi" `shouldBe` " hi"
+ it "not modify the list if it is already long enough" $ do
+ leftPad ' ' 5 "hihih" `shouldBe` "hihih"
+ leftPad ' ' 5 "hihihi" `shouldBe` "hihihi"
+
+spec_insertAt :: Spec
+spec_insertAt = do
+ describe "insertAt should" $ do
+ it "insert given list at the start of host list if index is 0 or negative" $ do
+ insertAt [0] 0 [1, 2, 3] `shouldBe` ([0, 1, 2, 3] :: [Int])
+ insertAt [0] (-1) [1, 2, 3] `shouldBe` ([0, 1, 2, 3] :: [Int])
+ it "insert given list in the host list at given index when index is in [1, host list length - 1]" $ do
+ insertAt [0] 1 [1, 2, 3] `shouldBe` ([1, 0, 2, 3] :: [Int])
+ insertAt [0] 2 [1, 2, 3] `shouldBe` ([1, 2, 0, 3] :: [Int])
+ it "insert given list at the end of host list if index is equal or bigger than host list length" $ do
+ insertAt [0] 3 [1, 2, 3] `shouldBe` ([1, 2, 3, 0] :: [Int])
+ insertAt [0] 4 [1, 2, 3] `shouldBe` ([1, 2, 3, 0] :: [Int])
diff --git a/web/blog/2021-12-02-waspello.md b/web/blog/2021-12-02-waspello.md
index 4e81f57bf..f2ebde948 100644
--- a/web/blog/2021-12-02-waspello.md
+++ b/web/blog/2021-12-02-waspello.md
@@ -138,7 +138,7 @@ page Main {
}
```
-All pretty straightforward so far! As you can see here, Wasp also provides [authentication out-of-the-box](/docs/language/basic-elements#authentication--authorization).
+All pretty straightforward so far! As you can see here, Wasp also provides [authentication out-of-the-box](/docs/language/features#authentication--authorization).
Currently, the majority of the client logic of Waspello is contained in `ext/MainPage.js` (we should break it down a little 😅 - [you can help us!](https://github.com/wasp-lang/wasp/issues/334)). Just to give you an idea, here's a quick glimpse into it:
@@ -162,7 +162,7 @@ const MainPage = ({ user }) => {
)
}
```
-Once you've defined a query or action as described above, you can immediately import it into your client code as shown in the code sample, by using the `@wasp` prefix in the import path. `useQuery` ensures reactivity so once the data changes the query will get re-fetched. You can find more details about it [here](/docs/language/basic-elements#usequery).
+Once you've defined a query or action as described above, you can immediately import it into your client code as shown in the code sample, by using the `@wasp` prefix in the import path. `useQuery` ensures reactivity so once the data changes the query will get re-fetched. You can find more details about it [here](/docs/language/features#usequery).
This is pretty much it from the stuff that works 😄 ! I kinda rushed a bit through things here - for more details on all Wasp features and to build your first app with Wasp, check out our [docs](/docs/).
diff --git a/web/docs/deploying.md b/web/docs/deploying.md
index cda561033..99cb1c1f9 100644
--- a/web/docs/deploying.md
+++ b/web/docs/deploying.md
@@ -18,7 +18,7 @@ wasp build
```
generates deployable code for the whole app in the `.wasp/build/` directory. Next, we will deploy this code.
-NOTE: You will not be able to build the app if you are using SQLite as a database (which is a default database) -> you will have to [switch to PostgreSQL](/docs/language/basic-elements#migrating-from-sqlite-to-postgresql).
+NOTE: You will not be able to build the app if you are using SQLite as a database (which is a default database) -> you will have to [switch to PostgreSQL](/docs/language/features#migrating-from-sqlite-to-postgresql).
# Deploying API server (backend)
In `.wasp/build/`, there is a `Dockerfile` describing an image for building the server.
diff --git a/web/docs/language/basic-elements.md b/web/docs/language/features.md
similarity index 80%
rename from web/docs/language/basic-elements.md
rename to web/docs/language/features.md
index 0d0bd62c6..83f26edf3 100644
--- a/web/docs/language/basic-elements.md
+++ b/web/docs/language/features.md
@@ -1,52 +1,68 @@
---
-title: Basic Elements
+title: Features
---
## App
-There can be only one `App` element per Wasp project. It serves as a starting point and defines global
-properties of your app. Currently, it is very simple:
+There can be only one declaration of `app` type per Wasp project.
+It serves as a starting point and defines global properties of your app.
```css
app todoApp {
- title: "ToDo App",
- head: [
- ""
- ]
+ title: "ToDo App",
+ head: [ // optional
+ ""
+ ]
}
```
-#### `app: identifier`
-Name of your app.
+### Fields
-#### `title: string`
+#### `title: string` (required)
Title of your app. It will be displayed in the browser tab, next to the favicon.
-#### `head: array of strings`
+#### `head: [string]` (optional)
Head of your HTML Document. Your app's metadata (styles, links, etc) can be added here.
+#### `auth: dict` (optional)
+Authentication and authorization configuration.
+Check [`app.auth`](/docs/language/features#authentication--authorization) for more details.
+
+#### `db: dict` (optional)
+Database configuration.
+Check [`app.db`](/docs/language/features#database) for more details.
+
+#### `server: dict` (optional)
+Server configuration.
+Check [`app.server`](/docs/language/features#server) for more details.
+
+#### `dependencies: [(string, string)]` (optional)
+List of dependencies (external libraries).
+Check [`app.dependencies`](/docs/language/features#dependencies) for more details.
## Page
-`Page` is the top-level layout abstraction. Your app can have multiple pages, and they are defined in Wasp
-as follows:
+`page` declaration is the top-level layout abstraction. Your app can have multiple pages.
+
```css
-page Main {
- component: import Main from "@ext/pages/Main",
- authRequired: false // optional property
+page MainPage {
+ component: import Main from "@ext/pages/Main",
+ authRequired: false // optional
}
```
-#### `page: identifier`
-Name of the page.
+Normally you will also want to associate `page` with a `route`, otherwise it won't be accessible in the app.
-#### `component: js import statement`
-Import statement of the page React element. See importing external code for details.
+### Fields
-`Page` also has to be associated with a `Route`, otherwise it won't be accessible in the app.
+#### `component: ExtImport` (required)
+Import statement of the React element that implements the page component.
+See importing external code for details.
-#### `authRequired: bool`
-Optional property - can be specified only if [`auth`](/docs/language/basic-elements#authentication--authorization) is declared. If set to `true`, only authenticated users will be able to access this page. Unauthenticated users will be redirected to a route declared by `onAuthFailedRedirectTo` property within `auth`.
+#### `authRequired: bool` (optional)
+Can be specified only if [`app.auth`](/docs/language/features#authentication--authorization) is defined.
+
+If set to `true`, only authenticated users will be able to access this page. Unauthenticated users will be redirected to a route defined by `onAuthFailedRedirectTo` property within `app.auth`.
If `authRequired` is set to `true`, the React component of a page (specified by `component` property) will be provided `user` object as a prop.
@@ -54,21 +70,25 @@ Check out this [section of our Todo app tutorial](/docs/tutorials/todo-app/auth#
## Route
-Using `Route` element is a way to implement routing functionality in Wasp:
+`route` declaration provides top-level routing functionality in Wasp.
+
```css
-route "/about" -> page About
+route AboutRoute { path: "/about", to: AboutPage }
```
-#### `route: string`
-URL path of the route. Route string can be parametrised and follows the same conventions as
+### Fields
+
+#### `path: string` (required)
+URL path of the route. Route path can be parametrised and follows the same conventions as
[React Router](https://reactrouter.com/web/).
-#### `page: page identifier`
-Page identifier of the route's target. Referenced page must be defined somewhere in `.wasp` file.
+#### `to: page` (required)
+Name of the `page` to which the path will lead.
+Referenced page must be defined somewhere in `.wasp` file.
### Example - parametrised URL path
```css
-route "/task/:id" -> page Task
+route TaskRoute { path: "/task/:id", to: TaskPage }
```
For details on URL path format check [React Router](https://reactrouter.com/web/)
documentation.
@@ -81,9 +101,9 @@ started:
```c title="todoApp.wasp"
// ...
-route "/task/:id" -> page Task
-page Task {
- component: import Task from "@ext/pages/Task"
+route TaskRoute { path: "/task/:id", to: TaskPage }
+page TaskPage {
+ component: import Task from "@ext/pages/Task"
}
```
@@ -107,9 +127,9 @@ Navigation can be performed from the React code via `` component, also us
```c title="todoApp.wasp"
// ...
-route "/home" -> page Home
-page Home {
- component: import Home from "@ext/pages/Home"
+route HomeRoute { path: "/home", to: HomePage }
+page HomePage {
+ component: import Home from "@ext/pages/Home"
}
```
@@ -126,10 +146,10 @@ const OtherPage = (props) => {
## Entity
-`Entity` element represents a database model. Wasp uses [Prisma](https://www.prisma.io/) to implement
-database functionality and currently provides only a thin layer above it.
+`entity` declaration represents a database model.
+Wasp uses [Prisma](https://www.prisma.io/) to implement database functionality and currently provides only a thin layer above it.
-Each `Entity` element corresponds 1-to-1 to Prisma data model and is defined in a following way:
+Each `Entity` declaration corresponds 1-to-1 to Prisma data model and is defined in a following way:
```css
entity Task {=psl
@@ -138,10 +158,8 @@ entity Task {=psl
isDone Boolean @default(false)
psl=}
```
-#### `entity: identifier`
-Name of the entity.
-#### `{=psl ... psl=}: PSL`
+### `{=psl ... psl=}: PSL`
Definition of entity fields in *Prisma Schema Language* (PSL). See
[here for intro and examples](https://www.prisma.io/docs/reference/tools-and-interfaces/prisma-schema)
and [here for a more exhaustive language specification](https://github.com/prisma/specs/tree/master/schema).
@@ -168,13 +186,18 @@ Queries are NodeJS functions that don't modify any state. Normally they fetch ce
To create a Wasp Query, we need two things: declaration in Wasp and implementation in NodeJS:
+1. `query` declaration in Wasp:
```c title="main.wasp"
// ...
query getTasks {
fn: import { getAllTasks } from "@ext/foo.js"
}
```
+`query` declaration type has two fields:
+- `fn: ExtImport` (required)
+- `entities: [Entity]` (optional)
+2. Implemenation in NodeJS:
```js title="ext/foo.js"
// ...
export getAllTasks = async (args, context) => {
@@ -291,13 +314,17 @@ More differences and action/query specific features will come in the future vers
## Dependencies
-You can specify additional npm dependencies in following way, in your `*.wasp` file:
+You can specify additional npm dependencies via `dependencies` field in `app` declaration, in following way:
```c
-dependencies {=json
- "redux": "^4.0.5",
- "react-redux": "^7.1.3"
-json=}
+app MyApp {
+ title: "My app",
+ // ...
+ dependencies: [
+ ("redux", "^4.0.5"),
+ ("react-redux", "^7.1.3")
+ ]
+)
```
You will need to re-run `wasp start` after adding a dependency for Wasp to pick it up.
@@ -310,29 +337,41 @@ In the future, we will add support for picking any version you like, but we have
## Authentication & Authorization
-Wasp provides authentication and authorization support out-of-the-box. Enabling it for your app is optional and can be done by adding `auth` element to your `.wasp` file:
+Wasp provides authentication and authorization support out-of-the-box. Enabling it for your app is optional and can be done by configuring `auth` field of the `app` declaration:
```css
-auth {
+app MyApp {
+ title: "My app",
+ // ...
+ auth: {
userEntity: User,
methods: [ EmailAndPassword ],
onAuthFailedRedirectTo: "/someRoute"
+ }
}
```
-`userEntity: entity`
+
+`app.auth` is a dictionary with following fields:
+
+#### `userEntity: entity` (required)
Entity which represents the user (sometimes also referred to as *Principal*).
-`methods: [AuthMethod]`
+#### `methods: [AuthMethod]` (required)
List of authentication methods that Wasp app supports. Currently supported methods are:
* `EmailAndPassword`: Provides support for authentication with email address and a password.
-`onAuthFailedRedirectTo: String` Name of the route where an unauthenticated user will be redirected to if they try to access a private page (which is declared by setting `authRequired: true` for a specific page).
+#### `onAuthFailedRedirectTo: String` (required)
+Path where an unauthenticated user will be redirected to if they try to access a private page (which is declared by setting `authRequired: true` for a specific page).
Check out this [section of our Todo app tutorial](/docs/tutorials/todo-app/auth#updating-main-page-to-check-if-user-is-authenticated) to see an example of usage.
+#### `onAuthSucceededRedirectTo: String` (optional)
+Path where a successfully authenticated user will be sent upon successful login/signup.
+Default value is "/".
+
### Email and Password
`EmailAndPassword` authentication method makes it possible to signup/login into the app by using email address and a password.
-This method requires that `userEntity` specified in `auth` element contains `email: string` and `password: string` fields.
+This method requires that `userEntity` specified in `auth` contains `email: string` and `password: string` fields.
We provide basic validations out of the box, which you can customize as shown below. Default validations are:
- `email`: non-empty
@@ -550,23 +589,21 @@ should be denied access to it.
## Server configuration
-```css
-server {
- ...
+Via `server` field of `app` declaration, you can configure behaviour of the Node.js server (one that is executing wasp operations).
+
+```c
+app MyApp {
+ title: "My app",
+ // ...
+ server: {
+ setupFn: import mySetupFunction from "@ext/myServerSetupCode.js"
+ }
}
```
-With `server` declaration, you can configure behaviour of the Node.js server (one that is executing wasp operations).
+`app.server` is a dictionary with following fields:
-Currently `server` supports only one option, `setupFn`, but there will likely be more options added in the future.
-
-### setupFn
-
-```css
-server {
- setupFn: import mySetupFunction from "@ext/myServerSetupCode.js"
-}
-```
+#### `setupFn: ExtImport` (optional)
`setupFn` declares a JS function that will be executed on server start. This function is expected to be async and will be awaited before server continues with its setup and starts serving any requests.
@@ -630,26 +667,31 @@ console.log(process.env.DATABASE_URL)
## Database
-You can specify database to be used by Wasp via `db` element (there can be only one such element per Wasp project):
+Via `db` field of `app` declaration, you can configure the database used by Wasp.
-```css
-db {
- system: PostgreSQL
+```c
+app MyApp {
+ title: "My app",
+ // ...
+ db: {
+ system: PostgreSQL
+ }
}
```
-If you don't have `db` block, default database is used (which is `SQLite`).
-If you create or modify `db` declaration, run `wasp db migrate-dev` to apply the changes.
+`app.db` is a dictionary with following fields:
-#### `system: identifier`
-Database system that Wasp will use. It can be either `PostgreSQL` or `SQLite`.
+#### `system: DbSystem`
+Database system that Wasp will use. It can be either `PostgreSQL` or `SQLite`.
+If not defined, or even if whole `db` field is not present, default value is `SQLite`.
+If you add/remove/modify `db` field, run `wasp db migrate-dev` to apply the changes.
### SQLite
Default database is `SQLite`, since it is great for getting started with a new project (needs no configuring), but it can be used only in development - once you want to deploy Wasp to production you will need to switch to `PostgreSQL` and stick with it.
Check below for more details on how to migrate from SQLite to PostgreSQL.
### PostgreSQL
-When using `PostgreSQL` (`db { system: PostgreSQL }`), you will need to spin up a postgres database on your own so it runs during development (when running `wasp start` or doing `wasp db ...` commands) and provide Wasp with `DATABASE_URL` environment variable that Wasp will use to connect to it.
+When using `PostgreSQL` (`db: { system: PostgreSQL }`), you will need to spin up a postgres database on your own so it runs during development (when running `wasp start` or doing `wasp db ...` commands) and provide Wasp with `DATABASE_URL` environment variable that Wasp will use to connect to it.
One of the easiest ways to do this is by spinning up postgres docker container when you need it with the shell command
```
@@ -668,6 +710,6 @@ to the `.env` file in the root directory of your Wasp project.
### Migrating from SQLite to PostgreSQL
To run Wasp app in production, you will need to switch from `SQLite` to `PostgreSQL`.
-1. Set `db.system` to `PostgreSQL` and set `DATABASE_URL` env var accordingly (as described [above](/docs/language/basic-elements#postgresql)).
+1. Set `app.db.system` to `PostgreSQL` and set `DATABASE_URL` env var accordingly (as described [above](/docs/language/features#postgresql)).
2. Delete old migrations, since they are SQLite migrations and can't be used with PostgreSQL: `rm -r migrations/`.
-3. Run `wasp db migrate-dev` to apply new changes and create new, initial migration. You will need to have your postgres database running while doing this (check [above](/docs/language/basic-elements#postgresql) for easy way to get it running).
+3. Run `wasp db migrate-dev` to apply new changes and create new, initial migration. You will need to have your postgres database running while doing this (check [above](/docs/language/features#postgresql) for easy way to get it running).
diff --git a/web/docs/language/overview.md b/web/docs/language/overview.md
index 75db6d1ba..8a95ab565 100644
--- a/web/docs/language/overview.md
+++ b/web/docs/language/overview.md
@@ -3,7 +3,7 @@ title: Overview
---
Wasp is a declarative language that recognizes web application-specific terms (e.g. *page* or *route*) as
-words of the language.
+words (types) of the language.
The basic idea is that the higher-level overview of an app (e.g. pages, routes, database model, ...) is defined in `*.wasp` files (for now just one), while the specific parts (web components, back-end queries, ...) are implemented in specific non-wasp technologies (React, NodeJS, Prisma) and then referenced in the `*.wasp` files.
@@ -29,8 +29,8 @@ app todoApp {
title: "ToDo App"
}
-route "/" -> page Main
-page Main {
+route RootRoute { path: "/", to: MainPage }
+page MainPage {
component: import Main from "@ext/pages/Main"
}
@@ -53,4 +53,4 @@ psl=}
You can check out a full working example [here](https://github.com/wasp-lang/wasp/tree/master/waspc/examples/todoApp).
-In the following sections each of these basic language elements is explained.
+In the following sections each of the basic language features is explained.
diff --git a/web/docs/language/syntax.md b/web/docs/language/syntax.md
new file mode 100644
index 000000000..e9a71c873
--- /dev/null
+++ b/web/docs/language/syntax.md
@@ -0,0 +1,76 @@
+---
+title: Syntax
+---
+
+Wasp is a declarative, statically typed, domain specific language (DSL).
+
+## Declarations
+
+The central point of Wasp language are **declarations**, and Wasp source is at the end just a bunch of declarations, each of them describing a part of your web app.
+
+```c
+app MyApp {
+ title: "My app"
+}
+
+route RootRoute { path: "/", to: DashboardPage }
+
+page DashboardPage {
+ component: import Dashboard from "@ext/Dashboard.js"
+}
+```
+
+In the example above we described a web app via three declarations: `app MyApp { ... }`, `route RootRoute { ... }` and `page DashboardPage { ... }`.
+
+Syntax for writing a declaration is ``, where:
+- `` is one of the declaration types offered by Wasp (`app`, `route`, ...)
+- `` is an identifier chosen by you to name this specific declaration
+- `` is the value/definition of the declaration itself, which has to match the specific declaration body type determined by the chosen declaration type.
+
+So, for `app` declaration above, we have:
+- declaration type `app`
+- declaration name `MyApp` (we could have used any other identifier, like `foobar`, `foo_bar`, or `hi3Ho`)
+- declaration body `{ title: "My app" }`, which is a dictionary with field `title` that has string value.
+ Type of this dictionary is in line with the declaration body type of the `app` declaration type.
+ If we provided something else, e.g. changed `title` to `little`, we would get a type error from Wasp compiler since that does not match the expected type of the declaration body for `app`.
+
+Each declaration has a meaning behind it that describes how your web app should behave and function.
+
+All the other types in Wasp language (primitive types (`string`, `number`), composite types (`dict`, `list`), enum types (`DbSystem`), ...) are used to define the declaration bodies.
+
+## Complete list of Wasp types
+Wasp's type system can be divided into two main categories of types: **fundamental types** and **domain types**.
+
+While fundamental types are here to be basic building blocks of a language, and are very similar to what you would see in other popular lanuages, domain types are what makes Wasp special, as they model the concepts of a web app like `page`, `route` and similar.
+
+- Fundamental types ([source of truth](https://github.com/wasp-lang/wasp/blob/master/waspc/src/Wasp/Analyzer/Type.hs))
+ - Primitive types
+ - **string** (`"foo"`, `"they said: \"hi\""`)
+ - **bool** (`true`, `false`)
+ - **number** (`12`, `14.5`)
+ - **declaration reference** (name of existing declaration: `TaskPage`, `updateTask`)
+ - **ExtImport** (external import) (`import Foo from "@ext/bar.js"`, `import { Smth } from "@ext/a/b.js"`)
+ - path has to be relative and start with "@ext". It is considered to be relative to the `ext/` directory.
+ - import has to be a default import `import Foo` or a single named import `import { Foo }`.
+ - **json** (`{=json { a: 5, b: ["hi"] } json=}`)
+ - **psl** (Prisma Schema Language) (`{=psl psl=}`)
+ - Check [Prisma docs](https://www.prisma.io/docs/concepts/components/prisma-schema/data-model) for the syntax of psl data model.
+ - Composite types
+ - **dict** (dictionary) (`{ a: 5, b: "foo" }`)
+ - **list** (`[1, 2, 3]`)
+ - **tuple** (`(1, "bar")`, `(2, 4, true)`)
+ - Tuples can be of size 2, 3 and 4.
+- Domain types ([source of truth](https://github.com/wasp-lang/wasp/blob/master/waspc/src/Wasp/Analyzer/StdTypeDefinitions.hs))
+ - Declaration types
+ - **app**
+ - **page**
+ - **route**
+ - **query**
+ - **action**
+ - **entity**
+ - Enum types
+ - **AuthMethod**
+ - **DbSystem**
+
+
+For more details about each of the domain types, both regarding their body types and what they mean, check the [Features](/language/features.md) section.
diff --git a/web/docs/tutorials/todo-app/auth.md b/web/docs/tutorials/todo-app/auth.md
index ba63ad492..f14a89306 100644
--- a/web/docs/tutorials/todo-app/auth.md
+++ b/web/docs/tutorials/todo-app/auth.md
@@ -33,18 +33,19 @@ wasp db migrate-dev
```
to propagate the schema change (we added User).
-## Defining `auth` declaration
-Next, we want to tell Wasp that we want full-stack [authentication](language/basic-elements.md#authentication--authorization) in our app, and that it should use entity `User` for it:
+## Defining `app.auth`
+Next, we want to tell Wasp that we want full-stack [authentication](language/features.md#authentication--authorization) in our app, and that it should use entity `User` for it:
-```c title="main.wasp"
-// ...
+```c {4-9} title="main.wasp"
+app TodoApp {
+ title: "Todo app",
-auth {
- // Expects entity User to have (email:String) and (password:String) fields.
- userEntity: User,
- methods: [ EmailAndPassword ], // More methods coming soon!
-
- onAuthFailedRedirectTo: "/login" // We'll see how this is used a bit later
+ auth: {
+ // Expects entity User to have (email:String) and (password:String) fields.
+ userEntity: User,
+ methods: [ EmailAndPassword ], // More methods coming soon!
+ onAuthFailedRedirectTo: "/login" // We'll see how this is used a bit later
+ }
}
```
What this means for us is that Wasp now offers us:
@@ -55,29 +56,29 @@ What this means for us is that Wasp now offers us:
This is a very high-level API for auth which makes it very easy to get started quickly, but is
not very flexible. If you require more control (e.g. want to execute some custom code on the server
-during signup, check out [lower-level auth API](/docs/language/basic-elements#lower-level-api).
+during signup, check out [lower-level auth API](/docs/language/features#lower-level-api).
Ok, that was easy!
-To recap, so far we have created:
+To recap, so far we have defined:
- `User` entity.
-- `auth` declaration thanks to which Wasp gives us plenty of auth functionality.
+- `app.auth` field, thanks to which Wasp gives us plenty of auth functionality.
## Adding Login and Signup pages
-When we declared `auth` we got login and signup forms generated for us, but now we have to use them in their pages. In our `main.wasp` we'll add the following:
+When we defined `app.auth` we got login and signup forms generated for us, but now we have to use them in their pages. In our `main.wasp` file we'll add the following:
```c title="main.wasp"
// ...
-route "/signup" -> page Signup
-page Signup {
- component: import Signup from "@ext/SignupPage"
+route SignupRoute { path: "/signup", to: SignupPage }
+page SignupPage {
+ component: import Signup from "@ext/SignupPage"
}
-route "/login" -> page Login
-page Login {
- component: import Login from "@ext/LoginPage"
+route LoginRoute { path: "/login", to: LoginPage }
+page LoginPage {
+ component: import Login from "@ext/LoginPage"
}
```
@@ -128,22 +129,22 @@ export default SignupPage
```
-## Updating Main page to check if user is authenticated
+## Updating `MainPage` page to check if user is authenticated
-Now, let's see how are we going to handle the situation when user is not logged in. `Main` page is a private
-page and we want users to be able to see it only if they are authenticated.
+Now, let's see how are we going to handle the situation when user is not logged in.
+`MainPage` page is a private page and we want users to be able to see it only if they are authenticated.
There is a specific Wasp feature that allows us to achieve this in a simple way:
```c {3} title="main.wasp"
// ...
-page Main {
+page MainPage {
authRequired: true,
component: import Main from "@ext/MainPage.js"
}
```
-With `authRequired: true` we declared that page `Main` is accessible only to the authenticated users.
-If an unauthenticated user tries to access route `/` where our page `Main` is, they will be redirected to `/login` as specified with `onAuthFailedRedirectTo` property in `auth`.
+With `authRequired: true` we declared that page `MainPage` is accessible only to the authenticated users.
+If an unauthenticated user tries to access route `/` where our page `MainPage` is, they will be redirected to `/login` as specified with `onAuthFailedRedirectTo` property in `app.auth`.
Also, when `authRequired` is set to `true`, the React component of a page (specified by `component` property within `page`) will be provided `user` object as a prop. It can be accessed like this:
diff --git a/web/docs/tutorials/todo-app/creating-new-project.md b/web/docs/tutorials/todo-app/creating-new-project.md
index e7dc8f7ed..1c52163b8 100644
--- a/web/docs/tutorials/todo-app/creating-new-project.md
+++ b/web/docs/tutorials/todo-app/creating-new-project.md
@@ -45,18 +45,18 @@ TodoApp/
```
Let's start with `main.wasp` file which introduces 3 new concepts:
-[app](language/basic-elements.md#app),
-[page](language/basic-elements.md#page) and
-[route](language/basic-elements.md#route).
+[app](language/features.md#app),
+[page](language/features.md#page) and
+[route](language/features.md#route).
```c title="main.wasp"
app TodoApp { // Main declaration, defines a new web app.
- title: "TodoApp" // Used as a browser tab title.
+ title: "Todo app" // Used as a browser tab title.
}
-route "/" -> page Main // Render page Main on url `/` (default url).
+route RootRoute { path: "/", to: MainPage } // Render page MainPage on url `/` (default url).
-page Main {
+page MainPage {
// We specify that ReactJS implementation of our page can be
// found in `ext/MainPage.js` as a default export (uses standard
// js import syntax).
@@ -64,7 +64,7 @@ page Main {
}
```
-And now to that React component we referenced in the `page Main { ... }` declaration in `main.wasp`:
+And now to that React component we referenced in the `page MainPage { ... }` declaration in `main.wasp`:
```jsx title="ext/MainPage.js"
import React from 'react'
import waspLogo from './waspLogo.png'
diff --git a/web/docs/tutorials/todo-app/dependencies.md b/web/docs/tutorials/todo-app/dependencies.md
index e7d6117cd..1bf2f993f 100644
--- a/web/docs/tutorials/todo-app/dependencies.md
+++ b/web/docs/tutorials/todo-app/dependencies.md
@@ -6,15 +6,19 @@ import useBaseUrl from '@docusaurus/useBaseUrl';
What is a Todo app without some clocks!? Well, still a Todo app, but certainly not as fun as one with the clocks!
-So, let's add a couple of clocks to our app, to help us track time while we perform our tasks (and to demonstrate `dependencies` feature).
+So, let's add a couple of clocks to our app, to help us track time while we perform our tasks (and to demonstrate `app.dependencies` feature).
-For this, we will use `react-clock` library from NPM. We can add it to our project as a [dependency](language/basic-elements.md#dependencies) like this:
-```c title="main.wasp"
-// ...
+For this, we will use `react-clock` library from NPM. We can add it to our project as a [dependency](language/features.md#dependencies) like this:
+```c {6-8} title="main.wasp"
+app TodoApp {
+ title: "Todo app",
-dependencies {=json
- "react-clock": "3.0.0"
-json=}
+ // ...
+
+ dependencies: [
+ ("react-clock", "3.0.0")
+ ]
+}
```
Run (if it is already running, stop it first and then run it again)
diff --git a/web/docs/tutorials/todo-app/listing-tasks.md b/web/docs/tutorials/todo-app/listing-tasks.md
index 0141ed8ef..2fa4c0999 100644
--- a/web/docs/tutorials/todo-app/listing-tasks.md
+++ b/web/docs/tutorials/todo-app/listing-tasks.md
@@ -8,7 +8,7 @@ We want to admire our tasks, so let's list them!
## Introducing operations (queries and actions)
-The primary way of interacting with entities in Wasp is via [operations (queries and actions)](language/basic-elements.md#queries-and-actions-aka-operations).
+The primary way of interacting with entities in Wasp is via [operations (queries and actions)](language/features.md#queries-and-actions-aka-operations).
Queries are here when we need to fetch/read something, while actions are here when we need to change/update something.
We will start with writing a query, since we are just listing tasks and not modifying anything for now.
@@ -19,7 +19,7 @@ To list tasks, we will need two things:
## Wasp query
-Let's implement `getTasks` [query](language/basic-elements.md#query).
+Let's implement `getTasks` [query](language/features.md#query).
It consists of a declaration in Wasp and implementation in JS (in `ext/` directory).
### Wasp declaration
@@ -100,7 +100,7 @@ export default MainPage
All of this is just regular React, except for the two special `@wasp` imports:
- `import getTasks from '@wasp/queries/getTasks'`: provides us with our freshly defined Wasp query.
- - `import { useQuery } from '@wasp/queries'`: provides us with Wasp's [useQuery](language/basic-elements.md#usequery) React hook which is actually just a thin wrapper over [react-query](https://github.com/tannerlinsley/react-query) [useQuery](https://react-query.tanstack.com/docs/guides/queries) hook, behaving very similarly while offering some extra integration with Wasp.
+ - `import { useQuery } from '@wasp/queries'`: provides us with Wasp's [useQuery](language/features.md#usequery) React hook which is actually just a thin wrapper over [react-query](https://github.com/tannerlinsley/react-query) [useQuery](https://react-query.tanstack.com/docs/guides/queries) hook, behaving very similarly while offering some extra integration with Wasp.
While we could call query directly as `getTasks()`, calling it as `useQuery(getTasks)` gives us the reactivity (React component gets re-rendered if result of the query changes).
diff --git a/web/docs/tutorials/todo-app/task-entity.md b/web/docs/tutorials/todo-app/task-entity.md
index a751f65cb..1d08bf91e 100644
--- a/web/docs/tutorials/todo-app/task-entity.md
+++ b/web/docs/tutorials/todo-app/task-entity.md
@@ -4,7 +4,7 @@ title: "Task entity"
import useBaseUrl from '@docusaurus/useBaseUrl';
-[Entities](language/basic-elements.md#entity) are one of the very central concepts in Wasp, and they mainly play the role of data models.
+[Entities](language/features.md#entity) are one of the very central concepts in Wasp, and they mainly play the role of data models.
Since our TodoApp is all about tasks, we will define Task entity in Wasp:
```c title="main.wasp"
diff --git a/web/docusaurus.config.js b/web/docusaurus.config.js
index 7c9c4e302..b4a80dbbe 100644
--- a/web/docusaurus.config.js
+++ b/web/docusaurus.config.js
@@ -71,7 +71,7 @@ module.exports = {
},
{
label: 'Reference',
- to: 'docs/language/basic-elements'
+ to: 'docs/language/features'
}
]
},
diff --git a/web/sidebars.js b/web/sidebars.js
index de5f71007..a8edf79e0 100644
--- a/web/sidebars.js
+++ b/web/sidebars.js
@@ -46,7 +46,8 @@ module.exports = {
collapsed: false,
items: [
'language/overview',
- 'language/basic-elements'
+ 'language/syntax',
+ 'language/features'
]
},
'cli',
diff --git a/web/src/pages/index.js b/web/src/pages/index.js
index 54282c707..980879075 100644
--- a/web/src/pages/index.js
+++ b/web/src/pages/index.js
@@ -121,19 +121,19 @@ function HeroCodeExample() {
const createAppWaspCode =
`app todoApp {
- title: "ToDo App" /* visible in tab */
+ title: "ToDo App", /* visible in tab */
+
+ auth: { /* full-stack auth out-of-the-box */
+ userEntity: User,
+ methods: [ EmailAndPassword ],
+ }
}
-route "/" -> page Main
-page Main {
+route RootRoute { path: "/", to: MainPage }
+page MainPage {
/* import your React code */
component: import Main from "@ext/Main.js"
}
-
-auth { /* full-stack auth out-of-the-box */
- userEntity: User,
- methods: [ EmailAndPassword ],
-}
`
return (
@@ -162,8 +162,8 @@ app todoApp {
}
/* routing */
-route "/" -> page Main
-page Main {
+route RootRoute { path: "/", to: MainPage }
+page MainPage {
component: import Main from "@ext/Main" /* import your React code */
}
`
@@ -193,15 +193,19 @@ export default () => Hello World!
)
} else if (currentCodeExample === CodeExample.ADD_AUTH) {
const exampleCode =
-`/* ... */
+`app todoApp {
+ /* ... */
-/* full-stack auth out-of-the-box */
-auth {
- userEntity: User,
- methods: [ EmailAndPassword ], /* more methods coming soon */
- onAuthFailedRedirectTo: "/login"
+ /* full-stack auth out-of-the-box */
+ auth: {
+ userEntity: User,
+ methods: [ EmailAndPassword ], /* more methods coming soon */
+ onAuthFailedRedirectTo: "/login"
+ }
}
+/* ... */
+
/* email & password required because of the auth method above */
entity User {=psl
id Int @id @default(autoincrement())
@@ -209,7 +213,7 @@ entity User {=psl
password String
psl=}
-page Main {
+page MainPage {
authRequired: true, /* available only to logged in users */
component: import Main from "@ext/Main"
}
@@ -236,7 +240,7 @@ export default ({ user }) => {
To learn more about authentication & authorization in Wasp, check
- the docs.
+ the docs.
)
@@ -292,7 +296,7 @@ export default () => {
To learn more about working with data in Wasp, check
- the docs.
+ the docs.