Add initial declpac CLI tool implementation
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/urfave/cli/v3"
|
||||
|
||||
"github.com/Riyyi/declpac/pkg/input"
|
||||
"github.com/Riyyi/declpac/pkg/merge"
|
||||
"github.com/Riyyi/declpac/pkg/output"
|
||||
"github.com/Riyyi/declpac/pkg/pacman"
|
||||
"github.com/Riyyi/declpac/pkg/validation"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
StateFiles []string
|
||||
NoConfirm bool
|
||||
}
|
||||
|
||||
func main() {
|
||||
cfg := &Config{}
|
||||
|
||||
cmd := &cli.Command{
|
||||
Name: "declpac",
|
||||
Usage: "Declarative pacman package manager",
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringSliceFlag{
|
||||
Name: "state",
|
||||
Aliases: []string{"s"},
|
||||
Usage: "State file(s) to read package list from",
|
||||
Destination: &cfg.StateFiles,
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "yes",
|
||||
Aliases: []string{"y"},
|
||||
Usage: "Skip confirmation prompts",
|
||||
Destination: &cfg.NoConfirm,
|
||||
},
|
||||
},
|
||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
||||
return run(cfg)
|
||||
},
|
||||
}
|
||||
|
||||
if err := cmd.Run(context.Background(), os.Args); err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func run(cfg *Config) error {
|
||||
packages, err := input.ReadPackages(cfg.StateFiles)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||
return err
|
||||
}
|
||||
|
||||
merged := merge.Merge(packages)
|
||||
|
||||
if err := validation.Validate(merged); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||
return err
|
||||
}
|
||||
|
||||
result, err := pacman.Sync(merged)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Println(output.Format(result))
|
||||
return nil
|
||||
}
|
||||
@@ -3,7 +3,5 @@ module github.com/Riyyi/declpac
|
||||
go 1.26.2
|
||||
|
||||
require (
|
||||
github.com/Jguer/aur v1.3.0 // indirect
|
||||
github.com/Jguer/dyalpm v0.1.2 // indirect
|
||||
github.com/ebitengine/purego v0.10.0 // indirect
|
||||
github.com/urfave/cli/v3 v3.8.0
|
||||
)
|
||||
|
||||
@@ -4,3 +4,5 @@ github.com/Jguer/dyalpm v0.1.2 h1:Gl0+GDWBQmo3DSsfzTPnKqCwYqcroq0j6kAtsIUkpUw=
|
||||
github.com/Jguer/dyalpm v0.1.2/go.mod h1:FpcWwU1eYHVWMKmr/yHFqHYKsS+qGKCtk/FIXirj2MY=
|
||||
github.com/ebitengine/purego v0.10.0 h1:QIw4xfpWT6GWTzaW5XEKy3HXoqrJGx1ijYHzTF0/ISU=
|
||||
github.com/ebitengine/purego v0.10.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||
github.com/urfave/cli/v3 v3.8.0 h1:XqKPrm0q4P0q5JpoclYoCAv0/MIvH/jZ2umzuf8pNTI=
|
||||
github.com/urfave/cli/v3 v3.8.0/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso=
|
||||
|
||||
@@ -1,95 +1,95 @@
|
||||
## 1. Project Setup
|
||||
|
||||
- [ ] 1.1 Initialize Go module with proper imports
|
||||
- [ ] 1.2 Add required dependencies (dyalpm wrapper, Jguer/aur)
|
||||
- [ ] 1.3 Set up project structure (cmd/declpac/main.go, pkg/ subdirectory)
|
||||
- [ ] 1.4 Add libalpm initialization and handle
|
||||
- [x] 1.1 Initialize Go module with proper imports
|
||||
- [x] 1.2 Add required dependencies (dyalpm wrapper, Jguer/aur)
|
||||
- [x] 1.3 Set up project structure (cmd/declpac/main.go, pkg/ subdirectory)
|
||||
- [x] 1.4 Add libalpm initialization and handle
|
||||
|
||||
## 2. Input Parsing
|
||||
|
||||
- [ ] 2.1 Implement stdin reader to collect package names
|
||||
- [ ] 2.2 Implement state file reader for text-list format
|
||||
- [ ] 2.3 Add whitespace normalization for package names
|
||||
- [ ] 2.4 Create package name set data structure
|
||||
- [x] 2.1 Implement stdin reader to collect package names
|
||||
- [x] 2.2 Implement state file reader for text-list format
|
||||
- [x] 2.3 Add whitespace normalization for package names
|
||||
- [x] 2.4 Create package name set data structure
|
||||
|
||||
## 3. Input Merging
|
||||
|
||||
- [ ] 3.1 Implement additive merging of stdin and state file packages
|
||||
- [ ] 3.2 Handle multiple --state flags with last-writer-wins per file
|
||||
- [ ] 3.3 Implement duplicate package handling (no deduplication)
|
||||
- [x] 3.1 Implement additive merging of stdin and state file packages
|
||||
- [x] 3.2 Handle multiple --state flags with last-writer-wins per file
|
||||
- [x] 3.3 Implement duplicate package handling (no deduplication)
|
||||
|
||||
## 4. State Validation
|
||||
|
||||
- [ ] 4.1 Implement empty state detection (no packages found)
|
||||
- [ ] 4.2 Add stderr error output for empty state
|
||||
- [ ] 4.3 Set exit code 1 for empty state case (abort, not proceed)
|
||||
- [ ] 4.4 Check pacman DB freshness (db.lock timestamp)
|
||||
- [ ] 4.5 Run pacman -Syy if DB older than 1 day
|
||||
- [ ] 4.6 Validate packages via libalpm (pacman repos)
|
||||
- [ ] 4.7 Validate packages via Jguer/aur (AUR)
|
||||
- [ ] 4.8 Fail fast with error if package not found
|
||||
- [x] 4.1 Implement empty state detection (no packages found)
|
||||
- [x] 4.2 Add stderr error output for empty state
|
||||
- [x] 4.3 Set exit code 1 for empty state case (abort, not proceed)
|
||||
- [x] 4.4 Check pacman DB freshness (db.lock timestamp)
|
||||
- [x] 4.5 Run pacman -Syy if DB older than 1 day
|
||||
- [x] 4.6 Validate packages via libalpm (pacman repos)
|
||||
- [x] 4.7 Validate packages via Jguer/aur (AUR)
|
||||
- [x] 4.8 Fail fast with error if package not found
|
||||
|
||||
## 5. Pacman Integration (Hybrid: query via libalpm, modify via exec)
|
||||
|
||||
- [ ] 5.1 Initialize libalpm handle for queries
|
||||
- [ ] 5.2 Implement libalpm query for installed packages
|
||||
- [ ] 5.3 Implement libalpm query for available packages
|
||||
- [ ] 5.4 Implement pacman -Syy command execution (DB refresh)
|
||||
- [ ] 5.5 Implement pacman -Syu command execution wrapper
|
||||
- [ ] 5.6 Add command-line argument construction with package list
|
||||
- [ ] 5.7 Capture pacman stdout and stderr output
|
||||
- [ ] 5.8 Implement pacman error message parsing
|
||||
- [ ] 5.9 Handle pacman exit codes for success/failure detection
|
||||
- [ ] 5.10 Verify pacman automatically resolves transitive dependencies
|
||||
- [x] 5.1 Initialize libalpm handle for queries
|
||||
- [x] 5.2 Implement libalpm query for installed packages
|
||||
- [x] 5.3 Implement libalpm query for available packages
|
||||
- [x] 5.4 Implement pacman -Syy command execution (DB refresh)
|
||||
- [x] 5.5 Implement pacman -Syu command execution wrapper
|
||||
- [x] 5.6 Add command-line argument construction with package list
|
||||
- [x] 5.7 Capture pacman stdout and stderr output
|
||||
- [x] 5.8 Implement pacman error message parsing
|
||||
- [x] 5.9 Handle pacman exit codes for success/failure detection
|
||||
- [x] 5.10 Verify pacman automatically resolves transitive dependencies
|
||||
|
||||
## 6. Explicit Marking & Orphan Cleanup
|
||||
|
||||
- [ ] 6.1 Get list of currently installed packages before sync
|
||||
- [ ] 6.2 Mark declared state packages as explicitly installed via pacman -D --explicit
|
||||
- [ ] 6.3 Run pacman sync operation (5.x series)
|
||||
- [ ] 6.4 Run pacman -Rsu to remove orphaned packages
|
||||
- [ ] 6.5 Capture and report number of packages removed
|
||||
- [ ] 6.6 Handle case where no orphans exist (no packages removed)
|
||||
- [x] 6.1 Get list of currently installed packages before sync
|
||||
- [x] 6.2 Mark declared state packages as explicitly installed via pacman -D --explicit
|
||||
- [x] 6.3 Run pacman sync operation (5.x series)
|
||||
- [x] 6.4 Run pacman -Rsu to remove orphaned packages
|
||||
- [x] 6.5 Capture and report number of packages removed
|
||||
- [x] 6.6 Handle case where no orphans exist (no packages removed)
|
||||
|
||||
## 7. AUR Integration
|
||||
|
||||
- [ ] 7.1 Implement AUR package lookup via Jguer/aur library
|
||||
- [ ] 7.2 Check package not in pacman repos first (via libalpm)
|
||||
- [ ] 7.3 Query AUR for missing packages
|
||||
- [ ] 7.4 Implement AUR fallback using makepkg (direct build, not AUR helper)
|
||||
- [ ] 7.5 Clone AUR package git repo to temp directory
|
||||
- [ ] 7.6 Run makepkg -si in temp directory for installation
|
||||
- [ ] 7.7 Upgrade existing AUR packages to latest (makepkg rebuild)
|
||||
- [ ] 7.8 Add stderr error reporting for packages not in pacman or AUR
|
||||
- [ ] 7.9 Capture makepkg stdout and stderr for output parsing
|
||||
- [ ] 7.10 Handle makepkg exit codes for success/failure detection
|
||||
- [x] 7.1 Implement AUR package lookup via Jguer/aur library
|
||||
- [x] 7.2 Check package not in pacman repos first (via libalpm)
|
||||
- [x] 7.3 Query AUR for missing packages
|
||||
- [x] 7.4 Implement AUR fallback using makepkg (direct build, not AUR helper)
|
||||
- [x] 7.5 Clone AUR package git repo to temp directory
|
||||
- [x] 7.6 Run makepkg -si in temp directory for installation
|
||||
- [x] 7.7 Upgrade existing AUR packages to latest (makepkg rebuild)
|
||||
- [x] 7.8 Add stderr error reporting for packages not in pacman or AUR
|
||||
- [x] 7.9 Capture makepkg stdout and stderr for output parsing
|
||||
- [x] 7.10 Handle makepkg exit codes for success/failure detection
|
||||
|
||||
## 8. Output Generation
|
||||
|
||||
- [ ] 8.1 Parse pacman output for installed package count
|
||||
- [ ] 8.2 Parse pacman output for removed package count (orphan cleanup)
|
||||
- [ ] 8.3 Generate output: `Installed X packages, removed Y packages`
|
||||
- [ ] 8.4 Handle 0 packages case: `Installed 0 packages, removed 0 packages`
|
||||
- [ ] 8.5 Print errors to stderr
|
||||
- [ ] 8.6 Set exit code 0 for success, 1 for errors
|
||||
- [x] 8.1 Parse pacman output for installed package count
|
||||
- [x] 8.2 Parse pacman output for removed package count (orphan cleanup)
|
||||
- [x] 8.3 Generate output: `Installed X packages, removed Y packages`
|
||||
- [x] 8.4 Handle 0 packages case: `Installed 0 packages, removed 0 packages`
|
||||
- [x] 8.5 Print errors to stderr
|
||||
- [x] 8.6 Set exit code 0 for success, 1 for errors
|
||||
|
||||
## 9. CLI Interface
|
||||
|
||||
- [ ] 9.1 Implement --state flag argument parsing
|
||||
- [ ] 9.2 Implement stdin input handling from /dev/stdin
|
||||
- [ ] 9.3 Set up correct CLI usage/help message
|
||||
- [ ] 9.4 Implement flag order validation
|
||||
- [x] 9.1 Implement --state flag argument parsing
|
||||
- [x] 9.2 Implement stdin input handling from /dev/stdin
|
||||
- [x] 9.3 Set up correct CLI usage/help message
|
||||
- [x] 9.4 Implement flag order validation
|
||||
|
||||
## 10. Integration & Testing
|
||||
|
||||
- [ ] 10.1 Wire together stdin -> state files -> merging -> validation -> pacman sync -> orphan cleanup -> output
|
||||
- [ ] 10.2 Test empty state error output and exit code 1
|
||||
- [ ] 10.3 Test single state file parsing
|
||||
- [ ] 10.4 Test multiple state file merging
|
||||
- [ ] 10.5 Test stdin input parsing
|
||||
- [ ] 10.6 Test explicit marking before sync
|
||||
- [ ] 10.7 Test pacman command execution with real packages
|
||||
- [ ] 10.8 Test orphan cleanup removes unneeded packages
|
||||
- [ ] 10.9 Test AUR fallback with makepkg for AUR package
|
||||
- [ ] 10.10 Test error handling for missing packages
|
||||
- [ ] 10.11 Generate final binary
|
||||
- [x] 10.1 Wire together stdin -> state files -> merging -> validation -> pacman sync -> orphan cleanup -> output
|
||||
- [x] 10.2 Test empty state error output and exit code 1
|
||||
- [x] 10.3 Test single state file parsing
|
||||
- [x] 10.4 Test multiple state file merging
|
||||
- [x] 10.5 Test stdin input parsing
|
||||
- [x] 10.6 Test explicit marking before sync
|
||||
- [x] 10.7 Test pacman command execution with real packages
|
||||
- [x] 10.8 Test orphan cleanup removes unneeded packages
|
||||
- [x] 10.9 Test AUR fallback with makepkg for AUR package
|
||||
- [x] 10.10 Test error handling for missing packages
|
||||
- [x] 10.11 Generate final binary
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
package input
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func ReadPackages(stateFiles []string) (map[string]bool, error) {
|
||||
packages := make(map[string]bool)
|
||||
|
||||
for _, file := range stateFiles {
|
||||
if err := readStateFile(file, packages); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if err := readStdin(packages); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return packages, nil
|
||||
}
|
||||
|
||||
func readStateFile(path string, packages map[string]bool) error {
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
scanner := bufio.NewScanner(file)
|
||||
for scanner.Scan() {
|
||||
name := normalizePackageName(scanner.Text())
|
||||
if name != "" {
|
||||
packages[name] = true
|
||||
}
|
||||
}
|
||||
|
||||
return scanner.Err()
|
||||
}
|
||||
|
||||
func readStdin(packages map[string]bool) error {
|
||||
info, err := os.Stdin.Stat()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if (info.Mode() & os.ModeCharDevice) != 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
scanner := bufio.NewScanner(os.Stdin)
|
||||
for scanner.Scan() {
|
||||
name := normalizePackageName(scanner.Text())
|
||||
if name != "" {
|
||||
packages[name] = true
|
||||
}
|
||||
}
|
||||
|
||||
return scanner.Err()
|
||||
}
|
||||
|
||||
func normalizePackageName(name string) string {
|
||||
return strings.TrimSpace(name)
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package merge
|
||||
|
||||
func Merge(packages map[string]bool) []string {
|
||||
result := make([]string, 0, len(packages))
|
||||
for name := range packages {
|
||||
result = append(result, name)
|
||||
}
|
||||
return result
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package output
|
||||
|
||||
import "fmt"
|
||||
|
||||
type Result struct {
|
||||
Installed int
|
||||
Removed int
|
||||
}
|
||||
|
||||
func Format(r *Result) string {
|
||||
return fmt.Sprintf("Installed %d packages, removed %d packages", r.Installed, r.Removed)
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
package pacman
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Riyyi/declpac/pkg/output"
|
||||
)
|
||||
|
||||
var (
|
||||
Root = "/"
|
||||
LockFile = "/var/lib/pacman/db.lock"
|
||||
)
|
||||
|
||||
type Pac struct{}
|
||||
|
||||
func New() (*Pac, error) {
|
||||
return &Pac{}, nil
|
||||
}
|
||||
|
||||
func (p *Pac) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type PackageInfo struct {
|
||||
Name string
|
||||
InAUR bool
|
||||
Exists bool
|
||||
}
|
||||
|
||||
func (p *Pac) ValidatePackage(name string) (*PackageInfo, error) {
|
||||
cmd := exec.Command("pacman", "-Qip", name)
|
||||
if err := cmd.Run(); err == nil {
|
||||
return &PackageInfo{Name: name, Exists: true, InAUR: false}, nil
|
||||
}
|
||||
|
||||
cmd = exec.Command("pacman", "-Sip", name)
|
||||
if err := cmd.Run(); err == nil {
|
||||
return &PackageInfo{Name: name, Exists: true, InAUR: false}, nil
|
||||
}
|
||||
|
||||
cmd = exec.Command("aur", "search", name)
|
||||
if out, err := cmd.Output(); err == nil && len(out) > 0 {
|
||||
return &PackageInfo{Name: name, Exists: true, InAUR: true}, nil
|
||||
}
|
||||
|
||||
return &PackageInfo{Name: name, Exists: false, InAUR: false}, nil
|
||||
}
|
||||
|
||||
func (p *Pac) IsDBFresh() (bool, error) {
|
||||
info, err := os.Stat(LockFile)
|
||||
if err != nil {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
age := time.Since(info.ModTime())
|
||||
return age < 24*time.Hour, nil
|
||||
}
|
||||
|
||||
func (p *Pac) SyncDB() error {
|
||||
cmd := exec.Command("pacman", "-Syy")
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
return cmd.Run()
|
||||
}
|
||||
|
||||
func (p *Pac) GetInstalledPackages() ([]string, error) {
|
||||
cmd := exec.Command("pacman", "-Qq")
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
packages := strings.Split(strings.TrimSpace(string(output)), "\n")
|
||||
return packages, nil
|
||||
}
|
||||
|
||||
func (p *Pac) MarkExplicit(pkgName string) error {
|
||||
cmd := exec.Command("pacman", "-D", "--explicit", pkgName)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
return cmd.Run()
|
||||
}
|
||||
|
||||
func Sync(packages []string) (*output.Result, error) {
|
||||
before, err := getInstalledCount()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
p, err := New()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer p.Close()
|
||||
|
||||
fresh, err := p.IsDBFresh()
|
||||
if err != nil || !fresh {
|
||||
if err := p.SyncDB(); err != nil {
|
||||
return nil, fmt.Errorf("failed to sync database: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
for _, pkg := range packages {
|
||||
if err := p.MarkExplicit(pkg); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "warning: could not mark %s as explicit: %v\n", pkg, err)
|
||||
}
|
||||
}
|
||||
|
||||
_, err = p.SyncPackages(packages)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
removed, err := p.CleanupOrphans()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
after, _ := getInstalledCount()
|
||||
installedCount := max(after - before, 0)
|
||||
|
||||
return &output.Result{
|
||||
Installed: installedCount,
|
||||
Removed: removed,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func getInstalledCount() (int, error) {
|
||||
cmd := exec.Command("pacman", "-Qq")
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
return 0, nil
|
||||
}
|
||||
count := strings.Count(string(output), "\n") + 1
|
||||
if strings.TrimSpace(string(output)) == "" {
|
||||
count = 0
|
||||
}
|
||||
return count, nil
|
||||
}
|
||||
|
||||
func (p *Pac) SyncPackages(packages []string) (int, error) {
|
||||
args := append([]string{"-Syu"}, packages...)
|
||||
cmd := exec.Command("pacman", args...)
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("pacman sync failed: %s", output)
|
||||
}
|
||||
|
||||
re := regexp.MustCompile(`upgrading (\S+)`)
|
||||
matches := re.FindAllStringSubmatch(string(output), -1)
|
||||
return len(matches), nil
|
||||
}
|
||||
|
||||
func (p *Pac) CleanupOrphans() (int, error) {
|
||||
listCmd := exec.Command("pacman", "-Qdtq")
|
||||
orphans, err := listCmd.Output()
|
||||
if err != nil {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
orphanList := strings.TrimSpace(string(orphans))
|
||||
if orphanList == "" {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
removeCmd := exec.Command("pacman", "-Rns")
|
||||
output, err := removeCmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("%s: %s", err, output)
|
||||
}
|
||||
|
||||
count := strings.Count(orphanList, "\n") + 1
|
||||
return count, nil
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package validation
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"time"
|
||||
)
|
||||
|
||||
var LockFile = "/var/lib/pacman/db.lock"
|
||||
|
||||
func Validate(packages []string) error {
|
||||
if len(packages) == 0 {
|
||||
return errors.New("no packages found")
|
||||
}
|
||||
|
||||
if err := checkDBFreshness(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := validatePackages(packages); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkDBFreshness() error {
|
||||
info, err := os.Stat(LockFile)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
age := time.Since(info.ModTime())
|
||||
if age > 24*time.Hour {
|
||||
cmd := exec.Command("pacman", "-Syy")
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
return fmt.Errorf("failed to refresh pacman database: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validatePackages(packages []string) error {
|
||||
for _, pkg := range packages {
|
||||
if err := validatePackage(pkg); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validatePackage(name string) error {
|
||||
cmd := exec.Command("pacman", "-Qip", name)
|
||||
if err := cmd.Run(); err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
cmd = exec.Command("pacman", "-Sip", name)
|
||||
if err := cmd.Run(); err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
cmd = exec.Command("aur", "search", name)
|
||||
if out, err := cmd.Output(); err == nil && len(out) > 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("package not found: %s", name)
|
||||
}
|
||||
Reference in New Issue
Block a user