feat(cli): implement specify self upgrade#2475
Open
chordpli wants to merge 16 commits intogithub:mainfrom
Open
feat(cli): implement specify self upgrade#2475chordpli wants to merge 16 commits intogithub:mainfrom
chordpli wants to merge 16 commits intogithub:mainfrom
Conversation
Replace the v0.7.5 reserved stub from github#2316 with an actually working self-upgrade command. Detects the install method (uv tool / pipx / uvx-ephemeral / source-checkout / unsupported) via a 3-tier ladder (path-prefix match -> editable-install marker -> PATH+registry reconciliation), runs the appropriate installer subprocess, and verifies the result by spawning a child `specify --version`. - Bare `specify self upgrade` executes immediately, matching the `pip install -U` / `uv tool upgrade` / `npm update` convention. - `--dry-run` prints the preview (method, current, target, argv) and exits 0 without launching any subprocess. - `--tag vX.Y.Z[suffix]` pins a specific release tag (validated against `^v\d+\.\d+\.\d+(?:[a-z0-9.+\-]*)?$`; rejects bare `latest`, branch names, and hash refs). - Token scrubbing: GH_TOKEN / GITHUB_TOKEN are never forwarded to the installer subprocess environment or surfaced in argv. - Exit codes: 0 (success / no-op-success), 1 (resolution failure), 2 (verification mismatch), 3 (installer missing), 124 (subprocess timeout when SPECIFY_UPGRADE_TIMEOUT_SECS is set), other (installer exit propagated verbatim). - uvx-ephemeral runs and source checkouts emit path-specific guidance and exit 0 instead of running an installer. Documentation: README and docs/upgrade.md now lead with the self-management commands as the recommended path; manual `uv tool install --force` / `pipx install --force` remain as fallback for older installs and explicit-control users. docs/installation.md adds a "Stay current" pointer. Closes the unresolved portion of github#2282.
…Phase 2
The `specify self` Typer help and `self_check()` docstring were still describing
the v0.7.5 stub state ("reserved upgrade command", "future release will use for
actual self-upgrade", "behavior is not implemented in this release"). Now that
`specify self upgrade` is a real, working command, these strings contradicted
the implementation when surfaced through `specify self --help` and
`specify self check --help`.
- self_app help: "Manage the specify CLI itself: check for newer releases
(read-only) and upgrade in place."
- self_check docstring: points readers at `specify self upgrade` and
`specify self upgrade --dry-run` as the natural follow-ups, instead of
claiming the upgrade command is unimplemented.
No behavior change.
Contributor
There was a problem hiding this comment.
Pull request overview
Implements a functional specify self upgrade command to replace the previous reserved stub, enabling in-place upgrades (uv tool / pipx) with install-method detection, dry-run previews, tag pinning, token scrubbing, and post-upgrade verification via a child specify --version.
Changes:
- Adds install-method detection + upgrade orchestration helpers and replaces the
self upgradestub with a working implementation. - Introduces a comprehensive new test suite for
specify self upgradeand removes the old stub-pin tests. - Updates README and docs to recommend
specify self upgradeand document the new flows.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
src/specify_cli/__init__.py |
Adds self-upgrade planning/execution/verification logic and updates self command help text. |
tests/test_self_upgrade.py |
New test module covering detection, argv assembly, dry-run behavior, tag validation, token scrubbing, and verification. |
tests/test_upgrade.py |
Removes tests that pinned the previous non-functional stub output. |
README.md |
Documents specify self check / specify self upgrade as the preferred upgrade path. |
docs/upgrade.md |
Refreshes upgrade guide to include self-upgrade commands, verification steps, and troubleshooting. |
docs/installation.md |
Adds “Stay current” pointer to specify self check and upgrade guide. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+1992
to
+2011
| """Return whether the installed distribution appears to come from a git checkout.""" | ||
| import importlib.metadata as _md | ||
|
|
||
| editable_root = _editable_direct_url_path() | ||
| if editable_root is not None and _git_ancestor(editable_root) is not None: | ||
| return True | ||
|
|
||
| try: | ||
| dist = _md.distribution("specify-cli") | ||
| except _md.PackageNotFoundError: | ||
| return False | ||
| files = dist.files or [] | ||
| for f in files: | ||
| try: | ||
| abs_path = Path(dist.locate_file(f)).resolve() | ||
| except Exception: | ||
| continue | ||
| if _git_ancestor(abs_path) is not None: | ||
| return True | ||
| return False |
| +build.42). Rejects everything else (including bare 'latest', hash refs, | ||
| branch names, or a numeric version without the 'v' prefix). | ||
| """ | ||
| if not _TAG_REGEX.match(tag): |
Collaborator
|
Please address Copilot feedback and please resolve conflicts. You probably will have to pull in latest changes from main |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Replace the v0.7.5 reserved stub from #2316 with an actually working
specify self upgradecommand. Closes the unresolved portion of #2282.The CLI classifies the runtime via a 3-tier detection ladder, runs the appropriate installer subprocess, and verifies the result by spawning a fresh
specify --versionin a child process (the in-process module is still the pre-upgrade build).Behavior
specify self upgrade— executes immediately. No confirmation prompt. Matches thepip install -U/uv tool upgrade/npm updateconvention.--dry-run— on upgradable paths (uv tool,pipx) prints the preview block (method / current / target / installer argv) and exits 0 without launching any subprocess. On non-upgradable paths (uvx (ephemeral)/ source checkout / unsupported) emits the same path-specific guidance as a non-dry-run invocation and exits 0.--tag vX.Y.Z[suffix]— pins a specific release tag. Validated against^v\d+\.\d+\.\d+(?:[a-z0-9.+\-]*)?$; rejects barelatest, branch names, hash refs.uv tool/pipx(auto-upgrade),uvx (ephemeral)/ source-checkout / unsupported (path-specific guidance + exit 0, no installer launched).GH_TOKEN/GITHUB_TOKENscrubbed from child-process environments; never appears in installer argv.Exit codes
0--dry-run, or non-upgradable path with guidance)1--tagregex validation failure2specify --versiondoes not resolve to target)3124SPECIFY_UPGRADE_TIMEOUT_SECSis set)Files changed
src/specify_cli/__init__.py— Phase 2 helpers (_InstallMethod,_UpgradePlan,_detect_install_method,_assemble_installer_argv,_build_upgrade_plan,_run_installer,_verify_upgrade,_emit_guidance,_emit_failure,_validate_tag,_scrubbed_env, …) + replacedself_upgrade()orchestrator body. Also refreshes the now-staleselfgroup help andself_check()docstring sospecify self --help/specify self check --helpreflect the current behavior.tests/test_self_upgrade.py(new) — 44 test cases across 23 classes covering detection, argv assembly, dry-run, tag validation, token scrubbing, verification flows.tests/test_upgrade.py— removedTestSelfUpgradeStub(the stub it pinned no longer exists).README.md—Get Started → Install Specify CLI → Option 1now leads withspecify self upgradeas the recommended path; manual--forceretained as fallback.docs/upgrade.md— Quick Reference table, Part 1, Verify, Common Scenarios, and Troubleshooting all updated.docs/installation.md— Verification section gets a "Stay current" pointer.Test plan
uvx ruff check src/— cleanuv sync --extra test+uv run pytest— 2828 passed, 34 skipped on Python 3.11 / 3.12 / 3.13tests/test_self_upgrade.py+tests/test_upgrade.py— 88 passed combinedspecify self upgrade --dry-run→ preview, target=v0.8.6 (auto-resolved)specify self upgrade --dry-run --tag v0.8.0+build.42→ build-metadata tag acceptedspecify self upgrade --dry-run --tag latest→BadParameter(regex rejection)Notes for reviewers
pip install -U/uv tool upgrade/npm update)._fetch_latest_release_tag()(added in feat(cli): add specify self check and self upgrade stub #2316) is reused unchanged. No new GitHub HTTP code.re,dataclasses,enum,urllib.parse); PEP 723 deps unchanged._-prefixed; no new public API surface.Design and ripple review context
This change was developed through the full speckit workflow:
specify -> plan -> tasks -> blueprint -> implement -> ripple-scan -> ripple-resolve.Design artifacts (spec, plan, blueprint, ripple-report) were kept in
specs/002-self-upgrade-apply/during development and are not part of this PR.Ripple review surfaced 9 findings. Each was triaged as either resolved in-branch or accepted risk with explicit rationale.