chore: convert to Turborepo + npm workspaces monorepo

- Move React/Vite frontend to apps/web/ (@budgetwise/web)
- Add apps/appwrite/ (@budgetwise/appwrite) to source-control the
  Appwrite backend: declarative schema in appwrite.json (5 collections),
  CLI-based deploy.sh for containerized use, functions/ dir for future
  Appwrite Functions
- Add turbo.json for task orchestration (build, deploy, dev)
- Replace .gitlab-ci.yml with Woodpecker CI pipelines in .woodpecker/:
    web-production.yml  — push to main → build + rsync to prod
    web-staging.yml     — push to staging → build + rsync to staging
    web-preview.yml     — PR open → deploy to {pr}.{domain}; PR close → cleanup
    appwrite.yml        — schema changes in apps/appwrite/ → CLI deploy
- All secrets injected via Woodpecker CI (no committed .env files)
This commit is contained in:
Kushal Gaywala
2026-02-28 19:16:26 +01:00
parent 8009c11581
commit e0e0cc65f1
49 changed files with 729 additions and 226 deletions

58
.woodpecker/appwrite.yml Normal file
View File

@@ -0,0 +1,58 @@
# ── Appwrite: Schema Deploy ────────────────────────────────────────────────────
# Triggered only when files inside apps/appwrite/ change.
# main branch → deploys to production Appwrite project
# staging branch → deploys to staging Appwrite project
#
# Uses the Appwrite CLI (via apps/appwrite/scripts/deploy.sh) to push the
# schema defined in apps/appwrite/appwrite.json declaratively.
#
# Required Woodpecker secrets:
# prod_appwrite_endpoint
# prod_appwrite_project_id
# prod_appwrite_api_key needs databases.write + collections.write scopes
# staging_appwrite_endpoint
# staging_appwrite_project_id
# staging_appwrite_api_key
# ─────────────────────────────────────────────────────────────────────────────
when:
- event: push
branch:
- main
- staging
path:
include:
- apps/appwrite/**
steps:
- name: deploy-production
image: node:20-alpine
environment:
APPWRITE_ENDPOINT:
from_secret: prod_appwrite_endpoint
APPWRITE_PROJECT_ID:
from_secret: prod_appwrite_project_id
APPWRITE_API_KEY:
from_secret: prod_appwrite_api_key
commands:
- npm ci
- npm run deploy -w @budgetwise/appwrite
when:
- branch: main
event: push
- name: deploy-staging
image: node:20-alpine
environment:
APPWRITE_ENDPOINT:
from_secret: staging_appwrite_endpoint
APPWRITE_PROJECT_ID:
from_secret: staging_appwrite_project_id
APPWRITE_API_KEY:
from_secret: staging_appwrite_api_key
commands:
- npm ci
- npm run deploy -w @budgetwise/appwrite
when:
- branch: staging
event: push

View File

@@ -0,0 +1,78 @@
# ── Web: Preview (Pull Requests) ──────────────────────────────────────────────
# Triggered on pull_request open/sync → deploys to https://{pr-number}.{base_domain}
# Triggered on pull_request_closed → cleans up the preview directory
#
# Preview environments share staging Appwrite credentials.
# PR number is available as $CI_COMMIT_PULL_REQUEST.
#
# Required Woodpecker secrets:
# base_domain e.g. budget.kushalgaywala.com
# staging_appwrite_endpoint
# staging_appwrite_project_id
# ssh_private_key
# deploy_host
# deploy_user
# deploy_path
# ─────────────────────────────────────────────────────────────────────────────
when:
- event: [pull_request, pull_request_closed]
steps:
- name: build
image: node:20-alpine
environment:
BASE_DOMAIN:
from_secret: base_domain
VITE_APPWRITE_ENDPOINT:
from_secret: staging_appwrite_endpoint
VITE_APPWRITE_PROJECT_ID:
from_secret: staging_appwrite_project_id
commands:
- export MR_URL="https://$CI_COMMIT_PULL_REQUEST.$BASE_DOMAIN"
- npm ci
- VITE_APP_URL=$MR_URL npm run build:preview -w @budgetwise/web
when:
- event: pull_request
- name: deploy
image: alpine
environment:
SSH_PRIVATE_KEY:
from_secret: ssh_private_key
DEPLOY_HOST:
from_secret: deploy_host
DEPLOY_USER:
from_secret: deploy_user
DEPLOY_PATH:
from_secret: deploy_path
commands:
- apk add --no-cache rsync openssh-client
- eval $(ssh-agent -s)
- echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -
- mkdir -p ~/.ssh && chmod 700 ~/.ssh
- ssh-keyscan -H "$DEPLOY_HOST" >> ~/.ssh/known_hosts
- rsync -avz --delete apps/web/dist/ "$DEPLOY_USER@$DEPLOY_HOST:$DEPLOY_PATH/mr-$CI_COMMIT_PULL_REQUEST/"
when:
- event: pull_request
- name: cleanup
image: alpine
environment:
SSH_PRIVATE_KEY:
from_secret: ssh_private_key
DEPLOY_HOST:
from_secret: deploy_host
DEPLOY_USER:
from_secret: deploy_user
DEPLOY_PATH:
from_secret: deploy_path
commands:
- apk add --no-cache openssh-client
- eval $(ssh-agent -s)
- echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -
- mkdir -p ~/.ssh && chmod 700 ~/.ssh
- ssh-keyscan -H "$DEPLOY_HOST" >> ~/.ssh/known_hosts
- ssh "$DEPLOY_USER@$DEPLOY_HOST" "rm -rf $DEPLOY_PATH/mr-$CI_COMMIT_PULL_REQUEST"
when:
- event: pull_request_closed

View File

@@ -0,0 +1,50 @@
# ── Web: Production ───────────────────────────────────────────────────────────
# Triggered on every push to `main`.
# Builds the Vite app with production credentials then rsyncs to the server.
#
# Required Woodpecker secrets (Repository → Settings → Secrets):
# prod_url e.g. https://budget.kushalgaywala.com
# prod_appwrite_endpoint e.g. https://appwrite.example.com/v1
# prod_appwrite_project_id
# ssh_private_key
# deploy_host
# deploy_user
# deploy_path e.g. /var/www/budget-app
# ─────────────────────────────────────────────────────────────────────────────
when:
- event: push
branch: main
steps:
- name: build
image: node:20-alpine
environment:
VITE_APP_URL:
from_secret: prod_url
VITE_APPWRITE_ENDPOINT:
from_secret: prod_appwrite_endpoint
VITE_APPWRITE_PROJECT_ID:
from_secret: prod_appwrite_project_id
commands:
- npm ci
- npm run build -w @budgetwise/web
- name: deploy
image: alpine
environment:
SSH_PRIVATE_KEY:
from_secret: ssh_private_key
DEPLOY_HOST:
from_secret: deploy_host
DEPLOY_USER:
from_secret: deploy_user
DEPLOY_PATH:
from_secret: deploy_path
commands:
- apk add --no-cache rsync openssh-client
- eval $(ssh-agent -s)
- echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -
- mkdir -p ~/.ssh && chmod 700 ~/.ssh
- ssh-keyscan -H "$DEPLOY_HOST" >> ~/.ssh/known_hosts
- rsync -avz --delete apps/web/dist/ "$DEPLOY_USER@$DEPLOY_HOST:$DEPLOY_PATH/production/"

View File

@@ -0,0 +1,50 @@
# ── Web: Staging ──────────────────────────────────────────────────────────────
# Triggered on every push to `staging`.
# Builds with --mode staging (uses .env.staging locally; CI uses secrets).
#
# Required Woodpecker secrets:
# staging_url
# staging_appwrite_endpoint
# staging_appwrite_project_id
# ssh_private_key
# deploy_host
# deploy_user
# deploy_path
# ─────────────────────────────────────────────────────────────────────────────
when:
- event: push
branch: staging
steps:
- name: build
image: node:20-alpine
environment:
VITE_APP_URL:
from_secret: staging_url
VITE_APPWRITE_ENDPOINT:
from_secret: staging_appwrite_endpoint
VITE_APPWRITE_PROJECT_ID:
from_secret: staging_appwrite_project_id
commands:
- npm ci
- npm run build:staging -w @budgetwise/web
- name: deploy
image: alpine
environment:
SSH_PRIVATE_KEY:
from_secret: ssh_private_key
DEPLOY_HOST:
from_secret: deploy_host
DEPLOY_USER:
from_secret: deploy_user
DEPLOY_PATH:
from_secret: deploy_path
commands:
- apk add --no-cache rsync openssh-client
- eval $(ssh-agent -s)
- echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -
- mkdir -p ~/.ssh && chmod 700 ~/.ssh
- ssh-keyscan -H "$DEPLOY_HOST" >> ~/.ssh/known_hosts
- rsync -avz --delete apps/web/dist/ "$DEPLOY_USER@$DEPLOY_HOST:$DEPLOY_PATH/staging/"