create-wp-reactor 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +21 -0
- package/README.md +41 -0
- package/package.json +3 -2
- package/templates/client/.env.example +20 -2
- package/templates/client/README.md +24 -5
- package/templates/client/apps/webapp/Dockerfile +40 -0
- package/templates/client/apps/webapp/scripts/clear-cache.mjs +45 -0
- package/templates/client/apps/wordpress/docker/bootstrap.sh +43 -0
- package/templates/client/docker-compose.dev.yml +93 -0
- package/templates/client/docs/deploy.md +105 -0
- package/templates/client/docs/environment.md +74 -0
- package/templates/client/docs/traefik-labels.md +78 -0
- package/templates/client/package.json +5 -1
- package/dist/index.d.ts +0 -18
- package/dist/index.js +0 -62
- package/dist/index.js.map +0 -1
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# create-wp-reactor
|
|
2
|
+
|
|
3
|
+
## 0.2.0
|
|
4
|
+
|
|
5
|
+
### Minor
|
|
6
|
+
|
|
7
|
+
- Stack dev local : `docker-compose.dev.yml` (WordPress image base + MariaDB +
|
|
8
|
+
Redis, thème enfant bind-monté) + `bootstrap.sh` + scripts `pnpm docker:*`.
|
|
9
|
+
- Docs déploiement : `docs/deploy.md` (procédure Coolify ordonnée + Cloudflare),
|
|
10
|
+
`docs/environment.md` (variables local / GitHub / Coolify), `docs/traefik-labels.md`.
|
|
11
|
+
- Script de purge edge Cloudflare (`scripts/clear-cache.mjs`) en post-deploy.
|
|
12
|
+
- Renommage `GA_FRONTEND_URL` → `FRONTEND_URL` dans les templates.
|
|
13
|
+
|
|
14
|
+
### Patch
|
|
15
|
+
|
|
16
|
+
- Fix : le `Dockerfile` de la webapp cliente était absent du repo généré
|
|
17
|
+
(effacé par le sync). Déplacé dans le starter → porté correctement au scaffold.
|
|
18
|
+
|
|
19
|
+
## 0.1.0
|
|
20
|
+
|
|
21
|
+
- Première publication. Scaffolder de repo client mince : `npm create wp-reactor <nom>`.
|
package/README.md
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# create-wp-reactor
|
|
2
|
+
|
|
3
|
+
Scaffolder de repo client **WP Reactor** — un framework headless WordPress
|
|
4
|
+
(kernel TanStack Start + modules commerce/auth/editorial + générateur de blocs).
|
|
5
|
+
|
|
6
|
+
```bash
|
|
7
|
+
npm create wp-reactor@latest mon-client
|
|
8
|
+
# ou : pnpm create wp-reactor mon-client
|
|
9
|
+
# ou : npx create-wp-reactor mon-client
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Génère un monorepo client « 2 mondes » :
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
apps/webapp/ coque TanStack Start (deps @wp-reactor/* publiées)
|
|
16
|
+
apps/wordpress/theme-<nom>/ thème ENFANT (branding + blocs propres)
|
|
17
|
+
docker-compose.dev.yml stack dev local (WordPress + DB + Redis)
|
|
18
|
+
.github/workflows/deploy.yml CI réutilisable du framework (GHCR → Coolify)
|
|
19
|
+
docs/ deploy.md, environment.md, traefik-labels.md
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Options
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
create-wp-reactor <nom> [--dir <path>] [--namespace <ns>]
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
- `--dir` : dossier cible (défaut : `<nom>` dans le cwd).
|
|
29
|
+
- `--namespace` : namespace des blocs Gutenberg (défaut : `<nom>` sans tirets).
|
|
30
|
+
|
|
31
|
+
## Après génération
|
|
32
|
+
|
|
33
|
+
Les packages runtime `@wp-reactor/*` sont sur **GitHub Packages** (privé) →
|
|
34
|
+
authentifie-toi avant `pnpm install` :
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
echo "//npm.pkg.github.com/:_authToken=<PAT read:packages>" >> ~/.npmrc
|
|
38
|
+
cd mon-client && pnpm install && pnpm dev
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Voir le `README.md` et `docs/` du repo généré pour le dev Docker et le déploiement.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-wp-reactor",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Scaffolder de repo client mince WP Reactor : émet apps/webapp (depuis le starter) + thème enfant + CI réutilisable + docker. Usage : npm create wp-reactor <nom>.",
|
|
6
6
|
"bin": {
|
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
"files": [
|
|
13
13
|
"dist",
|
|
14
14
|
"bin",
|
|
15
|
-
"templates"
|
|
15
|
+
"templates",
|
|
16
|
+
"CHANGELOG.md"
|
|
16
17
|
],
|
|
17
18
|
"scripts": {
|
|
18
19
|
"sync": "node scripts/sync-template.mjs",
|
|
@@ -1,5 +1,23 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
1
|
+
# Copie ce fichier en `.env` (gitignored). Doc complète : docs/environment.md
|
|
2
|
+
# Sert au stack dev local (docker-compose.dev.yml) + `pnpm dev`.
|
|
3
|
+
|
|
4
|
+
# --- WordPress local (docker-compose.dev.yml) ---
|
|
5
|
+
WP_PORT=8890
|
|
6
|
+
WP_HOME=http://localhost:8890
|
|
7
|
+
FRONTEND_URL=http://localhost:3000
|
|
8
|
+
WORDPRESS_DB_NAME=wordpress
|
|
9
|
+
WORDPRESS_DB_USER=wordpress
|
|
10
|
+
WORDPRESS_DB_PASSWORD=wordpress
|
|
11
|
+
MYSQL_ROOT_PASSWORD=root
|
|
12
|
+
|
|
13
|
+
# --- Provisioning (wp-bootstrap, profil tools) ---
|
|
14
|
+
WP_SITE_TITLE=__PROJECT_TITLE__
|
|
15
|
+
WP_ADMIN_USER=admin
|
|
16
|
+
WP_ADMIN_PASSWORD=admin
|
|
17
|
+
WP_ADMIN_EMAIL=admin@example.com
|
|
18
|
+
|
|
19
|
+
# --- Webapp (bakées au build Vite / lues par `pnpm dev`) ---
|
|
20
|
+
VITE_WORDPRESS_URL=http://localhost:8890
|
|
3
21
|
VITE_GRAPHQL_PATH=/graphql
|
|
4
22
|
VITE_FEATURE_EDITORIAL=true
|
|
5
23
|
VITE_FEATURE_ECOMMERCE=true
|
|
@@ -6,8 +6,11 @@ Repo client **WP Reactor** (framework headless WordPress) — monorepo léger «
|
|
|
6
6
|
apps/webapp/ coque TanStack Start (deps @wp-reactor/* publiées)
|
|
7
7
|
apps/wordpress/theme-__PROJECT_NAME__/ thème ENFANT (branding + blocs propres)
|
|
8
8
|
apps/wordpress/Dockerfile image WP = base framework + thème enfant overlayé
|
|
9
|
+
docker-compose.dev.yml stack dev local (WordPress + DB + Redis)
|
|
9
10
|
.github/workflows/deploy.yml appelle les workflows réutilisables du framework
|
|
10
11
|
wp-reactor.config.json config gen-block (namespace, chemins)
|
|
12
|
+
docs/environment.md où va chaque variable (local / GitHub / Coolify)
|
|
13
|
+
docs/traefik-labels.md labels Traefik à coller dans Coolify
|
|
11
14
|
```
|
|
12
15
|
|
|
13
16
|
## Prérequis
|
|
@@ -19,14 +22,27 @@ Authentifie-toi avant `pnpm install` :
|
|
|
19
22
|
echo "//npm.pkg.github.com/:_authToken=<PAT read:packages>" >> ~/.npmrc
|
|
20
23
|
```
|
|
21
24
|
|
|
22
|
-
## Dev
|
|
25
|
+
## Dev rapide (Docker)
|
|
26
|
+
|
|
27
|
+
WordPress (image base framework) + DB + Redis en conteneurs, webapp sur l'hôte.
|
|
23
28
|
|
|
24
29
|
```bash
|
|
30
|
+
# L'image WordPress base est privée (GHCR) → login une fois :
|
|
31
|
+
echo <PAT read:packages> | docker login ghcr.io -u <user> --password-stdin
|
|
32
|
+
|
|
25
33
|
pnpm install
|
|
26
|
-
cp .env.example .env
|
|
27
|
-
|
|
34
|
+
cp .env.example .env # ajuste si besoin
|
|
35
|
+
|
|
36
|
+
pnpm docker:up # WordPress + DB + Redis
|
|
37
|
+
pnpm docker:bootstrap # install WP + plugins GraphQL + thème enfant
|
|
38
|
+
pnpm dev # webapp sur http://localhost:3000
|
|
28
39
|
```
|
|
29
40
|
|
|
41
|
+
WordPress (admin / GraphQL) : `http://localhost:8890` (cf `WP_PORT`).
|
|
42
|
+
Arrêt : `pnpm docker:down` — remise à zéro (volumes) : `pnpm docker:reset`.
|
|
43
|
+
|
|
44
|
+
> Détail des variables d'environnement : **[`docs/environment.md`](docs/environment.md)**.
|
|
45
|
+
|
|
30
46
|
## Générer un bloc
|
|
31
47
|
|
|
32
48
|
```bash
|
|
@@ -36,5 +52,8 @@ pnpm gen:block schemas/<slug>.json # écrit thème enfant + renderer webapp +
|
|
|
36
52
|
## Déploiement
|
|
37
53
|
|
|
38
54
|
Push sur `main` → `.github/workflows/deploy.yml` build+push les images webapp & WordPress
|
|
39
|
-
sur GHCR via les workflows réutilisables du framework, puis déclenche Coolify
|
|
40
|
-
|
|
55
|
+
sur GHCR via les workflows réutilisables du framework, puis déclenche Coolify.
|
|
56
|
+
|
|
57
|
+
- **Procédure ordonnée + Cloudflare** : **[`docs/deploy.md`](docs/deploy.md)**
|
|
58
|
+
- **Variables / secrets** (GitHub Actions + Coolify) : **[`docs/environment.md`](docs/environment.md)**
|
|
59
|
+
- **Labels Traefik** à coller dans l'UI Coolify : **[`docs/traefik-labels.md`](docs/traefik-labels.md)**
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# syntax=docker/dockerfile:1.7
|
|
2
|
+
# Build de la coque webapp. Les @wp-reactor/* sont tirés depuis GitHub Packages
|
|
3
|
+
# (cf .npmrc) — passer NODE_AUTH_TOKEN (read:packages) au build.
|
|
4
|
+
#
|
|
5
|
+
# NB : ce Dockerfile est un artefact de TEMPLATE (porté dans le repo client par
|
|
6
|
+
# le scaffolder, qui substitue __PROJECT_NAME__). Le contexte de build = racine
|
|
7
|
+
# du repo client.
|
|
8
|
+
FROM node:22-alpine AS base
|
|
9
|
+
WORKDIR /app
|
|
10
|
+
ENV CI=true DO_NOT_TRACK=1
|
|
11
|
+
RUN corepack enable && corepack prepare pnpm@11.3.0 --activate
|
|
12
|
+
|
|
13
|
+
FROM base AS build
|
|
14
|
+
ARG NODE_AUTH_TOKEN
|
|
15
|
+
ARG VITE_WORDPRESS_URL
|
|
16
|
+
ARG VITE_GRAPHQL_PATH
|
|
17
|
+
ARG VITE_FEATURE_EDITORIAL
|
|
18
|
+
ARG VITE_FEATURE_ECOMMERCE
|
|
19
|
+
ARG VITE_FEATURE_AUTH
|
|
20
|
+
ARG SESSION_SECRET
|
|
21
|
+
ENV VITE_WORDPRESS_URL=${VITE_WORDPRESS_URL} \
|
|
22
|
+
VITE_GRAPHQL_PATH=${VITE_GRAPHQL_PATH} \
|
|
23
|
+
VITE_FEATURE_EDITORIAL=${VITE_FEATURE_EDITORIAL} \
|
|
24
|
+
VITE_FEATURE_ECOMMERCE=${VITE_FEATURE_ECOMMERCE} \
|
|
25
|
+
VITE_FEATURE_AUTH=${VITE_FEATURE_AUTH} \
|
|
26
|
+
SESSION_SECRET=${SESSION_SECRET}
|
|
27
|
+
COPY . .
|
|
28
|
+
RUN printf '//npm.pkg.github.com/:_authToken=%s\n' "${NODE_AUTH_TOKEN}" >> .npmrc \
|
|
29
|
+
&& pnpm install --frozen-lockfile \
|
|
30
|
+
&& pnpm --filter @__PROJECT_NAME__/webapp build \
|
|
31
|
+
&& rm -f .npmrc
|
|
32
|
+
|
|
33
|
+
FROM node:22-alpine AS runner
|
|
34
|
+
RUN apk add --no-cache curl
|
|
35
|
+
WORKDIR /app/apps/webapp
|
|
36
|
+
COPY --from=build /app/apps/webapp/.output ./.output
|
|
37
|
+
# Scripts ops (ex. purge Cloudflare en post-deployment Coolify).
|
|
38
|
+
COPY --from=build /app/apps/webapp/scripts ./scripts
|
|
39
|
+
EXPOSE 3000
|
|
40
|
+
CMD ["node", ".output/server/index.mjs"]
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// Purge complète du cache Cloudflare de la zone webapp.
|
|
2
|
+
//
|
|
3
|
+
// Lancé en "Post-deployment command" Coolify (après le health check, donc une
|
|
4
|
+
// fois le nouveau conteneur live) :
|
|
5
|
+
// node scripts/clear-cache.mjs
|
|
6
|
+
//
|
|
7
|
+
// Pourquoi : un redeploy ne vide pas le cache edge → l'ancien HTML caché
|
|
8
|
+
// référence des assets hashés disparus (404 CSS/JS). On purge pour que le 1er
|
|
9
|
+
// visiteur reconstruise le cache sur le nouveau build.
|
|
10
|
+
//
|
|
11
|
+
// Env requis (Coolify → Environment Variables de l'app webapp) :
|
|
12
|
+
// CF_ZONE_ID — Zone ID Cloudflare de la zone du front
|
|
13
|
+
// CF_API_TOKEN — token scopé `Zone → Cache Purge → Purge` sur cette zone
|
|
14
|
+
// Si l'un manque, la purge est ignorée (exit 0) — déploiement non bloqué.
|
|
15
|
+
|
|
16
|
+
const zoneId = process.env.CF_ZONE_ID;
|
|
17
|
+
const apiToken = process.env.CF_API_TOKEN;
|
|
18
|
+
|
|
19
|
+
if (!zoneId || !apiToken) {
|
|
20
|
+
console.warn("[clear-cache] CF_ZONE_ID/CF_API_TOKEN absent — purge ignorée.");
|
|
21
|
+
process.exit(0);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
const res = await fetch(
|
|
26
|
+
`https://api.cloudflare.com/client/v4/zones/${zoneId}/purge_cache`,
|
|
27
|
+
{
|
|
28
|
+
method: "POST",
|
|
29
|
+
headers: {
|
|
30
|
+
Authorization: `Bearer ${apiToken}`,
|
|
31
|
+
"Content-Type": "application/json",
|
|
32
|
+
},
|
|
33
|
+
body: JSON.stringify({ purge_everything: true }),
|
|
34
|
+
},
|
|
35
|
+
);
|
|
36
|
+
const json = await res.json();
|
|
37
|
+
if (!json.success) {
|
|
38
|
+
console.error("[clear-cache] échec:", JSON.stringify(json.errors ?? json));
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
console.log("[clear-cache] cache Cloudflare purgé ✅");
|
|
42
|
+
} catch (err) {
|
|
43
|
+
console.error("[clear-cache] erreur:", err);
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
set -eu
|
|
3
|
+
# Provisioning WordPress local : install core + plugins GraphQL headless +
|
|
4
|
+
# active le thème enfant. Idempotent (relançable sans casse).
|
|
5
|
+
WP_PATH="/var/www/html"
|
|
6
|
+
WP="wp --path=$WP_PATH --allow-root"
|
|
7
|
+
|
|
8
|
+
echo "→ Attente du core WordPress…"
|
|
9
|
+
for _ in $(seq 1 60); do
|
|
10
|
+
[ -f "$WP_PATH/wp-includes/version.php" ] && break
|
|
11
|
+
sleep 2
|
|
12
|
+
done
|
|
13
|
+
|
|
14
|
+
if [ ! -f "$WP_PATH/wp-config.php" ]; then
|
|
15
|
+
$WP config create \
|
|
16
|
+
--dbname="$WORDPRESS_DB_NAME" \
|
|
17
|
+
--dbuser="$WORDPRESS_DB_USER" \
|
|
18
|
+
--dbpass="$WORDPRESS_DB_PASSWORD" \
|
|
19
|
+
--dbhost="db" --skip-check
|
|
20
|
+
fi
|
|
21
|
+
|
|
22
|
+
if ! $WP core is-installed >/dev/null 2>&1; then
|
|
23
|
+
echo "→ Installation de WordPress…"
|
|
24
|
+
$WP core install \
|
|
25
|
+
--url="$WP_HOME" --title="$WP_SITE_TITLE" \
|
|
26
|
+
--admin_user="$WP_ADMIN_USER" --admin_password="$WP_ADMIN_PASSWORD" \
|
|
27
|
+
--admin_email="$WP_ADMIN_EMAIL" --skip-email
|
|
28
|
+
fi
|
|
29
|
+
|
|
30
|
+
$WP option update home "$WP_HOME"
|
|
31
|
+
$WP option update siteurl "$WP_HOME"
|
|
32
|
+
|
|
33
|
+
echo "→ Plugins GraphQL headless…"
|
|
34
|
+
$WP plugin install wp-graphql --activate
|
|
35
|
+
# Expose editorBlocks (+ attributs) consommés par @wp-reactor/editorial.
|
|
36
|
+
$WP plugin install https://github.com/wpengine/wp-graphql-content-blocks/archive/refs/heads/main.zip --activate || true
|
|
37
|
+
# Plugin headless (baké dans l'image base) : activer s'il est présent.
|
|
38
|
+
$WP plugin activate wp-reactor-headless || true
|
|
39
|
+
|
|
40
|
+
echo "→ Thème enfant ${CHILD_THEME}…"
|
|
41
|
+
$WP theme activate "$CHILD_THEME" || echo " (thème ${CHILD_THEME} pas encore prêt, ignoré)"
|
|
42
|
+
|
|
43
|
+
echo "✓ Bootstrap terminé — $WP_HOME"
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# =============================================================================
|
|
2
|
+
# Stack DEV local __PROJECT_TITLE__ — démarrage rapide.
|
|
3
|
+
# -----------------------------------------------------------------------------
|
|
4
|
+
# WordPress = image BASE du framework (ghcr.io/wp-reactor/wordpress-base, qui
|
|
5
|
+
# bake le thème parent + plugin). Le thème ENFANT est bind-monté en live pour
|
|
6
|
+
# l'éditer sans rebuild. La webapp tourne séparément sur l'hôte (`pnpm dev`,
|
|
7
|
+
# port 3000) — la plus rapide en dev (HMR).
|
|
8
|
+
#
|
|
9
|
+
# Prérequis : l'image base est PRIVÉE (GHCR) → s'authentifier une fois :
|
|
10
|
+
# echo <PAT read:packages> | docker login ghcr.io -u <user> --password-stdin
|
|
11
|
+
#
|
|
12
|
+
# Démarrage :
|
|
13
|
+
# cp .env.example .env # ajuste si besoin
|
|
14
|
+
# docker compose -f docker-compose.dev.yml up -d
|
|
15
|
+
# docker compose -f docker-compose.dev.yml --profile tools run --rm wp-bootstrap
|
|
16
|
+
# pnpm dev # webapp sur http://localhost:3000
|
|
17
|
+
# WordPress (admin/GraphQL) : http://localhost:${WP_PORT}
|
|
18
|
+
# =============================================================================
|
|
19
|
+
name: __PROJECT_NAME__-dev
|
|
20
|
+
|
|
21
|
+
services:
|
|
22
|
+
db:
|
|
23
|
+
image: mariadb:11
|
|
24
|
+
environment:
|
|
25
|
+
MARIADB_DATABASE: ${WORDPRESS_DB_NAME}
|
|
26
|
+
MARIADB_USER: ${WORDPRESS_DB_USER}
|
|
27
|
+
MARIADB_PASSWORD: ${WORDPRESS_DB_PASSWORD}
|
|
28
|
+
MARIADB_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
|
|
29
|
+
volumes:
|
|
30
|
+
- db_data:/var/lib/mysql
|
|
31
|
+
healthcheck:
|
|
32
|
+
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
|
|
33
|
+
interval: 5s
|
|
34
|
+
timeout: 5s
|
|
35
|
+
retries: 20
|
|
36
|
+
|
|
37
|
+
redis:
|
|
38
|
+
image: redis:7-alpine
|
|
39
|
+
|
|
40
|
+
wordpress:
|
|
41
|
+
image: ghcr.io/wp-reactor/wordpress-base:latest
|
|
42
|
+
depends_on:
|
|
43
|
+
db:
|
|
44
|
+
condition: service_healthy
|
|
45
|
+
redis:
|
|
46
|
+
condition: service_started
|
|
47
|
+
ports:
|
|
48
|
+
- "${WP_PORT}:80"
|
|
49
|
+
environment:
|
|
50
|
+
WORDPRESS_DB_HOST: db
|
|
51
|
+
WORDPRESS_DB_NAME: ${WORDPRESS_DB_NAME}
|
|
52
|
+
WORDPRESS_DB_USER: ${WORDPRESS_DB_USER}
|
|
53
|
+
WORDPRESS_DB_PASSWORD: ${WORDPRESS_DB_PASSWORD}
|
|
54
|
+
# Lues par la config bakée (wp-reactor-config.php via auto_prepend_file).
|
|
55
|
+
WP_HOME: ${WP_HOME}
|
|
56
|
+
WP_SITEURL: ${WP_HOME}
|
|
57
|
+
WP_ENVIRONMENT_TYPE: local
|
|
58
|
+
WP_REDIS_HOST: redis
|
|
59
|
+
FRONTEND_URL: ${FRONTEND_URL}
|
|
60
|
+
WP_DEBUG: "true"
|
|
61
|
+
GRAPHQL_DEBUG: "true"
|
|
62
|
+
volumes:
|
|
63
|
+
- wp_data:/var/www/html
|
|
64
|
+
# Thème ENFANT bind-monté en live (le parent + plugin viennent de l'image base).
|
|
65
|
+
- ./apps/wordpress/theme-__PROJECT_NAME__:/var/www/html/wp-content/themes/__PROJECT_NAME__
|
|
66
|
+
|
|
67
|
+
wp-bootstrap:
|
|
68
|
+
image: wordpress:cli-php8.4
|
|
69
|
+
profiles: ["tools"]
|
|
70
|
+
user: "33:33"
|
|
71
|
+
depends_on:
|
|
72
|
+
db:
|
|
73
|
+
condition: service_healthy
|
|
74
|
+
environment:
|
|
75
|
+
WORDPRESS_DB_HOST: db
|
|
76
|
+
WORDPRESS_DB_NAME: ${WORDPRESS_DB_NAME}
|
|
77
|
+
WORDPRESS_DB_USER: ${WORDPRESS_DB_USER}
|
|
78
|
+
WORDPRESS_DB_PASSWORD: ${WORDPRESS_DB_PASSWORD}
|
|
79
|
+
WP_HOME: ${WP_HOME}
|
|
80
|
+
WP_SITE_TITLE: ${WP_SITE_TITLE}
|
|
81
|
+
WP_ADMIN_USER: ${WP_ADMIN_USER}
|
|
82
|
+
WP_ADMIN_PASSWORD: ${WP_ADMIN_PASSWORD}
|
|
83
|
+
WP_ADMIN_EMAIL: ${WP_ADMIN_EMAIL}
|
|
84
|
+
CHILD_THEME: __PROJECT_NAME__
|
|
85
|
+
volumes:
|
|
86
|
+
- wp_data:/var/www/html
|
|
87
|
+
- ./apps/wordpress/theme-__PROJECT_NAME__:/var/www/html/wp-content/themes/__PROJECT_NAME__
|
|
88
|
+
- ./apps/wordpress/docker/bootstrap.sh:/bootstrap.sh:ro
|
|
89
|
+
entrypoint: ["sh", "/bootstrap.sh"]
|
|
90
|
+
|
|
91
|
+
volumes:
|
|
92
|
+
db_data:
|
|
93
|
+
wp_data:
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# Déploiement Coolify — procédure ordonnée
|
|
2
|
+
|
|
3
|
+
Deux **applications Coolify autonomes** par environnement : `__PROJECT_NAME__-wordpress`
|
|
4
|
+
et `__PROJECT_NAME__-webapp`. Chacune tire son image GHCR construite par
|
|
5
|
+
`.github/workflows/deploy.yml` (push sur `main`). Variables d'env : voir
|
|
6
|
+
[`environment.md`](./environment.md) ; labels Traefik : [`traefik-labels.md`](./traefik-labels.md).
|
|
7
|
+
|
|
8
|
+
## Prérequis (une fois)
|
|
9
|
+
|
|
10
|
+
1. **GHCR accessible par Coolify** : les images sont privées. Dans Coolify →
|
|
11
|
+
*Sources / Registries*, ajouter `ghcr.io` avec un PAT `read:packages`.
|
|
12
|
+
2. **DNS** chez Cloudflare : `__PROJECT_NAME__.example.com` (front) et
|
|
13
|
+
`wp.__PROJECT_NAME__.example.com` (WordPress) → IP du serveur Coolify, **proxy activé**
|
|
14
|
+
(nuage orange) pour le front, **DNS-only** (nuage gris) pour WordPress
|
|
15
|
+
(l'admin/GraphQL ne doit pas passer par le cache edge).
|
|
16
|
+
3. **Secrets GitHub Actions** renseignés (cf [`environment.md`](./environment.md) §2).
|
|
17
|
+
|
|
18
|
+
## Ordre de déploiement (1er provisioning)
|
|
19
|
+
|
|
20
|
+
> L'ordre compte : WordPress doit exister et répondre avant que la webapp (SSR)
|
|
21
|
+
> ne l'interroge.
|
|
22
|
+
|
|
23
|
+
### 1. App WordPress
|
|
24
|
+
|
|
25
|
+
1. Coolify → *New Resource* → *Docker Image* → `ghcr.io/<owner>/<repo>/wordpress:main`.
|
|
26
|
+
2. **Network** : réseau `coolify`. Ajouter un *Network Alias*
|
|
27
|
+
`__PROJECT_NAME__-wordpress-internal` (résolu par la webapp en SSR via `WORDPRESS_INTERNAL_URL`).
|
|
28
|
+
3. **Environment Variables** : section *App WordPress* de [`environment.md`](./environment.md).
|
|
29
|
+
4. **Labels** Traefik : bloc WordPress de [`traefik-labels.md`](./traefik-labels.md).
|
|
30
|
+
5. **Persistent Storage** : volume sur `/var/www/html` (conserve core, uploads, plugins tiers).
|
|
31
|
+
6. Déployer. Vérifier `https://wp.__PROJECT_NAME__.example.com/wp-admin` et `/graphql`.
|
|
32
|
+
|
|
33
|
+
### 2. Provisioning WordPress (wp-cli, une fois)
|
|
34
|
+
|
|
35
|
+
Installer DB + plugins GraphQL + activer le thème enfant. Depuis le serveur (ou un
|
|
36
|
+
job Coolify ponctuel), avec accès à la même DB/volume :
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
# active wp-graphql + wp-graphql-content-blocks, thème enfant __PROJECT_NAME__
|
|
40
|
+
wp plugin install wp-graphql --activate
|
|
41
|
+
wp theme activate __PROJECT_NAME__
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### 3. App Webapp
|
|
45
|
+
|
|
46
|
+
1. Coolify → *New Resource* → *Docker Image* → `ghcr.io/<owner>/<repo>/webapp:main`.
|
|
47
|
+
2. **Network** : réseau `coolify` (pour résoudre `__PROJECT_NAME__-wordpress-internal`).
|
|
48
|
+
3. **Environment Variables** : section *App Webapp* de [`environment.md`](./environment.md)
|
|
49
|
+
(dont `WORDPRESS_INTERNAL_URL=http://__PROJECT_NAME__-wordpress-internal`, `SESSION_SECRET`,
|
|
50
|
+
`CF_ZONE_ID`, `CF_API_TOKEN`).
|
|
51
|
+
4. **Labels** Traefik : bloc Webapp de [`traefik-labels.md`](./traefik-labels.md).
|
|
52
|
+
5. **Post-deployment command** : `node scripts/clear-cache.mjs` (purge Cloudflare, cf §Cloudflare).
|
|
53
|
+
6. Déployer. Vérifier `https://__PROJECT_NAME__.example.com`.
|
|
54
|
+
|
|
55
|
+
## Déploiements suivants (automatiques)
|
|
56
|
+
|
|
57
|
+
Push sur `main` → `deploy.yml` build+push les images puis appelle l'API Coolify
|
|
58
|
+
(`COOLIFY_*`) pour redéployer. La **post-deployment command** purge le cache edge.
|
|
59
|
+
Rien à faire manuellement.
|
|
60
|
+
|
|
61
|
+
## Cloudflare
|
|
62
|
+
|
|
63
|
+
Le HTML SSR public est **mutualisé pour tous les visiteurs** (l'auth et le panier
|
|
64
|
+
s'hydratent côté client, jamais au SSR) — il est donc cachable à l'edge. Le middleware
|
|
65
|
+
`cacheControl` (framework) émet `s-maxage` + `stale-while-revalidate` sur les pages
|
|
66
|
+
publiques et `private, no-cache` sur les routes perso (`/mon-compte`, `/cart`, `/login`,
|
|
67
|
+
`/preview`) ou toute réponse posant un `Set-Cookie`.
|
|
68
|
+
|
|
69
|
+
### Cache Rule (zone du front)
|
|
70
|
+
|
|
71
|
+
Crée **une Cache Rule** Cloudflare sur la zone `__PROJECT_NAME__.example.com` :
|
|
72
|
+
|
|
73
|
+
- **Si** : `Method eq GET` **et** l'URI ne commence pas par `/mon-compte`, `/cart`,
|
|
74
|
+
`/login`, `/preview`, `/api`.
|
|
75
|
+
- **Alors** : *Eligible for cache* + *Respect origin TTL* (honore le `s-maxage` envoyé
|
|
76
|
+
par l'origine — ne PAS forcer un Edge TTL fixe).
|
|
77
|
+
|
|
78
|
+
> ⚠️ Ne **pas** bypasser le cache sur présence de cookie : le HTML public ne porte
|
|
79
|
+
> jamais de `Set-Cookie` et reste cachable même si la requête a un cookie de session.
|
|
80
|
+
> Bypasser sur cookie casserait le cache dès le 1er ajout panier.
|
|
81
|
+
|
|
82
|
+
### Token de purge (post-deploy)
|
|
83
|
+
|
|
84
|
+
`scripts/clear-cache.mjs` purge la zone après chaque deploy (sinon l'ancien HTML
|
|
85
|
+
caché référence des assets hashés disparus → 404 CSS/JS). Crée un **API Token**
|
|
86
|
+
Cloudflare scopé `Zone → Cache Purge → Purge` sur la zone du front, puis renseigne
|
|
87
|
+
dans l'app webapp Coolify :
|
|
88
|
+
|
|
89
|
+
- `CF_ZONE_ID` — Zone ID de `__PROJECT_NAME__.example.com`
|
|
90
|
+
- `CF_API_TOKEN` — le token ci-dessus
|
|
91
|
+
|
|
92
|
+
Si l'un manque, la purge est ignorée (le déploiement n'échoue pas).
|
|
93
|
+
|
|
94
|
+
### WordPress (host dédié)
|
|
95
|
+
|
|
96
|
+
Le host `wp.__PROJECT_NAME__.example.com` reste **DNS-only** (pas de proxy edge) :
|
|
97
|
+
l'admin, GraphQL (POST) et `/wp-json` ne doivent jamais être mis en cache partagé.
|
|
98
|
+
Les assets/uploads WordPress sont cachés via les labels Traefik (cf `traefik-labels.md`),
|
|
99
|
+
pas par Cloudflare.
|
|
100
|
+
|
|
101
|
+
## Rollback
|
|
102
|
+
|
|
103
|
+
Les images sont taguées par SHA (`...:<sha>`). Pour revenir en arrière, pointer
|
|
104
|
+
l'app Coolify sur le tag SHA précédent et redéployer. Sauvegarder la DB avant un
|
|
105
|
+
déploiement prod à risque.
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# Variables d'environnement — où va quoi
|
|
2
|
+
|
|
3
|
+
Trois destinations distinctes. Ne pas mélanger.
|
|
4
|
+
|
|
5
|
+
## 1. Local (`.env`, gitignored)
|
|
6
|
+
|
|
7
|
+
Pour `docker-compose.dev.yml` + `pnpm dev`. Copie `.env.example` → `.env`.
|
|
8
|
+
|
|
9
|
+
| Variable | Rôle |
|
|
10
|
+
|---|---|
|
|
11
|
+
| `WP_PORT` | Port hôte du WordPress local (ex. 8890) |
|
|
12
|
+
| `WP_HOME` | URL du WP local (`http://localhost:${WP_PORT}`) |
|
|
13
|
+
| `FRONTEND_URL` | URL de la webapp en dev (`http://localhost:3000`) |
|
|
14
|
+
| `WORDPRESS_DB_NAME/_USER/_PASSWORD`, `MYSQL_ROOT_PASSWORD` | DB locale |
|
|
15
|
+
| `WP_SITE_TITLE`, `WP_ADMIN_USER/_PASSWORD/_EMAIL` | Provisioning (`wp-bootstrap`) |
|
|
16
|
+
| `VITE_*` | Bakées par Vite au `pnpm dev` (front) |
|
|
17
|
+
| `SESSION_SECRET` | Sceau du cookie de session (auth) |
|
|
18
|
+
|
|
19
|
+
> L'image WordPress de base est **privée** (GHCR). Avant le 1er `up` :
|
|
20
|
+
> `echo <PAT read:packages> | docker login ghcr.io -u <user> --password-stdin`
|
|
21
|
+
|
|
22
|
+
## 2. GitHub (CI — `.github/workflows/deploy.yml`)
|
|
23
|
+
|
|
24
|
+
Repo → *Settings* → *Secrets and variables* → *Actions*.
|
|
25
|
+
|
|
26
|
+
**Secrets** (sensibles) :
|
|
27
|
+
|
|
28
|
+
| Secret | Rôle |
|
|
29
|
+
|---|---|
|
|
30
|
+
| `SESSION_SECRET` | Sceau cookie session (baké au build webapp) |
|
|
31
|
+
| `COOLIFY_URL`, `COOLIFY_TOKEN` | API Coolify (déclenche le redeploy) |
|
|
32
|
+
| `COOLIFY_WEBAPP_UUID`, `COOLIFY_WORDPRESS_UUID` | UUID des apps Coolify à redéployer |
|
|
33
|
+
|
|
34
|
+
> `GITHUB_TOKEN` est fourni automatiquement (push GHCR + lecture des packages
|
|
35
|
+
> `@wp-reactor/*` privés au build). Aucun PAT à créer côté CI.
|
|
36
|
+
|
|
37
|
+
**Variables** (non sensibles, `vars.*`, bakées au build Vite) :
|
|
38
|
+
|
|
39
|
+
| Variable | Rôle |
|
|
40
|
+
|---|---|
|
|
41
|
+
| `VITE_WORDPRESS_URL` | URL publique du WordPress (`https://${WP_HOST}`) |
|
|
42
|
+
| `VITE_GRAPHQL_PATH` | Chemin GraphQL (`/graphql`) |
|
|
43
|
+
| `VITE_FEATURE_EDITORIAL/_ECOMMERCE/_AUTH` | Flags de modules |
|
|
44
|
+
|
|
45
|
+
## 3. Coolify (runtime des apps déployées)
|
|
46
|
+
|
|
47
|
+
Dans chaque application Coolify (webapp / WordPress), onglet *Environment Variables*.
|
|
48
|
+
Ce sont les variables **runtime** (lues à l'exécution), distinctes des `VITE_*`
|
|
49
|
+
qui, elles, sont figées au build.
|
|
50
|
+
|
|
51
|
+
**App WordPress** :
|
|
52
|
+
|
|
53
|
+
| Variable | Rôle |
|
|
54
|
+
|---|---|
|
|
55
|
+
| `WORDPRESS_DB_HOST/_NAME/_USER/_PASSWORD` | Connexion DB |
|
|
56
|
+
| `WP_HOME`, `WP_SITEURL` | `https://${WP_HOST}` |
|
|
57
|
+
| `FRONTEND_URL` | `https://${APP_HOST}` (redirection headless) |
|
|
58
|
+
| `WP_REDIS_HOST`, `WP_REDIS_PASSWORD` | Cache objet Redis |
|
|
59
|
+
| `WP_ENVIRONMENT_TYPE`, `WP_DEBUG` | Environnement |
|
|
60
|
+
|
|
61
|
+
> ⚠️ NE PAS définir `WORDPRESS_CONFIG_EXTRA` ni `WORDPRESS_DEBUG` côté Coolify :
|
|
62
|
+
> la config est bakée dans l'image base (`wp-reactor-config.php`). Utiliser `WP_DEBUG`.
|
|
63
|
+
|
|
64
|
+
**App Webapp** :
|
|
65
|
+
|
|
66
|
+
| Variable | Rôle |
|
|
67
|
+
|---|---|
|
|
68
|
+
| `WORDPRESS_INTERNAL_URL` | URL interne du WP en SSR (`http://__PROJECT_NAME__-wordpress-internal`) |
|
|
69
|
+
| `SESSION_SECRET` | Doit matcher celui du build |
|
|
70
|
+
|
|
71
|
+
## TLS / routing
|
|
72
|
+
|
|
73
|
+
Les **labels Traefik** (host, TLS, cache, compression) ne sont pas des variables
|
|
74
|
+
d'env : ils se collent dans l'UI Coolify → voir [`traefik-labels.md`](./traefik-labels.md).
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# Labels Traefik (à recopier dans Coolify)
|
|
2
|
+
|
|
3
|
+
La webapp **et** WordPress sont déployées comme **applications Coolify autonomes**
|
|
4
|
+
(images GHCR construites par `.github/workflows/deploy.yml`). Aucun runtime n'est
|
|
5
|
+
dans un compose de prod — les labels Traefik ci-dessous se configurent
|
|
6
|
+
**manuellement dans l'UI Coolify** de chaque application (onglet *Labels*).
|
|
7
|
+
|
|
8
|
+
- `${APP_HOST}` = host du front (ex. `__PROJECT_NAME__.example.com`)
|
|
9
|
+
- `${WP_HOST}` = host dédié WordPress (ex. `wp.__PROJECT_NAME__.example.com`)
|
|
10
|
+
|
|
11
|
+
> ⚠️ L'app WordPress doit aussi exposer l'**alias réseau** `__PROJECT_NAME__-wordpress-internal`
|
|
12
|
+
> sur le réseau `coolify` (Coolify → *Network Aliases*), sinon la webapp ne résout
|
|
13
|
+
> plus `WORDPRESS_INTERNAL_URL` en SSR.
|
|
14
|
+
|
|
15
|
+
## WordPress (`__PROJECT_NAME__-wordpress`)
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
traefik.enable=true
|
|
19
|
+
traefik.docker.network=coolify
|
|
20
|
+
traefik.http.services.__PROJECT_NAME__-wordpress.loadbalancer.server.port=80
|
|
21
|
+
|
|
22
|
+
# Médias uploads : cache revalidable (remplaçables à la même URL, non fingerprintés)
|
|
23
|
+
traefik.http.middlewares.__PROJECT_NAME__-wp-uploads-cache.headers.customResponseHeaders.Cache-Control=public, max-age=3600, stale-while-revalidate=86400
|
|
24
|
+
traefik.http.routers.__PROJECT_NAME__-wp-uploads.rule=Host(`${WP_HOST}`) && PathPrefix(`/wp-content/uploads`)
|
|
25
|
+
traefik.http.routers.__PROJECT_NAME__-wp-uploads.entrypoints=https
|
|
26
|
+
traefik.http.routers.__PROJECT_NAME__-wp-uploads.service=__PROJECT_NAME__-wordpress
|
|
27
|
+
traefik.http.routers.__PROJECT_NAME__-wp-uploads.priority=95
|
|
28
|
+
traefik.http.routers.__PROJECT_NAME__-wp-uploads.middlewares=__PROJECT_NAME__-wp-uploads-cache
|
|
29
|
+
traefik.http.routers.__PROJECT_NAME__-wp-uploads.tls=true
|
|
30
|
+
traefik.http.routers.__PROJECT_NAME__-wp-uploads.tls.certresolver=letsencrypt
|
|
31
|
+
|
|
32
|
+
# Assets fingerprintés (cache long immutable) — prioritaire sur le catch-all WP
|
|
33
|
+
traefik.http.middlewares.__PROJECT_NAME__-wp-assets-cache.headers.customResponseHeaders.Cache-Control=public, max-age=31536000, immutable
|
|
34
|
+
traefik.http.routers.__PROJECT_NAME__-wp-assets.rule=Host(`${WP_HOST}`) && (PathPrefix(`/wp-includes`) || PathPrefix(`/wp-content`))
|
|
35
|
+
traefik.http.routers.__PROJECT_NAME__-wp-assets.entrypoints=https
|
|
36
|
+
traefik.http.routers.__PROJECT_NAME__-wp-assets.service=__PROJECT_NAME__-wordpress
|
|
37
|
+
traefik.http.routers.__PROJECT_NAME__-wp-assets.priority=90
|
|
38
|
+
traefik.http.routers.__PROJECT_NAME__-wp-assets.middlewares=__PROJECT_NAME__-wp-assets-cache
|
|
39
|
+
traefik.http.routers.__PROJECT_NAME__-wp-assets.tls=true
|
|
40
|
+
traefik.http.routers.__PROJECT_NAME__-wp-assets.tls.certresolver=letsencrypt
|
|
41
|
+
|
|
42
|
+
# SEO (robots.txt + sitemaps) servis sur le host FRONT → WordPress
|
|
43
|
+
traefik.http.routers.__PROJECT_NAME__-wp-seo.rule=Host(`${APP_HOST}`) && (Path(`/robots.txt`) || Path(`/sitemap_index.xml`) || PathRegexp(`-sitemap\.(xml|xsl)$`))
|
|
44
|
+
traefik.http.routers.__PROJECT_NAME__-wp-seo.entrypoints=https
|
|
45
|
+
traefik.http.routers.__PROJECT_NAME__-wp-seo.service=__PROJECT_NAME__-wordpress
|
|
46
|
+
traefik.http.routers.__PROJECT_NAME__-wp-seo.priority=100
|
|
47
|
+
traefik.http.routers.__PROJECT_NAME__-wp-seo.tls=true
|
|
48
|
+
traefik.http.routers.__PROJECT_NAME__-wp-seo.tls.certresolver=letsencrypt
|
|
49
|
+
|
|
50
|
+
# Catch-all WordPress (admin, /graphql, /wp-json…) sur son host dédié
|
|
51
|
+
traefik.http.routers.__PROJECT_NAME__-wordpress.rule=Host(`${WP_HOST}`)
|
|
52
|
+
traefik.http.routers.__PROJECT_NAME__-wordpress.entrypoints=https
|
|
53
|
+
traefik.http.routers.__PROJECT_NAME__-wordpress.service=__PROJECT_NAME__-wordpress
|
|
54
|
+
traefik.http.routers.__PROJECT_NAME__-wordpress.priority=10
|
|
55
|
+
traefik.http.routers.__PROJECT_NAME__-wordpress.tls=true
|
|
56
|
+
traefik.http.routers.__PROJECT_NAME__-wordpress.tls.certresolver=letsencrypt
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Webapp (`__PROJECT_NAME__-webapp`)
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
traefik.enable=true
|
|
63
|
+
traefik.docker.network=coolify
|
|
64
|
+
traefik.http.services.__PROJECT_NAME__-webapp.loadbalancer.server.port=3000
|
|
65
|
+
|
|
66
|
+
# Compression
|
|
67
|
+
traefik.http.middlewares.__PROJECT_NAME__-webapp-compress.compress=true
|
|
68
|
+
traefik.http.middlewares.__PROJECT_NAME__-webapp-compress.compress.encodings=br,gzip
|
|
69
|
+
traefik.http.middlewares.__PROJECT_NAME__-webapp-compress.compress.defaultEncoding=gzip
|
|
70
|
+
|
|
71
|
+
# Front (catch-all, priorité basse pour laisser passer les routes WP/SEO ci-dessus)
|
|
72
|
+
traefik.http.routers.__PROJECT_NAME__-webapp.rule=Host(`${APP_HOST}`)
|
|
73
|
+
traefik.http.routers.__PROJECT_NAME__-webapp.entrypoints=https
|
|
74
|
+
traefik.http.routers.__PROJECT_NAME__-webapp.priority=1
|
|
75
|
+
traefik.http.routers.__PROJECT_NAME__-webapp.middlewares=__PROJECT_NAME__-webapp-compress
|
|
76
|
+
traefik.http.routers.__PROJECT_NAME__-webapp.tls=true
|
|
77
|
+
traefik.http.routers.__PROJECT_NAME__-webapp.tls.certresolver=letsencrypt
|
|
78
|
+
```
|
|
@@ -8,7 +8,11 @@
|
|
|
8
8
|
"build": "turbo run build",
|
|
9
9
|
"check-types": "turbo run check-types",
|
|
10
10
|
"lint": "turbo run lint",
|
|
11
|
-
"gen:block": "gen-block"
|
|
11
|
+
"gen:block": "gen-block",
|
|
12
|
+
"docker:up": "docker compose -f docker-compose.dev.yml up -d",
|
|
13
|
+
"docker:bootstrap": "docker compose -f docker-compose.dev.yml --profile tools run --rm wp-bootstrap",
|
|
14
|
+
"docker:down": "docker compose -f docker-compose.dev.yml down",
|
|
15
|
+
"docker:reset": "docker compose -f docker-compose.dev.yml down -v"
|
|
12
16
|
},
|
|
13
17
|
"devDependencies": {
|
|
14
18
|
"@wp-reactor/cli": "^0.1.0",
|
package/dist/index.d.ts
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
declare const TEMPLATES_DIR: string;
|
|
2
|
-
interface ScaffoldOptions {
|
|
3
|
-
/** Nom kebab du projet (ex. "client-2") → noms de packages, dossiers, thème. */
|
|
4
|
-
projectName: string;
|
|
5
|
-
/** Dossier cible (créé). */
|
|
6
|
-
targetDir: string;
|
|
7
|
-
/** Namespace des blocs gen-block. Défaut : projectName sans tirets. */
|
|
8
|
-
namespace?: string;
|
|
9
|
-
/** Override du dossier template (tests). */
|
|
10
|
-
templatesDir?: string;
|
|
11
|
-
}
|
|
12
|
-
interface ScaffoldResult {
|
|
13
|
-
targetDir: string;
|
|
14
|
-
written: string[];
|
|
15
|
-
}
|
|
16
|
-
declare function scaffold(opts: ScaffoldOptions): ScaffoldResult;
|
|
17
|
-
|
|
18
|
-
export { type ScaffoldOptions, type ScaffoldResult, TEMPLATES_DIR, scaffold };
|
package/dist/index.js
DELETED
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
// src/index.ts
|
|
2
|
-
import {
|
|
3
|
-
mkdirSync,
|
|
4
|
-
readFileSync,
|
|
5
|
-
readdirSync,
|
|
6
|
-
statSync,
|
|
7
|
-
writeFileSync
|
|
8
|
-
} from "fs";
|
|
9
|
-
import { dirname, join, resolve } from "path";
|
|
10
|
-
import { fileURLToPath } from "url";
|
|
11
|
-
var HERE = dirname(fileURLToPath(import.meta.url));
|
|
12
|
-
var TEMPLATES_DIR = resolve(HERE, "../templates/client");
|
|
13
|
-
function titleCase(kebab) {
|
|
14
|
-
return kebab.split(/[-_]/).filter(Boolean).map((w) => w[0].toUpperCase() + w.slice(1)).join(" ");
|
|
15
|
-
}
|
|
16
|
-
function applyTokens(input, tokens) {
|
|
17
|
-
let out = input;
|
|
18
|
-
for (const [key, value] of Object.entries(tokens)) {
|
|
19
|
-
out = out.split(key).join(value);
|
|
20
|
-
}
|
|
21
|
-
return out;
|
|
22
|
-
}
|
|
23
|
-
var DOTFILES = {
|
|
24
|
-
gitignore: ".gitignore",
|
|
25
|
-
npmrc: ".npmrc"
|
|
26
|
-
};
|
|
27
|
-
function copyTemplateTree(srcDir, destDir, tokens, written) {
|
|
28
|
-
for (const entry of readdirSync(srcDir)) {
|
|
29
|
-
const srcPath = join(srcDir, entry);
|
|
30
|
-
const destName = DOTFILES[entry] ?? applyTokens(entry, tokens);
|
|
31
|
-
const destPath = join(destDir, destName);
|
|
32
|
-
if (statSync(srcPath).isDirectory()) {
|
|
33
|
-
mkdirSync(destPath, { recursive: true });
|
|
34
|
-
copyTemplateTree(srcPath, destPath, tokens, written);
|
|
35
|
-
} else {
|
|
36
|
-
const content = applyTokens(readFileSync(srcPath, "utf8"), tokens);
|
|
37
|
-
mkdirSync(dirname(destPath), { recursive: true });
|
|
38
|
-
writeFileSync(destPath, content);
|
|
39
|
-
written.push(destPath);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
function scaffold(opts) {
|
|
44
|
-
const projectName = opts.projectName;
|
|
45
|
-
const namespace = opts.namespace ?? projectName.replace(/[-_]/g, "");
|
|
46
|
-
const templatesDir = opts.templatesDir ?? TEMPLATES_DIR;
|
|
47
|
-
const targetDir = resolve(opts.targetDir);
|
|
48
|
-
const tokens = {
|
|
49
|
-
__PROJECT_NAME__: projectName,
|
|
50
|
-
__PROJECT_TITLE__: titleCase(projectName),
|
|
51
|
-
__BLOCK_NAMESPACE__: namespace
|
|
52
|
-
};
|
|
53
|
-
const written = [];
|
|
54
|
-
mkdirSync(targetDir, { recursive: true });
|
|
55
|
-
copyTemplateTree(templatesDir, targetDir, tokens, written);
|
|
56
|
-
return { targetDir, written };
|
|
57
|
-
}
|
|
58
|
-
export {
|
|
59
|
-
TEMPLATES_DIR,
|
|
60
|
-
scaffold
|
|
61
|
-
};
|
|
62
|
-
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["// create-wp-reactor — scaffolder de repo client mince.\n//\n// Émet un monorepo client « 2 mondes » depuis un template BUNDLÉ (npx-friendly :\n// aucun accès au monorepo framework au runtime) :\n// • apps/webapp ← coque (starter synchronisé dans templates/, voir\n// scripts/sync-template.mjs)\n// • apps/wordpress ← thème ENFANT\n// • .github, docker, wp-reactor.config.json, workspace root\n//\n// Le runtime ne fait que copier templates/client/ en substituant les tokens.\n\nimport {\n\tmkdirSync,\n\treadFileSync,\n\treaddirSync,\n\tstatSync,\n\twriteFileSync,\n} from \"node:fs\";\nimport { dirname, join, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nconst HERE = dirname(fileURLToPath(import.meta.url));\n// src/ (dev via tsx) comme dist/ (build) sont à 1 niveau du package.\nconst TEMPLATES_DIR = resolve(HERE, \"../templates/client\");\n\nexport interface ScaffoldOptions {\n\t/** Nom kebab du projet (ex. \"client-2\") → noms de packages, dossiers, thème. */\n\tprojectName: string;\n\t/** Dossier cible (créé). */\n\ttargetDir: string;\n\t/** Namespace des blocs gen-block. Défaut : projectName sans tirets. */\n\tnamespace?: string;\n\t/** Override du dossier template (tests). */\n\ttemplatesDir?: string;\n}\n\nexport interface ScaffoldResult {\n\ttargetDir: string;\n\twritten: string[];\n}\n\nfunction titleCase(kebab: string): string {\n\treturn kebab\n\t\t.split(/[-_]/)\n\t\t.filter(Boolean)\n\t\t.map((w) => w[0].toUpperCase() + w.slice(1))\n\t\t.join(\" \");\n}\n\nfunction applyTokens(input: string, tokens: Record<string, string>): string {\n\tlet out = input;\n\tfor (const [key, value] of Object.entries(tokens)) {\n\t\tout = out.split(key).join(value);\n\t}\n\treturn out;\n}\n\n// npm strippe `.gitignore` et `.npmrc` des tarballs publiés : on les stocke\n// sans point dans le template et on les re-dotte à l'écriture.\nconst DOTFILES: Record<string, string> = {\n\tgitignore: \".gitignore\",\n\tnpmrc: \".npmrc\",\n};\n\n/** Copie récursive du template en substituant les tokens dans chemins + contenu. */\nfunction copyTemplateTree(\n\tsrcDir: string,\n\tdestDir: string,\n\ttokens: Record<string, string>,\n\twritten: string[],\n): void {\n\tfor (const entry of readdirSync(srcDir)) {\n\t\tconst srcPath = join(srcDir, entry);\n\t\tconst destName = DOTFILES[entry] ?? applyTokens(entry, tokens);\n\t\tconst destPath = join(destDir, destName);\n\t\tif (statSync(srcPath).isDirectory()) {\n\t\t\tmkdirSync(destPath, { recursive: true });\n\t\t\tcopyTemplateTree(srcPath, destPath, tokens, written);\n\t\t} else {\n\t\t\tconst content = applyTokens(readFileSync(srcPath, \"utf8\"), tokens);\n\t\t\tmkdirSync(dirname(destPath), { recursive: true });\n\t\t\twriteFileSync(destPath, content);\n\t\t\twritten.push(destPath);\n\t\t}\n\t}\n}\n\nexport function scaffold(opts: ScaffoldOptions): ScaffoldResult {\n\tconst projectName = opts.projectName;\n\tconst namespace = opts.namespace ?? projectName.replace(/[-_]/g, \"\");\n\tconst templatesDir = opts.templatesDir ?? TEMPLATES_DIR;\n\tconst targetDir = resolve(opts.targetDir);\n\tconst tokens: Record<string, string> = {\n\t\t__PROJECT_NAME__: projectName,\n\t\t__PROJECT_TITLE__: titleCase(projectName),\n\t\t__BLOCK_NAMESPACE__: namespace,\n\t};\n\tconst written: string[] = [];\n\n\tmkdirSync(targetDir, { recursive: true });\n\tcopyTemplateTree(templatesDir, targetDir, tokens, written);\n\n\treturn { targetDir, written };\n}\n\nexport { TEMPLATES_DIR };\n"],"mappings":";AAWA;AAAA,EACC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACM;AACP,SAAS,SAAS,MAAM,eAAe;AACvC,SAAS,qBAAqB;AAE9B,IAAM,OAAO,QAAQ,cAAc,YAAY,GAAG,CAAC;AAEnD,IAAM,gBAAgB,QAAQ,MAAM,qBAAqB;AAkBzD,SAAS,UAAU,OAAuB;AACzC,SAAO,MACL,MAAM,MAAM,EACZ,OAAO,OAAO,EACd,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,YAAY,IAAI,EAAE,MAAM,CAAC,CAAC,EAC1C,KAAK,GAAG;AACX;AAEA,SAAS,YAAY,OAAe,QAAwC;AAC3E,MAAI,MAAM;AACV,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,MAAM,GAAG;AAClD,UAAM,IAAI,MAAM,GAAG,EAAE,KAAK,KAAK;AAAA,EAChC;AACA,SAAO;AACR;AAIA,IAAM,WAAmC;AAAA,EACxC,WAAW;AAAA,EACX,OAAO;AACR;AAGA,SAAS,iBACR,QACA,SACA,QACA,SACO;AACP,aAAW,SAAS,YAAY,MAAM,GAAG;AACxC,UAAM,UAAU,KAAK,QAAQ,KAAK;AAClC,UAAM,WAAW,SAAS,KAAK,KAAK,YAAY,OAAO,MAAM;AAC7D,UAAM,WAAW,KAAK,SAAS,QAAQ;AACvC,QAAI,SAAS,OAAO,EAAE,YAAY,GAAG;AACpC,gBAAU,UAAU,EAAE,WAAW,KAAK,CAAC;AACvC,uBAAiB,SAAS,UAAU,QAAQ,OAAO;AAAA,IACpD,OAAO;AACN,YAAM,UAAU,YAAY,aAAa,SAAS,MAAM,GAAG,MAAM;AACjE,gBAAU,QAAQ,QAAQ,GAAG,EAAE,WAAW,KAAK,CAAC;AAChD,oBAAc,UAAU,OAAO;AAC/B,cAAQ,KAAK,QAAQ;AAAA,IACtB;AAAA,EACD;AACD;AAEO,SAAS,SAAS,MAAuC;AAC/D,QAAM,cAAc,KAAK;AACzB,QAAM,YAAY,KAAK,aAAa,YAAY,QAAQ,SAAS,EAAE;AACnE,QAAM,eAAe,KAAK,gBAAgB;AAC1C,QAAM,YAAY,QAAQ,KAAK,SAAS;AACxC,QAAM,SAAiC;AAAA,IACtC,kBAAkB;AAAA,IAClB,mBAAmB,UAAU,WAAW;AAAA,IACxC,qBAAqB;AAAA,EACtB;AACA,QAAM,UAAoB,CAAC;AAE3B,YAAU,WAAW,EAAE,WAAW,KAAK,CAAC;AACxC,mBAAiB,cAAc,WAAW,QAAQ,OAAO;AAEzD,SAAO,EAAE,WAAW,QAAQ;AAC7B;","names":[]}
|