mirror of
https://github.com/wasp-lang/wasp.git
synced 2024-08-17 03:00:59 +03:00
Replace Wasp with AppSpec and Parser with Analyzer. (#423)
This commit is contained in:
parent
b74eb88ff0
commit
8d2c33ab28
@ -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 {
|
||||
|
@ -3,55 +3,64 @@ app Conduit {
|
||||
|
||||
head: [
|
||||
"<link rel=\"stylesheet\" href=\"https://fonts.googleapis.com/css?family=Roboto:300,400,500&display=swap\" />"
|
||||
],
|
||||
|
||||
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=}
|
||||
|
@ -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"
|
||||
}
|
||||
|
||||
@ -80,8 +80,3 @@ entity User {=psl
|
||||
thoughts Thought[]
|
||||
tags Tag[]
|
||||
psl=}
|
||||
|
||||
dependencies {=json
|
||||
"react-markdown": "6.0.1",
|
||||
"color-hash": "2.0.1"
|
||||
json=}
|
@ -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=}
|
||||
|
@ -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)
|
||||
+}
|
||||
+
|
||||
```
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
@ -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?
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -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?
|
||||
}
|
||||
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
@ -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");
|
@ -1,4 +0,0 @@
|
||||
# Prisma Migrate lockfile v1
|
||||
|
||||
20201023121126-a
|
||||
20201023121536-b
|
@ -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"
|
@ -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`).
|
||||
|
@ -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\"",
|
||||
"}"
|
||||
]
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -12,7 +12,7 @@ const MainPage = () => {
|
||||
|
||||
<h2 className="welcome-title"> Welcome to Wasp - you just started a new app! </h2>
|
||||
<h3 className="welcome-subtitle">
|
||||
This is page <code>Main</code> located at route <code>/</code>.
|
||||
This is page <code>MainPage</code> located at route <code>/</code>.
|
||||
Open <code>ext/MainPage.js</code> to edit it.
|
||||
</h3>
|
||||
|
||||
|
@ -1,4 +1,3 @@
|
||||
{{={{> <}}=}}
|
||||
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
|
||||
|
||||
## Available Scripts
|
||||
|
@ -1,4 +1,3 @@
|
||||
{{={{> <}}=}}
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
|
@ -1,6 +1,6 @@
|
||||
{{={= =}=}}
|
||||
{
|
||||
"name": "{= wasp.app.name =}",
|
||||
"name": "{= appName =}",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
{=& depsChunk =},
|
||||
|
@ -1,6 +1,6 @@
|
||||
{{={{> <}}=}}
|
||||
{{={= =}=}}
|
||||
{
|
||||
"name": "{{> app.name <}}",
|
||||
"name": "{= appName =}",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
|
@ -1,4 +1,3 @@
|
||||
{{={{> <}}=}}
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
@ -1,4 +1,3 @@
|
||||
{{={= =}=}}
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import { ReactQueryCacheProvider } from 'react-query'
|
||||
|
@ -1,4 +1,3 @@
|
||||
{{={= =}=}}
|
||||
import api, { handleApiError } from '../api.js'
|
||||
import config from '../config.js'
|
||||
|
||||
|
@ -1,4 +1,3 @@
|
||||
{{={{> <}}=}}
|
||||
// This optional code is used to register a service worker.
|
||||
// register() is not called by default.
|
||||
|
||||
|
@ -1,19 +1,20 @@
|
||||
app todoApp {
|
||||
title: "ToDo App",
|
||||
head: [
|
||||
"<link rel=\"stylesheet\" href=\"https://fonts.googleapis.com/css?family=Roboto:300,400,500&display=swap\" />"
|
||||
]
|
||||
}
|
||||
|
||||
dependencies {=json
|
||||
"@material-ui/core": "4.11.3"
|
||||
json=}
|
||||
|
||||
auth {
|
||||
title: "ToDo App",
|
||||
head: [
|
||||
"<link rel=\"stylesheet\" href=\"https://fonts.googleapis.com/css?family=Roboto:300,400,500&display=swap\" />"
|
||||
],
|
||||
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
|
||||
}
|
||||
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 59 KiB |
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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})
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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'),
|
||||
|
38
waspc/src/Wasp/AppSpec/Operation.hs
Normal file
38
waspc/src/Wasp/AppSpec/Operation.hs
Normal file
@ -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
|
@ -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.
|
||||
|
76
waspc/src/Wasp/Error.hs
Normal file
76
waspc/src/Wasp/Error.hs
Normal file
@ -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
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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')
|
||||
|
@ -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
|
||||
|
@ -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 ++ " }")
|
||||
|
@ -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"
|
||||
|
@ -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)
|
||||
]
|
||||
)
|
||||
]
|
||||
|
@ -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)
|
||||
]
|
||||
|
@ -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 =
|
||||
|
@ -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'
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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")
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
@ -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
|
||||
}
|
@ -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.
|
||||
}
|
@ -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
|
@ -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<Task> ..." or "action<Task> ..."
|
||||
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)
|
@ -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)
|
@ -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
|
@ -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))
|
@ -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"
|
@ -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 <identifier> from "@ext/..."
|
||||
-- import { <identifier> } 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
|
||||
}
|
@ -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}
|
@ -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]
|
@ -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
|
||||
}
|
@ -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
|
||||
}
|
@ -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
|
||||
}
|
@ -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]
|
@ -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"
|
@ -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
|
||||
|
@ -1,6 +1,9 @@
|
||||
module Wasp.Util.Terminal
|
||||
( Style (..),
|
||||
applyStyles,
|
||||
styleCode,
|
||||
escapeCode,
|
||||
resetCode,
|
||||
)
|
||||
where
|
||||
|
||||
|
@ -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
|
||||
]
|
@ -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
|
||||
]
|
@ -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
|
||||
]
|
@ -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)
|
@ -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)
|
@ -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)
|
||||
]
|
@ -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
|
@ -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 <what> from <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)
|
||||
]
|
@ -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
|
||||
]
|
@ -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
|
@ -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
|
||||
]
|
@ -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
|
||||
]
|
@ -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
|
||||
]
|
@ -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
|
||||
]
|
@ -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
|
@ -1 +1 @@
|
||||
resolver: lts-18.15
|
||||
resolver: lts-18.21
|
||||
|
@ -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.
|
||||
|
@ -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 ")
|
||||
)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user