Compare commits
2 Commits
c4910889de
...
fc66775109
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fc66775109 | ||
|
|
77d52ea95c |
220
.claude/skills/push/SKILL.md
Normal file
220
.claude/skills/push/SKILL.md
Normal file
@ -0,0 +1,220 @@
|
||||
---
|
||||
name: push
|
||||
description: "Release automatisée Rubis : bump de version, ajout d'une entrée changelog, commit conventionnel et push. À utiliser quand l'utilisateur tape `/push` ou demande explicitement de déployer / sortir une nouvelle version / pousser les changements. Le skill orchestre les fichiers couplés (`apps/web/src/version.ts` + `apps/landing/src/content/changelog/<x.y.z>.md`) pour éviter de pusher une version SPA dont le toast pointe sur une ancre absente."
|
||||
allowed-tools:
|
||||
- Bash
|
||||
- Read
|
||||
- Write
|
||||
- Edit
|
||||
---
|
||||
|
||||
# `/push` — Release automatisée Rubis
|
||||
|
||||
Ce skill exécute le ritual de release complet du repo Rubis :
|
||||
|
||||
1. **Inspection** des changements en attente (`git status` + `git diff`)
|
||||
2. **Détermination du type** de release (`feature` / `improvement` / `fix`)
|
||||
3. **Bump semver** de `APP_VERSION` dans `apps/web/src/version.ts`
|
||||
4. **Création** de `apps/landing/src/content/changelog/<new-version>.md`
|
||||
5. **Stage + commit** avec message conventionnel (scope + body)
|
||||
6. **Push** sur `gitea/main`
|
||||
|
||||
Le double-fichier `version.ts` + `<version>.md` **doit** atterrir dans le même commit, sinon le toast SPA `<VersionToast/>` pointera sur une ancre absente côté `/changelog`. C'est précisément pour éviter cette désynchronisation qu'on a un skill dédié.
|
||||
|
||||
## Quand utiliser ce skill
|
||||
|
||||
Déclenche-toi automatiquement quand :
|
||||
- L'utilisateur tape `/push`
|
||||
- L'utilisateur demande "sors une nouvelle version", "release ça", "push avec changelog", "deploy"
|
||||
- L'utilisateur dit "commit + bump" ou variantes
|
||||
|
||||
**Ne déclenche PAS** ce skill quand :
|
||||
- L'utilisateur veut juste un commit local sans push (`/commit`)
|
||||
- L'utilisateur veut push une branche autre que `main`
|
||||
- L'utilisateur dit explicitement "sans bump" ou "pas de release" → fais un commit + push standard à la place
|
||||
|
||||
## Step 1 — Inspecter les changements
|
||||
|
||||
```bash
|
||||
git status --short
|
||||
git diff --stat HEAD
|
||||
git log --oneline -5 # pour le style de commit message
|
||||
```
|
||||
|
||||
Lis aussi `apps/web/src/version.ts` pour récupérer la version courante :
|
||||
|
||||
```bash
|
||||
grep -E '^export const APP_VERSION' apps/web/src/version.ts
|
||||
```
|
||||
|
||||
Extrais le semver (`X.Y.Z`).
|
||||
|
||||
**Si aucun changement** (`git status --short` vide) → stop, dis à l'utilisateur "Rien à committer". Ne crée pas une release vide.
|
||||
|
||||
**Si la branche n'est pas `main`** → demande confirmation avant de continuer (`git branch --show-current`). Si l'utilisateur est sur une branche feature, peut-être qu'il veut juste commit, pas release.
|
||||
|
||||
## Step 2 — Déterminer le type de release
|
||||
|
||||
Analyse les diffs et propose un type. Heuristiques :
|
||||
|
||||
| Signal dans les diffs | Type suggéré | Bump |
|
||||
|---|---|---|
|
||||
| Nouveau fichier de feature (component, route, page) | `feature` | minor (X.**Y**.0 → X.**Y+1**.0) |
|
||||
| Modif comportement utilisateur observable, nouvelle interaction | `feature` | minor |
|
||||
| Amélioration UI/UX, refactor visible, perf | `improvement` | patch (X.Y.**Z** → X.Y.**Z+1**) |
|
||||
| Correction d'un bug | `fix` | patch |
|
||||
| Modif uniquement docs (`/docs/`, `*.md` hors changelog) ou tests | → suggère de skip le bump | — |
|
||||
| Modif uniquement assets/marketing (`/docs/*.html`, images) | → suggère de skip le bump | — |
|
||||
|
||||
Présente la suggestion à l'utilisateur et **demande confirmation** :
|
||||
|
||||
```
|
||||
Type détecté : feature
|
||||
Nouvelle version : 1.10.0 → 1.11.0
|
||||
|
||||
Tu valides, ou tu veux changer (feature/improvement/fix/skip) ?
|
||||
```
|
||||
|
||||
Si `skip` → bascule vers un commit + push standard sans bump ni changelog.
|
||||
|
||||
## Step 3 — Collecter le contenu du changelog
|
||||
|
||||
Pose 2 questions courtes à l'utilisateur :
|
||||
|
||||
1. **Titre** (court, product-focused, en français, sans préfixe `v1.x.0 —`) — propose un titre tiré des diffs (commit messages déjà existants, noms de fichiers, etc.)
|
||||
2. **Highlights** (1 à 5 bullets) — chaque bullet décrit ce que l'utilisateur peut faire de nouveau. Pas de jargon tech (`refactor`, `stack`, `monorepo`). Propose-les d'abord depuis les diffs, l'utilisateur valide ou modifie.
|
||||
|
||||
**Optionnel** : si la release est suffisamment riche, propose aussi un **body markdown** (2-4 paragraphes max) qui explique le pourquoi. Sinon, body vide = entry courte uniquement avec les highlights, c'est ok.
|
||||
|
||||
**Ton** (rappel — ce sont les règles brand) :
|
||||
- Direct, concret, chaleureux, précis (cf. `/CLAUDE.md` §Voix)
|
||||
- "On parle comme un bon associé, pas comme une DAF"
|
||||
- Une phrase d'attitude max par entrée ("Un client remercié est un client qui revient")
|
||||
- Jamais de "we are excited to announce", jamais de superlatifs
|
||||
- Jamais de "recouvrement"
|
||||
- Variables markdown en backticks : `` `{{client.nom}}` ``
|
||||
|
||||
## Step 4 — Écrire les fichiers
|
||||
|
||||
### 4a. Bumper `apps/web/src/version.ts`
|
||||
|
||||
```ts
|
||||
export const APP_VERSION = "<new-version>";
|
||||
```
|
||||
|
||||
Edit (pas Write — préserve les commentaires) avec `replace_all: false`.
|
||||
|
||||
### 4b. Créer `apps/landing/src/content/changelog/<new-version>.md`
|
||||
|
||||
Format strict (validé par Zod via `apps/landing/src/content.config.ts`) :
|
||||
|
||||
```md
|
||||
---
|
||||
version: "<X.Y.Z>"
|
||||
date: <YYYY-MM-DD>
|
||||
title: "<titre court fourni par l'user>"
|
||||
type: feature # ou improvement / fix
|
||||
highlights:
|
||||
- "<highlight 1>"
|
||||
- "<highlight 2>"
|
||||
- "<highlight 3>"
|
||||
---
|
||||
|
||||
<body markdown optionnel — 2-4 paragraphes max>
|
||||
```
|
||||
|
||||
Règles :
|
||||
- `version` doit matcher le regex `^\d+\.\d+\.\d+$` et être identique à la valeur dans `version.ts`
|
||||
- `date` au format `YYYY-MM-DD` — utilise `date +%Y-%m-%d` côté bash pour avoir la vraie date du jour
|
||||
- `title` entre 3 et 80 caractères, sans préfixe `v1.x.0 —`
|
||||
- `type` ∈ `{feature, improvement, fix}`
|
||||
- `highlights` : 1 à 8 items, idéalement 2-5
|
||||
|
||||
## Step 5 — Commit + push
|
||||
|
||||
### 5a. Génère le message de commit
|
||||
|
||||
Suis la convention du repo (Conventional Commits avec scope) :
|
||||
|
||||
```
|
||||
<type>(<scope>): <description courte>
|
||||
|
||||
<body : 1-3 lignes qui expliquent le pourquoi, pas le quoi>
|
||||
<résumé des changements majeurs si plusieurs fichiers>
|
||||
|
||||
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||||
```
|
||||
|
||||
Mappings :
|
||||
| Release type | Commit type |
|
||||
|---|---|
|
||||
| `feature` | `feat` |
|
||||
| `improvement` | `feat` ou `refactor` selon la nature |
|
||||
| `fix` | `fix` |
|
||||
|
||||
Scope : à inférer depuis les diffs. Examples :
|
||||
- Diffs uniquement dans `apps/web/` → scope `web`
|
||||
- Diffs uniquement dans `apps/landing/` → scope `landing`
|
||||
- Diffs croisés (typique d'une release car version.ts + changelog/) → scope `release` ou pas de scope, et la description mentionne `v<x.y.z>`
|
||||
|
||||
Exemple :
|
||||
```
|
||||
feat(release): v1.11.0 — réécriture IA améliorée
|
||||
|
||||
Le bouton "Reformule" passe de 1 ton (ferme) à 3 (ferme, chaleureux,
|
||||
court), avec un aperçu diff côté à côté avant validation. Bumpe APP_VERSION
|
||||
et ajoute l'entrée changelog correspondante.
|
||||
|
||||
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||||
```
|
||||
|
||||
### 5b. Stage + commit
|
||||
|
||||
```bash
|
||||
# Stage les changements existants ET les nouveaux fichiers (version.ts + .md)
|
||||
git add apps/web/src/version.ts apps/landing/src/content/changelog/<new-version>.md
|
||||
# Plus tous les autres changements en attente (sauf si l'user a dit "stage only X")
|
||||
git add <fichiers détectés au step 1>
|
||||
|
||||
git commit -m "$(cat <<'EOF'
|
||||
<message complet via HEREDOC>
|
||||
EOF
|
||||
)"
|
||||
```
|
||||
|
||||
⚠️ **Ne JAMAIS** :
|
||||
- Utiliser `git add -A` ou `git add .` (peut catch des secrets, des fichiers temporaires, etc.)
|
||||
- Utiliser `--no-verify` (les hooks pre-commit existent pour une raison)
|
||||
- `git commit --amend` (toujours un nouveau commit propre)
|
||||
|
||||
### 5c. Push
|
||||
|
||||
```bash
|
||||
git push gitea main
|
||||
```
|
||||
|
||||
Le remote `gitea` pointe sur `https://git.arthurbarre.fr/ordinarthur/rubis.git`. Le CI Gitea déclenche le build et le rollout K3s automatiquement.
|
||||
|
||||
## Step 6 — Récap à l'utilisateur
|
||||
|
||||
Affiche un tableau récap après le push :
|
||||
|
||||
```
|
||||
Pushed sur gitea/main :
|
||||
- <commit hash> <commit subject>
|
||||
- Version : <old> → <new>
|
||||
- Changelog : apps/landing/src/content/changelog/<new>.md
|
||||
- Toast SPA : effectif au prochain reload pour les users dont
|
||||
localStorage["rubis:last-seen-version"] < <new>
|
||||
|
||||
Le CI Gitea va déclencher le rebuild des images landing + web
|
||||
(et la propagation sur K3s). Vérifie sur git.arthurbarre.fr.
|
||||
```
|
||||
|
||||
## Edge cases
|
||||
|
||||
- **Build SPA cassé** (typecheck erreurs pré-existantes) : commit/push quand même mais flag clairement que le CI risque d'échouer. Ne tente pas de fixer ces erreurs dans le release commit — c'est un commit séparé.
|
||||
- **Conflits avec le remote** : si `git push` échoue avec `[rejected] non-fast-forward`, fais `git pull --rebase gitea main` puis re-push. Si rebase échoue → stop, demande à l'user.
|
||||
- **Pre-commit hook échoue** : NE fais PAS `--amend`, NE skip PAS avec `--no-verify`. Lis l'erreur, propose un fix, puis re-commit (nouveau commit, hash neuf).
|
||||
- **Version déjà existante** (le `.md` existe déjà sous ce numéro) : stop. Soit l'user a oublié de bumper `version.ts`, soit il y a un conflit de release parallèle. Demande clarification.
|
||||
- **Versionnage incohérent** : si `version.ts` est `1.10.0` mais le dernier `.md` est `1.12.0`, signale-le avant tout bump. Probablement quelqu'un a oublié de commit le bump correspondant.
|
||||
@ -78,6 +78,7 @@ Direct, concret, chaleureux, précis, empathique. *On parle comme un bon associ
|
||||
- Mode démo (sandbox in-app sans engagement)
|
||||
- App mobile (web responsive)
|
||||
- **Blog `rubis.pro/blog`** — SSR par `apps/landing` (Astro 6), contenu en DB (`posts`) servi par `apps/api` via `/api/v1/posts/*`, admin de validation côté `app.rubis.pro/admin/blog`, génération hebdomadaire IA via cron (Sonnet 4.6) avec review humaine obligatoire. Détails dans `/docs/tech/architecture.md`.
|
||||
- **Changelog `rubis.pro/changelog`** — SSG par `apps/landing`, contenu en MD versionné dans `apps/landing/src/content/changelog/<version>.md` (Astro content collections, schéma Zod), RSS à `/changelog/rss.xml`. Toast SPA "Nouvelle version" déclenché par `apps/web/src/components/version-toast.tsx` quand `APP_VERSION` (`apps/web/src/version.ts`) diffère de `localStorage["rubis:last-seen-version"]`. Workflow release : `/push` (cf. `.claude/skills/push/SKILL.md`) bump la version + crée le .md + commit + push.
|
||||
|
||||
### OUT (V2 ou plus tard)
|
||||
|
||||
@ -153,6 +154,9 @@ Voir `/docs/decisions.md` pour le log complet avec rationale.
|
||||
| `/Dockerfile` | Build nginx-alpine servant `/landing/` sur port 80 |
|
||||
| `/k3s/` | Manifests Kubernetes (namespace, deployment, service) |
|
||||
| `/.claude/deploy-memory.md` | Procédure de déploiement (Gitea CI ou manuel) |
|
||||
| `/.claude/skills/push/SKILL.md` | Skill Claude Code `/push` — release automatisée (bump version + entrée changelog + commit + push) |
|
||||
| `/apps/landing/src/content/changelog/` | Une entrée Markdown par version (source de vérité du changelog public `/changelog`) |
|
||||
| `/apps/web/src/version.ts` | Constante `APP_VERSION` — déclenche le toast "Nouvelle version" dans la SPA |
|
||||
|
||||
## Déploiement
|
||||
|
||||
|
||||
@ -4,6 +4,28 @@
|
||||
|
||||
---
|
||||
|
||||
## ADR-022 · Changelog en Markdown versionné (pas en DB)
|
||||
|
||||
- **Date** : 2026-05-11
|
||||
- **Statut** : ✅ Validée
|
||||
- **Contexte** : on a besoin d'un changelog public (SEO + tenir les users au courant) avec un toast in-app qui linke vers l'entrée correspondante. Question : où vit la donnée ?
|
||||
- **Décision** : MD files versionnés dans le repo (`apps/landing/src/content/changelog/<x.y.z>.md`), chargés par les content collections d'Astro (schéma Zod). Pas de DB, pas d'admin.
|
||||
- **Rationale** :
|
||||
- **Le changelog change avec le code, pas indépendamment.** Une nouvelle entrée = une release = une PR. Versionner dans Git aligne naturellement les deux.
|
||||
- **Pas d'admin à maintenir.** Le blog a une UI admin parce qu'il est généré par IA hebdomadaire + needs human review. Le changelog est écrit par le dev qui ship — il n'a pas besoin d'une UI séparée.
|
||||
- **Review en PR** — l'entrée passe sous les yeux comme tout autre code, attrape les fautes et le ton off-brand avant publication.
|
||||
- **SSG = LCP optimal.** Contenu figé au build, pas d'appel API au render.
|
||||
- **Schéma typé** (`version`/`date`/`type`/`highlights[]`) validé à build-time par Zod → impossible de pusher un .md mal formé.
|
||||
- **Alternatives écartées** :
|
||||
- Mêmes tables `posts` que le blog → forçait un admin UI inutile, et la cadence (chaque release vs chaque semaine) n'a rien à voir
|
||||
- JSON unique avec toutes les versions → difficile à diff en PR, conflits de merge sur les fichiers volumineux
|
||||
- GitHub Releases / Gitea Releases → pas indexable Google, pas brandé, pas dans la DA
|
||||
- **Conséquences** :
|
||||
- Le bump de `apps/web/src/version.ts` et l'ajout du `.md` doivent être **dans le même commit**, sinon le toast SPA pointe sur une ancre absente
|
||||
- Skill `/push` (cf. `.claude/skills/push/SKILL.md`) automatise ce ritual
|
||||
|
||||
---
|
||||
|
||||
## ADR-001 · Conversion 1 rubis = 10 minutes libérées
|
||||
|
||||
- **Date** : 2026-05-05
|
||||
|
||||
@ -117,6 +117,15 @@ Rubis Sur l'Ongle libère le temps des dirigeants de TPE-PME en automatisant la
|
||||
- Tab bar 4 entrées max : Accueil · Factures · Plans · Réglages
|
||||
- Le rubis reste hero du mobile
|
||||
|
||||
### 4.9 Changelog public et toast de version
|
||||
|
||||
- **Page publique** `rubis.pro/changelog` — timeline reverse-chrono des versions livrées, une carte par version (chip `v1.x.0` + date + highlights bulletés + body markdown narrative). Sticky rail desktop avec jump-nav, glow rubis sur la version la plus récente.
|
||||
- **Tonalité** : produit-only, jamais tech (pas de "refactor", "stack", "monorepo"). On parle de ce que **l'utilisateur peut faire de nouveau**, pas de ce qu'on a codé. Une phrase d'attitude max par entrée.
|
||||
- **Toast in-app** : à l'ouverture de la SPA, si `APP_VERSION` (constante versionnée dans `apps/web/src/version.ts`) diffère de la version stockée en `localStorage["rubis:last-seen-version"]`, on affiche un toast Sonner persistant `Nouvelle version v1.x.0 — Voir les nouveautés ↗`. Clic → ouvre `/changelog#1.x.0` dans un nouvel onglet.
|
||||
- **Première visite** : silencieux, on enregistre la version sans toast (pas d'onboarding pollué).
|
||||
- **RSS** : flux 2.0 dispo à `/changelog/rss.xml`, auto-discovered depuis le `<head>` de toutes les pages.
|
||||
- **SEO** : `prerender = true`, JSON-LD `WebPage` + `mainEntity[TechArticle]` par version pour les rich snippets Google.
|
||||
|
||||
## 5. Fonctionnalités OUT (V2+)
|
||||
|
||||
### V2 (planifié)
|
||||
|
||||
@ -207,6 +207,13 @@ Cf. `apps/api/app/controllers/blog_controller.ts` + `apps/api/app/transformers/p
|
||||
- **Lucide React** pour les icônes
|
||||
- **Bricolage Grotesque + Inter** via Google Fonts (cohérent landing)
|
||||
|
||||
### Versionnage SPA + toast de release
|
||||
|
||||
- `apps/web/src/version.ts` exporte `APP_VERSION` (semver string) et `CHANGELOG_URL` (`https://rubis.pro/changelog`).
|
||||
- Bump manuel à chaque release, dans la même PR que l'ajout de l'entrée changelog côté `apps/landing`.
|
||||
- `<VersionToast/>` (mount-once dans `__root.tsx`) compare cette constante à `localStorage["rubis:last-seen-version"]`. Toast persistant Sonner si différent. Cf. `apps/web/src/components/version-toast.tsx`.
|
||||
- localStorage indisponible (private mode) → fail silent, pas de toast et pas de crash.
|
||||
|
||||
### Auth côté SPA (cf. ADR-017)
|
||||
- **Access token** stocké en mémoire (variable de module / state Query) — pas localStorage pour éviter XSS
|
||||
- **Refresh token** en cookie httpOnly + SameSite=Strict
|
||||
@ -258,6 +265,7 @@ Landing publique + blog, en Astro avec rendu hybride statique/SSR. Image Docker
|
||||
| `/blog` | SSR | Doit refléter immédiatement les publications admin |
|
||||
| `/blog/:slug` | SSR | Idem + 404 dynamique |
|
||||
| `/blog/rss.xml`, `/sitemap.xml`, `/robots.txt` | SSR (endpoints `.ts`) | Liste à jour |
|
||||
| `/changelog`, `/changelog/rss.xml` | SSG (`prerender = true`) | Contenu MD versionné, regénéré à chaque release |
|
||||
|
||||
**Cache HTTP** : pages SSR avec `Cache-Control: public, max-age=300, stale-while-revalidate=86400`. Absorbe les pics, et un publish admin propage en ≤5 min sans purge active.
|
||||
|
||||
@ -266,6 +274,9 @@ Landing publique + blog, en Astro avec rendu hybride statique/SSR. Image Docker
|
||||
apps/landing/
|
||||
├── astro.config.mjs # output: server, adapter Node, tailwindcss plugin
|
||||
├── src/
|
||||
│ ├── content.config.ts # collections Astro (changelog)
|
||||
│ ├── content/changelog/ # 1 .md par version livrée (frontmatter Zod)
|
||||
│ │ ├── 1.0.0.md ... 1.10.0.md
|
||||
│ ├── pages/
|
||||
│ │ ├── index.astro # / (SSG)
|
||||
│ │ ├── mentions-legales.astro # SSG
|
||||
@ -273,10 +284,13 @@ apps/landing/
|
||||
│ │ ├── cgv.astro # SSG
|
||||
│ │ ├── sitemap.xml.ts # SSR endpoint
|
||||
│ │ ├── robots.txt.ts # SSR endpoint
|
||||
│ │ └── blog/
|
||||
│ │ ├── index.astro # /blog (SSR)
|
||||
│ │ ├── [slug].astro # /blog/:slug (SSR)
|
||||
│ │ └── rss.xml.ts # SSR endpoint
|
||||
│ │ ├── blog/
|
||||
│ │ │ ├── index.astro # /blog (SSR)
|
||||
│ │ │ ├── [slug].astro # /blog/:slug (SSR)
|
||||
│ │ │ └── rss.xml.ts # SSR endpoint
|
||||
│ │ └── changelog/
|
||||
│ │ ├── index.astro # /changelog (SSG)
|
||||
│ │ └── rss.xml.ts # SSG endpoint
|
||||
│ ├── layouts/
|
||||
│ │ ├── Layout.astro # SEO complet, Header + Footer
|
||||
│ │ └── LegalLayout.astro # wrapper prose pour pages légales
|
||||
@ -290,6 +304,21 @@ apps/landing/
|
||||
│ └── public/ # favicons, manifest
|
||||
```
|
||||
|
||||
### Mécanique du changelog (release workflow)
|
||||
|
||||
À chaque release, **deux fichiers doivent être modifiés en même temps**, dans le même commit :
|
||||
|
||||
1. `apps/web/src/version.ts` — bump de la constante `APP_VERSION` (semver, sans préfixe `v`)
|
||||
2. `apps/landing/src/content/changelog/<APP_VERSION>.md` — nouvelle entrée Markdown avec frontmatter Zod-validé (`version`, `date`, `title`, `type`, `highlights[]`)
|
||||
|
||||
Le composant `<VersionToast/>` monté au root de la SPA (`apps/web/src/routes/__root.tsx`) compare `APP_VERSION` à `localStorage["rubis:last-seen-version"]` au mount :
|
||||
|
||||
- Clé absente (1re visite) → on enregistre `APP_VERSION` en silence, pas de toast
|
||||
- Clé identique → rien
|
||||
- Clé différente → toast Sonner persistant avec action `Voir les nouveautés ↗` qui ouvre `https://rubis.pro/changelog#<APP_VERSION>` dans un nouvel onglet. localStorage est mis à jour à l'affichage (donc l'user ne reverra plus le toast pour cette version même s'il ferme sans cliquer).
|
||||
|
||||
Pour automatiser le ritual des deux fichiers, le skill Claude Code `/push` (cf. `.claude/skills/push/SKILL.md`) bump la version, crée le `.md` correspondant, commit avec un message conventional, et push sur `gitea/main`.
|
||||
|
||||
### Connexion à l'API
|
||||
|
||||
En SSR, Astro fetch directement le service Adonis via le DNS K3s (interne, pas de TLS) — cf. `API_URL` dans le ConfigMap `rubis-landing-config` :
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user