Fundaia

Entrar Empezar

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** con branches, branches-ignore, paths y paths-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** con inputs (string, boolean, number, choice): el botón Ejecutar de la pantalla, fundaia workflows run o la herramienta run_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: conclusions dice 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; con cancel-in-progress: true la 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.

ClaveQué hace
needsJobs que deben terminar bien antes. needs.<job>.result y needs.<job>.outputs.<x> en expresiones.
ifExpresión que decide si corre: always(), failure(), fundaia.ref_name == 'main'…
envVariables para todos sus pasos, además de las del workflow.
memory, cpusTecho del contenedor: 1024 MB y 1 CPU por defecto, hasta 8192 y 4.
timeout-minutes60 por defecto, 360 como máximo.
cacheRutas del contenedor que sobreviven entre ejecuciones, como volúmenes con nombre.
servicesContenedores auxiliares (postgres:16, redis:7) alcanzables por su nombre.
artifactsRutas bajo el checkout que se guardan al terminar, como un tar.gz descargable.
outputsValores para los jobs que dependen de este, desde steps.<id>.outputs.
strategy.matrixUna copia del job por combinación, con include, exclude y fail-fast.
continue-on-errorQue falle sin tumbar la ejecución.
shellbash (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 vezDos en toda la máquina; uno arranca solo si la memoria libre alcanza para su memory y la de sus services
Memoria por job1024 MB por defecto, de 128 a 8192
CPU por job1 por defecto, hasta 4
Tiempo por job60 minutos por defecto, hasta 360
Por workflow20 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).