# create-wp-reactor

Scaffolder de repo client **WP Reactor** — un framework headless WordPress
(kernel TanStack Start + modules commerce/auth/editorial + générateur de blocs).

```bash
npm create wp-reactor@latest mon-client
# ou : pnpm create wp-reactor mon-client
# ou : npx create-wp-reactor mon-client
```

Génère un monorepo client « 2 mondes » :

```
apps/webapp/                 coque TanStack Start (deps @wp-reactor/* publiées)
apps/wordpress/theme-<nom>/  thème ENFANT (branding + blocs propres)
docker-compose.dev.yml       stack dev local (WordPress + DB + Redis)
.github/workflows/deploy.yml CI réutilisable du framework (GHCR → Coolify)
docs/                        deploy.md, environment.md, traefik-labels.md
```

## Options

```
create-wp-reactor <nom> [--dir <path>] [--namespace <ns>] [--features <liste>]
create-wp-reactor update [--dir <path>]
create-wp-reactor --provision [<config>]     # défaut : .env.provision
```

- `--dir` : dossier cible (défaut : `<nom>` dans le cwd).
- `--namespace` : namespace des blocs Gutenberg (défaut : `<nom>` sans tirets).
- `--features` : domaines optionnels retenus, séparés par des virgules.

### `--features`

L'**éditorial est le socle** : tout site en a besoin, il est toujours généré.
`commerce` et `auth` sont indépendants — un catalogue sans compte client existe,
un espace membre sans catalogue aussi.

```bash
create-wp-reactor mon-client                        # tout (défaut)
create-wp-reactor mon-client --features commerce    # socle + catalogue
create-wp-reactor mon-client --features editorial   # socle seul
```

Un domaine écarté ne laisse rien derrière lui : ni ses fichiers
(`src/commerce/`, `src/cart.ts`, `src/server/cart*.ts` pour le commerce), ni sa
dépendance `@wp-reactor/*` dans le `package.json` de la webapp, ni sa couture
dans `platform.ts` et `routes/__root.tsx`.

Le défaut reste la variante **complète** : le template est exécutable tel quel,
et c'est ce que la sandbox de dogfood construit. Le provisioning
(`--provision --bootstrap`) génère lui aussi la variante complète.

Côté template, les coutures sont délimitées par des marqueurs que le scaffold
retire — `// @wpr:commerce` … `// @wpr:end`, en `#` dans les `.env.example` et
en `{/* */}` dans le JSX. La variante `:wrap` (`// @wpr:auth:wrap`) sert aux
enveloppes JSX : elle retire la balise et désindente son contenu d'un cran.

### `--provision`

Scaffolde **puis** monte l'infra complète, là où il fallait jusqu'ici enchaîner
une dizaine d'étapes manuelles dans l'UI Coolify, l'UI GitHub et Cloudflare.

Une commande, un fichier :

```bash
npx create-wp-reactor@latest --provision
```

Au premier appel, le fichier de configuration n'existe pas : la commande le
**génère** (`.env.provision`, en `chmod 600`), liste ce qu'il reste à remplir,
et attend.

```
✓ .env.provision créé (chmod 600).
  Remplis-le — il contient des jetons, ne le committe pas.

  Manquant : WPR_PROJECT_NAME, WPR_DOMAIN, WPR_GH_OWNER, WPR_COOLIFY_SERVER,
             GITHUB_TOKEN, COOLIFY_URL, COOLIFY_TOKEN, NODE_AUTH_TOKEN

  Remplis .env.provision, puis Entrée pour revérifier — q pour quitter.
```

Tu ouvres le fichier dans un autre terminal, tu le remplis, tu appuies sur
Entrée : il est relu et revalidé. Une fois complet, un récapitulatif s'affiche et
la confirmation est demandée **avant le moindre appel** — le provisioning crée de
l'infra facturée par des appels non idempotents, sans rollback.

```
  Projet      Acme Deco  (slug acme-deco)
  Front       https://acme.example.com
  WordPress   https://wp.acme.example.com
  Repo        acme-org/acme-deco (privé)
  Serveur     prod-1
  DNS         Cloudflare, zone abc123
  Jetons      GITHUB_TOKEN ✓  COOLIFY_TOKEN ✓  NODE_AUTH_TOKEN ✓  CF_API_TOKEN ✓

  Crée de l'infra réelle, sans rollback automatique. Continuer ? [y/N]
```

Aucune valeur de jeton n'est jamais affichée — seulement présent/absent.

| Étape | Ce qui est créé |
|---|---|
| 1 | Repo client généré, `.env` de dev local écrit avec un vrai `SESSION_SECRET` |
| 2 | Repo GitHub, variables `VITE_*` et secrets Actions |
| 3 | Enregistrements DNS Cloudflare (front proxifié, WordPress DNS-only) |
| 4 | Projet Coolify, MariaDB et Redis managés |
| 5 | Ressource Docker Compose WordPress + son environnement |
| 6 | Application webapp (labels Traefik, health check, purge post-deploy) |
| 7 | Secrets `COOLIFY_*_UUID`, puis premier push — qui déclenche la CI |

L'ordre dénoue un cycle : Coolify a besoin d'images sur GHCR, ces images viennent
de la CI, et la CI a besoin des UUID Coolify. Les ressources sont donc créées
sans être déployées, et c'est le push final qui lance la CI, laquelle construit
les images puis appelle elle-même `/api/v1/deploy`. Pas de polling, pas de
seconde commande.

Tous les secrets sont générés (`SESSION_SECRET`, mots de passe DB/Redis/admin
WordPress) et ne sont écrits que dans l'environnement Coolify — jamais dans le
repo. Le mot de passe admin est affiché une fois, en fin de commande.

#### Le fichier de configuration

Format `.env`. Il porte **tout** — paramètres et jetons — et le template généré
documente chaque clé.

| Clé | Rôle |
|---|---|
| `WPR_PROJECT_NAME` | nom lisible du client (slugifié pour les identifiants) |
| `WPR_DOMAIN` | domaine du front, sans schéma |
| `WPR_GH_OWNER` | propriétaire GitHub (compte perso ou organisation) |
| `WPR_COOLIFY_SERVER` | nom affiché ou UUID du serveur Coolify |
| `WPR_REPO_NAME`, `WPR_WP_HOST`, `WPR_NAMESPACE`, `WPR_DIR` | optionnels |
| `GITHUB_TOKEN` | `repo` + `write:packages` |
| `COOLIFY_URL` / `COOLIFY_TOKEN` | instance et jeton `write` + `deploy` |
| `NODE_AUTH_TOKEN` | PAT `read:packages`, utilisé par la CI pour les `@wp-reactor/*` |
| `CF_API_TOKEN` / `CF_ZONE_ID` | optionnels **par paire**, `Zone:DNS:Edit` — DNS et purge du cache |

Les jetons gardent leur nom conventionnel, sans préfixe : un
`export GITHUB_TOKEN=…` déjà présent dans ton shell fonctionne tel quel. Les
paramètres sont préfixés `WPR_` pour ne pas entrer en collision avec des noms
génériques — `DOMAIN` traîne dans beaucoup d'environnements.

**Précédence : flag CLI > variable du shell > fichier.** Ça permet de surcharger
une valeur ponctuellement sans éditer le fichier.

Le fichier ne concerne que le **provisioning** — rien à voir avec l'environnement
runtime des applications, qui vit dans Coolify.

#### Autres options

**`--dry-run`** imprime le plan d'appels sans en émettre aucun — pas même les
`GET`. C'est la porte de revue avant un run réel, puisque ces appels créent de
l'infra et ne sont pas idempotents. En contrepartie, un dry-run ne valide aucun
jeton : il ne contacte aucun serveur. Il saute aussi la confirmation, n'ayant
rien à créer.

**`--yes`** saute la confirmation, pour la CI. Sur une configuration incomplète,
la commande échoue en listant les clés manquantes au lieu d'attendre.

Hors terminal interactif (stdin redirigé, CI), la commande ne bloque jamais :
elle échoue en expliquant quoi faire.

**Forme à flags**, toujours supportée — utile pour scripter sans fichier :

```bash
GITHUB_TOKEN=… COOLIFY_URL=… COOLIFY_TOKEN=… NODE_AUTH_TOKEN=… \
npx create-wp-reactor@latest "Acme" --provision --yes \
  --domain acme.example.com --gh-owner acme-org --coolify-server prod-1
```

Options associées : `--repo-name` (défaut : le slug), `--wp-host` (défaut :
`wp.<domaine>`), `--dir`, `--namespace`.

**Reste manuel** : `docker login ghcr.io` en root sur l'hôte Coolify (l'API
n'expose pas les credentials de registry) et la Cache Rule Cloudflare. La
commande le rappelle en fin de course.

### `update`

Rafraîchit un repo client **déjà généré** sur deux axes, sans toucher la coque
`src/` ni le branding :

```bash
cd mon-client
npx create-wp-reactor@latest update   # `@latest` : cf. « quelles versions ? »
pnpm install                          # le lockfile a bougé
git diff                              # relire avant de committer
```

1. **Infra & skills** — re-pousse depuis le template embarqué : `docker-compose.dev.yml`,
   `docs/`, Dockerfiles, `.github/workflows/` (dossier entier, reusable vendorisé
   `build-and-deploy-image.yml` compris), et `.claude/skills/` (le skill agent `gen-block`
   suit les versions du CLI qu'il documente). Un skill ou un workflow propre au client
   posé à côté n'est jamais touché : `update` écrit, il ne purge pas.
2. **Deps `@wp-reactor/*`** — réécrit chaque range en `^<version>` dans **tous** les
   `package.json` du repo (racine, `apps/webapp`, thème enfant, packages ajoutés par
   le client). Le remplacement est chirurgical — seule la chaîne de version bouge,
   l'indentation et l'ordre des clés sont préservés, donc le diff ne montre que les
   bumps.

L'identité du projet (nom, namespace) est relue depuis `package.json` +
`wp-reactor.config.json`, donc les tokens du template sont re-rendus avec ton
branding. Hors d'un repo client, la commande échoue au lieu d'écrire.

**Quelles versions ?** Celles **embarquées dans le CLI exécuté** (manifeste
`versions.generated.ts`, figé au build) — aucun appel réseau. D'où
`create-wp-reactor@latest` : un `npx` qui recycle une version cachée bumpera vers
les versions de *cette* version-là.

Ce que `update` **ne** touche **pas** :

| Hors périmètre | Pourquoi |
|---|---|
| `apps/webapp/src/`, thème enfant | c'est ton code — la coque est un point de départ, pas un fichier managé |
| deps hors `@wp-reactor/*` (react, vite, turbo…) | les bumper pourrait casser ton app ; à faire à ton rythme |
| `package.json` (scripts, nom), `wp-reactor.config.json`, `.env.example`, `.gitignore`, `.npmrc` | personnalisés par le client |
| ranges `workspace:` / `link:` / `file:` / `portal:` | liens locaux (dogfood monorepo) — les réécrire casserait le lien |

Une dep `@wp-reactor/*` inconnue du manifeste (package renommé ou retiré) est
laissée telle quelle et signalée en `⚠` — jamais bumpée dans le vide.

## Après génération

Les packages runtime `@wp-reactor/*` sont sur **GitHub Packages** (privé) →
authentifie-toi avant `pnpm install` :

```bash
echo "//npm.pkg.github.com/:_authToken=<PAT read:packages>" >> ~/.npmrc
cd mon-client && pnpm install && pnpm dev
```

Voir le `README.md` et `docs/` du repo généré pour le dev Docker et le déploiement.
