* fix(appimage): stop shipping a dangling .DirIcon, and prove it in CI
The Linux icon is blank because the AppImage's .DirIcon is an absolute
symlink into the machine that built it. From the published v0.4.2:
.DirIcon -> /home/runner/work/OmniVoice-Studio/OmniVoice-Studio/frontend/
src-tauri/target/x86_64-unknown-linux-gnu/release/bundle/
appimage/OmniVoice Studio.AppDir/OmniVoice Studio.png
That path exists on nobody's computer. The link dangles the moment the
AppImage leaves CI, so file managers have no icon for the file, and the
integration tools that read .DirIcon install nothing. A dangling symlink is
not a build error — the bundle packs, runs, and passes every check we had —
which is how it shipped for a whole release without anyone noticing.
Locally built AppDirs are worse: both .DirIcon AND the root .desktop symlink
come out absolute, so a from-source bundle has no readable desktop entry
either, which is why the icon is missing in the menu and the dock too.
- `.DirIcon` is now a real file, copied in through `appimage.files` — the
same seam that already places the WebKitGTK marker.
- `bundle.category` is set, so the generated desktop entry stops emitting an
empty `Categories=`. That is not the same as omitting the key:
desktop-file-validate rejects the entry and menu builders skip it.
- verify-apprun-bundle.sh — already run against the extracted AppImage in the
release job — now fails when .DirIcon is missing or resolves outside the
bundle, when the .desktop entry does not resolve inside it, when Icon=
names a file that is not at the AppImage root, or when Categories= is
present but empty. Its unit test covers each of those, including the exact
shape v0.4.2 shipped.
The `.DirIcon` copy cannot be verified without a full release build, so the
guard is the load-bearing part: the next release either passes it or fails
loudly. It can no longer ship blank in silence.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* docs(changelog): stamp the AppImage icon entries with their PR ref
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* docs(changelog): fold the AppImage icon fix into the existing Fixed section
CodeRabbit (#1518): the Unreleased block must carry one `### Fixed`
section of one-line entries. Merge the two entries in and drop the
narrative and the version reference.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
95 lines
3.6 KiB
Bash
95 lines
3.6 KiB
Bash
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
|
VERIFY="$REPO_ROOT/scripts/verify-apprun-bundle.sh"
|
|
EXPECTED="$REPO_ROOT/frontend/src-tauri/appimage/AppRun"
|
|
TMP="$(mktemp -d)"
|
|
trap 'rm -rf "$TMP"' EXIT
|
|
|
|
make_root() {
|
|
local root="$1"
|
|
mkdir -p "$root/usr/lib" "$root/usr/share/icons/hicolor/128x128/apps"
|
|
printf '%s\n' '2.52.3' > "$root/usr/lib/.bundled-webkitgtk-version"
|
|
# A healthy icon set: a real root-level PNG named after Icon=, a .DirIcon
|
|
# that resolves to it, and a categorised desktop entry.
|
|
printf 'PNG' > "$root/usr/share/icons/hicolor/128x128/apps/omnivoice-studio.png"
|
|
cp "$root/usr/share/icons/hicolor/128x128/apps/omnivoice-studio.png" "$root/omnivoice-studio.png"
|
|
ln -sf omnivoice-studio.png "$root/.DirIcon"
|
|
cat > "$root/VoiceStudio.desktop" <<'DESKTOP'
|
|
[Desktop Entry]
|
|
Type=Application
|
|
Name=VoiceStudio
|
|
Exec=omnivoice-studio
|
|
Icon=omnivoice-studio
|
|
Categories=AudioVideo;Audio;
|
|
DESKTOP
|
|
}
|
|
|
|
printf '%s\n' '2.52.3' > "$TMP/expected-marker"
|
|
|
|
make_root "$TMP/direct"
|
|
install -m 755 "$EXPECTED" "$TMP/direct/AppRun"
|
|
bash "$VERIFY" "$TMP/direct" "$EXPECTED" "$TMP/expected-marker"
|
|
|
|
make_root "$TMP/wrapped"
|
|
install -m 755 "$EXPECTED" "$TMP/wrapped/AppRun.wrapped"
|
|
cat > "$TMP/wrapped/AppRun" <<'WRAPPER'
|
|
#!/usr/bin/env bash
|
|
this_dir="$(dirname "$(readlink -f "${0}")")"
|
|
exec "$this_dir"/AppRun.wrapped "$@"
|
|
WRAPPER
|
|
chmod 755 "$TMP/wrapped/AppRun"
|
|
bash "$VERIFY" "$TMP/wrapped" "$EXPECTED" "$TMP/expected-marker"
|
|
|
|
make_root "$TMP/broken"
|
|
printf '%s\n' '#!/usr/bin/env bash' 'exit 0' > "$TMP/broken/AppRun"
|
|
chmod 755 "$TMP/broken/AppRun"
|
|
if bash "$VERIFY" "$TMP/broken" "$EXPECTED" "$TMP/expected-marker" >/dev/null 2>&1; then
|
|
echo "FAIL: stock launcher was accepted without the custom launcher" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# ── Icon integrity ────────────────────────────────────────────────────────
|
|
# The exact shape that shipped in v0.4.2: .DirIcon pointing at the build
|
|
# machine's path, which dangles everywhere else.
|
|
make_root "$TMP/absicon"
|
|
install -m 755 "$EXPECTED" "$TMP/absicon/AppRun"
|
|
ln -sf "$TMP/absicon/omnivoice-studio.png" "$TMP/absicon/.DirIcon"
|
|
rm -f "$TMP/absicon/omnivoice-studio.png"
|
|
if bash "$VERIFY" "$TMP/absicon" "$EXPECTED" "$TMP/expected-marker" >/dev/null 2>&1; then
|
|
echo "FAIL: a dangling .DirIcon was accepted" >&2
|
|
exit 1
|
|
fi
|
|
|
|
make_root "$TMP/noicon"
|
|
install -m 755 "$EXPECTED" "$TMP/noicon/AppRun"
|
|
rm -f "$TMP/noicon/.DirIcon"
|
|
if bash "$VERIFY" "$TMP/noicon" "$EXPECTED" "$TMP/expected-marker" >/dev/null 2>&1; then
|
|
echo "FAIL: a missing .DirIcon was accepted" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Icon= naming a file that is not at the AppImage root: integration finds
|
|
# nothing to install and the menu entry draws blank.
|
|
make_root "$TMP/iconmismatch"
|
|
install -m 755 "$EXPECTED" "$TMP/iconmismatch/AppRun"
|
|
rm -f "$TMP/iconmismatch/omnivoice-studio.png"
|
|
printf 'PNG' > "$TMP/iconmismatch/SomethingElse.png"
|
|
ln -sf SomethingElse.png "$TMP/iconmismatch/.DirIcon"
|
|
if bash "$VERIFY" "$TMP/iconmismatch" "$EXPECTED" "$TMP/expected-marker" >/dev/null 2>&1; then
|
|
echo "FAIL: Icon= with no matching root PNG was accepted" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# The empty Categories= tauri emits when bundle.category is unset.
|
|
make_root "$TMP/nocategory"
|
|
install -m 755 "$EXPECTED" "$TMP/nocategory/AppRun"
|
|
sed -i 's/^Categories=.*/Categories=/' "$TMP/nocategory/VoiceStudio.desktop"
|
|
if bash "$VERIFY" "$TMP/nocategory" "$EXPECTED" "$TMP/expected-marker" >/dev/null 2>&1; then
|
|
echo "FAIL: an empty Categories= was accepted" >&2
|
|
exit 1
|
|
fi
|
|
|
|
echo "PASS: launcher chains classified; dangling, missing, mismatched icons and empty Categories rejected"
|