Fix production deploy: sync lockfile, Prisma engines, and build.

The Docker build failed because pnpm-lock.yaml was stale after removing Sanity
and adding Prisma/admin deps. Bundle debian-openssl-3.0.x engines for
node:20-slim, fix invalid tiptap package, and avoid DB access during next build.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Ali Taghavi
2026-05-19 00:48:32 +03:30
parent 548c08eb9d
commit a352d4e823
14 changed files with 884 additions and 8383 deletions

View File

@@ -19,11 +19,13 @@ FROM base AS builder
COPY --from=deps /app/node_modules ./node_modules
COPY . .
# Copy pre-downloaded Prisma engine binaries (linux-musl-openssl-3.0.x)
ENV PRISMA_QUERY_ENGINE_LIBRARY=/app/prisma-binaries/libquery_engine-linux-musl-openssl-3.0.x.so.node
ENV PRISMA_SCHEMA_ENGINE_BINARY=/app/prisma-binaries/schema-engine-linux-musl-openssl-3.0.x
# Pre-downloaded Prisma engines for node:20-slim (debian-openssl-3.0.x)
ENV PRISMA_QUERY_ENGINE_LIBRARY=/app/prisma-binaries/libquery_engine-debian-openssl-3.0.x.so.node
ENV PRISMA_SCHEMA_ENGINE_BINARY=/app/prisma-binaries/schema-engine-debian-openssl-3.0.x
RUN pnpm prisma generate
# Placeholder only — real DATABASE_URL is injected at runtime via docker-compose
ENV DATABASE_URL="mysql://build:build@localhost:3306/build"
RUN pnpm build
# ── runner ────────────────────────────────────────────────────────────────────
@@ -43,7 +45,7 @@ COPY --from=builder /app/prisma-binaries ./prisma-binaries
COPY --from=builder /app/node_modules/.prisma ./node_modules/.prisma
COPY --from=builder /app/node_modules/@prisma/client ./node_modules/@prisma/client
ENV PRISMA_QUERY_ENGINE_LIBRARY=/app/prisma-binaries/libquery_engine-linux-musl-openssl-3.0.x.so.node
ENV PRISMA_QUERY_ENGINE_LIBRARY=/app/prisma-binaries/libquery_engine-debian-openssl-3.0.x.so.node
COPY --from=builder /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./

View File

@@ -1,5 +1,3 @@
version: "3.9"
services:
db:
image: docker.arvancloud.ir/library/mariadb:11

View File

@@ -40,7 +40,7 @@
"@tiptap/extension-placeholder": "^2.11.0",
"@tiptap/extension-link": "^2.11.0",
"@tiptap/extension-image": "^2.11.0",
"@tiptap/extension-text-direction": "^2.11.0",
"tiptap-text-direction": "^0.4.0",
"@tiptap/extension-character-count": "^2.11.0"
},
"pnpm": {

9161
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

View File

View File

@@ -1,5 +1,6 @@
generator client {
provider = "prisma-client-js"
provider = "prisma-client-js"
binaryTargets = ["native", "debian-openssl-3.0.x"]
}
datasource db {

View File

@@ -0,0 +1,14 @@
#!/usr/bin/env bash
# Download Prisma engines for node:20-slim (debian-openssl-3.0.x). Run on a machine with internet.
set -euo pipefail
cd "$(dirname "$0")/.."
pnpm install --registry https://registry.npmjs.org
PRISMA_CLI_BINARY_TARGETS=debian-openssl-3.0.x pnpm exec prisma generate
ENGINES="$(find node_modules -path '*/@prisma/engines' -type d | head -1)"
mkdir -p prisma-binaries
cp "$ENGINES/libquery_engine-debian-openssl-3.0.x.so.node" prisma-binaries/
cp "$ENGINES/schema-engine-debian-openssl-3.0.x" prisma-binaries/
chmod +x prisma-binaries/schema-engine-debian-openssl-3.0.x
ls -lh prisma-binaries/

55
scripts/server-deploy.sh Executable file
View File

@@ -0,0 +1,55 @@
#!/usr/bin/env bash
# Manual deploy on NODE Cloud (no CI). Run on the server as root.
set -euo pipefail
APP_DIR="/srv/nodecloud/apps/biztaghavisite"
PORT=3009
cd "$APP_DIR"
echo "==> Pull latest code"
git pull origin main
echo "==> Ensure Docker network"
docker network inspect nodecloud-net >/dev/null 2>&1 || docker network create nodecloud-net
if [[ ! -f .env ]]; then
echo "ERROR: $APP_DIR/.env missing. Copy from .env.example and fill values."
exit 1
fi
echo "==> Build image (1020 min on first run; Liara mirror may be slow)"
docker compose build --no-cache
echo "==> Start MariaDB + app"
docker compose up -d
echo "==> Wait for MariaDB"
for i in $(seq 1 30); do
if docker compose exec -T db healthcheck.sh --connect --innodb_initialized >/dev/null 2>&1; then
echo " MariaDB healthy"
break
fi
sleep 2
done
echo "==> Apply database schema (idempotent)"
set -a
# shellcheck disable=SC1091
source .env
set +a
docker compose exec -T db mariadb -u "${DB_USER:-biztaghavi}" -p"${DB_PASSWORD}" "${DB_NAME:-biztaghavi}" \
< prisma/migrations/0_init/migration.sql || true
echo "==> Health check"
sleep 3
curl -sf "http://127.0.0.1:${PORT}/" -o /dev/null && echo "OK: app responds on :${PORT}" || {
echo "FAIL: app not responding"
docker compose logs --tail=80 next-app
exit 1
}
curl -sI -H "Host: biztaghavi.com" "http://127.0.0.1/" | head -5
docker compose ps
echo ""
echo "Done. Test https://biztaghavi.com — purge ArvanCloud cache if needed."
echo "Create admin once: curl -X POST https://biztaghavi.com/api/admin/setup ..."

View File

@@ -4,7 +4,7 @@ import Image from "next/image";
import { getTranslations } from "next-intl/server";
import { ExternalLink, Github, ArrowLeft, ArrowRight } from "lucide-react";
import { Link } from "@/lib/i18n/navigation";
import { getProjectBySlug, getAllProjectSlugs } from "@/lib/db/projects";
import { getProjectBySlug } from "@/lib/db/projects";
import { TiptapRenderer } from "@/components/writing/TiptapRenderer";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
@@ -13,11 +13,6 @@ interface Props {
params: Promise<{ locale: string; slug: string }>;
}
export async function generateStaticParams() {
const slugs = await getAllProjectSlugs();
return slugs.map((s) => ({ slug: s.slug }));
}
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { slug } = await params;
const project = await getProjectBySlug(slug);

View File

@@ -4,7 +4,7 @@ import Image from "next/image";
import { getTranslations } from "next-intl/server";
import { ArrowRight, ArrowLeft, Clock } from "lucide-react";
import { Link } from "@/lib/i18n/navigation";
import { getPostBySlug, getRelatedPosts, getAllPostSlugs, estimateReadTime } from "@/lib/db/posts";
import { getPostBySlug, getRelatedPosts, estimateReadTime } from "@/lib/db/posts";
import { TiptapRenderer } from "@/components/writing/TiptapRenderer";
import { ReadingProgress } from "@/components/writing/ReadingProgress";
import { ShareButtons } from "@/components/writing/ShareButtons";
@@ -16,11 +16,6 @@ interface Props {
params: Promise<{ locale: string; slug: string }>;
}
export async function generateStaticParams() {
const slugs = await getAllPostSlugs();
return slugs.map((s) => ({ slug: s.slug }));
}
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { slug } = await params;
const post = await getPostBySlug(slug);

View File

@@ -8,7 +8,7 @@ export async function POST(req: NextRequest) {
return Response.json({ message: "Invalid secret" }, { status: 401 });
}
revalidateTag("posts");
revalidateTag("projects");
revalidateTag("posts", "max");
revalidateTag("projects", "max");
return Response.json({ revalidated: true, now: Date.now() });
}

View File

@@ -5,7 +5,7 @@ import StarterKit from "@tiptap/starter-kit";
import Placeholder from "@tiptap/extension-placeholder";
import Link from "@tiptap/extension-link";
import Image from "@tiptap/extension-image";
import TextDirection from "@tiptap/extension-text-direction";
import TextDirection from "tiptap-text-direction";
import { useEffect, useCallback } from "react";
import {
Bold, Italic, Strikethrough, Code, Heading1, Heading2, Heading3,