Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package arkade for openSUSE:Factory checked 
in at 2026-02-10 21:13:32
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/arkade (Old)
 and      /work/SRC/openSUSE:Factory/.arkade.new.1670 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "arkade"

Tue Feb 10 21:13:32 2026 rev:74 rq:1332248 version:0.11.75

Changes:
--------
--- /work/SRC/openSUSE:Factory/arkade/arkade.changes    2026-02-09 
13:43:33.148395497 +0100
+++ /work/SRC/openSUSE:Factory/.arkade.new.1670/arkade.changes  2026-02-10 
21:14:39.001163207 +0100
@@ -1,0 +2,17 @@
+Tue Feb 10 12:45:19 UTC 2026 - Johannes Kastl 
<[email protected]>
+
+- Update to version 0.11.75:
+  * Fixes for assuming gh has --json output
+  * Update SLICER_BOX.md
+
+-------------------------------------------------------------------
+Tue Feb 10 05:52:08 UTC 2026 - Johannes Kastl 
<[email protected]>
+
+- Update to version 0.11.74:
+  * Initial gh release hidden command
+  * Guard verification output for progress bar/alt screen
+- Update to version 0.11.73:
+  * Use an alt screen to protect scroll buffer
+  * Print summary in non-interactive mode
+
+-------------------------------------------------------------------

Old:
----
  arkade-0.11.72.obscpio

New:
----
  arkade-0.11.75.obscpio

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ arkade.spec ++++++
--- /var/tmp/diff_new_pack.g0gryt/_old  2026-02-10 21:14:39.881200082 +0100
+++ /var/tmp/diff_new_pack.g0gryt/_new  2026-02-10 21:14:39.885200249 +0100
@@ -17,7 +17,7 @@
 
 
 Name:           arkade
-Version:        0.11.72
+Version:        0.11.75
 Release:        0
 Summary:        Open Source Kubernetes Marketplace
 License:        Apache-2.0

++++++ _service ++++++
--- /var/tmp/diff_new_pack.g0gryt/_old  2026-02-10 21:14:39.921201758 +0100
+++ /var/tmp/diff_new_pack.g0gryt/_new  2026-02-10 21:14:39.925201926 +0100
@@ -3,7 +3,7 @@
     <param name="url">https://github.com/alexellis/arkade</param>
     <param name="scm">git</param>
     <param name="exclude">.git</param>
-    <param name="revision">0.11.72</param>
+    <param name="revision">0.11.75</param>
     <param name="versionformat">@PARENT_TAG@</param>
     <param name="versionrewrite-pattern">v(.*)</param>
     <param name="changesgenerate">enable</param>

++++++ _servicedata ++++++
--- /var/tmp/diff_new_pack.g0gryt/_old  2026-02-10 21:14:39.945202764 +0100
+++ /var/tmp/diff_new_pack.g0gryt/_new  2026-02-10 21:14:39.949202931 +0100
@@ -1,6 +1,6 @@
 <servicedata>
 <service name="tar_scm">
                 <param name="url">https://github.com/alexellis/arkade</param>
-              <param 
name="changesrevision">0e580bd296b08c3ba66cd0cc51ce6a614f31321c</param></service></servicedata>
+              <param 
name="changesrevision">8e860ea5a87682d22908c2d4355c50f089b55c0a</param></service></servicedata>
 (No newline at EOF)
 

++++++ arkade-0.11.72.obscpio -> arkade-0.11.75.obscpio ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/arkade-0.11.72/Makefile new/arkade-0.11.75/Makefile
--- old/arkade-0.11.72/Makefile 2026-02-07 10:29:49.000000000 +0100
+++ new/arkade-0.11.75/Makefile 2026-02-10 11:35:47.000000000 +0100
@@ -24,6 +24,11 @@
 e2e:
        CGO_ENABLED=0 go test github.com/alexellis/arkade/pkg/get -cover --tags 
e2e -v
 
+.PHONY: dist-local
+dist-local:
+       mkdir -p bin
+       CGO_ENABLED=0 go build -ldflags $(LDFLAGS) -o bin/arkade
+
 .PHONY: dist
 dist:
        mkdir -p bin
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/arkade-0.11.72/SLICER_BOX.md 
new/arkade-0.11.75/SLICER_BOX.md
--- old/arkade-0.11.72/SLICER_BOX.md    2026-02-07 10:29:49.000000000 +0100
+++ new/arkade-0.11.75/SLICER_BOX.md    2026-02-10 11:35:47.000000000 +0100
@@ -2,6 +2,8 @@
 
 Linux sandbox for AI agents.
 
+Instructions from https://box.slicervm.com/
+
 ## Pre-reqs
 
 Must point slicer to the box service and token file:
@@ -65,8 +67,14 @@
 
 Only do this in case of a major issue or when having to test system-level 
changes.
 
-# Ensure SLICER_URL and SLICER_TOKEN_FILE are set
+Ensure SLICER_URL and SLICER_TOKEN_FILE are set
+
+```
 slicer vm delete VM_NAME
+```
+
 Then launch a new VM:
 
+```
 slicer vm launch
+```
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/arkade-0.11.72/cmd/AGENTS.md 
new/arkade-0.11.75/cmd/AGENTS.md
--- old/arkade-0.11.72/cmd/AGENTS.md    1970-01-01 01:00:00.000000000 +0100
+++ new/arkade-0.11.75/cmd/AGENTS.md    2026-02-10 11:35:47.000000000 +0100
@@ -0,0 +1,146 @@
+# AGENTS.md - Guide for AI Agents working on `arkade release`
+
+This document covers the hidden `release` command (`cmd/release.go`), which 
creates GitHub releases from any repo using `gh`.
+
+## Overview
+
+The command is registered in `main.go` as a hidden top-level command with 
aliases `rel` and `r`. It is not shown in `arkade --help` output.
+
+**Source file:** `cmd/release.go`
+**Registration:** `main.go` via `rootCmd.AddCommand(cmd.MakeRelease())`
+
+## What the command does
+
+1. Detects repo visibility (public/private) via `gh api repos/{owner}/{repo}`
+2. Queries the latest release tag via `gh api repos/{owner}/{repo}/releases`
+3. Parses the tag as semver and increments the patch version
+4. Gets the latest commit subject line via `git log` for the release title
+5. Creates the release via `gh release create`
+
+No release notes are generated. A separate webhook bot handles release note 
generation.
+
+## Public vs Private repo rules
+
+This is the most important design decision in the command.
+
+| | Public repo | Private / Internal repo |
+|---|---|---|
+| `--prerelease` | `true` (default) | `false` (default) |
+| `--latest` | `false` (default) | `true` (default) |
+
+**Why:** Public repos use a bot that promotes pre-releases to latest after 
verification. Private repos have no such bot, so releases are immediately 
marked as latest and stable.
+
+Visibility is auto-detected via `gh api repos/{owner}/{repo}`. The result is 
compared against `PRIVATE` and `INTERNAL` (both treated as private). Everything 
else (including `PUBLIC`) uses public-repo defaults.
+
+Both flags can be overridden explicitly:
+```bash
+arkade release --prerelease=false --latest=true
+```
+
+**When modifying this logic, never change the defaults without understanding 
the bot promotion workflow for public repos.**
+
+## Semver handling
+
+- Both `v`-prefixed (`v0.1.0`) and bare (`0.1.0`) tags are supported
+- The prefix style from the previous release is preserved in the new tag
+- The bump strategy is controlled by `--major`, `--minor`, `--patch` flags 
(mutually exclusive)
+- Default is `--patch` when none are specified
+- When a version is given as a positional argument, the bump flags are ignored
+- The `github.com/Masterminds/semver` library is used (already vendored, same 
as `cmd/chart/bump.go`)
+
+### Bump strategies
+
+| Flag | Example |
+|---|---|
+| `--patch` (default) | `v1.2.3` -> `v1.2.4` |
+| `--minor` | `v1.2.3` -> `v1.3.0` |
+| `--major` | `v1.2.3` -> `v2.0.0` |
+
+The `bumpVersion()` function handles all three via the `bumpStrategy` type. 
The `parseBumpStrategy()` function validates mutual exclusion of the flags.
+
+## Positional arguments
+
+The command accepts 0, 1, or 2 positional arguments:
+
+| Arguments | Behaviour |
+|---|---|
+| None | Version auto-detected, title from latest commit |
+| One, looks like semver | Used as version override, title from latest commit |
+| One, not semver | Used as title override, version auto-detected |
+| Two | First is version, second is title |
+
+The `looksLikeSemver()` heuristic checks: optional `v`/`V` prefix, starts with 
a digit, contains a dot.
+
+## External dependencies
+
+The command shells out to two tools:
+
+- **`gh`** (GitHub CLI) - for release listing, repo visibility, and release 
creation. Must be authenticated (`gh auth login`). Supports private repos.
+- **`git`** - for reading the latest commit message.
+
+Both are called via `github.com/alexellis/go-execute/v2`, not `os/exec` 
directly. This is consistent with the rest of the arkade codebase.
+
+## Flags
+
+| Flag | Type | Default | Description |
+|---|---|---|---|
+| `--dry-run` | bool | `false` | Print what would be created, don't call `gh 
release create` |
+| `--major` | bool | `false` | Bump the major version |
+| `--minor` | bool | `false` | Bump the minor version |
+| `--patch` | bool | `false` | Bump the patch version (this is the default 
when none specified) |
+| `--prerelease` | string | `""` (auto) | `"true"` or `"false"`. Auto-detected 
from repo visibility when empty |
+| `--latest` | string | `""` (auto) | `"true"` or `"false"`. Auto-detected 
from repo visibility when empty |
+| `--token` | string | `""` | GitHub token passed via `GH_TOKEN` env var to 
`gh` |
+
+Note: `--prerelease` and `--latest` are string flags (not bool) so that the 
empty string means "not set by user, use auto-detected default". This is 
intentional.
+
+Note: `--major`, `--minor`, `--patch` are mutually exclusive. If more than one 
is set, the command returns an error.
+
+## Testing
+
+There are no unit tests for this command. To verify behaviour:
+
+```bash
+# Build
+go build -o arkade .
+
+# Dry run in a public repo (default = patch bump)
+./arkade release --dry-run
+# Expected: Prerelease: true, Latest: false, Bump: patch
+
+# Minor bump
+./arkade release --dry-run --minor
+
+# Major bump
+./arkade release --dry-run --major
+
+# Mutual exclusion error
+./arkade release --dry-run --major --minor
+# Expected: error
+
+# Dry run with overrides
+./arkade release --dry-run --prerelease=false --latest=true
+
+# Test argument parsing
+./arkade rel --dry-run v1.0.0
+./arkade r --dry-run "Some title"
+./arkade release --dry-run 1.0.0 "Some title"
+
+# Verify aliases
+./arkade rel --help
+./arkade r --help
+```
+
+## Modifying the command
+
+### Adding new flags
+Add them in `MakeRelease()` before the `command.RunE` assignment. Use the same 
pattern as existing flags.
+
+### Changing visibility detection
+The `repoIsPrivate()` function is self-contained. It returns `true` for 
`PRIVATE` and `INTERNAL` visibility values from `gh`.
+
+### Changing the version bump strategy
+The `bumpVersion()` function handles version incrementing using the 
`bumpStrategy` type. It supports `bumpPatchStrategy`, `bumpMinorStrategy`, and 
`bumpMajorStrategy`. The user selects the strategy via `--major`, `--minor`, 
`--patch` flags, validated by `parseBumpStrategy()`.
+
+### Error handling for repos with no releases
+When `gh release list` returns no tags, `latestReleaseTag()` returns an error. 
The error message tells the user to pass a version explicitly as a positional 
argument.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/arkade-0.11.72/cmd/get.go 
new/arkade-0.11.75/cmd/get.go
--- old/arkade-0.11.72/cmd/get.go       2026-02-07 10:29:49.000000000 +0100
+++ new/arkade-0.11.75/cmd/get.go       2026-02-10 11:35:47.000000000 +0100
@@ -242,18 +242,26 @@
                        out = os.Stderr
                }
 
-               // Hide cursor during live rendering.
+               // Use alternate screen buffer for TTY progress so that
+               // intermediate frames don't pollute scrollback. The final
+               // completed state is printed as static text after leaving
+               // the alternate screen.
                if tty && renderProgress {
-                       fmt.Fprint(out, "\033[?25l")
-                       defer fmt.Fprint(out, "\033[?25h\n")
+                       fmt.Fprint(out, "\033[?1049h") // enter alternate screen
+                       fmt.Fprint(out, "\033[?25l")   // hide cursor
                }
 
-               // Restore cursor on signal.
-               go func() {
-                       <-signalChan
+               leaveAltScreen := func() {
                        if tty && renderProgress {
-                               fmt.Fprint(out, "\033[?25h\n")
+                               fmt.Fprint(out, "\033[?25h")   // restore cursor
+                               fmt.Fprint(out, "\033[?1049l") // leave 
alternate screen
                        }
+               }
+
+               // Restore terminal on signal.
+               go func() {
+                       <-signalChan
+                       leaveAltScreen()
                        os.Exit(2)
                }()
 
@@ -315,9 +323,14 @@
                if renderProgress {
                        if tty {
                                renderTTY(out, progress, parallel)
+                               leaveAltScreen()
+                               // Print a static final frame into the main 
scrollback.
+                               renderTTYFinal(out, progress)
                        } else {
                                renderPlain(out, progress)
                        }
+               } else if tty {
+                       leaveAltScreen()
                }
 
                if firstErr != nil {
@@ -340,15 +353,8 @@
                if !quiet {
                        fmt.Fprintln(out)
 
-                       multiTool := len(localToolsStore) > 1
-
-                       // Show summary in non-TTY mode always (CI needs it), 
and
-                       // in TTY mode only for multi-tool downloads 
(single-tool
-                       // TTY already has rich per-line output).
-                       if multiTool || !tty {
-                               // ── Group summary ────────────────────────────
-                               printGroupSummary(out, progress, 
time.Since(groupStart), tty)
-                       }
+                       // ── Group summary ────────────────────────────
+                       printGroupSummary(out, progress, 
time.Since(groupStart), tty)
 
                        if len(localToolsStore) > 0 {
                                arkadeBinInPath := movePath == "" && 
get.ArkadeInPath()
@@ -565,6 +571,79 @@
        fmt.Fprint(out, b.String())
 }
 
+// ── Static final frame (printed after leaving alternate screen) ────
+//
+// Shows the completed state of each tool as static text in the main
+// scrollback. No cursor movement or screen clearing.
+
+func renderTTYFinal(out io.Writer, progress []toolProgress) {
+       var b strings.Builder
+
+       b.WriteString("\033[1m Arkade by Alex Ellis - 
https://github.com/sponsors/alexellis\033[0m\n\n";)
+
+       nameW := 10
+       for i := range progress {
+               l := len(progress[i].name)
+               if progress[i].version != "" {
+                       l += len(progress[i].version) + 3
+               }
+               if l > nameW {
+                       nameW = l
+               }
+       }
+       if nameW > 40 {
+               nameW = 40
+       }
+
+       barW := 20
+
+       for i := range progress {
+               p := &progress[i]
+               read := atomic.LoadInt64(&p.bytesRead)
+               total := atomic.LoadInt64(&p.totalBytes)
+
+               displayName := p.name
+               if p.version != "" {
+                       displayName = fmt.Sprintf("%s (%s)", p.name, p.version)
+               }
+
+               switch p.status {
+               case stDone:
+                       pct := 100
+                       if total > 0 {
+                               pct = int(math.Round(float64(read) / 
float64(total) * 100))
+                               if pct > 100 {
+                                       pct = 100
+                               }
+                       }
+                       bar := renderBar(int64(pct), barW)
+                       sizeStr := units.HumanSize(float64(read))
+                       speed := float64(0)
+                       if p.elapsed > 0 {
+                               speed = float64(read) / p.elapsed.Seconds()
+                       }
+                       elapsed := fmtDuration(p.elapsed)
+                       b.WriteString(fmt.Sprintf(
+                               " \033[32m✔\033[0m  %-*s  %3d%%  %s  %8s  %9s/s 
 %s\n",
+                               nameW, displayName, pct, bar, sizeStr, 
units.HumanSize(speed), elapsed))
+
+               case stFailed:
+                       errMsg := ""
+                       if p.err != nil {
+                               errMsg = p.err.Error()
+                               if len(errMsg) > 40 {
+                                       errMsg = errMsg[:40] + "…"
+                               }
+                       }
+                       b.WriteString(fmt.Sprintf(
+                               " \033[31m✘\033[0m  %-*s  \033[31mfailed: 
%s\033[0m\n",
+                               nameW, p.name, errMsg))
+               }
+       }
+
+       fmt.Fprint(out, b.String())
+}
+
 // ── Non-TTY / plain renderer ───────────────────────────────────────
 //
 // Prints each state change on its own line, no ANSI, no overwrites.
@@ -587,7 +666,11 @@
                case stResolving:
                        fmt.Fprintf(out, "[resolving]   %s\n", p.name)
                case stDownloading:
-                       fmt.Fprintf(out, "[downloading] %s\n", p.name)
+                       displayName := p.name
+                       if p.version != "" {
+                               displayName = fmt.Sprintf("%s (%s)", p.name, 
p.version)
+                       }
+                       fmt.Fprintf(out, "[downloading] %s\n", displayName)
                case stDone:
                        read := atomic.LoadInt64(&p.bytesRead)
                        sizeStr := units.HumanSize(float64(read))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/arkade-0.11.72/cmd/release.go 
new/arkade-0.11.75/cmd/release.go
--- old/arkade-0.11.72/cmd/release.go   1970-01-01 01:00:00.000000000 +0100
+++ new/arkade-0.11.75/cmd/release.go   2026-02-10 11:35:47.000000000 +0100
@@ -0,0 +1,398 @@
+// Copyright (c) arkade author(s) 2024. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for 
full license information.
+
+package cmd
+
+import (
+       "context"
+       "fmt"
+       "strings"
+
+       "github.com/Masterminds/semver"
+       execute "github.com/alexellis/go-execute/v2"
+       "github.com/spf13/cobra"
+)
+
+func MakeRelease() *cobra.Command {
+       var command = &cobra.Command{
+               Use:   "release [version] [title]",
+               Short: "Create a GitHub release",
+               Long: `Create a GitHub release by querying the latest release 
tag,
+incrementing the version, and using the latest commit message as the
+release title.
+
+The latest release tag is obtained via "gh release list", so private
+repositories are supported when "gh" is authenticated.
+
+Version bump strategy (mutually exclusive, default is --patch):
+  --patch   v0.1.2 -> v0.1.3
+  --minor   v0.1.2 -> v0.2.0
+  --major   v0.1.2 -> v1.0.0
+
+Semver handling:
+  - Tags such as "v0.1.0" (prefixed) and "0.1.0" (bare) are both recognised.
+  - The "v" prefix is preserved if the previous tag used one.
+
+Repository visibility changes the defaults:
+  - Public repos:  --prerelease=true  --latest=false
+    A separate bot promotes pre-releases to latest after verification.
+  - Private repos: --prerelease=false --latest=true
+    Releases are immediately marked as latest and stable.
+
+Visibility is auto-detected via "gh repo view". Use --prerelease or --latest
+to override.
+
+Both version and title can be overridden with positional arguments. If only
+one argument is given it is treated as a version when it looks like semver,
+otherwise it is used as the title. Supply both to set both.
+When a version is given explicitly, --major/--minor/--patch are ignored.`,
+               Example: `  # Auto-detect everything (version, title, 
visibility)
+  # Bumps patch by default: v0.1.2 -> v0.1.3
+  arkade release
+
+  # Bump minor version: v0.1.2 -> v0.2.0
+  arkade release --minor
+
+  # Bump major version: v0.1.2 -> v1.0.0
+  arkade release --major
+
+  # Public repo: creates a pre-release, not marked latest
+  arkade release
+
+  # Private repo: creates a full release, marked latest
+  arkade rel
+
+  # Override the version (--major/--minor/--patch ignored)
+  arkade rel v0.5.0
+
+  # Override just the title
+  arkade r "Fix CI pipeline"
+
+  # Override both version and title
+  arkade release 0.3.0 "Add ARM64 support"
+
+  # Force a full (non-prerelease) release on a public repo
+  arkade release --prerelease=false --latest=true
+
+  # Dry run to see what would happen
+  arkade release --dry-run`,
+               Hidden:       true,
+               Aliases:      []string{"rel", "r"},
+               SilenceUsage: true,
+       }
+
+       command.Flags().Bool("dry-run", false, "Print what would be created 
without making the release")
+       command.Flags().Bool("major", false, "Bump the major version (v1.2.3 -> 
v2.0.0)")
+       command.Flags().Bool("minor", false, "Bump the minor version (v1.2.3 -> 
v1.3.0)")
+       command.Flags().Bool("patch", false, "Bump the patch version (v1.2.3 -> 
v1.2.4) (default when none specified)")
+       command.Flags().String("prerelease", "", "Mark as pre-release (default: 
true for public repos, false for private)")
+       command.Flags().String("latest", "", "Mark as latest release (default: 
false for public repos, true for private)")
+       command.Flags().String("token", "", "GitHub token, if not using gh's 
default auth")
+
+       command.RunE = func(cmd *cobra.Command, args []string) error {
+               var versionOverride, titleOverride string
+
+               if len(args) == 1 {
+                       if looksLikeSemver(args[0]) {
+                               versionOverride = args[0]
+                       } else {
+                               titleOverride = args[0]
+                       }
+               } else if len(args) == 2 {
+                       versionOverride = args[0]
+                       titleOverride = args[1]
+               } else if len(args) > 2 {
+                       return fmt.Errorf("expected at most 2 arguments: 
[version] [title], got %d", len(args))
+               }
+
+               dryRun, _ := cmd.Flags().GetBool("dry-run")
+               major, _ := cmd.Flags().GetBool("major")
+               minor, _ := cmd.Flags().GetBool("minor")
+               patch, _ := cmd.Flags().GetBool("patch")
+               prereleaseStr, _ := cmd.Flags().GetString("prerelease")
+               latestStr, _ := cmd.Flags().GetString("latest")
+               token, _ := cmd.Flags().GetString("token")
+
+               bump, err := parseBumpStrategy(major, minor, patch)
+               if err != nil {
+                       return err
+               }
+
+               ctx := context.Background()
+
+               // Detect repo visibility to set defaults
+               private, err := repoIsPrivate(ctx)
+               if err != nil {
+                       fmt.Printf("Warning: could not detect repo visibility: 
%s\nDefaulting to public repo behaviour (pre-release).\n", err)
+                       private = false
+               }
+
+               prerelease := !private // public -> true, private -> false
+               if prereleaseStr != "" {
+                       prerelease = prereleaseStr == "true"
+               }
+
+               latest := private // public -> false, private -> true
+               if latestStr != "" {
+                       latest = latestStr == "true"
+               }
+
+               if private {
+                       fmt.Println("Private repo detected.")
+               } else {
+                       fmt.Println("Public repo detected.")
+               }
+
+               // Resolve the new version tag
+               newTag, err := resolveNewTag(ctx, versionOverride, bump)
+               if err != nil {
+                       return err
+               }
+
+               // Resolve the release title
+               title, err := resolveTitle(ctx, titleOverride)
+               if err != nil {
+                       return err
+               }
+
+               if dryRun {
+                       fmt.Printf("Would create release:\n  Tag:        %s\n  
Title:      %s\n  Bump:       %s\n  Notes:      (none)\n  Prerelease: %v\n  
Latest:     %v\n", newTag, title, bump, prerelease, latest)
+                       return nil
+               }
+
+               ghArgs := []string{
+                       "release", "create", newTag,
+                       "--title", title,
+                       "--notes", "",
+               }
+
+               if prerelease {
+                       ghArgs = append(ghArgs, "--prerelease")
+               }
+
+               if latest {
+                       ghArgs = append(ghArgs, "--latest")
+               } else {
+                       ghArgs = append(ghArgs, "--latest=false")
+               }
+
+               task := execute.ExecTask{
+                       Command:      "gh",
+                       Args:         ghArgs,
+                       StreamStdio:  true,
+                       PrintCommand: true,
+               }
+
+               if token != "" {
+                       task.Env = []string{fmt.Sprintf("GH_TOKEN=%s", token)}
+               }
+
+               res, err := task.Execute(ctx)
+               if err != nil {
+                       return fmt.Errorf("failed to run gh: %w", err)
+               }
+               if res.ExitCode != 0 {
+                       return fmt.Errorf("gh exited with code %d: %s", 
res.ExitCode, res.Stderr)
+               }
+
+               kind := "release"
+               if prerelease {
+                       kind = "pre-release"
+               }
+               fmt.Printf("Created %s %s.\n", kind, newTag)
+               return nil
+       }
+
+       return command
+}
+
+// bumpStrategy represents which semver component to increment.
+type bumpStrategy int
+
+const (
+       bumpPatchStrategy bumpStrategy = iota
+       bumpMinorStrategy
+       bumpMajorStrategy
+)
+
+func (b bumpStrategy) String() string {
+       switch b {
+       case bumpMinorStrategy:
+               return "minor"
+       case bumpMajorStrategy:
+               return "major"
+       default:
+               return "patch"
+       }
+}
+
+// parseBumpStrategy validates the --major/--minor/--patch flags are mutually
+// exclusive and returns the selected strategy. Defaults to patch.
+func parseBumpStrategy(major, minor, patch bool) (bumpStrategy, error) {
+       set := 0
+       if major {
+               set++
+       }
+       if minor {
+               set++
+       }
+       if patch {
+               set++
+       }
+       if set > 1 {
+               return 0, fmt.Errorf("--major, --minor, and --patch are 
mutually exclusive")
+       }
+
+       if major {
+               return bumpMajorStrategy, nil
+       }
+       if minor {
+               return bumpMinorStrategy, nil
+       }
+       return bumpPatchStrategy, nil
+}
+
+// repoIsPrivate uses gh to check whether the current repo is private.
+func repoIsPrivate(ctx context.Context) (bool, error) {
+       task := execute.ExecTask{
+               Command: "gh",
+               Args:    []string{"api", "repos/{owner}/{repo}", "--jq", 
".visibility"},
+       }
+
+       res, err := task.Execute(ctx)
+       if err != nil {
+               return false, fmt.Errorf("failed to run gh: %w", err)
+       }
+       if res.ExitCode != 0 {
+               return false, fmt.Errorf("gh exited with code %d: %s", 
res.ExitCode, res.Stderr)
+       }
+
+       visibility := strings.TrimSpace(strings.ToUpper(res.Stdout))
+       return visibility == "PRIVATE" || visibility == "INTERNAL", nil
+}
+
+// resolveNewTag returns the tag for the new release. If versionOverride is
+// non-empty it is normalised and returned directly. Otherwise the latest
+// release tag is fetched via gh and bumped according to the strategy.
+func resolveNewTag(ctx context.Context, versionOverride string, bump 
bumpStrategy) (string, error) {
+       if versionOverride != "" {
+               return normaliseSemver(versionOverride)
+       }
+
+       latestTag, err := latestReleaseTag(ctx)
+       if err != nil {
+               return "", fmt.Errorf("could not determine latest release: 
%w\nUse a positional argument to set the version explicitly", err)
+       }
+
+       return bumpVersion(latestTag, bump)
+}
+
+// resolveTitle returns a release title. If titleOverride is non-empty it is
+// used directly. Otherwise the first line of the latest git commit message is
+// returned.
+func resolveTitle(ctx context.Context, titleOverride string) (string, error) {
+       if titleOverride != "" {
+               return titleOverride, nil
+       }
+
+       task := execute.ExecTask{
+               Command: "git",
+               Args:    []string{"log", "-1", "--format=%s"},
+       }
+
+       res, err := task.Execute(ctx)
+       if err != nil {
+               return "", fmt.Errorf("could not get latest commit message: 
%w", err)
+       }
+       if res.ExitCode != 0 {
+               return "", fmt.Errorf("git log exited with code %d: %s", 
res.ExitCode, res.Stderr)
+       }
+
+       msg := strings.TrimSpace(res.Stdout)
+       if msg == "" {
+               return "", fmt.Errorf("latest commit message is empty")
+       }
+
+       return msg, nil
+}
+
+// latestReleaseTag queries gh for the most recent release tag.
+func latestReleaseTag(ctx context.Context) (string, error) {
+       task := execute.ExecTask{
+               Command: "gh",
+               Args:    []string{"api", "repos/{owner}/{repo}/releases", 
"--jq", ".[0].tag_name"},
+       }
+
+       res, err := task.Execute(ctx)
+       if err != nil {
+               return "", fmt.Errorf("failed to run gh: %w", err)
+       }
+       if res.ExitCode != 0 {
+               return "", fmt.Errorf("gh exited with code %d: %s", 
res.ExitCode, res.Stderr)
+       }
+
+       tag := strings.TrimSpace(res.Stdout)
+       if tag == "" {
+               return "", fmt.Errorf("no releases found in this repository")
+       }
+
+       return tag, nil
+}
+
+// bumpVersion increments the specified semver component of a tag.
+// Both "v"-prefixed and bare versions are supported; the prefix style is
+// preserved in the output.
+func bumpVersion(tag string, bump bumpStrategy) (string, error) {
+       hasV := strings.HasPrefix(tag, "v")
+
+       ver, err := semver.NewVersion(tag)
+       if err != nil {
+               return "", fmt.Errorf("cannot parse %q as semver: %w", tag, err)
+       }
+
+       var bumped semver.Version
+       switch bump {
+       case bumpMajorStrategy:
+               bumped = ver.IncMajor()
+       case bumpMinorStrategy:
+               bumped = ver.IncMinor()
+       default:
+               bumped = ver.IncPatch()
+       }
+
+       if hasV {
+               return "v" + bumped.String(), nil
+       }
+       return bumped.String(), nil
+}
+
+// normaliseSemver validates that s looks like semver and returns it unchanged.
+func normaliseSemver(s string) (string, error) {
+       raw := s
+       if strings.HasPrefix(s, "v") {
+               s = s[1:]
+       }
+       if _, err := semver.NewVersion(s); err != nil {
+               return "", fmt.Errorf("%q is not a valid semver version", raw)
+       }
+       return raw, nil
+}
+
+// looksLikeSemver returns true when s resembles a semver string
+// (with or without a "v" prefix). This is intentionally lenient so that
+// users can pass e.g. "1.0" and have it treated as a version.
+func looksLikeSemver(s string) bool {
+       raw := s
+       if strings.HasPrefix(raw, "v") || strings.HasPrefix(raw, "V") {
+               raw = raw[1:]
+       }
+
+       // Quick heuristic: starts with a digit and contains a dot.
+       if len(raw) == 0 {
+               return false
+       }
+       if raw[0] < '0' || raw[0] > '9' {
+               return false
+       }
+       return strings.Contains(raw, ".")
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/arkade-0.11.72/main.go new/arkade-0.11.75/main.go
--- old/arkade-0.11.72/main.go  2026-02-07 10:29:49.000000000 +0100
+++ new/arkade-0.11.75/main.go  2026-02-10 11:35:47.000000000 +0100
@@ -32,6 +32,8 @@
        rootCmd.AddCommand(cmd.MakeUninstall())
        rootCmd.AddCommand(cmd.MakeShellCompletion())
 
+       rootCmd.AddCommand(cmd.MakeRelease())
+
        rootCmd.AddCommand(chart.MakeChart())
        rootCmd.AddCommand(system.MakeSystem())
        rootCmd.AddCommand(oci.MakeOci())
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/arkade-0.11.72/pkg/get/download.go 
new/arkade-0.11.75/pkg/get/download.go
--- old/arkade-0.11.72/pkg/get/download.go      2026-02-07 10:29:49.000000000 
+0100
+++ new/arkade-0.11.75/pkg/get/download.go      2026-02-10 11:35:47.000000000 
+0100
@@ -159,7 +159,9 @@
                        }
 
                        verifyURL := strings.TrimSpace(buf.String())
-                       log.Printf("Downloading SHA sum from: %s", verifyURL)
+                       if !quiet {
+                               log.Printf("Downloading SHA sum from: %s", 
verifyURL)
+                       }
                        shaSumManifest, err := fetchText(verifyURL)
                        if err != nil {
                                return "", "", err
@@ -193,7 +195,7 @@
 
                        if err := verifySHA(platformInfo.Checksum, 
outFilePath); err != nil {
                                return "", "", err
-                       } else {
+                       } else if !quiet {
                                log.Printf("SHA sum verified in %s.", 
time.Since(st).Round(time.Millisecond))
                        }
 
@@ -222,14 +224,16 @@
                        }
 
                        verifyURL := strings.TrimSpace(buf.String())
-                       log.Printf("Downloading SHA sum from: %s", verifyURL)
+                       if !quiet {
+                               log.Printf("Downloading SHA sum from: %s", 
verifyURL)
+                       }
                        shaSum, err := fetchText(verifyURL)
                        if err != nil {
                                return "", "", err
                        }
                        if err := verifySHA(shaSum, outFilePath); err != nil {
                                return "", "", err
-                       } else {
+                       } else if !quiet {
                                log.Printf("SHA sum verified in %s.", 
time.Since(st).Round(time.Millisecond))
                        }
                } else if tool.VerifyStrategy == AmpShasumStrategy {
@@ -257,14 +261,16 @@
                        }
 
                        verifyURL := strings.TrimSpace(buf.String())
-                       log.Printf("Downloading SHA sum from: %s", verifyURL)
+                       if !quiet {
+                               log.Printf("Downloading SHA sum from: %s", 
verifyURL)
+                       }
                        shaSum, err := fetchText(verifyURL)
                        if err != nil {
                                return "", "", err
                        }
                        if err := verifySHA(shaSum, outFilePath); err != nil {
                                return "", "", err
-                       } else {
+                       } else if !quiet {
                                log.Printf("SHA sum verified in %s.", 
time.Since(st).Round(time.Millisecond))
                        }
                }

++++++ arkade.obsinfo ++++++
--- /var/tmp/diff_new_pack.g0gryt/_old  2026-02-10 21:14:41.257257742 +0100
+++ /var/tmp/diff_new_pack.g0gryt/_new  2026-02-10 21:14:41.257257742 +0100
@@ -1,5 +1,5 @@
 name: arkade
-version: 0.11.72
-mtime: 1770456589
-commit: 0e580bd296b08c3ba66cd0cc51ce6a614f31321c
+version: 0.11.75
+mtime: 1770719747
+commit: 8e860ea5a87682d22908c2d4355c50f089b55c0a
 

++++++ vendor.tar.gz ++++++

Reply via email to