Développement
Dans le conteneur dev (recommandé)
docker build -f Dockerfile.local --build-arg UID=$(id -u) --build-arg GID=$(id -g) -t wap-crm:dev .
docker run -d --name wap-crm-dev --network proxy -v "$PWD":/app wap-crm:dev
docker exec -it wap-crm-dev bash
bun install
bun run dev # convex dev + vite (nécessite `bunx convex login` une fois)
bun run dev:frontend # vite seul (les fonctions déjà déployées suffisent)
Aucun port n'est publié : le conteneur rejoint le réseau proxy et le reverse
proxy de l'hôte route vers wap-crm-dev:4202 (vite écoute sur 0.0.0.0:4202).
Sans reverse proxy, remplacer --network proxy par -p 4202:4202 et ouvrir
http://localhost:4202. L'entrypoint du conteneur est inerte (tail -f) :
relancer docker exec -d wap-crm-dev bun run dev:frontend après un redémarrage.
Directement sur la machine
bun install
bun run dev
Scripts
| Script | Effet |
|---|---|
bun run dev | convex dev + vite en parallèle |
bun run dev:frontend / dev:backend | l'un des deux seulement |
bun run build | tsc --noEmit + vite build → dist/ |
bun run typecheck | tsconfig app + tsconfig convex + tsconfig tests |
bun run codegen | régénère convex/_generated (commité) |
bun run openapi | régénère convex/lib/api/openapi.generated.ts depuis docs/openapi.yaml (commité) |
bun run lint | Biome : format et règles ; un avertissement fait échouer la commande |
bun run unused | knip : exports, types, fichiers et dépendances que rien n'utilise (knip.ts) ; la CI échoue s'il en trouve. Ce qu'une surcouche importe est listé dans tests/*/extensionSurface.test.ts et compte comme utilisé |
bun run test | lance les suites bun:test |
bun run test:watch | idem, en mode watch |
Tests
Tous les tests sont sous tests/ (backend/ avec convex-test, frontend/),
aucun dans convex/ ni src/. tests/setup.ts, chargé avant chaque fichier
(bunfig.toml), pose quatre règles :
- Aucun test ne sort de la machine : une requête que le test n'a pas simulée est refusée et fait échouer le test qui l'a émise.
- Rien ne survit à un test : à la fin de chacun, ce qu'il a planifié sans l'exécuter est annulé, pour ne pas s'exécuter plus tard dans un autre test.
- L'horloge appartient au test : un backend de test (
createTestConvex) tourne sur une horloge que seul le test fait avancer ; rien de planifié ne part tout seul.runDue(t)exécute ce qui est dû maintenant,runAfter(t, ms)avance puis exécute,runAll(t)exécute tout,pinClock(date)fixe la date de départ. - Un test dit avec quelles extensions il tourne : un overlay assemblé sur
le cœur lance cette même suite, avec les siennes. Un test qui décrit le
cœur seul le dit (
setFrontendExtensionsForTests({})côté interface,setExtensionsForTests(defaultExtensions)côté backend), un test qui décrit un point d'extension le pose ; à la fin de chaque test, les deux côtés retrouvent les extensions installées. Aucun test n'écrit sursrc/extensions.tsx.
Les tests sont typés comme le reste (tests/tsconfig.json, dans
bun run typecheck). La suite ne dépend pas de l'ordre : la CI la lance dans
un ordre aléatoire (bun test --randomize, la graine est affichée pour
rejouer un échec avec --seed). Elle la lance une seconde fois avec un
overlay de substitution (bun run test:stand-in) : src/extensions.tsx
est remplacé par tests/support/overlayStandIn.ts, où chaque point
d'extension est défini et répond à tout, et convex/extensions.ts par
tests/support/backendStandIn.ts, qui répond là où une réponse se voit et
ne refuse rien, comme l'overlay privé sans tenant configuré ; un test du
cœur seul qui ne le dit pas échoue là, avant d'échouer chez un overlay.
Bootstrap & connexion locale
Un déploiement neuf n'a aucun utilisateur : créer un premier employé (seuls
les utilisateurs employee accèdent au CRM) :
bunx convex run seed/devEmployee:createDevEmployee '{"email":"you@example.com","firstName":"You","lastName":"Example"}'
La connexion passe ensuite par un code envoyé par e-mail : il faut un
fournisseur d'e-mail configuré (Brevo ou SMTP, Paramètres → E-mail), et
l'adresse dans DEV_WHITELIST_EMAILS si la liste est définie. Il n'existe pas
de porte dérobée de session.