feat: github release add archive_bin_name option

This commit is contained in:
2026-03-18 21:47:47 +02:00
parent e3f823859b
commit 6ea118c24f
3 changed files with 75 additions and 3 deletions

View File

@@ -315,6 +315,22 @@ These fields are shared by all installer types. Some fields may vary in behavior
download_filename: myapp_{tag}_linux.tar.gz # outputs: myapp_v1.0.0_linux.tar.gz
```
- `opts.archive_bin_name`: The name of the binary file inside the archive (tar/zip).
Use this when the filename inside the archive differs from the desired output `bin_name`.
If not set, falls back to `bin_name` (or the installer name).
```yaml
- name: cospend-cli
bin_name: cospend
type: github-release
opts:
repository: chenasraf/cospend-cli
destination: ~/.local/bin
strategy: tar
download_filename: cospend-cli-linux-{{ .Arch }}.tar.gz
archive_bin_name: cospend-cli # file inside the tar is "cospend-cli", output will be "cospend"
```
- `opts.github_token`: GitHub personal access token for authenticated API requests.
Authenticated requests have a much higher rate limit (5,000/hour vs 60/hour for
unauthenticated).

View File

@@ -40,6 +40,10 @@ type GitHubReleaseOpts struct {
// GithubToken is the GitHub personal access token for authenticated API requests.
// Supports environment variable expansion (e.g., "$GITHUB_TOKEN" or "${GITHUB_TOKEN}").
GithubToken *string
// ArchiveBinName is the name of the binary file inside the archive (tar/zip).
// Use this when the filename inside the archive differs from the desired output bin_name.
// If not set, falls back to bin_name (or the installer name).
ArchiveBinName *string
}
// GitHubReleaseInstallStrategy represents the installation strategy for a GitHub release.
@@ -193,7 +197,7 @@ func (i *GitHubReleaseInstaller) Install() error {
if err != nil {
return err
}
logger.Debug("Strategy 'tar': copying binary '%s' to destination", i.GetBinName())
logger.Debug("Strategy 'tar': copying binary '%s' to destination", i.GetArchiveBinName())
success, err = i.CopyExtractedFile(out, tmpDir)
if !success {
return fmt.Errorf("failed to copy extracted file: %w", err)
@@ -210,7 +214,7 @@ func (i *GitHubReleaseInstaller) Install() error {
if err != nil {
return err
}
logger.Debug("Strategy 'zip': copying binary '%s' to destination", i.GetBinName())
logger.Debug("Strategy 'zip': copying binary '%s' to destination", i.GetArchiveBinName())
success, err = i.CopyExtractedFile(out, tmpDir)
if !success {
return fmt.Errorf("failed to copy extracted file: %w", err)
@@ -299,6 +303,16 @@ func (i *GitHubReleaseInstaller) GetBinName() string {
return filepath.Base(*i.Info.Name)
}
// GetArchiveBinName returns the name of the binary file inside the archive.
// It uses ArchiveBinName from opts if provided, otherwise falls back to GetBinName().
func (i *GitHubReleaseInstaller) GetArchiveBinName() string {
opts := i.GetOpts()
if opts.ArchiveBinName != nil {
return *opts.ArchiveBinName
}
return i.GetBinName()
}
// CopyExtractedFile copies the extracted file from a temporary directory to the final destination.
func (i *GitHubReleaseInstaller) CopyExtractedFile(out *os.File, tmpDir string) (bool, error) {
binFile, err := os.Create(out.Name())
@@ -310,7 +324,7 @@ func (i *GitHubReleaseInstaller) CopyExtractedFile(out *os.File, tmpDir string)
logger.Warn("failed to close binFile %s: %v", binFile.Name(), cerr)
}
}()
tmpBinFile, err := os.Open(filepath.Join(tmpDir, i.GetBinName()))
tmpBinFile, err := os.Open(filepath.Join(tmpDir, i.GetArchiveBinName()))
if err != nil {
return false, fmt.Errorf("failed to open temporary file: %w", err)
}
@@ -397,6 +411,9 @@ func (i *GitHubReleaseInstaller) GetOpts() *GitHubReleaseOpts {
token = utils.GetRealPath(i.GetData().Environ(), token)
opts.GithubToken = &token
}
if archiveBinName, ok := (*info.Opts)["archive_bin_name"].(string); ok {
opts.ArchiveBinName = &archiveBinName
}
}
return opts
}

View File

@@ -175,6 +175,45 @@ func TestGitHubReleaseGetBinName(t *testing.T) {
})
}
func TestGitHubReleaseGetArchiveBinName(t *testing.T) {
logger.InitLogger(false)
t.Run("returns archive_bin_name when set", func(t *testing.T) {
binName := "cospend"
data := &appconfig.InstallerData{
Name: strPtr("cospend-cli"),
Type: appconfig.InstallerTypeGitHubRelease,
BinName: &binName,
Opts: &map[string]any{
"archive_bin_name": "cospend-cli",
},
}
installer := newTestGitHubReleaseInstaller(data)
assert.Equal(t, "cospend-cli", installer.GetArchiveBinName())
assert.Equal(t, "cospend", installer.GetBinName())
})
t.Run("falls back to bin_name when archive_bin_name not set", func(t *testing.T) {
binName := "custom-bin"
data := &appconfig.InstallerData{
Name: strPtr("my-app"),
Type: appconfig.InstallerTypeGitHubRelease,
BinName: &binName,
}
installer := newTestGitHubReleaseInstaller(data)
assert.Equal(t, "custom-bin", installer.GetArchiveBinName())
})
t.Run("falls back to name when neither set", func(t *testing.T) {
data := &appconfig.InstallerData{
Name: strPtr("my-app"),
Type: appconfig.InstallerTypeGitHubRelease,
}
installer := newTestGitHubReleaseInstaller(data)
assert.Equal(t, "my-app", installer.GetArchiveBinName())
})
}
func TestGitHubReleaseGetFilename(t *testing.T) {
logger.InitLogger(false)