Skip to content

feat(cli): Add upgrade command for CLI self-update#348

Merged
cameroncooke merged 4 commits intomainfrom
upgrade
Apr 19, 2026
Merged

feat(cli): Add upgrade command for CLI self-update#348
cameroncooke merged 4 commits intomainfrom
upgrade

Conversation

@cameroncooke
Copy link
Copy Markdown
Collaborator

Adds a new top-level xcodebuildmcp upgrade command that compares the
installed version against the latest GitHub release, detects the install
method (Homebrew, npm-global, npx, or unknown), shows a truncated release
notes excerpt, and either runs the correct upgrade command with inherited
stdio or prints the manual command for unsupported channels.

Behavior

  • --check reports current vs. latest without prompting (CI-friendly).
  • --yes / -y skips the confirmation and auto-runs the upgrade.
  • Homebrew: brew update then brew upgrade xcodebuildmcp.
  • npm-global: npm install -g xcodebuildmcp@latest.
  • npx: never auto-runs — prints the ephemeral-install note and exits 0
    so --yes does not error in scripts.
  • Unknown install: prints manual options, never auto-runs.
  • Non-TTY callers that could auto-upgrade but omit --yes exit 1 so
    scripts do not hang on an invisible confirm.
  • GitHub failures (timeout, non-200, 403 rate limit, malformed JSON) are
    reported with a clear message plus the manual upgrade command when
    detection succeeded.

Implementation notes

  • The command short-circuits in src/cli.ts mirroring init / setup,
    so no runtime/daemon bootstrap runs for this path.
  • Version generator (scripts/generate-version.ts) now emits
    packageName, repositoryOwner, repositoryName parsed from
    package.json; the command reads these from src/utils/version
    instead of hardcoding identifiers. Generator fails the build if
    repository.url is not a parseable GitHub URL.
  • Upgrade execution uses spawn(..., { stdio: 'inherit' }) with
    SIGINT/SIGTERM forwarding; the buffered CommandExecutor is
    deliberately not used because package-manager output and prompts need
    to stream live.
  • Semver comparison is done locally (leading v stripped, prerelease
    suffixes supported) to avoid adding a semver dependency for one
    command.
  • Install detection is conservative: realpath-based on
    process.argv[1] and process.execPath, matching Homebrew Cellar
    paths (Intel and Apple Silicon), global node_modules/xcodebuildmcp,
    and the npx _npx cache. Unknown paths never auto-run.

Tests

67 unit tests cover the full matrix: version parsing/compare edge cases,
detection for each channel with exact argv assertions, TTY and non-TTY
paths for every flag combination, release-notes truncation at both the
20-line and 2000-char caps, and every GitHub failure class. All
dependencies are injected through UpgradeDependencies — no real
network, spawn, or filesystem access in tests.

Add a new top-level `xcodebuildmcp upgrade` command that compares the
installed version against the latest GitHub release, detects the install
method (Homebrew, npm-global, npx, or unknown), shows a truncated
release-notes excerpt, and either runs the correct upgrade command with
inherited stdio or prints the manual command for unsupported channels.

Supports `--check` to report versions without prompting and `--yes`/`-y`
to skip the interactive confirmation for scripted use. Non-TTY callers
that could auto-upgrade but omit `--yes` exit with status 1 so scripts
do not hang.

Repo owner/name and package name are now emitted from the version
generator so the command reads them from package.json rather than
hardcoding. The command short-circuits in src/cli.ts to avoid the full
runtime bootstrap, mirroring `init` and `setup`.

Co-Authored-By: Claude <noreply@anthropic.com>
@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new bot commented Apr 19, 2026

Open in StackBlitz

npm i https://pkg.pr.new/xcodebuildmcp@348

commit: b17e405

Comment thread src/cli/commands/upgrade.ts Outdated
Each install channel now queries its own source of truth for the latest
version instead of always using GitHub Releases. This prevents misleading
results when release channels drift — e.g. GitHub may publish a version
before the Homebrew tap bumps, or npm dist-tags may lag a pre-release.

- Homebrew: `brew info --json=v2` (handles exit-0-with-empty-formulae)
- npm-global / npx: `npm view <pkg>@latest version --json`
- Unknown: falls back to GitHub Releases latest endpoint

Release notes are fetched separately by tag from GitHub and are non-fatal
if unavailable (404, timeout, network error).

New DI surface: fetchLatestVersionForChannel, fetchReleaseNotesForTag,
runChannelLookupCommand (for test isolation). The dependency factory
rebuilds derived defaults from merged overrides so tests can mock at
any level.

Co-Authored-By: Claude <noreply@anthropic.com>
Comment thread src/cli/commands/upgrade.ts
Comment thread src/cli/commands/upgrade.ts Outdated
Fix parseVersion regex to accept hyphens in prerelease and build-metadata
identifiers as required by SemVer 2.0.0, so versions like 1.0.0-alpha-1
or 1.0.0-rc-1+build-hash no longer fail to parse.

Check the exit code of `brew info` before attempting to parse its stdout.
Homebrew exits 0 for missing formulas (a quirk the existing empty-formulae
path handles), but other failure modes like a broken brew install or
permission errors exit non-zero with empty stdout — those now surface a
specific exit-code error instead of a misleading "invalid JSON output".

Reported in PR review by Cursor Bugbot and Sentry.

Co-Authored-By: Claude <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 59d3e80. Configure here.

Comment thread src/cli/commands/upgrade.ts Outdated
SemVer 2.0.0 §11 requires lexical comparison of prerelease identifiers in
ASCII sort order. The previous implementation used String.prototype.localeCompare,
which applies locale-dependent collation and can differ from ASCII ordering.
For example, "Alpha".localeCompare("alpha") returns 1 in common locales while
ASCII comparison returns -1.

Replace localeCompare with a UTF-16 code-unit comparison, which matches
ASCII ordering for the [0-9A-Za-z-] character set SemVer permits.

Reported in PR review by Cursor Bugbot.

Co-Authored-By: Claude <noreply@anthropic.com>
@cameroncooke cameroncooke merged commit 03e0a23 into main Apr 19, 2026
12 checks passed
@cameroncooke cameroncooke deleted the upgrade branch April 19, 2026 20:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant