Pular para conteúdo

Versionamento e release

Status: Aprovado · Responsável: Gustavo Madruga · Atualizado em: 2026-06-30

Como os apps da X-Adm são versionados e como uma release acontece. A norma está na constituição §5; esta página é o detalhe operacional.

SemVer — quando bumpar o quê

Todo app segue MAJOR.MINOR.PATCH:

Bump Quando Exemplo
MAJOR quebra contrato: API muda de forma incompatível, migração manual de dados/config, comportamento que o usuário percebe como "outro sistema" trocar o formato da planilha aceita
MINOR funcionalidade nova, compatível novo relatório, novo endpoint
PATCH correção ou ajuste interno, sem mudança de contrato bugfix, bump de dependência

Por stack:

  • Java — versão no build (build.gradle/gradle.properties ou pom.xml).
  • Flutterversion: X.Y.Z+N no pubspec.yaml: X.Y.Z é o SemVer, +N é o build number (inteiro, só cresce — exigência das lojas).
  • Node — campo "version" do package.json, mesma semântica.
  • Stack sem manifesto próprio (site estático, scripts) — arquivo VERSION na raiz do repo, contendo só X.Y.Z. É o caso do próprio site central de documentação.

O que é uma release

Uma release é um commit + uma tag + uma entrada de changelog, sempre juntos:

  1. Bump da versão no arquivo de build/manifesto.
  2. Entrada nova no CHANGELOG.md — formato Keep a Changelog, em pt-BR, seções Adicionado / Alterado / Corrigido / Removido.
  3. Tag Git vX.Y.Z (com v, igual à versão do passo 1).
  4. Push de commit e tag.

Release se faz pela skill /xadm-release (Claude Code), que executa os quatro passos na ordem e sem esquecer nenhum — não bumpe versão à mão. A versão canônica da skill é o template templates/xadm-release-skill.md do mirror público (também na página de templates): copie para .claude/skills/xadm-release/SKILL.md no repo do app, troque <app>/<repo> e mantenha só o bloco de "Particularidades" da sua stack. Não mude o fluxo — divergência entre apps é o que o template evita.

Projeto ainda sem CHANGELOG.md? Não crie à mão. A primeira /xadm-release o cria do zero (Keep a Changelog, pt-BR) inferindo as entradas do git log desde o início — o changelog nasce cobrindo o histórico na 1ª release, não precisa existir desde o commit 1. Idem para projeto sem tags: a skill trata "primeira release" e você escolhe a versão (ex. a que já está no build).

O deploy não muda: push em master continua sendo o gatilho do Coolify. A tag não deploya nada — ela é o marco rastreável de "o que estava no ar quando a versão X saiu".

Distribuição vs deploy (arquétipo CLI/worker)

O fluxo acima assume app servidor com deploy contínuo (Coolify, push em master). Um CLI/worker distribuído como artefato (um .jar, um binário) não tem deploy contínuo — ele é distribuído: a release publica/entrega o artefato versionado (<app>-<versão>.jar), e quem consome instala/atualiza. A mecânica de versão é a mesma (SemVer + tag + CHANGELOG, via /xadm-release); muda o destino: distribuição de artefato, não deploy de serviço. Sem /health nem health check (não é servidor) — a versão aparece no --version/log de startup (Java).

O marco é a Release do Forgejo, não só a tag. No Forgejo, tag ≠ Release: a tag sozinha deixa a aba Releases vazia e o artefato sem proveniência ("qual jar é a v0.1.6? quem buildou?"). Para app que distribui artefato, o release.yml (bloco opt-in do template) builda o artefato no CI e publica a Release — tag + notas da seção do CHANGELOG + o asset anexado —, que vira o marco real do que foi liberado, com download reproduzível. O operador baixa o jar da Release, não recebe uma cópia manual. Detalhe: o build precisa da imagem da stack (ci-java:<v>, a ci-base não tem JDK); a API do Forgejo é chamada por curl com o secrets.GITHUB_TOKEN (escopo de escrita em release; se restrito, token dedicado da org).

Health check obrigatório

Todo app deployável no Coolify define um health check (HEALTHCHECK na imagem ou endpoint configurado no painel, ex. /health) — sem ele o rolling update troca o container sem saber se o novo responde. Operação: Coolify.

Versão visível em runtime

Diagnóstico de produção não pode depender de abrir o painel. Como já há monitoramento (o watchdog), o /health deixou de ser formato livre: tem um shape mínimo (a cláusula "monitoramento → shape vira contrato" foi acionada).

A versão em runtime vem sempre da fonte do release (o manifesto da stack — gradle.properties/build.gradle.kts, pubspec.yaml, package.json, VERSION), nunca um literal no código. Literal mente (mostra a versão de ontem) e drifta a cada release, porque a /xadm-release só toca o manifesto + o CHANGELOG — não caça strings espalhadas. Uma única fonte; o runtime a lê.

Shape mínimo do /health (tudo que é servido por HTTP)

{ "status": "ok", "versao": "1.4.2" }
  • Obrigatório: status (string de liveness truthy quando vivo — o valor exato não é normativo: "ok", "UP", etc. valem; o /health nativo do Micronaut responde "UP" e está conforme, sem remapear) e versao (SemVer legível, da mesma fonte do release — sem criar segunda fonte de versão). Responder 200 quando vivo.
  • Opcionais úteis: commit, started_at. Acrescentar campo é livre; mudar/remover os dois obrigatórios é mudança de contrato — muda no padrão primeiro.
  • Java/Micronaut — uma task Gradle gera version.properties (de project.version) no classpath; um InfoSource expõe a versão em /info e no /health, e a 1ª linha do main loga a versão. ⚠️ A versão do @OpenAPIDefinition é contrato HTTP da API, outra coisa — não confundir com a versão de release.
  • Java CLI (picocli) — o --version lê de um IVersionProvider que carrega o version.properties gerado do project.version (a mesma task Gradle do Micronaut); nunca @Command(version = "0.1.0") literal — isso mente e drifta a cada release.
  • Node — ler a version do package.json.
  • Flutter web — é servido por HTTP, então tem /health e responde o shape mínimo {status, versao} (o nginx que serve o app monta a resposta, lendo a versão do version.json do build). ⚠️ Não sirva o version.json cru como /health: ele é outro endpoint (artefato do build, consumido pelo auto-update do app) e não tem status — e seu campo é version, não versao. /health = {status, versao}; /version.json = artefato do build, separado.
  • Flutter mobile — sem servidor, sem /health: a versão aparece in-app via package_info_plus.
  • App sem HTTP (cron, worker) — loga a versão na 1ª linha do startup.
  • Site estático (ex. o central) — expõe o equivalente em /versao.txt (+ /commit.txt), não um JSON. O /versao.txt é auto-rotulado (site X.Y.Z, não o número nu) para não ser confundido com a versão da constituição — ver abaixo.

Duas versões: site x constituição

O repo central tem dois números de versão desacoplados — confundi-los já levou um app a registrar a versão errada. Saiba distinguir:

Versão do SITE/portal Versão da CONSTITUIÇÃO
O que versiona o site docs.xadm.biz (deploy, HTML) o documento da constituição (o padrão em si)
Fonte VERSION na raiz do repo central frontmatter versao: de docs/documentacao/constituicao.md
Onde aparece /versao.txt (site X.Y.Z), tag vX.Y.Z, CHANGELOG.md, rodapé “Site vX.Y.Z” constituicao-versao.txt, app.json constituicao:, rodapé “Constituição vX.Y.Z”
Quem bumpa /xadm-release (push em master) edição da constituição (+ regen do manifesto)

Regra de ouro: a versão-base que um app registra (campo constituicao: do app.json) é sempre a da constituição, nunca a do site. As duas evoluem em ritmos diferentes (a constituição muda menos que o site).

Armadilha do clone local / bootstrap. Um app sem a /xadm-docs instalada só tem o link da constituição como porta de entrada; e se houver um clone do repo central no disco, o sinal mais gritante ali é o VERSION (= versão do site) e a tag vX.Y.Z — que são o número errado para "versão da constituição". A versão da constituição é sempre o versao: do frontmatter (ou constituicao-versao.txt no site), nunca o VERSION. Na dúvida, é o número rotulado como “Constituição” no rodapé.

O que o CI valida

O valida-release.py (mantido no repo central e publicado neste site, como o valida-frontmatter.py) confere:

  • a versão do manifesto é SemVer e tem entrada ## [X.Y.Z] no CHANGELOG.md;
  • em push de tag, a tag vX.Y.Z bate com a versão do manifesto.

Passo para o workflow de CI do app (no de build/teste — não no docs.yml):

- name: Validar release
  run: |
    curl -fsSL -o /tmp/valida-release.py \
      https://docs.xadm.biz/toolchain/valida-release.py
    python3 /tmp/valida-release.py .

Em workflow disparado por tag, acrescente --tag "${GITHUB_REF_NAME}" para conferir tag × manifesto.

Validação local: o mesmo comando, na raiz do repo.

Repo adotando o padrão agora

Entre criar o manifesto/CHANGELOG.md e fazer a primeira /xadm-release, o validador acusa "sem a seção da versão" — é o esperado. Rode a primeira /xadm-release antes de ligar o passo no CI.