Use dope.sh to generate a testable app from production template

This commit is contained in:
Mihovil Ilakovac
2025-10-07 13:39:10 +02:00
parent f0429afb33
commit ec70f80c8b
14 changed files with 14367 additions and 2 deletions

View File

@@ -82,7 +82,7 @@ There are two ways to get help or provide feedback (and we try to always respond
## Development Tools ## Development Tools
For information about the development tools used to maintain derived projects (like opensaas.sh), see [tools/README.md](./tools/README.md). For information about the development tools used to maintain derived projects (like opensaas.sh and template-test), see [tools/README.md](./tools/README.md).
## Contributing ## Contributing

2
template-test/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
app/
base-app/

38
template-test/README.md Normal file
View File

@@ -0,0 +1,38 @@
# Template Testing
Tests the production version of the Open SaaS template that users get from `wasp new -t saas`.
**Why this exists:** opensaas-sh tests `template/app` (current repo state), but `main` branch may differ from the last released template. This tests what users actually get from `wasp new -t saas`.
**How it works:** Scripts run `wasp new -t saas` to get a fresh template, then apply minimal production patches (Dummy→SendGrid email, dotenv-vault for secrets). Tests that the template builds and runs with production email provider.
## Regular Usage
When testing a new Wasp release:
```bash
cd template-test
./tools/patch.sh
cd app && npm run env:pull
wasp start # or wasp build
```
## Initial Setup
Only needed once to create the initial patches:
```bash
cd template-test
wasp new -t saas temp && cp -r temp/app/* app/ && rm -rf temp
cd app && git init && git add .
# Edit main.wasp: change Dummy→SendGrid
# Setup dotenv-vault with working credentials
cd .. && ./tools/diff.sh
git add app_diff/ && git commit -m "Add template-test patches"
```
## Requirements
MacOS users need: `brew install coreutils gpatch diffutils`

View File

@@ -0,0 +1,28 @@
--- /Users/ilakovac/dev/open-saas/template-test/tools/../../template-test/base-app/app/.env.vault
+++ template-test/app/.env.vault
@@ -0,0 +1,25 @@
+#/-------------------.env.vault---------------------/
+#/ cloud-agnostic vaulting standard /
+#/ [how it works](https://dotenv.org/env-vault) /
+#/--------------------------------------------------/
+
+# development
+DOTENV_VAULT_DEVELOPMENT="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"
+DOTENV_VAULT_DEVELOPMENT_VERSION=2
+
+# ci
+DOTENV_VAULT_CI="0kVgjX4gDSFLdOkWOqXHcN6VOvVhMN5wJ9zgF3M7enHE7613DjnvWxS/Xtr2kFNv71jp+Qf51mLpmajTR4kb0whC+uLFa/7xZtXBFV0k5ac4NglcAF2dPbbi2ymqqFzjz5SwTVjQ3xzqpGbmaw/spmoapH1YEaWphA6GSOkJciI8NRqOYsLq7T1AdPrPudt6SUaroRKHD/kPk6ipt7c6sWdkMzKRF2Yv9FVLHDSRr86HCfVjW5FDfgc2eo2ITOONys72Wk/JK1RB1fntT9YIxUP2mX5rWtxsRDw5PPiu4OmrfC93p81BhwlPFMBBV7fOnJKs7jx5NPUEWlPS8xf4StQ9by2/oPmBrsFdzI84ikF6yNvoac7idj2tHsnaOjPWMmTT3Fk88Bt+K0HqMjdpNT7TilEbXbIxP4vq5SjSK9/whnsEqymWFO1aimo+Z6rzC9HNXecoryjtvDk7Y9D60V4oLyNOnSMf4v/+hvIYoAwIfnajVGUQ3y5rw1TLOEm8YQ61DgmArWDk8GhHnk/U6zVIpE8r/wuvYhn5GjMrG+5ZreUJMjRN58Fo/PGJszAciQh65gD5iTlL7qiaM5qgRA7SWuywxJc+3q8YmcHLBAKS1MeSsOw4bHMH1mphO1tSobRvZCS5T/kpCSwbAZviZq5Vx+oFgk39ymAkQ9aKwiTrHtKr0V/BxpqeUt223UC/y3M1TuaCAjs2szKckGa5FPjzWMAdHKShum4ikwGncX6CA5i+c1zimO8+7gj1qQPdZPBpcgHQ2kzX5Njg3InENowIE3RXd+a/ZmKVyP1IkThofsozOgxC+V3OTqriI4SRBIU+ZQ2xfwnsuTETpe/0Bf/pvKanxqebkouHhSUTPK8aF4HOzPbyiPpjFLtw/dmlN9MXdZag02+JGhlMCQ=="
+DOTENV_VAULT_CI_VERSION=2
+
+# staging
+DOTENV_VAULT_STAGING="JMEI1tbOz427qXvVvq9eL5gCiTE62+uCHKjt20WI80iwr2ROHrVjeS4rOXVazZ+BWlnyI8utwk1h4qLqxnqE9PlaCxH78MZX/K8Z8DV30QciubrmgnleUuO25EPlivbHqWWn0nQEMGaLemERXisx/fb/F2VG4aJwMzHd0kEGTnIWjEczGV2VlAgrEobPSZBz6BC02ioIOh6+nbJd5aDaMxaxrxuEJUFTxXDjbEigsJfaZyj2BknsUu/hHdCIPP8TNmAZdPBxmjkiu6rLEKxJ/mC8Hjdu/deZa6fiL8rfLN5ckmIJC+oWW57ectOyPMTq2Byxq/GenB1gMCpB/eOpltfXJvwqSPOrk4EsD62Ycdpr5Dxn2Ui/MN7IW0PREQwkawEmgN/hFp/AVqV5x/dQakqnpFek3qG4nl9COvHWkujd0thfjxRg+iYPEZxKvw6jXPMuG5rUAq0hz2uiiQu+sl7EoPgVtMhmHAeA0XLLPN9yAcdTRH0fzXbgv+DCHR9Xi+my/9PNNi3FCQCqcKUiMSWRqA8pL9xGP7J45LICcmHbHuXhbffSp8sGvPthAKLbCiYBEH97PPOzH6qjaU5gz0qd/tM5XhwDYrWE8i/NXCFTX++l4BOJBN9zb71Sx2KALK5c2aQKjpN5LT1bnF30OchDkFXY/p79kuvopQIEkGJlcBBLyf8hgnlgQdx0e/nXFJVTh/evhc3ndp8nATFwZtoKx1Vgvrq4ux09aaO6ErII4QJZqMBjgpGuAMj9RNE0iUFwm1S9BVVyHqndDnr+mTyKpkUEqzGQHWuZ+KuAsnYQHcOJUo1Wh3zYs0BHswBvbenWcrM/0QRRLoSKziNIRfCudT+J/5ED6v/3JOhACvfq6dD7vlLgURof4b8y9bXb27GX2ycyHZGXjlbZgg=="
+DOTENV_VAULT_STAGING_VERSION=2
+
+# production
+DOTENV_VAULT_PRODUCTION="eSIUBg42a0f0KXdMcsp1tfg3nEHp6ESJNhNOysVtT8AZC/dAQculRnSkA07aY4mNPyAB7B1Y0MayMdNnnB44drdepJYUNNwsBwCxANVl6hgjll8q6CWEgE8e+vW6jTQJDq0myaCSvCgBqfQYDATTTHVJ1Q4FZECavJlqjfNIaAy09evubbhzTmL9z/i+S7jQJCLmirLET438Hnz04/i2S3CtdtsLEtjLWbUvrSkiVALBYkCFxQcmasoit2U/0fgjGaeNuWtfAB7Iku3CSdmcO8kQBtH0j+7TbW7I0cfVTUYH9ipcQCy0vjwx1GwsHWBi5IqPfj4bD/HUTPwinn7MrEBtM2cZ3uksSeWk1x+Ks7A5sQnL7XzgUxnl070TiRZJSb0TYuzXM8ILt7gq0frgKSUKRXkeH+egxjBrMAGQ7vY0ebdBHlBHPHOeF9iiFoaujWdReBitbawHYORG8Ct8pvstiTdj98w7g7WB434/Eh0L0pi8Ydepj40/8OpA3nCES9cCIi0uaFlr+arIh3w6h5FsTqaLgh+MmbpNXnRR99/r/0zzKq3/9d1wnIbc7uHkoOSEkTlD5ceEf+IO85A4jsQFOXYJpGV8ns8z4xWLTsU+3GyWIIGDkkNR0CY8PjdkBeQJPEvZNPDnn+LK9C42Clkm1eQy4aqHaagJxFGYp/j79S7Lm4kLJZMHywKi628CsHZO/moPvkfUGrEO2KmqWHvnxWDh5PggV3TAJWOMk7p2ykuFRRb26MgMf9f8oTa+CSs2Jk5W7TF10xaYZDz7fCyUBdMK+JcguPcq0mFw9eoDf9i8FLOKF26VuK7Z14+fRTZN/YHU+u75ru6WDymRpm0ZhCBDdYy80gM45089Scp2bpnKIWg6s7bLdb9R/NRUhK1PZRDEzw3SkDZBVQ=="
+DOTENV_VAULT_PRODUCTION_VERSION=2
+
+#/----------------settings/metadata-----------------/
+DOTENV_VAULT="vlt_57f67ae090fbd4c01066b1af1342a8ba518da1296c6595b9fcd233af37a5fce4"
+DOTENV_API_URL="https://vault.dotenv.org"
+DOTENV_CLI="npx dotenv-vault@latest"

View File

@@ -0,0 +1,12 @@
--- /Users/ilakovac/dev/open-saas/template-test/tools/../../template-test/base-app/app/.gitignore
+++ template-test/app/.gitignore
@@ -9,3 +9,8 @@
# Don't ignore example dotenv files.
!.env.example
!.env.*.example
+
+.env*
+.flaskenv*
+!.env.project
+!.env.vault
\ No newline at end of file

View File

@@ -0,0 +1,2 @@
.env.client.example
.env.server.example

View File

@@ -0,0 +1,28 @@
--- /Users/ilakovac/dev/open-saas/template-test/tools/../../template-test/base-app/app/main.wasp
+++ template-test/app/main.wasp
@@ -39,7 +39,7 @@
email: {
fromField: {
name: "Open SaaS App",
- email: "me@example.com"
+ email: "opensaastemplate@mg.wasp.sh"
},
emailVerification: {
clientRoute: EmailVerificationRoute,
@@ -84,14 +84,11 @@
},
emailSender: {
- // NOTE: "Dummy" provider is just for local development purposes.
- // Make sure to check the server logs for the email confirmation url (it will not be sent to an address)!
- // Once you are ready for production, switch to e.g. "SendGrid" or "Mailgun" providers. Check out https://docs.opensaas.sh/guides/email-sending/ .
- provider: Dummy,
+ provider: SMTP,
defaultFrom: {
name: "Open SaaS App",
// When using a real provider, e.g. SendGrid, you must use the same email address that you configured your account to send out emails with!
- email: "me@example.com"
+ email: "opensaastemplate@mg.wasp.sh"
},
},
}

View File

@@ -0,0 +1,175 @@
--- /Users/ilakovac/dev/open-saas/template-test/tools/../../template-test/base-app/app/migrations/20251007102522_init/migration.sql
+++ template-test/app/migrations/20251007102522_init/migration.sql
@@ -0,0 +1,172 @@
+-- CreateTable
+CREATE TABLE "User" (
+ "id" TEXT NOT NULL,
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ "email" TEXT,
+ "username" TEXT,
+ "isAdmin" BOOLEAN NOT NULL DEFAULT false,
+ "paymentProcessorUserId" TEXT,
+ "lemonSqueezyCustomerPortalUrl" TEXT,
+ "subscriptionStatus" TEXT,
+ "subscriptionPlan" TEXT,
+ "datePaid" TIMESTAMP(3),
+ "credits" INTEGER NOT NULL DEFAULT 3,
+
+ CONSTRAINT "User_pkey" PRIMARY KEY ("id")
+);
+
+-- CreateTable
+CREATE TABLE "GptResponse" (
+ "id" TEXT NOT NULL,
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ "updatedAt" TIMESTAMP(3) NOT NULL,
+ "userId" TEXT NOT NULL,
+ "content" TEXT NOT NULL,
+
+ CONSTRAINT "GptResponse_pkey" PRIMARY KEY ("id")
+);
+
+-- CreateTable
+CREATE TABLE "Task" (
+ "id" TEXT NOT NULL,
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ "userId" TEXT NOT NULL,
+ "description" TEXT NOT NULL,
+ "time" TEXT NOT NULL DEFAULT '1',
+ "isDone" BOOLEAN NOT NULL DEFAULT false,
+
+ CONSTRAINT "Task_pkey" PRIMARY KEY ("id")
+);
+
+-- CreateTable
+CREATE TABLE "File" (
+ "id" TEXT NOT NULL,
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ "userId" TEXT NOT NULL,
+ "name" TEXT NOT NULL,
+ "type" TEXT NOT NULL,
+ "key" TEXT NOT NULL,
+ "uploadUrl" TEXT NOT NULL,
+
+ CONSTRAINT "File_pkey" PRIMARY KEY ("id")
+);
+
+-- CreateTable
+CREATE TABLE "DailyStats" (
+ "id" SERIAL NOT NULL,
+ "date" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ "totalViews" INTEGER NOT NULL DEFAULT 0,
+ "prevDayViewsChangePercent" TEXT NOT NULL DEFAULT '0',
+ "userCount" INTEGER NOT NULL DEFAULT 0,
+ "paidUserCount" INTEGER NOT NULL DEFAULT 0,
+ "userDelta" INTEGER NOT NULL DEFAULT 0,
+ "paidUserDelta" INTEGER NOT NULL DEFAULT 0,
+ "totalRevenue" DOUBLE PRECISION NOT NULL DEFAULT 0,
+ "totalProfit" DOUBLE PRECISION NOT NULL DEFAULT 0,
+
+ CONSTRAINT "DailyStats_pkey" PRIMARY KEY ("id")
+);
+
+-- CreateTable
+CREATE TABLE "PageViewSource" (
+ "name" TEXT NOT NULL,
+ "date" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ "dailyStatsId" INTEGER,
+ "visitors" INTEGER NOT NULL,
+
+ CONSTRAINT "PageViewSource_pkey" PRIMARY KEY ("date","name")
+);
+
+-- CreateTable
+CREATE TABLE "Logs" (
+ "id" SERIAL NOT NULL,
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ "message" TEXT NOT NULL,
+ "level" TEXT NOT NULL,
+
+ CONSTRAINT "Logs_pkey" PRIMARY KEY ("id")
+);
+
+-- CreateTable
+CREATE TABLE "ContactFormMessage" (
+ "id" TEXT NOT NULL,
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ "userId" TEXT NOT NULL,
+ "content" TEXT NOT NULL,
+ "isRead" BOOLEAN NOT NULL DEFAULT false,
+ "repliedAt" TIMESTAMP(3),
+
+ CONSTRAINT "ContactFormMessage_pkey" PRIMARY KEY ("id")
+);
+
+-- CreateTable
+CREATE TABLE "Auth" (
+ "id" TEXT NOT NULL,
+ "userId" TEXT,
+
+ CONSTRAINT "Auth_pkey" PRIMARY KEY ("id")
+);
+
+-- CreateTable
+CREATE TABLE "AuthIdentity" (
+ "providerName" TEXT NOT NULL,
+ "providerUserId" TEXT NOT NULL,
+ "providerData" TEXT NOT NULL DEFAULT '{}',
+ "authId" TEXT NOT NULL,
+
+ CONSTRAINT "AuthIdentity_pkey" PRIMARY KEY ("providerName","providerUserId")
+);
+
+-- CreateTable
+CREATE TABLE "Session" (
+ "id" TEXT NOT NULL,
+ "expiresAt" TIMESTAMP(3) NOT NULL,
+ "userId" TEXT NOT NULL,
+
+ CONSTRAINT "Session_pkey" PRIMARY KEY ("id")
+);
+
+-- CreateIndex
+CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
+
+-- CreateIndex
+CREATE UNIQUE INDEX "User_username_key" ON "User"("username");
+
+-- CreateIndex
+CREATE UNIQUE INDEX "User_paymentProcessorUserId_key" ON "User"("paymentProcessorUserId");
+
+-- CreateIndex
+CREATE UNIQUE INDEX "DailyStats_date_key" ON "DailyStats"("date");
+
+-- CreateIndex
+CREATE UNIQUE INDEX "Auth_userId_key" ON "Auth"("userId");
+
+-- CreateIndex
+CREATE UNIQUE INDEX "Session_id_key" ON "Session"("id");
+
+-- CreateIndex
+CREATE INDEX "Session_userId_idx" ON "Session"("userId");
+
+-- AddForeignKey
+ALTER TABLE "GptResponse" ADD CONSTRAINT "GptResponse_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE "Task" ADD CONSTRAINT "Task_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE "File" ADD CONSTRAINT "File_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE "PageViewSource" ADD CONSTRAINT "PageViewSource_dailyStatsId_fkey" FOREIGN KEY ("dailyStatsId") REFERENCES "DailyStats"("id") ON DELETE SET NULL ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE "ContactFormMessage" ADD CONSTRAINT "ContactFormMessage_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE "Auth" ADD CONSTRAINT "Auth_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE "AuthIdentity" ADD CONSTRAINT "AuthIdentity_authId_fkey" FOREIGN KEY ("authId") REFERENCES "Auth"("id") ON DELETE CASCADE ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE "Session" ADD CONSTRAINT "Session_userId_fkey" FOREIGN KEY ("userId") REFERENCES "Auth"("id") ON DELETE CASCADE ON UPDATE CASCADE;

View File

@@ -0,0 +1,7 @@
--- /Users/ilakovac/dev/open-saas/template-test/tools/../../template-test/base-app/app/migrations/migration_lock.toml
+++ template-test/app/migrations/migration_lock.toml
@@ -0,0 +1,3 @@
+# Please do not edit this file manually
+# It should be added in your version-control system (i.e. Git)
+provider = "postgresql"
\ No newline at end of file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,13 @@
--- /Users/ilakovac/dev/open-saas/template-test/tools/../../template-test/base-app/app/package.json
+++ template-test/app/package.json
@@ -1,6 +1,10 @@
{
"name": "opensaas",
"type": "module",
+ "scripts": {
+ "env:pull": "npx dotenv-vault@latest pull development .env.server",
+ "env:push": "npx dotenv-vault@latest push development .env.server"
+ },
"dependencies": {
"@aws-sdk/client-s3": "^3.523.0",
"@aws-sdk/s3-presigned-post": "^3.750.0",

21
template-test/tools/diff.sh Executable file
View File

@@ -0,0 +1,21 @@
#!/usr/bin/env bash
set -e
command -v wasp >/dev/null || { echo "Error: Wasp CLI not found"; exit 1; }
SCRIPT_DIR=$(dirname "$(realpath "$0")")
ROOT_DIR="${SCRIPT_DIR}/../.."
BASE_DIR="${ROOT_DIR}/template-test/base-app"
# Clean up the temporary base app directory on exit.
trap 'rm -rf "${BASE_DIR}"' EXIT
cd "${ROOT_DIR}"
rm -rf "${BASE_DIR}"
(cd "${ROOT_DIR}/template-test" && wasp new -t saas base-app)
(cd "${BASE_DIR}/app" && git init -b main -q && git add .)
rm -rf template-test/app_diff
"${ROOT_DIR}/tools/dope.sh" "${BASE_DIR}/app" template-test/app diff

23
template-test/tools/patch.sh Executable file
View File

@@ -0,0 +1,23 @@
#!/usr/bin/env bash
set -e
command -v wasp >/dev/null || { echo "Error: Wasp CLI not found"; exit 1; }
SCRIPT_DIR=$(dirname "$(realpath "$0")")
ROOT_DIR="${SCRIPT_DIR}/../.."
BASE_DIR="${ROOT_DIR}/template-test/base-app"
# Clean up the temporary base app directory on exit.
trap 'rm -rf "${BASE_DIR}"' EXIT
cd "${ROOT_DIR}"
rm -rf "${BASE_DIR}"
(cd "${ROOT_DIR}/template-test" && wasp new -t saas base-app)
(cd "${BASE_DIR}/app" && git init -b main -q && git add .)
# Clean up existing derived app directory if it exists.
rm -rf template-test/app
"${ROOT_DIR}/tools/dope.sh" "${BASE_DIR}/app" template-test/app patch

View File

@@ -7,7 +7,7 @@ or to patch those diffs onto the base project to get the derived one. This is us
project has only small changes on top of the base project and you want to keep it in a directory project has only small changes on top of the base project and you want to keep it in a directory
in the same repo as the main project. in the same repo as the main project.
Since derived apps (like opensaas-sh) are just the Open SaaS template with some small tweaks, and Since derived apps (like opensaas-sh and template-test) are just the Open SaaS template with some small tweaks, and
we want to keep them up to date as the template changes, we don't version the actual app code in git. we want to keep them up to date as the template changes, we don't version the actual app code in git.
Instead, we version the diffs between it and the template in an `app_diff/` directory. Instead, we version the diffs between it and the template in an `app_diff/` directory.