A shepherd for your Appimages.
0

Configure Feed

Select the types of activity you want to include in your feed.

reorg

Aly Raffauf (Jun 17, 2026, 3:32 PM EDT) 8713a92f c4bc9003

+110 -8
+3 -3
.github/workflows/tests.yml
··· 19 19 cache: true 20 20 21 21 - name: Run tests 22 - run: go test ./src 22 + run: go test ./... 23 23 24 24 - name: Vet 25 - run: go vet ./src 25 + run: go vet ./... 26 26 27 27 - name: Build 28 - run: go build -o /tmp/appherder ./src 28 + run: go build ./...
+106
cmd/appherder/install_test.go
··· 1 + package main 2 + 3 + import ( 4 + "os" 5 + "path/filepath" 6 + "testing" 7 + ) 8 + 9 + // fakeExtract simulates `appimage --appimage-extract` by populating a 10 + // squashfs-root directory with a desktop file and icon, mirroring what a real 11 + // AppImage runtime writes during extraction. 12 + func fakeExtract(t *testing.T, desktop string, icon []byte) func(name string, args []string, dir string) error { 13 + t.Helper() 14 + return func(name string, args []string, dir string) error { 15 + root := filepath.Join(dir, "squashfs-root") 16 + if err := os.MkdirAll(root, 0o755); err != nil { 17 + return err 18 + } 19 + if desktop != "" { 20 + if err := os.WriteFile(filepath.Join(root, "example.desktop"), []byte(desktop), 0o644); err != nil { 21 + return err 22 + } 23 + } 24 + if icon != nil { 25 + if err := os.WriteFile(filepath.Join(root, ".DirIcon"), icon, 0o644); err != nil { 26 + return err 27 + } 28 + } 29 + return nil 30 + } 31 + } 32 + 33 + func TestInstallWritesAppImageIconAndDesktopFile(t *testing.T) { 34 + home := t.TempDir() 35 + src := t.TempDir() 36 + appimage := filepath.Join(src, "Example.AppImage") 37 + if err := os.WriteFile(appimage, []byte("appimage-bytes"), 0o644); err != nil { 38 + t.Fatal(err) 39 + } 40 + 41 + iconBytes := []byte("icon-bytes") 42 + a := app{ 43 + homeDir: func() (string, error) { return home, nil }, 44 + run: fakeExtract(t, sampleDesktopFile, iconBytes), 45 + } 46 + 47 + if err := a.install(appimage); err != nil { 48 + t.Fatalf("install: %v", err) 49 + } 50 + 51 + // AppImage copied into ~/AppImages and made executable. 52 + installedAppImage := filepath.Join(home, "AppImages", "Example.appimage") 53 + info, err := os.Stat(installedAppImage) 54 + if err != nil { 55 + t.Fatalf("stat installed appimage: %v", err) 56 + } 57 + if info.Mode().Perm()&0o111 == 0 { 58 + t.Fatalf("installed appimage is not executable: %v", info.Mode()) 59 + } 60 + if got, _ := os.ReadFile(installedAppImage); string(got) != "appimage-bytes" { 61 + t.Fatalf("installed appimage contents = %q", got) 62 + } 63 + 64 + // Icon copied into ~/AppImages/.icons/<app>. 65 + installedIcon := filepath.Join(home, "AppImages", ".icons", "Example") 66 + if got, _ := os.ReadFile(installedIcon); string(got) != string(iconBytes) { 67 + t.Fatalf("installed icon contents = %q", got) 68 + } 69 + 70 + // Desktop file written and patched to point at the installed paths. 71 + installedDesktop := filepath.Join(home, ".local", "share", "applications", "Example.desktop") 72 + desktop, err := readDesktopFile(installedDesktop) 73 + if err != nil { 74 + t.Fatalf("read installed desktop file: %v", err) 75 + } 76 + assertDesktopValue(t, desktop, desktopEntrySection, "TryExec", installedAppImage) 77 + assertDesktopValue(t, desktop, desktopEntrySection, "Icon", installedIcon) 78 + assertDesktopExec(t, desktop, desktopEntrySection, 79 + []string{"env", "FOO=bar", "DESKTOPINTEGRATION=1", installedAppImage, "%U"}) 80 + } 81 + 82 + func TestInstallWithoutDesktopFileStillInstallsAppImage(t *testing.T) { 83 + home := t.TempDir() 84 + src := t.TempDir() 85 + appimage := filepath.Join(src, "Example.AppImage") 86 + if err := os.WriteFile(appimage, []byte("appimage-bytes"), 0o644); err != nil { 87 + t.Fatal(err) 88 + } 89 + 90 + a := app{ 91 + homeDir: func() (string, error) { return home, nil }, 92 + run: fakeExtract(t, "", nil), 93 + } 94 + 95 + if err := a.install(appimage); err != nil { 96 + t.Fatalf("install: %v", err) 97 + } 98 + 99 + if _, err := os.Stat(filepath.Join(home, "AppImages", "Example.appimage")); err != nil { 100 + t.Fatalf("stat installed appimage: %v", err) 101 + } 102 + // No desktop file in the extracted AppImage means none is written. 103 + if _, err := os.Stat(filepath.Join(home, ".local", "share", "applications", "Example.desktop")); !os.IsNotExist(err) { 104 + t.Fatalf("expected no desktop file, stat err: %v", err) 105 + } 106 + }
+1 -5
package.nix
··· 7 7 version = "dev"; 8 8 src = ./.; 9 9 vendorHash = "sha256-Q+emMKLlnoRlYIe2nNZ6NKkg6bao1xj8CARkv5uiZRs="; 10 - subPackages = ["src"]; 11 - 12 - postInstall = '' 13 - mv "$out/bin/src" "$out/bin/appherder" 14 - ''; 10 + subPackages = ["cmd/appherder"]; 15 11 }
src/app.go cmd/appherder/app.go
src/appimage.go cmd/appherder/appimage.go
src/cli.go cmd/appherder/cli.go
src/desktop.go cmd/appherder/desktop.go
src/desktop_test.go cmd/appherder/desktop_test.go
src/exec.go cmd/appherder/exec.go
src/exec_test.go cmd/appherder/exec_test.go
src/files.go cmd/appherder/files.go
src/icons.go cmd/appherder/icons.go
src/install.go cmd/appherder/install.go
src/main.go cmd/appherder/main.go
src/uninstall.go cmd/appherder/uninstall.go
src/uninstall_test.go cmd/appherder/uninstall_test.go