#!/usr/bin/env bash # انتشارِ فرانت روی سرور: گرفتنِ کدِ تازه، build، و جابه‌جاییِ بی‌وقفه‌ی dist. # # ./deploy.sh # شاخه‌ی main # ./deploy.sh # # build در dist.next ساخته می‌شود و فقط اگر کامل موفق شد جای dist را می‌گیرد؛ # پس سایت حتی چند ثانیه هم بی‌فایل نمی‌ماند. buildِ قبلی در dist.prev می‌ماند: # برگشت: rm -rf dist && mv dist.prev dist # # dist/config.js (آدرسِ API در زمانِ اجرا) اگر روی سرور دستی ویرایش شده باشد، # به buildِ تازه منتقل می‌شود تا با هر انتشار از دست نرود. set -euo pipefail cd "$(dirname "$0")" BRANCH="${1:-main}" step() { printf '\n\033[0;32m▸ %s\033[0m\n' "$1"; } fail() { printf '\033[0;31m✖ %s\033[0m\n' "$1" >&2; exit 1; } step "گرفتنِ کدِ تازه ($BRANCH)" # DEPLOY_BUNDLE=/مسیر/فایل.bundle: کد از یک git bundle، نه از gitea (مثلِ deploy.shِ بک‌اند): برای وقتی که سرور # به gitea نمی‌رسد یا اعتبارنامه ندارد. فقط وقتی صریحاً داده شود؛ بی‌آن نرسیدن به gitea دیپلو را می‌ایستاند. if [ -n "${DEPLOY_BUNDLE:-}" ]; then [ -f "$DEPLOY_BUNDLE" ] || fail "DEPLOY_BUNDLE=$DEPLOY_BUNDLE وجود ندارد." git bundle verify "$DEPLOY_BUNDLE" >/dev/null 2>&1 || fail "bundle معتبر نیست یا کامیتِ پایه‌اش این‌جا نیست." git fetch "$DEPLOY_BUNDLE" "$BRANCH:refs/remotes/origin/$BRANCH" || fail "گرفتن از bundle نشد." else git fetch origin "$BRANCH" || fail "به gitea وصل نشد (اگر فقط از این سرور: DEPLOY_BUNDLE، بالای همین خط)." fi printf ' origin/%s = %s\n' "$BRANCH" "$(git rev-parse --short "origin/$BRANCH")" if [ -n "$(git status --porcelain --untracked-files=no)" ]; then mkdir -p .deploy-backups PATCH=".deploy-backups/local-changes-$(date +%Y%m%d-%H%M%S).patch" git diff > "$PATCH" printf ' تغییرِ دستی روی سرور بود؛ در %s ذخیره شد.\n' "$PATCH" fi git reset --hard "origin/$BRANCH" git log --oneline -1 step "نصبِ وابستگی‌ها" npm ci --no-audit --no-fund step "type-check و build" rm -rf dist.next npx tsc --noEmit || fail "type-check شکست خورد؛ سایت دست نخورد." npx vite build --outDir dist.next --emptyOutDir || fail "build شکست خورد؛ سایت دست نخورد." if [ -f dist/config.js ] && ! cmp -s dist/config.js public/config.js; then cp dist/config.js dist.next/config.js printf ' config.jsِ ویرایش‌شده‌ی سرور نگه داشته شد.\n' fi # فایل‌های buildهای قبلی (نامشان hash دارد و با هم برخورد نمی‌کنند) تا ۳۰ روز می‌مانند: گوشی‌ای که اپ از قبل # در آن باز است هنوز index.htmlِ قبلی را دارد و با باز کردنِ صفحه‌ای که بار نشده همان نام‌ها را می‌خواهد؛ بی‌این # ۴۰۴ و صفحه‌ی سفید می‌گرفت. cp -p زمانِ فایل را نگه می‌دارد، پس هر فایل ۳۰ روز بعد از buildِ خودش کنار می‌رود. if [ -d dist/assets ]; then find dist/assets -type f -mtime -30 -exec cp -pn {} dist.next/assets/ \; fi step "جابه‌جایی" rm -rf dist.prev [ -d dist ] && mv dist dist.prev mv dist.next dist API="$(grep -oh 'https\?://[a-zA-Z0-9.-]*/api' dist/assets/*.js | head -1)" printf ' منتشر شد. آدرسِ API در build: %s\n' "${API:-(هم‌دامنه /api یا config.js)}" printf ' کشِ آروان را برای index.html و config.js پاک کنید اگر تغییری دیده نشد.\n'