CI/CD y Despliegue
Tabla de Contenidos
- Visión general
- CI/CD del Monorepo (platform-core)
- CI/CD de MFEs (polyrepos)
- Entornos de Preview
- Gestión de Dependencias Cross-Repo
- Despliegue de Workers
- Seguridad del Pipeline
- Monitorización y Notificaciones
- Referencias
Visión general
La plataforma opera dos arquitecturas de pipeline CI/CD distintas que reflejan su estructura híbrida monorepo/polyrepo:
- Pipeline del monorepo (
platform-core) -- Gestiona paquetes compartidos (design system, SDK, utilidades, Workers) con versionado coordinado mediante Changesets y publicación en GitHub Packages. - Pipelines de MFE en polyrepos -- Cada repositorio de micro frontend tiene su propio pipeline independiente que compila, testea, despliega y registra el MFE en el Version Config Service.
Principios Clave
| Principio | Implementación |
|---|---|
| Feedback rápido | Turborepo remote caching, pasos de CI paralelizados, builds incrementales |
| Desplegabilidad independiente | Cada MFE se despliega a su propio ritmo sin coordinarse con otros equipos |
| Gestión automatizada de versiones | Changesets para paquetes del monorepo; git tags semánticos para MFEs |
| Entornos de preview | Cada PR obtiene una URL de preview única para el MFE modificado |
| Seguridad | Autenticación OIDC con Cloudflare (planificado), tokens de API de corta duración en CI |
Stack Tecnológico
- Plataforma CI/CD: GitHub Actions
- Orquestación del monorepo: Turborepo (con remote caching)
- Gestión de paquetes: pnpm (con workspace protocol en el monorepo)
- Gestión de versiones: Changesets (monorepo), git tags semánticos (polyrepos)
- Registro de paquetes: GitHub Packages (npm)
- Destino de despliegue: Cloudflare Workers, R2, KV
- Automatización de dependencias: Renovate
CI/CD del Monorepo (platform-core)
El monorepo platform-core contiene toda la infraestructura compartida: el design system, el MFE SDK, configuraciones TypeScript compartidas, Workers (Shell App, Version Config Service, Auth Gateway) y utilidades compartidas.
Pipeline de CI (en cada PR)
El pipeline de CI se ejecuta en cada pull request y push a main. Aprovecha Turborepo para ejecutar tareas solo en los paquetes que han cambiado, reduciendo drásticamente los tiempos de CI en cambios específicos.
.github/workflows/ci.yml
name: CI
on:
pull_request:
branches: [main]
push:
branches: [main]
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true
env:
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ vars.TURBO_TEAM }}
jobs:
ci:
name: Lint, Typecheck, Test, Build
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
# Full history is needed for Changesets to correctly determine
# which packages changed. fetch-depth: 2 is insufficient.
fetch-depth: 0
- name: Setup pnpm
uses: pnpm/action-setup@v4
- name: Setup Node.js
uses: actions/setup-node@v6
with:
# Node 20 reaches EOL in April 2026. Node 24 is now Active LTS.
node-version: 22
cache: pnpm
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Run lint
run: pnpm turbo run lint
- name: Run typecheck
run: pnpm turbo run typecheck
- name: Run tests
run: pnpm turbo run test -- --coverage
- name: Run build
run: pnpm turbo run build
- name: Upload coverage reports
if: github.event_name == 'pull_request'
uses: actions/upload-artifact@v6
with:
name: coverage-reports
path: packages/*/coverage/
retention-days: 7
Cómo funciona el caching de Turborepo en CI:
- Cada tarea (
lint,typecheck,test,build) declara sus inputs enturbo.json. - En la primera ejecución, los outputs se hashean y se suben al remote cache de Turborepo.
- Las ejecuciones posteriores con los mismos inputs producen cache hits, saltando la ejecución por completo.
- Un PR que solo modifica el design system saltará lint/test/build para los Workers y el SDK.
Pipeline de Release (Changesets)
El pipeline de release automatiza el bump de versiones, la generación de changelogs y la publicación usando Changesets. Los desarrolladores añaden archivos de changeset durante el desarrollo (pnpm changeset), y el pipeline de release los consume.
.github/workflows/release.yml
name: Release
on:
push:
branches: [main]
concurrency:
group: release-${{ github.ref }}
cancel-in-progress: false
env:
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ vars.TURBO_TEAM }}
permissions:
contents: write
packages: write
pull-requests: write
jobs:
release:
name: Release Packages
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Setup pnpm
uses: pnpm/action-setup@v4
- name: Setup Node.js
uses: actions/setup-node@v6
with:
# Node 20 reaches EOL in April 2026. Node 24 is now Active LTS.
node-version: 22
cache: pnpm
registry-url: https://npm.pkg.github.com
scope: '@org'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build all packages
run: pnpm turbo run build
- name: Create Release PR or Publish
id: changesets
uses: changesets/action@v1
with:
version: pnpm changeset version
publish: pnpm changeset publish
title: 'chore: version packages'
commit: 'chore: version packages'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Notify polyrepos of new releases
if: steps.changesets.outputs.published == 'true'
uses: actions/github-script@v8
with:
github-token: ${{ secrets.DISPATCH_TOKEN }}
script: |
const publishedPackages = JSON.parse('${{ steps.changesets.outputs.publishedPackages }}');
const repos = [
'myorg/mfe-dashboard',
'myorg/mfe-settings',
'myorg/mfe-analytics',
'myorg/mfe-billing',
];
const results = [];
for (const repo of repos) {
const [owner, repoName] = repo.split('/');
try {
await github.rest.repos.createDispatchEvent({
owner,
repo: repoName,
event_type: 'platform-core-release',
client_payload: {
packages: publishedPackages.map(p => ({
name: p.name,
version: p.version,
})),
},
});
results.push({ repo, status: 'success' });
} catch (error) {
// Log the error but do not fail the workflow -- dispatch
// failures should not block the release pipeline.
console.error(`Failed to dispatch to ${repo}: ${error.message}`);
results.push({ repo, status: 'failed', error: error.message });
}
}
const failed = results.filter(r => r.status === 'failed');
console.log(`Dispatched release notification to ${repos.length} repos (${failed.length} failed)`);
if (failed.length > 0) {
core.warning(`Repository dispatch failed for: ${failed.map(r => r.repo).join(', ')}. These repos will pick up updates on the next Renovate schedule.`);
}
Flujo de release:
- El desarrollador crea un PR con cambios de código y un archivo de changeset (p. ej.,
.changeset/cool-feature.md). - Se revisa el PR y se mergea a
main. - La Changesets Action detecta changesets pendientes y abre (o actualiza) un PR de "Version Packages".
- El PR de "Version Packages" contiene las versiones de
package.jsonincrementadas y los archivosCHANGELOG.mdactualizados. - Cuando el equipo mergea el PR de "Version Packages", la action publica todos los paquetes con versión actualizada en GitHub Packages.
- Tras la publicación, un evento
repository-dispatchnotifica a todos los polyrepos de MFE para que Renovate pueda crear PRs de actualización de forma inmediata.
Despliegue del Design System como MF Remote
El paquete del design system (@org/design-system) cumple un doble rol: se publica en GitHub Packages como paquete npm y se despliega como un remote de Module Federation para que los MFEs puedan consumir componentes compartidos en runtime.
Tras una publicación npm exitosa, el workflow de release también despliega el bundle del design system en R2 y lo registra en el Version Config Service.
Paso adicional de deploy añadido a .github/workflows/release.yml:
# --- Design System MF Remote Deployment ---
- name: Check if design system was published
id: check-ds
if: steps.changesets.outputs.published == 'true'
run: |
PUBLISHED='${{ steps.changesets.outputs.publishedPackages }}'
DS_VERSION=$(echo "$PUBLISHED" | jq -r '.[] | select(.name == "@org/design-system") | .version')
if [ -n "$DS_VERSION" ]; then
echo "version=$DS_VERSION" >> "$GITHUB_OUTPUT"
echo "published=true" >> "$GITHUB_OUTPUT"
else
echo "published=false" >> "$GITHUB_OUTPUT"
fi
- name: Build design system MF remote
if: steps.check-ds.outputs.published == 'true'
working-directory: packages/design-system
run: pnpm build:federation
env:
MF_PUBLIC_PATH: https://static.myplatform.com/design-system/${{ steps.check-ds.outputs.version }}/
- name: Upload design system to R2
if: steps.check-ds.outputs.published == 'true'
# Wrangler v3 reached EOL Q1 2026; use v4.
uses: cloudflare/wrangler-action@v4
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
command: >
r2 object put
static-assets/design-system/${{ steps.check-ds.outputs.version }}/
--local packages/design-system/dist/
--recursive
- name: Register design system version
if: steps.check-ds.outputs.published == 'true'
run: |
curl -sf -X POST \
"${{ vars.VERSION_CONFIG_SERVICE_URL }}/api/remotes/design-system/versions" \
-H "Authorization: Bearer ${{ secrets.VCS_DEPLOY_TOKEN }}" \
-H "Content-Type: application/json" \
-d '{
"version": "${{ steps.check-ds.outputs.version }}",
"manifestUrl": "https://static.myplatform.com/design-system/${{ steps.check-ds.outputs.version }}/mf-manifest.json",
"activateIn": ["dev"]
}'
Esto garantiza que cualquier nueva versión del design system esté disponible de inmediato como remote de Module Federation en el entorno dev. La promoción a staging y production se gestiona a través del Admin UI o la API del Version Config Service.
CI/CD de MFEs (polyrepos)
Cada micro frontend vive en su propio repositorio con su propio pipeline de CI/CD. Los pipelines siguen una estructura consistente pero pueden personalizarse por equipo.
Pipeline de CI (en cada PR)
.github/workflows/ci.yml
name: CI
on:
pull_request:
branches: [main]
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
packages: read
pull-requests: write
deployments: write
jobs:
ci:
name: Lint, Typecheck, Test
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Setup pnpm
uses: pnpm/action-setup@v4
- name: Setup Node.js
uses: actions/setup-node@v6
with:
# Node 20 reaches EOL in April 2026. Node 24 is now Active LTS.
node-version: 22
cache: pnpm
registry-url: https://npm.pkg.github.com
scope: '@org'
- name: Install dependencies
run: pnpm install --frozen-lockfile
env:
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Run lint
run: pnpm lint
- name: Run typecheck
run: pnpm typecheck
- name: Run unit tests
run: pnpm test -- --coverage
- name: Build MFE
run: pnpm build
env:
MF_PUBLIC_PATH: https://static.myplatform.com/${{ github.event.repository.name }}/pr-${{ github.event.pull_request.number }}/
- name: Upload build artifact
uses: actions/upload-artifact@v6
with:
name: mfe-build
path: dist/
retention-days: 3
e2e:
name: E2E Tests
needs: ci
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Setup pnpm
uses: pnpm/action-setup@v4
- name: Setup Node.js
uses: actions/setup-node@v6
with:
# Node 20 reaches EOL in April 2026. Node 24 is now Active LTS.
node-version: 22
cache: pnpm
registry-url: https://npm.pkg.github.com
scope: '@org'
- name: Install dependencies
run: pnpm install --frozen-lockfile
env:
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Install Playwright browsers
run: pnpm exec playwright install --with-deps chromium
- name: Download build artifact
uses: actions/download-artifact@v8
with:
name: mfe-build
path: dist/
# NOTE: The E2E tests reference BASE_URL=http://localhost:3000 but
# there is no step that starts a local dev server to serve the
# downloaded build artifact. You must add a server step before
# running E2E tests. For example, use `pnpm exec serve -s dist -l 3000`
# or `npx http-server dist -p 3000` as a background process:
- name: Start local preview server
run: npx --yes serve -s dist -l 3000 &
- name: Wait for server to be ready
run: npx --yes wait-on http://localhost:3000 --timeout 30000
- name: Run E2E tests
run: pnpm test:e2e
env:
BASE_URL: http://localhost:3000
- name: Upload Playwright report
if: failure()
uses: actions/upload-artifact@v6
with:
name: playwright-report
path: playwright-report/
retention-days: 7
preview:
name: Deploy Preview
needs: ci
runs-on: ubuntu-latest
timeout-minutes: 10
environment:
name: preview
url: ${{ steps.preview-url.outputs.url }}
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Download build artifact
uses: actions/download-artifact@v8
with:
name: mfe-build
path: dist/
- name: Upload preview to R2
# Wrangler v3 reached EOL Q1 2026; use v4.
uses: cloudflare/wrangler-action@v4
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
command: >
r2 object put
static-assets/${{ github.event.repository.name }}/pr-${{ github.event.pull_request.number }}/
--local dist/
--recursive
- name: Set preview URL
id: preview-url
run: |
SHELL_URL="${{ vars.SHELL_APP_DEV_URL }}"
MFE_NAME="${{ github.event.repository.name }}"
PR_NUM="${{ github.event.pull_request.number }}"
echo "url=${SHELL_URL}?mfe_${MFE_NAME#mfe-}=pr-${PR_NUM}" >> "$GITHUB_OUTPUT"
- name: Comment preview URL on PR
uses: actions/github-script@v8
with:
script: |
const mfeName = context.repo.repo;
const prNumber = context.payload.pull_request.number;
const previewUrl = '${{ steps.preview-url.outputs.url }}';
const directUrl = `https://static.myplatform.com/${mfeName}/pr-${prNumber}/`;
const body = [
`### Preview Deployment`,
``,
`| Resource | URL |`,
`|---|---|`,
`| **Shell with preview MFE** | [${previewUrl}](${previewUrl}) |`,
`| **Direct bundle** | [${directUrl}](${directUrl}) |`,
``,
`> Load this preview MFE in any environment by appending \`?mfe_${mfeName.replace('mfe-', '')}=pr-${prNumber}\` to the shell URL.`,
].join('\n');
// Find and update existing comment or create new one
const comments = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
});
const existing = comments.data.find(c =>
c.user.login === 'github-actions[bot]' &&
c.body.includes('### Preview Deployment')
);
if (existing) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existing.id,
body,
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
body,
});
}
Aspectos clave del pipeline de CI de MFE:
- El
.npmrcde cada polyrepo está configurado para obtener paquetes del scope@orgdesde GitHub Packages. - El paso de build establece
MF_PUBLIC_PATHa la URL de preview para que las referencias a chunks se resuelvan correctamente. - Los tests E2E se ejecutan contra el MFE compilado cargado en un entorno de shell aislado.
- El despliegue de preview sube el build a R2 en una ruta específica del PR.
Pipeline de Deploy (al mergear a main)
.github/workflows/deploy.yml
name: Deploy
on:
push:
branches: [main]
concurrency:
group: deploy-${{ github.ref }}
cancel-in-progress: false
permissions:
contents: write
packages: read
deployments: write
jobs:
deploy:
name: Build and Deploy
runs-on: ubuntu-latest
timeout-minutes: 15
environment:
name: dev
url: ${{ steps.deploy-url.outputs.url }}
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Setup pnpm
uses: pnpm/action-setup@v4
- name: Setup Node.js
uses: actions/setup-node@v6
with:
# Node 20 reaches EOL in April 2026. Node 24 is now Active LTS.
node-version: 22
cache: pnpm
registry-url: https://npm.pkg.github.com
scope: '@org'
- name: Install dependencies
run: pnpm install --frozen-lockfile
env:
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Determine version
id: version
run: |
VERSION=$(node -p "require('./package.json').version")
SHORT_SHA=$(git rev-parse --short HEAD)
FULL_VERSION="${VERSION}+${SHORT_SHA}"
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
echo "full_version=${FULL_VERSION}" >> "$GITHUB_OUTPUT"
echo "Deploying version: ${FULL_VERSION}"
- name: Build MFE
run: pnpm build
env:
MF_PUBLIC_PATH: https://static.myplatform.com/${{ github.event.repository.name }}/${{ steps.version.outputs.version }}/
NODE_ENV: production
- name: Upload bundles to R2
# Wrangler v3 reached EOL Q1 2026; use v4.
uses: cloudflare/wrangler-action@v4
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
command: >
r2 object put
static-assets/${{ github.event.repository.name }}/${{ steps.version.outputs.version }}/
--local dist/
--recursive
- name: Register version with Version Config Service
run: |
MFE_NAME="${{ github.event.repository.name }}"
VERSION="${{ steps.version.outputs.version }}"
FULL_VERSION="${{ steps.version.outputs.full_version }}"
COMMIT_SHA="${{ github.sha }}"
curl -sf -X POST \
"${{ vars.VERSION_CONFIG_SERVICE_URL }}/api/remotes/${MFE_NAME}/versions" \
-H "Authorization: Bearer ${{ secrets.VCS_DEPLOY_TOKEN }}" \
-H "Content-Type: application/json" \
-d "{
\"version\": \"${VERSION}\",
\"buildId\": \"${FULL_VERSION}\",
\"commitSha\": \"${COMMIT_SHA}\",
\"manifestUrl\": \"https://static.myplatform.com/${MFE_NAME}/${VERSION}/mf-manifest.json\",
\"activateIn\": [\"dev\"]
}"
- name: Tag release
run: |
git tag "v${{ steps.version.outputs.version }}"
git push origin "v${{ steps.version.outputs.version }}"
- name: Set deploy URL
id: deploy-url
run: |
echo "url=${{ vars.SHELL_APP_DEV_URL }}" >> "$GITHUB_OUTPUT"
- name: Notify Slack
if: always()
uses: slackapi/slack-github-action@v2
with:
webhook: ${{ secrets.SLACK_DEPLOY_WEBHOOK }}
webhook-type: incoming-webhook
payload: |
{
"text": "${{ job.status == 'success' && ':white_check_mark:' || ':x:' }} *${{ github.event.repository.name }}* v${{ steps.version.outputs.version }} deployment to *dev* ${{ job.status }}",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "${{ job.status == 'success' && ':white_check_mark:' || ':x:' }} *${{ github.event.repository.name }}* `v${{ steps.version.outputs.version }}` deployment to *dev* *${{ job.status }}*\n\n*Commit:* <${{ github.event.head_commit.url }}|${{ github.event.head_commit.message }}>\n*Author:* ${{ github.event.head_commit.author.name }}"
}
}
]
}
Flujo de despliegue:
- Se mergea código a
main. - El pipeline compila el MFE con un
MF_PUBLIC_PATHde producción apuntando a la ruta versionada en R2. - Los bundles compilados (incluyendo
mf-manifest.json) se suben a R2 en/{mfe-name}/{version}/. - La versión se registra en el Version Config Service y se auto-activa en el entorno
dev. - Se crea un git tag para trazabilidad.
- Se envía una notificación a Slack con el estado del despliegue.
La promoción a staging y production se realiza a través del Admin UI o la API, no a través del pipeline de CI. Esto desacopla el despliegue (subir artefactos) del release (activar una versión en un entorno).
Estrategia de versionado para MFEs
Las versiones de MFE siguen versionado semántico con directrices claras sobre cuándo incrementar cada segmento:
| Bump | Cuándo usarlo | Ejemplo |
|---|---|---|
| Major | Cambio incompatible en el contrato de Module Federation (exports renombrados, módulos expuestos eliminados, cambios en los requisitos de dependencias compartidas) | 1.0.0 a 2.0.0 |
| Minor | Nuevo módulo expuesto, nueva funcionalidad, adiciones a la API no incompatibles | 1.0.0 a 1.1.0 |
| Patch | Corrección de bugs, mejoras de rendimiento, cambios de estilos | 1.0.0 a 1.0.1 |
La versión se deriva de package.json y se etiqueta en git en cada despliegue a main.
scripts/version.sh
#!/usr/bin/env bash
set -euo pipefail
# Usage: ./scripts/version.sh <major|minor|patch>
# Bumps the version in package.json, commits, and prepares for deployment.
BUMP_TYPE="${1:?Usage: version.sh <major|minor|patch>}"
if [[ "$BUMP_TYPE" != "major" && "$BUMP_TYPE" != "minor" && "$BUMP_TYPE" != "patch" ]]; then
echo "Error: bump type must be one of: major, minor, patch"
exit 1
fi
# Ensure working directory is clean
if [[ -n "$(git status --porcelain)" ]]; then
echo "Error: working directory is not clean. Commit or stash changes first."
exit 1
fi
# Ensure we are on main
CURRENT_BRANCH=$(git branch --show-current)
if [[ "$CURRENT_BRANCH" != "main" ]]; then
echo "Warning: you are on branch '$CURRENT_BRANCH', not 'main'."
read -r -p "Continue anyway? [y/N] " confirm
[[ "$confirm" =~ ^[Yy]$ ]] || exit 0
fi
# Read current version
CURRENT_VERSION=$(node -p "require('./package.json').version")
echo "Current version: $CURRENT_VERSION"
# Bump version using npm (updates package.json)
NEW_VERSION=$(npm version "$BUMP_TYPE" --no-git-tag-version)
NEW_VERSION="${NEW_VERSION#v}" # strip leading 'v'
echo "New version: $NEW_VERSION"
# Commit the version bump
git add package.json
git commit -m "chore: bump version to ${NEW_VERSION}"
echo ""
echo "Version bumped to ${NEW_VERSION}."
echo "Push to main to trigger deployment: git push origin main"
echo "The deploy pipeline will create the git tag automatically."
Los equipos también pueden usar npm version patch/minor/major directamente. El pipeline de deploy lee de package.json y crea los git tags automáticamente.
Entornos de Preview
Preview Deployments por PR
Cada pull request en un polyrepo de MFE obtiene un despliegue de preview único. El MFE de preview se sube a R2 en una ruta específica del PR y puede ser cargado por la shell app mediante un parámetro de URL que sobreescribe la configuración.
Cómo funciona:
- El pipeline de CI compila el MFE con
MF_PUBLIC_PATHapuntando ahttps://static.myplatform.com/{mfe-name}/pr-{number}/. - Los bundles compilados se suben a R2 en
/{mfe-name}/pr-{number}/. - Se publica un comentario en el PR con un enlace a la shell app cargando el MFE de preview.
- La shell app detecta el parámetro
?mfe_{name}=pr-{number}y sobreescribe la URL del manifest para ese MFE. - Cuando el PR se cierra o se mergea, el preview se limpia.
Manejo de parámetros de URL en la shell app (simplificado):
// In the shell app's MFE resolution logic
function resolveManifestUrl(mfeName: string, defaultUrl: string): string {
if (typeof window === 'undefined') return defaultUrl;
const params = new URLSearchParams(window.location.search);
const override = params.get(`mfe_${mfeName}`);
if (override) {
// override can be "pr-123" or a full URL
if (override.startsWith('http')) {
return override;
}
return `https://static.myplatform.com/mfe-${mfeName}/${override}/mf-manifest.json`;
}
return defaultUrl;
}
Workflow de limpieza de preview (.github/workflows/preview-cleanup.yml):
name: Preview Cleanup
on:
pull_request:
types: [closed]
permissions:
contents: read
deployments: write
jobs:
cleanup:
name: Clean Up Preview
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Delete preview assets from R2
# Wrangler v3 reached EOL Q1 2026; use v4.
uses: cloudflare/wrangler-action@v4
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
command: >
r2 object delete
static-assets/${{ github.event.repository.name }}/pr-${{ github.event.pull_request.number }}/
--recursive
- name: Deactivate GitHub deployment
uses: actions/github-script@v8
with:
script: |
const deployments = await github.rest.repos.listDeployments({
owner: context.repo.owner,
repo: context.repo.repo,
environment: 'preview',
ref: context.payload.pull_request.head.ref,
});
for (const deployment of deployments.data) {
await github.rest.repos.createDeploymentStatus({
owner: context.repo.owner,
repo: context.repo.repo,
deployment_id: deployment.id,
state: 'inactive',
});
}
- name: Comment on PR
uses: actions/github-script@v8
with:
script: |
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.pull_request.number,
body: '> Preview environment has been cleaned up.',
});
Entornos de Preview para Workers
Para cambios en Workers dentro del monorepo, los entornos de preview usan el soporte de entornos integrado de Wrangler.
Opción 1: Entorno de preview con nombre
# Deploy to a preview environment with separate KV/D1 bindings
wrangler deploy --env preview
# The wrangler.toml defines the preview environment:
# [env.preview]
# name = "shell-app-preview"
# kv_namespaces = [{ binding = "CONFIG", id = "preview-kv-id" }]
# d1_databases = [{ binding = "DB", database_name = "platform-preview", database_id = "preview-db-id" }]
Opción 2: Sesión de dev remota
# Start a remote preview session (uses actual Cloudflare infrastructure)
wrangler dev --remote
# This creates a temporary Worker that is accessible at a unique URL
# Useful for quick manual testing without a full deployment
Opción 3: Worker preview específico por PR (en CI)
- name: Deploy Worker preview
if: github.event_name == 'pull_request'
# Wrangler v3 reached EOL Q1 2026; use v4.
uses: cloudflare/wrangler-action@v4
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
workingDirectory: workers/shell-app
command: deploy --env preview --name shell-app-pr-${{ github.event.pull_request.number }}
Los previews de Workers usan KV namespaces y bases de datos D1 separadas para evitar contaminar los datos de producción. El wrangler.toml de cada Worker define bindings para cada entorno ([env.preview], [env.staging], [env.production]).
Gestión de Dependencias Cross-Repo
Configuración de Renovate
Renovate automatiza las actualizaciones de dependencias en todos los repositorios. Un preset compartido en el monorepo garantiza un comportamiento consistente.
Preset compartido (platform-core/.github/renovate-preset.json):
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"description": "Shared Renovate preset for the micro frontends platform",
"extends": [
"config:recommended",
"helpers:pinGitHubActionDigests",
":semanticCommits"
],
"timezone": "Europe/London",
"schedule": ["before 9am on Monday"],
"prHourlyLimit": 4,
"prConcurrentLimit": 10,
"labels": ["dependencies"],
"hostRules": [
{
"matchHost": "npm.pkg.github.com",
"hostType": "npm",
"encrypted": {
"token": "ENCRYPTED_GITHUB_TOKEN_HERE"
}
}
],
"packageRules": [
{
"description": "Auto-merge patch updates from @org scope",
"matchPackagePatterns": ["^@org/"],
"matchUpdateTypes": ["patch"],
"automerge": true,
"automergeType": "pr",
"automergeStrategy": "squash",
"minimumReleaseAge": "0 days"
},
{
"description": "Group all @org packages in a single PR",
"matchPackagePatterns": ["^@org/"],
"matchUpdateTypes": ["minor", "major"],
"groupName": "platform-core packages",
"groupSlug": "platform-core"
},
{
"description": "Security updates get immediate PRs",
"matchCategories": ["security"],
"schedule": ["at any time"],
"minimumReleaseAge": "0 days",
"prPriority": 10,
"labels": ["dependencies", "security"]
},
{
"description": "Pin devDependencies",
"matchDepTypes": ["devDependencies"],
"rangeStrategy": "pin"
},
{
"description": "Group Rsbuild-related packages",
"matchPackagePatterns": ["^@rsbuild/", "^rsbuild"],
"groupName": "rsbuild",
"groupSlug": "rsbuild"
},
{
"description": "Group Module Federation packages",
"matchPackagePatterns": ["^@module-federation/"],
"groupName": "module-federation",
"groupSlug": "module-federation"
},
{
"description": "Group React-related packages",
"matchPackageNames": ["react", "react-dom", "@types/react", "@types/react-dom"],
"groupName": "react",
"groupSlug": "react"
},
{
"description": "Group Playwright packages",
"matchPackagePatterns": ["^@playwright/", "^playwright"],
"groupName": "playwright",
"groupSlug": "playwright"
}
]
}
Configuración: Generar el token encriptado de Renovate. El placeholder
ENCRYPTED_GITHUB_TOKEN_HEREdebe reemplazarse con un token encriptado real antes de que Renovate pueda acceder a GitHub Packages. Para generarlo:
- Crear un GitHub Personal Access Token (classic) con el scope
read:packages.- Ir a https://app.renovatebot.com/encrypt y pegar el token. Seleccionar la organización de la GitHub App de Renovate como scope.
- Copiar el valor encriptado y reemplazar
ENCRYPTED_GITHUB_TOKEN_HEREen el preset anterior.Consultar la documentación de secretos encriptados de Renovate para más detalles.
renovate.json del polyrepo de MFE (extiende el preset compartido):
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"github>myorg/platform-core:.github/renovate-preset.json"
]
}
Comportamientos clave:
- Las actualizaciones patch del scope
@orgse auto-mergean tras pasar CI (no requieren revisión manual para cambios internos no incompatibles). - Las actualizaciones minor y major del scope
@orgse agrupan en un único PR para reducir ruido. - Las vulnerabilidades de seguridad omiten la planificación semanal y crean PRs de inmediato.
- El resto de dependencias siguen la planificación semanal del lunes por la mañana.
Notificaciones vía Repository Dispatch
Cuando el monorepo publica nuevos paquetes, Renovate puede no detectar la actualización de inmediato (su planificación es semanal). Para garantizar una propagación rápida, el pipeline de release envía un evento repository-dispatch que lanza a Renovate a buscar actualizaciones de forma inmediata.
Handler de dispatch en el polyrepo de MFE (.github/workflows/renovate-trigger.yml):
name: Trigger Renovate
on:
repository_dispatch:
types: [platform-core-release]
permissions:
contents: write
pull-requests: write
jobs:
trigger-renovate:
name: Trigger Renovate Update Check
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Log received packages
run: |
echo "Received platform-core release notification:"
echo '${{ toJson(github.event.client_payload.packages) }}' | jq .
- name: Trigger Renovate via GitHub API
uses: actions/github-script@v8
with:
script: |
// Create a repository dispatch to trigger Renovate's on-demand check
await github.rest.repos.createDispatchEvent({
owner: context.repo.owner,
repo: context.repo.repo,
event_type: 'renovate',
});
console.log('Triggered Renovate update check');
Esto crea un ciclo de feedback ajustado: el monorepo publica una nueva versión, los repos de MFE reciben un PR de Renovate en minutos, el auto-merge se activa para actualizaciones patch, y el MFE se recompila con los últimos paquetes compartidos.
Despliegue de Workers
wrangler deploy
Cada Worker en el monorepo se despliega usando wrangler deploy con configuración específica por entorno.
Comandos de despliegue por tipo de Worker:
# Shell App Worker
wrangler deploy --config workers/shell-app/wrangler.toml --env production
# Version Config Service Worker
wrangler deploy --config workers/version-config-service/wrangler.toml --env production
# Auth Gateway Worker
wrangler deploy --config workers/auth-gateway/wrangler.toml --env production
# Static Asset Server Worker (if not using R2 public bucket)
wrangler deploy --config workers/static-server/wrangler.toml --env production
Gestión de secrets:
# Set secrets for each Worker (done once, not in CI)
# Shell App
wrangler secret put WORKOS_API_KEY --config workers/shell-app/wrangler.toml --env production
wrangler secret put WORKOS_CLIENT_ID --config workers/shell-app/wrangler.toml --env production
wrangler secret put SESSION_SECRET --config workers/shell-app/wrangler.toml --env production
# Version Config Service
wrangler secret put DEPLOY_TOKEN --config workers/version-config-service/wrangler.toml --env production
wrangler secret put ADMIN_API_KEY --config workers/version-config-service/wrangler.toml --env production
# Auth Gateway
wrangler secret put WORKOS_API_KEY --config workers/auth-gateway/wrangler.toml --env production
wrangler secret put JWT_SIGNING_KEY --config workers/auth-gateway/wrangler.toml --env production
Workflow de despliegue de Workers (.github/workflows/deploy-workers.yml):
name: Deploy Workers
on:
push:
branches: [main]
paths:
- 'workers/**'
- 'packages/shared-utils/**'
concurrency:
group: deploy-workers-${{ github.ref }}
cancel-in-progress: false
permissions:
contents: read
jobs:
detect-changes:
name: Detect Changed Workers
runs-on: ubuntu-latest
outputs:
shell-app: ${{ steps.changes.outputs.shell-app }}
version-config-service: ${{ steps.changes.outputs.version-config-service }}
auth-gateway: ${{ steps.changes.outputs.auth-gateway }}
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Detect changes
id: changes
uses: dorny/paths-filter@v3
with:
filters: |
shell-app:
- 'workers/shell-app/**'
- 'packages/shared-utils/**'
version-config-service:
- 'workers/version-config-service/**'
- 'packages/shared-utils/**'
auth-gateway:
- 'workers/auth-gateway/**'
- 'packages/shared-utils/**'
deploy-shell-app:
name: Deploy Shell App Worker
needs: detect-changes
if: needs.detect-changes.outputs.shell-app == 'true'
runs-on: ubuntu-latest
timeout-minutes: 10
environment:
name: production
url: https://app.myplatform.com
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Setup pnpm
uses: pnpm/action-setup@v4
- name: Setup Node.js
uses: actions/setup-node@v6
with:
# Node 20 reaches EOL in April 2026. Node 24 is now Active LTS.
node-version: 22
cache: pnpm
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build shared dependencies
run: pnpm turbo run build --filter=shell-app...
- name: Deploy to production
# Wrangler v3 reached EOL Q1 2026; use v4.
uses: cloudflare/wrangler-action@v4
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
workingDirectory: workers/shell-app
command: deploy --env production
deploy-version-config-service:
name: Deploy Version Config Service
needs: detect-changes
if: needs.detect-changes.outputs.version-config-service == 'true'
runs-on: ubuntu-latest
timeout-minutes: 10
environment:
name: production
url: https://vcs.myplatform.com
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Setup pnpm
uses: pnpm/action-setup@v4
- name: Setup Node.js
uses: actions/setup-node@v6
with:
# Node 20 reaches EOL in April 2026. Node 24 is now Active LTS.
node-version: 22
cache: pnpm
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build shared dependencies
run: pnpm turbo run build --filter=version-config-service...
- name: Run D1 migrations
id: d1-migrate
# Wrangler v3 reached EOL Q1 2026; use v4.
uses: cloudflare/wrangler-action@v4
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
workingDirectory: workers/version-config-service
command: d1 migrations apply platform-db --env production
- name: Verify D1 migrations succeeded
if: steps.d1-migrate.outcome != 'success'
run: |
echo "::error::D1 migrations failed. Aborting deployment to prevent deploying code against an incompatible database schema."
exit 1
- name: Deploy to production
# Only deploy if D1 migrations succeeded (the verify step above
# will have failed the workflow otherwise, but this condition
# provides an additional safety gate).
if: steps.d1-migrate.outcome == 'success'
# Wrangler v3 reached EOL Q1 2026; use v4.
uses: cloudflare/wrangler-action@v4
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
workingDirectory: workers/version-config-service
command: deploy --env production
deploy-auth-gateway:
name: Deploy Auth Gateway
needs: detect-changes
if: needs.detect-changes.outputs.auth-gateway == 'true'
runs-on: ubuntu-latest
timeout-minutes: 10
environment:
name: production
url: https://auth.myplatform.com
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Setup pnpm
uses: pnpm/action-setup@v4
- name: Setup Node.js
uses: actions/setup-node@v6
with:
# Node 20 reaches EOL in April 2026. Node 24 is now Active LTS.
node-version: 22
cache: pnpm
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build shared dependencies
run: pnpm turbo run build --filter=auth-gateway...
- name: Deploy to production
# Wrangler v3 reached EOL Q1 2026; use v4.
uses: cloudflare/wrangler-action@v4
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
workingDirectory: workers/auth-gateway
command: deploy --env production
Estrategia de despliegue
Cloudflare Workers se despliegan atómicamente en toda la red global. No existe el concepto de "blue/green" o "canary" a nivel de aplicación -- cada despliegue reemplaza la versión anterior a nivel mundial en segundos.
Despliegue gradual:
Cloudflare ofrece una funcionalidad integrada de despliegue gradual (llamada "Gradual Deployments" o "Versioned Deployments") que permite dividir el tráfico entre dos versiones de un Worker.
# Deploy a new version without activating it
wrangler versions upload --config workers/shell-app/wrangler.toml --env production
# Gradually roll out: 10% new version, 90% current version
wrangler versions deploy \
--version-id <new-version-id>=10% \
--version-id <current-version-id>=90% \
--config workers/shell-app/wrangler.toml \
--env production
# Increase to 50/50
wrangler versions deploy \
--version-id <new-version-id>=50% \
--version-id <current-version-id>=50% \
--config workers/shell-app/wrangler.toml \
--env production
# Full rollout
wrangler versions deploy \
--version-id <new-version-id>=100% \
--config workers/shell-app/wrangler.toml \
--env production
Rollback:
# Instant rollback to the previous version
wrangler rollback --config workers/shell-app/wrangler.toml --env production
# Or redeploy a specific previous version
wrangler versions deploy \
--version-id <previous-version-id>=100% \
--config workers/shell-app/wrangler.toml \
--env production
Para rollbacks de MFE, se usa el Version Config Service en su lugar:
# Activate a previous MFE version in production
curl -X POST \
"https://vcs.myplatform.com/api/remotes/mfe-dashboard/activate" \
-H "Authorization: Bearer $DEPLOY_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"version": "1.2.3",
"environment": "production"
}'
Esto actualiza inmediatamente la configuración de versiones, y la shell app comienza a cargar la versión anterior del MFE en la siguiente carga de página.
Seguridad del Pipeline
GitHub Actions OIDC para Cloudflare
En lugar de almacenar tokens de API de Cloudflare de larga duración en los secrets de GitHub, los pipelines deberían usar GitHub Actions OIDC (OpenID Connect) para obtener tokens de corta duración.
# In the workflow job
jobs:
deploy:
permissions:
id-token: write # Required for OIDC
contents: read
steps:
- name: Deploy to Cloudflare
# Wrangler v3 reached EOL Q1 2026; use v4.
uses: cloudflare/wrangler-action@v4
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
# When Cloudflare supports OIDC natively, this will change to:
# oidc:
# audience: "https://myplatform.cloudflareaccess.com"
TODO (objetivo: Q3 2026): Reemplazar los secrets
CLOUDFLARE_API_TOKENcon federación OIDC de GitHub Actions una vez que Cloudflare añada soporte nativo de OIDC para despliegues de Wrangler/Workers. Esto eliminará todos los tokens de API de Cloudflare de larga duración de los secrets de CI y se alineará con la postura de seguridad zero-trust. Seguir el progreso en el Cloudflare OIDC feature request y actualizar todos los pasos decloudflare/wrangler-actionpara usar el bloque de configuraciónoidcen lugar deapiToken.Estado actual: Los tokens de API de Cloudflare se almacenan como secrets a nivel de organización en GitHub. Estos tokens deben tener permisos mínimos (p. ej., Workers Scripts:Edit, R2:Edit, D1:Edit por cuenta) y rotarse trimestralmente hasta que OIDC esté disponible.
Gestión de Secrets
# Secrets are organized by scope and stored in GitHub repository/organization secrets
# Organization-level secrets (shared across all repos):
# - CLOUDFLARE_API_TOKEN : Cloudflare API token for deployments
# - CLOUDFLARE_ACCOUNT_ID : Cloudflare account identifier
# - SLACK_DEPLOY_WEBHOOK : Slack webhook for deployment notifications
# - TURBO_TOKEN : Turborepo remote cache token
# Repository-level secrets (per-repo):
# - VCS_DEPLOY_TOKEN : Token for Version Config Service API
# - DISPATCH_TOKEN : GitHub PAT for cross-repo dispatch events
# Environment-level secrets (per deployment environment):
# - WORKOS_API_KEY : WorkOS API key (different per environment)
# - SESSION_SECRET : Session encryption secret
# - JWT_SIGNING_KEY : JWT signing key
Reglas de Protección de Ramas
Todos los repositorios aplican las siguientes reglas de protección de ramas en main:
| Regla | Configuración |
|---|---|
| Requerir revisión de pull requests | 1 aprobación mínima |
| Descartar revisiones obsoletas en nuevos pushes | Habilitado |
| Requerir que los status checks pasen | El workflow de CI debe pasar |
| Requerir que las ramas estén actualizadas | Habilitado |
| Requerir commits firmados | Opcional (recomendado) |
| Restringir quién puede hacer push a ramas que coincidan | Solo automatización de releases |
| Permitir force pushes | Deshabilitado |
| Permitir eliminaciones | Deshabilitado |
Escaneo de Dependencias
# GitHub Dependabot is enabled for security alerts (in .github/dependabot.yml)
version: 2
updates:
- package-ecosystem: npm
directory: /
schedule:
interval: weekly
# Note: Renovate handles dependency PRs; Dependabot is only for security alerts
open-pull-requests-limit: 0
- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly
open-pull-requests-limit: 5
Renovate gestiona las actualizaciones regulares de dependencias, mientras que Dependabot proporciona alertas de vulnerabilidades de seguridad y puede crear PRs de seguridad de emergencia.
Monitorización y Notificaciones
Integración con Slack
Todos los pipelines de despliegue envían notificaciones a Slack con estado, versión e información del commit.
Configuración de notificación a Slack (acción reutilizable .github/actions/notify-slack/action.yml):
name: Notify Slack
description: Send deployment notification to Slack
inputs:
webhook-url:
description: Slack webhook URL
required: true
status:
description: Deployment status (success, failure, cancelled)
required: true
environment:
description: Target environment name
required: true
version:
description: Deployed version
required: false
default: 'N/A'
service-name:
description: Name of the deployed service
required: true
runs:
using: composite
steps:
- name: Send Slack notification
uses: slackapi/slack-github-action@v2
with:
webhook: ${{ inputs.webhook-url }}
webhook-type: incoming-webhook
payload: |
{
"blocks": [
{
"type": "header",
"text": {
"type": "plain_text",
"text": "${{ inputs.status == 'success' && 'Deployment Succeeded' || inputs.status == 'failure' && 'Deployment Failed' || 'Deployment Cancelled' }}"
}
},
{
"type": "section",
"fields": [
{ "type": "mrkdwn", "text": "*Service:*\n${{ inputs.service-name }}" },
{ "type": "mrkdwn", "text": "*Version:*\n`${{ inputs.version }}`" },
{ "type": "mrkdwn", "text": "*Environment:*\n${{ inputs.environment }}" },
{ "type": "mrkdwn", "text": "*Status:*\n${{ inputs.status }}" }
]
},
{
"type": "context",
"elements": [
{
"type": "mrkdwn",
"text": "<${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|View workflow run> | Triggered by ${{ github.actor }}"
}
]
}
]
}
Registro de Eventos de Despliegue
Cada despliegue se registra en la base de datos D1 del Version Config Service para auditoría y el dashboard del Admin UI.
// workers/version-config-service/src/handlers/deployments.ts
interface DeploymentEvent {
id: string;
remoteName: string;
version: string;
environment: string;
action: 'registered' | 'activated' | 'deactivated' | 'rolled_back';
triggeredBy: string;
commitSha: string | null;
buildId: string | null;
timestamp: string;
metadata: Record<string, string>;
}
export async function logDeploymentEvent(
db: D1Database,
event: Omit<DeploymentEvent, 'id' | 'timestamp'>
): Promise<DeploymentEvent> {
const id = crypto.randomUUID();
const timestamp = new Date().toISOString();
await db
.prepare(
`INSERT INTO deployment_events
(id, remote_name, version, environment, action, triggered_by, commit_sha, build_id, timestamp, metadata)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
)
.bind(
id,
event.remoteName,
event.version,
event.environment,
event.action,
event.triggeredBy,
event.commitSha,
event.buildId,
timestamp,
JSON.stringify(event.metadata)
)
.run();
return { ...event, id, timestamp };
}
export async function getDeploymentHistory(
db: D1Database,
remoteName: string,
options: { environment?: string; limit?: number } = {}
): Promise<DeploymentEvent[]> {
const { environment, limit = 50 } = options;
let query = `SELECT * FROM deployment_events WHERE remote_name = ?`;
const params: (string | number)[] = [remoteName];
if (environment) {
query += ` AND environment = ?`;
params.push(environment);
}
query += ` ORDER BY timestamp DESC LIMIT ?`;
params.push(limit);
const result = await db
.prepare(query)
.bind(...params)
.all<DeploymentEvent>();
return result.results;
}
Alertas de Despliegues Fallidos
Los despliegues fallidos disparan alertas adicionales más allá de Slack:
# Added to deployment workflows as a final step
- name: Alert on failure
if: failure()
uses: actions/github-script@v8
with:
script: |
// Create a GitHub issue for tracking
const title = `Deployment failure: ${{ github.event.repository.name }} to ${{ job.environment }}`;
const body = [
`## Deployment Failure`,
``,
`| Field | Value |`,
`|---|---|`,
`| **Service** | ${{ github.event.repository.name }} |`,
`| **Environment** | ${{ job.environment || 'dev' }} |`,
`| **Workflow run** | [View logs](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) |`,
`| **Commit** | [${{ github.sha }}](${{ github.server_url }}/${{ github.repository }}/commit/${{ github.sha }}) |`,
`| **Triggered by** | @${{ github.actor }} |`,
``,
`Please investigate and resolve.`,
].join('\n');
await github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title,
body,
labels: ['deployment-failure', 'priority:high'],
assignees: [context.actor],
});
Dashboard de Despliegues del Admin UI
El Admin UI (un MFE en sí mismo) proporciona un dashboard de despliegues que lee de la API del Version Config Service:
// In the Admin MFE: hooks/useDeploymentHistory.ts
import { useQuery } from '@tanstack/react-query';
interface DeploymentEvent {
id: string;
remoteName: string;
version: string;
environment: string;
action: 'registered' | 'activated' | 'deactivated' | 'rolled_back';
triggeredBy: string;
commitSha: string | null;
buildId: string | null;
timestamp: string;
}
export function useDeploymentHistory(
remoteName: string,
environment?: string
) {
return useQuery({
queryKey: ['deployment-history', remoteName, environment],
queryFn: async (): Promise<DeploymentEvent[]> => {
const params = new URLSearchParams();
if (environment) params.set('environment', environment);
const response = await fetch(
`/api/remotes/${remoteName}/deployments?${params}`,
{
headers: {
Authorization: `Bearer ${getAdminToken()}`,
},
}
);
if (!response.ok) {
throw new Error(`Failed to fetch deployment history: ${response.statusText}`);
}
return response.json();
},
refetchInterval: 30_000, // Poll every 30 seconds
});
}
El dashboard muestra:
- Una línea temporal de eventos de despliegue por MFE y entorno.
- Las versiones activas actualmente en cada entorno.
- Acciones rápidas: activar una versión, hacer rollback, comparar versiones.
- Metadatos del build incluyendo commit SHA, build ID y origen del trigger.
Referencias
- GitHub Actions Documentation
- Turborepo CI Guide
- Turborepo Remote Caching
- Changesets Documentation
- Changesets GitHub Action
- GitHub Packages (npm)
- Wrangler Deploy Documentation
- Wrangler Versioned Deployments
- Cloudflare R2 Wrangler Commands
- Renovate Documentation
- Renovate Shared Presets
- GitHub Actions OIDC
- Module Federation v2 Documentation