O GitHub Actions é uma ferramenta poderosa para orquestrar pipelines de CI/CD, mas às vezes você deseja um pouco mais de controle sobre o que acontece dentro deles. Uma composite action oferece a flexibilidade de uma etapa de fluxo de trabalho normal com a conveniência de uma ação... e, quando combinada com um coletor baseado em TypeScript, torna-se um componente reutilizável e de nível de plataforma.
Por que uma composite action?
- Executa no mesmo runner do job: ideal para tarefas de orquestração e arquivo de contexto (nenhum contêiner separado).
- Permite mixar shell e outras actions sem escrever JavaScript puro.
- Fácil de versionar e publicar no marketplace; repositórios mono ou multi-repo podem reutilizá-la.
Nós escolhemos esse modelo porque queríamos:
- Capturar contexto do workflow (branch, run_id, status, duração).
- Ler artefatos JSON produzidos pelo pipeline.
- Agregar logs/outputs e enviar para um serviço de telemetria.
- Não quebrar o job se algo falhar — execução sempre com
if: always().
Estrutura do repositório
.
├── action.yml # definição da composite action
├── package.json # dependências e scripts
├── src/
│ ├── collector.ts # lógica em TypeScript
│ └── lib/ # módulos auxiliares testáveis
├── dist/ # bundle JS (commitado)
├── tests/ # Jest unit tests
└── .github/workflows/ # workflows de demo, feature, main...
O collector.ts é o coração: lê variáveis de ambiente, analisa diretórios de artefatos, soma tamanhos de logs, faz retries no envio HTTP e gera um payload estruturado.
Exemplo de action.yml
name: "[ORG] Metrics-Collect Composite Action"
description: "Collect and send structured pipeline metrics to an external endpoint."
author: "Platform Engineering Team"
inputs:
endpoint_url:
description: "HTTP endpoint to receive metrics (if empty, dry-run)"
required: false
default: ""
auth_token:
description: "Token used for auth (pass as secret)"
required: false
default: ""
artifact_patterns:
description: "Comma-separated artifact name patterns to download"
required: false
default: ""
include_logs:
description: "Whether to attempt log collection"
required: false
default: "true"
working_directory:
description: "Working directory (for monorepos)"
required: false
default: ""
dry_run:
description: "If true, do not send HTTP requests"
required: false
default: "false"
outputs:
metrics_status:
description: "Overall status: ok | partial | failed"
value: ${{ steps.run-collector.outputs.metrics_status }}
payload_path:
description: "Local path to generated payload"
value: ${{ steps.run-collector.outputs.payload_path }}
runs:
using: "composite"
steps:
- name: Download artifacts (pattern)
uses: actions/download-artifact@v4
with:
name: ${{ inputs.artifact_patterns }}
path: ./artifacts
continue-on-error: true
- name: Run Metrics Collector
shell: bash
run: |
node $GITHUB_ACTION_PATH/dist/index.js \
--endpoint "${{ inputs.endpoint_url }}" \
--auth-token "${{ inputs.auth_token }}" \
--artifact-dir "./artifacts" \
--include-logs "${{ inputs.include_logs }}" \
--working-directory "${{ inputs.working_directory }}" \
--dry-run "${{ inputs.dry_run }}" || true
Collector em TypeScript
A justificativa por usar TypeScript:
- Tipagem, autocompletar e refatorações fáceis durante o desenvolvimento.
- Testes com Jest (busca por arquivos JSON, análise de logs, tratamento de erros).
- Construção um bundle único com
@vercel/nccgarante que o runner não precisa instalar nada.
Trecho de src/collector.ts:
async function main() {
const inps = parseArgs();
const ctx = await collectWorkflowContext();
const artifactFiles = await discoverJsonFiles(inps.artifactDir);
// ...agrega dados...
try {
const res = await sendWithRetry(inps.endpoint!, payload, headers, inps.retryCount!, inps.timeoutMs!);
// ...insere status/event_id no payload...
} catch (err) {
console.error('Failed to send payload', err);
}
}
main().catch(e => { console.error(e); process.exitCode = 0; });
O código trata todos os erros localmente, grava metrics-output/payload.json e produz outputs para a composite.
Integração com CI/CD
Adicionei um workflow de exemplo (.github/workflows/feature-workflow.yml) que roda o pipeline principal e, em seguida, chama a composite para coletar métricas:
jobs:
call-shared-ci: # job reutilizável de CI
uses: org/shared-workflows/backend-ci@v1
collect-metrics:
needs: [call-shared-ci]
if: always()
runs-on: ubuntu-latest
permissions:
contents: read
actions: read
steps:
- uses: actions/checkout@v6
- uses: org/metrics-collect@v1
with:
endpoint_url: ${{ secrets.METRICS_ENDPOINT }}
auth_token: ${{ secrets.METRICS_TOKEN }}
artifact_patterns: "step-result-*,coverage-report"
include_logs: "true"
A condição if: always() garante que, mesmo que a verificação de lint/test falhe, ainda coletamos dados.
Demo local com act
O repositório contém um workflow de demo que gera artefatos JSON falsos e executa a action com endpoint_url apontando para webhook.site:
on: workflow_dispatch
jobs:
demo:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- run: mkdir artifacts; echo '{"foo":1}' > artifacts/metrics.json
- uses: ./
with:
endpoint_url: ${{ secrets.INPUT_ENDPOINT_URL }}
include_logs: "true"
O act pode executar esse workflow offline, contanto que você passe -s INPUT_ENDPOINT_URL="https://webhook.site/...".
Casos de uso
- Telemetry / Analytics: enviar métricas de duração, falha e outputs para um motor centralizado.
- Auditoria: consolidar resultados de lint, testes e segurança em um único payload para relatórios de conformidade.
- Cross-repo dashboards: equipes podem empacotar essa action e aplicar em todos os repositórios simples ou monorepos.
Arquitetura definida
- Composite Action (orquestração): inputs/outputs declarados, download de artefatos, execução do binário bundle.
- Collector TypeScript: lógica pura, agnóstica de GitHub; módulos auxiliares testáveis.
-
Bundle
dist/index.js: resultante do@vercel/ncc(commitado). -
Workflows:
demo(para desenvolvimento),feature/staging/main(integração/prod).
Tolerância a falhas
- Falhas no download de artefatos ou envio HTTP não quebram o job (usar
continue-on-errore|| true). - Logs e payload são persistidos localmente (
metrics-output/) para inspeção. - Retries com backoff exponencial no módulo HTTP.
Publicando e versionando
Usamos semantic-release em uma workflow de release que dispara na conclusão da staging:
dependencies:
- name: release-workflow
uses: org/shared-workflows/shared-release@staging
Cada release produz uma tag (v1, v1.1.0, etc). Consumidores referenciam org/metrics-collect@v1 para estabilidade.
Conclusão
Transformar lógica de coleta de métricas em uma composite action permite padronizar instrumentação em toda a organização. O uso de TypeScript e bundling com ncc garante código limpo, testável e que roda rapidamente nos runners.
Top comments (0)