docs(ci): capitalise le piège Node 18→20 des apps Bun/Nuxt (page 6b)
Nouvelle page runbook « CI des apps Bun/Nuxt » : le runner par défaut est en Node 18, or nuxi/nuxt importent styleText de node:util (Node 20+) → la CI casse au build. Parade tenue (validée sur arcodange/kadans) : container node:20-bookworm + Bun via npm. Inclut un ci.yml canonique à copier + les pistes infra runner. Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
[Factory](../../../README.md) > [Doc](../../README.md) > [Runbooks](../README.md) > [Nouvelle application web](README.md) > **6b. CI des apps Bun/Nuxt**
|
||||
|
||||
# 6b. CI des apps Bun/Nuxt (`.gitea/workflows/ci.yml`)
|
||||
|
||||
> **Status:** ✅ Active
|
||||
> **Upstream:** [6. Workflows CI](06-ci-workflows.md)
|
||||
> **Related:** [1. Dépôt Gitea](01-gitea-repo.md) (secrets d'org) · [Conventions de nommage](conventions.md) · [ADR CI/CD](../../adr/03_cicd_gitea_action_argocd.md)
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
Une app **Bun + Nuxt** qui fait tourner ses gates (lint / typecheck / tests / `nuxt build`) **directement sur le runner** heurte un piège : le runner Gitea Actions par défaut expose **Node 18**, or `nuxi`/`nuxt` importent **`styleText` de `node:util`**, une API **ajoutée en Node 20** (v20.12) — la CI casse au premier `bun run build`. La parade tenue : lancer le job dans un **conteneur `node:20-bookworm`** et y installer Bun via npm. Ce workflow (`ci.yml`) est **distinct** des deux workflows de l'[étape 6](06-ci-workflows.md) (`vault.yaml` = `tofu apply`, `dockerimage.yaml` = build image) : il porte les **gates qualité** de l'app.
|
||||
|
||||
> [!WARNING]
|
||||
> **Le piège Node 18 → 20.** Symptôme au premier run : un crash `nuxi` du type `The requested module 'node:util' does not provide an export named 'styleText'`, alors que `bun install` a réussi. Cause : `styleText` n'existe pas en Node 18. Ce n'est **pas** un souci de Bun — Bun orchestre, mais Nuxt/Nuxi s'exécutent sous le **Node** de l'environnement.
|
||||
|
||||
## Pourquoi le runner est en Node 18
|
||||
|
||||
Les runners `act_runner` de la plateforme enregistrent **deux labels qui pointent la même image** — [`ansible/…/playbooks/03_cicd.yml`](../../../ansible/arcodange/factory/playbooks/03_cicd.yml) :
|
||||
|
||||
```
|
||||
ubuntu-latest → docker://gitea.arcodange.lab/arcodange-org/runner-images:ubuntu-latest-ca
|
||||
ubuntu-latest-ca → docker://gitea.arcodange.lab/arcodange-org/runner-images:ubuntu-latest-ca
|
||||
```
|
||||
|
||||
Cette image est construite par [`ansible/…/playbooks/ssl/ssl.yml`](../../../ansible/arcodange/factory/playbooks/ssl/ssl.yml), `FROM gitea/runner-images:ubuntu-latest` (+ le CA du homelab). Le Node embarqué dans cette base upstream est **Node 18** : sans conteneur explicite, **tout job** (app ou factory) s'exécute avec ce Node.
|
||||
|
||||
## La parade : `container: node:20-bookworm`
|
||||
|
||||
Modèle de `.gitea/workflows/ci.yml` à copier dans une app Bun/Nuxt (repris de [`arcodange/kadans`](https://gitea.arcodange.lab/arcodange/kadans/src/branch/main/.gitea/workflows/ci.yml)) :
|
||||
|
||||
```yaml
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push: { branches: [main] }
|
||||
pull_request:
|
||||
|
||||
concurrency:
|
||||
group: ci-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
gates:
|
||||
runs-on: ubuntu-latest
|
||||
# Node 20 requis : nuxi/nuxt importent `styleText` de `node:util` (Node 20+),
|
||||
# absent du Node 18 de l'image runner par défaut. `node:20-bookworm` = Node 20 + git.
|
||||
container: node:20-bookworm
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Bun
|
||||
run: npm install -g bun
|
||||
- name: Install deps (lockfile figé)
|
||||
run: bun install --frozen-lockfile
|
||||
- name: Gates (lint / typecheck / tests — adapter aux scripts de l'app)
|
||||
run: bun run check
|
||||
- name: Build Nuxt
|
||||
run: bun run build
|
||||
```
|
||||
|
||||
Points de vigilance :
|
||||
|
||||
- **Bun via npm.** L'image `node:20-bookworm` n'a pas Bun ; `npm install -g bun` suffit. (Alternative : l'image `oven/bun:1` — mais elle n'embarque **pas** Node, ce qui rejoue le problème inverse pour les outils qui veulent un binaire `node`.)
|
||||
- **`--frozen-lockfile`** en CI : échoue si le lockfile n'est pas à jour (garde-fou de reproductibilité).
|
||||
- **`bookworm` (Debian) plutôt qu'`alpine`** : `alpine` (musl) casse certaines dépendances natives de l'écosystème Nuxt.
|
||||
|
||||
> [!CAUTION]
|
||||
> **CA du homelab.** `node:20-bookworm` **ne fait pas confiance** au CA privé du homelab (contrairement à `runner-images:ubuntu-latest-ca`). La parade convient tant que les gates ne joignent que des endpoints **publics** (registre npm, `actions/checkout` via l'instance Gitea interne). Si un gate doit atteindre **`https://gitea.arcodange.lab`** (paquet privé, registre interne), le conteneur vanilla échouera la validation TLS — il faut alors une image Node 20 **avec** le CA (voir *Pistes* ci-dessous).
|
||||
|
||||
## Recommandation
|
||||
|
||||
Pour toute app **Bun/Nuxt** dont la CI fait `nuxt build`/`nuxi` **sur le runner**, ajouter `container: node:20-bookworm` au job (parade ci-dessus). C'est le contrat par défaut tant que l'image runner par défaut reste en Node 18.
|
||||
|
||||
Le **build d'image** (`dockerimage.yaml`, [étape 6](06-ci-workflows.md)) n'est **pas** concerné : la compilation Nuxt s'y fait **dans le `Dockerfile`** (base `node:20`/`oven/bun` au choix de l'app), pas sur le Node du runner.
|
||||
|
||||
## Pistes (infra runner — à valider par l'admin)
|
||||
|
||||
Ces options **retirent le contournement par-repo** mais touchent la **prod runner** (rebuild d'image + re-run Ansible `03_cicd` sur `pi1`/`pi3`, pas un simple merge). Laissées à l'arbitrage :
|
||||
|
||||
1. **Bumper l'image runner.** Dans [`ssl.yml`](../../../ansible/arcodange/factory/playbooks/ssl/ssl.yml), baser `runner-images:*-ca` sur une image `gitea/runner-images` qui embarque Node 20+ (vérifier le Node du tag), rebuild + push, puis re-run `03_cicd`. **Rayon d'impact large** : change l'environnement par défaut de **tous** les jobs, y compris les workflows factory `iac.yaml`/`postgres.yaml` (`runs-on: ubuntu-latest-ca`) — à revalider.
|
||||
2. **Ajouter un label `node20` dédié.** Publier une image `runner-images:node20-ca` (`FROM node:20-bookworm` + CA du homelab) et l'exposer via un label `node20` dans [`03_cicd.yml`](../../../ansible/arcodange/factory/playbooks/03_cicd.yml). Les apps opt-in avec `runs-on: node20`, **sans** `container:` par job, et **avec** le CA (résout la limite ci-dessus). Rayon d'impact **opt-in** (plus sûr), mais ajoute une image à maintenir.
|
||||
3. **Action réutilisable `bun-nuxt-ci`.** Sur le modèle des dépôts d'action de l'org (`arcodange-org/vault-action`), factoriser install-Bun + gates + build dans une action composite appelée en 3 lignes. Évite le copier-coller du YAML, indépendamment du Node du runner.
|
||||
|
||||
## Related
|
||||
|
||||
- [6. Workflows CI](06-ci-workflows.md) — `vault.yaml` (`tofu apply`) et `dockerimage.yaml` (build image) ; `ci.yml` (cette page) porte les gates de l'app.
|
||||
- [1. Dépôt Gitea](01-gitea-repo.md) — secrets d'org hérités par la CI.
|
||||
- Sources runner : [`03_cicd.yml`](../../../ansible/arcodange/factory/playbooks/03_cicd.yml) (labels) · [`ssl/ssl.yml`](../../../ansible/arcodange/factory/playbooks/ssl/ssl.yml) (image + CA).
|
||||
- Exemple vivant : [`arcodange/kadans` — `ci.yml`](https://gitea.arcodange.lab/arcodange/kadans/src/branch/main/.gitea/workflows/ci.yml).
|
||||
Reference in New Issue
Block a user