mirror of
https://github.com/Twigpine/zero.git
synced 2026-11-10 05:01:28 -10:00
* docs(readme): document /new, zero upgrade, and add Discord link Add the /new slash command and zero upgrade command to the README's command references, expand docs/UPDATE.md to cover the --apply install flow, and link the project Discord server. * docs(update): escape pipe in --endpoint table cell CodeRabbit flagged the unescaped | in the --endpoint placeholder breaking the Markdown table layout. * docs(readme-zh): bring /new and zero upgrade in line with README.md README_ZH.md had picked up the Discord link but was still missing the /new slash command and zero upgrade entries added to README.md, letting the translation drift out of sync. * cli: add upgrade to root help output * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <[email protected]> * docs: make required update flag explicit in common commands zero update errors without --check or --apply, so the bare form in the common-commands examples was misleading. Addresses the Copilot finding from the PR #489 review. * docs: use a permanent (non-expiring) Discord invite in the READMEs * docs(cli): match upgrade help summary to download/verify/install wording Copilot flagged the root zero --help summary for upgrade as less specific than the download/verify/install phrasing already used in README.md and docs/UPDATE.md for the same command. Co-Authored-By: Claude Sonnet 5 <[email protected]> * docs: use discord.gg short link for the community invite discord.com/invite/<code> and discord.gg/<code> resolve to the same invite; discord.gg is the canonical short form to publish. Co-Authored-By: Claude Sonnet 5 <[email protected]> * fix(update,readme-zh): keep npm --json stdout clean, add ZH Discussions route `zero upgrade --json` (and `zero update --apply --json`) on an npm-managed install wired npm's own stdout directly to the process stdout, so its progress output landed ahead of the JSON result and made it unparseable. Route npm's stdout into a buffer when JSON output is requested, only surfacing it in the error message if the install fails. README_ZH.md's Community section only pointed to Discord, unlike the English README, which reserves Discord for real-time chat and routes Q&A/ideas/sharing to GitHub Discussions per SUPPORT.md/CONTRIBUTING.md policy. Bring the Chinese section in line with the same split. Addresses the outstanding findings from jatmn's review on PR #489. Co-Authored-By: Claude Sonnet 5 <[email protected]> * docs(update): qualify npm-managed JSON output Document the npm subprocess output caveat instead of promising a single parseable JSON document for apply operations. Drop the JSON-specific updater plumbing and tests so the PR remains focused on documentation and help discovery. Amp-Thread-ID: https://ampcode.com/threads/T-019fa55a-8bcd-71bc-9bde-9b4c28b78b38 Co-authored-by: Pierre Bruno <[email protected]> * docs(update): align upgrade guidance Amp-Thread-ID: https://ampcode.com/threads/T-019fa7a0-1223-701d-9529-48ba5d7cf8c8 Co-authored-by: Pierre Bruno <[email protected]> * fix(update): keep the upgrade recommendation about this machine `zero update --check --target windows-x64` answers a question about a different machine, but the new call to action told the user to run `zero upgrade`, which only ever installs onto this one — replacing the target-specific download guidance the flag used to print. The recommendation is now conditional: a current-platform check keeps it, and a cross-target check gets the manual instruction plus a note naming the machine `zero upgrade` would have touched instead. Also align two flag contracts with what the code does: `zero update --help` described --repo/--endpoint as check-only although apply honors them, and UPDATE.md did not mention that both are ignored on an npm-managed apply, which takes its release from the npm registry rather than GitHub. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> * fix(update): do not send a custom-source check to the default source `zero upgrade` is a fresh invocation: it does not inherit the --repo/--endpoint of the check that suggested it, so recommending it bare after a custom-source check told the user to install from a repository they never asked about. The check now records the flag it was given and the guidance repeats it, naming where a bare upgrade would install from instead. ZERO_UPDATE_RELEASE_URL is deliberately not recorded: every Check reads it from the environment, including the one inside Apply, so a bare upgrade already follows it and telling the user to repeat it would be wrong. Two smaller edges from the same review: a cross-target check on a host with no published release target no longer ends by describing what `zero upgrade` does there, since it does not work there at all; and the flag table and help text now carry the npm caveat the prose bullet already had, so docs and help agree. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> * fix(update): keep source guidance accurate Amp-Thread-ID: https://ampcode.com/threads/T-019fafa2-7d9d-75bc-8801-6d0efc8db1df Co-authored-by: Pierre Bruno <[email protected]> --------- Co-authored-by: Copilot Autofix powered by AI <[email protected]> Co-authored-by: Vasanthdev2004 <[email protected]> Co-authored-by: Claude Sonnet 5 <[email protected]> Co-authored-by: Amp <[email protected]>
213 lines
6.5 KiB
Go
213 lines
6.5 KiB
Go
package cli
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/Gitlawb/zero/internal/update"
|
|
)
|
|
|
|
type updateOptions struct {
|
|
check bool
|
|
apply bool
|
|
json bool
|
|
repository string
|
|
endpoint string
|
|
timeout time.Duration
|
|
target string
|
|
}
|
|
|
|
func runUpdate(args []string, stdout io.Writer, stderr io.Writer, deps appDeps) int {
|
|
return runUpdateCommand(args, stdout, stderr, deps, false)
|
|
}
|
|
|
|
func runUpgrade(args []string, stdout io.Writer, stderr io.Writer, deps appDeps) int {
|
|
return runUpdateCommand(args, stdout, stderr, deps, true)
|
|
}
|
|
|
|
// runUpdateCommand backs both `zero update` and `zero upgrade`. defaultApply
|
|
// makes `--apply` the implicit behavior for `zero upgrade` when neither
|
|
// `--check` nor `--apply` is passed explicitly; `zero update` keeps requiring
|
|
// one of the two so existing scripts around `zero update --check` don't change.
|
|
func runUpdateCommand(args []string, stdout io.Writer, stderr io.Writer, deps appDeps, defaultApply bool) int {
|
|
options, help, err := parseUpdateArgs(args)
|
|
if err != nil {
|
|
return writeUsageError(stderr, err.Error())
|
|
}
|
|
if help {
|
|
if err := writeUpdateHelp(stdout); err != nil {
|
|
return exitCrash
|
|
}
|
|
return exitSuccess
|
|
}
|
|
if options.check && options.apply {
|
|
return writeUsageError(stderr, "Pass only one of --check or --apply.")
|
|
}
|
|
if !options.check && !options.apply {
|
|
if !defaultApply {
|
|
return writeUsageError(stderr, "Pass --check to check for updates or --apply to install one (or use `zero upgrade`).")
|
|
}
|
|
options.apply = true
|
|
}
|
|
if options.apply && options.target != "" {
|
|
return writeUsageError(stderr, "--target cannot be combined with --apply; it only verifies release assets for --check.")
|
|
}
|
|
updateOptions := update.Options{
|
|
CurrentVersion: version,
|
|
Repository: options.repository,
|
|
Endpoint: options.endpoint,
|
|
Timeout: options.timeout,
|
|
}
|
|
if options.target != "" {
|
|
target, err := update.ResolveTarget(options.target)
|
|
if err != nil {
|
|
return writeUsageError(stderr, err.Error())
|
|
}
|
|
updateOptions.GOOS = target.GOOS
|
|
updateOptions.GOARCH = target.GOARCH
|
|
}
|
|
if options.apply {
|
|
result, err := deps.applyUpdate(context.Background(), updateOptions)
|
|
if err != nil {
|
|
return writeAppError(stderr, "Could not install update: "+err.Error(), exitCrash)
|
|
}
|
|
if options.json {
|
|
if err := writePrettyJSON(stdout, result); err != nil {
|
|
return exitCrash
|
|
}
|
|
return exitSuccess
|
|
}
|
|
if _, err := fmt.Fprintln(stdout, update.FormatApply(result)); err != nil {
|
|
return exitCrash
|
|
}
|
|
return exitSuccess
|
|
}
|
|
result, err := deps.checkUpdate(context.Background(), updateOptions)
|
|
if err != nil {
|
|
return writeAppError(stderr, "Could not check for updates: "+err.Error(), exitCrash)
|
|
}
|
|
if options.json {
|
|
if err := writePrettyJSON(stdout, result); err != nil {
|
|
return exitCrash
|
|
}
|
|
return exitSuccess
|
|
}
|
|
if _, err := fmt.Fprintln(stdout, update.Format(result)); err != nil {
|
|
return exitCrash
|
|
}
|
|
return exitSuccess
|
|
}
|
|
|
|
func parseUpdateArgs(args []string) (updateOptions, bool, error) {
|
|
options := updateOptions{}
|
|
for index := 0; index < len(args); index++ {
|
|
arg := args[index]
|
|
switch {
|
|
case arg == "-h" || arg == "--help" || arg == "help":
|
|
return options, true, nil
|
|
case arg == "--check":
|
|
options.check = true
|
|
case arg == "--apply":
|
|
options.apply = true
|
|
case arg == "--json":
|
|
options.json = true
|
|
case arg == "--repo":
|
|
value, next, err := nextFlagValue(args, index, arg)
|
|
if err != nil {
|
|
return options, false, err
|
|
}
|
|
options.repository = strings.TrimSpace(value)
|
|
index = next
|
|
case strings.HasPrefix(arg, "--repo="):
|
|
options.repository = strings.TrimSpace(strings.TrimPrefix(arg, "--repo="))
|
|
case arg == "--endpoint":
|
|
value, next, err := nextFlagValue(args, index, arg)
|
|
if err != nil {
|
|
return options, false, err
|
|
}
|
|
options.endpoint = strings.TrimSpace(value)
|
|
index = next
|
|
case strings.HasPrefix(arg, "--endpoint="):
|
|
options.endpoint = strings.TrimSpace(strings.TrimPrefix(arg, "--endpoint="))
|
|
case arg == "--timeout":
|
|
value, next, err := nextFlagValue(args, index, arg)
|
|
if err != nil {
|
|
return options, false, err
|
|
}
|
|
timeout, err := parseUpdateTimeout(value)
|
|
if err != nil {
|
|
return options, false, err
|
|
}
|
|
options.timeout = timeout
|
|
index = next
|
|
case strings.HasPrefix(arg, "--timeout="):
|
|
timeout, err := parseUpdateTimeout(strings.TrimPrefix(arg, "--timeout="))
|
|
if err != nil {
|
|
return options, false, err
|
|
}
|
|
options.timeout = timeout
|
|
case arg == "--target":
|
|
value, next, err := nextFlagValue(args, index, arg)
|
|
if err != nil {
|
|
return options, false, err
|
|
}
|
|
target, err := parseUpdateTarget(value)
|
|
if err != nil {
|
|
return options, false, err
|
|
}
|
|
options.target = target
|
|
index = next
|
|
case strings.HasPrefix(arg, "--target="):
|
|
target, err := parseUpdateTarget(strings.TrimPrefix(arg, "--target="))
|
|
if err != nil {
|
|
return options, false, err
|
|
}
|
|
options.target = target
|
|
default:
|
|
return options, false, execUsageError{fmt.Sprintf("unknown update flag %q", arg)}
|
|
}
|
|
}
|
|
return options, false, nil
|
|
}
|
|
|
|
func parseUpdateTimeout(value string) (time.Duration, error) {
|
|
timeout, err := time.ParseDuration(strings.TrimSpace(value))
|
|
if err != nil {
|
|
return 0, execUsageError{fmt.Sprintf("invalid update timeout %q: use a duration like 5s or 750ms", value)}
|
|
}
|
|
if timeout <= 0 {
|
|
return 0, execUsageError{fmt.Sprintf("invalid update timeout %q: timeout must be a positive duration", value)}
|
|
}
|
|
return timeout, nil
|
|
}
|
|
|
|
func parseUpdateTarget(value string) (string, error) {
|
|
target := strings.TrimSpace(value)
|
|
if target == "" {
|
|
return "", execUsageError{"--target requires a non-empty value"}
|
|
}
|
|
return target, nil
|
|
}
|
|
|
|
func writeUpdateHelp(w io.Writer) error {
|
|
_, err := fmt.Fprint(w, `Usage:
|
|
zero update --check [flags]
|
|
zero update --apply [flags]
|
|
zero upgrade [flags]
|
|
|
|
Flags:
|
|
--check Check the latest GitHub release without installing
|
|
--apply Download, verify, and install the latest release
|
|
--json Print the update result as JSON
|
|
--repo <owner/repo> Repository to use when no endpoint is provided (--check and --apply; ignored for the install source by an npm-managed apply)
|
|
--endpoint <url|repo> Release API URL or owner/repo slug to use (--check and --apply; ignored for the install source by an npm-managed apply)
|
|
--timeout <duration> Release check timeout (default 5s)
|
|
--target <platform> Release target to verify with --check (for example windows-x64); not valid with --apply
|
|
-h, --help Show this help
|
|
`)
|
|
return err
|
|
}
|