diff --git a/.github/workflows/binaries.yml b/.github/workflows/binaries.yml index 1f14d59..97d1b86 100644 --- a/.github/workflows/binaries.yml +++ b/.github/workflows/binaries.yml @@ -107,6 +107,13 @@ jobs: chmod +x packaging/build_appimage.sh ./packaging/build_appimage.sh dist/${{ matrix.artifact_name }} + - name: Validate Linux release artifacts + if: matrix.os == 'ubuntu-22.04' + shell: bash + run: | + chmod +x scripts/validate-release-artifacts.sh + ./scripts/validate-release-artifacts.sh + - name: Build Flatpak (Linux) if: matrix.os == 'ubuntu-22.04' run: | diff --git a/pyproject.toml b/pyproject.toml index f9bbda0..b4c1ee7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,7 +14,7 @@ voxvera = ["templates/**", "src/**", "resources/**", "locales/**", "resources/bi [project] name = "voxvera" -version = "0.3.8" +version = "0.3.9" description = "VoxVera CLI utilities" readme = "README.md" authors = [{name = "VoxVera"}] diff --git a/scripts/validate-release-artifacts.sh b/scripts/validate-release-artifacts.sh new file mode 100644 index 0000000..f8e69d4 --- /dev/null +++ b/scripts/validate-release-artifacts.sh @@ -0,0 +1,110 @@ +#!/usr/bin/env bash +set -euo pipefail + +cd "$(dirname "$0")/.." + +ARCH="${ARCH_OVERRIDE:-$(uname -m)}" +if [[ "$ARCH" == "x86_64" ]]; then + DEB_ARCH="amd64" +else + DEB_ARCH="$ARCH" +fi + +DEB_PATH="${1:-voxvera/resources/bin/voxvera_$(python3 - <<'PY' +from voxvera import __version__ +print(__version__) +PY +)_${DEB_ARCH}.deb}" +APPIMAGE_PATH="${2:-voxvera/resources/bin/VoxVera-${ARCH}.AppImage}" + +tmpdir="$(mktemp -d)" +cleanup() { + rm -rf "$tmpdir" +} +trap cleanup EXIT + +json_get() { + local file="$1" + local key="$2" + python3 - "$file" "$key" <<'PY' +import json +import sys + +path = sys.argv[2].split(".") +data = json.load(open(sys.argv[1], "r", encoding="utf-8")) +for part in path: + data = data[part] +if isinstance(data, list): + print("\n".join(str(item) for item in data)) +else: + print(data) +PY +} + +require_non_empty_json_field() { + local file="$1" + local key="$2" + local value + value="$(json_get "$file" "$key")" + if [[ -z "${value// }" ]]; then + echo "Expected non-empty JSON field '$key' in $file" >&2 + exit 1 + fi +} + +echo "Validating Debian artifact: $DEB_PATH" +if [[ ! -f "$DEB_PATH" ]]; then + echo "Debian package not found: $DEB_PATH" >&2 + exit 1 +fi + +mkdir -p "$tmpdir/deb-root" "$tmpdir/deb-data" +dpkg-deb -x "$DEB_PATH" "$tmpdir/deb-root" + +if [[ ! -x "$tmpdir/deb-root/usr/bin/voxvera" ]]; then + echo "Packaged voxvera binary missing from .deb payload" >&2 + exit 1 +fi + +VOXVERA_DIR="$tmpdir/deb-data" \ + "$tmpdir/deb-root/usr/bin/voxvera" \ + --config "$tmpdir/deb-data/config.json" \ + init --non-interactive + +if [[ ! -f "$tmpdir/deb-data/config.json" ]]; then + echo ".deb validation failed: config.json was not created" >&2 + exit 1 +fi + +if [[ ! -f "$tmpdir/deb-data/host/voxvera/index.html" ]]; then + echo ".deb validation failed: built site missing index.html" >&2 + exit 1 +fi + +echo "Validating AppImage artifact: $APPIMAGE_PATH" +if [[ ! -f "$APPIMAGE_PATH" ]]; then + echo "AppImage not found: $APPIMAGE_PATH" >&2 + exit 1 +fi + +chmod +x "$APPIMAGE_PATH" +"$APPIMAGE_PATH" platform-status --json > "$tmpdir/platform-status.json" +require_non_empty_json_field "$tmpdir/platform-status.json" "tier_description" +require_non_empty_json_field "$tmpdir/platform-status.json" "hosting_model" + +VOXVERA_DIR="$tmpdir/appimage-data" \ + "$APPIMAGE_PATH" \ + --config "$tmpdir/appimage-data/config.json" \ + init --non-interactive + +if [[ ! -f "$tmpdir/appimage-data/config.json" ]]; then + echo "AppImage validation failed: config.json was not created" >&2 + exit 1 +fi + +if [[ ! -f "$tmpdir/appimage-data/host/voxvera/index.html" ]]; then + echo "AppImage validation failed: built site missing index.html" >&2 + exit 1 +fi + +echo "Release artifact validation passed." diff --git a/site/download/voxvera-source.zip b/site/download/voxvera-source.zip index b20ce22..37fac55 100644 Binary files a/site/download/voxvera-source.zip and b/site/download/voxvera-source.zip differ diff --git a/tests/test_integrity.py b/tests/test_integrity.py index 808f02d..a7a35ca 100644 --- a/tests/test_integrity.py +++ b/tests/test_integrity.py @@ -64,6 +64,18 @@ def test_frozen_builds_bundle_support_matrix(): assert '--add-data "support-matrix.json:."' in build_script assert '--add-data "support-matrix.json${SEP}."' in binaries_workflow + +def test_release_artifact_validation_is_wired_into_binaries_workflow(): + validation_script = REPO_ROOT / "scripts" / "validate-release-artifacts.sh" + binaries_workflow = (REPO_ROOT / ".github" / "workflows" / "binaries.yml").read_text(encoding="utf-8") + script_text = validation_script.read_text(encoding="utf-8") + + assert validation_script.exists() + assert "scripts/validate-release-artifacts.sh" in binaries_workflow + assert "dpkg-deb -x" in script_text + assert "platform-status --json" in script_text + assert "init --non-interactive" in script_text + def test_locale_completeness(): """Verify that all non-English locales have the same keys as English.""" locales_dir = REPO_ROOT / "voxvera" / "locales" diff --git a/voxvera/__init__.py b/voxvera/__init__.py index d337cc6..eb7e8da 100644 --- a/voxvera/__init__.py +++ b/voxvera/__init__.py @@ -1,7 +1,7 @@ import sys from pathlib import Path -__version__ = "0.3.8" +__version__ = "0.3.9" # Add the vendor directory to sys.path ONLY for PyInstaller (frozen) builds. # For pip/pipx installs the system/venv packages are correct and the vendored diff --git a/voxvera/cli.py b/voxvera/cli.py index 8ce0f8a..4ee7550 100644 --- a/voxvera/cli.py +++ b/voxvera/cli.py @@ -2076,7 +2076,8 @@ def main(argv=None): if args.template: copy_template(args.template) return - elif not args.non_interactive: + ensure_config_exists(config_path) + if not args.non_interactive: interactive_update(config_path) build_assets(config_path) elif args.command == "build":