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

23
.gitignore vendored
View File

@@ -1,12 +1,29 @@
# Dependencies
node_modules node_modules
dist
dist-ssr # Turbo cache
*.local .turbo
# Build outputs
apps/web/dist
apps/web/dist-ssr
# Local env files — never commit
.env .env
.env.local .env.local
.env.production .env.production
.env.staging .env.staging
.env.preview .env.preview
apps/**/.env
apps/**/.env.local
apps/**/.env.production
apps/**/.env.staging
apps/**/.env.preview
# OS
.DS_Store .DS_Store
*.pem *.pem
# Coverage
coverage coverage
*.local

View File

@@ -1,174 +0,0 @@
# ─────────────────────────────────────────────────────────────────────────────
# BudgetWise CI/CD Pipeline
#
# Branch → Environment mapping:
# main → production (https://{PROD_URL})
# staging → staging (https://{STAGING_URL})
# merge request → preview (https://{MR_IID}.{BASE_DOMAIN})
#
# All config lives in GitLab CI/CD variables (Settings → CI/CD → Variables).
# No .env files are committed — the build generates them at runtime.
#
# Required variables:
# SSH_PRIVATE_KEY Private key with access to DEPLOY_HOST
# DEPLOY_HOST Server hostname or IP
# DEPLOY_USER SSH username on the server
# DEPLOY_PATH Base deploy path e.g. /var/www/budget-app
#
# PROD_URL e.g. https://budget.kushalgaywala.com
# PROD_APPWRITE_ENDPOINT e.g. https://appwrite.example.com/v1
# PROD_APPWRITE_PROJECT_ID
#
# STAGING_URL e.g. https://staging.budget.kushalgaywala.com
# STAGING_APPWRITE_ENDPOINT
# STAGING_APPWRITE_PROJECT_ID
#
# BASE_DOMAIN MR preview base e.g. budget.kushalgaywala.com
# Preview URLs: https://{mr-iid}.{BASE_DOMAIN}
# Preview shares staging Appwrite credentials.
#
# Server directory layout:
# $DEPLOY_PATH/production/ ← prod
# $DEPLOY_PATH/staging/ ← staging
# $DEPLOY_PATH/mr-{iid}/ ← MR preview (removed on MR close)
# ─────────────────────────────────────────────────────────────────────────────
stages:
- build
- deploy
.deploy_base:
image: alpine:latest
before_script:
- 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
# ── Production ────────────────────────────────────────────────────────────────
build:production:
stage: build
image: node:20-alpine
variables:
VITE_APP_URL: $PROD_URL
VITE_APPWRITE_ENDPOINT: $PROD_APPWRITE_ENDPOINT
VITE_APPWRITE_PROJECT_ID: $PROD_APPWRITE_PROJECT_ID
script:
- npm ci
- npm run build
- echo "VITE_APP_URL=$PROD_URL" > deploy.env
artifacts:
paths:
- dist/
reports:
dotenv: deploy.env
expire_in: 1 hour
rules:
- if: $CI_COMMIT_BRANCH == "main"
deploy:production:
extends: .deploy_base
stage: deploy
environment:
name: production
url: $VITE_APP_URL
script:
- rsync -avz --delete dist/ "$DEPLOY_USER@$DEPLOY_HOST:$DEPLOY_PATH/production/"
needs:
- job: build:production
artifacts: true
rules:
- if: $CI_COMMIT_BRANCH == "main"
# ── Staging ───────────────────────────────────────────────────────────────────
build:staging:
stage: build
image: node:20-alpine
variables:
VITE_APP_URL: $STAGING_URL
VITE_APPWRITE_ENDPOINT: $STAGING_APPWRITE_ENDPOINT
VITE_APPWRITE_PROJECT_ID: $STAGING_APPWRITE_PROJECT_ID
script:
- npm ci
- npx tsc --noEmit
- npx vite build --mode staging
- echo "VITE_APP_URL=$STAGING_URL" > deploy.env
artifacts:
paths:
- dist/
reports:
dotenv: deploy.env
expire_in: 1 hour
rules:
- if: $CI_COMMIT_BRANCH == "staging"
deploy:staging:
extends: .deploy_base
stage: deploy
environment:
name: staging
url: $VITE_APP_URL
script:
- rsync -avz --delete dist/ "$DEPLOY_USER@$DEPLOY_HOST:$DEPLOY_PATH/staging/"
needs:
- job: build:staging
artifacts: true
rules:
- if: $CI_COMMIT_BRANCH == "staging"
# ── Preview / MR ──────────────────────────────────────────────────────────────
# One ephemeral environment per MR: https://{mr-iid}.{BASE_DOMAIN}
# Uses staging Appwrite credentials.
# Environment is torn down automatically when the MR is closed.
build:preview:
stage: build
image: node:20-alpine
variables:
VITE_APPWRITE_ENDPOINT: $STAGING_APPWRITE_ENDPOINT
VITE_APPWRITE_PROJECT_ID: $STAGING_APPWRITE_PROJECT_ID
script:
- npm ci
- npx tsc --noEmit
- export MR_URL="https://$CI_MERGE_REQUEST_IID.$BASE_DOMAIN"
- VITE_APP_URL=$MR_URL npx vite build --mode preview
- echo "VITE_APP_URL=$MR_URL" > deploy.env
artifacts:
paths:
- dist/
reports:
dotenv: deploy.env
expire_in: 1 day
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
deploy:preview:
extends: .deploy_base
stage: deploy
environment:
name: review/mr-$CI_MERGE_REQUEST_IID
url: $VITE_APP_URL
on_stop: stop:preview
script:
- rsync -avz --delete dist/ "$DEPLOY_USER@$DEPLOY_HOST:$DEPLOY_PATH/mr-$CI_MERGE_REQUEST_IID/"
needs:
- job: build:preview
artifacts: true
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
stop:preview:
extends: .deploy_base
stage: deploy
environment:
name: review/mr-$CI_MERGE_REQUEST_IID
action: stop
script:
- ssh "$DEPLOY_USER@$DEPLOY_HOST" "rm -rf $DEPLOY_PATH/mr-$CI_MERGE_REQUEST_IID"
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
when: manual
allow_failure: true

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/"

169
apps/appwrite/appwrite.json Normal file
View File

@@ -0,0 +1,169 @@
{
"$schema": "https://appwrite.io/sdk/schemas/appwrite.json",
"projectId": "",
"projectName": "BudgetWise",
"databases": [
{
"databaseId": "budget_db",
"name": "BudgetWise",
"enabled": true,
"collections": [
{
"databaseId": "budget_db",
"collectionId": "balance_sheets",
"name": "Balance Sheets",
"enabled": true,
"documentSecurity": false,
"permissions": [
"read(\"users\")",
"create(\"users\")",
"update(\"users\")",
"delete(\"users\")"
],
"attributes": [
{ "key": "month", "type": "integer", "required": true, "array": false },
{ "key": "year", "type": "integer", "required": true, "array": false },
{ "key": "buffer_type", "type": "string", "size": 20, "required": true, "array": false },
{ "key": "buffer_value", "type": "float", "required": true, "default": 0, "array": false },
{ "key": "user_id", "type": "string", "size": 64, "required": true, "array": false }
],
"indexes": [
{
"key": "user_month_year",
"type": "key",
"attributes": ["user_id", "month", "year"],
"orders": ["ASC", "DESC", "DESC"]
}
]
},
{
"databaseId": "budget_db",
"collectionId": "incomes",
"name": "Incomes",
"enabled": true,
"documentSecurity": false,
"permissions": [
"read(\"users\")",
"create(\"users\")",
"update(\"users\")",
"delete(\"users\")"
],
"attributes": [
{ "key": "balance_sheet_id", "type": "string", "size": 64, "required": true, "array": false },
{ "key": "name", "type": "string", "size": 128,"required": true, "array": false },
{ "key": "amount", "type": "float", "required": true, "array": false },
{ "key": "frequency", "type": "string", "size": 20, "required": true, "array": false },
{ "key": "user_id", "type": "string", "size": 64, "required": true, "array": false }
],
"indexes": [
{
"key": "user_sheet",
"type": "key",
"attributes": ["user_id", "balance_sheet_id"],
"orders": []
}
]
},
{
"databaseId": "budget_db",
"collectionId": "buckets",
"name": "Buckets",
"enabled": true,
"documentSecurity": false,
"permissions": [
"read(\"users\")",
"create(\"users\")",
"update(\"users\")",
"delete(\"users\")"
],
"attributes": [
{ "key": "name", "type": "string", "size": 128, "required": true, "array": false },
{ "key": "description", "type": "string", "size": 512, "required": false, "default": "", "array": false },
{ "key": "type", "type": "string", "size": 32, "required": true, "array": false },
{ "key": "current_balance", "type": "float", "required": true, "default": 0, "array": false },
{ "key": "goal_amount", "type": "float", "required": false, "default": 0, "array": false },
{ "key": "goal_type", "type": "string", "size": 20, "required": false, "default": "amount", "array": false },
{ "key": "goal_frequency", "type": "string", "size": 20, "required": false, "default": "monthly", "array": false },
{ "key": "return_percent", "type": "float", "required": false, "default": 0, "array": false },
{ "key": "return_frequency", "type": "string", "size": 20, "required": false, "default": "yearly", "array": false },
{ "key": "color", "type": "string", "size": 16, "required": true, "array": false },
{ "key": "sort_order", "type": "integer", "required": false, "default": 0, "array": false },
{ "key": "user_id", "type": "string", "size": 64, "required": true, "array": false }
],
"indexes": [
{
"key": "user_order",
"type": "key",
"attributes": ["user_id", "sort_order"],
"orders": ["ASC", "ASC"]
}
]
},
{
"databaseId": "budget_db",
"collectionId": "debts",
"name": "Debts",
"enabled": true,
"documentSecurity": false,
"permissions": [
"read(\"users\")",
"create(\"users\")",
"update(\"users\")",
"delete(\"users\")"
],
"attributes": [
{ "key": "name", "type": "string", "size": 128, "required": true, "array": false },
{ "key": "principal", "type": "float", "required": true, "array": false },
{ "key": "remaining_balance", "type": "float", "required": true, "array": false },
{ "key": "interest_rate", "type": "float", "required": true, "array": false },
{ "key": "interest_frequency", "type": "string", "size": 20, "required": true, "array": false },
{ "key": "term_months", "type": "integer", "required": true, "array": false },
{ "key": "monthly_payment", "type": "float", "required": true, "array": false },
{ "key": "is_auto_calculated", "type": "boolean", "required": true, "default": false, "array": false },
{ "key": "start_date", "type": "string", "size": 24, "required": true, "array": false },
{ "key": "user_id", "type": "string", "size": 64, "required": true, "array": false }
],
"indexes": [
{
"key": "user_id_idx",
"type": "key",
"attributes": ["user_id"],
"orders": []
}
]
},
{
"databaseId": "budget_db",
"collectionId": "transactions",
"name": "Transactions",
"enabled": true,
"documentSecurity": false,
"permissions": [
"read(\"users\")",
"create(\"users\")",
"update(\"users\")",
"delete(\"users\")"
],
"attributes": [
{ "key": "bucket_id", "type": "string", "size": 64, "required": true, "array": false },
{ "key": "type", "type": "string", "size": 20, "required": true, "array": false },
{ "key": "amount", "type": "float", "required": true, "array": false },
{ "key": "date", "type": "string", "size": 24, "required": true, "array": false },
{ "key": "notes", "type": "string", "size": 512, "required": false, "default": "", "array": false },
{ "key": "balance_after","type": "float", "required": true, "array": false },
{ "key": "user_id", "type": "string", "size": 64, "required": true, "array": false }
],
"indexes": [
{
"key": "user_bucket_date",
"type": "key",
"attributes": ["user_id", "bucket_id", "date"],
"orders": ["ASC", "ASC", "DESC"]
}
]
}
]
}
],
"functions": []
}

View File

View File

@@ -0,0 +1,12 @@
{
"name": "@budgetwise/appwrite",
"private": true,
"version": "1.0.0",
"scripts": {
"deploy": "bash scripts/deploy.sh",
"setup": "node scripts/setup-appwrite.cjs"
},
"devDependencies": {
"node-appwrite": "^12.0.0"
}
}

58
apps/appwrite/scripts/deploy.sh Executable file
View File

@@ -0,0 +1,58 @@
#!/usr/bin/env bash
# ─────────────────────────────────────────────────────────────────────────────
# BudgetWise Appwrite Deploy Script
#
# Deploys the schema defined in appwrite.json (databases, collections) using
# the Appwrite CLI. Runs functions deploy if functions/ contains any functions.
#
# Works locally and in CI (Woodpecker, Docker, etc.) — credentials come from
# environment variables, never from committed files.
#
# Required environment variables:
# APPWRITE_ENDPOINT e.g. https://appwrite.example.com/v1
# APPWRITE_PROJECT_ID The Appwrite project ID
# APPWRITE_API_KEY API key with databases.write + collections.write scopes
#
# Local usage:
# export APPWRITE_ENDPOINT=... APPWRITE_PROJECT_ID=... APPWRITE_API_KEY=...
# bash scripts/deploy.sh
#
# Or with a local .env file at apps/appwrite/.env:
# set -a && source .env && set +a && bash scripts/deploy.sh
# ─────────────────────────────────────────────────────────────────────────────
set -euo pipefail
APPWRITE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
CLI="npx --yes appwrite@6"
# ── Validate required env vars ────────────────────────────────────────────────
: "${APPWRITE_ENDPOINT:?APPWRITE_ENDPOINT is required}"
: "${APPWRITE_PROJECT_ID:?APPWRITE_PROJECT_ID is required}"
: "${APPWRITE_API_KEY:?APPWRITE_API_KEY is required}"
echo "==> Deploying Appwrite schema"
echo " Endpoint : $APPWRITE_ENDPOINT"
echo " Project : $APPWRITE_PROJECT_ID"
# ── Configure CLI session ─────────────────────────────────────────────────────
# Writes to ~/.appwrite/prefs.json — ephemeral in CI runners.
$CLI client \
--endpoint "$APPWRITE_ENDPOINT" \
--project-id "$APPWRITE_PROJECT_ID" \
--key "$APPWRITE_API_KEY"
# ── Deploy databases + collections ────────────────────────────────────────────
cd "$APPWRITE_DIR"
echo "==> Deploying databases..."
$CLI deploy database --all --yes
# ── Deploy functions (skipped if functions/ is empty) ─────────────────────────
if [ -d "functions" ] && [ -n "$(ls -A functions 2>/dev/null | grep -v '\.gitkeep')" ]; then
echo "==> Deploying functions..."
$CLI deploy function --all --yes
else
echo "==> No functions to deploy, skipping."
fi
echo "==> Appwrite deploy complete."

View File

@@ -19,7 +19,7 @@ const path = require('path');
function loadEnv() { function loadEnv() {
const envPath = path.join(__dirname, '..', '.env'); const envPath = path.join(__dirname, '..', '.env');
if (!fs.existsSync(envPath)) { if (!fs.existsSync(envPath)) {
console.error('❌ .env file not found. Copy .env.example to .env and fill in the values.'); console.error('❌ .env file not found. Copy .env.example from the repo root to apps/appwrite/.env and fill in the values.');
process.exit(1); process.exit(1);
} }
const lines = fs.readFileSync(envPath, 'utf8').split('\n'); const lines = fs.readFileSync(envPath, 'utf8').split('\n');

34
apps/web/package.json Normal file
View File

@@ -0,0 +1,34 @@
{
"name": "@budgetwise/web",
"private": true,
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"build:staging": "tsc && vite build --mode staging",
"build:preview": "tsc && vite build --mode preview",
"preview": "vite preview"
},
"dependencies": {
"appwrite": "^16.0.0",
"date-fns": "^3.3.1",
"lucide-react": "^0.344.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-router-dom": "^6.22.0",
"zustand": "^4.5.0"
},
"devDependencies": {
"@types/react": "^18.3.1",
"@types/react-dom": "^18.3.1",
"@vitejs/plugin-react": "^4.2.1",
"autoprefixer": "^10.4.17",
"postcss": "^8.4.35",
"tailwindcss": "^3.4.1",
"typescript": "^5.4.0",
"vite": "^5.1.0",
"vite-plugin-pwa": "^0.19.1",
"workbox-window": "^7.0.0"
}
}

View File

Before

Width:  |  Height:  |  Size: 573 B

After

Width:  |  Height:  |  Size: 573 B

185
package-lock.json generated
View File

@@ -1,11 +1,31 @@
{ {
"name": "budgetwise", "name": "budget-app",
"version": "1.0.0", "version": "1.0.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "budgetwise", "name": "budget-app",
"version": "1.0.0",
"workspaces": [
"apps/*"
],
"devDependencies": {
"turbo": "^2.3.3"
},
"engines": {
"node": ">=20"
}
},
"apps/appwrite": {
"name": "@budgetwise/appwrite",
"version": "1.0.0",
"devDependencies": {
"node-appwrite": "^12.0.0"
}
},
"apps/web": {
"name": "@budgetwise/web",
"version": "1.0.0", "version": "1.0.0",
"dependencies": { "dependencies": {
"appwrite": "^16.0.0", "appwrite": "^16.0.0",
@@ -21,7 +41,6 @@
"@types/react-dom": "^18.3.1", "@types/react-dom": "^18.3.1",
"@vitejs/plugin-react": "^4.2.1", "@vitejs/plugin-react": "^4.2.1",
"autoprefixer": "^10.4.17", "autoprefixer": "^10.4.17",
"node-appwrite": "^12.0.0",
"postcss": "^8.4.35", "postcss": "^8.4.35",
"tailwindcss": "^3.4.1", "tailwindcss": "^3.4.1",
"typescript": "^5.4.0", "typescript": "^5.4.0",
@@ -92,6 +111,7 @@
"integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@babel/code-frame": "^7.29.0", "@babel/code-frame": "^7.29.0",
"@babel/generator": "^7.29.0", "@babel/generator": "^7.29.0",
@@ -1617,6 +1637,14 @@
"node": ">=6.9.0" "node": ">=6.9.0"
} }
}, },
"node_modules/@budgetwise/appwrite": {
"resolved": "apps/appwrite",
"link": true
},
"node_modules/@budgetwise/web": {
"resolved": "apps/web",
"link": true
},
"node_modules/@esbuild/aix-ppc64": { "node_modules/@esbuild/aix-ppc64": {
"version": "0.21.5", "version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
@@ -2655,6 +2683,7 @@
"integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==", "integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==",
"devOptional": true, "devOptional": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@types/prop-types": "*", "@types/prop-types": "*",
"csstype": "^3.2.2" "csstype": "^3.2.2"
@@ -2724,6 +2753,7 @@
"integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"fast-deep-equal": "^3.1.3", "fast-deep-equal": "^3.1.3",
"fast-uri": "^3.0.1", "fast-uri": "^3.0.1",
@@ -2836,9 +2866,9 @@
} }
}, },
"node_modules/autoprefixer": { "node_modules/autoprefixer": {
"version": "10.4.24", "version": "10.4.27",
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.24.tgz", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.27.tgz",
"integrity": "sha512-uHZg7N9ULTVbutaIsDRoUkoS8/h3bdsmVJYZ5l3wv8Cp/6UIIoRDm90hZ+BwxUj/hGBEzLxdHNSKuFpn8WOyZw==", "integrity": "sha512-NP9APE+tO+LuJGn7/9+cohklunJsXWiaWEfV3si4Gi/XHDwVNgkwr1J3RQYFIvPy76GmJ9/bW8vyoU1LcxwKHA==",
"dev": true, "dev": true,
"funding": [ "funding": [
{ {
@@ -2857,7 +2887,7 @@
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"browserslist": "^4.28.1", "browserslist": "^4.28.1",
"caniuse-lite": "^1.0.30001766", "caniuse-lite": "^1.0.30001774",
"fraction.js": "^5.3.4", "fraction.js": "^5.3.4",
"picocolors": "^1.1.1", "picocolors": "^1.1.1",
"postcss-value-parser": "^4.2.0" "postcss-value-parser": "^4.2.0"
@@ -2967,9 +2997,9 @@
} }
}, },
"node_modules/brace-expansion": { "node_modules/brace-expansion": {
"version": "5.0.3", "version": "5.0.4",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz",
"integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==", "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -3012,6 +3042,7 @@
} }
], ],
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"baseline-browser-mapping": "^2.9.0", "baseline-browser-mapping": "^2.9.0",
"caniuse-lite": "^1.0.30001759", "caniuse-lite": "^1.0.30001759",
@@ -3094,9 +3125,9 @@
} }
}, },
"node_modules/caniuse-lite": { "node_modules/caniuse-lite": {
"version": "1.0.30001772", "version": "1.0.30001774",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001772.tgz", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001774.tgz",
"integrity": "sha512-mIwLZICj+ntVTw4BT2zfp+yu/AqV6GMKfJVJMx3MwPxs+uk/uj2GLl2dH8LQbjiLDX66amCga5nKFyDgRR43kg==", "integrity": "sha512-DDdwPGz99nmIEv216hKSgLD+D4ikHQHjBC/seF98N9CPqRX4M5mSxT9eTV6oyisnJcuzxtZy4n17yKKQYmYQOA==",
"dev": true, "dev": true,
"funding": [ "funding": [
{ {
@@ -3692,9 +3723,9 @@
} }
}, },
"node_modules/filelist": { "node_modules/filelist": {
"version": "1.0.4", "version": "1.0.6",
"resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.6.tgz",
"integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", "integrity": "sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA==",
"dev": true, "dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
@@ -3719,9 +3750,9 @@
} }
}, },
"node_modules/filelist/node_modules/minimatch": { "node_modules/filelist/node_modules/minimatch": {
"version": "5.1.7", "version": "5.1.9",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.7.tgz", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz",
"integrity": "sha512-FjiwU9HaHW6YB3H4a1sFudnv93lvydNjz2lmyUXR6IwKhGI+bgL3SOZrBGn6kvvX2pJvhEkGSGjyTHN47O4rqA==", "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==",
"dev": true, "dev": true,
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
@@ -4627,6 +4658,7 @@
"integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"bin": { "bin": {
"jiti": "bin/jiti.js" "jiti": "bin/jiti.js"
} }
@@ -4827,9 +4859,9 @@
} }
}, },
"node_modules/minimatch": { "node_modules/minimatch": {
"version": "10.2.2", "version": "10.2.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.2.tgz", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz",
"integrity": "sha512-+G4CpNBxa5MprY+04MbgOw1v7So6n5JY166pFi9KfYwT78fxScCeSNQSNzp6dpPSW2rONOps6Ocam1wFhCgoVw==", "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==",
"dev": true, "dev": true,
"license": "BlueOak-1.0.0", "license": "BlueOak-1.0.0",
"dependencies": { "dependencies": {
@@ -5120,6 +5152,7 @@
} }
], ],
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"nanoid": "^3.3.11", "nanoid": "^3.3.11",
"picocolors": "^1.1.1", "picocolors": "^1.1.1",
@@ -5322,6 +5355,7 @@
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"loose-envify": "^1.1.0" "loose-envify": "^1.1.0"
}, },
@@ -5334,6 +5368,7 @@
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"loose-envify": "^1.1.0", "loose-envify": "^1.1.0",
"scheduler": "^0.23.2" "scheduler": "^0.23.2"
@@ -6184,6 +6219,7 @@
"integrity": "sha512-jTwoImyr/QbOWFFso3YoU3ik0jBBDJ6JTOQiy/J2YxVJdZCc+5u7skhNwiOR3FQIygFqVUPHl7qbbxtjW2K3Qg==", "integrity": "sha512-jTwoImyr/QbOWFFso3YoU3ik0jBBDJ6JTOQiy/J2YxVJdZCc+5u7skhNwiOR3FQIygFqVUPHl7qbbxtjW2K3Qg==",
"dev": true, "dev": true,
"license": "BSD-2-Clause", "license": "BSD-2-Clause",
"peer": true,
"dependencies": { "dependencies": {
"@jridgewell/source-map": "^0.3.3", "@jridgewell/source-map": "^0.3.3",
"acorn": "^8.15.0", "acorn": "^8.15.0",
@@ -6268,6 +6304,7 @@
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"engines": { "engines": {
"node": ">=12" "node": ">=12"
}, },
@@ -6305,6 +6342,108 @@
"dev": true, "dev": true,
"license": "Apache-2.0" "license": "Apache-2.0"
}, },
"node_modules/turbo": {
"version": "2.8.12",
"resolved": "https://registry.npmjs.org/turbo/-/turbo-2.8.12.tgz",
"integrity": "sha512-auUAMLmi0eJhxDhQrxzvuhfEbICnVt0CTiYQYY8WyRJ5nwCDZxD0JG8bCSxT4nusI2CwJzmZAay5BfF6LmK7Hw==",
"dev": true,
"license": "MIT",
"bin": {
"turbo": "bin/turbo"
},
"optionalDependencies": {
"turbo-darwin-64": "2.8.12",
"turbo-darwin-arm64": "2.8.12",
"turbo-linux-64": "2.8.12",
"turbo-linux-arm64": "2.8.12",
"turbo-windows-64": "2.8.12",
"turbo-windows-arm64": "2.8.12"
}
},
"node_modules/turbo-darwin-64": {
"version": "2.8.12",
"resolved": "https://registry.npmjs.org/turbo-darwin-64/-/turbo-darwin-64-2.8.12.tgz",
"integrity": "sha512-EiHJmW2MeQQx+21x8hjMHw/uPhXt9PIxvDrxzOtyVwrXzL0tQmsxtO4qHf2l7uA+K6PUJ4+TjY1MHZDuCvWXrw==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
]
},
"node_modules/turbo-darwin-arm64": {
"version": "2.8.12",
"resolved": "https://registry.npmjs.org/turbo-darwin-arm64/-/turbo-darwin-arm64-2.8.12.tgz",
"integrity": "sha512-cbqqGN0vd7ly2TeuaM8k9AK9u1CABO4kBA5KPSqovTiLL3sORccn/mZzJSbvQf0EsYRfU34MgW5FotfwW3kx8Q==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
]
},
"node_modules/turbo-linux-64": {
"version": "2.8.12",
"resolved": "https://registry.npmjs.org/turbo-linux-64/-/turbo-linux-64-2.8.12.tgz",
"integrity": "sha512-jXKw9j4r4q6s0goSXuKI3aKbQK2qiNeP25lGGEnq018TM6SWRW1CCpPMxyG91aCKrub7wDm/K45sGNT4ZFBcFQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/turbo-linux-arm64": {
"version": "2.8.12",
"resolved": "https://registry.npmjs.org/turbo-linux-arm64/-/turbo-linux-arm64-2.8.12.tgz",
"integrity": "sha512-BRJCMdyXjyBoL0GYpvj9d2WNfMHwc3tKmJG5ATn2Efvil9LsiOsd/93/NxDqW0jACtHFNVOPnd/CBwXRPiRbwA==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/turbo-windows-64": {
"version": "2.8.12",
"resolved": "https://registry.npmjs.org/turbo-windows-64/-/turbo-windows-64-2.8.12.tgz",
"integrity": "sha512-vyFOlpFFzQFkikvSVhVkESEfzIopgs2J7J1rYvtSwSHQ4zmHxkC95Q8Kjkus8gg+8X2mZyP1GS5jirmaypGiPw==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
]
},
"node_modules/turbo-windows-arm64": {
"version": "2.8.12",
"resolved": "https://registry.npmjs.org/turbo-windows-arm64/-/turbo-windows-arm64-2.8.12.tgz",
"integrity": "sha512-9nRnlw5DF0LkJClkIws1evaIF36dmmMEO84J5Uj4oQ8C0QTHwlH7DNe5Kq2Jdmu8GXESCNDNuUYG8Cx6W/vm3g==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
]
},
"node_modules/type-fest": { "node_modules/type-fest": {
"version": "0.16.0", "version": "0.16.0",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.16.0.tgz", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.16.0.tgz",
@@ -6573,6 +6712,7 @@
"integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"esbuild": "^0.21.3", "esbuild": "^0.21.3",
"postcss": "^8.4.43", "postcss": "^8.4.43",
@@ -6941,6 +7081,7 @@
"integrity": "sha512-cIFJOD1DESzpjOBl763Kp1AH7UE/0fcdHe6rZXUdQ9c50uvgigvW97u3IcSeBwOkgqL/PXPBktBCh0KEu5L8XQ==", "integrity": "sha512-cIFJOD1DESzpjOBl763Kp1AH7UE/0fcdHe6rZXUdQ9c50uvgigvW97u3IcSeBwOkgqL/PXPBktBCh0KEu5L8XQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"bin": { "bin": {
"rollup": "dist/bin/rollup" "rollup": "dist/bin/rollup"
}, },

View File

@@ -1,34 +1,18 @@
{ {
"name": "budgetwise", "name": "budget-app",
"private": true, "private": true,
"version": "1.0.0", "version": "1.0.0",
"type": "module", "workspaces": [
"apps/*"
],
"scripts": { "scripts": {
"dev": "vite", "build": "turbo run build",
"build": "tsc && vite build", "dev": "turbo run dev"
"preview": "vite preview",
"setup:appwrite": "node scripts/setup-appwrite.cjs"
},
"dependencies": {
"appwrite": "^16.0.0",
"date-fns": "^3.3.1",
"lucide-react": "^0.344.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-router-dom": "^6.22.0",
"zustand": "^4.5.0"
}, },
"devDependencies": { "devDependencies": {
"@types/react": "^18.3.1", "turbo": "^2.3.3"
"@types/react-dom": "^18.3.1", },
"@vitejs/plugin-react": "^4.2.1", "engines": {
"autoprefixer": "^10.4.17", "node": ">=20"
"node-appwrite": "^12.0.0",
"postcss": "^8.4.35",
"tailwindcss": "^3.4.1",
"typescript": "^5.4.0",
"vite": "^5.1.0",
"vite-plugin-pwa": "^0.19.1",
"workbox-window": "^7.0.0"
} }
} }

26
turbo.json Normal file
View File

@@ -0,0 +1,26 @@
{
"$schema": "https://turbo.build/schema.json",
"tasks": {
"build": {
"inputs": [
"src/**",
"public/**",
"index.html",
"*.config.*",
"tsconfig*.json",
"$VITE_APP_URL",
"$VITE_APPWRITE_ENDPOINT",
"$VITE_APPWRITE_PROJECT_ID"
],
"outputs": ["dist/**"]
},
"deploy": {
"cache": false,
"outputs": []
},
"dev": {
"persistent": true,
"cache": false
}
}
}