Compare commits
	
		
			95 Commits
		
	
	
		
			v0.0.4
			...
			55132437b3
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 55132437b3 | |||
| 8b26e96af0 | |||
| bd41075e8a | |||
| 8225f41d0e | |||
| db060db4b1 | |||
| 863b6e923a | |||
| 51b41bdd90 | |||
| 76e073c77f | |||
| 770881bf67 | |||
| be79eba4c2 | |||
| 3e6d5f57cf | |||
| db5c344b2d | |||
| a750f4630e | |||
| 852e98088b | |||
| c832359e43 | |||
| fae4337748 | |||
| f21d02e5d1 | |||
| ef98a37b4a | |||
| 3829dc4cc4 | |||
| efca9d116e | |||
| acb71b873e | |||
| 8366a2ce86 | |||
| 37a6c682b5 | |||
| 80187b0969 | |||
| cecaead5b0 | |||
| a84622e182 | |||
| dd86148c22 | |||
| 6aed71af24 | |||
| 422e41db40 | |||
| 8dc0e53700 | |||
| 891df91fee | |||
| b7922f9687 | |||
| 0b53c16f9c | |||
| 81013ce376 | |||
| eb4e2281fa | |||
| c9bc1e7fc3 | |||
| 22a74aa793 | |||
| 964c45ffc4 | |||
| 9c0d9d0a34 | |||
| a8a870ce86 | |||
| fe477ba6d1 | |||
| 6cd0802f64 | |||
| 19ced9795c | |||
| dbfcde0125 | |||
| 14078ec904 | |||
| 715ec7fbe8 | |||
| 3c260a7c5f | |||
| 5e8ed1c2a0 | |||
| c51248793e | |||
| 5ed538c2c4 | |||
| 56ba3ec644 | |||
| 5d411ac538 | |||
| 269222b688 | |||
| 79c0e7eb12 | |||
| fcd5c1c14b | |||
| 9fa3977d3a | |||
| c2d396d68f | |||
| 23fd711be2 | |||
| 8f9bdf69ad | |||
| dd33a30c35 | |||
| a106100312 | |||
| df72d95ab7 | |||
| 40cf0da2c0 | |||
| 66a6b30d87 | |||
| 4eac9cd8aa | |||
| 664f01a794 | |||
| 0ac8ccac81 | |||
| 56550a5135 | |||
| b4f4633f6a | |||
| 67b9801f42 | |||
| 7164aac0b4 | |||
| 4e71a5c35c | |||
| eaf49a4594 | |||
| c0439a2b90 | |||
| 43d6461c71 | |||
| 2e591d9f1c | |||
| 7d00c7b5fb | |||
| ead0c79139 | |||
| 1377ef1bc9 | |||
| a88adb43fe | |||
| 3663a8ef8f | |||
| 5f12d2aee2 | |||
| 99b70859d1 | |||
| 715fd6ccc9 | |||
| 9a06894cfa | |||
| 01a9f23a64 | |||
| 6013bdf8b9 | |||
| fbf0aa3b4f | |||
| e4b8348823 | |||
| 554987325b | |||
| 74051861bf | |||
| edf5b67825 | |||
| 84336e4a30 | |||
| b3479bdf91 | |||
| a2bd151837 | 
							
								
								
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -1,3 +1,5 @@ | ||||
| /lure | ||||
| /lure-api-server | ||||
| /cmd/lure-api-server/lure-api-server | ||||
| /dist/ | ||||
| /version.txt | ||||
| /internal/config/version.txt | ||||
|   | ||||
| @@ -10,10 +10,10 @@ builds: | ||||
|     goos: | ||||
|       - linux | ||||
|     goarch: | ||||
|       - 386 | ||||
|       - amd64 | ||||
|       - arm | ||||
|       - 386 | ||||
|       - arm64 | ||||
|       - arm | ||||
|       - riscv64 | ||||
| archives: | ||||
|   - replacements: | ||||
| @@ -36,10 +36,16 @@ nfpms: | ||||
|       - apk | ||||
|       - deb | ||||
|       - rpm | ||||
|       - archlinux | ||||
|     provides: | ||||
|       - lure | ||||
|     conflicts: | ||||
|       - lure | ||||
|     contents: | ||||
|       - src: scripts/completion/bash | ||||
|         dst: /usr/share/bash-completion/completions/lure | ||||
|       - src: scripts/completion/zsh | ||||
|         dst: /usr/share/zsh/site-functions/_lure | ||||
| aurs: | ||||
|   - name: lure-bin | ||||
|     homepage: 'https://gitea.arsenm.dev/Arsen6331/lure' | ||||
| @@ -58,7 +64,11 @@ aurs: | ||||
|       - pacman | ||||
|     package: |- | ||||
|       # binaries | ||||
|       install -Dm755 "./lure" "${pkgdir}/usr/bin/lure" | ||||
|       install -Dm755 ./lure "${pkgdir}/usr/bin/lure" | ||||
|  | ||||
|       # completions | ||||
|       install -Dm755 ./scripts/completion/bash /usr/share/bash-completion/completions/lure | ||||
|       install -Dm755 ./scripts/completion/zsh /usr/share/zsh/site-functions/_lure | ||||
| release: | ||||
|   gitea: | ||||
|     owner: Arsen6331 | ||||
|   | ||||
							
								
								
									
										8
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										8
									
								
								Makefile
									
									
									
									
									
								
							| @@ -1,3 +1,5 @@ | ||||
| PREFIX ?= /usr/local | ||||
|  | ||||
| lure: version.txt | ||||
| 	go build | ||||
|  | ||||
| @@ -5,12 +7,14 @@ clean: | ||||
| 	rm -f lure | ||||
|  | ||||
| install: lure | ||||
| 	sudo install -Dm755 lure /usr/local/bin/lure | ||||
| 	install -Dm755 lure $(DESTDIR)$(PREFIX)/bin/lure | ||||
| 	install -Dm755 scripts/completion/bash $(DESTDIR)$(PREFIX)/share/bash-completion/completions/lure | ||||
| 	install -Dm755 scripts/completion/zsh $(DESTDIR)$(PREFIX)/share/zsh/site-functions/_lure | ||||
|  | ||||
| uninstall: | ||||
| 	rm -f /usr/local/bin/lure | ||||
| 	 | ||||
| version.txt: | ||||
| 	go generate | ||||
| 	go generate ./... | ||||
|  | ||||
| .PHONY: install clean uninstall | ||||
							
								
								
									
										46
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										46
									
								
								README.md
									
									
									
									
									
								
							| @@ -1,17 +1,31 @@ | ||||
| <img src="assets/logo.png" alt="LURE Logo" width="200"> | ||||
|  | ||||
| # LURE (Linux User REpository) | ||||
|  | ||||
| [](https://goreportcard.com/report/go.arsenm.dev/lure) | ||||
| [](https://ci.arsenm.dev/Arsen6331/lure) | ||||
| [](https://aur.archlinux.org/packages/lure-bin/) | ||||
|  | ||||
| LURE is intended to bring the AUR to all distros. It is currently in an ***alpha*** state and may not be stable. It can download a repository, build packages in it using a bash script similar to [PKGBUILD](https://wiki.archlinux.org/title/PKGBUILD), and then install them using your system package manager. | ||||
| LURE is a distro-agnostic build system for Linux, similar to the [AUR](https://wiki.archlinux.org/title/Arch_User_Repository). It is currently in an ***alpha*** state and may not be stable. It is currently able to successfully and consistently build and install packages on various distributions, but there are some bugs that still need to be ironed out. | ||||
|  | ||||
| LURE is written in pure Go and has zero dependencies after it's built. The only things LURE needs are a command for privilege elevation such as `sudo`, `doas`, etc. as well as a supported package manager. Currently, LURE supports `apt`, `pacman`, `apk`, `dnf`, `yum`, and `zypper`. If a supported package manager exists on your system, it will be detected and used automatically. | ||||
| LURE is written in pure Go and has zero dependencies after building. The only things LURE requires are a command for privilege elevation such as `sudo`, `doas`, etc. as well as a supported package manager. Currently, LURE supports `apt`, `pacman`, `apk`, `dnf`, `yum`, and `zypper`. If a supported package manager exists on your system, it will be detected and used automatically. | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## Installation | ||||
|  | ||||
| ### Install script | ||||
|  | ||||
| The LURE install script will automatically download and install the appropriate LURE package on your system. To use it, simply run the following command: | ||||
|  | ||||
| ```bash | ||||
| curl https://www.arsenm.dev/lure.sh | bash | ||||
| ``` | ||||
|  | ||||
| **IMPORTANT**: This method is not recommended as it executes any code that is stored at that URL. In order to make sure nothing malicious is going to occur, download the script and inspect it before running. | ||||
|  | ||||
| ### Packages | ||||
|  | ||||
| Distro packages and binary archives are provided at the latest Gitea release: https://gitea.arsenm.dev/Arsen6331/lure/releases/latest | ||||
|  | ||||
| LURE is also available on the AUR as [lure-bin](https://aur.archlinux.org/packages/lure-bin) | ||||
| @@ -28,11 +42,7 @@ sudo make install | ||||
|  | ||||
| ## Why? | ||||
|  | ||||
| The AUR is an amazing feature, and it's one of the main reasons I use Arch on all my daily driver devices. It is really simple while providing really useful functionality. I feel such a solution shouldn't be stuck in only a single distro, so I made LURE. | ||||
|  | ||||
| Like the AUR, it uses simple bash build scripts, but it doesn't depend on bash being installed at all. It uses an embedded, pure Go implementation of bash instead. Similarly, it uses Git to download the repos and sources, but doesn't depend on Git being installed. | ||||
|  | ||||
| This means it's really easy to deploy LURE on any distro that it has support for and on any CPU architecture. It also supports and automatically detects many package managers, so it's not limited to just `pacman`. | ||||
| Arch Linux's AUR is a very useful feature. It's one of the main reasons I and many others use Arch on most of their devices. However, Arch is not always a good choice. Whether you're running a server that needs to be stable over long periods of time, or you're a beginner and feel intimidated by Arch, there are many different reasons not to use it. Such useful functionality should not be restricted to only a single distro. That is what LURE is meant to solve. | ||||
|  | ||||
| --- | ||||
|  | ||||
| @@ -42,25 +52,31 @@ The documentation for LURE is in the [docs](docs) directory in this repo. | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## Web Interface | ||||
|  | ||||
| LURE now has a web interface! It's open source, licensed under the AGPLv3 (https://gitea.arsenm.dev/Arsen6331/lure-web), and is available at https://lure.arsenm.dev. | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## Repositories | ||||
|  | ||||
| Unlike the AUR, LURE supports using multiple repos. Also unlike the AUR, LURE's repos are a single git repo containing all the build scripts. Inside each LURE repo, there should be a separate directory for each package containing a `lure.sh` script, which is a PKGBUILD-like build script for LURE. The default repository is hosted on Github: https://github.com/Arsen6331/lure-repo. | ||||
| Unlike the AUR, LURE supports third-party repositories. Also unlike the AUR, LURE's repos are single git repositories containing all the build scripts. Inside each LURE repo, there is a separate directory for each package, containing a `lure.sh` script, which is a PKGBUILD-like build script for LURE. The default repository is hosted on Github: https://github.com/Arsen6331/lure-repo, and information about its packages is displayed at https://lure.arsenm.dev/pkgs. | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## Dependencies | ||||
|  | ||||
| As mentioned before, LURE has zero dependencies after it's built. All functionality that could be pure Go is pure Go. Thanks to the following packages for making this possible: | ||||
| As mentioned before, LURE has zero dependencies after compilation. Thanks to the following projects for making this possible: | ||||
|  | ||||
| - Bash: https://github.com/mvdan/sh | ||||
| - Git: https://github.com/go-git/go-git | ||||
| - Archiver: https://github.com/mholt/archiver | ||||
| - nfpm: https://github.com/goreleaser/nfpm | ||||
| - https://github.com/mvdan/sh | ||||
| - https://github.com/go-git/go-git | ||||
| - https://github.com/mholt/archiver | ||||
| - https://github.com/goreleaser/nfpm | ||||
| - https://github.com/charmbracelet/bubbletea | ||||
| - https://gitlab.com/cznic/sqlite | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## Planned Features | ||||
|  | ||||
| - Automated install script | ||||
| - Automated docker-based testing tool | ||||
| - Web interface for repos | ||||
							
								
								
									
										
											BIN
										
									
								
								assets/logo.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								assets/logo.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 28 KiB | 
							
								
								
									
										213
									
								
								build.go
									
									
									
									
									
								
							
							
						
						
									
										213
									
								
								build.go
									
									
									
									
									
								
							| @@ -29,7 +29,6 @@ import ( | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/AlecAivazis/survey/v2" | ||||
| 	_ "github.com/goreleaser/nfpm/v2/apk" | ||||
| 	_ "github.com/goreleaser/nfpm/v2/arch" | ||||
| 	_ "github.com/goreleaser/nfpm/v2/deb" | ||||
| @@ -39,9 +38,12 @@ import ( | ||||
|  | ||||
| 	"github.com/goreleaser/nfpm/v2" | ||||
| 	"github.com/goreleaser/nfpm/v2/files" | ||||
| 	"go.arsenm.dev/logger/log" | ||||
| 	"go.arsenm.dev/lure/distro" | ||||
| 	"go.arsenm.dev/lure/download" | ||||
| 	"go.arsenm.dev/lure/internal/config" | ||||
| 	"go.arsenm.dev/lure/internal/cpu" | ||||
| 	"go.arsenm.dev/lure/internal/repos" | ||||
| 	"go.arsenm.dev/lure/internal/shutils" | ||||
| 	"go.arsenm.dev/lure/internal/shutils/decoder" | ||||
| 	"go.arsenm.dev/lure/manager" | ||||
| @@ -87,6 +89,11 @@ type Scripts struct { | ||||
| func buildCmd(c *cli.Context) error { | ||||
| 	script := c.String("script") | ||||
|  | ||||
| 	err := repos.Pull(c.Context, gdb, cfg.Repos) | ||||
| 	if err != nil { | ||||
| 		log.Fatal("Error pulling repositories").Err(err).Send() | ||||
| 	} | ||||
|  | ||||
| 	mgr := manager.Detect() | ||||
| 	if mgr == nil { | ||||
| 		log.Fatal("Unable to detect supported package manager on system").Send() | ||||
| @@ -113,6 +120,8 @@ func buildCmd(c *cli.Context) error { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // buildPackage builds the script at the given path. It returns two slices. One contains the paths | ||||
| // to the built package(s), the other contains the names of the built package(s). | ||||
| func buildPackage(ctx context.Context, script string, mgr manager.Manager) ([]string, []string, error) { | ||||
| 	info, err := distro.ParseOSRelease(ctx) | ||||
| 	if err != nil { | ||||
| @@ -140,12 +149,19 @@ func buildPackage(ctx context.Context, script string, mgr manager.Manager) ([]st | ||||
|  | ||||
| 	fl.Close() | ||||
|  | ||||
| 	env := genBuildEnv(info) | ||||
| 	scriptDir := filepath.Dir(script) | ||||
| 	env := genBuildEnv(info, scriptDir) | ||||
|  | ||||
| 	// The first pass is just used to get variable values and runs before | ||||
| 	// the script is displayed, so it is restricted so as to prevent malicious | ||||
| 	// code from executing. | ||||
| 	runner, err := interp.New( | ||||
| 		interp.Env(expand.ListEnviron(env...)), | ||||
| 		interp.StdIO(os.Stdin, os.Stdout, os.Stderr), | ||||
| 		interp.ExecHandler(helpers.ExecHandler), | ||||
| 		interp.ExecHandler(rHelpers.ExecHandler(shutils.NopExec)), | ||||
| 		interp.ReadDirHandler(shutils.RestrictedReadDir(scriptDir)), | ||||
| 		interp.StatHandler(shutils.RestrictedStat(scriptDir)), | ||||
| 		interp.OpenHandler(shutils.RestrictedOpen(scriptDir)), | ||||
| 	) | ||||
| 	if err != nil { | ||||
| 		return nil, nil, err | ||||
| @@ -164,45 +180,23 @@ func buildPackage(ctx context.Context, script string, mgr manager.Manager) ([]st | ||||
| 		dec.LikeDistros = false | ||||
| 	} | ||||
|  | ||||
| 	fn, ok := dec.GetFunc("version") | ||||
| 	if ok { | ||||
| 		log.Info("Executing version()").Send() | ||||
|  | ||||
| 		buf := &bytes.Buffer{} | ||||
|  | ||||
| 		err = fn( | ||||
| 			ctx, | ||||
| 			interp.Dir(filepath.Dir(script)), | ||||
| 			interp.StdIO(os.Stdin, buf, os.Stderr), | ||||
| 		) | ||||
| 		if err != nil { | ||||
| 			return nil, nil, err | ||||
| 		} | ||||
|  | ||||
| 		newVer := strings.TrimSpace(buf.String()) | ||||
| 		err = setVersion(ctx, runner, newVer) | ||||
| 		if err != nil { | ||||
| 			return nil, nil, err | ||||
| 		} | ||||
|  | ||||
| 		log.Info("Updating version").Str("new", newVer).Send() | ||||
| 	} | ||||
|  | ||||
| 	var vars BuildVars | ||||
| 	err = dec.DecodeVars(&vars) | ||||
| 	if err != nil { | ||||
| 		return nil, nil, err | ||||
| 	} | ||||
|  | ||||
| 	err = promptViewScript(script, vars.Name) | ||||
| 	if err != nil { | ||||
| 		log.Fatal("Failed to prompt user to view build script").Err(err).Send() | ||||
| 	} | ||||
|  | ||||
| 	if !archMatches(vars.Architectures) { | ||||
| 		var buildAnyway bool | ||||
| 		survey.AskOne( | ||||
| 			&survey.Confirm{ | ||||
| 				Message: "Your system's CPU architecture doesn't match this package. Do you want to build anyway?", | ||||
| 				Default: true, | ||||
| 			}, | ||||
| 			&buildAnyway, | ||||
| 		) | ||||
| 		buildAnyway, err := yesNoPrompt("Your system's CPU architecture doesn't match this package. Do you want to build anyway?", true) | ||||
| 		if err != nil { | ||||
| 			return nil, nil, err | ||||
| 		} | ||||
|  | ||||
| 		if !buildAnyway { | ||||
| 			os.Exit(1) | ||||
| 		} | ||||
| @@ -210,7 +204,32 @@ func buildPackage(ctx context.Context, script string, mgr manager.Manager) ([]st | ||||
|  | ||||
| 	log.Info("Building package").Str("name", vars.Name).Str("version", vars.Version).Send() | ||||
|  | ||||
| 	baseDir := filepath.Join(cacheDir, "pkgs", vars.Name) | ||||
| 	// The second pass will be used to execute the actual functions, | ||||
| 	// so it cannot be restricted. The script has already been displayed | ||||
| 	// to the user by this point, so it should be safe | ||||
| 	runner, err = interp.New( | ||||
| 		interp.Env(expand.ListEnviron(env...)), | ||||
| 		interp.StdIO(os.Stdin, os.Stdout, os.Stderr), | ||||
| 		interp.ExecHandler(helpers.ExecHandler(nil)), | ||||
| 	) | ||||
| 	if err != nil { | ||||
| 		return nil, nil, err | ||||
| 	} | ||||
|  | ||||
| 	err = runner.Run(ctx, file) | ||||
| 	if err != nil { | ||||
| 		return nil, nil, err | ||||
| 	} | ||||
|  | ||||
| 	dec = decoder.New(info, runner) | ||||
|  | ||||
| 	// If distro was changed, the list of like distros | ||||
| 	// no longer applies, so disable its use | ||||
| 	if distroChanged { | ||||
| 		dec.LikeDistros = false | ||||
| 	} | ||||
|  | ||||
| 	baseDir := filepath.Join(config.PkgsDir, vars.Name) | ||||
| 	srcdir := filepath.Join(baseDir, "src") | ||||
| 	pkgdir := filepath.Join(baseDir, "pkg") | ||||
|  | ||||
| @@ -242,15 +261,25 @@ func buildPackage(ctx context.Context, script string, mgr manager.Manager) ([]st | ||||
| 	} | ||||
|  | ||||
| 	if len(buildDeps) > 0 { | ||||
| 		found, notFound, err := repos.FindPkgs(gdb, buildDeps) | ||||
| 		if err != nil { | ||||
| 			return nil, nil, err | ||||
| 		} | ||||
|  | ||||
| 		log.Info("Installing build dependencies").Send() | ||||
| 		installPkgs(ctx, buildDeps, mgr, false) | ||||
| 		installPkgs(ctx, flattenFoundPkgs(found, "install"), notFound, mgr) | ||||
| 	} | ||||
|  | ||||
| 	var builtDeps, builtNames, repoDeps []string | ||||
| 	if len(vars.Depends) > 0 { | ||||
| 		log.Info("Installing dependencies").Send() | ||||
|  | ||||
| 		scripts, notFound := findPkgs(vars.Depends) | ||||
| 		found, notFound, err := repos.FindPkgs(gdb, vars.Depends) | ||||
| 		if err != nil { | ||||
| 			return nil, nil, err | ||||
| 		} | ||||
|  | ||||
| 		scripts := getScriptPaths(flattenFoundPkgs(found, "install")) | ||||
| 		for _, script := range scripts { | ||||
| 			pkgPaths, pkgNames, err := buildPackage(ctx, script, mgr) | ||||
| 			if err != nil { | ||||
| @@ -270,13 +299,36 @@ func buildPackage(ctx context.Context, script string, mgr manager.Manager) ([]st | ||||
| 		return nil, nil, err | ||||
| 	} | ||||
|  | ||||
| 	repodir := filepath.Dir(script) | ||||
|  | ||||
| 	err = setDirVars(ctx, runner, srcdir, pkgdir, repodir) | ||||
| 	err = setDirVars(ctx, runner, srcdir, pkgdir) | ||||
| 	if err != nil { | ||||
| 		return nil, nil, err | ||||
| 	} | ||||
|  | ||||
| 	fn, ok := dec.GetFunc("version") | ||||
| 	if ok { | ||||
| 		log.Info("Executing version()").Send() | ||||
|  | ||||
| 		buf := &bytes.Buffer{} | ||||
|  | ||||
| 		err = fn( | ||||
| 			ctx, | ||||
| 			interp.Dir(srcdir), | ||||
| 			interp.StdIO(os.Stdin, buf, os.Stderr), | ||||
| 		) | ||||
| 		if err != nil { | ||||
| 			return nil, nil, err | ||||
| 		} | ||||
|  | ||||
| 		newVer := strings.TrimSpace(buf.String()) | ||||
| 		err = setVersion(ctx, runner, newVer) | ||||
| 		if err != nil { | ||||
| 			return nil, nil, err | ||||
| 		} | ||||
| 		vars.Version = newVer | ||||
|  | ||||
| 		log.Info("Updating version").Str("new", newVer).Send() | ||||
| 	} | ||||
|  | ||||
| 	fn, ok = dec.GetFunc("prepare") | ||||
| 	if ok { | ||||
| 		log.Info("Executing prepare()").Send() | ||||
| @@ -318,7 +370,7 @@ func buildPackage(ctx context.Context, script string, mgr manager.Manager) ([]st | ||||
| 	pkgInfo := &nfpm.Info{ | ||||
| 		Name:        vars.Name, | ||||
| 		Description: vars.Description, | ||||
| 		Arch:        runtime.GOARCH, | ||||
| 		Arch:        cpu.Arch(), | ||||
| 		Version:     vars.Version, | ||||
| 		Release:     strconv.Itoa(vars.Release), | ||||
| 		Homepage:    vars.Homepage, | ||||
| @@ -342,10 +394,6 @@ func buildPackage(ctx context.Context, script string, mgr manager.Manager) ([]st | ||||
| 		pkgInfo.Arch = "all" | ||||
| 	} | ||||
|  | ||||
| 	if pkgInfo.Arch == "arm" { | ||||
| 		pkgInfo.Arch = cpu.ARMVariant() | ||||
| 	} | ||||
|  | ||||
| 	contents := []*files.Content{} | ||||
| 	filepath.Walk(pkgdir, func(path string, fi os.FileInfo, err error) error { | ||||
| 		trimmed := strings.TrimPrefix(path, pkgdir) | ||||
| @@ -379,6 +427,7 @@ func buildPackage(ctx context.Context, script string, mgr manager.Manager) ([]st | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 			link = strings.TrimPrefix(link, pkgdir) | ||||
|  | ||||
| 			contents = append(contents, &files.Content{ | ||||
| 				Source:      link, | ||||
| @@ -441,10 +490,7 @@ func buildPackage(ctx context.Context, script string, mgr manager.Manager) ([]st | ||||
| 	} | ||||
|  | ||||
| 	if len(buildDeps) > 0 { | ||||
| 		var removeBuildDeps bool | ||||
| 		err = survey.AskOne(&survey.Confirm{ | ||||
| 			Message: "Would you like to remove build dependencies?", | ||||
| 		}, &removeBuildDeps) | ||||
| 		removeBuildDeps, err := yesNoPrompt("Would you like to remove build dependencies?", false) | ||||
| 		if err != nil { | ||||
| 			return nil, nil, err | ||||
| 		} | ||||
| @@ -468,14 +514,9 @@ func buildPackage(ctx context.Context, script string, mgr manager.Manager) ([]st | ||||
| 	return pkgPaths, pkgNames, nil | ||||
| } | ||||
|  | ||||
| func genBuildEnv(info *distro.OSRelease) []string { | ||||
| func genBuildEnv(info *distro.OSRelease, scriptdir string) []string { | ||||
| 	env := os.Environ() | ||||
|  | ||||
| 	arch := runtime.GOARCH | ||||
| 	if arch == "arm" { | ||||
| 		arch = cpu.ARMVariant() | ||||
| 	} | ||||
|  | ||||
| 	env = append( | ||||
| 		env, | ||||
| 		"DISTRO_NAME="+info.Name, | ||||
| @@ -484,8 +525,10 @@ func genBuildEnv(info *distro.OSRelease) []string { | ||||
| 		"DISTRO_VERSION_ID="+info.VersionID, | ||||
| 		"DISTRO_ID_LIKE="+strings.Join(info.Like, " "), | ||||
|  | ||||
| 		"ARCH="+arch, | ||||
| 		"ARCH="+cpu.Arch(), | ||||
| 		"NCPU="+strconv.Itoa(runtime.NumCPU()), | ||||
|  | ||||
| 		"scriptdir="+scriptdir, | ||||
| 	) | ||||
|  | ||||
| 	return env | ||||
| @@ -522,8 +565,8 @@ func getSources(ctx context.Context, srcdir string, bv *BuildVars) error { | ||||
|  | ||||
| // setDirVars sets srcdir and pkgdir. It's a very hacky way of doing so, | ||||
| // but setting the runner's Env and Vars fields doesn't seem to work. | ||||
| func setDirVars(ctx context.Context, runner *interp.Runner, srcdir, pkgdir, repodir string) error { | ||||
| 	cmd := "srcdir='" + srcdir + "'\npkgdir='" + pkgdir + "'\nrepodir='" + repodir + "'\n" | ||||
| func setDirVars(ctx context.Context, runner *interp.Runner, srcdir, pkgdir string) error { | ||||
| 	cmd := "srcdir='" + srcdir + "'\npkgdir='" + pkgdir + "'\n" | ||||
| 	fl, err := syntax.NewParser().Parse(strings.NewReader(cmd), "vars") | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| @@ -567,63 +610,18 @@ func setScripts(vars *BuildVars, info *nfpm.Info, scriptDir string) { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // getBuildVars only gets the build variables, while disabling exec, stat, open, and readdir | ||||
| func getBuildVars(ctx context.Context, script string, info *distro.OSRelease) (*BuildVars, error) { | ||||
| 	fl, err := os.Open(script) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	file, err := syntax.NewParser().Parse(fl, "lure.sh") | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	fl.Close() | ||||
|  | ||||
| 	runner, err := interp.New( | ||||
| 		interp.Env(expand.ListEnviron()), | ||||
| 		interp.ExecHandler(shutils.NopExec), | ||||
| 		interp.StatHandler(shutils.NopStat), | ||||
| 		interp.OpenHandler(shutils.NopOpen), | ||||
| 		interp.ReadDirHandler(shutils.NopReadDir), | ||||
| 	) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	err = runner.Run(ctx, file) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	dec := decoder.New(info, runner) | ||||
|  | ||||
| 	var vars BuildVars | ||||
| 	err = dec.DecodeVars(&vars) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	return &vars, nil | ||||
| } | ||||
|  | ||||
| // archMatches checks if your system architecture matches | ||||
| // one of the provided architectures | ||||
| func archMatches(architectures []string) bool { | ||||
| 	if slices.Contains(architectures, "all") { | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| 	arch := runtime.GOARCH | ||||
|  | ||||
| 	if arch == "arm" { | ||||
| 		arch = cpu.ARMVariant() | ||||
| 	} | ||||
|  | ||||
| 	if slices.Contains(architectures, "arm") { | ||||
| 		architectures = append(architectures, cpu.ARMVariant()) | ||||
| 	} | ||||
|  | ||||
| 	return slices.Contains(architectures, arch) | ||||
| 	return slices.Contains(architectures, cpu.Arch()) | ||||
| } | ||||
|  | ||||
| func setVersion(ctx context.Context, r *interp.Runner, to string) error { | ||||
| @@ -634,6 +632,7 @@ func setVersion(ctx context.Context, r *interp.Runner, to string) error { | ||||
| 	return r.Run(ctx, fl) | ||||
| } | ||||
|  | ||||
| // uniq removes all duplicates from string slices | ||||
| func uniq(ss ...*[]string) { | ||||
| 	for _, s := range ss { | ||||
| 		slices.Sort(*s) | ||||
|   | ||||
							
								
								
									
										91
									
								
								cli.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										91
									
								
								cli.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,91 @@ | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"os" | ||||
|  | ||||
| 	"github.com/AlecAivazis/survey/v2" | ||||
| 	"go.arsenm.dev/logger/log" | ||||
| 	"go.arsenm.dev/lure/internal/db" | ||||
| 	"go.arsenm.dev/lure/internal/pager" | ||||
| ) | ||||
|  | ||||
| // pkgPrompt asks the user to choose between multiple packages. | ||||
| // The user may choose multiple packages. | ||||
| func pkgPrompt(options []db.Package, verb string) ([]db.Package, error) { | ||||
| 	names := make([]string, len(options)) | ||||
| 	for i, option := range options { | ||||
| 		names[i] = option.Repository + "/" + option.Name + " " + option.Version | ||||
| 	} | ||||
|  | ||||
| 	prompt := &survey.MultiSelect{ | ||||
| 		Options: names, | ||||
| 		Message: "Choose which package(s) to " + verb, | ||||
| 	} | ||||
|  | ||||
| 	var choices []int | ||||
| 	err := survey.AskOne(prompt, &choices) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	out := make([]db.Package, len(choices)) | ||||
| 	for i, choiceIndex := range choices { | ||||
| 		out[i] = options[choiceIndex] | ||||
| 	} | ||||
|  | ||||
| 	return out, nil | ||||
| } | ||||
|  | ||||
| // yesNoPrompt asks the user a yes or no question, using def as the default answer | ||||
| func yesNoPrompt(msg string, def bool) (bool, error) { | ||||
| 	var answer bool | ||||
| 	err := survey.AskOne( | ||||
| 		&survey.Confirm{ | ||||
| 			Message: msg, | ||||
| 			Default: def, | ||||
| 		}, | ||||
| 		&answer, | ||||
| 	) | ||||
| 	return answer, err | ||||
| } | ||||
|  | ||||
| func promptViewScript(script string, name string) error { | ||||
| 	view, err := yesNoPrompt("Would you like to view the build script for "+name, false) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	if view { | ||||
| 		err = showScript(script, name) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
|  | ||||
| 		cont, err := yesNoPrompt("Would you still like to continue?", false) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
|  | ||||
| 		if !cont { | ||||
| 			log.Fatal("User chose not to continue after reading script").Send() | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func showScript(path, name string) error { | ||||
| 	scriptFl, err := os.Open(path) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	defer scriptFl.Close() | ||||
|  | ||||
| 	str, err := pager.SyntaxHighlightBash(scriptFl, cfg.PagerStyle) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	pgr := pager.New(name, str) | ||||
| 	return pgr.Run() | ||||
| } | ||||
							
								
								
									
										3
									
								
								cmd/lure-api-server/Dockerfile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								cmd/lure-api-server/Dockerfile
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| FROM alpine:latest | ||||
| COPY lure-api-server /usr/bin/lure-api-server | ||||
| ENTRYPOINT lure-api-server | ||||
							
								
								
									
										3
									
								
								cmd/lure-api-server/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								cmd/lure-api-server/README.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| # lure-api-server | ||||
|  | ||||
| `lure-api-server` is the backend API server for lure-web, the web interface for LURE. | ||||
							
								
								
									
										127
									
								
								cmd/lure-api-server/api.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										127
									
								
								cmd/lure-api-server/api.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,127 @@ | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/jmoiron/sqlx" | ||||
| 	"github.com/twitchtv/twirp" | ||||
| 	"go.arsenm.dev/lure/internal/api" | ||||
| 	"go.arsenm.dev/lure/internal/config" | ||||
| 	"go.arsenm.dev/lure/internal/db" | ||||
| ) | ||||
|  | ||||
| type lureWebAPI struct { | ||||
| 	db *sqlx.DB | ||||
| } | ||||
|  | ||||
| func (l lureWebAPI) Search(ctx context.Context, req *api.SearchRequest) (*api.SearchResponse, error) { | ||||
| 	query := "(name LIKE ? OR description LIKE ? OR json_array_contains(provides, ?))" | ||||
| 	args := []any{"%" + req.Query + "%", "%" + req.Query + "%", req.Query} | ||||
|  | ||||
| 	if req.FilterValue != nil && req.FilterType != api.FILTER_TYPE_NO_FILTER { | ||||
| 		switch req.FilterType { | ||||
| 		case api.FILTER_TYPE_IN_REPOSITORY: | ||||
| 			query += " AND repository = ?" | ||||
| 		case api.FILTER_TYPE_SUPPORTS_ARCH: | ||||
| 			query += " AND json_array_contains(architectures, ?)" | ||||
| 		} | ||||
| 		args = append(args, *req.FilterValue) | ||||
| 	} | ||||
|  | ||||
| 	if req.SortBy != api.SORT_BY_UNSORTED { | ||||
| 		switch req.SortBy { | ||||
| 		case api.SORT_BY_NAME: | ||||
| 			query += " ORDER BY name" | ||||
| 		case api.SORT_BY_REPOSITORY: | ||||
| 			query += " ORDER BY repository" | ||||
| 		case api.SORT_BY_VERSION: | ||||
| 			query += " ORDER BY version" | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if req.Limit != 0 { | ||||
| 		query += " LIMIT " + strconv.FormatInt(req.Limit, 10) | ||||
| 	} | ||||
|  | ||||
| 	result, err := db.GetPkgs(l.db, query, args...) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	out := &api.SearchResponse{} | ||||
| 	for result.Next() { | ||||
| 		pkg := &db.Package{} | ||||
| 		err = result.StructScan(pkg) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		out.Packages = append(out.Packages, dbPkgToAPI(pkg)) | ||||
| 	} | ||||
|  | ||||
| 	return out, err | ||||
| } | ||||
|  | ||||
| func (l lureWebAPI) GetPkg(ctx context.Context, req *api.GetPackageRequest) (*api.Package, error) { | ||||
| 	pkg, err := db.GetPkg(l.db, "name = ? AND repository = ?", req.Name, req.Repository) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return dbPkgToAPI(pkg), nil | ||||
| } | ||||
|  | ||||
| func (l lureWebAPI) GetBuildScript(ctx context.Context, req *api.GetBuildScriptRequest) (*api.GetBuildScriptResponse, error) { | ||||
| 	if strings.ContainsAny(req.Name, "./") || strings.ContainsAny(req.Repository, "./") { | ||||
| 		return nil, twirp.NewError(twirp.InvalidArgument, "name and repository must not contain . or /") | ||||
| 	} | ||||
|  | ||||
| 	scriptPath := filepath.Join(config.RepoDir, req.Repository, req.Name, "lure.sh") | ||||
| 	_, err := os.Stat(scriptPath) | ||||
| 	if os.IsNotExist(err) { | ||||
| 		return nil, twirp.NewError(twirp.NotFound, "requested package not found") | ||||
| 	} else if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	data, err := os.ReadFile(scriptPath) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	return &api.GetBuildScriptResponse{Script: string(data)}, nil | ||||
| } | ||||
|  | ||||
| func dbPkgToAPI(pkg *db.Package) *api.Package { | ||||
| 	return &api.Package{ | ||||
| 		Name:          pkg.Name, | ||||
| 		Repository:    pkg.Repository, | ||||
| 		Version:       pkg.Version, | ||||
| 		Release:       int64(pkg.Release), | ||||
| 		Epoch:         ptr(int64(pkg.Epoch)), | ||||
| 		Description:   &pkg.Description, | ||||
| 		Homepage:      &pkg.Homepage, | ||||
| 		Maintainer:    &pkg.Maintainer, | ||||
| 		Architectures: pkg.Architectures.Val, | ||||
| 		Licenses:      pkg.Licenses.Val, | ||||
| 		Provides:      pkg.Provides.Val, | ||||
| 		Conflicts:     pkg.Conflicts.Val, | ||||
| 		Replaces:      pkg.Replaces.Val, | ||||
| 		Depends:       dbMapToAPI(pkg.Depends.Val), | ||||
| 		BuildDepends:  dbMapToAPI(pkg.BuildDepends.Val), | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func ptr[T any](v T) *T { | ||||
| 	return &v | ||||
| } | ||||
|  | ||||
| func dbMapToAPI(m map[string][]string) map[string]*api.StringList { | ||||
| 	out := make(map[string]*api.StringList, len(m)) | ||||
| 	for override, list := range m { | ||||
| 		out[override] = &api.StringList{Entries: list} | ||||
| 	} | ||||
| 	return out | ||||
| } | ||||
							
								
								
									
										16
									
								
								cmd/lure-api-server/config.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								cmd/lure-api-server/config.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"go.arsenm.dev/logger/log" | ||||
| 	"go.arsenm.dev/lure/internal/config" | ||||
| 	"go.arsenm.dev/lure/internal/types" | ||||
| ) | ||||
|  | ||||
| var cfg types.Config | ||||
|  | ||||
| func init() { | ||||
| 	err := config.Decode(&cfg) | ||||
| 	if err != nil { | ||||
| 		log.Fatal("Error decoding config file").Err(err).Send() | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										32
									
								
								cmd/lure-api-server/db.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								cmd/lure-api-server/db.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,32 @@ | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"os" | ||||
|  | ||||
| 	"github.com/jmoiron/sqlx" | ||||
| 	"go.arsenm.dev/logger/log" | ||||
| 	"go.arsenm.dev/lure/internal/config" | ||||
| 	"go.arsenm.dev/lure/internal/db" | ||||
| ) | ||||
|  | ||||
| var gdb *sqlx.DB | ||||
|  | ||||
| func init() { | ||||
| 	fi, err := os.Stat(config.DBPath) | ||||
| 	if err == nil { | ||||
| 		// TODO: This should be removed by the first stable release. | ||||
| 		if fi.IsDir() { | ||||
| 			log.Fatal("Your package cache database is using the old database engine. Please remove ~/.cache/lure and then run `lure ref`.").Send() | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	gdb, err = sqlx.Open("sqlite", config.DBPath) | ||||
| 	if err != nil { | ||||
| 		log.Fatal("Error opening database").Err(err).Send() | ||||
| 	} | ||||
|  | ||||
| 	err = db.Init(gdb) | ||||
| 	if err != nil { | ||||
| 		log.Fatal("Error initializing database").Err(err).Send() | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										14
									
								
								cmd/lure-api-server/docker.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										14
									
								
								cmd/lure-api-server/docker.sh
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| #!/bin/bash | ||||
|  | ||||
| CGO_ENABLED=0 GOARCH=amd64 GOOS=linux go build | ||||
| docker buildx build --platform linux/amd64 --tag arsen6331/lure-api-server:amd64 --no-cache . | ||||
|  | ||||
| CGO_ENABLED=0 GOARCH=arm64 GOOS=linux go build | ||||
| docker buildx build --platform linux/arm64/v8 --tag arsen6331/lure-api-server:arm64 --no-cache . | ||||
|  | ||||
| docker login | ||||
| docker push arsen6331/lure-api-server -a | ||||
|  | ||||
| docker manifest rm arsen6331/lure-api-server:latest | ||||
| docker manifest create arsen6331/lure-api-server:latest --amend arsen6331/lure-api-server:arm64 --amend arsen6331/lure-api-server:amd64 | ||||
| docker manifest push arsen6331/lure-api-server:latest | ||||
							
								
								
									
										77
									
								
								cmd/lure-api-server/main.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								cmd/lure-api-server/main.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,77 @@ | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"flag" | ||||
| 	"net" | ||||
| 	"net/http" | ||||
| 	"os" | ||||
|  | ||||
| 	"github.com/twitchtv/twirp" | ||||
| 	"go.arsenm.dev/logger" | ||||
| 	"go.arsenm.dev/logger/log" | ||||
| 	"go.arsenm.dev/lure/internal/api" | ||||
| 	"go.arsenm.dev/lure/internal/repos" | ||||
| ) | ||||
|  | ||||
| func init() { | ||||
| 	log.Logger = logger.NewPretty(os.Stderr) | ||||
| } | ||||
|  | ||||
| func main() { | ||||
| 	ctx := context.Background() | ||||
|  | ||||
| 	addr := flag.String("a", ":8080", "Listen address for API server") | ||||
| 	logFile := flag.String("l", "", "Output file for JSON log") | ||||
| 	flag.Parse() | ||||
|  | ||||
| 	if *logFile != "" { | ||||
| 		fl, err := os.Create(*logFile) | ||||
| 		if err != nil { | ||||
| 			log.Fatal("Error creating log file").Err(err).Send() | ||||
| 		} | ||||
| 		defer fl.Close() | ||||
|  | ||||
| 		log.Logger = logger.NewMulti(log.Logger, logger.NewJSON(fl)) | ||||
| 	} | ||||
|  | ||||
| 	err := repos.Pull(ctx, gdb, cfg.Repos) | ||||
| 	if err != nil { | ||||
| 		log.Fatal("Error pulling repositories").Err(err).Send() | ||||
| 	} | ||||
|  | ||||
| 	sigCh := make(chan struct{}, 200) | ||||
| 	go repoPullWorker(ctx, sigCh) | ||||
|  | ||||
| 	var handler http.Handler | ||||
|  | ||||
| 	handler = api.NewAPIServer( | ||||
| 		lureWebAPI{db: gdb}, | ||||
| 		twirp.WithServerPathPrefix(""), | ||||
| 	) | ||||
| 	handler = allowAllCORSHandler(handler) | ||||
| 	handler = handleWebhook(handler, sigCh) | ||||
|  | ||||
| 	ln, err := net.Listen("tcp", *addr) | ||||
| 	if err != nil { | ||||
| 		log.Fatal("Error starting listener").Err(err).Send() | ||||
| 	} | ||||
|  | ||||
| 	log.Info("Starting HTTP API server").Str("addr", ln.Addr().String()).Send() | ||||
|  | ||||
| 	err = http.Serve(ln, handler) | ||||
| 	if err != nil { | ||||
| 		log.Fatal("Error while running server").Err(err).Send() | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func allowAllCORSHandler(h http.Handler) http.Handler { | ||||
| 	return http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { | ||||
| 		res.Header().Set("Access-Control-Allow-Origin", "*") | ||||
| 		res.Header().Set("Access-Control-Allow-Headers", "*") | ||||
| 		if req.Method == http.MethodOptions { | ||||
| 			return | ||||
| 		} | ||||
| 		h.ServeHTTP(res, req) | ||||
| 	}) | ||||
| } | ||||
							
								
								
									
										89
									
								
								cmd/lure-api-server/webhook.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								cmd/lure-api-server/webhook.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,89 @@ | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"crypto/hmac" | ||||
| 	"crypto/sha256" | ||||
| 	"encoding/hex" | ||||
| 	"errors" | ||||
| 	"io" | ||||
| 	"net/http" | ||||
| 	"os" | ||||
| 	"strings" | ||||
|  | ||||
| 	"go.arsenm.dev/logger/log" | ||||
| 	"go.arsenm.dev/lure/internal/repos" | ||||
| ) | ||||
|  | ||||
| func handleWebhook(next http.Handler, sigCh chan<- struct{}) http.Handler { | ||||
| 	return http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { | ||||
| 		if req.URL.Path == "/webhook" { | ||||
| 			if req.Method != http.MethodPost { | ||||
| 				res.WriteHeader(http.StatusMethodNotAllowed) | ||||
| 				return | ||||
| 			} | ||||
|  | ||||
| 			if req.Header.Get("X-GitHub-Event") != "push" { | ||||
| 				http.Error(res, "Only push events are accepted by this bot", http.StatusBadRequest) | ||||
| 				return | ||||
| 			} | ||||
|  | ||||
| 			err := verifySecure(req) | ||||
| 			if err != nil { | ||||
| 				http.Error(res, err.Error(), http.StatusInternalServerError) | ||||
| 				return | ||||
| 			} | ||||
|  | ||||
| 			sigCh <- struct{}{} | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		next.ServeHTTP(res, req) | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| func verifySecure(req *http.Request) error { | ||||
| 	sigStr := req.Header.Get("X-Hub-Signature-256") | ||||
| 	sig, err := hex.DecodeString(strings.TrimPrefix(sigStr, "sha256=")) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	secretStr, ok := os.LookupEnv("LURE_API_GITHUB_SECRET") | ||||
| 	if !ok { | ||||
| 		return errors.New("LURE_API_GITHUB_SECRET must be set to the secret used for setting up the github webhook") | ||||
| 	} | ||||
| 	secret := []byte(secretStr) | ||||
|  | ||||
| 	h := hmac.New(sha256.New, secret) | ||||
| 	_, err = io.Copy(h, req.Body) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	if !hmac.Equal(h.Sum(nil), sig) { | ||||
| 		log.Warn("Insecure webhook request"). | ||||
| 			Str("from", req.RemoteAddr). | ||||
| 			Bytes("sig", sig). | ||||
| 			Bytes("hmac", h.Sum(nil)). | ||||
| 			Send() | ||||
|  | ||||
| 		return errors.New("webhook signature mismatch") | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func repoPullWorker(ctx context.Context, sigCh <-chan struct{}) { | ||||
| 	for { | ||||
| 		select { | ||||
| 		case <-sigCh: | ||||
| 			err := repos.Pull(ctx, gdb, cfg.Repos) | ||||
| 			if err != nil { | ||||
| 				log.Warn("Error while pulling repositories").Err(err).Send() | ||||
| 			} | ||||
| 		case <-ctx.Done(): | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										101
									
								
								config.go
									
									
									
									
									
								
							
							
						
						
									
										101
									
								
								config.go
									
									
									
									
									
								
							| @@ -19,107 +19,18 @@ | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
|  | ||||
| 	"github.com/pelletier/go-toml/v2" | ||||
| 	"go.arsenm.dev/logger/log" | ||||
| 	"go.arsenm.dev/lure/internal/config" | ||||
| 	"go.arsenm.dev/lure/internal/types" | ||||
| 	"go.arsenm.dev/lure/manager" | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	cacheDir string | ||||
| 	cfgPath  string | ||||
| 	config   Config | ||||
| ) | ||||
|  | ||||
| type Config struct { | ||||
| 	RootCmd string `toml:"rootCmd"` | ||||
| 	Repos   []Repo `toml:"repo"` | ||||
| } | ||||
|  | ||||
| type Repo struct { | ||||
| 	Name string `toml:"name"` | ||||
| 	URL  string `toml:"url"` | ||||
| } | ||||
|  | ||||
| var defaultConfig = Config{ | ||||
| 	RootCmd: "sudo", | ||||
| 	Repos: []Repo{ | ||||
| 		{ | ||||
| 			Name: "default", | ||||
| 			URL:  "https://github.com/Arsen6331/lure-repo.git", | ||||
| 		}, | ||||
| 	}, | ||||
| } | ||||
| var cfg types.Config | ||||
|  | ||||
| func init() { | ||||
| 	cfg, cache, err := makeDirs() | ||||
| 	if err != nil { | ||||
| 		log.Fatal("Error creating directories").Err(err).Send() | ||||
| 	} | ||||
| 	cacheDir = cache | ||||
|  | ||||
| 	cfgPath = filepath.Join(cfg, "lure.toml") | ||||
|  | ||||
| 	cfgFl, err := os.Open(cfgPath) | ||||
| 	if err != nil { | ||||
| 		log.Fatal("Error opening config file").Err(err).Send() | ||||
| 	} | ||||
| 	defer cfgFl.Close() | ||||
|  | ||||
| 	err = toml.NewDecoder(cfgFl).Decode(&config) | ||||
| 	err := config.Decode(&cfg) | ||||
| 	if err != nil { | ||||
| 		log.Fatal("Error decoding config file").Err(err).Send() | ||||
| 	} | ||||
|  | ||||
| 	manager.DefaultRootCmd = config.RootCmd | ||||
| } | ||||
|  | ||||
| func makeDirs() (string, string, error) { | ||||
| 	cfgDir, err := os.UserConfigDir() | ||||
| 	if err != nil { | ||||
| 		return "", "", err | ||||
| 	} | ||||
|  | ||||
| 	baseCfgPath := filepath.Join(cfgDir, "lure") | ||||
|  | ||||
| 	err = os.MkdirAll(baseCfgPath, 0o755) | ||||
| 	if err != nil { | ||||
| 		return "", "", err | ||||
| 	} | ||||
|  | ||||
| 	cfgPath := filepath.Join(baseCfgPath, "lure.toml") | ||||
|  | ||||
| 	if _, err := os.Stat(cfgPath); err != nil { | ||||
| 		cfgFl, err := os.Create(cfgPath) | ||||
| 		if err != nil { | ||||
| 			return "", "", err | ||||
| 		} | ||||
|  | ||||
| 		err = toml.NewEncoder(cfgFl).Encode(&defaultConfig) | ||||
| 		if err != nil { | ||||
| 			return "", "", err | ||||
| 		} | ||||
|  | ||||
| 		cfgFl.Close() | ||||
| 	} | ||||
|  | ||||
| 	cacheDir, err := os.UserCacheDir() | ||||
| 	if err != nil { | ||||
| 		return "", "", err | ||||
| 	} | ||||
|  | ||||
| 	baseCachePath := filepath.Join(cacheDir, "lure") | ||||
|  | ||||
| 	err = os.MkdirAll(filepath.Join(baseCachePath, "repo"), 0o755) | ||||
| 	if err != nil { | ||||
| 		return "", "", err | ||||
| 	} | ||||
|  | ||||
| 	err = os.MkdirAll(filepath.Join(baseCachePath, "pkgs"), 0o755) | ||||
| 	if err != nil { | ||||
| 		return "", "", err | ||||
| 	} | ||||
|  | ||||
| 	return baseCfgPath, baseCachePath, nil | ||||
| 	manager.DefaultRootCmd = cfg.RootCmd | ||||
| } | ||||
|   | ||||
							
								
								
									
										32
									
								
								db.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								db.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,32 @@ | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"os" | ||||
|  | ||||
| 	"github.com/jmoiron/sqlx" | ||||
| 	"go.arsenm.dev/logger/log" | ||||
| 	"go.arsenm.dev/lure/internal/config" | ||||
| 	"go.arsenm.dev/lure/internal/db" | ||||
| ) | ||||
|  | ||||
| var gdb *sqlx.DB | ||||
|  | ||||
| func init() { | ||||
| 	fi, err := os.Stat(config.DBPath) | ||||
| 	if err == nil { | ||||
| 		// TODO: This should be removed by the first stable release. | ||||
| 		if fi.IsDir() { | ||||
| 			log.Fatal("Your package cache database is using the old database engine. Please remove ~/.cache/lure and then run `lure ref`.").Send() | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	gdb, err = sqlx.Open("sqlite", config.DBPath) | ||||
| 	if err != nil { | ||||
| 		log.Fatal("Error opening database").Err(err).Send() | ||||
| 	} | ||||
|  | ||||
| 	err = db.Init(gdb) | ||||
| 	if err != nil { | ||||
| 		log.Fatal("Error initializing database").Err(err).Send() | ||||
| 	} | ||||
| } | ||||
| @@ -46,8 +46,16 @@ type OSRelease struct { | ||||
| 	Logo             string | ||||
| } | ||||
|  | ||||
| // OSReleaseName returns the NAME field of the | ||||
| var parsed *OSRelease | ||||
|  | ||||
| // OSReleaseName returns a struct parsed from the system's os-release | ||||
| // file. It checks /etc/os-release as well as /usr/lib/os-release. | ||||
| // The returned OSRelease struct is a singleton. | ||||
| func ParseOSRelease(ctx context.Context) (*OSRelease, error) { | ||||
| 	if parsed != nil { | ||||
| 		return parsed, nil | ||||
| 	} | ||||
|  | ||||
| 	fl, err := os.Open("/usr/lib/os-release") | ||||
| 	if err != nil { | ||||
| 		fl, err = os.Open("/etc/os-release") | ||||
| @@ -99,5 +107,6 @@ func ParseOSRelease(ctx context.Context) (*OSRelease, error) { | ||||
| 		out.Like = strings.Split(runner.Vars["ID_LIKE"].Str, " ") | ||||
| 	} | ||||
|  | ||||
| 	parsed = out | ||||
| 	return out, nil | ||||
| } | ||||
|   | ||||
| @@ -1,6 +1,8 @@ | ||||
| # LURE Docs | ||||
|  | ||||
| - [Build Scripts](build-scripts.md) | ||||
| - [Usage](usage.md) | ||||
| - [Configuration](configuration.md) | ||||
| - [Adding Packages to LURE's repo](adding-packages.md) | ||||
| - [Usage](usage.md) | ||||
| - [Packages](packages) | ||||
|     - [Build Scripts](packages/build-scripts.md) | ||||
|     - [Package Conventions](packages/conventions.md) | ||||
|     - [Adding Packages to LURE's repo](packages/adding-packages.md) | ||||
							
								
								
									
										5
									
								
								docs/packages/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								docs/packages/README.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | ||||
| # LURE Docs > Packages | ||||
|  | ||||
| - [Build Scripts](build-scripts.md) | ||||
| - [Package Conventions](conventions.md) | ||||
| - [Adding Packages to LURE's repo](adding-packages.md) | ||||
| @@ -48,6 +48,7 @@ LURE uses build scripts similar to the AUR's PKGBUILDs. This is the documentatio | ||||
|     - [install-manual](#install-manual) | ||||
|     - [install-desktop](#install-desktop) | ||||
|     - [install-library](#install-library) | ||||
|     - [git-version](#git-version) | ||||
| 
 | ||||
| --- | ||||
| 
 | ||||
| @@ -242,11 +243,11 @@ The rest of the scripts are available in all packages. | ||||
| 
 | ||||
| This section documents user-defined functions that can be added to build scripts. Any functions marked with `(*)` are required. | ||||
| 
 | ||||
| All functions except for `version()` are executed in the `$srcdir` directory | ||||
| All functions are executed in the `$srcdir` directory | ||||
| 
 | ||||
| ### version | ||||
| 
 | ||||
| The `version()` function is the first to run. It updates the `version` variable. This allows for automatically deriving the version from sources. This is most useful for git packages, which usually don't need to be changed, so their `version` variable stays the same. | ||||
| The `version()` function updates the `version` variable. This allows for automatically deriving the version from sources. This is most useful for git packages, which usually don't need to be changed, so their `version` variable stays the same. | ||||
| 
 | ||||
| An example of using this for git: | ||||
| 
 | ||||
| @@ -259,8 +260,6 @@ version() { | ||||
| 
 | ||||
| The AUR equivalent is the [`pkgver()` function](https://wiki.archlinux.org/title/VCS_package_guidelines#The_pkgver()_function) | ||||
| 
 | ||||
| This function does not run in `$srcdir` because it is executed before the source directory is even created. Instead, it runs in `$repodir`. | ||||
| 
 | ||||
| ### prepare | ||||
| 
 | ||||
| The `prepare()` function is meant to prepare the sources for building and packaging. This is the function in which patches should be applied, for example, by the `patch` command, and where tools like `go generate` should be executed. | ||||
| @@ -424,7 +423,7 @@ Examples: | ||||
| 
 | ||||
| ```bash | ||||
| ./k9s completion fish | install-completion fish k9s | ||||
| install-completion bash k9s <./k9s/completions/k9s.fish | ||||
| install-completion bash k9s <./k9s/completions/k9s.bash | ||||
| ``` | ||||
| 
 | ||||
| ### install-manual | ||||
| @@ -468,3 +467,20 @@ Examples: | ||||
| ```bash | ||||
| install-library ./${name}/build/libadldap.so | ||||
| ``` | ||||
| 
 | ||||
| ### git-version | ||||
| 
 | ||||
| `git-version` returns a version number based on the git revision of a repository. | ||||
| 
 | ||||
| If an argument is provided, it will be used as the path to the repo. Otherwise, the current directory will be used. | ||||
| 
 | ||||
| The version number will be the amount of revisions, a dot, and the short hash of the current revision. For example: `118.e4b8348`. | ||||
| 
 | ||||
| The AUR's convention includes an `r` at the beginning of the version number. This is ommitted because some distros expect the version number to start with a digit. | ||||
| 
 | ||||
| Examples: | ||||
| 
 | ||||
| ```bash | ||||
| git-version | ||||
| git-version "$srcdir/itd" | ||||
| ``` | ||||
							
								
								
									
										36
									
								
								docs/packages/conventions.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								docs/packages/conventions.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,36 @@ | ||||
| # Package Conventions | ||||
|  | ||||
| ## General | ||||
|  | ||||
| Packages should have the name(s) of what they contain in their `provides` and `conflicts` arrays. That way, they can be installed by users without needing to know the full package name. For example, there are two LURE packages for ITD: `itd-bin`, and `itd-git`. Both of them have provides and conflicts arrays specifying the two commands they install: `itd`, and `itctl`. This means that if a user wants to install ITD, they simply have to type `lure in itd` and LURE will prompt them for which one they want to install. | ||||
|  | ||||
| ## Binary packages | ||||
|  | ||||
| Packages that install download and install precompiled binaries should have a `-bin` suffix. | ||||
|  | ||||
| ## Git packages | ||||
|  | ||||
| Packages that build and install programs from source code cloned directly from Git should have a `-git` suffix. | ||||
|  | ||||
| The versions of these packages should consist of the amount of revisions followed by the current revision, separated by a period. For example: `183.80187b0`. Note that unlike the AUR, there is no `r` at the beginning. This is because some package managers refuse to install packages whose version numbers don't start with a digit. | ||||
|  | ||||
| This version number can be obtained using the following command: | ||||
|  | ||||
| ```bash | ||||
| printf "%s.%s" "$(git rev-list --count HEAD)" "$(git rev-parse --short HEAD)" | ||||
| ``` | ||||
|  | ||||
| The `version()` function for such packages should use the LURE-provided `git-version` helper command, like so: | ||||
|  | ||||
| ```bash | ||||
| version() { | ||||
|     cd "$srcdir/$name" | ||||
|     git-version | ||||
| } | ||||
| ``` | ||||
|  | ||||
| This uses LURE's embedded Git implementation, which ensures that the user doesn't need Git installed on their system in order to install `-git` packages. | ||||
|  | ||||
| ## Other packages | ||||
|  | ||||
| Packages that download sources for a specific version of a program should not have any suffix, even if those sources are downloaded from Git. | ||||
| @@ -12,6 +12,8 @@ | ||||
|     - [addrepo](#addrepo) | ||||
|     - [removerepo](#removerepo) | ||||
|     - [refresh](#refresh) | ||||
|     - [fix](#fix) | ||||
|     - [version](#version) | ||||
| - [Environment Variables](#environment-variables) | ||||
|     - [LURE_DISTRO](#lure_distro) | ||||
|     - [LURE_PKG_FORMAT](#lure_pkg_format) | ||||
| @@ -23,12 +25,18 @@ | ||||
|  | ||||
| ### install | ||||
|  | ||||
| The install command installs a command from the LURE repos. Any packages that aren't found in LURE's repos get forwarded to the system package manager for installation. | ||||
| The install command installs a package from the LURE repos. Any packages that aren't found in LURE's repos get forwarded to the system package manager for installation. | ||||
|  | ||||
| Example: | ||||
| The package arguments do not have to be exact. LURE will check the `provides` array if an exact match is not found. There is also support for using "%" as a wildcard. | ||||
|  | ||||
| If multiple packages are found, you will be prompted to select which you want to install. | ||||
|  | ||||
| Examples: | ||||
|  | ||||
| ```shell | ||||
| lure in itd-bin | ||||
| lure in itd-bin # only finds itd-bin | ||||
| lure in itd # finds itd-bin and itd-git | ||||
| lure in it% # finds itd-bin, itd-git, and itgui-git | ||||
| ``` | ||||
|  | ||||
| ### remove | ||||
| @@ -55,20 +63,36 @@ lure up | ||||
|  | ||||
| The info command displays information about a package in LURE's repos. | ||||
|  | ||||
| The package arguments do not have to be exact. LURE will check the `provides` array if an exact match is not found. There is also support for using "%" as a wildcard. | ||||
|  | ||||
| If multiple packages are found, you will be prompted to select which you want to show. | ||||
|  | ||||
| Example: | ||||
|  | ||||
| ```shell | ||||
| lure info itd-bin | ||||
| lure info itd-bin # only finds itd-bin | ||||
| lure info itd # finds itd-bin and itd-git | ||||
| lure info it% # finds itd-bin, itd-git, and itgui-git | ||||
| ``` | ||||
|  | ||||
| ### list | ||||
|  | ||||
| The list command lists all LURE repo packages as well as their versions | ||||
|  | ||||
| Example: | ||||
| This command accepts a single optional argument. This argument is a pattern to filter found packages against. | ||||
|  | ||||
| The pattern does not have to be exact. LURE will check the `provides` array if an exact match is not found. There is also support for using "%" as a wildcard. | ||||
|  | ||||
| There is a `-I` or `--installed` flag that filters out any packages that are not installed on the system | ||||
|  | ||||
| Examples: | ||||
|  | ||||
| ```shell | ||||
| lure ls | ||||
| lure ls # lists all LURE packages | ||||
| lure ls -I # lists all installed packages | ||||
| lure ls i% # lists all packages starting with "i" | ||||
| lure ls %d # lists all packages ending with "d" | ||||
| lure ls -I i% # lists all installed packages that start with "i" | ||||
| ``` | ||||
|  | ||||
| ### build | ||||
| @@ -111,6 +135,26 @@ Example: | ||||
| lure ref | ||||
| ``` | ||||
|  | ||||
| ### fix | ||||
|  | ||||
| The fix command attempts to fix issues with LURE by deleting and rebuilding LURE's cache | ||||
|  | ||||
| Example: | ||||
|  | ||||
| ```shell | ||||
| lure fix | ||||
| ``` | ||||
|  | ||||
| ### version | ||||
|  | ||||
| The version command returns the current LURE version and exits | ||||
|  | ||||
| Example: | ||||
|  | ||||
| ```shell | ||||
| lure version | ||||
| ``` | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## Environment Variables | ||||
|   | ||||
							
								
								
									
										52
									
								
								fix.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								fix.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,52 @@ | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"os" | ||||
|  | ||||
| 	"github.com/jmoiron/sqlx" | ||||
| 	"github.com/urfave/cli/v2" | ||||
| 	"go.arsenm.dev/logger/log" | ||||
| 	"go.arsenm.dev/lure/internal/config" | ||||
| 	"go.arsenm.dev/lure/internal/db" | ||||
| 	"go.arsenm.dev/lure/internal/repos" | ||||
| ) | ||||
|  | ||||
| func fixCmd(c *cli.Context) error { | ||||
| 	gdb.Close() | ||||
|  | ||||
| 	log.Info("Removing cache directory").Send() | ||||
|  | ||||
| 	err := os.RemoveAll(config.CacheDir) | ||||
| 	if err != nil { | ||||
| 		log.Fatal("Unable to remove cache directory").Err(err).Send() | ||||
| 	} | ||||
|  | ||||
| 	log.Info("Rebuilding cache").Send() | ||||
|  | ||||
| 	err = os.MkdirAll(config.CacheDir, 0o755) | ||||
| 	if err != nil { | ||||
| 		log.Fatal("Unable to create new cache directory").Err(err).Send() | ||||
| 	} | ||||
|  | ||||
| 	gdb, err = sqlx.Open("sqlite", config.DBPath) | ||||
| 	if err != nil { | ||||
| 		log.Fatal("Unable to create new database").Err(err).Send() | ||||
| 	} | ||||
|  | ||||
| 	// Make sure the DB is rebuilt when repos are pulled | ||||
| 	config.DBPresent = false | ||||
|  | ||||
| 	err = db.Init(gdb) | ||||
| 	if err != nil { | ||||
| 		log.Fatal("Error initializing database").Err(err).Send() | ||||
| 	} | ||||
|  | ||||
| 	err = repos.Pull(c.Context, gdb, cfg.Repos) | ||||
| 	if err != nil { | ||||
| 		log.Fatal("Error pulling repos").Err(err).Send() | ||||
| 	} | ||||
|  | ||||
| 	log.Info("Done").Send() | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
							
								
								
									
										54
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										54
									
								
								go.mod
									
									
									
									
									
								
							| @@ -4,16 +4,26 @@ go 1.18 | ||||
|  | ||||
| require ( | ||||
| 	github.com/AlecAivazis/survey/v2 v2.3.6 | ||||
| 	github.com/alecthomas/chroma/v2 v2.4.0 | ||||
| 	github.com/charmbracelet/bubbles v0.14.0 | ||||
| 	github.com/charmbracelet/bubbletea v0.23.1 | ||||
| 	github.com/charmbracelet/lipgloss v0.6.0 | ||||
| 	github.com/go-git/go-billy/v5 v5.3.1 | ||||
| 	github.com/go-git/go-git/v5 v5.4.2 | ||||
| 	github.com/goreleaser/nfpm/v2 v2.20.0 | ||||
| 	github.com/jmoiron/sqlx v1.3.5 | ||||
| 	github.com/mholt/archiver/v4 v4.0.0-alpha.7 | ||||
| 	github.com/mitchellh/mapstructure v1.5.0 | ||||
| 	github.com/muesli/reflow v0.3.0 | ||||
| 	github.com/pelletier/go-toml/v2 v2.0.5 | ||||
| 	github.com/twitchtv/twirp v8.1.3+incompatible | ||||
| 	github.com/urfave/cli/v2 v2.16.3 | ||||
| 	go.arsenm.dev/logger v0.0.0-20220630204155-5ba23e583f0a | ||||
| 	go.arsenm.dev/logger v0.0.0-20221220032833-ba8a3cfb4668 | ||||
| 	golang.org/x/exp v0.0.0-20220916125017-b168a2c6b86b | ||||
| 	golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f | ||||
| 	golang.org/x/sys v0.3.0 | ||||
| 	google.golang.org/protobuf v1.27.1 | ||||
| 	gopkg.in/yaml.v3 v3.0.1 | ||||
| 	modernc.org/sqlite v1.20.0 | ||||
| 	mvdan.cc/sh/v3 v3.5.1 | ||||
| ) | ||||
|  | ||||
| @@ -23,22 +33,24 @@ require ( | ||||
| 	github.com/Masterminds/semver v1.5.0 // indirect | ||||
| 	github.com/Masterminds/semver/v3 v3.1.1 // indirect | ||||
| 	github.com/Masterminds/sprig v2.22.0+incompatible // indirect | ||||
| 	github.com/Microsoft/go-winio v0.5.1 // indirect | ||||
| 	github.com/Microsoft/go-winio v0.5.2 // indirect | ||||
| 	github.com/ProtonMail/go-crypto v0.0.0-20210512092938-c05353c2d58c // indirect | ||||
| 	github.com/acomagu/bufpipe v1.0.3 // indirect | ||||
| 	github.com/andybalholm/brotli v1.0.4 // indirect | ||||
| 	github.com/aymanbagabas/go-osc52 v1.0.3 // indirect | ||||
| 	github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb // indirect | ||||
| 	github.com/cavaliergopher/cpio v1.0.1 // indirect | ||||
| 	github.com/containerd/console v1.0.3 // indirect | ||||
| 	github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect | ||||
| 	github.com/dlclark/regexp2 v1.4.0 // indirect | ||||
| 	github.com/dsnet/compress v0.0.1 // indirect | ||||
| 	github.com/emirpasic/gods v1.12.0 // indirect | ||||
| 	github.com/frankban/quicktest v1.14.3 // indirect | ||||
| 	github.com/go-git/gcfg v1.5.0 // indirect | ||||
| 	github.com/go-git/go-billy/v5 v5.3.1 // indirect | ||||
| 	github.com/gobwas/glob v0.2.3 // indirect | ||||
| 	github.com/golang/snappy v0.0.4 // indirect | ||||
| 	github.com/google/uuid v1.3.0 // indirect | ||||
| 	github.com/gookit/color v1.5.1 // indirect | ||||
| 	github.com/gookit/color v1.5.2 // indirect | ||||
| 	github.com/goreleaser/chglog v0.2.2 // indirect | ||||
| 	github.com/goreleaser/fileglob v1.3.0 // indirect | ||||
| 	github.com/huandu/xstrings v1.3.2 // indirect | ||||
| @@ -48,27 +60,47 @@ require ( | ||||
| 	github.com/kevinburke/ssh_config v1.1.0 // indirect | ||||
| 	github.com/klauspost/compress v1.15.11 // indirect | ||||
| 	github.com/klauspost/pgzip v1.2.5 // indirect | ||||
| 	github.com/mattn/go-colorable v0.1.2 // indirect | ||||
| 	github.com/mattn/go-isatty v0.0.14 // indirect | ||||
| 	github.com/lucasb-eyer/go-colorful v1.2.0 // indirect | ||||
| 	github.com/mattn/go-colorable v0.1.11 // indirect | ||||
| 	github.com/mattn/go-isatty v0.0.16 // indirect | ||||
| 	github.com/mattn/go-localereader v0.0.1 // indirect | ||||
| 	github.com/mattn/go-runewidth v0.0.14 // indirect | ||||
| 	github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect | ||||
| 	github.com/mitchellh/copystructure v1.2.0 // indirect | ||||
| 	github.com/mitchellh/go-homedir v1.1.0 // indirect | ||||
| 	github.com/mitchellh/reflectwalk v1.0.2 // indirect | ||||
| 	github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b // indirect | ||||
| 	github.com/muesli/cancelreader v0.2.2 // indirect | ||||
| 	github.com/muesli/termenv v0.13.0 // indirect | ||||
| 	github.com/nwaples/rardecode/v2 v2.0.0-beta.2 // indirect | ||||
| 	github.com/pierrec/lz4/v4 v4.1.14 // indirect | ||||
| 	github.com/pierrec/lz4/v4 v4.1.15 // indirect | ||||
| 	github.com/pkg/errors v0.9.1 // indirect | ||||
| 	github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect | ||||
| 	github.com/rivo/uniseg v0.2.0 // indirect | ||||
| 	github.com/russross/blackfriday/v2 v2.1.0 // indirect | ||||
| 	github.com/sergi/go-diff v1.2.0 // indirect | ||||
| 	github.com/sirupsen/logrus v1.9.0 // indirect | ||||
| 	github.com/therootcompany/xz v1.0.1 // indirect | ||||
| 	github.com/ulikunitz/xz v0.5.10 // indirect | ||||
| 	github.com/xanzy/ssh-agent v0.3.1 // indirect | ||||
| 	github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect | ||||
| 	github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect | ||||
| 	github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect | ||||
| 	gitlab.com/digitalxero/go-conventional-commit v1.0.7 // indirect | ||||
| 	golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 // indirect | ||||
| 	golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect | ||||
| 	golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa // indirect | ||||
| 	golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect | ||||
| 	golang.org/x/net v0.0.0-20220812174116-3211cb980234 // indirect | ||||
| 	golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect | ||||
| 	golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect | ||||
| 	golang.org/x/text v0.3.7 // indirect | ||||
| 	golang.org/x/tools v0.1.12 // indirect | ||||
| 	gopkg.in/warnings.v0 v0.1.2 // indirect | ||||
| 	lukechampine.com/uint128 v1.2.0 // indirect | ||||
| 	modernc.org/cc/v3 v3.40.0 // indirect | ||||
| 	modernc.org/ccgo/v3 v3.16.13 // indirect | ||||
| 	modernc.org/libc v1.21.5 // indirect | ||||
| 	modernc.org/mathutil v1.5.0 // indirect | ||||
| 	modernc.org/memory v1.4.0 // indirect | ||||
| 	modernc.org/opt v0.1.3 // indirect | ||||
| 	modernc.org/strutil v1.1.3 // indirect | ||||
| 	modernc.org/token v1.0.1 // indirect | ||||
| ) | ||||
|   | ||||
							
								
								
									
										140
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										140
									
								
								go.sum
									
									
									
									
									
								
							| @@ -14,8 +14,8 @@ github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuN | ||||
| github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= | ||||
| github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= | ||||
| github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= | ||||
| github.com/Microsoft/go-winio v0.5.1 h1:aPJp2QD7OOrhO5tQXqQoGSJc+DjDtWTGLOmNyAm6FgY= | ||||
| github.com/Microsoft/go-winio v0.5.1/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= | ||||
| github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA= | ||||
| github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= | ||||
| github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s= | ||||
| github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w= | ||||
| github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo= | ||||
| @@ -25,12 +25,19 @@ github.com/ProtonMail/go-mime v0.0.0-20220302105931-303f85f7fe0f h1:CGq7OieOz3wy | ||||
| github.com/ProtonMail/gopenpgp/v2 v2.2.2 h1:u2m7xt+CZWj88qK1UUNBoXeJCFJwJCZ/Ff4ymGoxEXs= | ||||
| github.com/acomagu/bufpipe v1.0.3 h1:fxAGrHZTgQ9w5QqVItgzwj235/uYZYgbXitB+dLupOk= | ||||
| github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= | ||||
| github.com/alecthomas/assert/v2 v2.2.0 h1:f6L/b7KE2bfA+9O4FL3CM/xJccDEwPVYd5fALBiuwvw= | ||||
| github.com/alecthomas/chroma/v2 v2.4.0 h1:Loe2ZjT5x3q1bcWwemqyqEi8p11/IV/ncFCeLYDpWC4= | ||||
| github.com/alecthomas/chroma/v2 v2.4.0/go.mod h1:6kHzqF5O6FUSJzBXW7fXELjb+e+7OXW4UpoPqMO7IBQ= | ||||
| github.com/alecthomas/repr v0.1.0 h1:ENn2e1+J3k09gyj2shc0dHr/yjaWSHRlrJ4DPMevDqE= | ||||
| github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY= | ||||
| github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= | ||||
| github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA= | ||||
| github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= | ||||
| github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= | ||||
| github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= | ||||
| github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= | ||||
| github.com/aymanbagabas/go-osc52 v1.0.3 h1:DTwqENW7X9arYimJrPeGZcV0ln14sGMt3pHZspWD+Mg= | ||||
| github.com/aymanbagabas/go-osc52 v1.0.3/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4= | ||||
| github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb h1:m935MPodAbYS46DG4pJSv7WO+VECIWUQ7OJYSoTrMh4= | ||||
| github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI= | ||||
| github.com/caarlos0/go-rpmutils v0.2.1-0.20211112020245-2cd62ff89b11 h1:IRrDwVlWQr6kS1U8/EtyA1+EHcc4yl8pndcqXWrEamg= | ||||
| @@ -38,6 +45,17 @@ github.com/caarlos0/testfs v0.4.4 h1:3PHvzHi5Lt+g332CiShwS8ogTgS3HjrmzZxCm6JCDr8 | ||||
| github.com/caarlos0/testfs v0.4.4/go.mod h1:bRN55zgG4XCUVVHZCeU+/Tz1Q6AxEJOEJTliBy+1DMk= | ||||
| github.com/cavaliergopher/cpio v1.0.1 h1:KQFSeKmZhv0cr+kawA3a0xTQCU4QxXF1vhU7P7av2KM= | ||||
| github.com/cavaliergopher/cpio v1.0.1/go.mod h1:pBdaqQjnvXxdS/6CvNDwIANIFSP0xRKI16PX4xejRQc= | ||||
| github.com/charmbracelet/bubbles v0.14.0 h1:DJfCwnARfWjZLvMglhSQzo76UZ2gucuHPy9jLWX45Og= | ||||
| github.com/charmbracelet/bubbles v0.14.0/go.mod h1:bbeTiXwPww4M031aGi8UK2HT9RDWoiNibae+1yCMtcc= | ||||
| github.com/charmbracelet/bubbletea v0.21.0/go.mod h1:GgmJMec61d08zXsOhqRC/AiOx4K4pmz+VIcRIm1FKr4= | ||||
| github.com/charmbracelet/bubbletea v0.23.1 h1:CYdteX1wCiCzKNUlwm25ZHBIc1GXlYFyUIte8WPvhck= | ||||
| github.com/charmbracelet/bubbletea v0.23.1/go.mod h1:JAfGK/3/pPKHTnAS8JIE2u9f61BjWTQY57RbT25aMXU= | ||||
| github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao= | ||||
| github.com/charmbracelet/lipgloss v0.5.0/go.mod h1:EZLha/HbzEt7cYqdFPovlqy5FZPj0xFhg5SaqxScmgs= | ||||
| github.com/charmbracelet/lipgloss v0.6.0 h1:1StyZB9vBSOyuZxQUcUwGr17JmojPNm87inij9N3wJY= | ||||
| github.com/charmbracelet/lipgloss v0.6.0/go.mod h1:tHh2wr34xcHjC2HCXIlGSG1jaDF0S0atAUvBMP6Ppuk= | ||||
| github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw= | ||||
| github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= | ||||
| github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= | ||||
| github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= | ||||
| github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= | ||||
| @@ -46,9 +64,12 @@ github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr | ||||
| github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||||
| github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= | ||||
| github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||||
| github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E= | ||||
| github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= | ||||
| github.com/dsnet/compress v0.0.1 h1:PlZu0n3Tuv04TzpfPbrnI0HW/YwodEXDS+oPKahKF0Q= | ||||
| github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5JflhBbQEHo= | ||||
| github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY= | ||||
| github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= | ||||
| github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg= | ||||
| github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= | ||||
| github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= | ||||
| @@ -65,17 +86,22 @@ github.com/go-git/go-git-fixtures/v4 v4.2.1 h1:n9gGL1Ct/yIw+nfsfr8s4+sbhT+Ncu2Su | ||||
| github.com/go-git/go-git-fixtures/v4 v4.2.1/go.mod h1:K8zd3kDUAykwTdDCr+I0per6Y6vMiRR/nnVTBtavnB0= | ||||
| github.com/go-git/go-git/v5 v5.4.2 h1:BXyZu9t0VkbiHtqrsvdq39UDhGJTl1h55VW6CSC4aY4= | ||||
| github.com/go-git/go-git/v5 v5.4.2/go.mod h1:gQ1kArt6d+n+BGd+/B/I74HwRTLhth2+zti4ihgckDc= | ||||
| github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= | ||||
| github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= | ||||
| github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= | ||||
| github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= | ||||
| github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= | ||||
| github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= | ||||
| github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= | ||||
| github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= | ||||
| github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= | ||||
| github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= | ||||
| github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= | ||||
| github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ= | ||||
| github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= | ||||
| github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= | ||||
| github.com/gookit/color v1.5.1 h1:Vjg2VEcdHpwq+oY63s/ksHrgJYCTo0bwWvmmYWdE9fQ= | ||||
| github.com/gookit/color v1.5.1/go.mod h1:wZFzea4X8qN6vHOSP2apMb4/+w/orMznEzYsIHPaqKM= | ||||
| github.com/gookit/color v1.5.2 h1:uLnfXcaFjlrDnQDT+NCBcfhrXqYTx/rcCa6xn01Y8yI= | ||||
| github.com/gookit/color v1.5.2/go.mod h1:w8h4bGiHeeBpvQVePTutdbERIUf3oJE5lZ8HM0UgXyg= | ||||
| github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= | ||||
| github.com/goreleaser/chglog v0.2.2 h1:V7nf07baXtGAgGevvqgW2MM4kZ6gOr12vKNSAU3VIZ0= | ||||
| github.com/goreleaser/chglog v0.2.2/go.mod h1:2s5JwtCOWjZa8AIneL+xdUl9SRuigCjRHNHsX30dupE= | ||||
| @@ -83,6 +109,7 @@ github.com/goreleaser/fileglob v1.3.0 h1:/X6J7U8lbDpQtBvGcwwPS6OpzkNVlVEsFUVRx9+ | ||||
| github.com/goreleaser/fileglob v1.3.0/go.mod h1:Jx6BoXv3mbYkEzwm9THo7xbr5egkAraxkGorbJb4RxU= | ||||
| github.com/goreleaser/nfpm/v2 v2.20.0 h1:Q/CrX54KUMluz6+M/pjTbknFd5Dao8qXi0C6ZuFCtfY= | ||||
| github.com/goreleaser/nfpm/v2 v2.20.0/go.mod h1:/Fh6XfwT/T+D4qtNC2iXmHSD/1UT20JkvBXyJ6nFmOY= | ||||
| github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= | ||||
| github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog= | ||||
| github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68= | ||||
| github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw= | ||||
| @@ -93,6 +120,8 @@ github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK | ||||
| github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= | ||||
| github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= | ||||
| github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= | ||||
| github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= | ||||
| github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= | ||||
| github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= | ||||
| github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= | ||||
| github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= | ||||
| @@ -114,14 +143,30 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= | ||||
| github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= | ||||
| github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= | ||||
| github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= | ||||
| github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= | ||||
| github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0= | ||||
| github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= | ||||
| github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= | ||||
| github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= | ||||
| github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= | ||||
| github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE= | ||||
| github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= | ||||
| github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU= | ||||
| github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= | ||||
| github.com/mattn/go-colorable v0.1.11 h1:nQ+aFkoE2TMGc0b68U2OKSexC+eq46+XwZzWXHRmPYs= | ||||
| github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= | ||||
| github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= | ||||
| github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= | ||||
| github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= | ||||
| github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= | ||||
| github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= | ||||
| github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= | ||||
| github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= | ||||
| github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= | ||||
| github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= | ||||
| github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= | ||||
| github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= | ||||
| github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= | ||||
| github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= | ||||
| github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI= | ||||
| github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4= | ||||
| github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= | ||||
| github.com/mholt/archiver/v4 v4.0.0-alpha.7 h1:xzByj8G8tj0Oq7ZYYU4+ixL/CVb5ruWCm0EZQ1PjOkE= | ||||
| @@ -134,28 +179,47 @@ github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyua | ||||
| github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= | ||||
| github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= | ||||
| github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= | ||||
| github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b h1:1XF24mVaiu7u+CFywTdcDo2ie1pzzhwjt6RHqzpMU34= | ||||
| github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho= | ||||
| github.com/muesli/cancelreader v0.2.0/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= | ||||
| github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= | ||||
| github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= | ||||
| github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68/go.mod h1:Xk+z4oIWdQqJzsxyjgl3P22oYZnHdZ8FFTHAQQt5BMQ= | ||||
| github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= | ||||
| github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= | ||||
| github.com/muesli/termenv v0.11.1-0.20220204035834-5ac8409525e0/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs= | ||||
| github.com/muesli/termenv v0.11.1-0.20220212125758-44cd13922739/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs= | ||||
| github.com/muesli/termenv v0.13.0 h1:wK20DRpJdDX8b7Ek2QfhvqhRQFZ237RGRO0RQ/Iqdy0= | ||||
| github.com/muesli/termenv v0.13.0/go.mod h1:sP1+uffeLaEYpyOTb8pLCUctGcGLnoFjSn4YJK5e2bc= | ||||
| github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= | ||||
| github.com/nwaples/rardecode/v2 v2.0.0-beta.2 h1:e3mzJFJs4k83GXBEiTaQ5HgSc/kOK8q0rDaRO0MPaOk= | ||||
| github.com/nwaples/rardecode/v2 v2.0.0-beta.2/go.mod h1:yntwv/HfMc/Hbvtq9I19D1n58te3h6KsqCf3GxyfBGY= | ||||
| github.com/pelletier/go-toml/v2 v2.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwbMiyQg= | ||||
| github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas= | ||||
| github.com/pierrec/lz4/v4 v4.1.14 h1:+fL8AQEZtz/ijeNnpduH0bROTu0O3NZAlPjQxGn8LwE= | ||||
| github.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= | ||||
| github.com/pierrec/lz4/v4 v4.1.15 h1:MO0/ucJhngq7299dKLwIMtgTfbkoSPF6AoMYDd8Q4q0= | ||||
| github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= | ||||
| github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= | ||||
| github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= | ||||
| github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= | ||||
| github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= | ||||
| github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | ||||
| github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk= | ||||
| github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= | ||||
| github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= | ||||
| github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= | ||||
| github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= | ||||
| github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= | ||||
| github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= | ||||
| github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= | ||||
| github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= | ||||
| github.com/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= | ||||
| github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= | ||||
| github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= | ||||
| github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= | ||||
| github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= | ||||
| github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM= | ||||
| github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= | ||||
| github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= | ||||
| github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= | ||||
| github.com/smartystreets/assertions v1.2.0 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N3yZFZkDFs= | ||||
| github.com/smartystreets/goconvey v1.7.2 h1:9RBaZCeXEQ3UselpuwUQHltGVXvdwm6cv1hgR6gDIPg= | ||||
| github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | ||||
| @@ -166,11 +230,12 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P | ||||
| github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= | ||||
| github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= | ||||
| github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= | ||||
| github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= | ||||
| github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= | ||||
| github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= | ||||
| github.com/therootcompany/xz v1.0.1 h1:CmOtsn1CbtmyYiusbfmhmkpAAETj0wBIH6kCYaX+xzw= | ||||
| github.com/therootcompany/xz v1.0.1/go.mod h1:3K3UH1yCKgBneZYhuQUvJ9HPD19UEXEI0BWbMn8qNMY= | ||||
| github.com/twitchtv/twirp v8.1.3+incompatible h1:+F4TdErPgSUbMZMwp13Q/KgDVuI7HJXP61mNV3/7iuU= | ||||
| github.com/twitchtv/twirp v8.1.3+incompatible/go.mod h1:RRJoFSAmTEh2weEqWtpPE3vFK5YBhA6bqp2l1kfCC5A= | ||||
| github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= | ||||
| github.com/ulikunitz/xz v0.5.10 h1:t92gobL9l3HE202wg3rlk19F6X+JOxl9BBrCCMYEYd8= | ||||
| github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= | ||||
| @@ -180,26 +245,29 @@ github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6e | ||||
| github.com/xanzy/ssh-agent v0.3.1 h1:AmzO1SSWxw73zxFZPRwaMN1MohDw8UyHnmuxyceTEGo= | ||||
| github.com/xanzy/ssh-agent v0.3.1/go.mod h1:QIE4lCeL7nkC25x+yA3LBIYfwCc1TFziCtG7cBAac6w= | ||||
| github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo= | ||||
| github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 h1:QldyIu/L63oPpyvQmHgvgickp1Yw510KJOqX7H24mg8= | ||||
| github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs= | ||||
| github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= | ||||
| github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= | ||||
| github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= | ||||
| github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= | ||||
| gitlab.com/digitalxero/go-conventional-commit v1.0.7 h1:8/dO6WWG+98PMhlZowt/YjuiKhqhGlOCwlIV8SqqGh8= | ||||
| gitlab.com/digitalxero/go-conventional-commit v1.0.7/go.mod h1:05Xc2BFsSyC5tKhK0y+P3bs0AwUtNuTp+mTpbCU/DZ0= | ||||
| go.arsenm.dev/logger v0.0.0-20220630204155-5ba23e583f0a h1:9SF7Af1OnN9BlWk9yNYCZuv/uAhDMhzfZxtWJEqy6EE= | ||||
| go.arsenm.dev/logger v0.0.0-20220630204155-5ba23e583f0a/go.mod h1:RV2qydKDdoyaRkhAq8JEGvojR8eJ6bjq5WnSIlH7gYw= | ||||
| go.arsenm.dev/logger v0.0.0-20221220032833-ba8a3cfb4668 h1:7dSmQ79slzFpcii8zgQbEStxpkTPvq3tzWc7KX5uwGc= | ||||
| go.arsenm.dev/logger v0.0.0-20221220032833-ba8a3cfb4668/go.mod h1:RV2qydKDdoyaRkhAq8JEGvojR8eJ6bjq5WnSIlH7gYw= | ||||
| golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= | ||||
| golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= | ||||
| golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= | ||||
| golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= | ||||
| golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 h1:kUhD7nTDoI3fVd9G4ORWrbV5NY0liEs/Jg2pv5f+bBA= | ||||
| golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= | ||||
| golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa h1:zuSxTR4o9y82ebqCUJYNGJbGPo6sKVl54f/TVDObg1c= | ||||
| golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= | ||||
| golang.org/x/exp v0.0.0-20220916125017-b168a2c6b86b h1:SCE/18RnFsLrjydh/R/s5EVvHoZprqEQUuoxK8q2Pc4= | ||||
| golang.org/x/exp v0.0.0-20220916125017-b168a2c6b86b/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= | ||||
| golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= | ||||
| golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= | ||||
| golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= | ||||
| golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k= | ||||
| golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0= | ||||
| golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= | ||||
| golang.org/x/net v0.0.0-20220812174116-3211cb980234 h1:RDqmgfe7SvlMWoqC3xwQ2blLO3fcWcxMa3eBLRdRW7E= | ||||
| golang.org/x/net v0.0.0-20220812174116-3211cb980234/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= | ||||
| golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw= | ||||
| golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||||
| golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||||
| @@ -216,9 +284,14 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w | ||||
| golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s= | ||||
| golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ= | ||||
| golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= | ||||
| golang.org/x/term v0.0.0-20210503060354-a79de5458b56/go.mod h1:tfny5GFUkzUvx4ps4ajbZsCe5lw1metzhBm9T3x7oIY= | ||||
| golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= | ||||
| @@ -227,7 +300,12 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= | ||||
| golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= | ||||
| golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= | ||||
| golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= | ||||
| golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= | ||||
| golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= | ||||
| golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||||
| google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= | ||||
| google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= | ||||
| google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= | ||||
| gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||||
| gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||||
| gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||||
| @@ -244,5 +322,29 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C | ||||
| gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= | ||||
| gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= | ||||
| gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= | ||||
| lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI= | ||||
| lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= | ||||
| modernc.org/cc/v3 v3.40.0 h1:P3g79IUS/93SYhtoeaHW+kRCIrYaxJ27MFPv+7kaTOw= | ||||
| modernc.org/cc/v3 v3.40.0/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0= | ||||
| modernc.org/ccgo/v3 v3.16.13 h1:Mkgdzl46i5F/CNR/Kj80Ri59hC8TKAhZrYSaqvkwzUw= | ||||
| modernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY= | ||||
| modernc.org/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk= | ||||
| modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM= | ||||
| modernc.org/libc v1.21.5 h1:xBkU9fnHV+hvZuPSRszN0AXDG4M7nwPLwTWwkYcvLCI= | ||||
| modernc.org/libc v1.21.5/go.mod h1:przBsL5RDOZajTVslkugzLBj1evTue36jEomFQOoYuI= | ||||
| modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ= | ||||
| modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= | ||||
| modernc.org/memory v1.4.0 h1:crykUfNSnMAXaOJnnxcSzbUGMqkLWjklJKkBK2nwZwk= | ||||
| modernc.org/memory v1.4.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= | ||||
| modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= | ||||
| modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= | ||||
| modernc.org/sqlite v1.20.0 h1:80zmD3BGkm8BZ5fUi/4lwJQHiO3GXgIUvZRXpoIfROY= | ||||
| modernc.org/sqlite v1.20.0/go.mod h1:EsYz8rfOvLCiYTy5ZFsOYzoCcRMu98YYkwAcCw5YIYw= | ||||
| modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY= | ||||
| modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw= | ||||
| modernc.org/tcl v1.15.0 h1:oY+JeD11qVVSgVvodMJsu7Edf8tr5E/7tuhF5cNYz34= | ||||
| modernc.org/token v1.0.1 h1:A3qvTqOwexpfZZeyI0FeGPDlSWX5pjZu9hF4lU+EKWg= | ||||
| modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= | ||||
| modernc.org/z v1.7.0 h1:xkDw/KepgEjeizO2sNco+hqYkU12taxQFqPEmgm1GWE= | ||||
| mvdan.cc/sh/v3 v3.5.1 h1:hmP3UOw4f+EYexsJjFxvU38+kn+V/s2CclXHanIBkmQ= | ||||
| mvdan.cc/sh/v3 v3.5.1/go.mod h1:1JcoyAKm1lZw/2bZje/iYKWicU/KMd0rsyJeKHnsK4E= | ||||
|   | ||||
							
								
								
									
										55
									
								
								helpers.go
									
									
									
									
									
								
							
							
						
						
									
										55
									
								
								helpers.go
									
									
									
									
									
								
							| @@ -10,6 +10,8 @@ import ( | ||||
| 	"strings" | ||||
| 	"unsafe" | ||||
|  | ||||
| 	"github.com/go-git/go-git/v5" | ||||
| 	"github.com/go-git/go-git/v5/plumbing/object" | ||||
| 	"go.arsenm.dev/lure/internal/shutils" | ||||
| 	"golang.org/x/exp/slices" | ||||
| 	"mvdan.cc/sh/v3/interp" | ||||
| @@ -30,6 +32,12 @@ var helpers = shutils.ExecFuncs{ | ||||
| 	"install-manual":       installManualCmd, | ||||
| 	"install-completion":   installCompletionCmd, | ||||
| 	"install-library":      installLibraryCmd, | ||||
| 	"git-version":          gitVersionCmd, | ||||
| } | ||||
|  | ||||
| // rHelpers contains restricted read-only helpers that don't modify any state | ||||
| var rHelpers = shutils.ExecFuncs{ | ||||
| 	"git-version": gitVersionCmd, | ||||
| } | ||||
|  | ||||
| func installHelperCmd(prefix string, perms os.FileMode) shutils.ExecFunc { | ||||
| @@ -38,7 +46,7 @@ func installHelperCmd(prefix string, perms os.FileMode) shutils.ExecFunc { | ||||
| 			return shutils.InsufficientArgsError(cmd, 1, len(args)) | ||||
| 		} | ||||
|  | ||||
| 		from := args[0] | ||||
| 		from := resolvePath(hc, args[0]) | ||||
| 		to := "" | ||||
| 		if len(args) > 1 { | ||||
| 			to = filepath.Join(hc.Env.Get("pkgdir").Str, prefix, args[1]) | ||||
| @@ -59,7 +67,7 @@ func installManualCmd(hc interp.HandlerContext, cmd string, args []string) error | ||||
| 		return shutils.InsufficientArgsError(cmd, 1, len(args)) | ||||
| 	} | ||||
|  | ||||
| 	from := args[0] | ||||
| 	from := resolvePath(hc, args[0]) | ||||
| 	number := filepath.Base(from) | ||||
| 	// The man page may be compressed with gzip. | ||||
| 	// If it is, the .gz extension must be removed to properly | ||||
| @@ -95,7 +103,7 @@ func installCompletionCmd(hc interp.HandlerContext, cmd string, args []string) e | ||||
| 	var prefix string | ||||
| 	switch shell { | ||||
| 	case "bash": | ||||
| 		prefix = "/usr/share/bash-completion/completion" | ||||
| 		prefix = "/usr/share/bash-completion/completions" | ||||
| 	case "zsh": | ||||
| 		prefix = "/usr/share/zsh/site-functions" | ||||
| 		name = "_" + name | ||||
| @@ -189,6 +197,40 @@ func getLibPrefix(hc interp.HandlerContext) string { | ||||
| 	return out | ||||
| } | ||||
|  | ||||
| func gitVersionCmd(hc interp.HandlerContext, cmd string, args []string) error { | ||||
| 	path := hc.Dir | ||||
| 	if len(args) > 0 { | ||||
| 		path = resolvePath(hc, args[0]) | ||||
| 	} | ||||
|  | ||||
| 	r, err := git.PlainOpen(path) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("git-version: %w", err) | ||||
| 	} | ||||
|  | ||||
| 	revNum := 0 | ||||
| 	commits, err := r.Log(&git.LogOptions{}) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("git-version: %w", err) | ||||
| 	} | ||||
|  | ||||
| 	commits.ForEach(func(*object.Commit) error { | ||||
| 		revNum++ | ||||
| 		return nil | ||||
| 	}) | ||||
|  | ||||
| 	HEAD, err := r.Head() | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("git-version: %w", err) | ||||
| 	} | ||||
|  | ||||
| 	hash := HEAD.Hash().String() | ||||
|  | ||||
| 	fmt.Fprintf(hc.Stdout, "%d.%s", revNum, hash[:7]) | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func helperInstall(from, to string, perms os.FileMode) error { | ||||
| 	err := os.MkdirAll(filepath.Dir(to), 0o755) | ||||
| 	if err != nil { | ||||
| @@ -210,3 +252,10 @@ func helperInstall(from, to string, perms os.FileMode) error { | ||||
| 	_, err = io.Copy(dst, src) | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| func resolvePath(hc interp.HandlerContext, path string) string { | ||||
| 	if !filepath.IsAbs(path) { | ||||
| 		return filepath.Join(hc.Dir, path) | ||||
| 	} | ||||
| 	return path | ||||
| } | ||||
|   | ||||
							
								
								
									
										63
									
								
								info.go
									
									
									
									
									
								
							
							
						
						
									
										63
									
								
								info.go
									
									
									
									
									
								
							| @@ -19,10 +19,15 @@ | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"os" | ||||
|  | ||||
| 	"go.arsenm.dev/logger/log" | ||||
|  | ||||
| 	"github.com/urfave/cli/v2" | ||||
| 	"go.arsenm.dev/lure/distro" | ||||
| 	"go.arsenm.dev/lure/internal/overrides" | ||||
| 	"go.arsenm.dev/lure/internal/repos" | ||||
| 	"gopkg.in/yaml.v3" | ||||
| ) | ||||
|  | ||||
| @@ -32,28 +37,64 @@ func infoCmd(c *cli.Context) error { | ||||
| 		log.Fatalf("Command info expected at least 1 argument, got %d", args.Len()).Send() | ||||
| 	} | ||||
|  | ||||
| 	err := repos.Pull(c.Context, gdb, cfg.Repos) | ||||
| 	if err != nil { | ||||
| 		log.Fatal("Error pulling repositories").Err(err).Send() | ||||
| 	} | ||||
|  | ||||
| 	found, _, err := repos.FindPkgs(gdb, args.Slice()) | ||||
| 	if err != nil { | ||||
| 		log.Fatal("Error finding packages").Err(err).Send() | ||||
| 	} | ||||
|  | ||||
| 	if len(found) == 0 { | ||||
| 		os.Exit(1) | ||||
| 	} | ||||
|  | ||||
| 	pkgs := flattenFoundPkgs(found, "show") | ||||
|  | ||||
| 	var names []string | ||||
| 	all := c.Bool("all") | ||||
|  | ||||
| 	if !all { | ||||
| 		info, err := distro.ParseOSRelease(c.Context) | ||||
| 		if err != nil { | ||||
| 		log.Fatal("Error parsing os-release").Err(err).Send() | ||||
| 			log.Fatal("Error parsing os-release file").Err(err).Send() | ||||
| 		} | ||||
| 		names = overrides.Resolve(info, overrides.DefaultOpts) | ||||
| 	} | ||||
|  | ||||
| 	found, err := findPkg(args.First()) | ||||
| 	if err != nil { | ||||
| 		log.Fatal("Error finding package").Err(err).Send() | ||||
| 	for _, pkg := range pkgs { | ||||
| 		if !all { | ||||
| 			depsSet := false | ||||
| 			buildDepsSet := false | ||||
| 			for _, name := range names { | ||||
| 				if deps, ok := pkg.Depends.Val[name]; ok && !depsSet { | ||||
| 					pkg.Depends.Val = map[string][]string{name: deps} | ||||
| 					depsSet = true | ||||
| 				} | ||||
|  | ||||
| 	// if multiple are matched, only use the first one | ||||
| 	script := found[0] | ||||
|  | ||||
| 	vars, err := getBuildVars(c.Context, script, info) | ||||
| 	if err != nil { | ||||
| 		log.Fatal("Error getting build variables").Err(err).Send() | ||||
| 				if buildDeps, ok := pkg.BuildDepends.Val[name]; ok && !buildDepsSet { | ||||
| 					pkg.BuildDepends.Val = map[string][]string{name: buildDeps} | ||||
| 					buildDepsSet = true | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 	err = yaml.NewEncoder(os.Stdout).Encode(vars) | ||||
| 			if !depsSet { | ||||
| 				pkg.Depends.Val = nil | ||||
| 			} | ||||
|  | ||||
| 			if !buildDepsSet { | ||||
| 				pkg.BuildDepends.Val = nil | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		err = yaml.NewEncoder(os.Stdout).Encode(pkg) | ||||
| 		if err != nil { | ||||
| 			log.Fatal("Error encoding script variables").Err(err).Send() | ||||
| 		} | ||||
| 		fmt.Println("---") | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|   | ||||
							
								
								
									
										58
									
								
								install.go
									
									
									
									
									
								
							
							
						
						
									
										58
									
								
								install.go
									
									
									
									
									
								
							| @@ -20,8 +20,14 @@ package main | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"path/filepath" | ||||
|  | ||||
| 	"go.arsenm.dev/logger/log" | ||||
|  | ||||
| 	"github.com/urfave/cli/v2" | ||||
| 	"go.arsenm.dev/lure/internal/config" | ||||
| 	"go.arsenm.dev/lure/internal/db" | ||||
| 	"go.arsenm.dev/lure/internal/repos" | ||||
| 	"go.arsenm.dev/lure/manager" | ||||
| ) | ||||
|  | ||||
| @@ -36,21 +42,23 @@ func installCmd(c *cli.Context) error { | ||||
| 		log.Fatal("Unable to detect supported package manager on system").Send() | ||||
| 	} | ||||
|  | ||||
| 	installPkgs(c.Context, args.Slice(), mgr, true) | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func installPkgs(ctx context.Context, pkgs []string, mgr manager.Manager, pull bool) { | ||||
| 	if pull { | ||||
| 		err := pullRepos(ctx) | ||||
| 	err := repos.Pull(c.Context, gdb, cfg.Repos) | ||||
| 	if err != nil { | ||||
| 		log.Fatal("Error pulling repositories").Err(err).Send() | ||||
| 	} | ||||
|  | ||||
| 	found, notFound, err := repos.FindPkgs(gdb, args.Slice()) | ||||
| 	if err != nil { | ||||
| 		log.Fatal("Error finding packages").Err(err).Send() | ||||
| 	} | ||||
|  | ||||
| 	scripts, notFound := findPkgs(pkgs) | ||||
| 	installPkgs(c.Context, flattenFoundPkgs(found, "install"), notFound, mgr) | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // installPkgs installs non-LURE packages via the package manager, then builds and installs LURE | ||||
| // packages | ||||
| func installPkgs(ctx context.Context, pkgs []db.Package, notFound []string, mgr manager.Manager) { | ||||
| 	if len(notFound) > 0 { | ||||
| 		err := mgr.Install(nil, notFound...) | ||||
| 		if err != nil { | ||||
| @@ -58,9 +66,39 @@ func installPkgs(ctx context.Context, pkgs []string, mgr manager.Manager, pull b | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	installScripts(ctx, mgr, scripts) | ||||
| 	installScripts(ctx, mgr, getScriptPaths(pkgs)) | ||||
| } | ||||
|  | ||||
| // getScriptPaths generates a slice of script paths corresponding to the | ||||
| // given packages | ||||
| func getScriptPaths(pkgs []db.Package) []string { | ||||
| 	var scripts []string | ||||
| 	for _, pkg := range pkgs { | ||||
| 		scriptPath := filepath.Join(config.RepoDir, pkg.Repository, pkg.Name, "lure.sh") | ||||
| 		scripts = append(scripts, scriptPath) | ||||
| 	} | ||||
| 	return scripts | ||||
| } | ||||
|  | ||||
| // flattenFoundPkgs attempts to flatten the map of slices of packages into a single slice | ||||
| // of packages by prompting the users if multiple packages match. | ||||
| func flattenFoundPkgs(found map[string][]db.Package, verb string) []db.Package { | ||||
| 	var outPkgs []db.Package | ||||
| 	for _, pkgs := range found { | ||||
| 		if len(pkgs) > 1 { | ||||
| 			choices, err := pkgPrompt(pkgs, verb) | ||||
| 			if err != nil { | ||||
| 				log.Fatal("Error prompting for choice of package").Send() | ||||
| 			} | ||||
| 			outPkgs = append(outPkgs, choices...) | ||||
| 		} else if len(pkgs) == 1 { | ||||
| 			outPkgs = append(outPkgs, pkgs[0]) | ||||
| 		} | ||||
| 	} | ||||
| 	return outPkgs | ||||
| } | ||||
|  | ||||
| // installScripts builds and installs LURE build scripts | ||||
| func installScripts(ctx context.Context, mgr manager.Manager, scripts []string) { | ||||
| 	for _, script := range scripts { | ||||
| 		builtPkgs, _, err := buildPackage(ctx, script, mgr) | ||||
|   | ||||
							
								
								
									
										4
									
								
								internal/api/gen.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								internal/api/gen.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | ||||
| package api | ||||
|  | ||||
| //go:generate protoc --twirp_out=. lure.proto | ||||
| //go:generate protoc --go_out=. lure.proto | ||||
							
								
								
									
										885
									
								
								internal/api/lure.pb.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										885
									
								
								internal/api/lure.pb.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,885 @@ | ||||
| // Code generated by protoc-gen-go. DO NOT EDIT. | ||||
| // versions: | ||||
| // 	protoc-gen-go v1.28.1 | ||||
| // 	protoc        v3.21.9 | ||||
| // source: lure.proto | ||||
|  | ||||
| package api | ||||
|  | ||||
| import ( | ||||
| 	protoreflect "google.golang.org/protobuf/reflect/protoreflect" | ||||
| 	protoimpl "google.golang.org/protobuf/runtime/protoimpl" | ||||
| 	reflect "reflect" | ||||
| 	sync "sync" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	// Verify that this generated code is sufficiently up-to-date. | ||||
| 	_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) | ||||
| 	// Verify that runtime/protoimpl is sufficiently up-to-date. | ||||
| 	_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) | ||||
| ) | ||||
|  | ||||
| // SORT_BY represents possible things to sort packages by | ||||
| type SORT_BY int32 | ||||
|  | ||||
| const ( | ||||
| 	SORT_BY_UNSORTED   SORT_BY = 0 | ||||
| 	SORT_BY_NAME       SORT_BY = 1 | ||||
| 	SORT_BY_REPOSITORY SORT_BY = 2 | ||||
| 	SORT_BY_VERSION    SORT_BY = 3 | ||||
| ) | ||||
|  | ||||
| // Enum value maps for SORT_BY. | ||||
| var ( | ||||
| 	SORT_BY_name = map[int32]string{ | ||||
| 		0: "UNSORTED", | ||||
| 		1: "NAME", | ||||
| 		2: "REPOSITORY", | ||||
| 		3: "VERSION", | ||||
| 	} | ||||
| 	SORT_BY_value = map[string]int32{ | ||||
| 		"UNSORTED":   0, | ||||
| 		"NAME":       1, | ||||
| 		"REPOSITORY": 2, | ||||
| 		"VERSION":    3, | ||||
| 	} | ||||
| ) | ||||
|  | ||||
| func (x SORT_BY) Enum() *SORT_BY { | ||||
| 	p := new(SORT_BY) | ||||
| 	*p = x | ||||
| 	return p | ||||
| } | ||||
|  | ||||
| func (x SORT_BY) String() string { | ||||
| 	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) | ||||
| } | ||||
|  | ||||
| func (SORT_BY) Descriptor() protoreflect.EnumDescriptor { | ||||
| 	return file_lure_proto_enumTypes[0].Descriptor() | ||||
| } | ||||
|  | ||||
| func (SORT_BY) Type() protoreflect.EnumType { | ||||
| 	return &file_lure_proto_enumTypes[0] | ||||
| } | ||||
|  | ||||
| func (x SORT_BY) Number() protoreflect.EnumNumber { | ||||
| 	return protoreflect.EnumNumber(x) | ||||
| } | ||||
|  | ||||
| // Deprecated: Use SORT_BY.Descriptor instead. | ||||
| func (SORT_BY) EnumDescriptor() ([]byte, []int) { | ||||
| 	return file_lure_proto_rawDescGZIP(), []int{0} | ||||
| } | ||||
|  | ||||
| // FILTER_TYPE represents possible filters for packages | ||||
| type FILTER_TYPE int32 | ||||
|  | ||||
| const ( | ||||
| 	FILTER_TYPE_NO_FILTER     FILTER_TYPE = 0 | ||||
| 	FILTER_TYPE_IN_REPOSITORY FILTER_TYPE = 1 | ||||
| 	FILTER_TYPE_SUPPORTS_ARCH FILTER_TYPE = 2 | ||||
| ) | ||||
|  | ||||
| // Enum value maps for FILTER_TYPE. | ||||
| var ( | ||||
| 	FILTER_TYPE_name = map[int32]string{ | ||||
| 		0: "NO_FILTER", | ||||
| 		1: "IN_REPOSITORY", | ||||
| 		2: "SUPPORTS_ARCH", | ||||
| 	} | ||||
| 	FILTER_TYPE_value = map[string]int32{ | ||||
| 		"NO_FILTER":     0, | ||||
| 		"IN_REPOSITORY": 1, | ||||
| 		"SUPPORTS_ARCH": 2, | ||||
| 	} | ||||
| ) | ||||
|  | ||||
| func (x FILTER_TYPE) Enum() *FILTER_TYPE { | ||||
| 	p := new(FILTER_TYPE) | ||||
| 	*p = x | ||||
| 	return p | ||||
| } | ||||
|  | ||||
| func (x FILTER_TYPE) String() string { | ||||
| 	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) | ||||
| } | ||||
|  | ||||
| func (FILTER_TYPE) Descriptor() protoreflect.EnumDescriptor { | ||||
| 	return file_lure_proto_enumTypes[1].Descriptor() | ||||
| } | ||||
|  | ||||
| func (FILTER_TYPE) Type() protoreflect.EnumType { | ||||
| 	return &file_lure_proto_enumTypes[1] | ||||
| } | ||||
|  | ||||
| func (x FILTER_TYPE) Number() protoreflect.EnumNumber { | ||||
| 	return protoreflect.EnumNumber(x) | ||||
| } | ||||
|  | ||||
| // Deprecated: Use FILTER_TYPE.Descriptor instead. | ||||
| func (FILTER_TYPE) EnumDescriptor() ([]byte, []int) { | ||||
| 	return file_lure_proto_rawDescGZIP(), []int{1} | ||||
| } | ||||
|  | ||||
| // SearchRequest is a request to search for packages | ||||
| type SearchRequest struct { | ||||
| 	state         protoimpl.MessageState | ||||
| 	sizeCache     protoimpl.SizeCache | ||||
| 	unknownFields protoimpl.UnknownFields | ||||
|  | ||||
| 	Query       string      `protobuf:"bytes,1,opt,name=query,proto3" json:"query,omitempty"` | ||||
| 	Limit       int64       `protobuf:"varint,2,opt,name=limit,proto3" json:"limit,omitempty"` | ||||
| 	SortBy      SORT_BY     `protobuf:"varint,3,opt,name=sort_by,json=sortBy,proto3,enum=lure.SORT_BY" json:"sort_by,omitempty"` | ||||
| 	FilterType  FILTER_TYPE `protobuf:"varint,4,opt,name=filter_type,json=filterType,proto3,enum=lure.FILTER_TYPE" json:"filter_type,omitempty"` | ||||
| 	FilterValue *string     `protobuf:"bytes,5,opt,name=filter_value,json=filterValue,proto3,oneof" json:"filter_value,omitempty"` | ||||
| } | ||||
|  | ||||
| func (x *SearchRequest) Reset() { | ||||
| 	*x = SearchRequest{} | ||||
| 	if protoimpl.UnsafeEnabled { | ||||
| 		mi := &file_lure_proto_msgTypes[0] | ||||
| 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||
| 		ms.StoreMessageInfo(mi) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (x *SearchRequest) String() string { | ||||
| 	return protoimpl.X.MessageStringOf(x) | ||||
| } | ||||
|  | ||||
| func (*SearchRequest) ProtoMessage() {} | ||||
|  | ||||
| func (x *SearchRequest) ProtoReflect() protoreflect.Message { | ||||
| 	mi := &file_lure_proto_msgTypes[0] | ||||
| 	if protoimpl.UnsafeEnabled && x != nil { | ||||
| 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||
| 		if ms.LoadMessageInfo() == nil { | ||||
| 			ms.StoreMessageInfo(mi) | ||||
| 		} | ||||
| 		return ms | ||||
| 	} | ||||
| 	return mi.MessageOf(x) | ||||
| } | ||||
|  | ||||
| // Deprecated: Use SearchRequest.ProtoReflect.Descriptor instead. | ||||
| func (*SearchRequest) Descriptor() ([]byte, []int) { | ||||
| 	return file_lure_proto_rawDescGZIP(), []int{0} | ||||
| } | ||||
|  | ||||
| func (x *SearchRequest) GetQuery() string { | ||||
| 	if x != nil { | ||||
| 		return x.Query | ||||
| 	} | ||||
| 	return "" | ||||
| } | ||||
|  | ||||
| func (x *SearchRequest) GetLimit() int64 { | ||||
| 	if x != nil { | ||||
| 		return x.Limit | ||||
| 	} | ||||
| 	return 0 | ||||
| } | ||||
|  | ||||
| func (x *SearchRequest) GetSortBy() SORT_BY { | ||||
| 	if x != nil { | ||||
| 		return x.SortBy | ||||
| 	} | ||||
| 	return SORT_BY_UNSORTED | ||||
| } | ||||
|  | ||||
| func (x *SearchRequest) GetFilterType() FILTER_TYPE { | ||||
| 	if x != nil { | ||||
| 		return x.FilterType | ||||
| 	} | ||||
| 	return FILTER_TYPE_NO_FILTER | ||||
| } | ||||
|  | ||||
| func (x *SearchRequest) GetFilterValue() string { | ||||
| 	if x != nil && x.FilterValue != nil { | ||||
| 		return *x.FilterValue | ||||
| 	} | ||||
| 	return "" | ||||
| } | ||||
|  | ||||
| // StringList contains a list of strings | ||||
| type StringList struct { | ||||
| 	state         protoimpl.MessageState | ||||
| 	sizeCache     protoimpl.SizeCache | ||||
| 	unknownFields protoimpl.UnknownFields | ||||
|  | ||||
| 	Entries []string `protobuf:"bytes,1,rep,name=entries,proto3" json:"entries,omitempty"` | ||||
| } | ||||
|  | ||||
| func (x *StringList) Reset() { | ||||
| 	*x = StringList{} | ||||
| 	if protoimpl.UnsafeEnabled { | ||||
| 		mi := &file_lure_proto_msgTypes[1] | ||||
| 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||
| 		ms.StoreMessageInfo(mi) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (x *StringList) String() string { | ||||
| 	return protoimpl.X.MessageStringOf(x) | ||||
| } | ||||
|  | ||||
| func (*StringList) ProtoMessage() {} | ||||
|  | ||||
| func (x *StringList) ProtoReflect() protoreflect.Message { | ||||
| 	mi := &file_lure_proto_msgTypes[1] | ||||
| 	if protoimpl.UnsafeEnabled && x != nil { | ||||
| 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||
| 		if ms.LoadMessageInfo() == nil { | ||||
| 			ms.StoreMessageInfo(mi) | ||||
| 		} | ||||
| 		return ms | ||||
| 	} | ||||
| 	return mi.MessageOf(x) | ||||
| } | ||||
|  | ||||
| // Deprecated: Use StringList.ProtoReflect.Descriptor instead. | ||||
| func (*StringList) Descriptor() ([]byte, []int) { | ||||
| 	return file_lure_proto_rawDescGZIP(), []int{1} | ||||
| } | ||||
|  | ||||
| func (x *StringList) GetEntries() []string { | ||||
| 	if x != nil { | ||||
| 		return x.Entries | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // Package represents a LURE package | ||||
| type Package struct { | ||||
| 	state         protoimpl.MessageState | ||||
| 	sizeCache     protoimpl.SizeCache | ||||
| 	unknownFields protoimpl.UnknownFields | ||||
|  | ||||
| 	Name          string                 `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` | ||||
| 	Repository    string                 `protobuf:"bytes,2,opt,name=repository,proto3" json:"repository,omitempty"` | ||||
| 	Version       string                 `protobuf:"bytes,3,opt,name=version,proto3" json:"version,omitempty"` | ||||
| 	Release       int64                  `protobuf:"varint,4,opt,name=release,proto3" json:"release,omitempty"` | ||||
| 	Epoch         *int64                 `protobuf:"varint,5,opt,name=epoch,proto3,oneof" json:"epoch,omitempty"` | ||||
| 	Description   *string                `protobuf:"bytes,6,opt,name=description,proto3,oneof" json:"description,omitempty"` | ||||
| 	Homepage      *string                `protobuf:"bytes,7,opt,name=homepage,proto3,oneof" json:"homepage,omitempty"` | ||||
| 	Maintainer    *string                `protobuf:"bytes,8,opt,name=maintainer,proto3,oneof" json:"maintainer,omitempty"` | ||||
| 	Architectures []string               `protobuf:"bytes,9,rep,name=architectures,proto3" json:"architectures,omitempty"` | ||||
| 	Licenses      []string               `protobuf:"bytes,10,rep,name=licenses,proto3" json:"licenses,omitempty"` | ||||
| 	Provides      []string               `protobuf:"bytes,11,rep,name=provides,proto3" json:"provides,omitempty"` | ||||
| 	Conflicts     []string               `protobuf:"bytes,12,rep,name=conflicts,proto3" json:"conflicts,omitempty"` | ||||
| 	Replaces      []string               `protobuf:"bytes,13,rep,name=replaces,proto3" json:"replaces,omitempty"` | ||||
| 	Depends       map[string]*StringList `protobuf:"bytes,14,rep,name=depends,proto3" json:"depends,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` | ||||
| 	BuildDepends  map[string]*StringList `protobuf:"bytes,15,rep,name=build_depends,json=buildDepends,proto3" json:"build_depends,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` | ||||
| } | ||||
|  | ||||
| func (x *Package) Reset() { | ||||
| 	*x = Package{} | ||||
| 	if protoimpl.UnsafeEnabled { | ||||
| 		mi := &file_lure_proto_msgTypes[2] | ||||
| 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||
| 		ms.StoreMessageInfo(mi) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (x *Package) String() string { | ||||
| 	return protoimpl.X.MessageStringOf(x) | ||||
| } | ||||
|  | ||||
| func (*Package) ProtoMessage() {} | ||||
|  | ||||
| func (x *Package) ProtoReflect() protoreflect.Message { | ||||
| 	mi := &file_lure_proto_msgTypes[2] | ||||
| 	if protoimpl.UnsafeEnabled && x != nil { | ||||
| 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||
| 		if ms.LoadMessageInfo() == nil { | ||||
| 			ms.StoreMessageInfo(mi) | ||||
| 		} | ||||
| 		return ms | ||||
| 	} | ||||
| 	return mi.MessageOf(x) | ||||
| } | ||||
|  | ||||
| // Deprecated: Use Package.ProtoReflect.Descriptor instead. | ||||
| func (*Package) Descriptor() ([]byte, []int) { | ||||
| 	return file_lure_proto_rawDescGZIP(), []int{2} | ||||
| } | ||||
|  | ||||
| func (x *Package) GetName() string { | ||||
| 	if x != nil { | ||||
| 		return x.Name | ||||
| 	} | ||||
| 	return "" | ||||
| } | ||||
|  | ||||
| func (x *Package) GetRepository() string { | ||||
| 	if x != nil { | ||||
| 		return x.Repository | ||||
| 	} | ||||
| 	return "" | ||||
| } | ||||
|  | ||||
| func (x *Package) GetVersion() string { | ||||
| 	if x != nil { | ||||
| 		return x.Version | ||||
| 	} | ||||
| 	return "" | ||||
| } | ||||
|  | ||||
| func (x *Package) GetRelease() int64 { | ||||
| 	if x != nil { | ||||
| 		return x.Release | ||||
| 	} | ||||
| 	return 0 | ||||
| } | ||||
|  | ||||
| func (x *Package) GetEpoch() int64 { | ||||
| 	if x != nil && x.Epoch != nil { | ||||
| 		return *x.Epoch | ||||
| 	} | ||||
| 	return 0 | ||||
| } | ||||
|  | ||||
| func (x *Package) GetDescription() string { | ||||
| 	if x != nil && x.Description != nil { | ||||
| 		return *x.Description | ||||
| 	} | ||||
| 	return "" | ||||
| } | ||||
|  | ||||
| func (x *Package) GetHomepage() string { | ||||
| 	if x != nil && x.Homepage != nil { | ||||
| 		return *x.Homepage | ||||
| 	} | ||||
| 	return "" | ||||
| } | ||||
|  | ||||
| func (x *Package) GetMaintainer() string { | ||||
| 	if x != nil && x.Maintainer != nil { | ||||
| 		return *x.Maintainer | ||||
| 	} | ||||
| 	return "" | ||||
| } | ||||
|  | ||||
| func (x *Package) GetArchitectures() []string { | ||||
| 	if x != nil { | ||||
| 		return x.Architectures | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (x *Package) GetLicenses() []string { | ||||
| 	if x != nil { | ||||
| 		return x.Licenses | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (x *Package) GetProvides() []string { | ||||
| 	if x != nil { | ||||
| 		return x.Provides | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (x *Package) GetConflicts() []string { | ||||
| 	if x != nil { | ||||
| 		return x.Conflicts | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (x *Package) GetReplaces() []string { | ||||
| 	if x != nil { | ||||
| 		return x.Replaces | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (x *Package) GetDepends() map[string]*StringList { | ||||
| 	if x != nil { | ||||
| 		return x.Depends | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (x *Package) GetBuildDepends() map[string]*StringList { | ||||
| 	if x != nil { | ||||
| 		return x.BuildDepends | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| type GetPackageRequest struct { | ||||
| 	state         protoimpl.MessageState | ||||
| 	sizeCache     protoimpl.SizeCache | ||||
| 	unknownFields protoimpl.UnknownFields | ||||
|  | ||||
| 	Name       string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` | ||||
| 	Repository string `protobuf:"bytes,2,opt,name=repository,proto3" json:"repository,omitempty"` | ||||
| } | ||||
|  | ||||
| func (x *GetPackageRequest) Reset() { | ||||
| 	*x = GetPackageRequest{} | ||||
| 	if protoimpl.UnsafeEnabled { | ||||
| 		mi := &file_lure_proto_msgTypes[3] | ||||
| 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||
| 		ms.StoreMessageInfo(mi) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (x *GetPackageRequest) String() string { | ||||
| 	return protoimpl.X.MessageStringOf(x) | ||||
| } | ||||
|  | ||||
| func (*GetPackageRequest) ProtoMessage() {} | ||||
|  | ||||
| func (x *GetPackageRequest) ProtoReflect() protoreflect.Message { | ||||
| 	mi := &file_lure_proto_msgTypes[3] | ||||
| 	if protoimpl.UnsafeEnabled && x != nil { | ||||
| 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||
| 		if ms.LoadMessageInfo() == nil { | ||||
| 			ms.StoreMessageInfo(mi) | ||||
| 		} | ||||
| 		return ms | ||||
| 	} | ||||
| 	return mi.MessageOf(x) | ||||
| } | ||||
|  | ||||
| // Deprecated: Use GetPackageRequest.ProtoReflect.Descriptor instead. | ||||
| func (*GetPackageRequest) Descriptor() ([]byte, []int) { | ||||
| 	return file_lure_proto_rawDescGZIP(), []int{3} | ||||
| } | ||||
|  | ||||
| func (x *GetPackageRequest) GetName() string { | ||||
| 	if x != nil { | ||||
| 		return x.Name | ||||
| 	} | ||||
| 	return "" | ||||
| } | ||||
|  | ||||
| func (x *GetPackageRequest) GetRepository() string { | ||||
| 	if x != nil { | ||||
| 		return x.Repository | ||||
| 	} | ||||
| 	return "" | ||||
| } | ||||
|  | ||||
| // SearchResponse contains returned packages | ||||
| type SearchResponse struct { | ||||
| 	state         protoimpl.MessageState | ||||
| 	sizeCache     protoimpl.SizeCache | ||||
| 	unknownFields protoimpl.UnknownFields | ||||
|  | ||||
| 	Packages []*Package `protobuf:"bytes,1,rep,name=packages,proto3" json:"packages,omitempty"` | ||||
| } | ||||
|  | ||||
| func (x *SearchResponse) Reset() { | ||||
| 	*x = SearchResponse{} | ||||
| 	if protoimpl.UnsafeEnabled { | ||||
| 		mi := &file_lure_proto_msgTypes[4] | ||||
| 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||
| 		ms.StoreMessageInfo(mi) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (x *SearchResponse) String() string { | ||||
| 	return protoimpl.X.MessageStringOf(x) | ||||
| } | ||||
|  | ||||
| func (*SearchResponse) ProtoMessage() {} | ||||
|  | ||||
| func (x *SearchResponse) ProtoReflect() protoreflect.Message { | ||||
| 	mi := &file_lure_proto_msgTypes[4] | ||||
| 	if protoimpl.UnsafeEnabled && x != nil { | ||||
| 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||
| 		if ms.LoadMessageInfo() == nil { | ||||
| 			ms.StoreMessageInfo(mi) | ||||
| 		} | ||||
| 		return ms | ||||
| 	} | ||||
| 	return mi.MessageOf(x) | ||||
| } | ||||
|  | ||||
| // Deprecated: Use SearchResponse.ProtoReflect.Descriptor instead. | ||||
| func (*SearchResponse) Descriptor() ([]byte, []int) { | ||||
| 	return file_lure_proto_rawDescGZIP(), []int{4} | ||||
| } | ||||
|  | ||||
| func (x *SearchResponse) GetPackages() []*Package { | ||||
| 	if x != nil { | ||||
| 		return x.Packages | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| type GetBuildScriptRequest struct { | ||||
| 	state         protoimpl.MessageState | ||||
| 	sizeCache     protoimpl.SizeCache | ||||
| 	unknownFields protoimpl.UnknownFields | ||||
|  | ||||
| 	Name       string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` | ||||
| 	Repository string `protobuf:"bytes,2,opt,name=repository,proto3" json:"repository,omitempty"` | ||||
| } | ||||
|  | ||||
| func (x *GetBuildScriptRequest) Reset() { | ||||
| 	*x = GetBuildScriptRequest{} | ||||
| 	if protoimpl.UnsafeEnabled { | ||||
| 		mi := &file_lure_proto_msgTypes[5] | ||||
| 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||
| 		ms.StoreMessageInfo(mi) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (x *GetBuildScriptRequest) String() string { | ||||
| 	return protoimpl.X.MessageStringOf(x) | ||||
| } | ||||
|  | ||||
| func (*GetBuildScriptRequest) ProtoMessage() {} | ||||
|  | ||||
| func (x *GetBuildScriptRequest) ProtoReflect() protoreflect.Message { | ||||
| 	mi := &file_lure_proto_msgTypes[5] | ||||
| 	if protoimpl.UnsafeEnabled && x != nil { | ||||
| 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||
| 		if ms.LoadMessageInfo() == nil { | ||||
| 			ms.StoreMessageInfo(mi) | ||||
| 		} | ||||
| 		return ms | ||||
| 	} | ||||
| 	return mi.MessageOf(x) | ||||
| } | ||||
|  | ||||
| // Deprecated: Use GetBuildScriptRequest.ProtoReflect.Descriptor instead. | ||||
| func (*GetBuildScriptRequest) Descriptor() ([]byte, []int) { | ||||
| 	return file_lure_proto_rawDescGZIP(), []int{5} | ||||
| } | ||||
|  | ||||
| func (x *GetBuildScriptRequest) GetName() string { | ||||
| 	if x != nil { | ||||
| 		return x.Name | ||||
| 	} | ||||
| 	return "" | ||||
| } | ||||
|  | ||||
| func (x *GetBuildScriptRequest) GetRepository() string { | ||||
| 	if x != nil { | ||||
| 		return x.Repository | ||||
| 	} | ||||
| 	return "" | ||||
| } | ||||
|  | ||||
| type GetBuildScriptResponse struct { | ||||
| 	state         protoimpl.MessageState | ||||
| 	sizeCache     protoimpl.SizeCache | ||||
| 	unknownFields protoimpl.UnknownFields | ||||
|  | ||||
| 	Script string `protobuf:"bytes,1,opt,name=script,proto3" json:"script,omitempty"` | ||||
| } | ||||
|  | ||||
| func (x *GetBuildScriptResponse) Reset() { | ||||
| 	*x = GetBuildScriptResponse{} | ||||
| 	if protoimpl.UnsafeEnabled { | ||||
| 		mi := &file_lure_proto_msgTypes[6] | ||||
| 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||
| 		ms.StoreMessageInfo(mi) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (x *GetBuildScriptResponse) String() string { | ||||
| 	return protoimpl.X.MessageStringOf(x) | ||||
| } | ||||
|  | ||||
| func (*GetBuildScriptResponse) ProtoMessage() {} | ||||
|  | ||||
| func (x *GetBuildScriptResponse) ProtoReflect() protoreflect.Message { | ||||
| 	mi := &file_lure_proto_msgTypes[6] | ||||
| 	if protoimpl.UnsafeEnabled && x != nil { | ||||
| 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||
| 		if ms.LoadMessageInfo() == nil { | ||||
| 			ms.StoreMessageInfo(mi) | ||||
| 		} | ||||
| 		return ms | ||||
| 	} | ||||
| 	return mi.MessageOf(x) | ||||
| } | ||||
|  | ||||
| // Deprecated: Use GetBuildScriptResponse.ProtoReflect.Descriptor instead. | ||||
| func (*GetBuildScriptResponse) Descriptor() ([]byte, []int) { | ||||
| 	return file_lure_proto_rawDescGZIP(), []int{6} | ||||
| } | ||||
|  | ||||
| func (x *GetBuildScriptResponse) GetScript() string { | ||||
| 	if x != nil { | ||||
| 		return x.Script | ||||
| 	} | ||||
| 	return "" | ||||
| } | ||||
|  | ||||
| var File_lure_proto protoreflect.FileDescriptor | ||||
|  | ||||
| var file_lure_proto_rawDesc = []byte{ | ||||
| 	0x0a, 0x0a, 0x6c, 0x75, 0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x04, 0x6c, 0x75, | ||||
| 	0x72, 0x65, 0x22, 0xd0, 0x01, 0x0a, 0x0d, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x71, | ||||
| 	0x75, 0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x01, 0x20, | ||||
| 	0x01, 0x28, 0x09, 0x52, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, | ||||
| 	0x6d, 0x69, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, | ||||
| 	0x12, 0x26, 0x0a, 0x07, 0x73, 0x6f, 0x72, 0x74, 0x5f, 0x62, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, | ||||
| 	0x0e, 0x32, 0x0d, 0x2e, 0x6c, 0x75, 0x72, 0x65, 0x2e, 0x53, 0x4f, 0x52, 0x54, 0x5f, 0x42, 0x59, | ||||
| 	0x52, 0x06, 0x73, 0x6f, 0x72, 0x74, 0x42, 0x79, 0x12, 0x32, 0x0a, 0x0b, 0x66, 0x69, 0x6c, 0x74, | ||||
| 	0x65, 0x72, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x11, 0x2e, | ||||
| 	0x6c, 0x75, 0x72, 0x65, 0x2e, 0x46, 0x49, 0x4c, 0x54, 0x45, 0x52, 0x5f, 0x54, 0x59, 0x50, 0x45, | ||||
| 	0x52, 0x0a, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x54, 0x79, 0x70, 0x65, 0x12, 0x26, 0x0a, 0x0c, | ||||
| 	0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x05, 0x20, 0x01, | ||||
| 	0x28, 0x09, 0x48, 0x00, 0x52, 0x0b, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, | ||||
| 	0x65, 0x88, 0x01, 0x01, 0x42, 0x0f, 0x0a, 0x0d, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x5f, | ||||
| 	0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x26, 0x0a, 0x0a, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x4c, | ||||
| 	0x69, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x18, 0x01, | ||||
| 	0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x65, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x22, 0xe4, 0x05, | ||||
| 	0x0a, 0x07, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, | ||||
| 	0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1e, 0x0a, | ||||
| 	0x0a, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, | ||||
| 	0x09, 0x52, 0x0a, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x18, 0x0a, | ||||
| 	0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, | ||||
| 	0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x72, 0x65, 0x6c, 0x65, 0x61, | ||||
| 	0x73, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, | ||||
| 	0x65, 0x12, 0x19, 0x0a, 0x05, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, | ||||
| 	0x48, 0x00, 0x52, 0x05, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x88, 0x01, 0x01, 0x12, 0x25, 0x0a, 0x0b, | ||||
| 	0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, | ||||
| 	0x09, 0x48, 0x01, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, | ||||
| 	0x88, 0x01, 0x01, 0x12, 0x1f, 0x0a, 0x08, 0x68, 0x6f, 0x6d, 0x65, 0x70, 0x61, 0x67, 0x65, 0x18, | ||||
| 	0x07, 0x20, 0x01, 0x28, 0x09, 0x48, 0x02, 0x52, 0x08, 0x68, 0x6f, 0x6d, 0x65, 0x70, 0x61, 0x67, | ||||
| 	0x65, 0x88, 0x01, 0x01, 0x12, 0x23, 0x0a, 0x0a, 0x6d, 0x61, 0x69, 0x6e, 0x74, 0x61, 0x69, 0x6e, | ||||
| 	0x65, 0x72, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x48, 0x03, 0x52, 0x0a, 0x6d, 0x61, 0x69, 0x6e, | ||||
| 	0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x88, 0x01, 0x01, 0x12, 0x24, 0x0a, 0x0d, 0x61, 0x72, 0x63, | ||||
| 	0x68, 0x69, 0x74, 0x65, 0x63, 0x74, 0x75, 0x72, 0x65, 0x73, 0x18, 0x09, 0x20, 0x03, 0x28, 0x09, | ||||
| 	0x52, 0x0d, 0x61, 0x72, 0x63, 0x68, 0x69, 0x74, 0x65, 0x63, 0x74, 0x75, 0x72, 0x65, 0x73, 0x12, | ||||
| 	0x1a, 0x0a, 0x08, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, | ||||
| 	0x09, 0x52, 0x08, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x70, | ||||
| 	0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x73, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x70, | ||||
| 	0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x6f, 0x6e, 0x66, 0x6c, | ||||
| 	0x69, 0x63, 0x74, 0x73, 0x18, 0x0c, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6e, 0x66, | ||||
| 	0x6c, 0x69, 0x63, 0x74, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, | ||||
| 	0x73, 0x18, 0x0d, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, | ||||
| 	0x73, 0x12, 0x34, 0x0a, 0x07, 0x64, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x73, 0x18, 0x0e, 0x20, 0x03, | ||||
| 	0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6c, 0x75, 0x72, 0x65, 0x2e, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, | ||||
| 	0x65, 0x2e, 0x44, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, | ||||
| 	0x64, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x73, 0x12, 0x44, 0x0a, 0x0d, 0x62, 0x75, 0x69, 0x6c, 0x64, | ||||
| 	0x5f, 0x64, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x73, 0x18, 0x0f, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, | ||||
| 	0x2e, 0x6c, 0x75, 0x72, 0x65, 0x2e, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x2e, 0x42, 0x75, | ||||
| 	0x69, 0x6c, 0x64, 0x44, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, | ||||
| 	0x0c, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x44, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x73, 0x1a, 0x4c, 0x0a, | ||||
| 	0x0c, 0x44, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, | ||||
| 	0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, | ||||
| 	0x26, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, | ||||
| 	0x2e, 0x6c, 0x75, 0x72, 0x65, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x4c, 0x69, 0x73, 0x74, | ||||
| 	0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x51, 0x0a, 0x11, 0x42, | ||||
| 	0x75, 0x69, 0x6c, 0x64, 0x44, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, | ||||
| 	0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, | ||||
| 	0x65, 0x79, 0x12, 0x26, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, | ||||
| 	0x0b, 0x32, 0x10, 0x2e, 0x6c, 0x75, 0x72, 0x65, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x4c, | ||||
| 	0x69, 0x73, 0x74, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x08, | ||||
| 	0x0a, 0x06, 0x5f, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, 0x64, 0x65, 0x73, | ||||
| 	0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x68, 0x6f, 0x6d, | ||||
| 	0x65, 0x70, 0x61, 0x67, 0x65, 0x42, 0x0d, 0x0a, 0x0b, 0x5f, 0x6d, 0x61, 0x69, 0x6e, 0x74, 0x61, | ||||
| 	0x69, 0x6e, 0x65, 0x72, 0x22, 0x47, 0x0a, 0x11, 0x47, 0x65, 0x74, 0x50, 0x61, 0x63, 0x6b, 0x61, | ||||
| 	0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, | ||||
| 	0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1e, 0x0a, | ||||
| 	0x0a, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, | ||||
| 	0x09, 0x52, 0x0a, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x22, 0x3b, 0x0a, | ||||
| 	0x0e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, | ||||
| 	0x29, 0x0a, 0x08, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, | ||||
| 	0x0b, 0x32, 0x0d, 0x2e, 0x6c, 0x75, 0x72, 0x65, 0x2e, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, | ||||
| 	0x52, 0x08, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x73, 0x22, 0x4b, 0x0a, 0x15, 0x47, 0x65, | ||||
| 	0x74, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x52, 0x65, 0x71, 0x75, | ||||
| 	0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, | ||||
| 	0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x72, 0x65, 0x70, 0x6f, 0x73, | ||||
| 	0x69, 0x74, 0x6f, 0x72, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x65, 0x70, | ||||
| 	0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x22, 0x30, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x42, 0x75, | ||||
| 	0x69, 0x6c, 0x64, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, | ||||
| 	0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, | ||||
| 	0x09, 0x52, 0x06, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x2a, 0x3e, 0x0a, 0x07, 0x53, 0x4f, 0x52, | ||||
| 	0x54, 0x5f, 0x42, 0x59, 0x12, 0x0c, 0x0a, 0x08, 0x55, 0x4e, 0x53, 0x4f, 0x52, 0x54, 0x45, 0x44, | ||||
| 	0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x4e, 0x41, 0x4d, 0x45, 0x10, 0x01, 0x12, 0x0e, 0x0a, 0x0a, | ||||
| 	0x52, 0x45, 0x50, 0x4f, 0x53, 0x49, 0x54, 0x4f, 0x52, 0x59, 0x10, 0x02, 0x12, 0x0b, 0x0a, 0x07, | ||||
| 	0x56, 0x45, 0x52, 0x53, 0x49, 0x4f, 0x4e, 0x10, 0x03, 0x2a, 0x42, 0x0a, 0x0b, 0x46, 0x49, 0x4c, | ||||
| 	0x54, 0x45, 0x52, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x12, 0x0d, 0x0a, 0x09, 0x4e, 0x4f, 0x5f, 0x46, | ||||
| 	0x49, 0x4c, 0x54, 0x45, 0x52, 0x10, 0x00, 0x12, 0x11, 0x0a, 0x0d, 0x49, 0x4e, 0x5f, 0x52, 0x45, | ||||
| 	0x50, 0x4f, 0x53, 0x49, 0x54, 0x4f, 0x52, 0x59, 0x10, 0x01, 0x12, 0x11, 0x0a, 0x0d, 0x53, 0x55, | ||||
| 	0x50, 0x50, 0x4f, 0x52, 0x54, 0x53, 0x5f, 0x41, 0x52, 0x43, 0x48, 0x10, 0x02, 0x32, 0xb9, 0x01, | ||||
| 	0x0a, 0x03, 0x41, 0x50, 0x49, 0x12, 0x33, 0x0a, 0x06, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x12, | ||||
| 	0x13, 0x2e, 0x6c, 0x75, 0x72, 0x65, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x71, | ||||
| 	0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x6c, 0x75, 0x72, 0x65, 0x2e, 0x53, 0x65, 0x61, 0x72, | ||||
| 	0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x30, 0x0a, 0x06, 0x47, 0x65, | ||||
| 	0x74, 0x50, 0x6b, 0x67, 0x12, 0x17, 0x2e, 0x6c, 0x75, 0x72, 0x65, 0x2e, 0x47, 0x65, 0x74, 0x50, | ||||
| 	0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0d, 0x2e, | ||||
| 	0x6c, 0x75, 0x72, 0x65, 0x2e, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x12, 0x4b, 0x0a, 0x0e, | ||||
| 	0x47, 0x65, 0x74, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x12, 0x1b, | ||||
| 	0x2e, 0x6c, 0x75, 0x72, 0x65, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x53, 0x63, | ||||
| 	0x72, 0x69, 0x70, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x6c, 0x75, | ||||
| 	0x72, 0x65, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x53, 0x63, 0x72, 0x69, 0x70, | ||||
| 	0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x08, 0x5a, 0x06, 0x2e, 0x2e, 0x2f, | ||||
| 	0x61, 0x70, 0x69, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, | ||||
| } | ||||
|  | ||||
| var ( | ||||
| 	file_lure_proto_rawDescOnce sync.Once | ||||
| 	file_lure_proto_rawDescData = file_lure_proto_rawDesc | ||||
| ) | ||||
|  | ||||
| func file_lure_proto_rawDescGZIP() []byte { | ||||
| 	file_lure_proto_rawDescOnce.Do(func() { | ||||
| 		file_lure_proto_rawDescData = protoimpl.X.CompressGZIP(file_lure_proto_rawDescData) | ||||
| 	}) | ||||
| 	return file_lure_proto_rawDescData | ||||
| } | ||||
|  | ||||
| var file_lure_proto_enumTypes = make([]protoimpl.EnumInfo, 2) | ||||
| var file_lure_proto_msgTypes = make([]protoimpl.MessageInfo, 9) | ||||
| var file_lure_proto_goTypes = []interface{}{ | ||||
| 	(SORT_BY)(0),                   // 0: lure.SORT_BY | ||||
| 	(FILTER_TYPE)(0),               // 1: lure.FILTER_TYPE | ||||
| 	(*SearchRequest)(nil),          // 2: lure.SearchRequest | ||||
| 	(*StringList)(nil),             // 3: lure.StringList | ||||
| 	(*Package)(nil),                // 4: lure.Package | ||||
| 	(*GetPackageRequest)(nil),      // 5: lure.GetPackageRequest | ||||
| 	(*SearchResponse)(nil),         // 6: lure.SearchResponse | ||||
| 	(*GetBuildScriptRequest)(nil),  // 7: lure.GetBuildScriptRequest | ||||
| 	(*GetBuildScriptResponse)(nil), // 8: lure.GetBuildScriptResponse | ||||
| 	nil,                            // 9: lure.Package.DependsEntry | ||||
| 	nil,                            // 10: lure.Package.BuildDependsEntry | ||||
| } | ||||
| var file_lure_proto_depIdxs = []int32{ | ||||
| 	0,  // 0: lure.SearchRequest.sort_by:type_name -> lure.SORT_BY | ||||
| 	1,  // 1: lure.SearchRequest.filter_type:type_name -> lure.FILTER_TYPE | ||||
| 	9,  // 2: lure.Package.depends:type_name -> lure.Package.DependsEntry | ||||
| 	10, // 3: lure.Package.build_depends:type_name -> lure.Package.BuildDependsEntry | ||||
| 	4,  // 4: lure.SearchResponse.packages:type_name -> lure.Package | ||||
| 	3,  // 5: lure.Package.DependsEntry.value:type_name -> lure.StringList | ||||
| 	3,  // 6: lure.Package.BuildDependsEntry.value:type_name -> lure.StringList | ||||
| 	2,  // 7: lure.API.Search:input_type -> lure.SearchRequest | ||||
| 	5,  // 8: lure.API.GetPkg:input_type -> lure.GetPackageRequest | ||||
| 	7,  // 9: lure.API.GetBuildScript:input_type -> lure.GetBuildScriptRequest | ||||
| 	6,  // 10: lure.API.Search:output_type -> lure.SearchResponse | ||||
| 	4,  // 11: lure.API.GetPkg:output_type -> lure.Package | ||||
| 	8,  // 12: lure.API.GetBuildScript:output_type -> lure.GetBuildScriptResponse | ||||
| 	10, // [10:13] is the sub-list for method output_type | ||||
| 	7,  // [7:10] is the sub-list for method input_type | ||||
| 	7,  // [7:7] is the sub-list for extension type_name | ||||
| 	7,  // [7:7] is the sub-list for extension extendee | ||||
| 	0,  // [0:7] is the sub-list for field type_name | ||||
| } | ||||
|  | ||||
| func init() { file_lure_proto_init() } | ||||
| func file_lure_proto_init() { | ||||
| 	if File_lure_proto != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	if !protoimpl.UnsafeEnabled { | ||||
| 		file_lure_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { | ||||
| 			switch v := v.(*SearchRequest); i { | ||||
| 			case 0: | ||||
| 				return &v.state | ||||
| 			case 1: | ||||
| 				return &v.sizeCache | ||||
| 			case 2: | ||||
| 				return &v.unknownFields | ||||
| 			default: | ||||
| 				return nil | ||||
| 			} | ||||
| 		} | ||||
| 		file_lure_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { | ||||
| 			switch v := v.(*StringList); i { | ||||
| 			case 0: | ||||
| 				return &v.state | ||||
| 			case 1: | ||||
| 				return &v.sizeCache | ||||
| 			case 2: | ||||
| 				return &v.unknownFields | ||||
| 			default: | ||||
| 				return nil | ||||
| 			} | ||||
| 		} | ||||
| 		file_lure_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { | ||||
| 			switch v := v.(*Package); i { | ||||
| 			case 0: | ||||
| 				return &v.state | ||||
| 			case 1: | ||||
| 				return &v.sizeCache | ||||
| 			case 2: | ||||
| 				return &v.unknownFields | ||||
| 			default: | ||||
| 				return nil | ||||
| 			} | ||||
| 		} | ||||
| 		file_lure_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { | ||||
| 			switch v := v.(*GetPackageRequest); i { | ||||
| 			case 0: | ||||
| 				return &v.state | ||||
| 			case 1: | ||||
| 				return &v.sizeCache | ||||
| 			case 2: | ||||
| 				return &v.unknownFields | ||||
| 			default: | ||||
| 				return nil | ||||
| 			} | ||||
| 		} | ||||
| 		file_lure_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { | ||||
| 			switch v := v.(*SearchResponse); i { | ||||
| 			case 0: | ||||
| 				return &v.state | ||||
| 			case 1: | ||||
| 				return &v.sizeCache | ||||
| 			case 2: | ||||
| 				return &v.unknownFields | ||||
| 			default: | ||||
| 				return nil | ||||
| 			} | ||||
| 		} | ||||
| 		file_lure_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { | ||||
| 			switch v := v.(*GetBuildScriptRequest); i { | ||||
| 			case 0: | ||||
| 				return &v.state | ||||
| 			case 1: | ||||
| 				return &v.sizeCache | ||||
| 			case 2: | ||||
| 				return &v.unknownFields | ||||
| 			default: | ||||
| 				return nil | ||||
| 			} | ||||
| 		} | ||||
| 		file_lure_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { | ||||
| 			switch v := v.(*GetBuildScriptResponse); i { | ||||
| 			case 0: | ||||
| 				return &v.state | ||||
| 			case 1: | ||||
| 				return &v.sizeCache | ||||
| 			case 2: | ||||
| 				return &v.unknownFields | ||||
| 			default: | ||||
| 				return nil | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	file_lure_proto_msgTypes[0].OneofWrappers = []interface{}{} | ||||
| 	file_lure_proto_msgTypes[2].OneofWrappers = []interface{}{} | ||||
| 	type x struct{} | ||||
| 	out := protoimpl.TypeBuilder{ | ||||
| 		File: protoimpl.DescBuilder{ | ||||
| 			GoPackagePath: reflect.TypeOf(x{}).PkgPath(), | ||||
| 			RawDescriptor: file_lure_proto_rawDesc, | ||||
| 			NumEnums:      2, | ||||
| 			NumMessages:   9, | ||||
| 			NumExtensions: 0, | ||||
| 			NumServices:   1, | ||||
| 		}, | ||||
| 		GoTypes:           file_lure_proto_goTypes, | ||||
| 		DependencyIndexes: file_lure_proto_depIdxs, | ||||
| 		EnumInfos:         file_lure_proto_enumTypes, | ||||
| 		MessageInfos:      file_lure_proto_msgTypes, | ||||
| 	}.Build() | ||||
| 	File_lure_proto = out.File | ||||
| 	file_lure_proto_rawDesc = nil | ||||
| 	file_lure_proto_goTypes = nil | ||||
| 	file_lure_proto_depIdxs = nil | ||||
| } | ||||
							
								
								
									
										82
									
								
								internal/api/lure.proto
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								internal/api/lure.proto
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,82 @@ | ||||
| syntax = "proto3"; | ||||
| package lure; | ||||
|  | ||||
| // Slight hack to provide protoc with a package name | ||||
| option go_package = "../api"; | ||||
|  | ||||
| // SORT_BY represents possible things to sort packages by | ||||
| enum SORT_BY { | ||||
|     UNSORTED = 0; | ||||
|     NAME = 1; | ||||
|     REPOSITORY = 2; | ||||
|     VERSION = 3; | ||||
| } | ||||
|  | ||||
| // FILTER_TYPE represents possible filters for packages | ||||
| enum FILTER_TYPE { | ||||
|     NO_FILTER = 0; | ||||
|     IN_REPOSITORY = 1; | ||||
|     SUPPORTS_ARCH = 2; | ||||
| } | ||||
|  | ||||
| // SearchRequest is a request to search for packages | ||||
| message SearchRequest { | ||||
|     string query = 1; | ||||
|     int64 limit = 2; | ||||
|     SORT_BY sort_by = 3; | ||||
|     FILTER_TYPE filter_type = 4; | ||||
|     optional string filter_value = 5; | ||||
| } | ||||
|  | ||||
| // StringList contains a list of strings | ||||
| message StringList { | ||||
|     repeated string entries = 1; | ||||
| } | ||||
|  | ||||
| // Package represents a LURE package | ||||
| message Package { | ||||
|     string name = 1; | ||||
|     string repository = 2; | ||||
|     string version = 3; | ||||
|     int64 release = 4; | ||||
|     optional int64 epoch = 5; | ||||
|     optional string description = 6; | ||||
|     optional string homepage = 7; | ||||
|     optional string maintainer = 8; | ||||
|     repeated string architectures = 9; | ||||
|     repeated string licenses = 10; | ||||
|     repeated string provides = 11; | ||||
|     repeated string conflicts = 12; | ||||
|     repeated string replaces = 13; | ||||
|     map<string, StringList> depends = 14; | ||||
|     map<string, StringList> build_depends = 15; | ||||
| } | ||||
|  | ||||
| message GetPackageRequest { | ||||
|     string name = 1; | ||||
|     string repository = 2; | ||||
| } | ||||
|  | ||||
| // SearchResponse contains returned packages | ||||
| message SearchResponse { | ||||
|     repeated Package packages = 1; | ||||
| } | ||||
|  | ||||
| message GetBuildScriptRequest { | ||||
|     string name = 1; | ||||
|     string repository = 2; | ||||
| } | ||||
|  | ||||
| message GetBuildScriptResponse { | ||||
|     string script = 1; | ||||
| } | ||||
|  | ||||
| // Web is the LURE Web service | ||||
| service API { | ||||
|     // Search searches through LURE packages in the database | ||||
|     rpc Search(SearchRequest) returns (SearchResponse); | ||||
|     // GetPkg gets a single LURE package from the database | ||||
|     rpc GetPkg(GetPackageRequest) returns (Package); | ||||
|     // GetBuildScript returns the build script for the given package | ||||
|     rpc GetBuildScript(GetBuildScriptRequest) returns (GetBuildScriptResponse); | ||||
| } | ||||
							
								
								
									
										1703
									
								
								internal/api/lure.twirp.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1703
									
								
								internal/api/lure.twirp.go
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										36
									
								
								internal/config/config.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								internal/config/config.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,36 @@ | ||||
| package config | ||||
|  | ||||
| import ( | ||||
| 	"os" | ||||
|  | ||||
| 	"github.com/pelletier/go-toml/v2" | ||||
| 	"go.arsenm.dev/lure/internal/types" | ||||
| ) | ||||
|  | ||||
| var defaultConfig = types.Config{ | ||||
| 	RootCmd:          "sudo", | ||||
| 	PagerStyle:       "native", | ||||
| 	IgnorePkgUpdates: []string{}, | ||||
| 	Repos: []types.Repo{ | ||||
| 		{ | ||||
| 			Name: "default", | ||||
| 			URL:  "https://github.com/Arsen6331/lure-repo.git", | ||||
| 		}, | ||||
| 	}, | ||||
| } | ||||
|  | ||||
| // Decode decodes the config file into the given | ||||
| // pointer | ||||
| func Decode(cfg *types.Config) error { | ||||
| 	cfgFl, err := os.Open(ConfigPath) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	defer cfgFl.Close() | ||||
|  | ||||
| 	// Write defaults to pointer in case some values are not set in the config | ||||
| 	*cfg = defaultConfig | ||||
| 	// Set repos to nil so as to avoid a duplicate default | ||||
| 	cfg.Repos = nil | ||||
| 	return toml.NewDecoder(cfgFl).Decode(cfg) | ||||
| } | ||||
							
								
								
									
										76
									
								
								internal/config/dirs.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								internal/config/dirs.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,76 @@ | ||||
| package config | ||||
|  | ||||
| import ( | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
|  | ||||
| 	"github.com/pelletier/go-toml/v2" | ||||
| 	"go.arsenm.dev/logger/log" | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	ConfigDir  string | ||||
| 	ConfigPath string | ||||
| 	CacheDir   string | ||||
| 	RepoDir    string | ||||
| 	PkgsDir    string | ||||
| 	DBPath     string | ||||
| ) | ||||
|  | ||||
| // DBPresent is true if the database | ||||
| // was present when LURE was started | ||||
| var DBPresent bool | ||||
|  | ||||
| func init() { | ||||
| 	cfgDir, err := os.UserConfigDir() | ||||
| 	if err != nil { | ||||
| 		log.Fatal("Unable to detect user config directory").Err(err).Send() | ||||
| 	} | ||||
|  | ||||
| 	ConfigDir = filepath.Join(cfgDir, "lure") | ||||
|  | ||||
| 	err = os.MkdirAll(ConfigDir, 0o755) | ||||
| 	if err != nil { | ||||
| 		log.Fatal("Unable to create LURE config directory").Err(err).Send() | ||||
| 	} | ||||
|  | ||||
| 	ConfigPath = filepath.Join(ConfigDir, "lure.toml") | ||||
|  | ||||
| 	if _, err := os.Stat(ConfigPath); err != nil { | ||||
| 		cfgFl, err := os.Create(ConfigPath) | ||||
| 		if err != nil { | ||||
| 			log.Fatal("Unable to create LURE config file").Err(err).Send() | ||||
| 		} | ||||
|  | ||||
| 		err = toml.NewEncoder(cfgFl).Encode(&defaultConfig) | ||||
| 		if err != nil { | ||||
| 			log.Fatal("Error encoding default configuration").Err(err).Send() | ||||
| 		} | ||||
|  | ||||
| 		cfgFl.Close() | ||||
| 	} | ||||
|  | ||||
| 	cacheDir, err := os.UserCacheDir() | ||||
| 	if err != nil { | ||||
| 		log.Fatal("Unable to detect cache directory").Err(err).Send() | ||||
| 	} | ||||
|  | ||||
| 	CacheDir = filepath.Join(cacheDir, "lure") | ||||
| 	RepoDir = filepath.Join(CacheDir, "repo") | ||||
| 	PkgsDir = filepath.Join(CacheDir, "pkgs") | ||||
|  | ||||
| 	err = os.MkdirAll(RepoDir, 0o755) | ||||
| 	if err != nil { | ||||
| 		log.Fatal("Unable to create repo cache directory").Err(err).Send() | ||||
| 	} | ||||
|  | ||||
| 	err = os.MkdirAll(PkgsDir, 0o755) | ||||
| 	if err != nil { | ||||
| 		log.Fatal("Unable to create package cache directory").Err(err).Send() | ||||
| 	} | ||||
|  | ||||
| 	DBPath = filepath.Join(CacheDir, "db") | ||||
|  | ||||
| 	_, err = os.ReadDir(DBPath) | ||||
| 	DBPresent = err == nil | ||||
| } | ||||
							
								
								
									
										6
									
								
								internal/config/version.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								internal/config/version.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| package config | ||||
|  | ||||
| import _ "embed" | ||||
|  | ||||
| //go:embed version.txt | ||||
| var Version string | ||||
| @@ -20,6 +20,7 @@ package cpu | ||||
|  | ||||
| import ( | ||||
| 	"os" | ||||
| 	"runtime" | ||||
| 	"strings" | ||||
|  | ||||
| 	"golang.org/x/sys/cpu" | ||||
| @@ -42,3 +43,12 @@ func ARMVariant() string { | ||||
| 		return "arm5" | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Arch returns the canonical CPU architecture of the system | ||||
| func Arch() string { | ||||
| 	arch := runtime.GOARCH | ||||
| 	if arch == "arm" { | ||||
| 		arch = ARMVariant() | ||||
| 	} | ||||
| 	return arch | ||||
| } | ||||
|   | ||||
							
								
								
									
										197
									
								
								internal/db/db.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										197
									
								
								internal/db/db.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,197 @@ | ||||
| package db | ||||
|  | ||||
| import ( | ||||
| 	"database/sql" | ||||
| 	"database/sql/driver" | ||||
| 	"encoding/json" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
|  | ||||
| 	"github.com/jmoiron/sqlx" | ||||
| 	"golang.org/x/exp/slices" | ||||
| 	"modernc.org/sqlite" | ||||
| ) | ||||
|  | ||||
| func init() { | ||||
| 	sqlite.MustRegisterScalarFunction("json_array_contains", 2, JsonArrayContains) | ||||
| } | ||||
|  | ||||
| // Package is a LURE package's database representation | ||||
| type Package struct { | ||||
| 	Name          string                    `sh:"name,required" db:"name"` | ||||
| 	Version       string                    `sh:"version,required" db:"version"` | ||||
| 	Release       int                       `sh:"release,required" db:"release"` | ||||
| 	Epoch         uint                      `sh:"epoch" db:"epoch"` | ||||
| 	Description   string                    `sh:"desc" db:"description"` | ||||
| 	Homepage      string                    `sh:"homepage" db:"homepage"` | ||||
| 	Maintainer    string                    `sh:"maintainer" db:"maintainer"` | ||||
| 	Architectures JSON[[]string]            `sh:"architectures" db:"architectures"` | ||||
| 	Licenses      JSON[[]string]            `sh:"license" db:"licenses"` | ||||
| 	Provides      JSON[[]string]            `sh:"provides" db:"provides"` | ||||
| 	Conflicts     JSON[[]string]            `sh:"conflicts" db:"conflicts"` | ||||
| 	Replaces      JSON[[]string]            `sh:"replaces" db:"replaces"` | ||||
| 	Depends       JSON[map[string][]string] `db:"depends"` | ||||
| 	BuildDepends  JSON[map[string][]string] `db:"builddepends"` | ||||
| 	Repository    string                    `db:"repository"` | ||||
| } | ||||
|  | ||||
| // Init initializes the database | ||||
| func Init(db *sqlx.DB) error { | ||||
| 	*db = *db.Unsafe() | ||||
| 	_, err := db.Exec(` | ||||
| 		CREATE TABLE IF NOT EXISTS pkgs ( | ||||
| 			name          TEXT NOT NULL, | ||||
| 			repository    TEXT NOT NULL, | ||||
| 			version       TEXT NOT NULL, | ||||
| 			release       INT  NOT NULL, | ||||
| 			epoch         INT, | ||||
| 			description   TEXT, | ||||
| 			homepage      TEXT, | ||||
| 			maintainer    TEXT, | ||||
| 			architectures TEXT CHECK(architectures = 'null' OR (JSON_VALID(architectures) AND JSON_TYPE(architectures) = 'array')), | ||||
| 			licenses      TEXT CHECK(licenses = 'null' OR (JSON_VALID(licenses) AND JSON_TYPE(licenses) = 'array')), | ||||
| 			provides      TEXT CHECK(provides = 'null' OR (JSON_VALID(provides) AND JSON_TYPE(provides) = 'array')), | ||||
| 			conflicts     TEXT CHECK(conflicts = 'null' OR (JSON_VALID(conflicts) AND JSON_TYPE(conflicts) = 'array')), | ||||
| 			replaces      TEXT CHECK(replaces = 'null' OR (JSON_VALID(replaces) AND JSON_TYPE(replaces) = 'array')), | ||||
| 			depends       TEXT CHECK(depends = 'null' OR (JSON_VALID(depends) AND JSON_TYPE(depends) = 'object')), | ||||
| 			builddepends  TEXT CHECK(builddepends = 'null' OR (JSON_VALID(builddepends) AND JSON_TYPE(builddepends) = 'object')), | ||||
| 			UNIQUE(name, repository) | ||||
| 		); | ||||
| 	`) | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| // InsertPackage adds a package to the database | ||||
| func InsertPackage(db *sqlx.DB, pkg Package) error { | ||||
| 	_, err := db.NamedExec(` | ||||
| 		INSERT OR REPLACE INTO pkgs ( | ||||
| 			name, | ||||
| 			repository, | ||||
| 			version, | ||||
| 			release, | ||||
| 			epoch, | ||||
| 			description, | ||||
| 			homepage, | ||||
| 			maintainer, | ||||
| 			architectures, | ||||
| 			licenses, | ||||
| 			provides, | ||||
| 			conflicts, | ||||
| 			replaces, | ||||
| 			depends, | ||||
| 			builddepends | ||||
| 		) VALUES ( | ||||
| 			:name, | ||||
| 			:repository, | ||||
| 			:version, | ||||
| 			:release, | ||||
| 			:epoch, | ||||
| 			:description, | ||||
| 			:homepage, | ||||
| 			:maintainer, | ||||
| 			:architectures, | ||||
| 			:licenses, | ||||
| 			:provides, | ||||
| 			:conflicts, | ||||
| 			:replaces, | ||||
| 			:depends, | ||||
| 			:builddepends | ||||
| 		); | ||||
| 	`, pkg) | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| // GetPkgs returns a result containing packages that match the where conditions | ||||
| func GetPkgs(db *sqlx.DB, where string, args ...any) (*sqlx.Rows, error) { | ||||
| 	stream, err := db.Queryx("SELECT * FROM pkgs WHERE "+where, args...) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return stream, nil | ||||
| } | ||||
|  | ||||
| // GetPkg returns a single package that match the where conditions | ||||
| func GetPkg(db *sqlx.DB, where string, args ...any) (*Package, error) { | ||||
| 	out := &Package{} | ||||
| 	err := db.Get(out, "SELECT * FROM pkgs WHERE "+where+" LIMIT 1", args...) | ||||
| 	return out, err | ||||
| } | ||||
|  | ||||
| // DeletePkgs deletes all packages matching the where conditions | ||||
| func DeletePkgs(db *sqlx.DB, where string, args ...any) error { | ||||
| 	_, err := db.Exec("DELETE FROM pkgs WHERE "+where, args...) | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| func JsonArrayContains(ctx *sqlite.FunctionContext, args []driver.Value) (driver.Value, error) { | ||||
| 	value, ok := args[0].(string) | ||||
| 	if !ok { | ||||
| 		return nil, errors.New("both arguments to json_array_contains must be strings") | ||||
| 	} | ||||
|  | ||||
| 	item, ok := args[1].(string) | ||||
| 	if !ok { | ||||
| 		return nil, errors.New("both arguments to json_array_contains must be strings") | ||||
| 	} | ||||
|  | ||||
| 	var array []string | ||||
| 	err := json.Unmarshal([]byte(value), &array) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	return slices.Contains(array, item), nil | ||||
| } | ||||
|  | ||||
| type JSON[T any] struct { | ||||
| 	Val T | ||||
| } | ||||
|  | ||||
| func NewJSON[T any](v T) JSON[T] { | ||||
| 	return JSON[T]{Val: v} | ||||
| } | ||||
|  | ||||
| func (s *JSON[T]) Scan(val any) error { | ||||
| 	if val == nil { | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	switch val := val.(type) { | ||||
| 	case string: | ||||
| 		err := json.Unmarshal([]byte(val), &s.Val) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	case sql.NullString: | ||||
| 		if val.Valid { | ||||
| 			err := json.Unmarshal([]byte(val.String), &s.Val) | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 		} | ||||
| 	default: | ||||
| 		return errors.New("sqlite json types must be strings") | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (s JSON[T]) Value() (driver.Value, error) { | ||||
| 	data, err := json.Marshal(s.Val) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return string(data), nil | ||||
| } | ||||
|  | ||||
| func (s JSON[T]) MarshalYAML() (any, error) { | ||||
| 	return s.Val, nil | ||||
| } | ||||
|  | ||||
| func (s JSON[T]) String() string { | ||||
| 	return fmt.Sprint(s.Val) | ||||
| } | ||||
|  | ||||
| func (s JSON[T]) GoString() string { | ||||
| 	return fmt.Sprintf("%#v", s.Val) | ||||
| } | ||||
							
								
								
									
										234
									
								
								internal/db/db_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										234
									
								
								internal/db/db_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,234 @@ | ||||
| package db_test | ||||
|  | ||||
| import ( | ||||
| 	"reflect" | ||||
| 	"strings" | ||||
| 	"testing" | ||||
|  | ||||
| 	"github.com/jmoiron/sqlx" | ||||
| 	"go.arsenm.dev/lure/internal/db" | ||||
| ) | ||||
|  | ||||
| var testPkg = db.Package{ | ||||
| 	Name:          "test", | ||||
| 	Version:       "0.0.1", | ||||
| 	Release:       1, | ||||
| 	Epoch:         2, | ||||
| 	Description:   "Test package", | ||||
| 	Homepage:      "https://lure.arsenm.dev", | ||||
| 	Maintainer:    "Arsen Musayelyan <arsen@arsenm.dev>", | ||||
| 	Architectures: db.NewJSON([]string{"arm64", "amd64"}), | ||||
| 	Licenses:      db.NewJSON([]string{"GPL-3.0-or-later"}), | ||||
| 	Provides:      db.NewJSON([]string{"test"}), | ||||
| 	Conflicts:     db.NewJSON([]string{"test"}), | ||||
| 	Replaces:      db.NewJSON([]string{"test-old"}), | ||||
| 	Depends: db.NewJSON(map[string][]string{ | ||||
| 		"": {"sudo"}, | ||||
| 	}), | ||||
| 	BuildDepends: db.NewJSON(map[string][]string{ | ||||
| 		"":     {"golang"}, | ||||
| 		"arch": {"go"}, | ||||
| 	}), | ||||
| 	Repository: "default", | ||||
| } | ||||
|  | ||||
| func getDB(t *testing.T) (*sqlx.DB, error) { | ||||
| 	t.Helper() | ||||
|  | ||||
| 	gdb, err := sqlx.Open("sqlite", ":memory:") | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	err = db.Init(gdb) | ||||
| 	return gdb, err | ||||
| } | ||||
|  | ||||
| func TestInit(t *testing.T) { | ||||
| 	gdb, err := sqlx.Open("sqlite", ":memory:") | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Expected no error, got %s", err) | ||||
| 	} | ||||
| 	defer gdb.Close() | ||||
|  | ||||
| 	err = db.Init(gdb) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Expected no error, got %s", err) | ||||
| 	} | ||||
|  | ||||
| 	_, err = gdb.Exec("SELECT * FROM pkgs") | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Expected no error, got %s", err) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestInsertPackage(t *testing.T) { | ||||
| 	gdb, err := getDB(t) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Expected no error, got %s", err) | ||||
| 	} | ||||
| 	defer gdb.Close() | ||||
|  | ||||
| 	err = db.InsertPackage(gdb, testPkg) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Expected no error, got %s", err) | ||||
| 	} | ||||
|  | ||||
| 	dbPkg := db.Package{} | ||||
| 	err = sqlx.Get(gdb, &dbPkg, "SELECT * FROM pkgs WHERE name = 'test' AND repository = 'default'") | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Expected no error, got %s", err) | ||||
| 	} | ||||
|  | ||||
| 	if !reflect.DeepEqual(testPkg, dbPkg) { | ||||
| 		t.Errorf("Expected test package to be the same as database package") | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestGetPkgs(t *testing.T) { | ||||
| 	gdb, err := getDB(t) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Expected no error, got %s", err) | ||||
| 	} | ||||
| 	defer gdb.Close() | ||||
|  | ||||
| 	x1 := testPkg | ||||
| 	x1.Name = "x1" | ||||
| 	x2 := testPkg | ||||
| 	x2.Name = "x2" | ||||
|  | ||||
| 	err = db.InsertPackage(gdb, x1) | ||||
| 	if err != nil { | ||||
| 		t.Errorf("Expected no error, got %s", err) | ||||
| 	} | ||||
|  | ||||
| 	err = db.InsertPackage(gdb, x2) | ||||
| 	if err != nil { | ||||
| 		t.Errorf("Expected no error, got %s", err) | ||||
| 	} | ||||
|  | ||||
| 	result, err := db.GetPkgs(gdb, "name LIKE 'x%'") | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Expected no error, got %s", err) | ||||
| 	} | ||||
|  | ||||
| 	for result.Next() { | ||||
| 		var dbPkg db.Package | ||||
| 		err = result.StructScan(&dbPkg) | ||||
| 		if err != nil { | ||||
| 			t.Errorf("Expected no error, got %s", err) | ||||
| 		} | ||||
|  | ||||
| 		if !strings.HasPrefix(dbPkg.Name, "x") { | ||||
| 			t.Errorf("Expected package name to start with 'x', got %s", dbPkg.Name) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestGetPkg(t *testing.T) { | ||||
| 	gdb, err := getDB(t) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Expected no error, got %s", err) | ||||
| 	} | ||||
| 	defer gdb.Close() | ||||
|  | ||||
| 	x1 := testPkg | ||||
| 	x1.Name = "x1" | ||||
| 	x2 := testPkg | ||||
| 	x2.Name = "x2" | ||||
|  | ||||
| 	err = db.InsertPackage(gdb, x1) | ||||
| 	if err != nil { | ||||
| 		t.Errorf("Expected no error, got %s", err) | ||||
| 	} | ||||
|  | ||||
| 	err = db.InsertPackage(gdb, x2) | ||||
| 	if err != nil { | ||||
| 		t.Errorf("Expected no error, got %s", err) | ||||
| 	} | ||||
|  | ||||
| 	pkg, err := db.GetPkg(gdb, "name LIKE 'x%' ORDER BY name") | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Expected no error, got %s", err) | ||||
| 	} | ||||
|  | ||||
| 	if pkg.Name != "x1" { | ||||
| 		t.Errorf("Expected x1 package, got %s", pkg.Name) | ||||
| 	} | ||||
|  | ||||
| 	if !reflect.DeepEqual(*pkg, x1) { | ||||
| 		t.Errorf("Expected x1 to be %v, got %v", x1, *pkg) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestDeletePkgs(t *testing.T) { | ||||
| 	gdb, err := getDB(t) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Expected no error, got %s", err) | ||||
| 	} | ||||
| 	defer gdb.Close() | ||||
|  | ||||
| 	x1 := testPkg | ||||
| 	x1.Name = "x1" | ||||
| 	x2 := testPkg | ||||
| 	x2.Name = "x2" | ||||
|  | ||||
| 	err = db.InsertPackage(gdb, x1) | ||||
| 	if err != nil { | ||||
| 		t.Errorf("Expected no error, got %s", err) | ||||
| 	} | ||||
|  | ||||
| 	err = db.InsertPackage(gdb, x2) | ||||
| 	if err != nil { | ||||
| 		t.Errorf("Expected no error, got %s", err) | ||||
| 	} | ||||
|  | ||||
| 	err = db.DeletePkgs(gdb, "name = 'x1'") | ||||
| 	if err != nil { | ||||
| 		t.Errorf("Expected no error, got %s", err) | ||||
| 	} | ||||
|  | ||||
| 	var dbPkg db.Package | ||||
| 	err = gdb.Get(&dbPkg, "SELECT * FROM pkgs WHERE name LIKE 'x%' ORDER BY name LIMIT 1;") | ||||
| 	if err != nil { | ||||
| 		t.Errorf("Expected no error, got %s", err) | ||||
| 	} | ||||
|  | ||||
| 	if dbPkg.Name != "x2" { | ||||
| 		t.Errorf("Expected x2 package, got %s", dbPkg.Name) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestJsonArrayContains(t *testing.T) { | ||||
| 	gdb, err := getDB(t) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Expected no error, got %s", err) | ||||
| 	} | ||||
| 	defer gdb.Close() | ||||
|  | ||||
| 	x1 := testPkg | ||||
| 	x1.Name = "x1" | ||||
| 	x2 := testPkg | ||||
| 	x2.Name = "x2" | ||||
| 	x2.Provides.Val = append(x2.Provides.Val, "x") | ||||
|  | ||||
| 	err = db.InsertPackage(gdb, x1) | ||||
| 	if err != nil { | ||||
| 		t.Errorf("Expected no error, got %s", err) | ||||
| 	} | ||||
|  | ||||
| 	err = db.InsertPackage(gdb, x2) | ||||
| 	if err != nil { | ||||
| 		t.Errorf("Expected no error, got %s", err) | ||||
| 	} | ||||
|  | ||||
| 	var dbPkg db.Package | ||||
| 	err = gdb.Get(&dbPkg, "SELECT * FROM pkgs WHERE json_array_contains(provides, 'x');") | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Expected no error, got %s", err) | ||||
| 	} | ||||
|  | ||||
| 	if dbPkg.Name != "x2" { | ||||
| 		t.Errorf("Expected x2 package, got %s", dbPkg.Name) | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										99
									
								
								internal/overrides/overrides.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										99
									
								
								internal/overrides/overrides.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,99 @@ | ||||
| package overrides | ||||
|  | ||||
| import ( | ||||
| 	"runtime" | ||||
| 	"strings" | ||||
|  | ||||
| 	"go.arsenm.dev/lure/distro" | ||||
| 	"go.arsenm.dev/lure/internal/cpu" | ||||
| ) | ||||
|  | ||||
| type Opts struct { | ||||
| 	Name        string | ||||
| 	Overrides   bool | ||||
| 	LikeDistros bool | ||||
| } | ||||
|  | ||||
| var DefaultOpts = &Opts{ | ||||
| 	Overrides:   true, | ||||
| 	LikeDistros: true, | ||||
| } | ||||
|  | ||||
| // Resolve generates a slice of possible override names in the order that they should be checked | ||||
| func Resolve(info *distro.OSRelease, opts *Opts) []string { | ||||
| 	if opts == nil { | ||||
| 		opts = DefaultOpts | ||||
| 	} | ||||
|  | ||||
| 	if !opts.Overrides { | ||||
| 		return []string{opts.Name} | ||||
| 	} | ||||
|  | ||||
| 	architectures := []string{runtime.GOARCH} | ||||
|  | ||||
| 	if runtime.GOARCH == "arm" { | ||||
| 		// More specific goes first | ||||
| 		architectures[0] = cpu.ARMVariant() | ||||
| 		architectures = append(architectures, "arm") | ||||
| 	} | ||||
|  | ||||
| 	distros := []string{info.ID} | ||||
| 	if opts.LikeDistros { | ||||
| 		distros = append(distros, info.Like...) | ||||
| 	} | ||||
|  | ||||
| 	var out []string | ||||
| 	for _, arch := range architectures { | ||||
| 		for _, distro := range distros { | ||||
| 			if opts.Name == "" { | ||||
| 				out = append( | ||||
| 					out, | ||||
| 					arch+"_"+distro, | ||||
| 					distro, | ||||
| 				) | ||||
| 			} else { | ||||
| 				out = append( | ||||
| 					out, | ||||
| 					opts.Name+"_"+arch+"_"+distro, | ||||
| 					opts.Name+"_"+distro, | ||||
| 				) | ||||
| 			} | ||||
| 		} | ||||
| 		if opts.Name == "" { | ||||
| 			out = append(out, arch) | ||||
| 		} else { | ||||
| 			out = append(out, opts.Name+"_"+arch) | ||||
| 		} | ||||
| 	} | ||||
| 	out = append(out, opts.Name) | ||||
|  | ||||
| 	for index, item := range out { | ||||
| 		out[index] = strings.ReplaceAll(item, "-", "_") | ||||
| 	} | ||||
|  | ||||
| 	return out | ||||
| } | ||||
|  | ||||
| func (o *Opts) WithName(name string) *Opts { | ||||
| 	out := &Opts{} | ||||
| 	*out = *o | ||||
|  | ||||
| 	out.Name = name | ||||
| 	return out | ||||
| } | ||||
|  | ||||
| func (o *Opts) WithOverrides(v bool) *Opts { | ||||
| 	out := &Opts{} | ||||
| 	*out = *o | ||||
|  | ||||
| 	out.Overrides = v | ||||
| 	return out | ||||
| } | ||||
|  | ||||
| func (o *Opts) WithLikeDistros(v bool) *Opts { | ||||
| 	out := &Opts{} | ||||
| 	*out = *o | ||||
|  | ||||
| 	out.LikeDistros = v | ||||
| 	return out | ||||
| } | ||||
							
								
								
									
										88
									
								
								internal/overrides/overrides_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								internal/overrides/overrides_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,88 @@ | ||||
| package overrides_test | ||||
|  | ||||
| import ( | ||||
| 	"reflect" | ||||
| 	"testing" | ||||
|  | ||||
| 	"go.arsenm.dev/lure/distro" | ||||
| 	"go.arsenm.dev/lure/internal/overrides" | ||||
| ) | ||||
|  | ||||
| var info = &distro.OSRelease{ | ||||
| 	ID:   "centos", | ||||
| 	Like: []string{"rhel", "fedora"}, | ||||
| } | ||||
|  | ||||
| func TestResolve(t *testing.T) { | ||||
| 	names := overrides.Resolve(info, nil) | ||||
|  | ||||
| 	expected := []string{ | ||||
| 		"amd64_centos", | ||||
| 		"centos", | ||||
| 		"amd64_rhel", | ||||
| 		"rhel", | ||||
| 		"amd64_fedora", | ||||
| 		"fedora", | ||||
| 		"amd64", | ||||
| 		"", | ||||
| 	} | ||||
|  | ||||
| 	if !reflect.DeepEqual(names, expected) { | ||||
| 		t.Errorf("expected %v, got %v", expected, names) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestResolveName(t *testing.T) { | ||||
| 	names := overrides.Resolve(info, &overrides.Opts{ | ||||
| 		Name:        "deps", | ||||
| 		Overrides:   true, | ||||
| 		LikeDistros: true, | ||||
| 	}) | ||||
|  | ||||
| 	expected := []string{ | ||||
| 		"deps_amd64_centos", | ||||
| 		"deps_centos", | ||||
| 		"deps_amd64_rhel", | ||||
| 		"deps_rhel", | ||||
| 		"deps_amd64_fedora", | ||||
| 		"deps_fedora", | ||||
| 		"deps_amd64", | ||||
| 		"deps", | ||||
| 	} | ||||
|  | ||||
| 	if !reflect.DeepEqual(names, expected) { | ||||
| 		t.Errorf("expected %v, got %v", expected, names) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestResolveNoLikeDistros(t *testing.T) { | ||||
| 	names := overrides.Resolve(info, &overrides.Opts{ | ||||
| 		Overrides:   true, | ||||
| 		LikeDistros: false, | ||||
| 	}) | ||||
|  | ||||
| 	expected := []string{ | ||||
| 		"amd64_centos", | ||||
| 		"centos", | ||||
| 		"amd64", | ||||
| 		"", | ||||
| 	} | ||||
|  | ||||
| 	if !reflect.DeepEqual(names, expected) { | ||||
| 		t.Errorf("expected %v, got %v", expected, names) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestResolveNoOverrides(t *testing.T) { | ||||
| 	names := overrides.Resolve(info, &overrides.Opts{ | ||||
| 		Name:        "deps", | ||||
| 		Overrides:   false, | ||||
| 		LikeDistros: false, | ||||
| 	}) | ||||
|  | ||||
| 	expected := []string{"deps"} | ||||
|  | ||||
| 	if !reflect.DeepEqual(names, expected) { | ||||
| 		t.Errorf("expected %v, got %v", expected, names) | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										19
									
								
								internal/pager/highlighting.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								internal/pager/highlighting.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | ||||
| package pager | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"io" | ||||
|  | ||||
| 	"github.com/alecthomas/chroma/v2/quick" | ||||
| ) | ||||
|  | ||||
| func SyntaxHighlightBash(r io.Reader, style string) (string, error) { | ||||
| 	data, err := io.ReadAll(r) | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
|  | ||||
| 	w := &bytes.Buffer{} | ||||
| 	err = quick.Highlight(w, string(data), "bash", "terminal", style) | ||||
| 	return w.String(), err | ||||
| } | ||||
							
								
								
									
										124
									
								
								internal/pager/pager.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										124
									
								
								internal/pager/pager.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,124 @@ | ||||
| package pager | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/charmbracelet/bubbles/viewport" | ||||
| 	tea "github.com/charmbracelet/bubbletea" | ||||
| 	"github.com/charmbracelet/lipgloss" | ||||
| 	"github.com/muesli/reflow/wordwrap" | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	titleStyle lipgloss.Style | ||||
| 	infoStyle  lipgloss.Style | ||||
| ) | ||||
|  | ||||
| func init() { | ||||
| 	b := lipgloss.RoundedBorder() | ||||
| 	b.Right = "\u251C" | ||||
| 	titleStyle = lipgloss.NewStyle().BorderStyle(b).Padding(0, 1) | ||||
|  | ||||
| 	b = lipgloss.RoundedBorder() | ||||
| 	b.Left = "\u2524" | ||||
| 	infoStyle = titleStyle.Copy().BorderStyle(b) | ||||
| } | ||||
|  | ||||
| type Pager struct { | ||||
| 	model pagerModel | ||||
| } | ||||
|  | ||||
| func New(name, content string) *Pager { | ||||
| 	return &Pager{ | ||||
| 		model: pagerModel{ | ||||
| 			name:    name, | ||||
| 			content: content, | ||||
| 		}, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (p *Pager) Run() error { | ||||
| 	prog := tea.NewProgram( | ||||
| 		p.model, | ||||
| 		tea.WithMouseCellMotion(), | ||||
| 	) | ||||
|  | ||||
| 	_, err := prog.Run() | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| type pagerModel struct { | ||||
| 	name     string | ||||
| 	content  string | ||||
| 	ready    bool | ||||
| 	viewport viewport.Model | ||||
| } | ||||
|  | ||||
| func (pm pagerModel) Init() tea.Cmd { | ||||
| 	return tea.ClearScreen | ||||
| } | ||||
|  | ||||
| func (pm pagerModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { | ||||
| 	var ( | ||||
| 		cmd  tea.Cmd | ||||
| 		cmds []tea.Cmd | ||||
| 	) | ||||
|  | ||||
| 	switch msg := msg.(type) { | ||||
| 	case tea.KeyMsg: | ||||
| 		k := msg.String() | ||||
| 		if k == "ctrl+c" || k == "q" || k == "esc" { | ||||
| 			return pm, tea.Quit | ||||
| 		} | ||||
| 	case tea.WindowSizeMsg: | ||||
| 		headerHeight := lipgloss.Height(pm.headerView()) | ||||
| 		footerHeight := lipgloss.Height(pm.footerView()) | ||||
| 		verticalMarginHeight := headerHeight + footerHeight | ||||
|  | ||||
| 		if !pm.ready { | ||||
| 			pm.viewport = viewport.New(msg.Width, msg.Height-verticalMarginHeight) | ||||
| 			pm.viewport.HighPerformanceRendering = true | ||||
| 			pm.viewport.YPosition = headerHeight + 1 | ||||
| 			pm.viewport.SetContent(wordwrap.String(pm.content, msg.Width)) | ||||
| 			pm.ready = true | ||||
| 		} else { | ||||
| 			pm.viewport.Width = msg.Width | ||||
| 			pm.viewport.Height = msg.Height - verticalMarginHeight | ||||
| 		} | ||||
|  | ||||
| 		cmds = append(cmds, viewport.Sync(pm.viewport)) | ||||
| 	} | ||||
|  | ||||
| 	// Handle keyboard and mouse events in the viewport | ||||
| 	pm.viewport, cmd = pm.viewport.Update(msg) | ||||
| 	cmds = append(cmds, cmd) | ||||
|  | ||||
| 	return pm, tea.Batch(cmds...) | ||||
| } | ||||
|  | ||||
| func (pm pagerModel) View() string { | ||||
| 	if !pm.ready { | ||||
| 		return "\n  Initializing..." | ||||
| 	} | ||||
| 	return fmt.Sprintf("%s\n%s\n%s", pm.headerView(), pm.viewport.View(), pm.footerView()) | ||||
| } | ||||
|  | ||||
| func (pm pagerModel) headerView() string { | ||||
| 	title := titleStyle.Render(pm.name) | ||||
| 	line := strings.Repeat("─", max(0, pm.viewport.Width-lipgloss.Width(title))) | ||||
| 	return lipgloss.JoinHorizontal(lipgloss.Center, title, line) | ||||
| } | ||||
|  | ||||
| func (pm pagerModel) footerView() string { | ||||
| 	info := infoStyle.Render(fmt.Sprintf("%3.f%%", pm.viewport.ScrollPercent()*100)) | ||||
| 	line := strings.Repeat("─", max(0, pm.viewport.Width-lipgloss.Width(info))) | ||||
| 	return lipgloss.JoinHorizontal(lipgloss.Center, line, info) | ||||
| } | ||||
|  | ||||
| func max(a, b int) int { | ||||
| 	if a > b { | ||||
| 		return a | ||||
| 	} | ||||
| 	return b | ||||
| } | ||||
							
								
								
									
										64
									
								
								internal/repos/find.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								internal/repos/find.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,64 @@ | ||||
| package repos | ||||
|  | ||||
| import ( | ||||
| 	"github.com/jmoiron/sqlx" | ||||
| 	"go.arsenm.dev/lure/internal/db" | ||||
| ) | ||||
|  | ||||
| // FindPkgs looks for packages matching the inputs inside the database. | ||||
| // It returns a map that maps the package name input to the packages found for it. | ||||
| // It also returns a slice that contains the names of all packages that were not found. | ||||
| func FindPkgs(gdb *sqlx.DB, pkgs []string) (map[string][]db.Package, []string, error) { | ||||
| 	found := map[string][]db.Package{} | ||||
| 	notFound := []string(nil) | ||||
|  | ||||
| 	for _, pkgName := range pkgs { | ||||
| 		if pkgName == "" { | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		result, err := db.GetPkgs(gdb, "name LIKE ?", pkgName) | ||||
| 		if err != nil { | ||||
| 			return nil, nil, err | ||||
| 		} | ||||
|  | ||||
| 		added := 0 | ||||
| 		for result.Next() { | ||||
| 			var pkg db.Package | ||||
| 			err = result.StructScan(&pkg) | ||||
| 			if err != nil { | ||||
| 				return nil, nil, err | ||||
| 			} | ||||
|  | ||||
| 			added++ | ||||
| 			found[pkgName] = append(found[pkgName], pkg) | ||||
| 		} | ||||
| 		result.Close() | ||||
|  | ||||
| 		if added == 0 { | ||||
| 			result, err := db.GetPkgs(gdb, "json_array_contains(provides, ?)", pkgName) | ||||
| 			if err != nil { | ||||
| 				return nil, nil, err | ||||
| 			} | ||||
|  | ||||
| 			for result.Next() { | ||||
| 				var pkg db.Package | ||||
| 				err = result.StructScan(&pkg) | ||||
| 				if err != nil { | ||||
| 					return nil, nil, err | ||||
| 				} | ||||
|  | ||||
| 				added++ | ||||
| 				found[pkgName] = append(found[pkgName], pkg) | ||||
| 			} | ||||
|  | ||||
| 			result.Close() | ||||
| 		} | ||||
|  | ||||
| 		if added == 0 { | ||||
| 			notFound = append(notFound, pkgName) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return found, notFound, nil | ||||
| } | ||||
							
								
								
									
										135
									
								
								internal/repos/find_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										135
									
								
								internal/repos/find_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,135 @@ | ||||
| package repos_test | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"reflect" | ||||
| 	"strings" | ||||
| 	"testing" | ||||
|  | ||||
| 	"github.com/jmoiron/sqlx" | ||||
| 	"go.arsenm.dev/lure/internal/db" | ||||
| 	"go.arsenm.dev/lure/internal/repos" | ||||
| 	"go.arsenm.dev/lure/internal/types" | ||||
| ) | ||||
|  | ||||
| func TestFindPkgs(t *testing.T) { | ||||
| 	gdb, err := sqlx.Open("sqlite", ":memory:") | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Expected no error, got %s", err) | ||||
| 	} | ||||
| 	defer gdb.Close() | ||||
|  | ||||
| 	err = db.Init(gdb) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Expected no error, got %s", err) | ||||
| 	} | ||||
|  | ||||
| 	setCfgDirs(t) | ||||
| 	defer removeCacheDir(t) | ||||
|  | ||||
| 	ctx := context.Background() | ||||
|  | ||||
| 	err = repos.Pull(ctx, gdb, []types.Repo{ | ||||
| 		{ | ||||
| 			Name: "default", | ||||
| 			URL:  "https://github.com/Arsen6331/lure-repo.git", | ||||
| 		}, | ||||
| 	}) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Expected no error, got %s", err) | ||||
| 	} | ||||
|  | ||||
| 	found, notFound, err := repos.FindPkgs(gdb, []string{"itd", "nonexistentpackage1", "nonexistentpackage2"}) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Expected no error, got %s", err) | ||||
| 	} | ||||
|  | ||||
| 	if !reflect.DeepEqual(notFound, []string{"nonexistentpackage1", "nonexistentpackage2"}) { | ||||
| 		t.Errorf("Expected 'nonexistentpackage{1,2} not to be found") | ||||
| 	} | ||||
|  | ||||
| 	if len(found) != 1 { | ||||
| 		t.Errorf("Expected 1 package found, got %d", len(found)) | ||||
| 	} | ||||
|  | ||||
| 	itdPkgs, ok := found["itd"] | ||||
| 	if !ok { | ||||
| 		t.Fatalf("Expected 'itd' packages to be found") | ||||
| 	} | ||||
|  | ||||
| 	if len(itdPkgs) < 2 { | ||||
| 		t.Errorf("Expected two 'itd' packages to be found") | ||||
| 	} | ||||
|  | ||||
| 	for i, pkg := range itdPkgs { | ||||
| 		if !strings.HasPrefix(pkg.Name, "itd") { | ||||
| 			t.Errorf("Expected package name of all found packages to start with 'itd', got %s on element %d", pkg.Name, i) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestFindPkgsEmpty(t *testing.T) { | ||||
| 	gdb, err := sqlx.Open("sqlite", ":memory:") | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Expected no error, got %s", err) | ||||
| 	} | ||||
| 	defer gdb.Close() | ||||
|  | ||||
| 	err = db.Init(gdb) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Expected no error, got %s", err) | ||||
| 	} | ||||
|  | ||||
| 	setCfgDirs(t) | ||||
| 	defer removeCacheDir(t) | ||||
|  | ||||
| 	err = db.InsertPackage(gdb, db.Package{ | ||||
| 		Name:        "test1", | ||||
| 		Repository:  "default", | ||||
| 		Version:     "0.0.1", | ||||
| 		Release:     1, | ||||
| 		Description: "Test package 1", | ||||
| 		Provides:    db.NewJSON([]string{""}), | ||||
| 	}) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Expected no error, got %s", err) | ||||
| 	} | ||||
|  | ||||
| 	err = db.InsertPackage(gdb, db.Package{ | ||||
| 		Name:        "test2", | ||||
| 		Repository:  "default", | ||||
| 		Version:     "0.0.1", | ||||
| 		Release:     1, | ||||
| 		Description: "Test package 2", | ||||
| 		Provides:    db.NewJSON([]string{"test"}), | ||||
| 	}) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Expected no error, got %s", err) | ||||
| 	} | ||||
|  | ||||
| 	found, notFound, err := repos.FindPkgs(gdb, []string{"test", ""}) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Expected no error, got %s", err) | ||||
| 	} | ||||
|  | ||||
| 	if len(notFound) != 0 { | ||||
| 		t.Errorf("Expected all packages to be found") | ||||
| 	} | ||||
|  | ||||
| 	if len(found) != 1 { | ||||
| 		t.Errorf("Expected 1 package found, got %d", len(found)) | ||||
| 	} | ||||
|  | ||||
| 	testPkgs, ok := found["test"] | ||||
| 	if !ok { | ||||
| 		t.Fatalf("Expected 'test' packages to be found") | ||||
| 	} | ||||
|  | ||||
| 	if len(testPkgs) != 1 { | ||||
| 		t.Errorf("Expected one 'test' package to be found, got %d", len(testPkgs)) | ||||
| 	} | ||||
|  | ||||
| 	if testPkgs[0].Name != "test2" { | ||||
| 		t.Errorf("Expected 'test2' package, got '%s'", testPkgs[0].Name) | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										397
									
								
								internal/repos/pull.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										397
									
								
								internal/repos/pull.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,397 @@ | ||||
| package repos | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"errors" | ||||
| 	"io" | ||||
| 	"net/url" | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/go-git/go-billy/v5" | ||||
| 	"github.com/go-git/go-billy/v5/osfs" | ||||
| 	"github.com/go-git/go-git/v5" | ||||
| 	"github.com/go-git/go-git/v5/plumbing" | ||||
| 	"github.com/go-git/go-git/v5/plumbing/format/diff" | ||||
| 	"github.com/jmoiron/sqlx" | ||||
| 	"github.com/pelletier/go-toml/v2" | ||||
| 	"go.arsenm.dev/logger/log" | ||||
| 	"go.arsenm.dev/lure/distro" | ||||
| 	"go.arsenm.dev/lure/download" | ||||
| 	"go.arsenm.dev/lure/internal/config" | ||||
| 	"go.arsenm.dev/lure/internal/db" | ||||
| 	"go.arsenm.dev/lure/internal/shutils" | ||||
| 	"go.arsenm.dev/lure/internal/shutils/decoder" | ||||
| 	"go.arsenm.dev/lure/internal/types" | ||||
| 	"go.arsenm.dev/lure/vercmp" | ||||
| 	"mvdan.cc/sh/v3/expand" | ||||
| 	"mvdan.cc/sh/v3/interp" | ||||
| 	"mvdan.cc/sh/v3/syntax" | ||||
| ) | ||||
|  | ||||
| // Pull pulls the provided repositories. If a repo doesn't exist, it will be cloned | ||||
| // and its packages will be written to the DB. If it does exist, it will be pulled. | ||||
| // In this case, only changed packages will be processed. | ||||
| func Pull(ctx context.Context, gdb *sqlx.DB, repos []types.Repo) error { | ||||
| 	for _, repo := range repos { | ||||
| 		repoURL, err := url.Parse(repo.URL) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
|  | ||||
| 		log.Info("Pulling repository").Str("name", repo.Name).Send() | ||||
| 		repoDir := filepath.Join(config.RepoDir, repo.Name) | ||||
|  | ||||
| 		var repoFS billy.Filesystem | ||||
| 		gitDir := filepath.Join(repoDir, ".git") | ||||
| 		// Only pull repos that contain valid git repos | ||||
| 		if fi, err := os.Stat(gitDir); err == nil && fi.IsDir() { | ||||
| 			r, err := git.PlainOpen(repoDir) | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
|  | ||||
| 			w, err := r.Worktree() | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
|  | ||||
| 			old, err := r.Head() | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
|  | ||||
| 			err = w.PullContext(ctx, &git.PullOptions{Progress: os.Stderr}) | ||||
| 			if errors.Is(err, git.NoErrAlreadyUpToDate) { | ||||
| 				log.Info("Repository up to date").Str("name", repo.Name).Send() | ||||
| 			} else if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 			repoFS = w.Filesystem | ||||
|  | ||||
| 			// Make sure the DB is created even if the repo is up to date | ||||
| 			if !errors.Is(err, git.NoErrAlreadyUpToDate) || !config.DBPresent { | ||||
| 				new, err := r.Head() | ||||
| 				if err != nil { | ||||
| 					return err | ||||
| 				} | ||||
|  | ||||
| 				// If the DB was not present at startup, that means it's | ||||
| 				// empty. In this case, we need to update the DB fully | ||||
| 				// rather than just incrementally. | ||||
| 				if config.DBPresent { | ||||
| 					err = processRepoChanges(ctx, repo, r, w, old, new, gdb) | ||||
| 					if err != nil { | ||||
| 						return err | ||||
| 					} | ||||
| 				} else { | ||||
| 					err = processRepoFull(ctx, repo, repoDir, gdb) | ||||
| 					if err != nil { | ||||
| 						return err | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 		} else { | ||||
| 			err = os.RemoveAll(repoDir) | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
|  | ||||
| 			err = os.MkdirAll(repoDir, 0o755) | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
|  | ||||
| 			if !strings.HasPrefix(repoURL.Scheme, "git+") { | ||||
| 				repoURL.Scheme = "git+" + repoURL.Scheme | ||||
| 			} | ||||
|  | ||||
| 			err = download.Get(ctx, download.GetOptions{ | ||||
| 				SourceURL:   repoURL.String(), | ||||
| 				Destination: repoDir, | ||||
| 			}) | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
|  | ||||
| 			err = processRepoFull(ctx, repo, repoDir, gdb) | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
|  | ||||
| 			repoFS = osfs.New(repoDir) | ||||
| 		} | ||||
|  | ||||
| 		fl, err := repoFS.Open("lure-repo.toml") | ||||
| 		if err != nil { | ||||
| 			log.Warn("Git repository does not appear to be a valid LURE repo").Str("repo", repo.Name).Send() | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		var repoCfg types.RepoConfig | ||||
| 		err = toml.NewDecoder(fl).Decode(&repoCfg) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		fl.Close() | ||||
|  | ||||
| 		currentVer, _, _ := strings.Cut(config.Version, "-") | ||||
| 		if vercmp.Compare(currentVer, repoCfg.Repo.MinVersion) == -1 { | ||||
| 			log.Warn("LURE repo's minumum LURE version is greater than the current version. Try updating LURE if something doesn't work.").Str("repo", repo.Name).Send() | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| type actionType uint8 | ||||
|  | ||||
| const ( | ||||
| 	actionDelete actionType = iota | ||||
| 	actionUpdate | ||||
| ) | ||||
|  | ||||
| type action struct { | ||||
| 	Type actionType | ||||
| 	File string | ||||
| } | ||||
|  | ||||
| func processRepoChanges(ctx context.Context, repo types.Repo, r *git.Repository, w *git.Worktree, old, new *plumbing.Reference, gdb *sqlx.DB) error { | ||||
| 	oldCommit, err := r.CommitObject(old.Hash()) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	newCommit, err := r.CommitObject(new.Hash()) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	patch, err := oldCommit.Patch(newCommit) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	var actions []action | ||||
| 	for _, fp := range patch.FilePatches() { | ||||
| 		from, to := fp.Files() | ||||
|  | ||||
| 		if !isValid(from, to) { | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		if to == nil { | ||||
| 			actions = append(actions, action{ | ||||
| 				Type: actionDelete, | ||||
| 				File: from.Path(), | ||||
| 			}) | ||||
| 		} else if from == nil { | ||||
| 			actions = append(actions, action{ | ||||
| 				Type: actionUpdate, | ||||
| 				File: to.Path(), | ||||
| 			}) | ||||
| 		} else { | ||||
| 			if from.Path() != to.Path() { | ||||
| 				actions = append(actions, | ||||
| 					action{ | ||||
| 						Type: actionDelete, | ||||
| 						File: from.Path(), | ||||
| 					}, | ||||
| 					action{ | ||||
| 						Type: actionUpdate, | ||||
| 						File: to.Path(), | ||||
| 					}, | ||||
| 				) | ||||
| 			} else { | ||||
| 				actions = append(actions, action{ | ||||
| 					Type: actionUpdate, | ||||
| 					File: to.Path(), | ||||
| 				}) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	repoDir := w.Filesystem.Root() | ||||
| 	parser := syntax.NewParser() | ||||
|  | ||||
| 	for _, action := range actions { | ||||
| 		env := append(os.Environ(), "scriptdir="+filepath.Dir(filepath.Join(repoDir, action.File))) | ||||
| 		runner, err := interp.New( | ||||
| 			interp.Env(expand.ListEnviron(env...)), | ||||
| 			interp.ExecHandler(shutils.NopExec), | ||||
| 			interp.ReadDirHandler(shutils.RestrictedReadDir(repoDir)), | ||||
| 			interp.StatHandler(shutils.RestrictedStat(repoDir)), | ||||
| 			interp.OpenHandler(shutils.RestrictedOpen(repoDir)), | ||||
| 			interp.StdIO(shutils.NopRWC{}, shutils.NopRWC{}, shutils.NopRWC{}), | ||||
| 		) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
|  | ||||
| 		switch action.Type { | ||||
| 		case actionDelete: | ||||
| 			if filepath.Base(action.File) != "lure.sh" { | ||||
| 				continue | ||||
| 			} | ||||
|  | ||||
| 			scriptFl, err := oldCommit.File(action.File) | ||||
| 			if err != nil { | ||||
| 				return nil | ||||
| 			} | ||||
|  | ||||
| 			r, err := scriptFl.Reader() | ||||
| 			if err != nil { | ||||
| 				return nil | ||||
| 			} | ||||
|  | ||||
| 			var pkg db.Package | ||||
| 			err = parseScript(ctx, parser, runner, r, &pkg) | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
|  | ||||
| 			err = db.DeletePkgs(gdb, "name = ? AND repository = ?", pkg.Name, repo.Name) | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 		case actionUpdate: | ||||
| 			if filepath.Base(action.File) != "lure.sh" { | ||||
| 				action.File = filepath.Join(filepath.Dir(action.File), "lure.sh") | ||||
| 			} | ||||
|  | ||||
| 			scriptFl, err := newCommit.File(action.File) | ||||
| 			if err != nil { | ||||
| 				return nil | ||||
| 			} | ||||
|  | ||||
| 			r, err := scriptFl.Reader() | ||||
| 			if err != nil { | ||||
| 				return nil | ||||
| 			} | ||||
|  | ||||
| 			pkg := db.Package{ | ||||
| 				Depends:      db.NewJSON(map[string][]string{}), | ||||
| 				BuildDepends: db.NewJSON(map[string][]string{}), | ||||
| 				Repository:   repo.Name, | ||||
| 			} | ||||
|  | ||||
| 			err = parseScript(ctx, parser, runner, r, &pkg) | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
|  | ||||
| 			resolveOverrides(runner, &pkg) | ||||
|  | ||||
| 			err = db.InsertPackage(gdb, pkg) | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // isValid makes sure the path of the file being updated is valid. | ||||
| // It checks to make sure the file is not within a nested directory | ||||
| // and that it is called lure.sh. | ||||
| func isValid(from, to diff.File) bool { | ||||
| 	var path string | ||||
| 	if from != nil { | ||||
| 		path = from.Path() | ||||
| 	} | ||||
| 	if to != nil { | ||||
| 		path = to.Path() | ||||
| 	} | ||||
|  | ||||
| 	match, _ := filepath.Match("*/*.sh", path) | ||||
| 	return match | ||||
| } | ||||
|  | ||||
| func processRepoFull(ctx context.Context, repo types.Repo, repoDir string, gdb *sqlx.DB) error { | ||||
| 	glob := filepath.Join(repoDir, "/*/lure.sh") | ||||
| 	matches, err := filepath.Glob(glob) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	parser := syntax.NewParser() | ||||
|  | ||||
| 	for _, match := range matches { | ||||
| 		env := append(os.Environ(), "scriptdir="+filepath.Dir(match)) | ||||
| 		runner, err := interp.New( | ||||
| 			interp.Env(expand.ListEnviron(env...)), | ||||
| 			interp.ExecHandler(shutils.NopExec), | ||||
| 			interp.ReadDirHandler(shutils.RestrictedReadDir(repoDir)), | ||||
| 			interp.StatHandler(shutils.RestrictedStat(repoDir)), | ||||
| 			interp.OpenHandler(shutils.RestrictedOpen(repoDir)), | ||||
| 			interp.StdIO(shutils.NopRWC{}, shutils.NopRWC{}, shutils.NopRWC{}), | ||||
| 		) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
|  | ||||
| 		scriptFl, err := os.Open(match) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
|  | ||||
| 		pkg := db.Package{ | ||||
| 			Depends:      db.NewJSON(map[string][]string{}), | ||||
| 			BuildDepends: db.NewJSON(map[string][]string{}), | ||||
| 			Repository:   repo.Name, | ||||
| 		} | ||||
|  | ||||
| 		err = parseScript(ctx, parser, runner, scriptFl, &pkg) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
|  | ||||
| 		resolveOverrides(runner, &pkg) | ||||
|  | ||||
| 		err = db.InsertPackage(gdb, pkg) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func parseScript(ctx context.Context, parser *syntax.Parser, runner *interp.Runner, r io.ReadCloser, pkg *db.Package) error { | ||||
| 	defer r.Close() | ||||
| 	fl, err := parser.Parse(r, "lure.sh") | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	runner.Reset() | ||||
| 	err = runner.Run(ctx, fl) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	d := decoder.New(&distro.OSRelease{}, runner) | ||||
| 	d.Overrides = false | ||||
| 	d.LikeDistros = false | ||||
| 	return d.DecodeVars(pkg) | ||||
| } | ||||
|  | ||||
| func resolveOverrides(runner *interp.Runner, pkg *db.Package) { | ||||
| 	for name, val := range runner.Vars { | ||||
| 		if strings.HasPrefix(name, "deps") { | ||||
| 			override := strings.TrimPrefix(name, "deps") | ||||
| 			override = strings.TrimPrefix(override, "_") | ||||
|  | ||||
| 			pkg.Depends.Val[override] = val.List | ||||
| 		} else if strings.HasPrefix(name, "build_deps") { | ||||
| 			override := strings.TrimPrefix(name, "build_deps") | ||||
| 			override = strings.TrimPrefix(override, "_") | ||||
|  | ||||
| 			pkg.BuildDepends.Val[override] = val.List | ||||
| 		} else { | ||||
| 			continue | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										95
									
								
								internal/repos/pull_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										95
									
								
								internal/repos/pull_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,95 @@ | ||||
| package repos_test | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
| 	"testing" | ||||
|  | ||||
| 	"github.com/jmoiron/sqlx" | ||||
| 	"go.arsenm.dev/lure/internal/config" | ||||
| 	"go.arsenm.dev/lure/internal/db" | ||||
| 	"go.arsenm.dev/lure/internal/repos" | ||||
| 	"go.arsenm.dev/lure/internal/types" | ||||
| ) | ||||
|  | ||||
| func setCfgDirs(t *testing.T) { | ||||
| 	t.Helper() | ||||
|  | ||||
| 	var err error | ||||
| 	config.CacheDir, err = os.MkdirTemp("/tmp", "lure-pull-test.*") | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Expected no error, got %s", err) | ||||
| 	} | ||||
|  | ||||
| 	config.RepoDir = filepath.Join(config.CacheDir, "repo") | ||||
| 	config.PkgsDir = filepath.Join(config.CacheDir, "pkgs") | ||||
|  | ||||
| 	err = os.MkdirAll(config.RepoDir, 0o755) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Expected no error, got %s", err) | ||||
| 	} | ||||
|  | ||||
| 	err = os.MkdirAll(config.PkgsDir, 0o755) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Expected no error, got %s", err) | ||||
| 	} | ||||
|  | ||||
| 	config.DBPath = filepath.Join(config.CacheDir, "db") | ||||
| } | ||||
|  | ||||
| func removeCacheDir(t *testing.T) { | ||||
| 	t.Helper() | ||||
|  | ||||
| 	err := os.RemoveAll(config.CacheDir) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Expected no error, got %s", err) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestPull(t *testing.T) { | ||||
| 	gdb, err := sqlx.Open("sqlite", ":memory:") | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Expected no error, got %s", err) | ||||
| 	} | ||||
| 	defer gdb.Close() | ||||
|  | ||||
| 	err = db.Init(gdb) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Expected no error, got %s", err) | ||||
| 	} | ||||
|  | ||||
| 	setCfgDirs(t) | ||||
| 	defer removeCacheDir(t) | ||||
|  | ||||
| 	ctx := context.Background() | ||||
|  | ||||
| 	err = repos.Pull(ctx, gdb, []types.Repo{ | ||||
| 		{ | ||||
| 			Name: "default", | ||||
| 			URL:  "https://github.com/Arsen6331/lure-repo.git", | ||||
| 		}, | ||||
| 	}) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Expected no error, got %s", err) | ||||
| 	} | ||||
|  | ||||
| 	result, err := db.GetPkgs(gdb, "name LIKE 'itd%'") | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Expected no error, got %s", err) | ||||
| 	} | ||||
|  | ||||
| 	var pkgAmt int | ||||
| 	for result.Next() { | ||||
| 		var dbPkg db.Package | ||||
| 		err = result.StructScan(&dbPkg) | ||||
| 		if err != nil { | ||||
| 			t.Errorf("Expected no error, got %s", err) | ||||
| 		} | ||||
| 		pkgAmt++ | ||||
| 	} | ||||
|  | ||||
| 	if pkgAmt < 2 { | ||||
| 		t.Errorf("Expected 2 packages to match, got %d", pkgAmt) | ||||
| 	} | ||||
| } | ||||
| @@ -21,14 +21,12 @@ package decoder | ||||
| import ( | ||||
| 	"context" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"reflect" | ||||
| 	"runtime" | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/mitchellh/mapstructure" | ||||
| 	"go.arsenm.dev/lure/distro" | ||||
| 	"go.arsenm.dev/lure/internal/cpu" | ||||
| 	"go.arsenm.dev/lure/internal/overrides" | ||||
| 	"golang.org/x/exp/slices" | ||||
| 	"mvdan.cc/sh/v3/expand" | ||||
| 	"mvdan.cc/sh/v3/interp" | ||||
| @@ -70,6 +68,13 @@ func (d *Decoder) DecodeVar(name string, val any) error { | ||||
|  | ||||
| 	dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ | ||||
| 		WeaklyTypedInput: true, | ||||
| 		DecodeHook: mapstructure.DecodeHookFuncValue(func(from, to reflect.Value) (interface{}, error) { | ||||
| 			if strings.Contains(to.Type().String(), "db.JSON") { | ||||
| 				to.FieldByName("Val").Set(from) | ||||
| 				return to, nil | ||||
| 			} | ||||
| 			return from.Interface(), nil | ||||
| 		}), | ||||
| 		Result:  val, | ||||
| 		TagName: "sh", | ||||
| 	}) | ||||
| @@ -162,7 +167,7 @@ func (d *Decoder) GetFunc(name string) (ScriptFunc, bool) { | ||||
| } | ||||
|  | ||||
| func (d *Decoder) getFunc(name string) *syntax.Stmt { | ||||
| 	names := d.genPossibleNames(name) | ||||
| 	names := overrides.Resolve(d.info, overrides.DefaultOpts.WithName(name)) | ||||
| 	for _, fnName := range names { | ||||
| 		fn, ok := d.runner.Funcs[fnName] | ||||
| 		if ok { | ||||
| @@ -175,7 +180,7 @@ func (d *Decoder) getFunc(name string) *syntax.Stmt { | ||||
| // getVar gets a variable based on its name, taking into account | ||||
| // override variables and nameref variables. | ||||
| func (d *Decoder) getVar(name string) *expand.Variable { | ||||
| 	names := d.genPossibleNames(name) | ||||
| 	names := overrides.Resolve(d.info, overrides.DefaultOpts.WithName(name)) | ||||
| 	for _, varName := range names { | ||||
| 		val, ok := d.runner.Vars[varName] | ||||
| 		if ok { | ||||
| @@ -193,43 +198,3 @@ func (d *Decoder) getVar(name string) *expand.Variable { | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // genPossibleNames generates a slice of the possible names that | ||||
| // could be used in the order that they should be checked | ||||
| func (d *Decoder) genPossibleNames(name string) []string { | ||||
| 	if !d.Overrides { | ||||
| 		return []string{name} | ||||
| 	} | ||||
|  | ||||
| 	architectures := []string{runtime.GOARCH} | ||||
|  | ||||
| 	if runtime.GOARCH == "arm" { | ||||
| 		// More specific goes first | ||||
| 		architectures[0] = cpu.ARMVariant() | ||||
| 		architectures = append(architectures, "arm") | ||||
| 	} | ||||
|  | ||||
| 	distros := []string{d.info.ID} | ||||
| 	if d.LikeDistros { | ||||
| 		distros = append(distros, d.info.Like...) | ||||
| 	} | ||||
|  | ||||
| 	var out []string | ||||
| 	for _, arch := range architectures { | ||||
| 		for _, distro := range distros { | ||||
| 			out = append( | ||||
| 				out, | ||||
| 				fmt.Sprintf("%s_%s_%s", name, arch, distro), | ||||
| 				fmt.Sprintf("%s_%s", name, distro), | ||||
| 			) | ||||
| 		} | ||||
| 		out = append(out, fmt.Sprintf("%s_%s", name, arch)) | ||||
| 	} | ||||
| 	out = append(out, name) | ||||
|  | ||||
| 	for index, item := range out { | ||||
| 		out[index] = strings.ReplaceAll(item, "-", "_") | ||||
| 	} | ||||
|  | ||||
| 	return out | ||||
| } | ||||
|   | ||||
							
								
								
									
										206
									
								
								internal/shutils/decoder/decoder_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										206
									
								
								internal/shutils/decoder/decoder_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,206 @@ | ||||
| package decoder_test | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"context" | ||||
| 	"errors" | ||||
| 	"os" | ||||
| 	"reflect" | ||||
| 	"strings" | ||||
| 	"testing" | ||||
|  | ||||
| 	"go.arsenm.dev/lure/distro" | ||||
| 	"go.arsenm.dev/lure/internal/shutils/decoder" | ||||
| 	"mvdan.cc/sh/v3/interp" | ||||
| 	"mvdan.cc/sh/v3/syntax" | ||||
| ) | ||||
|  | ||||
| type BuildVars struct { | ||||
| 	Name          string   `sh:"name,required"` | ||||
| 	Version       string   `sh:"version,required"` | ||||
| 	Release       int      `sh:"release,required"` | ||||
| 	Epoch         uint     `sh:"epoch"` | ||||
| 	Description   string   `sh:"desc"` | ||||
| 	Homepage      string   `sh:"homepage"` | ||||
| 	Maintainer    string   `sh:"maintainer"` | ||||
| 	Architectures []string `sh:"architectures"` | ||||
| 	Licenses      []string `sh:"license"` | ||||
| 	Provides      []string `sh:"provides"` | ||||
| 	Conflicts     []string `sh:"conflicts"` | ||||
| 	Depends       []string `sh:"deps"` | ||||
| 	BuildDepends  []string `sh:"build_deps"` | ||||
| 	Replaces      []string `sh:"replaces"` | ||||
| } | ||||
|  | ||||
| const testScript = ` | ||||
| 	name='test' | ||||
| 	version='0.0.1' | ||||
| 	release=1 | ||||
| 	epoch=2 | ||||
| 	desc="Test package" | ||||
| 	homepage='https://lure.arsenm.dev' | ||||
| 	maintainer='Arsen Musayelyan <arsen@arsenm.dev>' | ||||
| 	architectures=('arm64' 'amd64') | ||||
| 	license=('GPL-3.0-or-later') | ||||
| 	provides=('test') | ||||
| 	conflicts=('test') | ||||
| 	replaces=('test-old') | ||||
| 	replaces_test_os=('test-legacy') | ||||
|  | ||||
| 	deps=('sudo') | ||||
|  | ||||
| 	build_deps=('golang') | ||||
| 	build_deps_arch=('go') | ||||
|  | ||||
| 	test() { | ||||
| 		echo "Test" | ||||
| 	} | ||||
|  | ||||
| 	package() { | ||||
| 		install-binary test | ||||
| 	} | ||||
| ` | ||||
|  | ||||
| var osRelease = &distro.OSRelease{ | ||||
| 	ID:   "test_os", | ||||
| 	Like: []string{"arch"}, | ||||
| } | ||||
|  | ||||
| func TestDecodeVars(t *testing.T) { | ||||
| 	ctx := context.Background() | ||||
|  | ||||
| 	fl, err := syntax.NewParser().Parse(strings.NewReader(testScript), "lure.sh") | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Expected no error, got %s", err) | ||||
| 	} | ||||
|  | ||||
| 	runner, err := interp.New() | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Expected no error, got %s", err) | ||||
| 	} | ||||
|  | ||||
| 	err = runner.Run(ctx, fl) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Expected no error, got %s", err) | ||||
| 	} | ||||
|  | ||||
| 	dec := decoder.New(osRelease, runner) | ||||
|  | ||||
| 	var bv BuildVars | ||||
| 	err = dec.DecodeVars(&bv) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Expected no error, got %s", err) | ||||
| 	} | ||||
|  | ||||
| 	expected := BuildVars{ | ||||
| 		Name:          "test", | ||||
| 		Version:       "0.0.1", | ||||
| 		Release:       1, | ||||
| 		Epoch:         2, | ||||
| 		Description:   "Test package", | ||||
| 		Homepage:      "https://lure.arsenm.dev", | ||||
| 		Maintainer:    "Arsen Musayelyan <arsen@arsenm.dev>", | ||||
| 		Architectures: []string{"arm64", "amd64"}, | ||||
| 		Licenses:      []string{"GPL-3.0-or-later"}, | ||||
| 		Provides:      []string{"test"}, | ||||
| 		Conflicts:     []string{"test"}, | ||||
| 		Replaces:      []string{"test-legacy"}, | ||||
| 		Depends:       []string{"sudo"}, | ||||
| 		BuildDepends:  []string{"go"}, | ||||
| 	} | ||||
|  | ||||
| 	if !reflect.DeepEqual(bv, expected) { | ||||
| 		t.Errorf("Expected %v, got %v", expected, bv) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestDecodeVarsMissing(t *testing.T) { | ||||
| 	ctx := context.Background() | ||||
|  | ||||
| 	const testScript = ` | ||||
| 		name='test' | ||||
| 		epoch=2 | ||||
| 		desc="Test package" | ||||
| 		homepage='https://lure.arsenm.dev' | ||||
| 		maintainer='Arsen Musayelyan <arsen@arsenm.dev>' | ||||
| 		architectures=('arm64' 'amd64') | ||||
| 		license=('GPL-3.0-or-later') | ||||
| 		provides=('test') | ||||
| 		conflicts=('test') | ||||
| 		replaces=('test-old') | ||||
| 		replaces_test_os=('test-legacy') | ||||
|  | ||||
| 		deps=('sudo') | ||||
|  | ||||
| 		build_deps=('golang') | ||||
| 		build_deps_arch=('go') | ||||
|  | ||||
| 		test() { | ||||
| 			echo "Test" | ||||
| 		} | ||||
|  | ||||
| 		package() { | ||||
| 			install-binary test | ||||
| 		} | ||||
| 	` | ||||
|  | ||||
| 	fl, err := syntax.NewParser().Parse(strings.NewReader(testScript), "lure.sh") | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Expected no error, got %s", err) | ||||
| 	} | ||||
|  | ||||
| 	runner, err := interp.New() | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Expected no error, got %s", err) | ||||
| 	} | ||||
|  | ||||
| 	err = runner.Run(ctx, fl) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Expected no error, got %s", err) | ||||
| 	} | ||||
|  | ||||
| 	dec := decoder.New(osRelease, runner) | ||||
|  | ||||
| 	var bv BuildVars | ||||
| 	err = dec.DecodeVars(&bv) | ||||
|  | ||||
| 	var notFoundErr decoder.VarNotFoundError | ||||
| 	if !errors.As(err, ¬FoundErr) { | ||||
| 		t.Fatalf("Expected VarNotFoundError, got %T %v", err, err) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestGetFunc(t *testing.T) { | ||||
| 	ctx := context.Background() | ||||
|  | ||||
| 	fl, err := syntax.NewParser().Parse(strings.NewReader(testScript), "lure.sh") | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Expected no error, got %s", err) | ||||
| 	} | ||||
|  | ||||
| 	runner, err := interp.New() | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Expected no error, got %s", err) | ||||
| 	} | ||||
|  | ||||
| 	err = runner.Run(ctx, fl) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Expected no error, got %s", err) | ||||
| 	} | ||||
|  | ||||
| 	dec := decoder.New(osRelease, runner) | ||||
| 	fn, ok := dec.GetFunc("test") | ||||
| 	if !ok { | ||||
| 		t.Fatalf("Expected test() function to exist") | ||||
| 	} | ||||
|  | ||||
| 	buf := &bytes.Buffer{} | ||||
| 	err = fn(ctx, interp.StdIO(os.Stdin, buf, buf)) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Expected no error, got %s", err) | ||||
| 	} | ||||
|  | ||||
| 	if buf.String() != "Test\n" { | ||||
| 		t.Fatalf(`Expected "Test\n", got %#v`, buf.String()) | ||||
| 	} | ||||
| } | ||||
| @@ -39,7 +39,11 @@ type ExecFunc func(hc interp.HandlerContext, name string, args []string) error | ||||
|  | ||||
| type ExecFuncs map[string]ExecFunc | ||||
|  | ||||
| func (ef ExecFuncs) ExecHandler(ctx context.Context, args []string) error { | ||||
| // ExecHandler returns a new ExecHandlerFunc that falls back to fallback | ||||
| // if the command cannot be found in the map. If fallback is nil, the default | ||||
| // handler is used. | ||||
| func (ef ExecFuncs) ExecHandler(fallback interp.ExecHandlerFunc) interp.ExecHandlerFunc { | ||||
| 	return func(ctx context.Context, args []string) error { | ||||
| 		name := args[0] | ||||
|  | ||||
| 		if fn, ok := ef[name]; ok { | ||||
| @@ -51,6 +55,9 @@ func (ef ExecFuncs) ExecHandler(ctx context.Context, args []string) error { | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 	defExec := interp.DefaultExecHandler(2 * time.Second) | ||||
| 	return defExec(ctx, args) | ||||
| 		if fallback == nil { | ||||
| 			fallback = interp.DefaultExecHandler(2 * time.Second) | ||||
| 		} | ||||
| 		return fallback(ctx, args) | ||||
| 	} | ||||
| } | ||||
|   | ||||
							
								
								
									
										106
									
								
								internal/shutils/exec_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										106
									
								
								internal/shutils/exec_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,106 @@ | ||||
| package shutils_test | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"strings" | ||||
| 	"testing" | ||||
|  | ||||
| 	"go.arsenm.dev/lure/distro" | ||||
| 	"go.arsenm.dev/lure/internal/shutils" | ||||
| 	"go.arsenm.dev/lure/internal/shutils/decoder" | ||||
| 	"mvdan.cc/sh/v3/interp" | ||||
| 	"mvdan.cc/sh/v3/syntax" | ||||
| ) | ||||
|  | ||||
| const testScript = ` | ||||
| 	name='test' | ||||
| 	version='0.0.1' | ||||
| 	release=1 | ||||
| 	epoch=2 | ||||
| 	desc="Test package" | ||||
| 	homepage='https://lure.arsenm.dev' | ||||
| 	maintainer='Arsen Musayelyan <arsen@arsenm.dev>' | ||||
| 	architectures=('arm64' 'amd64') | ||||
| 	license=('GPL-3.0-or-later') | ||||
| 	provides=('test') | ||||
| 	conflicts=('test') | ||||
| 	replaces=('test-old') | ||||
| 	replaces_test_os=('test-legacy') | ||||
|  | ||||
| 	deps=('sudo') | ||||
|  | ||||
| 	build_deps=('golang') | ||||
| 	build_deps_arch=('go') | ||||
|  | ||||
| 	test() { | ||||
| 		test-cmd "Hello, World" | ||||
| 		test-fb | ||||
| 	} | ||||
|  | ||||
| 	package() { | ||||
| 		install-binary test | ||||
| 	} | ||||
| ` | ||||
|  | ||||
| var osRelease = &distro.OSRelease{ | ||||
| 	ID:   "test_os", | ||||
| 	Like: []string{"arch"}, | ||||
| } | ||||
|  | ||||
| func TestExecFuncs(t *testing.T) { | ||||
| 	ctx := context.Background() | ||||
|  | ||||
| 	fl, err := syntax.NewParser().Parse(strings.NewReader(testScript), "lure.sh") | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Expected no error, got %s", err) | ||||
| 	} | ||||
|  | ||||
| 	runner, err := interp.New() | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Expected no error, got %s", err) | ||||
| 	} | ||||
|  | ||||
| 	err = runner.Run(ctx, fl) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Expected no error, got %s", err) | ||||
| 	} | ||||
|  | ||||
| 	dec := decoder.New(osRelease, runner) | ||||
| 	fn, ok := dec.GetFunc("test") | ||||
| 	if !ok { | ||||
| 		t.Fatalf("Expected test() function to exist") | ||||
| 	} | ||||
|  | ||||
| 	eh := shutils.ExecFuncs{ | ||||
| 		"test-cmd": func(hc interp.HandlerContext, name string, args []string) error { | ||||
| 			if name != "test-cmd" { | ||||
| 				t.Errorf("Expected name to be 'test-cmd', got '%s'", name) | ||||
| 			} | ||||
|  | ||||
| 			if len(args) < 1 { | ||||
| 				t.Fatalf("Expected at least one argument, got %d", len(args)) | ||||
| 			} | ||||
|  | ||||
| 			if args[0] != "Hello, World" { | ||||
| 				t.Errorf("Expected first argument to be 'Hello, World', got '%s'", args[0]) | ||||
| 			} | ||||
|  | ||||
| 			return nil | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	fbInvoked := false | ||||
| 	fbHandler := func(context.Context, []string) error { | ||||
| 		fbInvoked = true | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	err = fn(ctx, interp.ExecHandler(eh.ExecHandler(fbHandler))) | ||||
| 	if err != nil { | ||||
| 		t.Errorf("Expected no error, got %s", err) | ||||
| 	} | ||||
|  | ||||
| 	if !fbInvoked { | ||||
| 		t.Errorf("Expected fallback handler to be invoked") | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										40
									
								
								internal/shutils/nop_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								internal/shutils/nop_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,40 @@ | ||||
| package shutils_test | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"context" | ||||
| 	"os" | ||||
| 	"strings" | ||||
| 	"testing" | ||||
|  | ||||
| 	"go.arsenm.dev/lure/internal/shutils" | ||||
| 	"mvdan.cc/sh/v3/interp" | ||||
| 	"mvdan.cc/sh/v3/syntax" | ||||
| ) | ||||
|  | ||||
| func TestNopExec(t *testing.T) { | ||||
| 	ctx := context.Background() | ||||
|  | ||||
| 	fl, err := syntax.NewParser().Parse(strings.NewReader(`/bin/echo test`), "lure.sh") | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Expected no error, got %s", err) | ||||
| 	} | ||||
|  | ||||
| 	buf := &bytes.Buffer{} | ||||
| 	runner, err := interp.New( | ||||
| 		interp.ExecHandler(shutils.NopExec), | ||||
| 		interp.StdIO(os.Stdin, buf, buf), | ||||
| 	) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Expected no error, got %s", err) | ||||
| 	} | ||||
|  | ||||
| 	err = runner.Run(ctx, fl) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Expected no error, got %s", err) | ||||
| 	} | ||||
|  | ||||
| 	if buf.String() != "" { | ||||
| 		t.Fatalf("Expected empty string, got %#v", buf.String()) | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										76
									
								
								internal/shutils/restricted.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								internal/shutils/restricted.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,76 @@ | ||||
| /* | ||||
|  * LURE - Linux User REpository | ||||
|  * Copyright (C) 2022 Arsen Musayelyan | ||||
|  * | ||||
|  * This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU General Public License as published by | ||||
|  * the Free Software Foundation, either version 3 of the License, or | ||||
|  * (at your option) any later version. | ||||
|  * | ||||
|  * This program is distributed in the hope that it will be useful, | ||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|  * GNU General Public License for more details. | ||||
|  * | ||||
|  * You should have received a copy of the GNU General Public License | ||||
|  * along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
|  */ | ||||
|  | ||||
| package shutils | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"io" | ||||
| 	"os" | ||||
| 	"strings" | ||||
| 	"time" | ||||
|  | ||||
| 	"golang.org/x/exp/slices" | ||||
| 	"mvdan.cc/sh/v3/interp" | ||||
| ) | ||||
|  | ||||
| func RestrictedReadDir(allowedPrefixes ...string) interp.ReadDirHandlerFunc { | ||||
| 	return func(ctx context.Context, s string) ([]os.FileInfo, error) { | ||||
| 		for _, allowedPrefix := range allowedPrefixes { | ||||
| 			if strings.HasPrefix(s, allowedPrefix) { | ||||
| 				return interp.DefaultReadDirHandler()(ctx, s) | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		return nil, os.ErrNotExist | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func RestrictedStat(allowedPrefixes ...string) interp.StatHandlerFunc { | ||||
| 	return func(ctx context.Context, s string, b bool) (os.FileInfo, error) { | ||||
| 		for _, allowedPrefix := range allowedPrefixes { | ||||
| 			if strings.HasPrefix(s, allowedPrefix) { | ||||
| 				return interp.DefaultStatHandler()(ctx, s, b) | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		return nil, os.ErrNotExist | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func RestrictedOpen(allowedPrefixes ...string) interp.OpenHandlerFunc { | ||||
| 	return func(ctx context.Context, s string, i int, fm os.FileMode) (io.ReadWriteCloser, error) { | ||||
| 		for _, allowedPrefix := range allowedPrefixes { | ||||
| 			if strings.HasPrefix(s, allowedPrefix) { | ||||
| 				return interp.DefaultOpenHandler()(ctx, s, i, fm) | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		return NopRWC{}, nil | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func RestrictedExec(allowedCmds ...string) interp.ExecHandlerFunc { | ||||
| 	return func(ctx context.Context, args []string) error { | ||||
| 		if slices.Contains(allowedCmds, args[0]) { | ||||
| 			return interp.DefaultExecHandler(2*time.Second)(ctx, args) | ||||
| 		} | ||||
|  | ||||
| 		return nil | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										15
									
								
								internal/types/config.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								internal/types/config.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | ||||
| package types | ||||
|  | ||||
| // Config represents the LURE configuration file | ||||
| type Config struct { | ||||
| 	RootCmd          string   `toml:"rootCmd"` | ||||
| 	PagerStyle       string   `toml:"pagerStyle"` | ||||
| 	IgnorePkgUpdates []string `toml:"ignorePkgUpdates"` | ||||
| 	Repos            []Repo   `toml:"repo"` | ||||
| } | ||||
|  | ||||
| // Repo represents a LURE repo within a configuration file | ||||
| type Repo struct { | ||||
| 	Name string `toml:"name"` | ||||
| 	URL  string `toml:"url"` | ||||
| } | ||||
							
								
								
									
										8
									
								
								internal/types/repo.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								internal/types/repo.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | ||||
| package types | ||||
|  | ||||
| // RepoConfig represents a LURE repo's lure-repo.toml file. | ||||
| type RepoConfig struct { | ||||
| 	Repo struct { | ||||
| 		MinVersion string `toml:"minVersion"` | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										59
									
								
								list.go
									
									
									
									
									
								
							
							
						
						
									
										59
									
								
								list.go
									
									
									
									
									
								
							| @@ -22,27 +22,66 @@ import ( | ||||
| 	"fmt" | ||||
|  | ||||
| 	"github.com/urfave/cli/v2" | ||||
| 	"go.arsenm.dev/lure/distro" | ||||
| 	"go.arsenm.dev/logger/log" | ||||
| 	"go.arsenm.dev/lure/internal/db" | ||||
| 	"go.arsenm.dev/lure/internal/repos" | ||||
| 	"go.arsenm.dev/lure/manager" | ||||
| ) | ||||
|  | ||||
| func listCmd(c *cli.Context) error { | ||||
| 	info, err := distro.ParseOSRelease(c.Context) | ||||
| 	err := repos.Pull(c.Context, gdb, cfg.Repos) | ||||
| 	if err != nil { | ||||
| 		log.Fatal("Error parsing os-release").Err(err).Send() | ||||
| 		log.Fatal("Error pulling repositories").Err(err).Send() | ||||
| 	} | ||||
|  | ||||
| 	pkgs, err := findPkg("*") | ||||
| 	if err != nil { | ||||
| 		log.Fatal("Error finding packages").Err(err).Send() | ||||
| 	where := "true" | ||||
| 	args := []any(nil) | ||||
| 	if c.NArg() > 0 { | ||||
| 		where = "name LIKE ? OR json_array_contains(provides, ?)" | ||||
| 		args = []any{c.Args().First(), c.Args().First()} | ||||
| 	} | ||||
|  | ||||
| 	for _, script := range pkgs { | ||||
| 		vars, err := getBuildVars(c.Context, script, info) | ||||
| 	result, err := db.GetPkgs(gdb, where, args...) | ||||
| 	if err != nil { | ||||
| 			log.Fatal("Error getting build variables").Err(err).Send() | ||||
| 		log.Fatal("Error getting packages").Err(err).Send() | ||||
| 	} | ||||
| 	defer result.Close() | ||||
|  | ||||
| 	var installed map[string]string | ||||
| 	if c.Bool("installed") { | ||||
| 		mgr := manager.Detect() | ||||
| 		if mgr == nil { | ||||
| 			log.Fatal("Unable to detect supported package manager on system").Send() | ||||
| 		} | ||||
|  | ||||
| 		fmt.Println(vars.Name, vars.Version) | ||||
| 		installed, err = mgr.ListInstalled(&manager.Opts{AsRoot: false}) | ||||
| 		if err != nil { | ||||
| 			log.Fatal("Error listing installed packages").Err(err).Send() | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	for result.Next() { | ||||
| 		var pkg db.Package | ||||
| 		err := result.StructScan(&pkg) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
|  | ||||
| 		version := pkg.Version | ||||
| 		if c.Bool("installed") { | ||||
| 			instVersion, ok := installed[pkg.Name] | ||||
| 			if !ok { | ||||
| 				return nil | ||||
| 			} else { | ||||
| 				version = instVersion | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		fmt.Printf("%s/%s %s\n", pkg.Repository, pkg.Name, version) | ||||
| 	} | ||||
|  | ||||
| 	if err != nil { | ||||
| 		log.Fatal("Error iterating over packages").Err(err).Send() | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
|   | ||||
							
								
								
									
										72
									
								
								main.go
									
									
									
									
									
								
							
							
						
						
									
										72
									
								
								main.go
									
									
									
									
									
								
							| @@ -20,16 +20,26 @@ package main | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"fmt" | ||||
| 	"os" | ||||
| 	"os/signal" | ||||
| 	"strings" | ||||
| 	"syscall" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/urfave/cli/v2" | ||||
| 	"go.arsenm.dev/logger" | ||||
| 	"go.arsenm.dev/logger/log" | ||||
| 	"go.arsenm.dev/lure/internal/config" | ||||
| 	"go.arsenm.dev/lure/internal/db" | ||||
| 	"go.arsenm.dev/lure/manager" | ||||
| ) | ||||
|  | ||||
| var log = logger.NewPretty(os.Stderr) | ||||
| //go:generate scripts/gen-version.sh | ||||
|  | ||||
| func init() { | ||||
| 	log.Logger = logger.NewCLI(os.Stderr) | ||||
| } | ||||
|  | ||||
| func main() { | ||||
| 	ctx := context.Background() | ||||
| @@ -39,18 +49,27 @@ func main() { | ||||
| 		<-ctx.Done() | ||||
| 		// Exit the program after a maximum of 200ms | ||||
| 		time.Sleep(200 * time.Millisecond) | ||||
| 		gdb.Close() | ||||
| 		os.Exit(0) | ||||
| 	}() | ||||
|  | ||||
| 	app := &cli.App{ | ||||
| 		Name:  "lure", | ||||
| 		Usage: "Linux User REpository", | ||||
| 		Flags: []cli.Flag{ | ||||
| 			&cli.StringFlag{ | ||||
| 				Name:    "pm-args", | ||||
| 				Aliases: []string{"P"}, | ||||
| 				Usage:   "Arguments to be passed on to the package manager", | ||||
| 			}, | ||||
| 		}, | ||||
| 		Commands: []*cli.Command{ | ||||
| 			{ | ||||
| 				Name:         "install", | ||||
| 				Usage:        "Install a new package", | ||||
| 				Aliases:      []string{"in"}, | ||||
| 				Action:       installCmd, | ||||
| 				BashComplete: completionInstall, | ||||
| 			}, | ||||
| 			{ | ||||
| 				Name:    "remove", | ||||
| @@ -65,11 +84,24 @@ func main() { | ||||
| 				Action:  upgradeCmd, | ||||
| 			}, | ||||
| 			{ | ||||
| 				Flags: []cli.Flag{ | ||||
| 					&cli.BoolFlag{ | ||||
| 						Name:    "all", | ||||
| 						Aliases: []string{"a"}, | ||||
| 						Usage:   "Show all information, not just for the current distro", | ||||
| 					}, | ||||
| 				}, | ||||
| 				Name:   "info", | ||||
| 				Usage:  "Print information about a package", | ||||
| 				Action: infoCmd, | ||||
| 			}, | ||||
| 			{ | ||||
| 				Flags: []cli.Flag{ | ||||
| 					&cli.BoolFlag{ | ||||
| 						Name:    "installed", | ||||
| 						Aliases: []string{"I"}, | ||||
| 					}, | ||||
| 				}, | ||||
| 				Name:    "list", | ||||
| 				Usage:   "List LURE repo packages", | ||||
| 				Aliases: []string{"ls"}, | ||||
| @@ -128,12 +160,30 @@ func main() { | ||||
| 				Aliases: []string{"ref"}, | ||||
| 				Action:  refreshCmd, | ||||
| 			}, | ||||
| 			{ | ||||
| 				Name:   "fix", | ||||
| 				Usage:  "Attempt to fix problems with LURE", | ||||
| 				Action: fixCmd, | ||||
| 			}, | ||||
| 			{ | ||||
| 				Name:   "version", | ||||
| 				Usage:  "Display the current LURE version and exit", | ||||
| 				Action: displayVersion, | ||||
| 			}, | ||||
| 		}, | ||||
| 		Before: func(c *cli.Context) error { | ||||
| 			args := strings.Split(c.String("pm-args"), " ") | ||||
| 			if len(args) == 1 && args[0] == "" { | ||||
| 				args = nil | ||||
| 			} | ||||
|  | ||||
| 			manager.Args = append(manager.Args, args...) | ||||
| 			return nil | ||||
| 		}, | ||||
| 		After: func(ctx *cli.Context) error { | ||||
| 			return gdb.Close() | ||||
| 		}, | ||||
| 		EnableBashCompletion: true, | ||||
| 	} | ||||
|  | ||||
| 	err := app.RunContext(ctx, os.Args) | ||||
| @@ -143,6 +193,24 @@ func main() { | ||||
| } | ||||
|  | ||||
| func displayVersion(c *cli.Context) error { | ||||
| 	print(version) | ||||
| 	print(config.Version) | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func completionInstall(c *cli.Context) { | ||||
| 	result, err := db.GetPkgs(gdb, "true") | ||||
| 	if err != nil { | ||||
| 		log.Fatal("Error getting packages").Err(err).Send() | ||||
| 	} | ||||
| 	defer result.Close() | ||||
|  | ||||
| 	for result.Next() { | ||||
| 		var pkg db.Package | ||||
| 		err = result.StructScan(&pkg) | ||||
| 		if err != nil { | ||||
| 			log.Fatal("Error iterating over packages").Err(err).Send() | ||||
| 		} | ||||
|  | ||||
| 		fmt.Println(pkg.Name) | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -159,6 +159,7 @@ func (a *APK) getCmd(opts *Opts, mgrCmd string, args ...string) *exec.Cmd { | ||||
| 	var cmd *exec.Cmd | ||||
| 	if opts.AsRoot { | ||||
| 		cmd = exec.Command(getRootCmd(a.rootCmd), mgrCmd) | ||||
| 		cmd.Args = append(cmd.Args, opts.Args...) | ||||
| 		cmd.Args = append(cmd.Args, args...) | ||||
| 	} else { | ||||
| 		cmd = exec.Command(mgrCmd, args...) | ||||
|   | ||||
| @@ -145,6 +145,7 @@ func (a *APT) getCmd(opts *Opts, mgrCmd string, args ...string) *exec.Cmd { | ||||
| 	var cmd *exec.Cmd | ||||
| 	if opts.AsRoot { | ||||
| 		cmd = exec.Command(getRootCmd(a.rootCmd), mgrCmd) | ||||
| 		cmd.Args = append(cmd.Args, opts.Args...) | ||||
| 		cmd.Args = append(cmd.Args, args...) | ||||
| 	} else { | ||||
| 		cmd = exec.Command(mgrCmd, args...) | ||||
|   | ||||
| @@ -60,7 +60,7 @@ func (d *DNF) Sync(opts *Opts) error { | ||||
|  | ||||
| func (d *DNF) Install(opts *Opts, pkgs ...string) error { | ||||
| 	opts = ensureOpts(opts) | ||||
| 	cmd := d.getCmd(opts, "dnf", "install") | ||||
| 	cmd := d.getCmd(opts, "dnf", "install", "--allowerasing") | ||||
| 	cmd.Args = append(cmd.Args, pkgs...) | ||||
| 	setCmdEnv(cmd) | ||||
| 	err := cmd.Run() | ||||
| @@ -153,6 +153,7 @@ func (d *DNF) getCmd(opts *Opts, mgrCmd string, args ...string) *exec.Cmd { | ||||
| 	var cmd *exec.Cmd | ||||
| 	if opts.AsRoot { | ||||
| 		cmd = exec.Command(getRootCmd(d.rootCmd), mgrCmd) | ||||
| 		cmd.Args = append(cmd.Args, opts.Args...) | ||||
| 		cmd.Args = append(cmd.Args, args...) | ||||
| 	} else { | ||||
| 		cmd = exec.Command(mgrCmd, args...) | ||||
|   | ||||
| @@ -23,9 +23,12 @@ import ( | ||||
| 	"os/exec" | ||||
| ) | ||||
|  | ||||
| var Args []string | ||||
|  | ||||
| type Opts struct { | ||||
| 	AsRoot    bool | ||||
| 	NoConfirm bool | ||||
| 	Args      []string | ||||
| } | ||||
|  | ||||
| var DefaultOpts = &Opts{ | ||||
| @@ -114,7 +117,8 @@ func setCmdEnv(cmd *exec.Cmd) { | ||||
|  | ||||
| func ensureOpts(opts *Opts) *Opts { | ||||
| 	if opts == nil { | ||||
| 		return DefaultOpts | ||||
| 		opts = DefaultOpts | ||||
| 	} | ||||
| 	opts.Args = append(opts.Args, Args...) | ||||
| 	return opts | ||||
| } | ||||
|   | ||||
| @@ -152,6 +152,7 @@ func (p *Pacman) getCmd(opts *Opts, mgrCmd string, args ...string) *exec.Cmd { | ||||
| 	var cmd *exec.Cmd | ||||
| 	if opts.AsRoot { | ||||
| 		cmd = exec.Command(getRootCmd(p.rootCmd), mgrCmd) | ||||
| 		cmd.Args = append(cmd.Args, opts.Args...) | ||||
| 		cmd.Args = append(cmd.Args, args...) | ||||
| 	} else { | ||||
| 		cmd = exec.Command(mgrCmd, args...) | ||||
|   | ||||
| @@ -60,7 +60,7 @@ func (y *YUM) Sync(opts *Opts) error { | ||||
|  | ||||
| func (y *YUM) Install(opts *Opts, pkgs ...string) error { | ||||
| 	opts = ensureOpts(opts) | ||||
| 	cmd := y.getCmd(opts, "yum", "install") | ||||
| 	cmd := y.getCmd(opts, "yum", "install", "--allowerasing") | ||||
| 	cmd.Args = append(cmd.Args, pkgs...) | ||||
| 	setCmdEnv(cmd) | ||||
| 	err := cmd.Run() | ||||
| @@ -153,6 +153,7 @@ func (y *YUM) getCmd(opts *Opts, mgrCmd string, args ...string) *exec.Cmd { | ||||
| 	var cmd *exec.Cmd | ||||
| 	if opts.AsRoot { | ||||
| 		cmd = exec.Command(getRootCmd(y.rootCmd), mgrCmd) | ||||
| 		cmd.Args = append(cmd.Args, opts.Args...) | ||||
| 		cmd.Args = append(cmd.Args, args...) | ||||
| 	} else { | ||||
| 		cmd = exec.Command(mgrCmd, args...) | ||||
|   | ||||
| @@ -153,6 +153,7 @@ func (z *Zypper) getCmd(opts *Opts, mgrCmd string, args ...string) *exec.Cmd { | ||||
| 	var cmd *exec.Cmd | ||||
| 	if opts.AsRoot { | ||||
| 		cmd = exec.Command(getRootCmd(z.rootCmd), mgrCmd) | ||||
| 		cmd.Args = append(cmd.Args, opts.Args...) | ||||
| 		cmd.Args = append(cmd.Args, args...) | ||||
| 	} else { | ||||
| 		cmd = exec.Command(mgrCmd, args...) | ||||
|   | ||||
							
								
								
									
										231
									
								
								repo.go
									
									
									
									
									
								
							
							
						
						
									
										231
									
								
								repo.go
									
									
									
									
									
								
							| @@ -19,59 +19,49 @@ | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"net/url" | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/AlecAivazis/survey/v2" | ||||
| 	"github.com/go-git/go-git/v5" | ||||
| 	"github.com/pelletier/go-toml/v2" | ||||
| 	"github.com/urfave/cli/v2" | ||||
| 	"go.arsenm.dev/lure/download" | ||||
| 	"go.arsenm.dev/logger/log" | ||||
| 	"go.arsenm.dev/lure/internal/config" | ||||
| 	"go.arsenm.dev/lure/internal/db" | ||||
| 	"go.arsenm.dev/lure/internal/repos" | ||||
| 	"go.arsenm.dev/lure/internal/types" | ||||
| 	"golang.org/x/exp/slices" | ||||
| ) | ||||
|  | ||||
| type PkgNotFoundError struct { | ||||
| 	pkgName string | ||||
| } | ||||
|  | ||||
| func (p PkgNotFoundError) Error() string { | ||||
| 	return "package '" + p.pkgName + "' could not be found in any repository" | ||||
| } | ||||
|  | ||||
| type RepoConfig struct { | ||||
| 	Repo struct { | ||||
| 		MinVersion string `toml:"minVersion"` | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func addrepoCmd(c *cli.Context) error { | ||||
| 	name := c.String("name") | ||||
| 	repoURL := c.String("url") | ||||
|  | ||||
| 	for _, repo := range config.Repos { | ||||
| 	for _, repo := range cfg.Repos { | ||||
| 		if repo.URL == repoURL { | ||||
| 			log.Fatal("Repo already exists").Str("name", repo.Name).Send() | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	config.Repos = append(config.Repos, Repo{ | ||||
| 	cfg.Repos = append(cfg.Repos, types.Repo{ | ||||
| 		Name: name, | ||||
| 		URL:  repoURL, | ||||
| 	}) | ||||
|  | ||||
| 	cfgFl, err := os.Create(cfgPath) | ||||
| 	cfgFl, err := os.Create(config.ConfigPath) | ||||
| 	if err != nil { | ||||
| 		log.Fatal("Error opening config file").Err(err).Send() | ||||
| 	} | ||||
|  | ||||
| 	err = toml.NewEncoder(cfgFl).Encode(&config) | ||||
| 	err = toml.NewEncoder(cfgFl).Encode(&cfg) | ||||
| 	if err != nil { | ||||
| 		log.Fatal("Error encoding config").Err(err).Send() | ||||
| 	} | ||||
|  | ||||
| 	err = repos.Pull(c.Context, gdb, cfg.Repos) | ||||
| 	if err != nil { | ||||
| 		log.Fatal("Error pulling repos").Err(err).Send() | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| @@ -80,7 +70,7 @@ func removerepoCmd(c *cli.Context) error { | ||||
|  | ||||
| 	found := false | ||||
| 	index := 0 | ||||
| 	for i, repo := range config.Repos { | ||||
| 	for i, repo := range cfg.Repos { | ||||
| 		if repo.Name == name { | ||||
| 			index = i | ||||
| 			found = true | ||||
| @@ -90,206 +80,35 @@ func removerepoCmd(c *cli.Context) error { | ||||
| 		log.Fatal("Repo does not exist").Str("name", name).Send() | ||||
| 	} | ||||
|  | ||||
| 	config.Repos = slices.Delete(config.Repos, index, index+1) | ||||
| 	cfg.Repos = slices.Delete(cfg.Repos, index, index+1) | ||||
|  | ||||
| 	cfgFl, err := os.Create(cfgPath) | ||||
| 	cfgFl, err := os.Create(config.ConfigPath) | ||||
| 	if err != nil { | ||||
| 		log.Fatal("Error opening config file").Err(err).Send() | ||||
| 	} | ||||
|  | ||||
| 	err = toml.NewEncoder(cfgFl).Encode(&config) | ||||
| 	err = toml.NewEncoder(cfgFl).Encode(&cfg) | ||||
| 	if err != nil { | ||||
| 		log.Fatal("Error encoding config").Err(err).Send() | ||||
| 	} | ||||
|  | ||||
| 	err = os.RemoveAll(filepath.Join(cacheDir, "repo", name)) | ||||
| 	err = os.RemoveAll(filepath.Join(config.RepoDir, name)) | ||||
| 	if err != nil { | ||||
| 		log.Fatal("Error removing repo directory").Err(err).Send() | ||||
| 	} | ||||
|  | ||||
| 	err = db.DeletePkgs(gdb, "repository = ?", name) | ||||
| 	if err != nil { | ||||
| 		log.Fatal("Error removing packages from database").Err(err).Send() | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func refreshCmd(c *cli.Context) error { | ||||
| 	err := pullRepos(c.Context) | ||||
| 	err := repos.Pull(c.Context, gdb, cfg.Repos) | ||||
| 	if err != nil { | ||||
| 		log.Fatal("Error pulling repos").Err(err).Send() | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func findPkg(pkg string) ([]string, error) { | ||||
| 	baseRepoDir := filepath.Join(cacheDir, "repo") | ||||
|  | ||||
| 	var out []string | ||||
| 	for _, repo := range config.Repos { | ||||
| 		repoDir := filepath.Join(baseRepoDir, repo.Name) | ||||
| 		err := os.MkdirAll(repoDir, 0o755) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
|  | ||||
| 		glob := filepath.Join(repoDir, pkg, "lure.sh") | ||||
| 		matches, err := filepath.Glob(glob) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
|  | ||||
| 		if len(matches) == 0 { | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		out = append(out, matches...) | ||||
| 	} | ||||
|  | ||||
| 	if len(out) == 0 { | ||||
| 		return nil, PkgNotFoundError{pkgName: pkg} | ||||
| 	} | ||||
|  | ||||
| 	return out, nil | ||||
| } | ||||
|  | ||||
| func pkgPrompt(options []string) ([]string, error) { | ||||
| 	names := make([]string, len(options)) | ||||
| 	for i, option := range options { | ||||
| 		pkgDir := filepath.Dir(option) | ||||
| 		repoDir := filepath.Dir(pkgDir) | ||||
| 		names[i] = filepath.Base(repoDir) + "/" + filepath.Base(pkgDir) | ||||
| 	} | ||||
|  | ||||
| 	prompt := &survey.MultiSelect{ | ||||
| 		Options: names, | ||||
| 		Message: "Choose which package(s) to install", | ||||
| 	} | ||||
|  | ||||
| 	var choices []int | ||||
| 	err := survey.AskOne(prompt, &choices) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	out := make([]string, len(choices)) | ||||
| 	for i, choiceIndex := range choices { | ||||
| 		out[i] = options[choiceIndex] | ||||
| 	} | ||||
|  | ||||
| 	return out, nil | ||||
| } | ||||
|  | ||||
| func findPkgs(pkgs []string) (scripts, notFound []string) { | ||||
| 	for _, pkg := range pkgs { | ||||
| 		found, err := findPkg(pkg) | ||||
| 		if _, ok := err.(PkgNotFoundError); ok { | ||||
| 			notFound = append(notFound, pkg) | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		if len(found) == 1 { | ||||
| 			scripts = append(scripts, found...) | ||||
| 		} else { | ||||
| 			choices, err := pkgPrompt(found) | ||||
| 			if err != nil { | ||||
| 				log.Fatal("Error prompting for package choices").Err(err).Send() | ||||
| 			} | ||||
|  | ||||
| 			scripts = append(scripts, choices...) | ||||
| 		} | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func pullRepos(ctx context.Context) error { | ||||
| 	baseRepoDir := filepath.Join(cacheDir, "repo") | ||||
|  | ||||
| 	for _, repo := range config.Repos { | ||||
| 		repoURL, err := url.Parse(repo.URL) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
|  | ||||
| 		log.Info("Pulling repository").Str("name", repo.Name).Send() | ||||
| 		repoDir := filepath.Join(baseRepoDir, repo.Name) | ||||
|  | ||||
| 		gitDir := filepath.Join(repoDir, ".git") | ||||
| 		if fi, err := os.Stat(gitDir); err == nil && fi.IsDir() { | ||||
| 			r, err := git.PlainOpen(repoDir) | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
|  | ||||
| 			w, err := r.Worktree() | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
|  | ||||
| 			err = w.PullContext(ctx, &git.PullOptions{Progress: os.Stderr}) | ||||
| 			if err == git.NoErrAlreadyUpToDate { | ||||
| 				log.Info("Repository up to date").Str("name", repo.Name).Send() | ||||
| 				continue | ||||
| 			} else if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
|  | ||||
| 			fl, err := w.Filesystem.Open("lure-repo.toml") | ||||
| 			if err != nil { | ||||
| 				log.Warn("Git repository does not appear to be a valid LURE repo").Str("repo", repo.Name).Send() | ||||
| 				continue | ||||
| 			} | ||||
|  | ||||
| 			var repoCfg RepoConfig | ||||
| 			err = toml.NewDecoder(fl).Decode(&repoCfg) | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 			fl.Close() | ||||
|  | ||||
| 			currentVer, _, _ := strings.Cut(version, "-") | ||||
| 			if vercmp(currentVer, repoCfg.Repo.MinVersion) == -1 { | ||||
| 				log.Warn("LURE repo's minumum LURE version is greater than the current version. Try updating LURE if something doesn't work.").Str("repo", repo.Name).Send() | ||||
| 			} | ||||
|  | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		err = os.RemoveAll(repoDir) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
|  | ||||
| 		err = os.MkdirAll(repoDir, 0o755) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
|  | ||||
| 		if !strings.HasPrefix(repoURL.Scheme, "git+") { | ||||
| 			repoURL.Scheme = "git+" + repoURL.Scheme | ||||
| 		} | ||||
|  | ||||
| 		err = download.Get(ctx, download.GetOptions{ | ||||
| 			SourceURL:   repoURL.String(), | ||||
| 			Destination: repoDir, | ||||
| 		}) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
|  | ||||
| 		fl, err := os.Open(filepath.Join(repoDir, "lure-repo.toml")) | ||||
| 		if err != nil { | ||||
| 			log.Warn("Git repository does not appear to be a valid LURE repo").Str("repo", repo.Name).Send() | ||||
| 		} | ||||
|  | ||||
| 		var repoCfg RepoConfig | ||||
| 		err = toml.NewDecoder(fl).Decode(&repoCfg) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		fl.Close() | ||||
|  | ||||
| 		currentVer, _, _ := strings.Cut(version, "-") | ||||
| 		if vercmp(currentVer, repoCfg.Repo.MinVersion) == -1 { | ||||
| 			log.Warn("LURE repo's minumum LURE version is greater than the current version. Try updating LURE if something doesn't work.").Str("repo", repo.Name).Send() | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|   | ||||
							
								
								
									
										21
									
								
								scripts/completion/bash
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								scripts/completion/bash
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | ||||
| #! /bin/bash | ||||
|  | ||||
| : ${PROG:=$(basename ${BASH_SOURCE})} | ||||
|  | ||||
| _cli_bash_autocomplete() { | ||||
|   if [[ "${COMP_WORDS[0]}" != "source" ]]; then | ||||
|     local cur opts base | ||||
|     COMPREPLY=() | ||||
|     cur="${COMP_WORDS[COMP_CWORD]}" | ||||
|     if [[ "$cur" == "-"* ]]; then | ||||
|       opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} ${cur} --generate-bash-completion ) | ||||
|     else | ||||
|       opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} --generate-bash-completion ) | ||||
|     fi | ||||
|     COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) | ||||
|     return 0 | ||||
|   fi | ||||
| } | ||||
|  | ||||
| complete -o bashdefault -o default -o nospace -F _cli_bash_autocomplete $PROG | ||||
| unset PROG | ||||
							
								
								
									
										20
									
								
								scripts/completion/zsh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								scripts/completion/zsh
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | ||||
| #compdef lure | ||||
|  | ||||
| _cli_zsh_autocomplete() { | ||||
|   local -a opts | ||||
|   local cur | ||||
|   cur=${words[-1]} | ||||
|   if [[ "$cur" == "-"* ]]; then | ||||
|     opts=("${(@f)$(${words[@]:0:#words[@]-1} ${cur} --generate-bash-completion)}") | ||||
|   else | ||||
|     opts=("${(@f)$(${words[@]:0:#words[@]-1} --generate-bash-completion)}") | ||||
|   fi | ||||
|  | ||||
|   if [[ "${opts[1]}" != "" ]]; then | ||||
|     _describe 'values' opts | ||||
|   else | ||||
|     _files | ||||
|   fi | ||||
| } | ||||
|  | ||||
| compdef _cli_zsh_autocomplete lure | ||||
| @@ -1,3 +1,3 @@ | ||||
| #!/bin/bash | ||||
|  | ||||
| git describe --tags > version.txt | ||||
| git describe --tags > internal/config/version.txt | ||||
							
								
								
									
										82
									
								
								scripts/install.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								scripts/install.sh
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,82 @@ | ||||
| #!/bin/bash | ||||
|  | ||||
| info() { | ||||
|   echo $'\x1b[32m[INFO]\x1b[0m' $@ | ||||
| } | ||||
|  | ||||
| warn() { | ||||
|   echo $'\x1b[31m[WARN]\x1b[0m' $@ | ||||
| } | ||||
|  | ||||
| error() { | ||||
|   echo $'\x1b[31;1m[ERR]\x1b[0m' $@ | ||||
|   exit 1 | ||||
| } | ||||
|  | ||||
| installPkg() { | ||||
|   rootCmd="" | ||||
|   if command -v doas &>/dev/null; then | ||||
|     rootCmd="doas" | ||||
|   elif command -v sudo &>/dev/null; then | ||||
|     rootCmd="sudo" | ||||
|   else | ||||
|     warn "No privilege elevation command (e.g. sudo, doas) detected" | ||||
|   fi | ||||
|    | ||||
|   case $1 in | ||||
|   pacman) $rootCmd pacman --noconfirm -U ${@:2} ;; | ||||
|   apk) $rootCmd apk add --allow-untrusted ${@:2} ;; | ||||
|   *) $rootCmd $1 install -y ${@:2} ;; | ||||
|   esac | ||||
| } | ||||
|  | ||||
| if ! command -v curl &>/dev/null; then | ||||
|   error "This script requires the curl command. Please install it and run again." | ||||
| fi | ||||
|  | ||||
| pkgFormat="" | ||||
| pkgMgr="" | ||||
| if command -v pacman &>/dev/null; then | ||||
|   info "Detected pacman" | ||||
|   pkgFormat="pkg.tar.zst" | ||||
|   pkgMgr="pacman" | ||||
| elif command -v apt &>/dev/null; then | ||||
|   info "Detected apt" | ||||
|   pkgFormat="deb" | ||||
|   pkgMgr="apt" | ||||
| elif command -v dnf &>/dev/null; then | ||||
|   info "Detected dnf" | ||||
|   pkgFormat="rpm" | ||||
|   pkgMgr="dnf" | ||||
| elif command -v yum &>/dev/null; then | ||||
|   info "Detected yum" | ||||
|   pkgFormat="rpm" | ||||
|   pkgMgr="yum" | ||||
| elif command -v zypper &>/dev/null; then | ||||
|   info "Detected zypper" | ||||
|   pkgFormat="rpm" | ||||
|   pkgMgr="zypper" | ||||
| elif command -v apk &>/dev/null; then | ||||
|   info "Detected apk" | ||||
|   pkgFormat="apk" | ||||
|   pkgMgr="apk" | ||||
| else | ||||
|   error "No supported package manager detected!" | ||||
| fi | ||||
|  | ||||
| latestVersion=$(curl -sI 'https://gitea.arsenm.dev/Arsen6331/lure/releases/latest' | grep -o 'location: .*' | rev | cut -d '/' -f1 | rev | tr -d '[:space:]') | ||||
| info "Found latest LURE version:" $latestVersion | ||||
|  | ||||
| fname="$(mktemp -u -p /tmp "lure.XXXXXXXXXX").${pkgFormat}" | ||||
| url="https://gitea.arsenm.dev/Arsen6331/lure/releases/download/${latestVersion}/linux-user-repository-${latestVersion#v}-linux-$(uname -m).${pkgFormat}" | ||||
|  | ||||
| info "Downloading LURE package"  | ||||
| curl -L $url -o $fname | ||||
|  | ||||
| info "Installing LURE package" | ||||
| installPkg $pkgMgr $fname | ||||
|  | ||||
| info "Cleaning up" | ||||
| rm $fname | ||||
|  | ||||
| info "Done!" | ||||
							
								
								
									
										52
									
								
								upgrade.go
									
									
									
									
									
								
							
							
						
						
									
										52
									
								
								upgrade.go
									
									
									
									
									
								
							| @@ -23,8 +23,14 @@ import ( | ||||
| 	"fmt" | ||||
|  | ||||
| 	"github.com/urfave/cli/v2" | ||||
| 	"go.arsenm.dev/logger/log" | ||||
| 	"go.arsenm.dev/lure/distro" | ||||
| 	"go.arsenm.dev/lure/internal/db" | ||||
| 	"go.arsenm.dev/lure/internal/repos" | ||||
| 	"go.arsenm.dev/lure/manager" | ||||
| 	"go.arsenm.dev/lure/vercmp" | ||||
| 	"golang.org/x/exp/maps" | ||||
| 	"golang.org/x/exp/slices" | ||||
| ) | ||||
|  | ||||
| func upgradeCmd(c *cli.Context) error { | ||||
| @@ -38,7 +44,7 @@ func upgradeCmd(c *cli.Context) error { | ||||
| 		log.Fatal("Unable to detect supported package manager on system").Send() | ||||
| 	} | ||||
|  | ||||
| 	err = pullRepos(c.Context) | ||||
| 	err = repos.Pull(c.Context, gdb, cfg.Repos) | ||||
| 	if err != nil { | ||||
| 		log.Fatal("Error pulling repos").Err(err).Send() | ||||
| 	} | ||||
| @@ -49,7 +55,7 @@ func upgradeCmd(c *cli.Context) error { | ||||
| 	} | ||||
|  | ||||
| 	if len(updates) > 0 { | ||||
| 		installPkgs(c.Context, updates, mgr, false) | ||||
| 		installPkgs(c.Context, updates, nil, mgr) | ||||
| 	} else { | ||||
| 		log.Info("There is nothing to do.").Send() | ||||
| 	} | ||||
| @@ -57,41 +63,47 @@ func upgradeCmd(c *cli.Context) error { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func checkForUpdates(ctx context.Context, mgr manager.Manager, info *distro.OSRelease) ([]string, error) { | ||||
| func checkForUpdates(ctx context.Context, mgr manager.Manager, info *distro.OSRelease) ([]db.Package, error) { | ||||
| 	installed, err := mgr.ListInstalled(nil) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	var out []string | ||||
| 	for name, version := range installed { | ||||
| 		scripts, err := findPkg(name) | ||||
| 	pkgNames := maps.Keys(installed) | ||||
| 	found, _, err := repos.FindPkgs(gdb, pkgNames) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	var out []db.Package | ||||
| 	for pkgName, pkgs := range found { | ||||
| 		if slices.Contains(cfg.IgnorePkgUpdates, pkgName) { | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		// since we're not using a glob, we can assume a single item | ||||
| 		script := scripts[0] | ||||
|  | ||||
| 		vars, err := getBuildVars(ctx, script, info) | ||||
| 		if err != nil { | ||||
| 			log.Fatal("Error getting build variables").Err(err).Send() | ||||
| 		if len(pkgs) > 1 { | ||||
| 			// Puts the element with the highest version first | ||||
| 			slices.SortFunc(pkgs, func(a, b db.Package) bool { | ||||
| 				return vercmp.Compare(a.Version, b.Version) == 1 | ||||
| 			}) | ||||
| 		} | ||||
|  | ||||
| 		repoVer := vars.Version | ||||
| 		if vars.Release != 0 && vars.Epoch == 0 { | ||||
| 			repoVer = fmt.Sprintf("%s-%d", vars.Version, vars.Release) | ||||
| 		} else if vars.Release != 0 && vars.Epoch != 0 { | ||||
| 			repoVer = fmt.Sprintf("%d:%s-%d", vars.Epoch, vars.Version, vars.Release) | ||||
| 		// First element is the package we want to install | ||||
| 		pkg := pkgs[0] | ||||
|  | ||||
| 		repoVer := pkg.Version | ||||
| 		if pkg.Release != 0 && pkg.Epoch == 0 { | ||||
| 			repoVer = fmt.Sprintf("%s-%d", pkg.Version, pkg.Release) | ||||
| 		} else if pkg.Release != 0 && pkg.Epoch != 0 { | ||||
| 			repoVer = fmt.Sprintf("%d:%s-%d", pkg.Epoch, pkg.Version, pkg.Release) | ||||
| 		} | ||||
|  | ||||
| 		c := vercmp(repoVer, version) | ||||
| 		c := vercmp.Compare(repoVer, installed[pkgName]) | ||||
| 		if c == 0 || c == -1 { | ||||
| 			continue | ||||
| 		} else if c == 1 { | ||||
| 			out = append(out, name) | ||||
| 			out = append(out, pkg) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return out, nil | ||||
| } | ||||
|   | ||||
| @@ -16,7 +16,7 @@ | ||||
|  * along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
|  */ | ||||
| 
 | ||||
| package main | ||||
| package vercmp | ||||
| 
 | ||||
| import ( | ||||
| 	_ "embed" | ||||
| @@ -26,16 +26,11 @@ import ( | ||||
| 	"golang.org/x/exp/slices" | ||||
| ) | ||||
| 
 | ||||
| //go:generate scripts/gen-version.sh | ||||
| 
 | ||||
| //go:embed version.txt | ||||
| var version string | ||||
| 
 | ||||
| // vercmp compares two version strings. | ||||
| // Compare compares two version strings. | ||||
| // It returns 1 if v1 is greater, | ||||
| // 0 if the versions are equal, | ||||
| // and -1 if v2 is greater | ||||
| func vercmp(v1, v2 string) int { | ||||
| func Compare(v1, v2 string) int { | ||||
| 	if v1 == v2 { | ||||
| 		return 0 | ||||
| 	} | ||||
| @@ -16,7 +16,7 @@ | ||||
|  * along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
|  */ | ||||
| 
 | ||||
| package main | ||||
| package vercmp | ||||
| 
 | ||||
| import ( | ||||
| 	"testing" | ||||
| @@ -67,13 +67,13 @@ func TestVerCmp(t *testing.T) { | ||||
| 
 | ||||
| 	for _, it := range table { | ||||
| 		t.Run(it.v1+"/"+it.v2, func(t *testing.T) { | ||||
| 			c := vercmp(it.v1, it.v2) | ||||
| 			c := Compare(it.v1, it.v2) | ||||
| 			if c != it.expected { | ||||
| 				t.Errorf("Expected %d, got %d", it.expected, c) | ||||
| 			} | ||||
| 
 | ||||
| 			// Ensure opposite comparison gives opposite value | ||||
| 			c = -vercmp(it.v2, it.v1) | ||||
| 			c = -Compare(it.v2, it.v1) | ||||
| 			if c != it.expected { | ||||
| 				t.Errorf("Expected %d, got %d (opposite)", it.expected, c) | ||||
| 			} | ||||
		Reference in New Issue
	
	Block a user