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:
23
.gitignore
vendored
23
.gitignore
vendored
@@ -1,12 +1,29 @@
|
||||
# Dependencies
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Turbo cache
|
||||
.turbo
|
||||
|
||||
# Build outputs
|
||||
apps/web/dist
|
||||
apps/web/dist-ssr
|
||||
|
||||
# Local env files — never commit
|
||||
.env
|
||||
.env.local
|
||||
.env.production
|
||||
.env.staging
|
||||
.env.preview
|
||||
apps/**/.env
|
||||
apps/**/.env.local
|
||||
apps/**/.env.production
|
||||
apps/**/.env.staging
|
||||
apps/**/.env.preview
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# Coverage
|
||||
coverage
|
||||
*.local
|
||||
|
||||
174
.gitlab-ci.yml
174
.gitlab-ci.yml
@@ -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
58
.woodpecker/appwrite.yml
Normal 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
|
||||
78
.woodpecker/web-preview.yml
Normal file
78
.woodpecker/web-preview.yml
Normal 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
|
||||
50
.woodpecker/web-production.yml
Normal file
50
.woodpecker/web-production.yml
Normal 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/"
|
||||
50
.woodpecker/web-staging.yml
Normal file
50
.woodpecker/web-staging.yml
Normal 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
169
apps/appwrite/appwrite.json
Normal 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": []
|
||||
}
|
||||
0
apps/appwrite/functions/.gitkeep
Normal file
0
apps/appwrite/functions/.gitkeep
Normal file
12
apps/appwrite/package.json
Normal file
12
apps/appwrite/package.json
Normal 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
58
apps/appwrite/scripts/deploy.sh
Executable 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."
|
||||
@@ -19,7 +19,7 @@ const path = require('path');
|
||||
function loadEnv() {
|
||||
const envPath = path.join(__dirname, '..', '.env');
|
||||
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);
|
||||
}
|
||||
const lines = fs.readFileSync(envPath, 'utf8').split('\n');
|
||||
34
apps/web/package.json
Normal file
34
apps/web/package.json
Normal 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"
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 573 B After Width: | Height: | Size: 573 B |
185
package-lock.json
generated
185
package-lock.json
generated
@@ -1,11 +1,31 @@
|
||||
{
|
||||
"name": "budgetwise",
|
||||
"name": "budget-app",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"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",
|
||||
"dependencies": {
|
||||
"appwrite": "^16.0.0",
|
||||
@@ -21,7 +41,6 @@
|
||||
"@types/react-dom": "^18.3.1",
|
||||
"@vitejs/plugin-react": "^4.2.1",
|
||||
"autoprefixer": "^10.4.17",
|
||||
"node-appwrite": "^12.0.0",
|
||||
"postcss": "^8.4.35",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"typescript": "^5.4.0",
|
||||
@@ -92,6 +111,7 @@
|
||||
"integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.29.0",
|
||||
"@babel/generator": "^7.29.0",
|
||||
@@ -1617,6 +1637,14 @@
|
||||
"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": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
|
||||
@@ -2655,6 +2683,7 @@
|
||||
"integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/prop-types": "*",
|
||||
"csstype": "^3.2.2"
|
||||
@@ -2724,6 +2753,7 @@
|
||||
"integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"fast-uri": "^3.0.1",
|
||||
@@ -2836,9 +2866,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/autoprefixer": {
|
||||
"version": "10.4.24",
|
||||
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.24.tgz",
|
||||
"integrity": "sha512-uHZg7N9ULTVbutaIsDRoUkoS8/h3bdsmVJYZ5l3wv8Cp/6UIIoRDm90hZ+BwxUj/hGBEzLxdHNSKuFpn8WOyZw==",
|
||||
"version": "10.4.27",
|
||||
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.27.tgz",
|
||||
"integrity": "sha512-NP9APE+tO+LuJGn7/9+cohklunJsXWiaWEfV3si4Gi/XHDwVNgkwr1J3RQYFIvPy76GmJ9/bW8vyoU1LcxwKHA==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -2857,7 +2887,7 @@
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"browserslist": "^4.28.1",
|
||||
"caniuse-lite": "^1.0.30001766",
|
||||
"caniuse-lite": "^1.0.30001774",
|
||||
"fraction.js": "^5.3.4",
|
||||
"picocolors": "^1.1.1",
|
||||
"postcss-value-parser": "^4.2.0"
|
||||
@@ -2967,9 +2997,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "5.0.3",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz",
|
||||
"integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==",
|
||||
"version": "5.0.4",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz",
|
||||
"integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -3012,6 +3042,7 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"baseline-browser-mapping": "^2.9.0",
|
||||
"caniuse-lite": "^1.0.30001759",
|
||||
@@ -3094,9 +3125,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/caniuse-lite": {
|
||||
"version": "1.0.30001772",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001772.tgz",
|
||||
"integrity": "sha512-mIwLZICj+ntVTw4BT2zfp+yu/AqV6GMKfJVJMx3MwPxs+uk/uj2GLl2dH8LQbjiLDX66amCga5nKFyDgRR43kg==",
|
||||
"version": "1.0.30001774",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001774.tgz",
|
||||
"integrity": "sha512-DDdwPGz99nmIEv216hKSgLD+D4ikHQHjBC/seF98N9CPqRX4M5mSxT9eTV6oyisnJcuzxtZy4n17yKKQYmYQOA==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -3692,9 +3723,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/filelist": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz",
|
||||
"integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==",
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.6.tgz",
|
||||
"integrity": "sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
@@ -3719,9 +3750,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/filelist/node_modules/minimatch": {
|
||||
"version": "5.1.7",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.7.tgz",
|
||||
"integrity": "sha512-FjiwU9HaHW6YB3H4a1sFudnv93lvydNjz2lmyUXR6IwKhGI+bgL3SOZrBGn6kvvX2pJvhEkGSGjyTHN47O4rqA==",
|
||||
"version": "5.1.9",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz",
|
||||
"integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
@@ -4627,6 +4658,7 @@
|
||||
"integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"jiti": "bin/jiti.js"
|
||||
}
|
||||
@@ -4827,9 +4859,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/minimatch": {
|
||||
"version": "10.2.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.2.tgz",
|
||||
"integrity": "sha512-+G4CpNBxa5MprY+04MbgOw1v7So6n5JY166pFi9KfYwT78fxScCeSNQSNzp6dpPSW2rONOps6Ocam1wFhCgoVw==",
|
||||
"version": "10.2.4",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz",
|
||||
"integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==",
|
||||
"dev": true,
|
||||
"license": "BlueOak-1.0.0",
|
||||
"dependencies": {
|
||||
@@ -5120,6 +5152,7 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"nanoid": "^3.3.11",
|
||||
"picocolors": "^1.1.1",
|
||||
@@ -5322,6 +5355,7 @@
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
|
||||
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0"
|
||||
},
|
||||
@@ -5334,6 +5368,7 @@
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
|
||||
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0",
|
||||
"scheduler": "^0.23.2"
|
||||
@@ -6184,6 +6219,7 @@
|
||||
"integrity": "sha512-jTwoImyr/QbOWFFso3YoU3ik0jBBDJ6JTOQiy/J2YxVJdZCc+5u7skhNwiOR3FQIygFqVUPHl7qbbxtjW2K3Qg==",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@jridgewell/source-map": "^0.3.3",
|
||||
"acorn": "^8.15.0",
|
||||
@@ -6268,6 +6304,7 @@
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
@@ -6305,6 +6342,108 @@
|
||||
"dev": true,
|
||||
"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": {
|
||||
"version": "0.16.0",
|
||||
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.16.0.tgz",
|
||||
@@ -6573,6 +6712,7 @@
|
||||
"integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"esbuild": "^0.21.3",
|
||||
"postcss": "^8.4.43",
|
||||
@@ -6941,6 +7081,7 @@
|
||||
"integrity": "sha512-cIFJOD1DESzpjOBl763Kp1AH7UE/0fcdHe6rZXUdQ9c50uvgigvW97u3IcSeBwOkgqL/PXPBktBCh0KEu5L8XQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"rollup": "dist/bin/rollup"
|
||||
},
|
||||
|
||||
36
package.json
36
package.json
@@ -1,34 +1,18 @@
|
||||
{
|
||||
"name": "budgetwise",
|
||||
"name": "budget-app",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"type": "module",
|
||||
"workspaces": [
|
||||
"apps/*"
|
||||
],
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc && vite build",
|
||||
"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"
|
||||
"build": "turbo run build",
|
||||
"dev": "turbo run dev"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.3.1",
|
||||
"@types/react-dom": "^18.3.1",
|
||||
"@vitejs/plugin-react": "^4.2.1",
|
||||
"autoprefixer": "^10.4.17",
|
||||
"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"
|
||||
"turbo": "^2.3.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
}
|
||||
}
|
||||
|
||||
26
turbo.json
Normal file
26
turbo.json
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user