# El reporter de Vitest en tu CI

> Pipelines completos para @probara/vitest-reporter en GitHub Actions, GitLab CI, Jenkins, CircleCI, Azure Pipelines, Bitbucket Pipelines y Buildkite, con secretos, sharding, detección del CI y seguridad frente a forks.

El reporter se ejecuta dentro de `npx vitest run`: pasa el token al paso que ejecuta tus pruebas, y
cada resultado se envía cuando Vitest termina. No hay un paso de envío aparte, y el token nunca se
imprime: un problema nombra la variable, nunca su valor. Todos los ejemplos suponen que el reporter
está registrado en `vitest.config.mts` como en
[Primeros pasos](https://docs.probara.net/es/developers/ci-integrations/vitest/#3-añádelo-a-la-configuración-de-vitest).

## En cualquier CI

- **El token es un secreto; el ID del Proyecto, no.** Guarda el token de aplicación (de la tarjeta
  **Vitest** en **Workspace › Integraciones**) como un secreto llamado `PROBARA_API_TOKEN`. Pon el
  ID del Proyecto en `vitest.config.mts` (`projectId`) o en el pipeline (`PROBARA_PROJECT`).
- **Node.js 22.12 o posterior** en el agente, igual que la ejecución de Vitest de la que forma parte
  el reporter.
- **Las pruebas fallidas hacen fallar el paso; los problemas al reportar, no**: el código de salida
  de Vitest sigue siendo el de las pruebas
  ([lo que el reporter nunca hace](https://docs.probara.net/es/developers/ci-integrations/vitest/troubleshooting/#lo-que-el-reporter-nunca-le-hace-a-la-ejecución)).
- **`npm test` también funciona** cuando tu script `test` ejecuta `vitest run`: el reporter sale de
  la configuración, no del comando. Un `--reporter` en la línea de comandos sustituye los reporters
  de la configuración, incluido este.
- **Fija la versión** en `package.json` (o en el lockfile), para que una versión nueva nunca cambie
  un pipeline por sorpresa
  ([actualizar el reporter](https://docs.probara.net/es/developers/ci-integrations/vitest/migrating/#actualizar-el-reporter)).
- **Los builds de forks no reportan nada**: sin el secreto, el reporter no envía nada y las pruebas
  se ejecutan como siempre. Cada sección de abajo explica cómo mantener el secreto fuera de los
  builds de forks en ese CI, y cómo sus recetas desactivan ahí el envío (`PROBARA_ENABLED=false`).
- **Los jobs con sharding** crean una ejecución por shard salvo que estén vinculados entre sí. En GitHub
  Actions los jobs de un mismo workflow comparten una ejecución automáticamente
  ([jobs en paralelo](#jobs-en-paralelo-una-ejecución-cerrada-por-el-job-final)). Fuera de GitHub
  Actions, fusiona los informes blob de los shards en un job, o crea la ejecución primero, pasa su ULID
  a cada shard y ciérrala al final
  ([sharding](https://docs.probara.net/es/developers/ci-integrations/vitest/sharding/)).

### Lo que rellena la detección del CI

Salvo que una opción o una variable `PROBARA_*` los defina, la ejecución toma su nombre y su origen
del CI:

| CI                  | Se detecta por           | Nombre de la ejecución                            |
| ------------------- | ------------------------ | ------------------------------------------------- |
| GitHub Actions      | `GITHUB_ACTIONS=true`    | `GITHUB_WORKFLOW #GITHUB_RUN_NUMBER`              |
| GitLab CI           | `GITLAB_CI=true`         | `CI_PROJECT_NAME #CI_PIPELINE_IID`                |
| Jenkins             | `JENKINS_URL`            | `JOB_NAME #BUILD_NUMBER`                          |
| CircleCI            | `CIRCLECI=true`          | `CIRCLE_PROJECT_REPONAME #CIRCLE_BUILD_NUM`       |
| Azure Pipelines     | `TF_BUILD=True`          | `BUILD_DEFINITIONNAME #BUILD_BUILDNUMBER`         |
| Bitbucket Pipelines | `BITBUCKET_BUILD_NUMBER` | `BITBUCKET_REPO_SLUG #BITBUCKET_BUILD_NUMBER`     |
| Buildkite           | `BUILDKITE=true`         | `BUILDKITE_PIPELINE_SLUG #BUILDKITE_BUILD_NUMBER` |

La rama, el commit y la URL del build de cada uno se indican en su sección, y en la
[detección del CI de `@probara/core`](https://github.com/cynch-me/probara-sdk/blob/main/packages/core/README.md#ci-detection).
Sustitúyelos con `PROBARA_BRANCH`, `PROBARA_COMMIT` y `PROBARA_BUILD_URL`.

## GitHub Actions

### 1. Añade el secreto

En el repositorio (o la organización): **Settings → Secrets and variables → Actions → New
repository secret**, con el nombre `PROBARA_API_TOKEN` y el token de aplicación como valor.

### 2. Pasa el token al paso de pruebas

```yaml
name: Vitest

on:
  push:
    branches: [main]
  pull_request:

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5
      - uses: actions/setup-node@v5
        with:
          node-version: 22
      - run: npm ci
      - name: Run Vitest tests
        run: npx vitest run
        env:
          PROBARA_API_TOKEN: ${{ secrets.PROBARA_API_TOKEN }}
          PROBARA_PROJECT: SHOP
          # Fork pull requests get no secrets: report nothing there.
          PROBARA_ENABLED: ${{ secrets.PROBARA_API_TOKEN != '' }}
```

### Jobs con sharding: una ejecución a partir de los blobs

Los shards escriben informes blob de Vitest con el reporter junto a `blob`, y un job los fusiona:
todo el pipeline es una sola ejecución, con cada intento y cada archivo
([patrón 1](https://docs.probara.net/es/developers/ci-integrations/vitest/sharding/#patrón-1-fusionar-los-informes-blob)).
Solo el job que fusiona necesita el token:

```yaml
jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        shardIndex: [1, 2, 3, 4]
        shardTotal: [4]
    steps:
      - uses: actions/checkout@v5
      - uses: actions/setup-node@v5
        with:
          node-version: 22
      - run: npm ci
      - run: npx vitest run --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} --reporter=blob --reporter=@probara/vitest-reporter
      - uses: actions/upload-artifact@v4
        if: ${{ !cancelled() }}
        with:
          name: vitest-blob-${{ matrix.shardIndex }}
          path: .vitest
          include-hidden-files: true
          retention-days: 1

  report:
    needs: test
    if: ${{ !cancelled() }}
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5
      - uses: actions/setup-node@v5
        with:
          node-version: 22
      - run: npm ci
      - uses: actions/download-artifact@v4
        with:
          pattern: vitest-blob-*
          path: .vitest
          merge-multiple: true
      - run: npx vitest --merge-reports
        env:
          PROBARA_API_TOKEN: ${{ secrets.PROBARA_API_TOKEN }}
          PROBARA_PROJECT: SHOP
          PROBARA_ENABLED: ${{ secrets.PROBARA_API_TOKEN != '' }}
```

- `.vitest` contiene los blobs y el `attachmentsDir` de Vitest 5; es una carpeta oculta, de ahí
  `include-hidden-files: true`. En Vitest 3 y 4, sube `.vitest-reports` y `.vitest-attachments`
  (uno por línea de `path:`), y descárgalos en `.`.
- Los runners alojados en GitHub hacen el checkout de cada job en la misma ruta, así que la fusión
  conserva todas las claves
  ([fusiona en la misma ruta](https://docs.probara.net/es/developers/ci-integrations/vitest/sharding/#fusiona-en-la-misma-ruta)).
- El job `report` se ejecuta también después de shards fallidos, y su código de salida es el de
  las pruebas fusionadas.

### Jobs en paralelo: una ejecución, cerrada por el job final

En GitHub Actions los jobs de un mismo workflow envían a una misma ejecución: el origen de la
ejecución identifica la ejecución del workflow por su repositorio, su id y su intento, y el primer job
que envía crea la ejecución. Por defecto, cada job cierra su ejecución al terminar, así que los jobs
en paralelo definen `PROBARA_CLOSE_RUN: false`. Un job final cierra la ejecución con
`probara run close`, que no necesita aquí el ULID: `needs` hace que espere a todos los jobs de
pruebas, y `if: always()` lo ejecuta aunque fallen. Cada shard envía al terminar.

```yaml
jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        shard: [1, 2]
    steps:
      - uses: actions/checkout@v5
      - uses: actions/setup-node@v5
        with:
          node-version: 22
      - run: npm ci
      - run: npx vitest run --shard=${{ matrix.shard }}/2
        env:
          PROBARA_API_TOKEN: ${{ secrets.PROBARA_API_TOKEN }}
          PROBARA_PROJECT: SHOP
          # A shard leaves the run open for the others.
          PROBARA_CLOSE_RUN: false

  probara:
    needs: test
    if: always()
    runs-on: ubuntu-latest
    steps:
      - uses: actions/setup-node@v5
        with:
          node-version: 22
      - run: npx --yes @probara/cli@0.6 run close
        env:
          PROBARA_API_TOKEN: ${{ secrets.PROBARA_API_TOKEN }}
          PROBARA_PROJECT: SHOP
```

- **Un job tras el cierre** recibe `409 ci_run_closed`: el reporter registra el motivo, no envía nada
  más, y las pruebas conservan su código de salida
  ([solución de problemas](https://docs.probara.net/es/developers/ci-integrations/vitest/troubleshooting/#un-job-envía-después-de-cerrarse-su-ejecución)).
  Mantén cada job que envía en `needs`.
- **Una nueva ejecución del workflow** (re-run) es otro intento: envía a una ejecución propia.
- **Un solo job** no necesita nada de esto: su ejecución se cierra al terminar, como en cualquier otro caso.

### Detección del CI

| Campo            | De dónde sale                                                                                |
| ---------------- | -------------------------------------------------------------------------------------------- |
| Nombre           | `GITHUB_WORKFLOW #GITHUB_RUN_NUMBER`, por ejemplo `Vitest #42`                               |
| Rama             | `GITHUB_HEAD_REF` (la rama de la pull request), si no `GITHUB_REF_NAME`; ninguna para un tag |
| Commit           | `GITHUB_SHA`                                                                                 |
| URL del build    | `GITHUB_SERVER_URL/GITHUB_REPOSITORY/actions/runs/GITHUB_RUN_ID`                             |
| Repositorio      | `GITHUB_REPOSITORY`                                                                          |
| Autor del commit | `GITHUB_ACTOR`                                                                               |
| Pull request     | `<n>` de `GITHUB_REF` `refs/pull/<n>/merge`                                                  |
| Workflow         | `GITHUB_WORKFLOW`                                                                            |
| Ejecución de CI  | `GITHUB_RUN_ID`, intento `GITHUB_RUN_ATTEMPT`                                                |
| Job de CI        | `GITHUB_JOB`                                                                                 |

En los eventos `pull_request`, `GITHUB_SHA` es el commit de merge que creó GitHub, no el último
commit de la rama. Para registrar este último, añade
`PROBARA_COMMIT: ${{ github.event.pull_request.head.sha }}`. En GitHub Actions el commit debe ser un
SHA hexadecimal de 7 a 40 caracteres: otro valor se descarta con una advertencia.

### Pull requests desde forks

Los workflows que lanza una pull request desde un fork reciben los secretos vacíos. Sin el token, el
reporter lo indica, no envía nada, y las pruebas se ejecutan como siempre:

```text
$ PROBARA_PROJECT=SHOP npx vitest run
[probara] Probara reporting is off: The API token is not set: pass apiToken or set PROBARA_API_TOKEN
```

Los workflows de arriba definen `PROBARA_ENABLED` según si el secreto está presente, así que los
builds de forks no reportan ni registran nada. No uses `pull_request_target` para pasar el secreto
a un build de un fork: ejecuta el código del fork con tus secretos.

## GitLab CI

### 1. Añade la variable

**Settings → CI/CD → Variables → Add variable**: clave `PROBARA_API_TOKEN`, el token como valor, con
**Mask variable** activado. **Protect variable** la limita a ramas y tags protegidos: actívalo solo
si reportas únicamente desde ellos.

### 2. El job de pruebas

```yaml
stages:
  - test

vitest:
  stage: test
  image: node:22
  variables:
    PROBARA_PROJECT: SHOP
  script:
    - npm ci
    - npx vitest run
```

- Un job sin la variable (el pipeline de un fork, una rama no protegida con una variable protegida)
  ejecuta sus pruebas y no envía nada; el reporter registra que el token no está definido.
- Para conservar lo que no se pudo enviar, define `PROBARA_RESULTS_FILE` y guarda el archivo como
  artefacto
  ([archivo de resultados](https://docs.probara.net/es/developers/ci-integrations/vitest/results-and-retries/#en-ci)).

### Shards con `parallel:`: una ejecución a partir de los blobs

Los shards de `parallel:` escriben informes blob de Vitest con el reporter junto a `blob`, y un job
los fusiona en una sola ejecución
([patrón 1](https://docs.probara.net/es/developers/ci-integrations/vitest/sharding/#patrón-1-fusionar-los-informes-blob)).
Solo el job que fusiona necesita el token:

```yaml
stages:
  - test
  - report

vitest:
  stage: test
  image: node:22
  parallel: 4
  script:
    - npm ci
    - npx vitest run --shard=$CI_NODE_INDEX/$CI_NODE_TOTAL --reporter=blob --reporter=@probara/vitest-reporter
  artifacts:
    when: always
    paths:
      - .vitest/

report-to-probara:
  stage: report
  image: node:22
  needs:
    - job: vitest
      artifacts: true
  variables:
    PROBARA_PROJECT: SHOP
  rules:
    # After failed shards too, and only where the token is set.
    - if: $PROBARA_API_TOKEN
      when: always
  script:
    - npm ci
    - npx vitest --merge-reports
```

- Un `needs:` sobre un job `parallel:` descarga los artefactos de todas las instancias en la misma
  carpeta; el blob de cada shard tiene su propio nombre de archivo (`blob-1-4.json`...), así que
  quedan uno junto a otro.
- `.vitest/` contiene los blobs y el `attachmentsDir` de Vitest 5. En Vitest 3 y 4, guarda en su
  lugar `.vitest-reports/` y `.vitest-attachments/`.
- El job que fusiona debe hacer el checkout en el `CI_PROJECT_DIR` de los shards, o todas las claves
  cambian
  ([fusiona en la misma ruta](https://docs.probara.net/es/developers/ci-integrations/vitest/sharding/#fusiona-en-la-misma-ruta)).

Para que cada shard reporte en cuanto termina, crea la ejecución en un job `.pre`: consulta el
[patrón 2](https://docs.probara.net/es/developers/ci-integrations/vitest/sharding/#gitlab-ci).

### Detección del CI

| Campo         | De dónde sale                                                                                                                      |
| ------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| Nombre        | `CI_PROJECT_NAME #CI_PIPELINE_IID`, por ejemplo `shop #42`                                                                         |
| Rama          | `CI_MERGE_REQUEST_SOURCE_BRANCH_NAME` en un pipeline de merge request, si no `CI_COMMIT_REF_NAME`; ninguna para un pipeline de tag |
| Commit        | `CI_COMMIT_SHA`                                                                                                                    |
| URL del build | `CI_PIPELINE_URL`                                                                                                                  |

### Merge requests desde forks

Una merge request desde un fork ejecuta su pipeline en el proyecto del fork, que no tiene tus
variables de CI/CD: el reporter no envía nada ahí, y la regla `if: $PROBARA_API_TOKEN` deja fuera el
job que fusiona. Si los mantenedores ejecutan pipelines de forks en el proyecto principal, esos
pipelines sí reciben la variable, así que revisa el cambio antes de ejecutarlo.

## Jenkins

### 1. Añade la credencial

**Manage Jenkins → Credentials → (un dominio) → Add Credentials**: tipo **Secret text**, ID
`probara-api-token`, el token como secreto.

### 2. El pipeline

Un `Jenkinsfile` declarativo con Node.js 22 en el agente vincula el token a la etapa de pruebas con
`withCredentials`:

```groovy
pipeline {
  agent any
  environment {
    PROBARA_PROJECT = 'SHOP'
  }
  stages {
    stage('Test') {
      steps {
        sh 'npm ci'
        withCredentials([string(credentialsId: 'probara-api-token', variable: 'PROBARA_API_TOKEN')]) {
          sh 'npx vitest run'
        }
      }
    }
  }
}
```

Jenkins oculta el secreto vinculado en el registro del build, y el reporter nunca lo imprime.

### Etapas en paralelo: una ejecución

Las etapas en paralelo que ejecutan `--shard`s crean una ejecución cada una salvo que compartan una:
créala en una primera etapa, pásala a cada una y ciérrala en un bloque `post { always { ... } }`
([patrón 2](https://docs.probara.net/es/developers/ci-integrations/vitest/sharding/#patrón-2-crea-la-ejecución-reporta-desde-cada-shard-y-ciérrala)):

```groovy
pipeline {
  agent any
  environment {
    PROBARA_PROJECT = 'SHOP'
  }
  stages {
    stage('Create the Probara run') {
      steps {
        sh 'npm ci'
        withCredentials([string(credentialsId: 'probara-api-token', variable: 'PROBARA_API_TOKEN')]) {
          sh 'npx @probara/cli run create > probara-run-ulid'
        }
        script {
          env.PROBARA_RUN_ULID = readFile('probara-run-ulid').trim()
        }
      }
    }
    stage('Test') {
      parallel {
        stage('Shard 1') {
          steps {
            withCredentials([string(credentialsId: 'probara-api-token', variable: 'PROBARA_API_TOKEN')]) {
              sh 'npx vitest run --shard=1/2'
            }
          }
        }
        stage('Shard 2') {
          steps {
            withCredentials([string(credentialsId: 'probara-api-token', variable: 'PROBARA_API_TOKEN')]) {
              sh 'npx vitest run --shard=2/2'
            }
          }
        }
      }
    }
  }
  post {
    always {
      withCredentials([string(credentialsId: 'probara-api-token', variable: 'PROBARA_API_TOKEN')]) {
        sh 'npx @probara/cli run close'
      }
    }
  }
}
```

Aquí los dos shards comparten un workspace, instalado una sola vez antes de que empiecen (dos
`npm ci` a la vez en una misma carpeta se estorbarían entre sí); en agentes distintos, cada uno hace
su propio checkout e instalación. Cada shard envía sus resultados al terminar, a la ejecución de la
primera etapa.

### Pull requests desde forks

Jenkins no oculta por sí solo las credenciales a los builds de pull requests: un job multibranch que
construye forks puede pasar el token a su código. Configura **Discover pull requests from forks →
Trust** para que una pull request de cualquier otra persona se construya con el `Jenkinsfile` de su
rama de destino: en el branch source de GitHub, **From users with Admin or Write permission** (o
**Nobody**); en el de Bitbucket, **Nobody**, o **Forks in the same account** a partir de la versión
871 del plugin (la corrección de CVE-2024-28152). **Everyone** construye un fork con su propio
`Jenkinsfile`, que puede vincular la credencial por sí mismo, y deja sin efecto la protección
`env.CHANGE_FORK` de abajo.

En el `Jenkinsfile` de la rama de destino, vincula la credencial solo cuando `env.CHANGE_FORK` no
está definido (Jenkins lo define para una pull request desde un fork), y desactiva el envío en el
build de un fork:

```groovy
pipeline {
  agent any
  environment {
    PROBARA_PROJECT = 'SHOP'
  }
  stages {
    stage('Test') {
      steps {
        sh 'npm ci'
        script {
          if (env.CHANGE_FORK) {
            withEnv(['PROBARA_ENABLED=false']) {
              sh 'npx vitest run'
            }
          } else {
            withCredentials([string(credentialsId: 'probara-api-token', variable: 'PROBARA_API_TOKEN')]) {
              sh 'npx vitest run'
            }
          }
        }
      }
    }
  }
}
```

En el pipeline en paralelo, envuelve cada `withCredentials` del mismo modo: con el envío
desactivado, `run create` no escribe ningún ULID y `run close` sale con 0 sin token.

### Detección del CI

Con `JENKINS_URL` definido, la ejecución se llama `JOB_NAME #BUILD_NUMBER`, y su rama, su commit y
la URL del build salen de `BRANCH_NAME` (o `GIT_BRANCH`), `GIT_COMMIT` y `BUILD_URL`.

## CircleCI

### 1. Añade el token

**Organization Settings → Contexts → Create Context** (por ejemplo `probara-reporting`), y después
añade la variable de entorno `PROBARA_API_TOKEN`. También sirve una variable de entorno del proyecto
(**Project Settings → Environment Variables**); en ese caso, quita `context:` de abajo.

### 2. El job

Un `.circleci/config.yml` con una imagen de Node.js, dividido en shards con `parallelism` dentro de
una ejecución creada al principio
([patrón 2](https://docs.probara.net/es/developers/ci-integrations/vitest/sharding/#patrón-2-crea-la-ejecución-reporta-desde-cada-shard-y-ciérrala)).
Sin `parallelism`, quédate solo con el job `test` y ejecuta `npx vitest run`:

```yaml
version: 2.1

jobs:
  create-run:
    docker:
      - image: cimg/node:22.12
    environment:
      PROBARA_PROJECT: SHOP
    steps:
      - run: |
          # Forked pull requests get no secrets: report nothing there.
          if [ -z "$PROBARA_API_TOKEN" ]; then export PROBARA_ENABLED=false; fi
          PROBARA_RUN_ULID=$(npx @probara/cli run create)
          echo "PROBARA_RUN_ULID=$PROBARA_RUN_ULID" > probara.env
      - persist_to_workspace:
          root: .
          paths: [probara.env]
  test:
    docker:
      - image: cimg/node:22.12
    parallelism: 4
    environment:
      PROBARA_PROJECT: SHOP
    steps:
      - checkout
      - attach_workspace:
          at: .
      - run: npm ci
      - run: |
          if [ -z "$PROBARA_API_TOKEN" ]; then export PROBARA_ENABLED=false; fi
          export $(cat probara.env)
          npx vitest run --shard=$((CIRCLE_NODE_INDEX + 1))/$CIRCLE_NODE_TOTAL
  close-run:
    docker:
      - image: cimg/node:22.12
    environment:
      PROBARA_PROJECT: SHOP
    steps:
      - attach_workspace:
          at: .
      - run:
          when: always
          command: |
            if [ -z "$PROBARA_API_TOKEN" ]; then export PROBARA_ENABLED=false; fi
            export $(cat probara.env)
            npx @probara/cli run close

workflows:
  vitest:
    jobs:
      - create-run:
          context: probara-reporting
      - test:
          context: probara-reporting
          requires: [create-run]
      - close-run:
          context: probara-reporting
          requires:
            - test: [success, failed]
```

`CIRCLE_NODE_INDEX` cuenta desde 0, y los shards de Vitest desde 1. Cada shard envía sus resultados
al terminar, a la ejecución de `run create`; `close-run` la cierra también después de shards
fallidos.

### Forks y detección del CI

CircleCI no pasa los contextos ni las variables del proyecto a los builds de pull requests desde
forks (salvo que **Pass secrets to builds from forked pull requests** esté activado: déjalo
desactivado). La primera línea de cada paso desactiva ahí el envío: `run create` no imprime ningún
ULID y sale con 0, los shards no reportan nada, y `run close` sale con 0. Sin esa línea,
`run create` sale con 2 porque falta el token.

Con `CIRCLECI=true`, la ejecución se llama `CIRCLE_PROJECT_REPONAME #CIRCLE_BUILD_NUM`, con la rama
`CIRCLE_BRANCH`, el commit `CIRCLE_SHA1` y la URL del build `CIRCLE_BUILD_URL`.

## Azure Pipelines

### 1. Añade la variable

**Pipelines → tu pipeline → Edit → Variables → New variable**: nombre `PROBARA_API_TOKEN`, el token
como valor, con **Keep this value secret** activado. También sirve un grupo de variables de la
biblioteca. Las variables secretas nunca llegan solas a los scripts: asigna la variable en el `env`
del paso, como abajo.

### 2. El job

```yaml
pool:
  vmImage: ubuntu-latest

steps:
  - task: NodeTool@0
    inputs:
      versionSpec: '22.x'
  - script: npm ci
  - script: npx vitest run
    env:
      PROBARA_API_TOKEN: $(PROBARA_API_TOKEN)
      PROBARA_PROJECT: SHOP
```

### Jobs con sharding: una ejecución

Dividido en shards con `parallel` dentro de una ejecución que crea un primer job y cierra uno
último que se ejecuta incluso después de shards fallidos
([patrón 2](https://docs.probara.net/es/developers/ci-integrations/vitest/sharding/#patrón-2-crea-la-ejecución-reporta-desde-cada-shard-y-ciérrala)):

```yaml
jobs:
  - job: create_run
    pool:
      vmImage: ubuntu-latest
    steps:
      - task: NodeTool@0
        inputs:
          versionSpec: '22.x'
      - script: |
          set -e
          ulid=$(npx @probara/cli run create)
          echo "##vso[task.setvariable variable=ulid;isOutput=true]$ulid"
        name: probara
        env:
          PROBARA_API_TOKEN: $(PROBARA_API_TOKEN)
          PROBARA_PROJECT: SHOP

  - job: test
    dependsOn: create_run
    strategy:
      parallel: 4
    variables:
      PROBARA_RUN_ULID: $[ dependencies.create_run.outputs['probara.ulid'] ]
    pool:
      vmImage: ubuntu-latest
    steps:
      - task: NodeTool@0
        inputs:
          versionSpec: '22.x'
      - script: npm ci
      - script: npx vitest run --shard=$(System.JobPositionInPhase)/$(System.TotalJobsInPhase)
        env:
          PROBARA_API_TOKEN: $(PROBARA_API_TOKEN)
          PROBARA_PROJECT: SHOP
          PROBARA_RUN_ULID: $(PROBARA_RUN_ULID)

  - job: close_run
    dependsOn: [create_run, test]
    condition: always()
    variables:
      PROBARA_RUN_ULID: $[ dependencies.create_run.outputs['probara.ulid'] ]
    pool:
      vmImage: ubuntu-latest
    steps:
      - task: NodeTool@0
        inputs:
          versionSpec: '22.x'
      - script: npx @probara/cli run close
        env:
          PROBARA_API_TOKEN: $(PROBARA_API_TOKEN)
          PROBARA_PROJECT: SHOP
          PROBARA_RUN_ULID: $(PROBARA_RUN_ULID)
```

`System.JobPositionInPhase` cuenta desde 1, igual que los shards de Vitest. Cada shard envía sus
resultados al terminar, a la ejecución de `create_run`.

### Forks y detección del CI

Los builds de pull requests desde forks no reciben variables secretas salvo que **Make secrets
available to builds of forks** esté activado (déjalo desactivado). Ahí `$(PROBARA_API_TOKEN)` no se
sustituye y llega al reporter como texto: Probara lo rechaza (`401`), el reporter registra que los
resultados no se enviaron, y las pruebas se ejecutan como siempre. En el pipeline con sharding, el
`run create` de un fork sale con 1 del mismo modo, y sus shards nunca empiezan. Para no reportar
nada en los builds de forks, empieza cada script que ejecuta `vitest` o `probara` con
`if [ "$SYSTEM_PULLREQUEST_ISFORK" = True ]; then export PROBARA_ENABLED=false; fi`: `run create`
no imprime entonces ningún ULID y sale con 0, los shards no reportan nada, y `run close` sale con 0.

Con `TF_BUILD=True`, la ejecución se llama `BUILD_DEFINITIONNAME #BUILD_BUILDNUMBER`, y su rama, su
commit y la URL del build salen de las variables del pipeline.

## Bitbucket Pipelines

### 1. Añade la variable

**Repository settings → Pipelines → Repository variables**: nombre `PROBARA_API_TOKEN`, el token
como valor, con **Secured** activado. Bitbucket pasa las variables del repositorio a cada paso.

### 2. El pipeline

```yaml
image: node:22

pipelines:
  default:
    - step:
        name: Vitest
        script:
          - npm ci
          - export PROBARA_PROJECT=SHOP
          - npx vitest run
```

### Pasos en paralelo: una ejecución

Los pasos en paralelo que ejecutan cada uno un `--shard` reportan en una ejecución que crea un primer
paso y cierra uno último
([patrón 2](https://docs.probara.net/es/developers/ci-integrations/vitest/sharding/#patrón-2-crea-la-ejecución-reporta-desde-cada-shard-y-ciérrala)):

```yaml
image: node:22

pipelines:
  default:
    - step:
        name: Create the Probara run
        script:
          # Builds without the secured variable (such as forks) report nothing.
          - if [ -z "$PROBARA_API_TOKEN" ]; then export PROBARA_ENABLED=false; fi
          - export PROBARA_PROJECT=SHOP
          - PROBARA_RUN_ULID=$(npx @probara/cli run create)
          - echo "PROBARA_RUN_ULID=$PROBARA_RUN_ULID" > probara.env
        artifacts:
          - probara.env
    - parallel:
        - step:
            name: Shard 1
            script:
              - if [ -z "$PROBARA_API_TOKEN" ]; then export PROBARA_ENABLED=false; fi
              - npm ci
              - export PROBARA_PROJECT=SHOP $(cat probara.env)
              - npx vitest run --shard=1/2
        - step:
            name: Shard 2
            script:
              - if [ -z "$PROBARA_API_TOKEN" ]; then export PROBARA_ENABLED=false; fi
              - npm ci
              - export PROBARA_PROJECT=SHOP $(cat probara.env)
              - npx vitest run --shard=2/2
    - step:
        name: Close the Probara run
        script:
          - if [ -z "$PROBARA_API_TOKEN" ]; then export PROBARA_ENABLED=false; fi
          - export PROBARA_PROJECT=SHOP $(cat probara.env)
          - npx @probara/cli run close
```

Cada shard envía sus resultados al terminar, incluidas las pruebas fallidas. Bitbucket omite los
pasos posteriores a uno fallido, así que un shard con pruebas fallidas deja la ejecución abierta:
ciérrala en Probara, o a mano
([si el cierre nunca se ejecutó](https://docs.probara.net/es/developers/ci-integrations/vitest/sharding/#si-el-cierre-nunca-se-ejecutó)).
Por el mismo motivo, este pipeline no fusiona informes blob: el paso que fusiona no se ejecutaría
después de un shard con pruebas fallidas, y no se reportaría ninguno de los shards.

### Forks y detección del CI

Los pipelines de un fork se ejecutan en el repositorio del fork, con las variables del fork, así que
no tienen token. La línea `if` de cada paso desactiva ahí el envío: `run create` no imprime ningún
ULID y sale con 0, los shards no reportan nada, y `run close` sale con 0. Sin ella, `run create`
sale con 2 porque falta el token. En el pipeline de un solo paso, el reporter solo registra que el
token no está definido.

Con `BITBUCKET_BUILD_NUMBER` definido, la ejecución se llama
`BITBUCKET_REPO_SLUG #BITBUCKET_BUILD_NUMBER`, con la rama `BITBUCKET_BRANCH`, el commit
`BITBUCKET_COMMIT` y un enlace al pipeline.

## Buildkite

### 1. Añade el token

Expón `PROBARA_API_TOKEN` a los pasos desde un almacén de secretos: un hook `environment` del agente
que lo lee de tu gestor de secretos, o
[Buildkite secrets](https://buildkite.com/docs/pipelines/security/secrets/buildkite-secrets).

### 2. El pipeline

Un `pipeline.yml` cuyos jobs en paralelo reportan en una ejecución que crea un primer paso y que se
cierra después de ellos, incluso si fallaron
([patrón 2](https://docs.probara.net/es/developers/ci-integrations/vitest/sharding/#patrón-2-crea-la-ejecución-reporta-desde-cada-shard-y-ciérrala)).
Sin `parallelism`, basta con un paso que ejecute `npx vitest run`:

```yaml
env:
  PROBARA_PROJECT: SHOP

steps:
  - label: Create the Probara run
    key: create-run
    command: |
      PROBARA_RUN_ULID="$(npx @probara/cli run create)"
      if [ -n "$$PROBARA_RUN_ULID" ]; then buildkite-agent meta-data set probara-run "$$PROBARA_RUN_ULID"; fi

  - label: Vitest
    depends_on: create-run
    parallelism: 4
    command: |
      npm ci
      export PROBARA_RUN_ULID="$(buildkite-agent meta-data get probara-run)"
      npx vitest run --shard=$((BUILDKITE_PARALLEL_JOB + 1))/$$BUILDKITE_PARALLEL_JOB_COUNT

  - wait: ~
    continue_on_failure: true

  - label: Close the Probara run
    command: PROBARA_RUN_ULID="$(buildkite-agent meta-data get probara-run)" npx @probara/cli run close
```

`buildkite-agent pipeline upload` sustituye `$NAME` y `${NAME}` al subir el pipeline, y una
variable que no conoce en ese momento, como `BUILDKITE_PARALLEL_JOB_COUNT`, que solo reciben los
jobs en paralelo, queda vacía; un `$$` se deja como un solo `$` para la shell del job, mientras que
`$(...)` y `$((...))` se dejan tal cual. La ejecución se crea en una línea propia, así que un
`run create` fallido hace fallar el primer paso en lugar de guardar un ULID vacío. Con el envío
desactivado (un build de un fork, abajo), `run create` no imprime ningún ULID y sale con 0: no hay
nada que guardar, y los demás pasos se ejecutan también con el envío desactivado. Cada shard envía
sus resultados al terminar. `BUILDKITE_PARALLEL_JOB` cuenta desde 0, y los shards de Vitest desde 1.

Con `BUILDKITE=true`, la ejecución se llama `BUILDKITE_PIPELINE_SLUG #BUILDKITE_BUILD_NUMBER`, con
la rama `BUILDKITE_BRANCH`, el commit `BUILDKITE_COMMIT` y la URL del build `BUILDKITE_BUILD_URL`.

### Pull requests desde forks

Buildkite construye pull requests desde forks solo si el pipeline lo permite (desactivado por
defecto). Esos builds se ejecutan en tus agentes, y `pipeline upload` lee el `pipeline.yml` del
propio fork, que también elige la cola de los agentes. El código del fork se ejecuta en la shell
del job como el usuario del agente, así que en un agente que puede leer el token de tu almacén de
secretos, el código del fork también puede leerlo: ningún hook ni paso lo impide. Mantén
desactivados los builds de forks, o mantenlos fuera de esos agentes: su hook `environment` rechaza
el build de un fork (`BUILDKITE_PULL_REQUEST_REPO` lo nombra), y los forks solo se construyen en
una cola o un clúster aparte cuyos agentes no pueden leer el almacén de secretos:

```bash
# The environment hook of the agents that can read the token: no build of a fork runs there.
if [ -n "$BUILDKITE_PULL_REQUEST_REPO" ]; then
  case "${BUILDKITE_PULL_REQUEST_REPO#*://}" in
    github.com/acme/shop.git) ;;
    *)
      echo "Refusing a build of a fork: $BUILDKITE_PULL_REQUEST_REPO" >&2
      exit 1
      ;;
  esac
fi
export PROBARA_API_TOKEN="$(your-secrets-manager get probara-api-token)"
```

`BUILDKITE_PULL_REQUEST_REPO` está vacío fuera de las pull requests, y es una URL `https://` desde
2022 (antes, una `git://`), así que el hook lo compara sin el esquema: pon tu repositorio en lugar
de `github.com/acme/shop.git`. Si no coincide, tus propias pull requests también fallan en estos
agentes, lo que se ve en la primera. En los agentes que construyen forks, define
`PROBARA_ENABLED=false` en su hook `environment`: las pruebas del fork se ejecutan y
`npx vitest run` no reporta nada, y `run create` y `run close` salen con 0 sin token.

Relacionado: [Sharding y proyectos](https://docs.probara.net/es/developers/ci-integrations/vitest/sharding/) ·
[Archivo de resultados en CI](https://docs.probara.net/es/developers/ci-integrations/vitest/results-and-retries/#en-ci) ·
[Solución de problemas](https://docs.probara.net/es/developers/ci-integrations/vitest/troubleshooting/) · Fuente:
[docs/ci](https://github.com/cynch-me/probara-sdk/tree/main/packages/vitest-reporter/docs/ci)
