Releases
O MangoStudio é distribuído como binários standalone (GitHub Releases), imagem
Docker no GHCR, CLI npm (mangostudio), tap Homebrew, bucket Scoop
(Windows) e crate launcher no crates.io (cargo install mangostudio). O
changelog é gerado a partir de Conventional Commits com
git-cliff na preparação da release e verificado no
momento da tag; nada é editado manualmente.
Suporte Rust e releases
Builds do código-fonte Rust e novas releases de crates Rust usam um único mínimo de Rust 1.99.0.
Isso substitui de propósito o piso 1.97.0 do workspace, que seguia stable-minus-two, e a exceção
1.96.0 do launcher. Todo crate estável herda o rust-version do workspace raiz, e o pin de
desenvolvimento também é 1.99.0. A regra vale para o launcher publicado mangostudio e o SDK Rust
mango-protocol, além dos crates não publicados do runtime. O SDK de agentes externos mantém
seu próprio mínimo e sua política de releases.
Quem compila o launcher com Rust 1.96 ou 1.97 precisa atualizar o compilador antes de instalar uma release que contenha esta mudança:
rustup toolchain install 1.99.0 --profile minimal
cargo +1.99.0 install mangostudio --lockedContribuidores podem deixar o rustup instalar o compilador do rust-toolchain.toml. Remova ou
atualize um override antigo de RUSTUP_TOOLCHAIN ou uma seleção explícita de
cargo +1.96.0/cargo +1.97.0. Consumidores do SDK Rust também precisam atualizar o compilador
local e do CI antes de adotar uma release com o novo mínimo. Os metadados de crates já publicados
continuam iguais; quem precisa de um compilador antigo pode manter uma versão publicada cujo
rust-version o aceite. O SDK TypeScript e os usuários dos binários pré-compilados do app não
passam a precisar de um compilador Rust local. cargo binstall não compila quando encontra um
arquivo pré-compilado compatível, mas a compilação do launcher como fallback exige Rust 1.99.0
ou mais recente.
O launcher mantém a versão da aplicação e a linha de releases com tags v*. O SDK Rust mantém a
versão dos pacotes do protocolo e a linha protocol-v*; compartilhar o compilador mínimo não
une suas versões. Trate isso como uma mudança intencional de compatibilidade de código-fonte nas
notas das próximas releases da aplicação e do protocolo que a incluírem. Gere cada changelog
pela preparação normal da sua linha de releases e revise a seção para incluir o requisito de
Rust 1.99 e a migração. Escolha versões de pacotes pelas regras de release existentes. Esta
mudança não altera versões de pacotes nem a versão do wire; changelogs gerados nunca são editados
manualmente.
Assinante confiável da release
Os workflows da aplicação e do protocolo verificam uma tag anotada e assinada
com SSH no commit do checkout antes de executar código da tag ou publicar em
qualquer canal. O verificador, sua versão do Bun e a política pública vêm de
main protegido, independentemente da tag autenticada. O verificador roda no
arquivo extraído confiável, com carregamento automático de .env desativado,
para impedir que a configuração Bun da candidata execute um preload antes da
autenticação. A política pública em .github/release-allowed-signers confia em julio@polycarpo.dev,
fingerprint ED25519 SHA256:2dqX4aQDayJuTK97HAXfcMlVrcfuPEsatwTAn4sxDSo, para
assinaturas Git. Uma tag sem assinatura, outro assinante, uma política ausente
ou uma tag apontando para outro commit falha no gate. O nome assinado da tag
deve corresponder ao ref e à versão da release, para impedir reutilização da
assinatura sob outro nome. A release manual da aplicação exige exatamente o
ref de tag v<versão>. O bypass de verificação da fonte não pula essas checagens.
A rotação da chave exige atualização revisada da
política pública; nenhuma chave privada fica no repositório. As checagens de
ancestralidade e a idempotência de reexecução continuam valendo. Os dry runs
testam tags temporárias confiáveis, sem assinatura e não confiáveis localmente,
sem publicá-las.
Contrato one-shot
Com os secrets abaixo configurados, a release é bun run release:prepare <versão>, um commit e o push de uma tag semver assinada (v0.2.0). O workflow
valida o lockstep de versão e o changelog pré-tag, gera todos os artefatos e
publica cada canal de forma independente. A tag carrega o próprio
CHANGELOG.md — nada é escrito de volta em main após a release.
| Secret | Usado por | Escopo |
|---|---|---|
NPM_TOKEN |
npm-publish (env opcional), npm-canary (repo) |
Token legado só quando workflow_dispatch define allow_legacy_npm_token=true; secret do repositório para canary |
DIST_REPOS_TOKEN |
homebrew, scoop |
Secret do environment release — PAT fine-grained com contents read/write no tap Homebrew e no bucket Scoop |
CARGO_REGISTRY_TOKEN |
cargo-publish (opcional) |
Token legado do crates.io usado só quando workflow_dispatch define allow_legacy_cargo_token=true |
(built-in GITHUB_TOKEN) |
github-release, docker, o canal canary, attestations |
Sem setup extra — releases por tag concedem packages: write para GHCR e id-token: write para OIDC do crates.io e do npm |
Environment release
As credenciais de publish estável vivem no GitHub Environment release, não
como secrets soltos no repositório:
- Branches e tags de deployment: restritas a tags
v*.*.*. - Reviewers obrigatórios: nenhum — o push de uma tag ainda libera a release sem intervenção.
- Jobs que o declaram:
github-release,docker,npm-publish,homebrew,scoopecargo-publish. Um novo canal de publish entra neste environment; não se adiciona um secret de repositório. - Canary fica de fora:
.github/workflows/canary.ymlpublica a cada push verde emmain, o que a regra de tag bloquearia. Continua usando oNPM_TOKENcom escopo de repositório. - Dispatch manual: como o environment é restrito a tags, um
workflow_dispatchderelease.ymlprecisa apontar para uma ref de tagv*.*.*(gh workflow run release.yml --ref v0.2.0), não para um branch.
Checklist de setup único
Complete uma vez por fork ou org antes do primeiro push de tag:
- Crie o tap Homebrew compartilhado
juliopolycarpo/homebrew-tapcom diretórioFormula/. - Crie o bucket Scoop compartilhado
juliopolycarpo/scoop-bucketcom diretóriobucket/. - Reserve o nome do crate
mangostudiono crates.io e gere um token de API só se ainda precisar do fallback legado temporário. - Configure Trusted Publishing no crates.io para o crate
mangostudio: Settings -> Trusted Publishing -> Add -> GitHub, repository ownerjuliopolycarpo, repository namemangostudio, workflow filenamerelease.yml, e deixe o campo de environment vazio. O jobcargo-publishdeclaraenvironment: release, mas configs do crates.io sem environment continuam batendo — só preencha o environment no crates.io se quiser exigir um de propósito. - Crie o GitHub Environment
release(regra de tagv*.*.*, sem reviewers) e adicioneDIST_REPOS_TOKENcomo secret do environment. MantenhaNPM_TOKENno environment só enquanto o escape hatchallow_legacy_npm_tokenainda puder ser necessário. Mantenha umNPM_TOKENcom escopo de repositório para o canary. MantenhaCARGO_REGISTRY_TOKENsó enquanto o escape hatchallow_legacy_cargo_tokenainda for necessário. Depois de uma release verde pelo environment, remova oDIST_REPOS_TOKENde nível de repositório. - Depois que uma release provar que
cargo-publishgerou o token de Trusted Publishing, revogue e removaCARGO_REGISTRY_TOKEN. - Configure Trusted Publishing no npm para
mangostudioe cada pacote@mangostudio/cli-*(Settings -> Trusted Publisher): repositóriojuliopolycarpo/mangostudio, workflowrelease.yml, environmentrelease, açãonpm publish. O npm permite até 10 trusted publishers por pacote, então uma segunda linha poderia registrar o workflow chamador do canary,ci.yml(nãocanary.yml) — mas.github/workflows/canary.ymlsempre exige e usa oNPM_TOKENdo repositório hoje; registrar essa linha não remove a necessidade do token até que o próprio workflow migre para OIDC (fora do escopo aqui). Não ative “disallow tokens” no npm enquanto o canary ainda precisar desse token. - Após a primeira release estável verde via OIDC no npm, remova o
NPM_TOKENdo environmentreleasese o caminho legado não for mais necessário. - Após o primeiro push no GHCR, defina a visibilidade do pacote
ghcr.io/juliopolycarpo/mangostudiocomo public nas configurações de pacotes do GitHub. - Não é preciso token extra nem ajuste de proteção de branch para o changelog:
CHANGELOG.mdentra emmainno commit de preparação da release (bun run release:prepare) antes do push da tag, e o workflow de release apenas verifica que ele está lá.
Nomenclatura de assets de release
Todo canal downstream (Homebrew, Scoop, launcher Cargo, os scripts de
instalação em scripts/install/) codifica estes nomes públicos de assets. Não os
renomeie sem atualizar todos os templates e instaladores na mesma release.
| Asset | Notas |
|---|---|
mangostudio-<version>-<platform>.tar.gz |
Plataformas Linux e macOS |
mangostudio-<version>-<platform>.zip |
Plataformas Windows |
mangostudio-<version>-frontend-dist.tar.gz |
Bundle do frontend apenas |
SHA256SUMS |
Checksums de todos os assets acima |
Cada arquivo de plataforma tem raiz plana: mangostudio (ou
mangostudio.exe) e README.md — sem diretório de plataforma aninhado. O
binário embarca a UI do frontend; nenhum diretório de assets vizinho é
necessário em tempo de execução.
Os scripts de instalação são assets de release nos dois canais, copiados sem
alteração de scripts/install/ para release-assets/ e listados em
SHA256SUMS ao lado dos arquivos que instalam. As URLs canônicas são
https://github.com/juliopolycarpo/mangostudio/releases/latest/download/install.sh
e .../install.ps1; ambos baixam os arquivos de plataforma acima e os verificam
contra SHA256SUMS. O binário do hub embute os mesmos bytes e os executa
localmente em mangostudio upgrade (--local, --use, --prune,
--uninstall); o dry-run compara mangostudio __installer sh com o arquivo do
repositório para que os dois não divirjam.
Como o binário do runtime é construído
O hub é compilado com Bun; mangostudio-runtime é o binário cargo de
crates/mangostudio-runtime. distribution-build.yml chama
.github/workflows/runtime-build.yml, que constrói cada um dos oito alvos num
runner capaz de linká-lo e o envia como artefato
runtime-<source-sha>-<platform-id>. O job de empacotamento baixa esses
artefatos em .mango/runtime-prebuilt/ e roda
bun run build --binary --runtime-dir .mango/runtime-prebuilt, que compila
apenas os hubs e copia cada runtime ao lado do seu hub. Nada depois de
.mango/out/<platform>/ mudou: arquivos, assets crus, SHA256SUMS, pacotes npm,
imagens Docker e instaladores veem os mesmos dois arquivos de antes.
| Plataformas | Runner | Alvo Rust / toolchain |
|---|---|---|
linux-x64, linux-arm64 |
ubuntu-latest |
<arch>-unknown-linux-gnu.2.17 via cargo-zigbuild (piso glibc) |
linux-x64-musl, linux-arm64-musl |
ubuntu-latest |
<arch>-unknown-linux-musl via cargo-zigbuild (estático) |
darwin-x64, darwin-arm64 |
macos-latest |
x86_64-apple-darwin / aarch64-apple-darwin, Xcode |
windows-x64, windows-arm64 |
windows-latest |
x86_64-pc-windows-msvc / aarch64-pc-windows-msvc, MSVC |
- Piso de glibc: 2.17. O hub compilado com Bun já exige
GLIBC_2.17(seu símbolo versionado mais alto nas duas arquiteturas), então linkar o runtime no mesmo piso não acrescenta requisito ao par.GLIBC_FLOORemscripts/lib/runtime-build.tsé o único lugar que o nomeia. - Dependências C e assembly.
ringerquickjs-syssão compilados por alvo;aws-lcnão está no grafo. No Linux o zig é o toolchain C deles;windows-arm64é compilado de forma cruzada na imagem Windows x64, que traz as bibliotecas MSVC ARM64 e o clang de que oringprecisa. - CRT estático no Windows.
.cargo/config.tomllinka o runtime C do MSVC estaticamente (+crt-static). Um CRT dinâmico importaVCRUNTIME140.dll, que vem do Visual C++ Redistributable e não do Windows, e o runtime não iniciaria numa máquina sem ele. A verificação de staging rejeita qualquer runtime Windows que importe DLLsVCRUNTIME*/MSVCP*. - Perfil de release. O
[profile.release]do workspace constrói o runtime comopt-level = 3, LTO fat, uma codegen unit estrip = true; veja runtime-metrics.md para o custo em tamanho e tempo de build. Ele mantémpanic = "unwind"de propósito: o isolamento de panics dos handlers captura o unwind, então o crate se recusa a compilar companic = "abort". - Pins. Rust vem de
rust-toolchain.toml; zig é baixado com versão e SHA-256 fixos (ZIG_VERSION/ZIG_SHA256); cargo-zigbuild é instalado em versão fixa pelotaiki-e/install-action, que verifica o checksum. - Versão. Cada runtime recebe a versão da release em tempo de compilação via
MANGOSTUDIO_RELEASE_VERSIONe a imprime sozinha em--version, a linha que o doctor do hub e o provisionamento WSL/SSH comparam literalmente. - Verificações.
scripts/build-runtime.tse o staging com--runtime-dirleem o cabeçalho de cada binário (scripts/lib/executable-header.ts): formato, CPU, loader glibc ou binário estático, e nenhum símboloGLIBC_acima do piso. Um runtime executável na máquina também precisa responder--versioncom a versão da release.
Localmente, bun run build --binary --platform <host> roda
cargo build --release --locked -p mangostudio-runtime --target <triple> para o
alvo do próprio host. Qualquer outro alvo precisa de um runtime pré-construído em
--runtime-dir <dir> (ou RUNTIME_DIR), no layout
<dir>/<platform-id>/mangostudio-runtime[.exe] — por exemplo, de
bun run build:runtime --platform linux-arm64 --zig --out <dir>. Um arquivo
ausente é um erro que o nomeia; o build nunca usa outro runtime no lugar.
Fonte da versão
Existe uma versão de release. A version do package.json raiz é canônica;
a variável de ambiente VERSION (definida pelo workflow a partir da tag) a
sobrescreve. bun run check:versions valida lockstep entre root, workspaces,
packages/cli, crates/mangostudio-launcher/Cargo.toml e a entrada do launcher
no Cargo.lock raiz. A versão do protocolo no mesmo lockfile continua independente.
Canal canary
Todo commit que entra verde em main é publicado como canary. O job canary
em .github/workflows/ci.yml é gated no CI / Gate agregado e em um push para
main, então o commit que acabou de ficar verde é a fonte do canary — sem
trigger separado. Ele chama o reutilizável .github/workflows/canary.yml.
Este repositório tem releases imutáveis habilitadas, então o canary não é
mais uma pre-release rolling: ele corta uma release por commit verde,
com tag v<versão> usando a versão com SHA que os binários reportam (ex.:
v0.1.1-canary.abc1234). Os assets não são mais renomeados para uma versão
rolling — mantêm o nome de build, por exemplo
mangostudio-0.1.1-canary.abc1234-linux-x64.tar.gz. A pre-release rolling
v<root>-canary publicada antes de 2026-09-16 fica congelada e imutável;
nada a republica, e hubs instalados a partir dela continuam resolvendo-a.
Consuma builds canary via npm ou pelos arquivos da pre-release mais recente:
# npm — a dist-tag `canary`; `latest` nunca é tocado
npm install -g mangostudio@canary
# GitHub Releases — os arquivos da pre-release canary mais recente e SHA256SUMS
gh release download --repo juliopolycarpo/mangostudio \
"$(gh release list --repo juliopolycarpo/mangostudio --json tagName,isPrerelease \
--jq 'map(select(.isPrerelease and (.tagName | test("-canary")))) | first | .tagName')"
# Um hub instalado — canary mais recente, ou um commit específico
mangostudio upgrade --canary
mangostudio upgrade --canary 1234abc- GitHub Releases (
github-release-canary): uma pre-releasev<versão>por commit verde emmain, com seus assets eSHA256SUMS. Releases antigas são podadas porscripts/release/prune-canary-releases.ts(mantém 14 por padrão), junto com suas tags — o rulesetrelease tagsexcluirefs/tags/v*-canary.*; qualquer outra tagv*continua sem poder ser apagada nem movida. A janela conta apenas releases publicadas: um upload interrompido nunca desaloja um build que os hubs ainda usam, e o draft que ele deixou para trás é apagado de qualquer forma. - npm (
npm-canary):mangostudio@<versão>na dist-tagcanary, entãolatestnunca aponta para um canary.
Nada reconstrói mais uma tag canary a partir de uma versão: install.sh --canary, install.ps1 -Canary e a verificação de atualização do hub sempre
resolvem a pre-release canary mais recente na lista de releases do GitHub.
Cada canal é independente e idempotente (igual à release por tag): uma falha não
bloqueia a outra e Re-run failed jobs re-executa só o canal que falhou (o
job canary-summary escreve uma tabela ✅/❌ por canal). O grupo de concorrência
canary-publish cancela runs superadas em voo, então a dist-tag npm sempre
acompanha o commit verde mais recente; versões npm por commit são únicas,
então um run cancelado nunca deixa um half-publish conflitante.
Ressalvas:
ghcr.io/juliopolycarpo/mangostudio:canarye:canary-<sha7>não são mais atualizados. As tags GHCR canary existentes permanecem no registry até serem limpas manualmente. Releases por tag ainda publicam o conjunto completo de imagens Docker.- O workflow não publica mais canaries no crates.io. O launcher
<root>-canaryatualmente publicado continua funcionando porque aponta para a pre-releasev<root>-canarycongelada — releases imutáveis significam que os assets dela nunca podem ser substituídos, então a URL do launcher continua resolvendo mesmo que essa release nunca mais seja republicada. Nenhum crate canary novo é publicado, e um bump futuro de versão raiz não ganha um<root>-canarya menos que o publish canary no crates.io seja reintroduzido de propósito. - Tags
v<version>-canary.<sha7>continuam excluídas do trigger de release (!v*-canary*) como guarda para tags antigas ou manuais.
Cortar uma release
Releases são orientadas por tag. A partir de um main atualizado:
-
Prepare a release — um comando faz bump de todos os manifests lockstep, regenera
CHANGELOG.mdcom git-cliff e re-executacheck:versions --expectcomo autoverificação:bun run release:prepare 0.2.0 -
Faça commit da árvore preparada como o commit de preparação da release (
cliff.tomlignora commitschore(release), então ele nunca reentra em um changelog futuro):git add -A && git commit -s -S -m "chore(release): v0.2.0" -
Depois que esse commit entrar em
main(pelo fluxo normal de PR), crie e faça push da tag (deve coincidir com a versão commitada):git tag -s v0.2.0 -m "v0.2.0" git push origin v0.2.0
Uma tag cujo commit não tenha a seção de changelog ou uma versão em lockstep
falha no job prepare antes de qualquer artefato ser produzido, apontando a
correção (bun run release:prepare). O mesmo job recusa liberar um commit que
não seja ancestral de origin/main ou cujo CI / Gate agregado não tenha
concluído com success. O gate é resolvido pela própria run de push em main do
ci.yml para o commit, e não pelo nome da check run, porque cargo-shim.yml e
release-dry-run.yml também expõem um job chamado Gate. Push de tag não pode
pular esse gate de proveniência, e um gate ainda em execução também bloqueia —
espere o CI ficar verde em main antes de empurrar a tag. Só
workflow_dispatch pode definir allow_unverified_source=true como escape
deliberado (registrado como aviso no workflow); use quando a run de CI sumiu da
API mas o commit ainda é o da release, e mencione o bypass nas notas de release.
Re-run failed jobs é sempre seguro: jobs de canal são independentes — uma
falha nunca bloqueia as outras. Versões npm já publicadas são ignoradas, e um
re-run contra uma GitHub Release já publicada verifica os assets em vez de
reenviá-los — releases imutáveis não podem receber clobber. Para durabilidade
extra: artefatos de build retêm por
30 dias, o job docker retenta cada push multi-arch contra o artefato de
distribuição verificado, e o job release-summary (sempre executa) escreve uma
tabela ✅/❌ por canal mais o resultado de auth/provenance do npm e do crates.io.
O workflow executa 14 jobs: prepare, build, verify-build, github-release, docker,
verify-image, npm-publish, homebrew, scoop, cargo-publish,
verify-release, verify-cargo, verify-homebrew e
release-summary. Veja a
versão em inglês para a tabela
completa de jobs e detalhes por canal (npm, Docker, Homebrew, Scoop, crates.io).