chore(i18n): finish translating code comments and scripts to English
CI / Format and clippy (push) Successful in 1m57s
CI / Dependency audit (push) Successful in 13s
CI / Check desktop app (Linux) (push) Successful in 3m54s
CI / Tests (Linux) (push) Failing after 7m25s
Release / Create release (push) Successful in 6s
Release / CLI (linux-aarch64) (push) Successful in 1m48s
Release / CLI (linux-x86_64) (push) Successful in 2m39s
Release / Desktop app (linux-aarch64) (push) Successful in 10m9s
Release / Desktop app (linux-x86_64) (push) Successful in 12m20s
Release / Desktop app (windows) (push) Successful in 7m32s
Release / Build latest.json (push) Successful in 5s
CI / Format and clippy (push) Successful in 1m57s
CI / Dependency audit (push) Successful in 13s
CI / Check desktop app (Linux) (push) Successful in 3m54s
CI / Tests (Linux) (push) Failing after 7m25s
Release / Create release (push) Successful in 6s
Release / CLI (linux-aarch64) (push) Successful in 1m48s
Release / CLI (linux-x86_64) (push) Successful in 2m39s
Release / Desktop app (linux-aarch64) (push) Successful in 10m9s
Release / Desktop app (linux-x86_64) (push) Successful in 12m20s
Release / Desktop app (windows) (push) Successful in 7m32s
Release / Build latest.json (push) Successful in 5s
Completes the English-only standardization for code, comments, scripts and CI templates (UI/CLI-facing strings stay bilingual pt-BR/en, untouched). Covers the remaining crates/, apps/gui/ui/ and apps/gui/src-tauri/ comments, .gitea/ workflow and template comments, .githooks/ hooks, and the scripts/ source comments for the files already renamed in a prior commit (check.sh, generate-logo.py, install-hooks.sh). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VAyr8d7HuAdUGB6aA5xmDD
This commit is contained in:
1 parent
da212e6926
commit
6328220c60
129 files changed
+1961
-1834
No files matched your search
+14
-17
@@ -1,35 +1,32 @@
|
||||
#!/usr/bin/env bash
|
||||
# Instala pacotes do apt no CI sem depender do espelho estar rápido: usa os .deb em cache quando
|
||||
# existem e, se o apt travar ou falhar, tenta de novo com tempo limite por tentativa.
|
||||
# Installs apt packages in CI without depending on fast mirrors: reuses cached .deb
|
||||
# archives when present and retries with per-attempt timeouts if apt stalls or fails.
|
||||
#
|
||||
# Os passos de apt do CI já levaram de 13 a 28 minutos num dia de espelho lento, contra uns 35 s
|
||||
# no normal. O cache evita o download; o tempo limite evita ficar preso.
|
||||
#
|
||||
# Uso: scripts/apt-install.sh <pasta-do-cache> <pacote>...
|
||||
# Usage: scripts/apt-install.sh <cache-dir> <package>...
|
||||
set -euo pipefail
|
||||
[ $# -ge 2 ] || { echo "uso: $0 <pasta-do-cache> <pacote>..." >&2; exit 2; }
|
||||
[ $# -ge 2 ] || { echo "usage: $0 <cache-dir> <package>..." >&2; exit 2; }
|
||||
cache="$1"; shift
|
||||
pkgs=("$@")
|
||||
mkdir -p "$cache/partial"
|
||||
# O apt roda como root e deixa arquivos (lock, .deb) que o usuário do runner não lê; sem devolver a
|
||||
# posse, o actions/cache falha ao empacotar a pasta ("tar failed with exit code 2") e nunca grava.
|
||||
# apt runs as root and leaves files (lock, .deb) unreadable by non-root runner users;
|
||||
# restore ownership on exit so cache directories remain accessible.
|
||||
trap 'sudo chown -R "$(id -u):$(id -g)" "$cache" 2>/dev/null || true; rm -f "$cache/lock"' EXIT
|
||||
opts=(-o "Dir::Cache::archives=$cache" -y)
|
||||
|
||||
# 1) com .deb em cache e índice presente: instala direto, sem nem atualizar o índice do apt
|
||||
# 1) With cached .deb files and package index present: install directly without updating apt lists
|
||||
if [ -z "$(dpkg --print-foreign-architectures)" ] && ls "$cache"/*.deb >/dev/null 2>&1 && ls /var/lib/apt/lists/*Packages* >/dev/null 2>&1 && timeout 300 sudo apt-get "${opts[@]}" install "${pkgs[@]}"; then
|
||||
echo "instalado a partir do cache"
|
||||
echo "installed from cache"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# 2) sem cache (ou multiarch / incompleto): atualiza e instala, até 3 tentativas com tempo limite
|
||||
for tentativa in 1 2 3; do
|
||||
# 2) Cache miss (or multiarch / incomplete): update and install, up to 3 attempts with timeout
|
||||
for attempt in 1 2 3; do
|
||||
if timeout 240 sudo apt-get update && { [ -z "$(dpkg --print-foreign-architectures)" ] || timeout 600 sudo apt-get "${opts[@]}" upgrade; } && timeout 600 sudo apt-get "${opts[@]}" install "${pkgs[@]}"; then
|
||||
echo "instalado (tentativa $tentativa)"
|
||||
echo "installed (attempt $attempt)"
|
||||
exit 0
|
||||
fi
|
||||
echo "apt falhou ou passou do tempo (tentativa $tentativa de 3)" >&2
|
||||
sleep $((tentativa * 10))
|
||||
echo "apt failed or timed out (attempt $attempt of 3)" >&2
|
||||
sleep $((attempt * 10))
|
||||
done
|
||||
echo "não foi possível instalar: ${pkgs[*]}" >&2
|
||||
echo "failed to install: ${pkgs[*]}" >&2
|
||||
exit 1
|
||||
+21
-22
@@ -1,13 +1,12 @@
|
||||
#!/usr/bin/env node
|
||||
// Confere se o dicionário inglês cobre os textos da interface.
|
||||
// Verifies that the English dictionary covers all user-facing interface strings.
|
||||
//
|
||||
// A varredura é estática e sem dependências: lê os arquivos da interface
|
||||
// (JS e HTML), os rótulos que o núcleo em Rust envia para a tela (metadados,
|
||||
// tipos de arquivo e saúde do disco) e falha quando encontra uma string que
|
||||
// parece português e não tem tradução. Também recusa chaves duplicadas no
|
||||
// dicionário (num objeto JS a última vence em silêncio).
|
||||
// Static zero-dependency scanner: reads the UI files (JS and HTML) and the
|
||||
// labels emitted by the Rust core (metadata, file types, disk health), failing
|
||||
// whenever a string looks like Portuguese and lacks a translation. Also rejects
|
||||
// duplicate keys in the dictionary (in a JS object literal the last key wins silently).
|
||||
//
|
||||
// Uso: node scripts/check-ui-i18n.mjs
|
||||
// Usage: node scripts/check-ui-i18n.mjs
|
||||
|
||||
import { readFileSync, readdirSync } from 'node:fs';
|
||||
import { join, relative } from 'node:path';
|
||||
@@ -16,7 +15,7 @@ import { fileURLToPath } from 'node:url';
|
||||
const root = join(fileURLToPath(new URL('.', import.meta.url)), '..');
|
||||
const rel = (p) => relative(root, p).replaceAll('\\', '/');
|
||||
|
||||
// ---------------------------------------------------------------- dicionário
|
||||
// ---------------------------------------------------------------- dictionary
|
||||
|
||||
const dictPath = join(root, 'apps/gui/ui/i18n-en.js');
|
||||
const dictSource = readFileSync(dictPath, 'utf8');
|
||||
@@ -33,8 +32,8 @@ while ((m = keyRe.exec(dictSource))) {
|
||||
}
|
||||
|
||||
if (duplicates.length) {
|
||||
console.error('Chaves duplicadas em apps/gui/ui/i18n-en.js:');
|
||||
for (const d of duplicates) console.error(` ${JSON.stringify(d.key)} (linhas ${d.first} e ${d.again})`);
|
||||
console.error('Duplicate keys in apps/gui/ui/i18n-en.js:');
|
||||
for (const d of duplicates) console.error(` ${JSON.stringify(d.key)} (lines ${d.first} and ${d.again})`);
|
||||
process.exitCode = 1;
|
||||
}
|
||||
|
||||
@@ -53,7 +52,7 @@ function translated(text) {
|
||||
return patterns.some((re) => re.test(text));
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- heurística
|
||||
// ---------------------------------------------------------------- heuristic
|
||||
|
||||
const ACCENT = /[áàâãäéèêëíìîïóòôõöúùûüçÁÀÂÃÄÉÈÊËÍÌÎÏÓÒÔÕÖÚÙÛÜÇ]/;
|
||||
const WORDS = [
|
||||
@@ -92,13 +91,13 @@ function suspicious(text) {
|
||||
if (ALLOW.has(s) || ALLOW_EXACT.has(s)) return false;
|
||||
if (ALLOW_PREFIX.some((p) => s.startsWith(p))) return false;
|
||||
if (!isPortuguese(s)) return false;
|
||||
// técnico: seletores, classes, extensões, expressões
|
||||
// Technical tokens: CSS selectors, classes, extensions, expressions
|
||||
if (/^[.#\w-]+(\s*[,>+~]\s*[.#\w-]+)+$/.test(s)) return false;
|
||||
if (/^[a-z0-9_.:/@%{}$-]+$/i.test(s) && !ACCENT.test(s)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- extratores
|
||||
// ---------------------------------------------------------------- extractors
|
||||
|
||||
function jsStrings(source, file, baseLine = 1) {
|
||||
const out = [];
|
||||
@@ -212,14 +211,14 @@ function jsStrings(source, file, baseLine = 1) {
|
||||
function htmlStrings(source, file, baseLine = 1) {
|
||||
const out = [];
|
||||
const lineAt = (idx) => baseLine + source.slice(0, idx).split('\n').length - 1;
|
||||
// todo texto entre tags (inclusive começo e fim) — nós de texto reais
|
||||
// Text between tags (including leading and trailing text nodes)
|
||||
let last = 0;
|
||||
for (const m of source.matchAll(/<[^>]*>/g)) {
|
||||
out.push({ text: source.slice(last, m.index), file, line: lineAt(last) });
|
||||
last = m.index + m[0].length;
|
||||
}
|
||||
out.push({ text: source.slice(last), file, line: lineAt(last) });
|
||||
// atributos traduzíveis (com aspas simples ou duplas)
|
||||
// Translatable attributes (single or double quotes)
|
||||
for (const m of source.matchAll(/\b(placeholder|title|aria-label|alt|data-empty)\s*=\s*(?:"([^"]*)"|'([^']*)')/g)) {
|
||||
out.push({ text: m[2] ?? m[3], file, line: lineAt(m.index) });
|
||||
}
|
||||
@@ -229,16 +228,16 @@ function htmlStrings(source, file, baseLine = 1) {
|
||||
function rustLabels(source, file) {
|
||||
const out = [];
|
||||
const lineAt = (idx) => source.slice(0, idx).split('\n').length;
|
||||
// m.add("Rótulo", ...) e m.add(\n "Rótulo", ...)
|
||||
// m.add("Label", ...) and m.add(\n "Label", ...)
|
||||
for (const m of source.matchAll(/\.add\(\s*"([^"]+)"/g)) out.push({ text: m[1], file, line: lineAt(m.index) });
|
||||
// m.kind = "Tipo".into()
|
||||
// m.kind = "Kind".into()
|
||||
for (const m of source.matchAll(/\.kind\s*=\s*"([^"]+)"/g)) out.push({ text: m[1], file, line: lineAt(m.index) });
|
||||
// rótulos de listas (saúde do disco, tipos de arquivo) dentro de arrays de tuplas
|
||||
// List labels (disk health, file types) inside tuple arrays
|
||||
for (const m of source.matchAll(/\(\s*"([^"]{2,60})"\s*,\s*"(?:[^"]*)"\s*\)/g)) out.push({ text: m[1], file, line: lineAt(m.index) });
|
||||
return out;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- coleta
|
||||
// ---------------------------------------------------------------- collection
|
||||
|
||||
const uiFiles = readdirSync(join(root, 'apps/gui/ui'))
|
||||
.filter((f) => f.endsWith('.js') && f !== 'i18n-en.js')
|
||||
@@ -278,12 +277,12 @@ for (const { text, file, line } of found) {
|
||||
}
|
||||
|
||||
if (missing.length) {
|
||||
console.error('Textos em português sem tradução em apps/gui/ui/i18n-en.js:\n');
|
||||
console.error('Untranslated Portuguese strings in apps/gui/ui/i18n-en.js:\n');
|
||||
for (const x of missing) console.error(` ${x.file}:${x.line} ${JSON.stringify(x.text)}`);
|
||||
console.error(`\n${missing.length} texto(s) sem tradução.`);
|
||||
console.error(`\n${missing.length} untranslated string(s).`);
|
||||
process.exitCode = 1;
|
||||
}
|
||||
|
||||
if (!process.exitCode) {
|
||||
console.log(`i18n ok: ${found.length} textos verificados, ${Object.keys(EN).length} chaves no dicionário.`);
|
||||
console.log(`i18n ok: ${found.length} strings checked, ${Object.keys(EN).length} dictionary keys.`);
|
||||
}
|
||||
+41
-34
@@ -1,68 +1,75 @@
|
||||
#!/usr/bin/env bash
|
||||
# Verificação local: roda o mesmo que o CI roda, antes de você dar push.
|
||||
# Local verification: runs the same fast checks as CI before pushing.
|
||||
#
|
||||
# scripts/verificar.sh rápido (~1 min): formatação, clippy, tradução, scripts, workflows
|
||||
# scripts/verificar.sh --completo inclui os testes do workspace e o cargo audit
|
||||
# scripts/check.sh fast (~1 min): fmt, clippy, i18n, scripts, workflows, semver
|
||||
# scripts/check.sh --full also runs workspace tests and cargo audit
|
||||
#
|
||||
# O CI leva de 5 a 30 minutos por execução e roda em Linux; quase tudo que o faz falhar
|
||||
# aparece aqui em segundos. Se isto passa, o CI só deve revelar o que é próprio dele
|
||||
# (imagens baixadas, apt do runner).
|
||||
# CI takes 5 to 30 minutes per run; almost everything that breaks CI is caught
|
||||
# here in seconds.
|
||||
set -uo pipefail
|
||||
cd "$(dirname "$0")/.."
|
||||
completo=0
|
||||
[ "${1:-}" = "--completo" ] && completo=1
|
||||
falhas=()
|
||||
passo() { # passo "nome" comando...
|
||||
local nome="$1"; shift
|
||||
printf '\n\033[1m== %s\033[0m\n' "$nome"
|
||||
if "$@"; then printf '\033[32mok\033[0m %s\n' "$nome"; else printf '\033[31mFALHOU\033[0m %s\n' "$nome"; falhas+=("$nome"); fi
|
||||
full=0
|
||||
[ "${1:-}" = "--full" ] || [ "${1:-}" = "--completo" ] && full=1
|
||||
failures=()
|
||||
step() { # step "name" command...
|
||||
local name="$1"; shift
|
||||
printf '\n\033[1m== %s\033[0m\n' "$name"
|
||||
if "$@"; then printf '\033[32mok\033[0m %s\n' "$name"; else printf '\033[31mFAILED\033[0m %s\n' "$name"; failures+=("$name"); fi
|
||||
}
|
||||
|
||||
passo "formatação (cargo fmt)" cargo fmt --all -- --check
|
||||
# as mesmas flags do CI: avisos viram erro, inclusive nos testes
|
||||
passo "clippy (workspace, todos os alvos)" env RUSTFLAGS="-D warnings" cargo clippy --workspace --all-targets
|
||||
passo "cobertura da tradução da interface" node scripts/check-ui-i18n.mjs
|
||||
passo "teste do latest.json" python3 -B scripts/test_make_latest_json.py
|
||||
passo "sintaxe dos scripts bash" bash -c 'for f in scripts/*.sh .githooks/*; do bash -n "$f" || exit 1; done'
|
||||
passo "workflows e templates (YAML válido)" python3 -c "
|
||||
step "formatting (cargo fmt)" cargo fmt --all -- --check
|
||||
# Same flags as CI: warnings are treated as errors across all targets, including tests
|
||||
step "clippy (workspace, all targets)" env RUSTFLAGS="-D warnings" cargo clippy --workspace --all-targets
|
||||
step "UI translation coverage" node scripts/check-ui-i18n.mjs
|
||||
step "latest.json unit tests" python3 -B scripts/test_make_latest_json.py
|
||||
step "bash script syntax" bash -c 'for f in scripts/*.sh .githooks/*; do bash -n "$f" || exit 1; done'
|
||||
step "workflows and templates (valid YAML)" python3 -c "
|
||||
import glob, yaml
|
||||
for f in glob.glob('.gitea/**/*.yml', recursive=True):
|
||||
yaml.safe_load(open(f))
|
||||
"
|
||||
passo "versões consistentes" python3 -c "
|
||||
step "consistent Semantic Versioning 2.0.0" python3 -c "
|
||||
import json, re, sys
|
||||
from pathlib import Path
|
||||
|
||||
semver_re = re.compile(
|
||||
r'^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)'
|
||||
r'(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?'
|
||||
r'(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$'
|
||||
)
|
||||
|
||||
def cargo_version(path):
|
||||
m = re.search(r'^version\s*=\s*\"([^\"]+)\"', Path(path).read_text(), re.M)
|
||||
if not m:
|
||||
sys.exit(f'versão não encontrada em {path}')
|
||||
sys.exit(f'version not found in {path}')
|
||||
return m.group(1)
|
||||
|
||||
versoes = [
|
||||
versions = [
|
||||
cargo_version('Cargo.toml'),
|
||||
cargo_version('apps/gui/src-tauri/Cargo.toml'),
|
||||
json.load(open('apps/gui/src-tauri/tauri.conf.json'))['version'],
|
||||
]
|
||||
if len(set(versoes)) != 1:
|
||||
sys.exit(f'versões divergentes: Cargo.toml={versoes[0]}, src-tauri={versoes[1]}, tauri.conf={versoes[2]}')
|
||||
print(versoes[0])
|
||||
if len(set(versions)) != 1:
|
||||
sys.exit(f'version mismatch: Cargo.toml={versions[0]}, src-tauri={versions[1]}, tauri.conf={versions[2]}')
|
||||
if not semver_re.match(versions[0]):
|
||||
sys.exit(f'invalid SemVer 2.0.0 version: {versions[0]}')
|
||||
print(versions[0])
|
||||
"
|
||||
if [ -f apps/gui/src-tauri/Cargo.toml ] && pkg-config --exists webkit2gtk-4.1 2>/dev/null; then
|
||||
passo "app desktop (cargo check)" env RUSTFLAGS="" cargo check --manifest-path apps/gui/src-tauri/Cargo.toml
|
||||
step "desktop app (cargo check)" env RUSTFLAGS="" cargo check --manifest-path apps/gui/src-tauri/Cargo.toml
|
||||
else
|
||||
printf '\n(app desktop: pulado, faltam as bibliotecas do WebKitGTK; o CI compila)\n'
|
||||
printf '\n(desktop app: skipped, WebKitGTK development libraries not installed; built in CI)\n'
|
||||
fi
|
||||
|
||||
if [ "$completo" = 1 ]; then
|
||||
passo "testes (cargo test --workspace)" cargo test --workspace
|
||||
passo "auditoria de dependências (cargo audit)" cargo audit
|
||||
if [ "$full" = 1 ]; then
|
||||
step "tests (cargo test --workspace)" cargo test --workspace
|
||||
step "dependency audit (cargo audit)" cargo audit
|
||||
fi
|
||||
|
||||
printf '\n'
|
||||
if [ ${#falhas[@]} -eq 0 ]; then
|
||||
printf '\033[32mtudo certo\033[0m: pode dar push.\n'
|
||||
if [ ${#failures[@]} -eq 0 ]; then
|
||||
printf '\033[32mall checks passed\033[0m: ready to push.\n'
|
||||
else
|
||||
printf '\033[31mfalhou:\033[0m %s\n' "${falhas[*]}"
|
||||
printf '\033[31mfailed:\033[0m %s\n' "${failures[*]}"
|
||||
exit 1
|
||||
fi
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
# Baixa imagens de referência (BitLocker e FileVault2) do conjunto de testes do cryptsetup.
|
||||
# Ficam em target/testdata (não versionado). Sem elas, os testes correspondentes são pulados.
|
||||
# Downloads reference disk images (BitLocker, FileVault 2, dfvfs, AFF4) used by integration tests.
|
||||
# Stored in target/testdata (untracked). When absent, the corresponding tests are skipped.
|
||||
set -euo pipefail
|
||||
dest="$(cd "$(dirname "$0")/.." && pwd)/target/testdata"
|
||||
mkdir -p "$dest"
|
||||
|
||||
+30
-28
@@ -1,47 +1,49 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Gera, a partir da logo oficial, o ícone base usado pelo Tauri.
|
||||
"""Generates the base Tauri icon from the official project logo.
|
||||
|
||||
A logo oficial é `logo.png` na raiz do repositório (RGBA, fundo transparente,
|
||||
serve aos temas claro e escuro). Ela não é alterada aqui. Saída:
|
||||
apps/gui/src-tauri/icons/source.png 1024 px, a arte sobre o fundo escuro com cantos arredondados;
|
||||
o CI e `npx @tauri-apps/cli icon` geram o resto dos ícones.
|
||||
The official logo is `logo.png` in the repository root (RGBA, transparent
|
||||
background, suitable for both light and dark themes). It is not modified here.
|
||||
Output:
|
||||
apps/gui/src-tauri/icons/source.png 1024 px artwork centered over a rounded
|
||||
dark background; CI and `npx @tauri-apps/cli icon`
|
||||
derive all remaining icon formats from it.
|
||||
|
||||
Uso: python3 scripts/gerar-logo.py
|
||||
Usage: python3 scripts/generate-logo.py
|
||||
"""
|
||||
import os
|
||||
from PIL import Image, ImageDraw
|
||||
|
||||
RAIZ = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..")
|
||||
ICONES = os.path.join(RAIZ, "apps/gui/src-tauri/icons")
|
||||
FUNDO_ICONE = (10, 16, 28) # o azul-noite do fundo da arte original
|
||||
ROOT = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..")
|
||||
ICONS_DIR = os.path.join(ROOT, "apps/gui/src-tauri/icons")
|
||||
ICON_BG = (10, 16, 28) # dark navy background matching the original artwork
|
||||
|
||||
|
||||
def recortar_quadrado(im, margem=0.04):
|
||||
"""Recorta no que é visível (opacidade > 16, ignora resquícios) e centraliza num quadrado."""
|
||||
def crop_square(im, margin=0.04):
|
||||
"""Crops to visible pixels (opacity > 16, ignoring faint artifacts) and centers in a square."""
|
||||
box = im.getchannel("A").point(lambda v: 255 if v > 16 else 0).getbbox()
|
||||
im = im.crop(box)
|
||||
w, h = im.size
|
||||
lado = round(max(w, h) * (1 + 2 * margem))
|
||||
quadro = Image.new("RGBA", (lado, lado), (0, 0, 0, 0))
|
||||
quadro.paste(im, ((lado - w) // 2, (lado - h) // 2), im)
|
||||
return quadro
|
||||
side = round(max(w, h) * (1 + 2 * margin))
|
||||
canvas = Image.new("RGBA", (side, side), (0, 0, 0, 0))
|
||||
canvas.paste(im, ((side - w) // 2, (side - h) // 2), im)
|
||||
return canvas
|
||||
|
||||
|
||||
def main():
|
||||
logo = recortar_quadrado(Image.open(os.path.join(RAIZ, "logo.png")).convert("RGBA"))
|
||||
logo = crop_square(Image.open(os.path.join(ROOT, "logo.png")).convert("RGBA"))
|
||||
|
||||
lado = 1024
|
||||
fundo = Image.new("RGBA", (lado, lado), FUNDO_ICONE + (255,))
|
||||
arte = logo.resize((round(lado * 0.82),) * 2, Image.LANCZOS)
|
||||
fundo.paste(arte, ((lado - arte.width) // 2, (lado - arte.height) // 2), arte)
|
||||
mascara = Image.new("L", (lado, lado), 0)
|
||||
ImageDraw.Draw(mascara).rounded_rectangle((0, 0, lado - 1, lado - 1), radius=round(lado * 0.2237), fill=255)
|
||||
icone = Image.new("RGBA", (lado, lado), (0, 0, 0, 0))
|
||||
icone.paste(fundo, (0, 0), mascara)
|
||||
os.makedirs(ICONES, exist_ok=True)
|
||||
destino = os.path.join(ICONES, "source.png")
|
||||
icone.save(destino, optimize=True)
|
||||
os.chmod(destino, 0o644)
|
||||
side = 1024
|
||||
bg = Image.new("RGBA", (side, side), ICON_BG + (255,))
|
||||
art = logo.resize((round(side * 0.82),) * 2, Image.LANCZOS)
|
||||
bg.paste(art, ((side - art.width) // 2, (side - art.height) // 2), art)
|
||||
mask = Image.new("L", (side, side), 0)
|
||||
ImageDraw.Draw(mask).rounded_rectangle((0, 0, side - 1, side - 1), radius=round(side * 0.2237), fill=255)
|
||||
icon = Image.new("RGBA", (side, side), (0, 0, 0, 0))
|
||||
icon.paste(bg, (0, 0), mask)
|
||||
os.makedirs(ICONS_DIR, exist_ok=True)
|
||||
dest = os.path.join(ICONS_DIR, "source.png")
|
||||
icon.save(dest, optimize=True)
|
||||
os.chmod(dest, 0o644)
|
||||
print("icons/source.png")
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
# Liga o hook de pre-push do repositório (roda scripts/verificar.sh antes de cada push).
|
||||
# Enables the repository Git hooks (.githooks/pre-push and .githooks/commit-msg).
|
||||
set -euo pipefail
|
||||
cd "$(dirname "$0")/.."
|
||||
git config core.hooksPath .githooks
|
||||
echo "hook ligado: git push passa a rodar scripts/verificar.sh (desligue com: git config --unset core.hooksPath)"
|
||||
echo "hooks enabled: git commit validates Conventional Commits in English and git push runs scripts/check.sh (disable with: git config --unset core.hooksPath)"
|
||||
+13
-13
@@ -1,13 +1,13 @@
|
||||
#!/usr/bin/env bash
|
||||
# Gera imagens de disco de teste (FAT32, exFAT, NTFS, ext4) com arquivos
|
||||
# conhecidos, apaga alguns e grava os SHA-256 esperados em <saida>/expected-<fs>.txt.
|
||||
# Generates test disk images (FAT32, exFAT, NTFS, ext4) populated with known files,
|
||||
# deletes a subset of them, and writes expected SHA-256 digests to <out>/expected-<fs>.txt.
|
||||
#
|
||||
# Uso: scripts/make-fixtures.sh <diretório-de-saída> [fat32 exfat ntfs ext4]
|
||||
# Requer (Linux): mtools, dosfstools, exfatprogs/exfat-fuse, ntfs-3g, e2fsprogs,
|
||||
# imagemagick, zip, python3 e FUSE para exFAT/NTFS.
|
||||
# Usage: scripts/make-fixtures.sh <output-dir> [fat32 exfat ntfs ext4]
|
||||
# Requires (Linux): mtools, dosfstools, exfatprogs/exfat-fuse, ntfs-3g, e2fsprogs,
|
||||
# imagemagick, zip, python3, and FUSE for exFAT/NTFS.
|
||||
set -euo pipefail
|
||||
|
||||
OUT="$(realpath -m "${1:?uso: $0 <saida> [fs...]}")"
|
||||
OUT="$(realpath -m "${1:?usage: $0 <output-dir> [fs...]}")"
|
||||
shift || true
|
||||
FSES=("${@:-fat32 exfat ntfs ext4}")
|
||||
[ $# -eq 0 ] && FSES=(fat32 exfat ntfs ext4)
|
||||
@@ -17,7 +17,7 @@ SIZE_MB=96
|
||||
CONTENT="$OUT/content"
|
||||
rm -rf "$CONTENT"; mkdir -p "$CONTENT/Documentos/Trabalho" "$CONTENT/Fotos"
|
||||
|
||||
# --- conteúdo de teste (mistura de formatos com e sem assinatura) ---
|
||||
# --- test content (mix of formats with and without file signatures) ---
|
||||
convert -size 1400x900 plasma:fractal -quality 92 "$CONTENT/Fotos/praia.jpg"
|
||||
convert -size 900x700 plasma:red-blue "$CONTENT/Fotos/montanha.png"
|
||||
convert -size 600x400 plasma:green-yellow "$CONTENT/Fotos/mantida.jpg"
|
||||
@@ -26,7 +26,7 @@ head -c 3000000 /dev/urandom > "$CONTENT/Documentos/dados.bin"
|
||||
python3 - "$CONTENT" <<'PY'
|
||||
import sys, sqlite3, os
|
||||
d = sys.argv[1]
|
||||
pdf = b"%PDF-1.4\n" + b"".join(b"%% linha %d preenchimento %s\n" % (i, os.urandom(40).hex().encode()) for i in range(6000)) + b"trailer\n<< /Root 1 0 R >>\n%%EOF\n"
|
||||
pdf = b"%PDF-1.4\n" + b"".join(b"%% line %d padding %s\n" % (i, os.urandom(40).hex().encode()) for i in range(6000)) + b"trailer\n<< /Root 1 0 R >>\n%%EOF\n"
|
||||
open(d + "/Documentos/Trabalho/relatorio.pdf", "wb").write(pdf)
|
||||
db = d + "/Documentos/contatos.db"
|
||||
c = sqlite3.connect(db)
|
||||
@@ -38,7 +38,7 @@ mkdir -p "$OUT/zip" && ( cd "$OUT/zip" && rm -rf * && mkdir -p word _rels && ech
|
||||
&& head -c 400000 /dev/urandom | base64 > word/document.xml && zip -q -r "$CONTENT/Documentos/Trabalho/tese.docx" . )
|
||||
rm -rf "$OUT/zip"
|
||||
|
||||
# Arquivos que serão APAGADOS (caminhos relativos a $CONTENT)
|
||||
# Files that will be DELETED (paths relative to $CONTENT)
|
||||
DELETE=(Fotos/praia.jpg Fotos/montanha.png Documentos/Trabalho/notas.txt Documentos/Trabalho/relatorio.pdf Documentos/Trabalho/tese.docx Documentos/contatos.db Documentos/dados.bin)
|
||||
|
||||
write_expected() { # $1=fs
|
||||
@@ -60,13 +60,13 @@ for fs in "${FSES[@]}"; do
|
||||
for f in "${DELETE[@]}"; do mdel -i "$img" "::/$f"; done
|
||||
;;
|
||||
exfat)
|
||||
# Sem FUSE sem privilégio, só o sistema vazio é criado aqui; o teste em Rust
|
||||
# injeta as entradas (veja crates/guara-core/tests/exfat.rs).
|
||||
# Without unprivileged FUSE, only the empty filesystem is formatted here;
|
||||
# the Rust test injects entries directly (see crates/guara-core/tests/exfat.rs).
|
||||
mkfs.exfat -L GUARATEST "$img" >/dev/null
|
||||
;;
|
||||
ntfs)
|
||||
# ntfscp só grava na raiz; "apagar" = limpar o bit "em uso" do registro da MFT,
|
||||
# exatamente o que o NTFS faz (clusters e nome permanecem no disco).
|
||||
# ntfscp writes to the root directory; "deleting" clears the in-use bit on the MFT
|
||||
# record, matching standard NTFS deletion behavior (clusters and filename remain).
|
||||
mkntfs -F -Q -L GUARATEST "$img" >/dev/null 2>&1
|
||||
for f in $(cd "$CONTENT" && find . -type f | sed 's|^\./||'); do
|
||||
ntfscp -f "$img" "$CONTENT/$f" "/$(basename "$f")" >/dev/null 2>&1
|
||||
|
||||
+13
-13
@@ -1,12 +1,12 @@
|
||||
#!/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 no Gitea.
|
||||
"""Builds the latest.json manifest read by the Tauri updater from the release
|
||||
bundles and `.sig` signatures already uploaded to the Gitea release.
|
||||
|
||||
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.
|
||||
Runs in the final publish job after all matrix packaging jobs complete. Building
|
||||
the manifest in a single step avoids race conditions from parallel matrix jobs.
|
||||
|
||||
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.
|
||||
Usage: make-latest-json.py <tag> <notes.md> <out.json>
|
||||
Requires API (Gitea repository API URL) and TOKEN in the environment.
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
@@ -15,8 +15,8 @@ import urllib.parse
|
||||
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.
|
||||
# (platform keys, package filename patterns). {v} = version without leading "v".
|
||||
# No macOS: no Apple hardware/signing runner is available.
|
||||
PLATFORMS = [
|
||||
(["linux-x86_64", "linux-x86_64-appimage"], ("Guará_{v}_amd64.AppImage", "Guara_{v}_amd64.AppImage")),
|
||||
(["linux-aarch64", "linux-aarch64-appimage"], ("Guará_{v}_aarch64.AppImage", "Guara_{v}_aarch64.AppImage")),
|
||||
@@ -36,9 +36,9 @@ def api_get_text(url):
|
||||
|
||||
|
||||
def build(version, notes, assets, pub_date):
|
||||
"""`assets` = {nome_do_pacote: (url, assinatura)}. Devolve o dicionário do latest.json.
|
||||
"""`assets` = {package_name: (url, signature)}. Returns the latest.json dict.
|
||||
|
||||
Plataforma sem pacote ou sem assinatura sai da lista; o chamador decide se isso é erro."""
|
||||
Platforms missing a package or signature are omitted; the caller decides whether that is fatal."""
|
||||
platforms = {}
|
||||
for keys, patterns in PLATFORMS:
|
||||
name = next((n for p in patterns if (n := p.format(v=version)) in assets), None)
|
||||
@@ -66,12 +66,12 @@ def main():
|
||||
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"))
|
||||
# Exige os pacotes assinados de Linux (x86_64 e aarch64) e Windows.
|
||||
# Require signed packages for Linux (x86_64 and aarch64) and Windows.
|
||||
platforms = data["platforms"]
|
||||
for must in ("linux-x86_64", "linux-aarch64", "windows-x86_64"):
|
||||
if must not in platforms:
|
||||
sys.exit(f"faltam pacotes ou assinaturas de {must} (achei {sorted(platforms)}); "
|
||||
"confira se TAURI_SIGNING_PRIVATE_KEY está nos segredos")
|
||||
sys.exit(f"missing package or signature for {must} (found {sorted(platforms)}); "
|
||||
"verify that TAURI_SIGNING_PRIVATE_KEY is set in repository secrets")
|
||||
with open(out, "w", encoding="utf-8") as f:
|
||||
json.dump(data, f, indent=2, ensure_ascii=False)
|
||||
f.write("\n")
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Gera uma imagem FAT32 com muitas fotos JPEG (EXIF de câmeras/datas/GPS) já apagadas.
|
||||
"""Generates a FAT32 disk image containing deleted JPEG photos with camera/date/GPS EXIF tags.
|
||||
|
||||
Uso: make-photo-fixture.py <saida.img> [quantidade=300] [copias=1]
|
||||
(`copias` repete cada foto N vezes com outro nome: é um jeito rápido de gerar milhares de arquivos.)
|
||||
Serve para testar a navegação por data/câmera/pasta e as miniaturas.
|
||||
Usage: make-photo-fixture.py <out.img> [count=300] [copies=1]
|
||||
(`copies` duplicates each photo N times under a different filename to quickly generate thousands of files.)
|
||||
Used to test date/camera/folder browsing and thumbnail generation.
|
||||
"""
|
||||
import os, random, shutil, struct, subprocess, sys, tempfile
|
||||
|
||||
@@ -80,7 +80,7 @@ for i in range(count):
|
||||
subprocess.run(['convert', '-seed', str(i), '-size', f'{w}x{h}', 'plasma:fractal', '-gravity', 'center', '-pointsize', '34', '-fill', 'white',
|
||||
'-stroke', 'black', '-strokewidth', '1', '-annotate', '0', f'{y}-{mo:02d}-{d:02d}\n#{i}', '-quality', '82', p], check=True, stderr=subprocess.DEVNULL)
|
||||
raw = open(p, 'rb').read()
|
||||
if i % 9 != 0: # alguns sem EXIF
|
||||
if i % 9 != 0: # leave some images without EXIF metadata
|
||||
gps = places[i % 4] if i % 3 == 0 else None
|
||||
raw = add_exif(raw, make_exif(cam[0], cam[1], dt, gps, rnd.choice([1, 1, 1, 6, 8])))
|
||||
open(p, 'wb').write(raw)
|
||||
@@ -109,4 +109,4 @@ for folder, name, p in files:
|
||||
for folder, name, p in files:
|
||||
subprocess.run(['mdel', '-i', out, f'::/{folder}/{name}'], check=True)
|
||||
shutil.rmtree(tmp)
|
||||
print(f'ok: {out} ({len(files)} fotos apagadas)')
|
||||
print(f'ok: {out} ({len(files)} deleted photos)')
|
||||
@@ -1,18 +1,18 @@
|
||||
#!/usr/bin/env bash
|
||||
# Cria uma imagem XFS com exclusão REAL (feita pelo kernel), para validar a recuperação de XFS
|
||||
# sem simulação. Precisa de root, porque monta a imagem por loop: sudo scripts/make-xfs-real-deletion.sh
|
||||
# Creates an XFS disk image with REAL kernel-performed file deletion to validate
|
||||
# XFS recovery without simulation. Requires root (mounts via loop device):
|
||||
# sudo scripts/make-xfs-real-deletion.sh
|
||||
#
|
||||
# Saída em target/testdata/xfs-real-delete/: disk.img e expected.tsv (nome, tamanho, sha256 e se
|
||||
# o arquivo foi apagado). O teste em crates/guara-core/tests/xfs.rs usa esses dois arquivos e é
|
||||
# pulado quando eles não existem.
|
||||
# Output in target/testdata/xfs-real-delete/: disk.img and expected.tsv (path, size,
|
||||
# sha256, and deletion status). Used by crates/guara-core/tests/xfs.rs when present.
|
||||
set -euo pipefail
|
||||
[ "$(id -u)" = 0 ] || { echo "rode com sudo: sudo $0" >&2; exit 1; }
|
||||
[ "$(id -u)" = 0 ] || { echo "run with sudo: sudo $0" >&2; exit 1; }
|
||||
root="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
out="${1:-$root/target/testdata/xfs-real-delete}"
|
||||
mkdir -p "$out"
|
||||
img="$out/disk.img"
|
||||
rm -f "$img"
|
||||
truncate -s 320M "$img" # o mkfs.xfs recente recusa menos de 300 MiB
|
||||
truncate -s 320M "$img" # recent mkfs.xfs rejects volumes smaller than 300 MiB
|
||||
mkfs.xfs -q -f "$img"
|
||||
mnt="$(mktemp -d)"
|
||||
cleanup() { mountpoint -q "$mnt" && umount "$mnt"; rmdir "$mnt" 2>/dev/null || true; }
|
||||
@@ -27,14 +27,14 @@ def data(seed, n):
|
||||
x ^= (x << 13) & 0xFFFFFFFFFFFFFFFF; x ^= x >> 7; x ^= (x << 17) & 0xFFFFFFFFFFFFFFFF
|
||||
out.append((x >> 24) & 0xFF)
|
||||
return bytes(out)
|
||||
files = [ # (caminho, tamanho, semente, apagar?)
|
||||
files = [ # (relative_path, size, seed, should_delete)
|
||||
("docs/relatorio.dat", 150000, 5, True),
|
||||
("docs/mantido.dat", 90000, 6, False),
|
||||
("fotos/foto1.bin", 300000, 7, True),
|
||||
("fotos/foto2.bin", 40000, 8, False),
|
||||
("raiz-apagada.txt", 20000, 9, True),
|
||||
]
|
||||
# um arquivo com estrutura reconhecível (SQLite), para a recuperação por carving
|
||||
# Include a file with a recognizable header (SQLite) to test carving recovery
|
||||
real = open(sqlite, "rb").read()
|
||||
rows = []
|
||||
files.append(("dados/exemplo.sqlite", len(real), 0, True))
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Marca registros da MFT como apagados (limpa o bit 'em uso') para fixtures de teste.
|
||||
"""Marks NTFS MFT records as deleted (clears the in-use bit) for test fixtures.
|
||||
|
||||
Uso: ntfs-mark-deleted.py imagem.img caminho/arquivo1 caminho/arquivo2 ...
|
||||
Só o nome base é usado (ntfscp grava na raiz).
|
||||
Usage: ntfs-mark-deleted.py image.img path/file1 path/file2 ...
|
||||
Only the basename is matched (ntfscp writes files to the root directory).
|
||||
"""
|
||||
import os
|
||||
import struct
|
||||
@@ -17,7 +17,7 @@ mft_lcn = struct.unpack_from("<Q", boot, 48)[0]
|
||||
rs = struct.unpack_from("<b", boot, 64)[0]
|
||||
rec = rs * bps * spc if rs > 0 else 1 << -rs
|
||||
f.seek(mft_lcn * bps * spc)
|
||||
data = f.read(rec * 4096) # MFT inicial é contígua em imagens pequenas
|
||||
data = f.read(rec * 4096) # Initial MFT is contiguous on small volumes
|
||||
count = 0
|
||||
for i in range(len(data) // rec):
|
||||
r = bytearray(data[i * rec:(i + 1) * rec])
|
||||
@@ -39,4 +39,4 @@ for i in range(len(data) // rec):
|
||||
break
|
||||
p += ln
|
||||
f.flush()
|
||||
print(f"marcados como apagados: {count}", file=sys.stderr)
|
||||
print(f"marked as deleted: {count}", file=sys.stderr)
|
||||
@@ -1,19 +1,19 @@
|
||||
#!/usr/bin/env bash
|
||||
# Compila o CLI para uma plataforma e empacota: guara-<tag>-<plataforma>.tar.gz
|
||||
# com um .sha256 ao lado.
|
||||
# Builds the CLI for a target platform and packages it as:
|
||||
# guara-<tag>-<platform>.tar.gz alongside a .sha256 checksum file.
|
||||
#
|
||||
# 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.
|
||||
# Supported platforms: linux-x86_64 and linux-aarch64. When cross-compiling
|
||||
# from a non-x86_64 host, uses gcc-x86-64-linux-gnu. Writes the archives to the
|
||||
# repository root and prints their filenames.
|
||||
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>}
|
||||
tag=${1:?usage: package-cli.sh <tag> <platform> <rust-target>}
|
||||
platform=${2:?usage: package-cli.sh <tag> <platform> <rust-target>}
|
||||
target=${3:?usage: package-cli.sh <tag> <platform> <rust-target>}
|
||||
|
||||
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
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
#!/usr/bin/env bash
|
||||
# Imprime as notas de uma release: a seção do CHANGELOG.md daquela versão, ou o
|
||||
# log de commits desde a tag anterior quando o changelog não tem a seção.
|
||||
# Prints release notes for a given tag: extracts the matching CHANGELOG.md
|
||||
# section, or falls back to the commit log since the previous tag.
|
||||
#
|
||||
# scripts/release-notes.sh v1.2.3
|
||||
set -euo pipefail
|
||||
|
||||
tag=${1:?uso: release-notes.sh <tag>}
|
||||
tag=${1:?usage: release-notes.sh <tag>}
|
||||
version=${tag#v}
|
||||
|
||||
section=""
|
||||
@@ -22,7 +22,7 @@ if [ -n "$(printf '%s' "$section" | tr -d '[:space:]')" ]; then
|
||||
printf '%s\n' "$section"
|
||||
else
|
||||
previous=$(git describe --tags --abbrev=0 "$tag^" 2>/dev/null || true)
|
||||
echo "## Mudanças${previous:+ desde $previous}"
|
||||
echo "## Changes${previous:+ since $previous}"
|
||||
echo
|
||||
git log --no-merges --format='- %s (%h)' "${previous:+$previous..}$tag"
|
||||
fi
|
||||
@@ -1,14 +1,14 @@
|
||||
#!/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).
|
||||
# Uploads assets to a Gitea release, replacing any existing asset with the same
|
||||
# filename so re-running a CI job never fails on duplicate attachments.
|
||||
#
|
||||
# API=https://git.exemplo.com/api/v1/repos/dono/repo TOKEN=... \
|
||||
# scripts/release-upload.sh v1.2.3 arquivo1 arquivo2
|
||||
# API=https://git.example.com/api/v1/repos/owner/repo TOKEN=... \
|
||||
# scripts/release-upload.sh v1.2.3 file1 file2
|
||||
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>...}
|
||||
: "${API:?set API (Gitea repository API URL)}"
|
||||
: "${TOKEN:?set TOKEN}"
|
||||
tag=${1:?usage: release-upload.sh <tag> <file>...}
|
||||
shift
|
||||
|
||||
auth=(-H "Authorization: token $TOKEN")
|
||||
@@ -21,5 +21,5 @@ for file in "$@"; 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=$encoded" >/dev/null
|
||||
echo "enviado: $name"
|
||||
echo "uploaded: $name"
|
||||
done
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Teste de scripts/make-latest-json.py: python3 scripts/test_make_latest_json.py"""
|
||||
"""Unit tests for scripts/make-latest-json.py: python3 scripts/test_make_latest_json.py"""
|
||||
import importlib.util
|
||||
import os
|
||||
import sys
|
||||
@@ -16,19 +16,19 @@ BASE = "https://git.lucasrguerra.dev.br/lucasrguerra/guara/releases/download/v1.
|
||||
|
||||
def assets(version="1.3.2"):
|
||||
names = [patterns[0].format(v=version) for _, patterns in m.PLATFORMS]
|
||||
return {n: (BASE + n, f"assinatura-de-{n}\n") for n in names}
|
||||
return {n: (BASE + n, f"sig-of-{n}\n") for n in names}
|
||||
|
||||
|
||||
class LatestJson(unittest.TestCase):
|
||||
def test_all_platforms_and_variants(self):
|
||||
d = m.build("1.3.2", "### Novidades\n", assets(), "2026-10-30T12:00:00Z")
|
||||
d = m.build("1.3.2", "### What's New\n", assets(), "2026-10-30T12:00:00Z")
|
||||
self.assertEqual(d["version"], "1.3.2")
|
||||
self.assertEqual(d["notes"], "### Novidades")
|
||||
self.assertEqual(d["notes"], "### What's New")
|
||||
p = d["platforms"]
|
||||
for k in ("linux-x86_64", "linux-x86_64-appimage", "linux-aarch64", "windows-x86_64", "windows-x86_64-nsis"):
|
||||
self.assertIn(k, p)
|
||||
self.assertTrue(p["windows-x86_64"]["url"].endswith("x64-setup.exe"))
|
||||
self.assertEqual(p["linux-x86_64"]["signature"], "assinatura-de-Guará_1.3.2_amd64.AppImage")
|
||||
self.assertEqual(p["linux-x86_64"]["signature"], "sig-of-Guará_1.3.2_amd64.AppImage")
|
||||
|
||||
def test_missing_package_is_left_out(self):
|
||||
a = assets()
|
||||
|
||||
Reference in new issue
Block a user