mirror of
https://github.com/Valkyrie00/bold-brew.git
synced 2026-03-14 14:25:53 +01:00
feat(brewfile): add remote Brewfile support via HTTPS URLs
Users can now load Brewfiles from remote URLs: bbrew -f https://example.com/Brewfile Remote files are downloaded to a temp file and auto-cleaned on exit. Only HTTPS URLs are supported for security.
This commit is contained in:
parent
ca20863f4c
commit
f4e9c32987
2 changed files with 70 additions and 8 deletions
|
|
@ -19,12 +19,13 @@ func main() {
|
|||
fmt.Fprintf(os.Stderr, "Bold Brew - A TUI for Homebrew package management\n\n")
|
||||
fmt.Fprintf(os.Stderr, "Usage: bbrew [options]\n\n")
|
||||
fmt.Fprintf(os.Stderr, "Options:\n")
|
||||
fmt.Fprintf(os.Stderr, " -f <path> Path to Brewfile (show only packages from this Brewfile)\n")
|
||||
fmt.Fprintf(os.Stderr, " -f <path|url> Path or URL to Brewfile\n")
|
||||
fmt.Fprintf(os.Stderr, " -v, --version Show version information\n")
|
||||
fmt.Fprintf(os.Stderr, " -h, --help Show this help message\n")
|
||||
fmt.Fprintf(os.Stderr, "\nExamples:\n")
|
||||
fmt.Fprintf(os.Stderr, " bbrew Launch the TUI with all packages\n")
|
||||
fmt.Fprintf(os.Stderr, " bbrew -f ~/Brewfile Launch with packages from Brewfile\n")
|
||||
fmt.Fprintf(os.Stderr, " bbrew -f ~/Brewfile Launch with packages from local Brewfile\n")
|
||||
fmt.Fprintf(os.Stderr, " bbrew -f https://... Launch with packages from remote Brewfile\n")
|
||||
}
|
||||
|
||||
flag.Parse()
|
||||
|
|
@ -35,15 +36,17 @@ func main() {
|
|||
os.Exit(0)
|
||||
}
|
||||
|
||||
// Validate Brewfile path if provided
|
||||
// Resolve Brewfile path (handles both local and remote URLs)
|
||||
var cleanup func()
|
||||
if *brewfilePath != "" {
|
||||
if _, err := os.Stat(*brewfilePath); os.IsNotExist(err) {
|
||||
fmt.Fprintf(os.Stderr, "Error: Brewfile not found: %s\n", *brewfilePath)
|
||||
os.Exit(1)
|
||||
} else if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: Cannot access Brewfile: %v\n", err)
|
||||
localPath, cleanupFn, err := services.ResolveBrewfilePath(*brewfilePath)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
*brewfilePath = localPath
|
||||
cleanup = cleanupFn
|
||||
defer cleanup()
|
||||
}
|
||||
|
||||
// Initialize app service
|
||||
|
|
|
|||
|
|
@ -25,11 +25,70 @@ package services
|
|||
import (
|
||||
"bbrew/internal/models"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ResolveBrewfilePath resolves a Brewfile path which can be local or a remote URL.
|
||||
// Returns the local file path and a cleanup function to call when done.
|
||||
// For local files, cleanup is a no-op. For remote files, cleanup removes the temp file.
|
||||
func ResolveBrewfilePath(pathOrURL string) (localPath string, cleanup func(), err error) {
|
||||
// Check if it's a remote URL (HTTPS only for security)
|
||||
if strings.HasPrefix(pathOrURL, "https://") {
|
||||
localPath, err = downloadBrewfile(pathOrURL)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
// Return cleanup function that removes the temp file
|
||||
cleanup = func() { os.Remove(localPath) }
|
||||
return localPath, cleanup, nil
|
||||
}
|
||||
|
||||
// Local file - validate it exists
|
||||
if _, err := os.Stat(pathOrURL); os.IsNotExist(err) {
|
||||
return "", nil, fmt.Errorf("brewfile not found: %s", pathOrURL)
|
||||
} else if err != nil {
|
||||
return "", nil, fmt.Errorf("cannot access Brewfile: %w", err)
|
||||
}
|
||||
|
||||
// No cleanup needed for local files
|
||||
return pathOrURL, func() {}, nil
|
||||
}
|
||||
|
||||
// downloadBrewfile downloads a remote Brewfile to a temporary file.
|
||||
func downloadBrewfile(url string) (string, error) {
|
||||
fmt.Fprintf(os.Stderr, "Downloading Brewfile from %s...\n", url)
|
||||
|
||||
resp, err := http.Get(url) // #nosec G107 - URL is user-provided, HTTPS enforced
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to fetch URL: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return "", fmt.Errorf("HTTP %d: %s", resp.StatusCode, resp.Status)
|
||||
}
|
||||
|
||||
// Create temp file
|
||||
tempFile, err := os.CreateTemp(os.TempDir(), "bbrew-remote-*.brewfile")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to create temp file: %w", err)
|
||||
}
|
||||
defer tempFile.Close()
|
||||
|
||||
// Copy content
|
||||
if _, err = io.Copy(tempFile, resp.Body); err != nil {
|
||||
os.Remove(tempFile.Name())
|
||||
return "", fmt.Errorf("failed to save Brewfile: %w", err)
|
||||
}
|
||||
|
||||
return filepath.Clean(tempFile.Name()), nil
|
||||
}
|
||||
|
||||
// parseBrewfileWithTaps parses a Brewfile and returns taps and packages separately.
|
||||
func parseBrewfileWithTaps(filepath string) (*models.BrewfileResult, error) {
|
||||
// #nosec G304 -- filepath is user-provided via CLI flag
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue