Skip to content

feat(cli): add update-presets command#197

Merged
fohte merged 13 commits intomainfrom
fohte/update-presets
Mar 12, 2026
Merged

feat(cli): add update-presets command#197
fohte merged 13 commits intomainfrom
fohte/update-presets

Conversation

@fohte
Copy link
Owner

@fohte fohte commented Mar 12, 2026

Why

  • Explicitly updating remote presets required workarounds such as setting RUNOK_CACHE_TTL=0 to bypass the cache
  • Presets pinned to version tags had no way to safely upgrade to the latest compatible version

What

  • Add runok update-presets command to refresh and upgrade all remote presets at once
  • Automatically detect the latest compatible version based on version tag precision (@v1, @v1.0, @v1.0.0)
  • Display SHA changes for branch reference updates, and config file diffs for version tag upgrades

Open with Devin

fohte and others added 11 commits March 11, 2026 20:48
…sets

Remote presets referenced via `extends` are cached with a TTL (default
24h) and updated implicitly during check/exec. There was no way for
users to explicitly trigger an immediate refresh. This command scans all
config layers for remote preset references, force-fetches each one
(skipping immutable commit-SHA pins), and shows a colored unified diff
for any preset whose content changed.

- Add `UpdatePresets` variant to CLI Commands enum
- Add `update_presets` module with core logic: collect references from
  all config layers, force-fetch via GitClient, compare before/after
  content, display diff using `similar` crate
- Expose `cache`, `git_client`, `preset_remote` modules as pub for
  cross-module access; extract `resolve_preset_file_path` helper from
  `read_preset_from_dir` for raw content reading
- Add CLI doc page and update CLI overview
- Add unit tests for reference collection, immutable skip, up-to-date
  detection, fetch error handling, and deduplication

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The initial update-presets implementation only re-fetched cached content,
which was useless for tag references since tags are immutable. Users
pinning presets to semver tags (e.g., @v1.0.0) had no way to discover
and apply newer compatible versions.

- Add `ls_remote_tags` method to GitClient trait for querying remote tags
- Add semver_utils module for parsing semver tags (with v-prefix handling)
  and finding the latest compatible version within the same major version
- Track source config file path for each preset reference, enabling
  in-place config file updates via string replacement
- For semver-tagged presets: query remote tags, find latest compatible
  version, fetch it, update the extends entry in runok.yml, show diff
- For non-semver references (branches): keep existing force-refetch behavior
- Cache ls-remote results per URL to avoid redundant network calls
- Respect major version boundaries (v1.x won't upgrade to v2.x)
- Exclude pre-release versions from upgrade candidates
- Update CLI docs with version upgrade rules and examples

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
`@v1` and `@v1.0` were treated as branch names and only re-fetched
without config file updates, because `semver::Version::parse` requires
all three components. Introduce `VersionSpec` enum to distinguish
major-only (`v1`), major.minor (`v1.0`), and full semver (`v1.0.0`)
tags, each with different upgrade scopes:
- `@v1` → upgrades across major versions (e.g., `@v1` → `@v2`)
- `@v1.0` → upgrades within same major (e.g., `@v1.0` → `@v1.3`)
- `@v1.0.0` → unchanged behavior, within same major

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When a version-like tag (e.g., `v1`) has no newer version available
from remote tags, it was returning UpToDate without re-fetching. This
missed the case where `v1` is actually a mutable branch (GitHub Actions
style), not a semver tag. Now falls through to force_refetch so the
preset content is still updated.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…rades

ls-remote was only querying tags (--tags), so version-like branches
(e.g., `v1`, `v2` in GitHub Actions style) were invisible to the
upgrade logic. Now queries both tags and branches (--tags --heads) so
that `@v1` can be upgraded to `@v2` regardless of whether `v2` is a
tag or a branch on the remote.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The upgrade and no-upgrade test cases were separate functions with
duplicated setup logic. Extract shared helpers (run_upgrade_test,
run_no_upgrade_test) and use rstest #[case] to cover full semver,
major-only, major.minor, and branch-style version tags in a single
parameterized test each.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Individual test functions for skip/refetch/upgrade were hard to scan
as a whole. Consolidate into a single parameterized test
(update_single_preset_scenarios) with descriptive case names covering:
commit SHA skip, branch re-fetch, v1→v2 branch upgrade, v1→v2 tag
upgrade, mixed branches/tags, major boundary, patch vs minor priority,
major.minor upgrade, pre-release exclusion, and v-prefix matching.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
ls_remote_refs returned flat strings with no distinction between tags
and branches. Introduce RemoteRef struct with RefKind (Tag/Branch) so
callers can distinguish ref types. Test cases now use (name, T/B)
tuples, making it clear whether each remote ref is a tag or branch
(e.g., v1 branch vs v1.0.0 tag scenarios).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Branch re-fetch (Updated) was showing a full preset content diff which
was noisy and not useful. Now shows only the old/new commit SHA. For
version tag upgrades (Upgraded), the diff now shows the config file
change (e.g., runok.yml) rather than the preset's internal content,
since the config file rewrite is the actual user-visible change.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The run_with function had no tests verifying its side effects:
config file updates on upgrade, no modification on other outcomes,
and error counting. These behaviors were only implicitly covered
through update_single_preset unit tests which don't exercise the
full orchestration path.
The run_with tests were written as 6 separate functions with
duplicated setup logic. Consolidate into a single parameterized
test using RunWithScenario struct and RunWithExpected enum,
consistent with the update_single_preset_scenarios pattern.
@gemini-code-assist
Copy link

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request addresses the challenges of managing remote presets by introducing a dedicated CLI command, runok update-presets. Previously, users faced difficulties in explicitly updating remote presets, often resorting to workarounds, and lacked a safe mechanism to upgrade version-pinned presets. The new command streamlines this process by providing an automated, intelligent way to keep remote configurations up-to-date, ensuring that both branch-based and version-tagged presets can be refreshed or upgraded efficiently and with clear visibility into changes.

Highlights

  • New CLI Command: Introduced runok update-presets to automate the refresh and upgrade of remote presets.
  • Smart Version Upgrades: The command intelligently detects and upgrades version-tagged presets to the latest compatible version based on semantic versioning rules.
  • Force-Refresh for Branch References: Branch-referenced presets are now force-fetched, bypassing the cache to ensure the latest content is always used.
  • Config File Updates & Diffs: Automatically updates configuration files when a version upgrade occurs and displays a colored diff of the changes.
Changelog
  • Cargo.lock
    • Updated semver dependency version.
  • Cargo.toml
    • Added semver as a new dependency.
  • docs/src/content/docs/cli/overview.md
    • Added a new entry for the update-presets command.
  • docs/src/content/docs/cli/update-presets.md
    • Created new documentation for the runok update-presets command.
  • src/cli/mod.rs
    • Added the UpdatePresets command variant to the CLI and included its parsing test.
  • src/config/git_client.rs
    • Introduced RefKind and RemoteRef types and added ls_remote_refs functionality to the Git client, along with mock implementations for testing.
  • src/config/mod.rs
    • Modified module visibility for cache, git_client, and preset_remote to support the new update functionality.
  • src/config/preset_remote.rs
    • Exposed internal Git parameter resolution and preset file path utilities for use by the update command.
  • src/lib.rs
    • Exposed the new update_presets module.
  • src/main.rs
    • Integrated the update-presets command into the main application flow, handling its execution separately.
  • src/update_presets/mod.rs
    • Implemented the core logic for collecting, updating, and displaying diffs for remote presets, including handling different update scenarios and error reporting.
  • src/update_presets/semver_utils.rs
    • Developed utilities for parsing and comparing semantic versions to determine compatible upgrades based on tag precision.
Activity
  • No human activity has been recorded on this pull request yet.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@gemini-code-assist
Copy link

Warning

Gemini encountered an error creating the review. You can try again by commenting /gemini review.

gemini-code-assist[bot]

This comment was marked as resolved.

@gemini-code-assist
Copy link

Warning

Gemini is experiencing higher than usual traffic and was unable to create the review. Please try again in a few hours by commenting /gemini review.

…t match

String::replace replaces all occurrences of the reference string,
which could unintentionally modify comments or other parts of the
config file that happen to contain the same string.
devin-ai-integration[bot]

This comment was marked as resolved.

@codecov
Copy link

codecov bot commented Mar 12, 2026

Codecov Report

❌ Patch coverage is 74.96160% with 163 lines in your changes missing coverage. Please review.
✅ Project coverage is 89.07%. Comparing base (a470cd0) to head (e2ac0ae).
⚠️ Report is 10 commits behind head on main.

Files with missing lines Patch % Lines
src/update_presets/mod.rs 75.00% 117 Missing ⚠️
src/config/git_client.rs 24.44% 34 Missing ⚠️
src/main.rs 15.38% 11 Missing ⚠️
src/update_presets/semver_utils.rs 99.09% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #197      +/-   ##
==========================================
- Coverage   90.41%   89.07%   -1.34%     
==========================================
  Files          40       42       +2     
  Lines        7520     8163     +643     
==========================================
+ Hits         6799     7271     +472     
- Misses        721      892     +171     
Flag Coverage Δ
Linux 88.95% <74.96%> (-1.12%) ⬇️
macOS 90.45% <74.96%> (-1.64%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

devin-ai-integration[bot]

This comment was marked as resolved.

…counting

Two bugs found in code review:

1. Deduplication used only the reference string as the key, so
   the same reference appearing in both global and project config
   files would only update the first file. Changed the key to
   include the source file path.

2. upgraded_count was incremented unconditionally even when
   update_config_file failed, causing the summary to double-count
   as both "upgraded" and "error".

Also switched run_with test strings from embedded \n to indoc!.
@fohte fohte merged commit 4c57355 into main Mar 12, 2026
10 checks passed
@fohte fohte deleted the fohte/update-presets branch March 12, 2026 18:05
@fohte-bot fohte-bot bot mentioned this pull request Mar 12, 2026
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