feat: align with uv ecosystem, best-effort resolution, github action#12
Conversation
Reframe the tool as "uv lock --upgrade for your pyproject.toml specifiers" and make it feel native to uv across the CLI, output, and internals. CLI: - uv-style options: --project, --directory, -P/--upgrade-package, --all-groups, --index-url, --offline, -n/--no-cache, -q/--quiet, -v/--verbose, --color, -V/--version - add --check to fail CI when upgrades are available - keep -f/--filepath as a hidden, backwards-compatible alias Output: - uv-style status lines (Updated <name> v<old> -> v<new>, Resolved/Audited), warning:/error: prefixes, and clap-like help (bold headers, cyan flags) Internals: - parse specifiers with packaging (PEP 440/508) instead of regex; only the version token is rewritten so formatting, extras and markers are preserved - only raise lower bounds (>=, >, ~=); pinned/capped/excluded are skipped - PyPIClient now uses the PEP 691 simple JSON API, is index-aware via [[tool.uv.index]], fetches concurrently, caches, and honors --offline - run uv lock in the project directory and roll back on failure - drop the httpx monkeypatch and the regex-based exception hierarchy Tests rewritten for the new API; 100% coverage on covered modules.
- add a top-level entry-point wrapper that catches ClickException, Abort, BaseError and unexpected errors and renders them as `error: <message>` with the correct exit code, so users never see a raw Python traceback (use --verbose to re-raise unexpected errors for debugging) - give the logger a uv-style `Caused by:` chain and use it for `uv lock` failures (with rollback) and for malformed/unreadable pyproject.toml - usage errors now print uv's lowercase `error:` prefix - ignore TRY400 project-wide; the custom logger uses error(cause=...) rather than stdlib logging.exception
- read defaults from a [tool.uv-upsync] table (exclude, group, upgrade-package, all-groups, index-url) with validation; resolution precedence is CLI > config > defaults, and the index layers as --index-url > [tool.uv-upsync] > [[tool.uv.index]] > PyPI - ship .pre-commit-hooks.yaml with `uv-upsync` and `uv-upsync-check` hooks - ship a composite action.yml (inputs: args, version, working-directory) - document configuration, pre-commit and GitHub Action usage in the README and add a uv-style tagline; refresh CLAUDE.md
By default, when the full upgrade set does not lock, keep the maximal subset that resolves instead of discarding every bump. A single incompatible dependency no longer costs you the rest of the upgrades. - split parsers into plan_updates (compute, no mutation) and apply_updates (return a deep-copied document with a chosen subset applied) - __main__ tries the full set first (fast happy path), then greedily narrows to the maximal resolvable subset on failure, reporting which packages were held back; the working tree always ends in a lockable state - add --strict (today's all-or-nothing rollback, exit 2) and --no-lock (write upgrades without running uv lock) - document the resolution behavior and new flags in the README and CLAUDE.md
- handle compound specifiers like >=1.2,<2.0: raise the lower bound to the latest version that still satisfies the cap and any !=/exclusion clauses (previously multi-clause specifiers were skipped entirely) - --max-bump patch|minor|major holds back larger jumps so minors can be auto-applied while majors are left for review; held-back majors are reported in verbose output - --prerelease opts into pre-release versions (stable-only by default) - PyPIClient now returns the full version list; version selection moved to parsers.select_new_version, which has the specifier context - [tool.uv-upsync] gains max-bump and prerelease keys
- --format text|json|markdown renders the upgrade summary; json/markdown go to stdout only (status lines are suppressed) for clean scripting - new report module renders the applied and held-back updates - the GitHub Action captures the summary and exposes it as a `summary` output, so `--format markdown` produces a ready-made pull request body - document --format and the action output in the README and CLAUDE.md
- --resolve: when a dependency does not lock at its latest version, bisect its eligible versions for the highest one that does, instead of holding it back entirely (e.g. numpy 2.4.6 -> 2.0.2 under requires-python >=3.9) - held-back messages now name the conflicting peer when uv's resolver error mentions another project dependency (e.g. "conflicts with sphinx") - parsers gains eligible_versions, at_version, find_conflicts and collect_all_names; Update carries the original text/operator so it can be rewritten to any version - [tool.uv-upsync] gains a resolve key - add a VHS demo (assets/demo.tape + sample project), a `just demo` recipe, and a workflow that renders and commits assets/demo.gif; reference it in the README
Welcome to Codecov 🎉Once you merge this PR into your default branch, you're all set! Codecov will compare coverage reports and display results in all future pull requests. Thanks for integrating Codecov - We've got you covered ☂️ |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a3afb2df8c
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| shell: bash | ||
| working-directory: ${{ inputs.working-directory }} | ||
| run: | | ||
| summary="$(uvx ${{ inputs.version != '' && format('uv-upsync@{0}', inputs.version) || 'uv-upsync' }} ${{ inputs.args }})" |
There was a problem hiding this comment.
Preserve action summaries on check failures
When users run the composite action with args: --check --format markdown, uv-upsync intentionally exits 1 after printing the summary, but this shell: bash step is executed by GitHub with -e, so the failing command substitution aborts the step before status=$? and the $GITHUB_OUTPUT write run. That makes the documented summary output unavailable exactly for the CI-gate case; disable errexit around this command or otherwise capture the status without letting the assignment terminate the step.
Useful? React with 👍 / 👎.
| ) -> exceptions.UVCommandError | None: | ||
| _write(pyproject, group, updates, filepath, all_groups=all_groups) | ||
| try: | ||
| uv.lock(offline=offline, cwd=filepath.parent) |
There was a problem hiding this comment.
When --index-url is supplied on the uv-upsync CLI, candidate versions are fetched from that index, but the subsequent lock is still invoked as plain uv lock in the project directory. I checked uv lock --help, and --index-url <INDEX_URL>/--default-index are the flags that change the package index for locking; without threading the selected index through here, a private-only version can be written and then resolved against PyPI/project defaults, causing best-effort to hold it back or strict mode to roll back even though it exists on the requested index.
Useful? React with 👍 / 👎.
| if indexes: | ||
| return str(indexes[0].get("url")) |
There was a problem hiding this comment.
Don't treat extra indexes as the default index
For projects that declare [[tool.uv.index]] entries without default = true, this returns the first extra index as the sole Simple API base for every dependency. uv lock --help describes --index URLs as being used in addition to the default PyPI index, so a common config such as a PyTorch or internal supplemental index will make uv-upsync query that supplemental index for public packages like click or pytest and incorrectly report no upgrades, even though uv itself would still resolve them from PyPI.
Useful? React with 👍 / 👎.
Pull Request Checklist
Summary
uv-upsyncas a uv-native tool: uv-style CLI flags/output, PEP 691 simple index (index-aware via[[tool.uv.index]]),packaging-based specifier parsing, and auv lockround-trip with rollback--resolvebisects for the highest version that locks;--strictand--no-lockopt-outs--max-bump patch|minor|major,--prerelease,--format text|json|markdown,[tool.uv-upsync]config, pre-commit hook, composite GitHub Action exposing a MarkdownsummaryoutputChecklist