Files
factory/doc/runbooks/new-web-app/06b-bun-nuxt-ci.md
T
arcodangeandClaude Opus 4.8 f5c01da70d 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]>
2026-07-12 13:31:41 +02:00

6.6 KiB

Factory > Doc > Runbooks > Nouvelle application web > 6b. CI des apps Bun/Nuxt

6b. CI des apps Bun/Nuxt (.gitea/workflows/ci.yml)

Status: Active Upstream: 6. Workflows CI Related: 1. Dépôt Gitea (secrets d'org) · Conventions de nommage · ADR CI/CD


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 (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 imageansible/…/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, 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) :

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) 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, 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. 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.