release 1.3.2: update from Gitea, Linux-only CLI, desktop app for Linux and Windows
CI / Formatação e clippy (push) Successful in 2m5s
CI / Auditoria de dependências (push) Successful in 10s
CI / Compila o app desktop (Linux) (push) Successful in 1m14s
CI / Testes (Linux) (push) Successful in 4m36s

The updater reads latest.json from Gitea, built by scripts/make-latest-json.py
against the Gitea API (no gh CLI). Release workflow builds the Linux CLI and
the Linux+Windows desktop app, signs the updater artifacts and attaches
everything to the release.

Needs the repo secrets TAURI_SIGNING_PRIVATE_KEY and
TAURI_SIGNING_PRIVATE_KEY_PASSWORD, which I could not set (secret-store writes
are reserved for the user) - set them from ~/.tauri/guara-updater.key and
.pass before pushing a tag.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Lucas RayanandClaude Sonnet 5.5 committed 2026-10-30 13:41:21 -03:00
1 parent 8ba3151512
commit b47708126e
14 files changed
+319 -46

No files matched your search

+106 -5
View File
@@ -1,14 +1,25 @@
name: Release
# Cria a entrada em "Releases" quando uma tag v* é enviada, com as notas da
# seção correspondente do CHANGELOG.md (scripts/release-notes.sh).
# Quando uma tag v* é enviada: cria a entrada em "Releases" com as notas do
# CHANGELOG.md, compila o CLI (Linux x86_64 e aarch64) e o app desktop (Linux
# e Windows, este por compilação cruzada com cargo-xwin), e monta o
# latest.json que o atualizador do app lê.
#
# Só as notas: os binários para macOS e Windows exigiam runners que o Gitea daqui
# não tem (o único runner é Linux arm64), então não são gerados neste fluxo.
# Sem macOS: o Tauri precisa do SDK e da assinatura da Apple, presos a
# hardware Apple e a uma conta de desenvolvedor, que este runner não tem.
#
# Segredos de usuário necessários: REGISTRY_TOKEN (não usado aqui, fica para o
# registro de containers de outros projetos), TAURI_SIGNING_PRIVATE_KEY e
# TAURI_SIGNING_PRIVATE_KEY_PASSWORD (assinam os pacotes para o atualizador).
on:
push:
tags: ['v*']
env:
CARGO_TERM_COLOR: always
CARGO_HOME: /opt/hostedtoolcache/cargo
RUSTUP_HOME: /opt/hostedtoolcache/rustup
jobs:
release:
name: Cria a release
@@ -23,9 +34,99 @@ jobs:
TOKEN: ${{ gitea.token }}
API: ${{ gitea.server_url }}/api/v1/repos/${{ gitea.repository }}
run: |
# Uma execução repetida não recria a release.
if curl -fsS -H "Authorization: token $TOKEN" "$API/releases/tags/$TAG" >/dev/null 2>&1; then
echo "a release $TAG já existe"; exit 0
fi
scripts/release-notes.sh "$TAG" > notes.md
pre=false; case "$TAG" in *-*) pre=true ;; esac
jq -n --arg tag "$TAG" --arg name "Guará ${TAG#v}" --rawfile body notes.md --argjson pre "$pre" \
'{tag_name:$tag, name:$name, body:$body, prerelease:$pre}' > release.json
curl -fsS -X POST "$API/releases" -H "Authorization: token $TOKEN" \
-H 'Content-Type: application/json' -d @release.json
-H 'Content-Type: application/json' -d @release.json >/dev/null
cli:
name: CLI (${{ matrix.platform }})
needs: release
runs-on: ubuntu-latest
env:
CARGO_TARGET_DIR: /opt/hostedtoolcache/cargo-target/guara-release-cli
strategy:
fail-fast: false
matrix:
include:
- { platform: linux-aarch64, target: aarch64-unknown-linux-gnu, apt: "" }
- { platform: linux-x86_64, target: x86_64-unknown-linux-gnu, apt: "gcc-x86-64-linux-gnu" }
steps:
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
- name: Linker desta plataforma
if: ${{ matrix.apt != '' }}
run: |
apt-get update -qq
apt-get install -y -qq ${{ matrix.apt }}
- name: Compila e empacota
run: scripts/package-cli.sh "${{ gitea.ref_name }}" "${{ matrix.platform }}" "${{ matrix.target }}" | tee packages.txt
- name: Anexa à release
env:
TOKEN: ${{ gitea.token }}
API: ${{ gitea.server_url }}/api/v1/repos/${{ gitea.repository }}
run: scripts/release-upload.sh "${{ gitea.ref_name }}" $(cat packages.txt)
gui:
name: App desktop (${{ matrix.platform }})
needs: release
runs-on: ubuntu-latest
env:
CARGO_TARGET_DIR: /opt/hostedtoolcache/cargo-target/guara-release-gui
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
strategy:
fail-fast: false
matrix:
platform: [linux, windows]
steps:
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@stable
- uses: actions/setup-node@v7
with:
node-version: 22
- name: Dependências do Tauri (Linux)
if: matrix.platform == 'linux'
timeout-minutes: 25
run: scripts/apt-install.sh ~/apt-cache libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev patchelf
- name: Ferramentas para compilação cruzada (Windows)
if: matrix.platform == 'windows'
run: |
apt-get update -qq
apt-get install -y -qq nsis lld llvm clang
command -v cargo-xwin >/dev/null || cargo install cargo-xwin --locked
- name: Compila e empacota
run: scripts/build-gui.sh "${{ matrix.platform }}"
- name: Anexa à release
env:
TOKEN: ${{ gitea.token }}
API: ${{ gitea.server_url }}/api/v1/repos/${{ gitea.repository }}
run: scripts/release-upload.sh "${{ gitea.ref_name }}" dist-gui/*
atualizador:
name: Monta o latest.json
needs: gui
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- name: latest.json
env:
TAG: ${{ gitea.ref_name }}
TOKEN: ${{ gitea.token }}
API: ${{ gitea.server_url }}/api/v1/repos/${{ gitea.repository }}
run: |
scripts/release-notes.sh "$TAG" > notes.md
python3 scripts/make-latest-json.py "$TAG" notes.md latest.json
- name: Anexa à release
env:
TOKEN: ${{ gitea.token }}
API: ${{ gitea.server_url }}/api/v1/repos/${{ gitea.repository }}
run: scripts/release-upload.sh "${{ gitea.ref_name }}" latest.json
+4
View File
@@ -17,3 +17,7 @@ node_modules/
*.img
*.guara-session.json
.DS_Store
# Pacotes gerados por scripts/package-cli.sh
/guara-v*
/dist-gui
+17
View File
@@ -4,6 +4,23 @@
Format based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versions follow SemVer.
## [1.3.2] - 2026-10-30
The project moved from GitHub to Gitea, and the update check followed it.
### Changed
- **Updates now come from Gitea.** The desktop app's update card reads
`latest.json` from `git.lucasrguerra.dev.br` instead of GitHub Releases.
Versions up to 1.3.1 still look at GitHub, which is no longer reachable, so
they cannot find this release: download 1.3.2 once, and later updates are
automatic.
- The CLI is published for Linux x86_64 and aarch64 only, built by the Gitea CI
when a `v*` tag is pushed, with a `.sha256` next to each package. **There is
no Windows CLI any more.** The desktop app still ships for Linux (AppImage,
`.deb`) and Windows (NSIS `.exe`, cross-compiled from Linux). There is no
macOS build: it needs Apple's SDK and code signing, which this project's CI
(one Linux runner) cannot provide.
## [1.3.1] - 2026-10-23
### Changed
+17
View File
@@ -4,6 +4,23 @@
Formato baseado em [Keep a Changelog](https://keepachangelog.com/pt-BR/1.1.0/); versões seguem SemVer.
## [1.3.2] - 2026-10-30
O projeto saiu do GitHub para o Gitea, e a verificação de atualização foi junto.
### Alterado
- **As atualizações agora vêm do Gitea.** O cartão de atualização do app
desktop lê o `latest.json` em `git.lucasrguerra.dev.br`, e não mais as
Releases do GitHub. As versões até a 1.3.1 ainda olham o GitHub, que não
responde mais, e por isso não enxergam esta release: baixe a 1.3.2 uma vez, e
as próximas atualizações são automáticas.
- O CLI é publicado só para Linux x86_64 e aarch64, compilado pelo CI do Gitea
quando uma tag `v*` é enviada, com um `.sha256` ao lado de cada pacote. **Não
há mais CLI para Windows.** O app desktop continua saindo para Linux
(AppImage, `.deb`) e Windows (`.exe` NSIS, compilado a partir do Linux). Não
há build para macOS: exige o SDK e a assinatura da Apple, que o CI deste
projeto (um único runner Linux) não tem.
## [1.3.1] - 2026-10-23
### Alterado
Generated
+2 -2
View File
@@ -416,7 +416,7 @@ dependencies = [
[[package]]
name = "guara-cli"
version = "1.3.1"
version = "1.3.2"
dependencies = [
"clap",
"ctrlc",
@@ -429,7 +429,7 @@ dependencies = [
[[package]]
name = "guara-core"
version = "1.3.1"
version = "1.3.2"
dependencies = [
"aes",
"argon2",
+1 -1
View File
@@ -3,7 +3,7 @@ resolver = "2"
members = ["crates/guara-core", "crates/guara-cli"]
[workspace.package]
version = "1.3.1"
version = "1.3.2"
edition = "2021"
license = "MIT OR Apache-2.0"
repository = "https://github.com/lucasrguerra/guara"
+2 -2
View File
@@ -3,10 +3,10 @@
[package]
name = "guara-gui"
description = "Guará: interface gráfica de recuperação de arquivos"
version = "1.3.1"
version = "1.3.2"
edition = "2021"
license = "MIT OR Apache-2.0"
repository = "https://github.com/lucasrguerra/guara"
repository = "https://git.lucasrguerra.dev.br/lucasrguerra/guara"
rust-version = "1.80"
[workspace]
+1 -1
View File
@@ -39,7 +39,7 @@ async fn update_check(app: tauri::AppHandle) -> Result<Option<UpdateInfo>, Strin
Ok(found.map(|u| UpdateInfo {
current: u.current_version.clone(),
notes: u.body.clone().unwrap_or_default(),
url: format!("https://github.com/lucasrguerra/guara/releases/tag/v{}", u.version),
url: format!("https://git.lucasrguerra.dev.br/lucasrguerra/guara/releases/tag/v{}", u.version),
version: u.version,
auto: self_update_possible(),
}))
+2 -2
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "Guara",
"version": "1.3.1",
"version": "1.3.2",
"identifier": "dev.guara.app",
"build": {
"frontendDist": "../ui"
@@ -41,7 +41,7 @@
"updater": {
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDhENzRCRUU5NEI3QTAxQzgKUldUSUFYcEw2YjUwamFzMnpXM3ZKTEZPN1JIeXdLL3Q5SEF1M1FxQ284WFM0UWNsdlAyNUloWTEK",
"endpoints": [
"https://github.com/lucasrguerra/guara/releases/latest/download/latest.json"
"https://git.lucasrguerra.dev.br/lucasrguerra/guara/releases/latest/download/latest.json"
],
"windows": {
"installMode": "passive"
+70
View File
@@ -0,0 +1,70 @@
#!/usr/bin/env bash
# Compila o app desktop (Tauri) e deixa os pacotes em dist-gui/.
#
# scripts/build-gui.sh linux # AppImage e .deb
# scripts/build-gui.sh windows # instalador NSIS, compilado a partir do Linux
# scripts/build-gui.sh windows-docker # o mesmo, num container, sem instalar nada
#
# O updater do Tauri assina cada pacote (createUpdaterArtifacts: true), então
# TAURI_SIGNING_PRIVATE_KEY e TAURI_SIGNING_PRIVATE_KEY_PASSWORD precisam estar
# no ambiente. Sem eles no ambiente, e com uma chave local em ~/.tauri (gerada
# por `tauri signer generate`), este script a carrega sozinho.
#
# O Windows usa cargo-xwin (SDK do Windows baixado na hora) e o NSIS; o Tauri
# documenta isso como compilação cruzada experimental. Precisa de: nsis, lld,
# llvm, clang e `cargo install cargo-xwin`. O .msi não sai daqui (o WiX é só do
# Windows).
set -euo pipefail
cd "$(dirname "$0")/.."
platform=${1:?uso: build-gui.sh <linux|windows|windows-docker>}
tauri() { npx --yes @tauri-apps/cli@2 "$@"; }
if [ -z "${TAURI_SIGNING_PRIVATE_KEY:-}" ] && [ -f "$HOME/.tauri/guara-updater.key" ]; then
export TAURI_SIGNING_PRIVATE_KEY="$(cat "$HOME/.tauri/guara-updater.key")"
[ -f "$HOME/.tauri/guara-updater.pass" ] && export TAURI_SIGNING_PRIVATE_KEY_PASSWORD="$(cat "$HOME/.tauri/guara-updater.pass")"
fi
: "${TAURI_SIGNING_PRIVATE_KEY:?defina TAURI_SIGNING_PRIVATE_KEY (ou gere a chave local com 'tauri signer generate')}"
if [ "$platform" = windows-docker ]; then
docker volume create guara-cargo >/dev/null
docker volume create guara-rustup >/dev/null
mkdir -p dist-gui
exec docker run --rm -v "$PWD":/src -v guara-cargo:/root/.cargo -v guara-rustup:/root/.rustup \
-w /src -e HOST_UID="$(id -u)" -e HOST_GID="$(id -g)" \
-e TAURI_SIGNING_PRIVATE_KEY -e TAURI_SIGNING_PRIVATE_KEY_PASSWORD \
ubuntu:24.04 bash -c '
set -e
export DEBIAN_FRONTEND=noninteractive
apt-get update -qq
apt-get install -y -qq curl ca-certificates build-essential pkg-config nsis lld llvm clang nodejs npm unzip >/dev/null
[ -x /root/.cargo/bin/rustup ] || curl -fsSL https://sh.rustup.rs | sh -s -- -y --profile minimal >/dev/null
export PATH=/root/.cargo/bin:$PATH
command -v cargo-xwin >/dev/null || cargo install cargo-xwin --locked
scripts/build-gui.sh windows
chown -R "$HOST_UID:$HOST_GID" dist-gui apps/gui/src-tauri/target 2>/dev/null || true'
fi
mkdir -p dist-gui
cd apps/gui/src-tauri
[ -f icons/icon.ico ] || tauri icon icons/source.png -o icons
case "$platform" in
linux)
tauri build --bundles appimage,deb
bundle=target/release/bundle
cp "$bundle"/appimage/*.AppImage "$bundle"/deb/*.deb ../../../dist-gui/
cp "$bundle"/appimage/*.AppImage.sig ../../../dist-gui/ 2>/dev/null || true
;;
windows)
rustup target add x86_64-pc-windows-msvc >/dev/null
tauri build --runner cargo-xwin --target x86_64-pc-windows-msvc --bundles nsis
bundle=target/x86_64-pc-windows-msvc/release/bundle/nsis
cp "$bundle"/*-setup.exe ../../../dist-gui/
cp "$bundle"/*-setup.exe.sig ../../../dist-gui/ 2>/dev/null || true
;;
*) echo "plataforma desconhecida: $platform" >&2; exit 2 ;;
esac
ls -1 ../../../dist-gui
Regular → Executable
+26 -19
View File
@@ -1,29 +1,37 @@
#!/usr/bin/env python3
"""Monta o latest.json que o atualizador do app lê (Tauri updater), a partir dos pacotes e das
assinaturas `.sig` já enviados à release.
assinaturas `.sig` já enviados à release no Gitea.
Roda no job de publicação, depois que todos os jobs de empacotamento terminaram. Montar o arquivo
Roda no job de publicação, depois que os jobs de empacotamento terminaram. Montar o arquivo
aqui, em um passo só, evita a corrida de quem tenta atualizá-lo em paralelo por plataforma.
Uso: make-latest-json.py <tag> <notas.md> <saida.json> (usa `gh` e GH_REPO/GITHUB_REPOSITORY)
Uso: make-latest-json.py <tag> <notas.md> <saida.json>
Precisa de API (URL do repositório na API do Gitea) e TOKEN no ambiente.
"""
import json
import os
import subprocess
import sys
import tempfile
import urllib.request
from datetime import datetime, timezone
# (chaves de plataforma, padrão do nome do pacote). {v} = versão sem o "v".
# Sem macOS: não há runner para compilar essa plataforma.
PLATFORMS = [
(["linux-x86_64", "linux-x86_64-appimage"], "Guara_{v}_amd64.AppImage"),
(["windows-x86_64", "windows-x86_64-nsis"], "Guara_{v}_x64-setup.exe"),
(["windows-x86_64-msi"], "Guara_{v}_x64_en-US.msi"),
(["darwin-aarch64", "darwin-aarch64-app"], "Guara_{v}_aarch64.app.tar.gz"),
(["darwin-x86_64", "darwin-x86_64-app"], "Guara_{v}_x64.app.tar.gz"),
]
def api_get(path):
req = urllib.request.Request(f"{os.environ['API']}{path}", headers={"Authorization": f"token {os.environ['TOKEN']}"})
return json.load(urllib.request.urlopen(req))
def api_get_text(url):
req = urllib.request.Request(url, headers={"Authorization": f"token {os.environ['TOKEN']}"})
return urllib.request.urlopen(req).read().decode()
def build(version, notes, assets, pub_date):
"""`assets` = {nome_do_pacote: (url, assinatura)}. Devolve o dicionário do latest.json.
@@ -42,20 +50,19 @@ def build(version, notes, assets, pub_date):
def main():
tag, notes_path, out = sys.argv[1:4]
version = tag.lstrip("v")
repo = os.environ.get("GH_REPO") or os.environ["GITHUB_REPOSITORY"]
rel = json.loads(subprocess.check_output(["gh", "release", "view", tag, "--repo", repo, "--json", "assets"]))
urls = {a["name"]: a["url"] for a in rel["assets"]}
rel = api_get(f"/releases/tags/{tag}")
by_name = {a["name"]: a for a in rel["assets"]}
assets = {}
with tempfile.TemporaryDirectory() as d:
for _, pattern in PLATFORMS:
name = pattern.format(v=version)
if name in urls and name + ".sig" in urls:
subprocess.check_call(["gh", "release", "download", tag, "--repo", repo, "-p", name + ".sig", "-D", d])
assets[name] = (urls[name], open(os.path.join(d, name + ".sig")).read())
for _, pattern in PLATFORMS:
name = pattern.format(v=version)
sig_name = name + ".sig"
if name in by_name and sig_name in by_name:
sig = api_get_text(by_name[sig_name]["browser_download_url"])
assets[name] = (by_name[name]["browser_download_url"], sig)
data = build(version, open(notes_path, encoding="utf-8").read(), assets,
datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"))
# Sem o Linux (AppImage) o atualizador do dono do projeto não funciona: não publica pela metade.
for must in ("linux-x86_64", "windows-x86_64", "darwin-aarch64", "darwin-x86_64"):
# Sem Linux e Windows o atualizador não funciona para ninguém: não publica pela metade.
for must in ("linux-x86_64", "windows-x86_64"):
if must not in data["platforms"]:
sys.exit(f"faltam o pacote ou a assinatura de {must}; confira se TAURI_SIGNING_PRIVATE_KEY está nos segredos")
with open(out, "w", encoding="utf-8") as f:
+36
View File
@@ -0,0 +1,36 @@
#!/usr/bin/env bash
# Compila o CLI para uma plataforma e empacota: guara-<tag>-<plataforma>.tar.gz
# com um .sha256 ao lado.
#
# scripts/package-cli.sh v1.3.2 linux-x86_64 x86_64-unknown-linux-gnu
#
# Plataformas: linux-x86_64 e linux-aarch64. O alvo diferente da máquina de
# build usa o linker cruzado correspondente (gcc-x86-64-linux-gnu). Escreve os
# arquivos na raiz do repositório e imprime os nomes.
set -euo pipefail
cd "$(dirname "$0")/.."
tag=${1:?uso: package-cli.sh <tag> <plataforma> <alvo-rust>}
platform=${2:?uso: package-cli.sh <tag> <plataforma> <alvo-rust>}
target=${3:?uso: package-cli.sh <tag> <plataforma> <alvo-rust>}
if [ "$target" = x86_64-unknown-linux-gnu ] && [ "$(uname -m)" != x86_64 ]; then
export CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER=x86_64-linux-gnu-gcc
export CC_x86_64_unknown_linux_gnu=x86_64-linux-gnu-gcc
fi
rustup target add "$target" >/dev/null 2>&1 || true
cargo build --release --locked -p guara-cli --target "$target"
name="guara-${tag}-${platform}"
rm -rf "$name"; mkdir "$name"
cp "${CARGO_TARGET_DIR:-target}/${target}/release/guara" "$name/"
cp README.md LICENSE-MIT LICENSE-APACHE "$name/"
tar czf "$name.tar.gz" "$name"
asset="$name.tar.gz"
rm -rf "$name"
sha256sum "$asset" > "$asset.sha256"
echo "$asset"
echo "$asset.sha256"
+24
View File
@@ -0,0 +1,24 @@
#!/usr/bin/env bash
# Anexa arquivos a uma release do Gitea, trocando os de mesmo nome (assim uma
# execução repetida do CI não falha por anexo duplicado).
#
# API=https://git.exemplo.com/api/v1/repos/dono/repo TOKEN=... \
# scripts/release-upload.sh v1.2.3 arquivo1 arquivo2
set -euo pipefail
: "${API:?defina API (URL do repositório na API do Gitea)}"
: "${TOKEN:?defina TOKEN}"
tag=${1:?uso: release-upload.sh <tag> <arquivo>...}
shift
auth=(-H "Authorization: token $TOKEN")
id=$(curl -fsS "${auth[@]}" "$API/releases/tags/$tag" | jq -r .id)
for file in "$@"; do
name=$(basename "$file")
for old in $(curl -fsS "${auth[@]}" "$API/releases/$id/assets" | jq -r --arg n "$name" '.[] | select(.name == $n) | .id'); do
curl -fsS -X DELETE "${auth[@]}" "$API/releases/$id/assets/$old"
done
curl -fsS -X POST "${auth[@]}" -F "attachment=@$file" "$API/releases/$id/assets?name=$name" >/dev/null
echo "enviado: $name"
done
+11 -14
View File
@@ -8,37 +8,34 @@ spec = importlib.util.spec_from_file_location("m", os.path.join(os.path.dirname(
m = importlib.util.module_from_spec(spec)
spec.loader.exec_module(m)
BASE = "https://github.com/x/y/releases/download/v1.3.0/"
BASE = "https://git.lucasrguerra.dev.br/lucasrguerra/guara/releases/download/v1.3.2/"
def assets(version="1.3.0"):
def assets(version="1.3.2"):
names = [p.format(v=version) for _, p in m.PLATFORMS]
return {n: (BASE + n, f"assinatura-de-{n}\n") for n in names}
class LatestJson(unittest.TestCase):
def test_all_platforms_and_variants(self):
d = m.build("1.3.0", "### Novidades\n", assets(), "2026-10-23T12:00:00Z")
self.assertEqual(d["version"], "1.3.0")
d = m.build("1.3.2", "### Novidades\n", assets(), "2026-10-30T12:00:00Z")
self.assertEqual(d["version"], "1.3.2")
self.assertEqual(d["notes"], "### Novidades")
p = d["platforms"]
for k in ("linux-x86_64", "linux-x86_64-appimage", "windows-x86_64", "windows-x86_64-nsis",
"windows-x86_64-msi", "darwin-aarch64", "darwin-x86_64"):
for k in ("linux-x86_64", "linux-x86_64-appimage", "windows-x86_64", "windows-x86_64-nsis"):
self.assertIn(k, p)
# a chave genérica do Windows aponta para o instalador NSIS, não para o MSI
self.assertTrue(p["windows-x86_64"]["url"].endswith("x64-setup.exe"))
self.assertTrue(p["windows-x86_64-msi"]["url"].endswith(".msi"))
self.assertEqual(p["linux-x86_64"]["signature"], "assinatura-de-Guara_1.3.0_amd64.AppImage")
self.assertEqual(p["linux-x86_64"]["signature"], "assinatura-de-Guara_1.3.2_amd64.AppImage")
def test_missing_package_is_left_out(self):
a = assets()
del a["Guara_1.3.0_x64.app.tar.gz"]
d = m.build("1.3.0", "", a, "2026-10-23T12:00:00Z")
self.assertNotIn("darwin-x86_64", d["platforms"])
self.assertIn("darwin-aarch64", d["platforms"])
del a["Guara_1.3.2_x64-setup.exe"]
d = m.build("1.3.2", "", a, "2026-10-30T12:00:00Z")
self.assertNotIn("windows-x86_64", d["platforms"])
self.assertIn("linux-x86_64", d["platforms"])
def test_other_versions_do_not_leak(self):
d = m.build("1.3.0", "", assets("1.2.0"), "2026-10-23T12:00:00Z")
d = m.build("1.3.2", "", assets("1.2.0"), "2026-10-30T12:00:00Z")
self.assertEqual(d["platforms"], {})