name: ps-css-build description: > Sets up a modern CSS build pipeline for a PrestaShop child theme: source partials in _dev/css/, lightningcss bundle+minify to a single generated assets/css/custom.css, pnpm 11 with supply-chain hardening, pre-commit hook with zero-dependency Node fallback, and agent/team documentation. Replaces runtime @import chains and manual ?v= cache-bust versioning. Use when scaffolding CSS architecture for a new PS child theme, when a theme has multiple @import'ed CSS partials loaded at runtime, when the user mentions manual CSS version bumps, CSS cache busting problems, "el CSS no se actualiza", or wants the Milagros-style CSS build on another project. version: "0.1.0" metadata: author: Eduardo Calvo
PS CSS Build — pipeline para child themes
Montado y probado en milagros-colombia-b2b-v9.1 (PS 9.1, child de Panda).
Sustituye: @import runtime (N requests encadenados) + bumps manuales ?v=N
- hacks tipo
_bust={$smarty.now}(que anulan caché por completo).
Resultado final
themes/{child}/
├── _dev/
│ ├── README.md # doc del sistema (plantilla abajo)
│ └── css/
│ ├── custom.css # entry point: @import de los parciales (SIN ?v=)
│ ├── tokens.css # :root design tokens
│ ├── base.css
│ ├── components.css
│ ├── utilities.css
│ └── pages/*.css
├── scripts/bundle-css.js # fallback cero-deps para el hook
├── assets/css/custom.css # GENERADO — único CSS servido
├── package.json # pnpm + scripts css/watch
├── pnpm-workspace.yaml # supply-chain hardening
├── pnpm-lock.yaml
└── .gitignore # node_modules/
.githooks/pre-commit # raíz del repo
.gitattributes # dist marcado generated
PS core auto-registra assets/css/custom.css como theme-custom — mantener
ese path/nombre exacto = cero cambios PHP, tema padre intacto.
Paso 1 — Mover fuentes
cd themes/{child}
mkdir -p _dev/css
git mv assets/css/*.css _dev/css/ # parciales + custom.css
git mv assets/css/pages _dev/css/pages # si existe
Limpiar el entry point _dev/css/custom.css:
- Quitar todos los
?v=Nde los imports:sed -i '' -E "s/\.css\?v=[0-9]+'/.css'/g" _dev/css/custom.css - CRÍTICO: buscar
@importcon URL remota (Google Fonts) en TODOS los parciales:grep -rn "@import url('http" _dev/css/. lightningcss NO bundlea remotas — panic con error crípticoResolverError NotFound. Moverlas como<link rel="preconnect">+<link rel="stylesheet">al overridetemplates/_partials/stylesheets.tpl(mejor perf además: descarga paralela). - Revisar
url()relativos en parciales (grep -rn "url(" _dev/css/ | grep -v @import). Rutas absolutas (/img/...) OK; relativas pueden romper al cambiar de nivel.
Paso 2 — package.json + pnpm hardened
themes/{child}/package.json:
{
"name": "{child}-theme",
"private": true,
"packageManager": "pnpm@11.1.2",
"scripts": {
"css": "lightningcss --bundle --minify --targets 'defaults' _dev/css/custom.css -o assets/css/custom.css",
"watch": "chokidar '_dev/css/**/*.css' -c 'pnpm run --silent css' --initial"
},
"devDependencies": {
"chokidar-cli": "^3.0.0",
"lightningcss-cli": "^1.30.2"
}
}
Pin de versión pnpm exacta (la instalada: pnpm --version), no 11.x.x.
themes/{child}/pnpm-workspace.yaml (ver skills supply-chain-security y
gitlab-security-setup para el detalle):
allowBuilds:
lightningcss-cli: true # único postinstall permitido
minimumReleaseAge: 4320 # 72h quarantine (estándar Cinetic)
blockExoticSubdeps: true # bloquea transitivas git/tarball (pnpm 10.26+)
overrides:
form-data: ">=4.0.4"
axios: ">=1.15.2"
lodash: ">=4.18.0"
picomatch: ">=4.0.4"
qs: ">=6.14.2"
themes/{child}/.gitignore: node_modules/
pnpm install --dir themes/{child}
pnpm --dir themes/{child} run css # genera el dist
Gotcha: pnpm 11 bloqueará el postinstall de lightningcss-cli en el primer
install y escribirá un placeholder allowBuilds: lightningcss-cli: set this to true or false en pnpm-workspace.yaml — editarlo a true y re-instalar.
Test real: rm -rf node_modules && pnpm install --frozen-lockfile —
la caché caliente NO aplica las políticas.
Flujo de desarrollo diario (documentar al usuario)
Mientras se edita CSS, el watcher debe estar corriendo en una terminal:
pnpm --dir themes/{child} run watch
- Guardas un parcial en
_dev/css/→ chokidar dispara lightningcss → dist regenerado en ~10ms → recarga del browser y listo. --initialhace un build al arrancar, así que el dist nunca queda stale al empezar la sesión.- Sin watch corriendo, los cambios en
_dev/css/NO se ven en el browser (el browser carga el dist generado, no los fuentes). Síntoma típico: "cambié el CSS y no sale" → watch no estaba corriendo → build puntual:pnpm --dir themes/{child} run css. - Combina con la skill
ps-watch(BrowserSync) para recarga automática del browser además del rebuild: chokidar regenera el dist y BrowserSync detecta el cambio enassets/css/y recarga. Watch + BrowserSync = guardar y ver.
Paso 3 — Limpiar el tpl
Revisar templates/_partials/stylesheets.tpl del child: si tiene hacks de
cache-bust (?_bust={$smarty.now}, timestamps), quitarlos — anulan caché en
cada pageview. Si el override solo existía para eso, dejarlo igual que el
padre + el bloque de fonts:
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family={...}&display=swap">
{foreach $stylesheets.external as $stylesheet}
<link rel="stylesheet" href="{$stylesheet.uri}" media="{$stylesheet.media}">
{/foreach}
...resto igual que el padre (Panda añade $sttheme.custom_css al final)...
Cache-busting de producción: el dist commiteado cambia de contenido → ETag
nuevo → revalidación. Si nginx sirve .css con expires largo, opciones:
Cache-Control: no-cache para el dir del tema (304s baratos), o activar
CCC "Smart cache CSS" en BO Rendimiento (ahora funciona porque el CSS es
plano — con @imports runtime se rompía).
Paso 4 — Hook pre-commit con fallback cero-deps
.githooks/pre-commit (raíz, ejecutable chmod +x):
#!/bin/sh
# Rebuild theme CSS bundle when staged changes touch _dev/css sources.
# Works for everyone: pnpm if installed, zero-dep Node.js bundler otherwise.
THEME="themes/{child}"
if git diff --cached --quiet -- "$THEME/_dev/css"; then
exit 0
fi
echo "pre-commit: _dev/css changed — rebuilding custom.css..."
if [ -d "$THEME/node_modules" ]; then
pnpm --dir "$THEME" run --silent css || exit 1
echo "pre-commit: rebuilt OK (minified)"
else
node "$THEME/scripts/bundle-css.js" || exit 1
echo "pre-commit: rebuilt OK (unminified — run 'pnpm install --dir $THEME' for minified)"
fi
git add "$THEME/assets/css/custom.css"
themes/{child}/scripts/bundle-css.js — bundler Node sin dependencias
(resuelve @imports recursivo, salta remotas). Copiarlo de
milagros-colombia-b2b-v9.1/themes/milagros/scripts/bundle-css.js ajustando
nada (usa rutas relativas a __dirname).
Activación (por clone, git no auto-activa hooks por diseño):
git config core.hooksPath .githooks
.gitattributes (raíz):
themes/{child}/assets/css/custom.css linguist-generated=true -diff
Paso 5 — Documentación (obligatorio, es la red de seguridad real)
El hook NO se activa solo en clones nuevos. La protección para compañeros/ agentes que no conocen el sistema es documentación:
- CLAUDE.md del proyecto — sección "CSS del tema hijo — REGLA OBLIGATORIA":
dist generado (jamás editarlo), fuentes en
_dev/css/, comando rebuild, activación del hook, sin?v=, remotas como<link>, no tocar hardening pnpm. Copiar la sección demilagros-colombia-b2b-v9.1/CLAUDE.md. themes/{child}/_dev/README.md— doc completa: diagrama del flujo, setup por clone, watch, cómo funciona el hook (2 caminos), supply chain, tabla troubleshooting. Copiar demilagros-colombia-b2b-v9.1/themes/milagros/_dev/README.md.
Paso 6 — Verificar
pnpm --dir themes/{child} run css→ dist generado, comparar tamaño razonable- Cascade:
tail -c 300 assets/css/custom.css— los overrides del entry deben estar al FINAL (lightningcss respeta orden de imports) - Hook ambos caminos: stage un parcial →
.githooks/pre-commit→ exit 0 y dist staged; repetir connode_modulesrenombrado (camino fallback) - En Lando con agent-browser: página renderiza,
<link>custom.css sin query hacks, tokens aplicados (getComputedStylede una variable), fuente OK rm -rf node_modules && pnpm install --frozen-lockfile && pnpm run css
Limitación conocida (aceptada)
Si alguien clona, NO activa el hook y commitea cambios de _dev/css/, el dist
queda desactualizado y "su cambio no sale". Mitigación: documentación (Paso 5)
- el que integre con agente lo detecta por CLAUDE.md. Opciones descartadas en
Milagros por complejidad/deps: Husky, package.json raíz con prepare, build en
deploy, job CI de sincronía (válida si el proyecto ya tiene runners — ver
gitlab-security-setup).