Workflows
Un proyecto puede llevar sus pipelines en el repositorio, en .fundaia/workflows/*.yml, con la sintaxis de GitHub Actions. Los ejecuta la plataforma, en contenedores de su propia máquina: no hace falta GitHub Actions para probar, construir ni publicar nada. Un workflow tiene jobs con pasos; un push, una programación o un botón producen una ejecución.
# .fundaia/workflows/ci.yml
name: CI
on:
push:
branches: [main]
paths-ignore: ['docs/**']
workflow_dispatch:
inputs:
lane:
type: choice
options: [beta, release]
default: beta
env:
NODE_ENV: test
jobs:
test:
image: oven/bun:1
cache: ['~/.bun/install/cache']
steps:
- run: bun install --frozen-lockfile
- name: Tests
run: bun test
build-android:
needs: [test]
image: ghcr.io/cirruslabs/flutter:stable
memory: 4096
timeout-minutes: 90
artifacts: ['build/app/outputs/**/*.apk']
steps:
- run: flutter build apk --release
- if: failure()
run: echo "Falló el build" >> "$GITHUB_STEP_SUMMARY"
publish:
needs: [build-android]
if: fundaia.ref_name == 'main'
steps:
- run: ls /fundaia/artifacts/build-android
- run: ./scripts/publish.sh --lane "${{ inputs.lane }}"
env:
STORE_TOKEN: ${{ secrets.STORE_TOKEN }}
Cuándo se ejecuta
- **
on.push** conbranches,branches-ignore,pathsypaths-ignore, con los mismos patrones que GitHub (*,**,!patrón). Se mira cada rama que sigue algún servicio del proyecto, una vez por repositorio y rama, cada dos minutos o al instante si el webhook de GitHub está configurado. Un push ejecuta cada workflow una vez, aunque cuatro servicios compartan el repositorio. - **
on.workflow_dispatch** coninputs(string,boolean,number,choice): el botón Ejecutar de la pantalla,fundaia workflows runo la herramientarun_workflow. - **
on.schedule** con expresiones cron de cinco campos, en UTC. - **
on.workflow_run**: el workflow arranca cuando termina otro, en el mismo commit y la misma rama. Es el post-workflow (publicar cuando el build acabó, avisar cuando los tests fallaron) y se escribe como en GitHub, con una llave más que ahorra repetir la condición en cada job:conclusionsdice con qué finales arranca (success,failure,cancelled; sin ella, con cualquiera). Una cadena llega como mucho a tres niveles, y un workflow no puede seguirse a sí mismo. - **
concurrency** agrupa ejecuciones: la más antigua del grupo va primero; concancel-in-progress: truela nueva cancela a las demás.
# .fundaia/workflows/publish.yml
name: Publish
on:
workflow_run:
workflows: [CI] # por su `name`
branches: [main]
conclusions: [success] # sólo si CI pasó
jobs:
publish:
steps:
- run: ./scripts/publish.sh "${{ fundaia.event.workflow_run.head_sha }}"
En la pantalla, la ejecución dice tras CI #41 y enlaza con ella; la de CI lista debajo, en Después, lo que arrancó al terminar. En las expresiones y en el entorno, fundaia.event.workflow_run (name, run_number, conclusion, head_sha, head_branch) y FUNDAIA_PARENT_RUN_ID, FUNDAIA_PARENT_CONCLUSION.
La primera vez que la plataforma ve una rama no ejecuta nada: anota dónde está y a partir del siguiente push compara.
Qué hay en un job
Un job es un contenedor de la imagen que nombra image (container también vale), con el código en /workspace y sus pasos ejecutados en orden dentro de él. Sin image, Ubuntu 24.04. Todo lo que puede correr en Linux corre aquí: Bun, Node, Python, Rust, Flutter, Gradle, fastlane para subir a las tiendas. Compilar para iOS necesita macOS y no cabe en esta máquina; lo que sí cabe es lanzar y esperar un build remoto (EAS, Codemagic, un Mac propio) desde un paso.
| Clave | Qué hace |
|---|---|
needs | Jobs que deben terminar bien antes. needs.<job>.result y needs.<job>.outputs.<x> en expresiones. |
if | Expresión que decide si corre: always(), failure(), fundaia.ref_name == 'main'… |
env | Variables para todos sus pasos, además de las del workflow. |
memory, cpus | Techo del contenedor: 1024 MB y 1 CPU por defecto, hasta 8192 y 4. |
timeout-minutes | 60 por defecto, 360 como máximo. |
cache | Rutas del contenedor que sobreviven entre ejecuciones, como volúmenes con nombre. |
services | Contenedores auxiliares (postgres:16, redis:7) alcanzables por su nombre. |
artifacts | Rutas bajo el checkout que se guardan al terminar, como un tar.gz descargable. |
outputs | Valores para los jobs que dependen de este, desde steps.<id>.outputs. |
strategy.matrix | Una copia del job por combinación, con include, exclude y fail-fast. |
continue-on-error | Que falle sin tumbar la ejecución. |
shell | bash (por defecto; sh si la imagen no tiene bash), sh, python, node. |
Cada paso lleva run y opcionalmente name, id, env, working-directory, shell, timeout-minutes, continue-on-error e if. uses: no existe aquí: los pasos son comandos. uses: actions/checkout se ignora porque el código ya está en /workspace.
Sin repetirse
Lo que todos los jobs comparten se escribe una vez, en defaults, y cada job cambia sólo lo suyo. Además de run.shell y run.working-directory (como en GitHub), aquí defaults acepta image, memory, cpus, timeout-minutes y cache:
defaults:
image: oven/bun:1
memory: 2048
cache: ['~/.bun/install/cache']
run:
shell: bash
jobs:
test:
steps: [{ run: bun test }]
build:
memory: 4096 # el resto viene de defaults
steps: [{ run: bun run build }]
Los anclas de YAML también funcionan, para pasos que se repiten en varios jobs:
x-install: &install
name: Instalar
run: bun install --frozen-lockfile
jobs:
test:
steps: [*install, { run: bun test }]
lint:
steps: [*install, { run: bun run lint }]
Secretos y variables
${{ secrets.NOMBRE }} y ${{ vars.NOMBRE }} leen las variables del alcance de proyecto, nunca las de un servicio: un job de tests no debe recibir el DATABASE_URL de producción por estar cerca. Un secreto que no existe rechaza el job con una frase, en vez de valer '' como en GitHub. Los valores secretos se enmascaran en el log.
Contextos disponibles: fundaia (o github, con los mismos nombres: sha, ref_name, repository, run_number, event_name, actor…), env, inputs, matrix, needs, steps, runner, job. Funciones: contains, startsWith, endsWith, format, join, toJSON, fromJSON, always, success, failure, cancelled.
Cada paso recibe CI=true, FUNDAIA_* y los GITHUB_* que las herramientas buscan (GITHUB_SHA, GITHUB_REF_NAME, GITHUB_REPOSITORY, GITHUB_RUN_NUMBER, GITHUB_WORKSPACE, RUNNER_OS…), y los cuatro ficheros de GitHub:
echo "version=1.2.3" >> "$GITHUB_OUTPUT" # outputs del paso (necesita id:)
echo "PATH_EXTRA=/opt/x" >> "$GITHUB_ENV" # variable para los pasos siguientes
echo "/opt/x/bin" >> "$GITHUB_PATH" # PATH para los pasos siguientes
echo "## 12 tests pasan" >> "$GITHUB_STEP_SUMMARY" # se muestra en la ejecución
Artefactos entre jobs
Lo que un job guarda en artifacts aparece en los jobs que dependen de él bajo /fundaia/artifacts/<job>/, y se descarga desde la pantalla, la API o fundaia workflows artifact <job> --into ./salida. Se borra con la ejecución, a los noventa días.
Recursos y límites
| Jobs a la vez | Dos en toda la máquina; uno arranca solo si la memoria libre alcanza para su memory y la de sus services |
| Memoria por job | 1024 MB por defecto, de 128 a 8192 |
| CPU por job | 1 por defecto, hasta 4 |
| Tiempo por job | 60 minutos por defecto, hasta 360 |
| Por workflow | 20 jobs, 50 pasos por job, 32 combinaciones de matriz, 5 services y 8 rutas de cache |
Las imágenes se descargan una vez y se reutilizan. El contenedor corre como root dentro de podman sin privilegios, con las capacidades justas para instalar paquetes.
Desde fuera
fundaia workflows list tienda
fundaia workflows runs tienda --path .fundaia/workflows/ci.yml
fundaia workflows run tienda .fundaia/workflows/ci.yml --input lane=release
fundaia workflows show <run>
fundaia workflows logs <job>
fundaia workflows cancel <run>
fundaia workflows rerun <run>
En el MCP: list_workflows, list_workflow_runs, read_workflow_run, read_workflow_job_log, run_workflow, rerun_workflow y cancel_workflow_run. Las rutas HTTP están en la API.
Las comprobaciones de un commit aparecen como ✓ 5 / 8 en su fila del historial de despliegues y en su página, contando la última ejecución de cada workflow.
Lo que no hay
pull_request y tags como disparadores, workflow_run con types distintos de completed, uses: de acciones, runs-on (se acepta y se ignora), permissions, environment, secrets de servicios, hashFiles (devuelve '') y los pasos con if que miren steps.<id>.outputs de pasos anteriores (se resuelven al arrancar el job).