Image Docker, CI et docs pour le serveur Rust

Image en trois étapes (frontend Vite, binaire Rust, Debian slim avec
FFmpeg), healthcheck /healthz, dépendances npm propres à l'ancien
serveur retirées, CI Rust (fmt, clippy, tests) et documentation de la
mise à jour au prochain démarrage de la stack.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013qMexTfVRaegbPAgsagCAJ
This commit is contained in:
Claude committed 2026-10-12 17:20:38 +00:00
1 parent f9828d4887
commit 25703abb7d
14 files changed
+272 -8270

No files matched your search

+5
View File
@@ -38,3 +38,8 @@ logs
# Uploads (seront montés comme volume)
uploads/*
# Compilation Rust
server/target
**/target
.claude
+20 -4
View File
@@ -6,8 +6,8 @@ on:
pull_request:
jobs:
app:
name: Application (TypeScript)
frontend:
name: Frontend (TypeScript)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
@@ -15,13 +15,29 @@ jobs:
with:
node-version: 20
cache: npm
# Les dépendances natives (canvas, sharp) et le navigateur Remotion ne
# servent pas aux vérifications : on saute leurs scripts d'installation.
- run: npm ci --ignore-scripts
- run: npm run check
- run: npm test
- run: npm run build
server:
name: Serveur (Rust)
runs-on: ubuntu-latest
defaults:
run:
working-directory: server
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy
- uses: Swatinem/rust-cache@v2
with:
workspaces: server
- run: cargo fmt --check
- run: cargo clippy --locked --all-targets -- -D warnings
- run: cargo test --locked
ffmpeg-service:
name: Service vidéo (Python)
runs-on: ubuntu-latest
+1
View File
@@ -35,3 +35,4 @@ npm-debug.log*
Thumbs.db
# Rust
server/target
.claude/worktrees
+30 -14
View File
@@ -4,7 +4,8 @@
L'application utilise Docker Compose pour orchestrer :
- **PostgreSQL 16** : Base de données sur réseau interne
- **Social Flow App** : Application Node.js exposée sur le port **4523**
- **Social Flow App** : serveur Rust (API + frontend compilé), port interne **5555**
- **ffmpeg-api** / **remotion-renderer** : services de traitement vidéo des Reels
## 🚀 Démarrage rapide
@@ -203,7 +204,8 @@ La stack se construit depuis les sources : `app` et `ffmpeg-api` ont une section
## 📊 Healthchecks
- **PostgreSQL** : Vérifie que la base est prête avant de démarrer l'app
- **Migrations** : Exécutées automatiquement au démarrage (`drizzle-kit push --force`)
- **Migrations** : exécutées automatiquement par le serveur à chaque démarrage (idempotentes)
- **Application** : `GET /healthz` répond `ok` quand le serveur et la base sont joignables (healthcheck de l'image)
## 🐛 Dépannage
@@ -232,23 +234,37 @@ Vérifiez que `DATABASE_URL` dans `.env` correspond aux variables `PGUSER`, `PGP
## 📦 Volumes Docker
- `postgres_data` : Données persistantes de PostgreSQL
- Mappages locaux :
- `./attached_assets` → `/app/attached_assets` (médias uploadés)
- `./migrations` → `/app/migrations` (migrations DB)
- `postgres_data` : données persistantes de PostgreSQL
- `encryption_key` : clé de chiffrement des jetons (`/app/.encryption-key-data`)
- `audio_uploads`, `media_uploads`, `logos_uploads`, `stories_uploads` : fichiers téléversés (`/app/uploads/...`)
## 🔄 Mise à jour de l'application
L'image `socialflow-app` est construite à partir des sources
(`pull_policy: build`) : le prochain démarrage de la stack après un `git pull`
applique la nouvelle version.
```bash
# 1. Pull les dernières modifications
# 1. Récupérer les dernières modifications
git pull
# 2. Reconstruire l'image
docker-compose build
# 2. Reconstruire et redémarrer (Portainer : « Update the stack »)
docker compose up -d --build
# 3. Redémarrer
docker-compose up -d
# 4. Vérifier les logs
docker-compose logs -f app
# 3. Vérifier les logs
docker compose logs -f app
```
Au démarrage, le serveur complète le schéma de la base (tables, colonnes,
index) : aucune commande de migration n'est nécessaire. Les données, les
fichiers téléversés, la clé de chiffrement et les sessions ouvertes sont
conservés.
### Passage au serveur Rust
Depuis la version 2, le serveur est écrit en Rust (dossier `server/`) et
remplace l'ancien serveur Node/Express. La bascule ne demande rien de plus
qu'une reconstruction : mêmes variables d'environnement, même base, mêmes
volumes, et les utilisateurs déjà connectés le restent (cookie `connect.sid`
et table `session` repris tels quels). Le rendu Remotion des Reels tourne
désormais dans son propre conteneur (`remotion-renderer`).
+60 -76
View File
@@ -1,84 +1,68 @@
# Debian slim — glibc requis par le Chrome headless shell de Remotion (posix_fallocate64, etc.)
FROM node:20-slim
# SocialFlow — image de l'application : serveur Rust + frontend React compilé.
#
# Trois étapes : le frontend est compilé par Vite, le serveur par Cargo, et
# l'image finale ne contient que le binaire, les fichiers statiques et FFmpeg
# (vignettes vidéo, durée des pistes audio). Pas de Node à l'exécution.
# ---------------------------------------------------------------------------
# 1. Frontend (Vite)
# ---------------------------------------------------------------------------
FROM node:20-slim AS frontend
WORKDIR /app
COPY package.json package-lock.json ./
# Aucune dépendance native n'est utile au build du frontend
RUN npm ci --ignore-scripts --no-audit --no-fund
COPY client ./client
COPY shared ./shared
COPY attached_assets ./attached_assets
COPY vite.config.ts tsconfig.json tailwind.config.ts postcss.config.js components.json ./
RUN npx vite build
# ---------------------------------------------------------------------------
# 2. Serveur (Rust)
# ---------------------------------------------------------------------------
FROM rust:1-slim-bookworm AS backend
WORKDIR /app/server
# Dépendances compilées dans une couche à part : elles ne sont recompilées que
# si Cargo.toml / Cargo.lock changent.
COPY server/Cargo.toml server/Cargo.lock ./
RUN mkdir src && echo 'fn main() {}' > src/main.rs \
&& cargo build --release --locked \
&& rm -rf src target/release/socialflow target/release/deps/socialflow-* target/release/.fingerprint/socialflow-*
COPY server/src ./src
RUN cargo build --release --locked
# ---------------------------------------------------------------------------
# 3. Image d'exécution
# ---------------------------------------------------------------------------
FROM debian:bookworm-slim
RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates \
ffmpeg \
fonts-dejavu-core \
fontconfig \
curl \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
# Augmenter la mémoire AVANT npm install + build (évite OOM sur les grosses dépendances)
ENV NODE_OPTIONS="--max-old-space-size=4096"
COPY --from=backend /app/server/target/release/socialflow /usr/local/bin/socialflow
COPY --from=frontend /app/dist/public ./dist/public
COPY public-root ./public-root
COPY server/fonts ./server/fonts
# Dépendances système : build tools, canvas/sharp, fonts, ffmpeg + dépendances Chrome
RUN apt-get update && apt-get install -y --no-install-recommends \
python3 make g++ \
libcairo2-dev libjpeg-dev libpango1.0-dev libgif-dev libpixman-1-dev \
libvips-dev pkg-config \
fonts-dejavu-core fontconfig \
curl tar bzip2 \
ffmpeg \
libcap2-bin \
ca-certificates \
# Dépendances Chrome headless
libasound2 \
libatk-bridge2.0-0 \
libatk1.0-0 \
libcups2 \
libdbus-1-3 \
libdrm2 \
libgbm1 \
libglib2.0-0 \
libgtk-3-0 \
libnspr4 \
libnss3 \
libpango-1.0-0 \
libx11-6 \
libx11-xcb1 \
libxcb1 \
libxcomposite1 \
libxdamage1 \
libxext6 \
libxfixes3 \
libxkbcommon0 \
libxrandr2 \
libxshmfence1 \
libxtst6 \
&& rm -rf /var/lib/apt/lists/*
# Copier les fichiers de configuration
COPY package*.json ./
# Installer les dépendances
RUN npm install --legacy-peer-deps --no-audit --no-fund
# Copier tout le code source
COPY . .
# Télécharger et installer la police DejaVu Sans
RUN mkdir -p server/fonts && \
cd server/fonts && \
curl -L -f --retry 3 --retry-delay 2 -o dejavu.tar.bz2 "https://sourceforge.net/projects/dejavu/files/dejavu/2.37/dejavu-fonts-ttf-2.37.tar.bz2/download" && \
tar -xjf dejavu.tar.bz2 && \
cp dejavu-fonts-ttf-2.37/ttf/DejaVuSans.ttf . && \
rm -rf dejavu.tar.bz2 dejavu-fonts-ttf-2.37 && \
echo "✓ Police DejaVu Sans installée"
# Build du frontend et backend
RUN npm run build
# Nettoyer les fichiers inutiles
RUN rm -rf client/node_modules \
client/.vite \
node_modules/.cache \
/tmp/remotion-*
# Pré-bundler la composition Remotion
RUN node scripts/prebundle-remotion.mjs || true
# Télécharger le Chrome headless shell de Remotion (binaire glibc natif sur Debian)
RUN npx remotion browser ensure && echo "✓ Remotion browser prêt"
# Exposer le port
EXPOSE 5555
RUN mkdir -p uploads/media uploads/logos uploads/stories uploads/audio uploads/temp
ENV NODE_ENV=production
ENV PORT=5555
EXPOSE 5555
CMD ["npm", "run", "start"]
HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \
CMD curl -fsS "http://localhost:${PORT}/healthz" > /dev/null || exit 1
CMD ["socialflow"]
+45 -34
View File
@@ -100,9 +100,9 @@ Architecture Production-Ready avec sécurité renforcée.
| Fonctionnalité | Détail |
| ------------------------------- | ---------------------------------------------------- |
| **Authentification** | Passport.js + bcrypt, sessions PostgreSQL |
| **Rate Limiting** | 100 req/15min global, 5 tentatives login/15min |
| **Headers de sécurité** | Helmet (CSP, HSTS, XSS protection) |
| **Authentification** | bcrypt, sessions PostgreSQL (cookie signé) |
| **Rate Limiting** | 1000 req/15min global, 30 tentatives login/15min |
| **Headers de sécurité** | CSP, HSTS, X-Frame-Options, nosniff… |
| **Chiffrement des tokens** | Tokens Facebook/Instagram chiffrés (AES-256) |
| **Gestion multi-utilisateurs** | Rôles Admin / User, permissions par page |
| **Console SQL** | Requêtes directes en base (admin uniquement) |
@@ -117,43 +117,53 @@ Architecture Production-Ready avec sécurité renforcée.
socialflow/
├── client/ # Frontend React (Vite + TailwindCSS)
│ └── src/
│ ├── pages/ # 17 pages desktop + 13 pages mobile
│ ├── components/ # 47 composants UI (Radix/shadcn)
│ └── hooks/ # Hooks React personnalisés
├── server/ # Backend Express.js (TypeScript)
│ ├── routes.ts # 45+ endpoints REST API
│ ├── routes/ # Routes modulaires (analytics, reels)
│ ├── services/ # 12 services métier
│ │ ├── analytics.ts # Sync Facebook Graph API
│ │ ├── facebook.ts # Publication Facebook/Instagram
│ │ ├── openrouter.ts # Génération texte IA
│ │ ├── cloudinary.ts # Upload cloud médias
│ │ ├── ffmpeg.ts # Encodage vidéo (microservice)
│ │ ├── freesound.ts # Musique libre de droits
│ │ ├── jamendo.ts # Musique libre de droits
│ │ ├── imageProcessor.ts # Traitement images (Sharp)
│ │ ├── scheduler.ts # Publications programmées
│ │ └── token_manager.ts # Gestion tokens OAuth
│ └── cron.ts # Tâches planifiées
├── shared/ # Schéma DB partagé (Drizzle ORM)
│ ├── pages/ # Pages desktop + mobile
│ ├── components/ # Composants UI (Radix/shadcn)
│ └── remotion/ # Compositions vidéo des Reels (aperçu + rendu)
├── server/ # Backend Rust (axum + sqlx)
│ ├── Cargo.toml
│ └── src/
│ ├── main.rs # Démarrage : migrations, routes, tâches de fond
│ ├── migrate.rs # Schéma PostgreSQL, mis à jour au démarrage
│ ├── auth.rs # Sessions (cookie connect.sid) et rôles
│ ├── db/ # Accès aux données
│ ├── routes/ # API REST (/api, /api/v1 externe, pages légales)
│ └── services/ # Facebook, TikTok, OpenRouter, Reels, scheduler, crons
├── shared/ # Types et logique partagés par le frontend
├── remotion-service/ # Rendu des Reels avec Remotion (Node + Chrome)
├── ffmpeg-service/ # Microservice FFmpeg (Python/FastAPI)
├── docker-compose.yml # Stack Docker (3 services)
└── Dockerfile # Image production Node.js
├── qwen-tts/ # Voix Qwen3-TTS (optionnel)
├── docker-compose.yml # Stack Docker
└── Dockerfile # Image de l'application (Rust + frontend compilé)
```
### Stack Technique
| Couche | Technologies |
| ---------- | --------------------------------------------------------------------------- |
| **Frontend** | React 18, Vite, TailwindCSS, Radix UI, Recharts, Framer Motion, Wouter |
| **Backend** | Express.js, TypeScript, Passport.js, Helmet, node-cron |
| **BDD** | PostgreSQL 16, Drizzle ORM, connect-pg-simple (sessions) |
| **Frontend** | React 18, Vite, TailwindCSS, Radix UI, Recharts, Wouter |
| **Backend** | Rust : axum, tokio, sqlx, reqwest |
| **BDD** | PostgreSQL 16 (schéma créé et migré automatiquement au démarrage) |
| **IA** | OpenRouter (multi-modèle : GPT-4, Claude, Gemini, etc.) |
| **Médias** | Cloudinary (upload), Sharp (images), FFmpeg microservice (vidéo) |
| **Musique** | FreeSound API, Jamendo API |
| **Réseaux** | Facebook Graph API v19 (Pages, Instagram, Insights) |
| **Médias** | Stockage local, FFmpeg (vignettes, microservice vidéo), Remotion (Reels) |
| **Réseaux** | Facebook Graph API (Pages, Instagram, Insights), TikTok Content Posting |
| **Infra** | Docker Compose, Nginx reverse proxy |
### Mise à jour
L'image de l'application est construite à partir des sources (`pull_policy: build`).
Après un `git pull`, le prochain démarrage de la stack reconstruit l'image et
applique la nouvelle version :
```bash
git pull
docker compose up -d --build
```
Au démarrage, le serveur met le schéma de la base à jour tout seul (aucune
commande de migration à lancer). Les données, les médias, la clé de chiffrement
et les sessions ouvertes sont conservés.
---
## 🚀 Installation Rapide
@@ -161,7 +171,7 @@ socialflow/
### Prérequis
- [Docker](https://www.docker.com/) & Docker Compose
- [Node.js](https://nodejs.org/) v18+ (pour le développement local)
- Pour le développement local : [Rust](https://rustup.rs/) (stable), [Node.js](https://nodejs.org/) 20+ et PostgreSQL
### 1. Récupérer l'application
@@ -199,11 +209,12 @@ FACEBOOK_APP_SECRET=...
```bash
# Production (Docker)
docker-compose up -d
docker compose up -d --build
# Développement local
# Développement local (deux terminaux)
npm install
npm run dev
DATABASE_URL=postgresql://... npm run dev:server # API Rust sur le port 5000
npm run dev # Frontend Vite, relaie /api vers le port 5000
```
🚀 Rendez-vous sur **`http://localhost:5555`** pour commencer !
-1
View File
@@ -69,7 +69,6 @@ services:
- media_uploads:/app/uploads/media
- logos_uploads:/app/uploads/logos
- stories_uploads:/app/uploads/stories
command: npm run start
# Service FFmpeg dédié
ffmpeg-api:
+89 -8090
View File
File diff suppressed because it is too large. Load diff
+4 -48
View File
@@ -4,22 +4,19 @@
"type": "module",
"license": "MIT",
"scripts": {
"dev": "NODE_ENV=development tsx server/index.ts",
"build": "vite build && esbuild server/index.ts --platform=node --packages=external --bundle --format=esm --outdir=dist",
"start": "NODE_ENV=production node dist/index.js",
"dev": "vite",
"dev:server": "cargo run --manifest-path server/Cargo.toml",
"build": "vite build",
"start": "cargo run --release --manifest-path server/Cargo.toml",
"check": "tsc",
"db:push": "drizzle-kit push",
"test": "vitest run"
},
"dependencies": {
"@aws-sdk/client-s3": "^3.1016.0",
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/sortable": "^10.0.0",
"@dnd-kit/utilities": "^3.2.2",
"@fontsource/montserrat": "^5.3.0",
"@hookform/resolvers": "^3.10.0",
"@jridgewell/trace-mapping": "^0.3.25",
"@neondatabase/serverless": "^0.10.4",
"@radix-ui/react-accordion": "^1.2.4",
"@radix-ui/react-alert-dialog": "^1.1.7",
"@radix-ui/react-aspect-ratio": "^1.1.3",
@@ -47,46 +44,20 @@
"@radix-ui/react-toggle": "^1.1.3",
"@radix-ui/react-toggle-group": "^1.1.3",
"@radix-ui/react-tooltip": "^1.2.0",
"@remotion/bundler": "^4.0.438",
"@remotion/cli": "^4.0.438",
"@remotion/player": "^4.0.438",
"@remotion/renderer": "^4.0.438",
"@tanstack/react-query": "^5.60.5",
"@twemoji/api": "^17.0.2",
"@types/bcrypt": "^6.0.0",
"@types/multer": "^2.0.0",
"@types/node-cron": "^3.0.11",
"@types/node-fetch": "^2.6.13",
"@types/pg": "^8.15.5",
"bcrypt": "^6.0.0",
"canvas": "^2.11.2",
"class-variance-authority": "^0.7.1",
"cloudinary": "^2.7.0",
"clsx": "^2.1.1",
"cmdk": "^1.1.1",
"connect-pg-simple": "^10.0.0",
"date-fns": "^3.6.0",
"drizzle-orm": "^0.39.1",
"drizzle-zod": "^0.7.0",
"embla-carousel-react": "^8.6.0",
"emoji-regex": "^10.6.0",
"express": "^4.21.2",
"express-rate-limit": "^7.5.1",
"express-session": "^1.18.1",
"framer-motion": "^11.13.1",
"helmet": "^8.1.0",
"html-to-image": "^1.11.13",
"html2canvas": "^1.4.1",
"input-otp": "^1.4.2",
"lucide-react": "^0.453.0",
"memorystore": "^1.6.7",
"multer": "^2.0.2",
"music-metadata": "^11.12.3",
"next-themes": "^0.4.6",
"node-cron": "^4.2.1",
"passport": "^0.7.0",
"passport-local": "^1.0.0",
"pg": "^8.16.3",
"react": "^18.3.1",
"react-day-picker": "^8.10.1",
"react-dom": "^18.3.1",
@@ -97,15 +68,12 @@
"react-webcam": "^7.2.0",
"recharts": "^2.15.2",
"remotion": "^4.0.438",
"sharp": "^0.34.4",
"tailwind-merge": "^2.6.0",
"tailwindcss-animate": "^1.0.7",
"tw-animate-css": "^1.2.5",
"twemoji": "^14.0.2",
"twemoji-parser": "^14.0.0",
"vaul": "^1.1.2",
"wouter": "^3.3.5",
"ws": "^8.18.0",
"zod": "^3.24.2",
"zod-validation-error": "^3.4.0"
},
@@ -115,27 +83,15 @@
"@replit/vite-plugin-runtime-error-modal": "^0.0.3",
"@tailwindcss/typography": "^0.5.15",
"@tailwindcss/vite": "^4.1.3",
"@types/connect-pg-simple": "^7.0.3",
"@types/express": "4.17.21",
"@types/express-session": "^1.18.0",
"@types/node": "20.16.11",
"@types/passport": "^1.0.16",
"@types/passport-local": "^1.0.38",
"@types/react": "^18.3.11",
"@types/react-dom": "^18.3.1",
"@types/ws": "^8.5.13",
"@vitejs/plugin-react": "^4.7.0",
"autoprefixer": "^10.4.20",
"drizzle-kit": "^0.31.4",
"esbuild": "^0.25.0",
"postcss": "^8.4.47",
"tailwindcss": "^3.4.17",
"tsx": "^4.20.5",
"typescript": "5.6.3",
"vite": "^5.4.20",
"vitest": "^3.2.7"
},
"optionalDependencies": {
"bufferutil": "^4.0.8"
}
}
+1 -1
View File
@@ -32,7 +32,7 @@ Preferred communication style: Simple, everyday language.
The frontend is built with React and TypeScript, leveraging Vite for bundling and Wouter for routing. State management is primarily handled by TanStack Query. UI components are constructed using Radix UI primitives and shadcn/ui, styled with Tailwind CSS, emphasizing a component-based, responsive, mobile-first design with real-time data synchronization. Dark mode is the default theme.
### Backend Architecture
The backend is developed in Node.js with Express.js, entirely in TypeScript. It adheres to a RESTful API design with dedicated endpoints for authentication, user management, AI content generation, media handling, social page management, and post scheduling. Multer is utilized for file uploads, integrated with Cloudinary. Node-cron manages background processes for scheduled post publication. The architecture incorporates a service layer, storage abstraction, and a middleware-based request processing pipeline. Session management is handled by `express-session` with `connect-pg-simple`.
The backend is written in Rust (axum, tokio, sqlx) in the `server/` crate. It exposes the same RESTful API as the former Node/Express server (authentication, user management, AI content generation, media handling, social page management, post scheduling, external API). Uploads are stored locally under `uploads/`. Background tasks (scheduled publications, token checks, analytics, media purge, Reel render queue) run as tokio tasks. Sessions are stored in the PostgreSQL `session` table with a signed `connect.sid` cookie, compatible with the former server. The database schema is created and migrated automatically at startup (`server/src/migrate.rs`). Remotion Reel rendering runs in a separate Node sidecar (`remotion-service/`).
### Database Architecture
PostgreSQL serves as the database, interfaced using Drizzle ORM for type-safe operations and Neon serverless driver for WebSocket support. The schema includes tables for `users`, `cloudinary_config`, `social_pages`, `media`, `posts`, `post_media`, `scheduled_posts`, and `ai_generations`. Drizzle Kit is used for schema migrations.
+9
View File
@@ -24,6 +24,7 @@ use crate::state::AppState;
pub fn router(state: AppState) -> Router {
let api = Router::new()
.route("/healthz", axum::routing::get(healthz))
.merge(auth::router())
.merge(core::router())
.nest("/api/analytics", analytics::router())
@@ -56,6 +57,14 @@ pub fn router(state: AppState) -> Router {
.with_state(state)
}
/// Sonde de santé (Docker) : le serveur répond et la base est joignable.
async fn healthz(State(state): State<AppState>) -> Response {
match sqlx::query_scalar::<_, i32>("SELECT 1").fetch_one(&state.db.pool).await {
Ok(_) => (StatusCode::OK, "ok").into_response(),
Err(_) => (StatusCode::SERVICE_UNAVAILABLE, "database unavailable").into_response(),
}
}
/// Route inconnue : JSON pour `/api`, sinon fichiers publics puis l'application React.
async fn fallback(State(state): State<AppState>, headers: HeaderMap, request: Request) -> Response {
let path = request.uri().path().to_string();
+1 -1
View File
@@ -1,5 +1,5 @@
{
"include": ["client/src/**/*", "shared/**/*", "server/**/*"],
"include": ["client/src/**/*", "shared/**/*"],
"exclude": ["node_modules", "build", "dist", "**/*.test.ts"],
"compilerOptions": {
"incremental": true,
+6
View File
@@ -32,6 +32,12 @@ export default defineConfig({
emptyOutDir: true,
},
server: {
// En développement, l'API et les fichiers sont servis par le serveur Rust
// (`npm run dev:server`, port 5000 par défaut).
proxy: {
"/api": process.env.API_URL ?? "http://localhost:5000",
"/uploads": process.env.API_URL ?? "http://localhost:5000",
},
fs: {
strict: true,
deny: ["**/.*"],
+1 -1
View File
@@ -9,7 +9,7 @@ export default defineConfig({
},
},
test: {
include: ["shared/**/*.test.ts", "server/**/*.test.ts"],
include: ["shared/**/*.test.ts"],
environment: "node",
},
});