package update
import (
"fmt"
"os"
"strings"
"github.com/spf13/cobra"
cmdutil "gitcode.com/gitcode-cli/cli/pkg/cmdutil"
"gitcode.com/gitcode-cli/cli/pkg/installupdate"
)
type result struct {
Status string `json:"status"`
Distribution string `json:"distribution"`
Current string `json:"current"`
Latest string `json:"latest"`
Message string `json:"message"`
}
var (
checkNow = installupdate.CheckNow
startDetached = installupdate.StartDetached
)
func NewCmdUpdate(_ *cmdutil.Factory) *cobra.Command {
var checkOnly bool
var jsonOutput bool
cmd := &cobra.Command{
Use: "update",
Short: "Check for or apply an installation-channel update",
Long: `Update GitCode CLI through the channel that owns the current installation.
Global npm wrappers update the installed npm coordinate (atomgit-cli,
@atomgit-cli/cli, or @gitcode-cli/cli) directly. npm-bootstrap installs run
a foreground update check first: when a newer stable version exists, the
atomic replacement is scheduled after the current process exits; otherwise
the current version is reported and nothing is scheduled. Other package
managers remain user-controlled and are never invoked or removed implicitly.`,
RunE: func(cmd *cobra.Command, args []string) error {
manifest, err := installupdate.LoadBootstrapManifest()
if err == nil {
if checkOnly {
return installupdate.RunCheck(manifest, jsonOutput, cmd.OutOrStdout(), cmd.ErrOrStderr())
}
return runBootstrapUpdate(cmd, manifest, jsonOutput)
}
distribution := detectDistribution()
message := managerMessage(distribution)
return writeResult(cmd, jsonOutput, result{Status: "manual", Distribution: distribution, Message: message})
},
}
cmd.Flags().BoolVar(&checkOnly, "check", false, "Check for an update without installing it")
cmdutil.AddJSONFlag(cmd, &jsonOutput)
return cmd
}
func runBootstrapUpdate(cmd *cobra.Command, manifest *installupdate.Manifest, jsonOutput bool) error {
check, err := checkNow(manifest)
if err != nil {
writeFailure(cmd, manifest, err, jsonOutput)
return err
}
if check.Status == "available" {
if err := startDetached(manifest, true); err != nil {
writeFailure(cmd, manifest, err, jsonOutput)
return err
}
return writeResult(cmd, jsonOutput, result{
Status: "scheduled",
Distribution: "npm-bootstrap",
Current: manifest.Version,
Latest: check.Latest,
Message: "Update scheduled; the new stable version will be used on the next launch.",
})
}
return writeResult(cmd, jsonOutput, result{
Status: check.Status,
Distribution: "npm-bootstrap",
Current: manifest.Version,
Latest: check.Latest,
Message: check.Message,
})
}
func writeFailure(cmd *cobra.Command, manifest *installupdate.Manifest, failure error, jsonOutput bool) {
if !jsonOutput {
return
}
_ = writeResult(cmd, true, result{
Status: "error",
Distribution: "npm-bootstrap",
Current: manifest.Version,
Message: failure.Error(),
})
}
func detectDistribution() string {
binary := os.Getenv("GITCODE_CLI_BINARY")
if binary == "" {
if resolved, err := os.Executable(); err == nil {
binary = resolved
}
}
env := make(map[string]string)
for _, item := range os.Environ() {
key, value, ok := strings.Cut(item, "=")
if ok {
env[strings.ToUpper(key)] = value
}
}
return installupdate.DetectDistribution(env, binary)
}
func managerMessage(distribution string) string {
switch distribution {
case "pypi":
return "This installation is managed by Python; upgrade it explicitly with pipx or pip in its environment."
case "uv":
return "This installation is managed by uv; run \"uv tool upgrade gitcode-cli\"."
case "homebrew":
return "This installation is managed by Homebrew; run brew upgrade gc."
case "deb", "rpm", "system-package":
return "This installation is managed by the operating-system package manager; use apt, dnf, or rpm explicitly."
case "npm", "pnpm", "npm-local":
return "Run update through the npm gitcode wrapper so it can safely replace its bundled binary."
case "npm-bootstrap":
return `This installation was bootstrapped by npm; repair it by rerunning "npx --yes --package=<coordinate>@latest gitcode install".`
default:
return "Download and verify a newer release archive, or rebuild from the desired source tag."
}
}
func writeResult(cmd *cobra.Command, jsonOutput bool, value result) error {
if jsonOutput {
return cmdutil.WriteJSON(cmd.OutOrStdout(), value)
}
_, err := fmt.Fprintln(cmd.OutOrStdout(), value.Message)
return err
}