upd-cli 0.1.2__tar.gz → 0.1.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {upd_cli-0.1.2 → upd_cli-0.1.4}/CHANGELOG.md +23 -0
- {upd_cli-0.1.2 → upd_cli-0.1.4}/Cargo.lock +1 -1
- {upd_cli-0.1.2 → upd_cli-0.1.4}/Cargo.toml +1 -1
- {upd_cli-0.1.2 → upd_cli-0.1.4}/PKG-INFO +38 -3
- {upd_cli-0.1.2 → upd_cli-0.1.4}/README.md +37 -2
- {upd_cli-0.1.2 → upd_cli-0.1.4}/src/audit/mod.rs +38 -39
- {upd_cli-0.1.2 → upd_cli-0.1.4}/src/cli.rs +35 -2
- {upd_cli-0.1.2 → upd_cli-0.1.4}/src/cooldown.rs +1 -40
- {upd_cli-0.1.2 → upd_cli-0.1.4}/src/lib.rs +3 -1
- {upd_cli-0.1.2 → upd_cli-0.1.4}/src/lockfile.rs +174 -71
- {upd_cli-0.1.2 → upd_cli-0.1.4}/src/main.rs +76 -12
- {upd_cli-0.1.2 → upd_cli-0.1.4}/src/updater/mod.rs +372 -79
- upd_cli-0.1.4/src/updater/npm_range.rs +257 -0
- {upd_cli-0.1.2 → upd_cli-0.1.4}/src/updater/package_json.rs +432 -17
- upd_cli-0.1.4/src/version/compare.rs +79 -0
- {upd_cli-0.1.2 → upd_cli-0.1.4}/src/version/mod.rs +1 -0
- upd_cli-0.1.4/tests/discovery_no_ignore.rs +114 -0
- {upd_cli-0.1.2 → upd_cli-0.1.4}/.mise.toml +0 -0
- {upd_cli-0.1.2 → upd_cli-0.1.4}/.pre-commit-config.yaml +0 -0
- {upd_cli-0.1.2 → upd_cli-0.1.4}/.pre-commit-hooks.yaml +0 -0
- {upd_cli-0.1.2 → upd_cli-0.1.4}/.rumdl.toml +0 -0
- {upd_cli-0.1.2 → upd_cli-0.1.4}/LICENSE +0 -0
- {upd_cli-0.1.2 → upd_cli-0.1.4}/Makefile +0 -0
- {upd_cli-0.1.2 → upd_cli-0.1.4}/assets/logo-wide.svg +0 -0
- {upd_cli-0.1.2 → upd_cli-0.1.4}/assets/logo.svg +0 -0
- {upd_cli-0.1.2 → upd_cli-0.1.4}/pyproject.toml +0 -0
- {upd_cli-0.1.2 → upd_cli-0.1.4}/python/upd_cli/__init__.py +0 -0
- {upd_cli-0.1.2 → upd_cli-0.1.4}/python/upd_cli/__main__.py +0 -0
- {upd_cli-0.1.2 → upd_cli-0.1.4}/python/upd_cli/py.typed +0 -0
- {upd_cli-0.1.2 → upd_cli-0.1.4}/rust-toolchain.toml +0 -0
- {upd_cli-0.1.2 → upd_cli-0.1.4}/src/align.rs +0 -0
- {upd_cli-0.1.2 → upd_cli-0.1.4}/src/audit/cache.rs +0 -0
- {upd_cli-0.1.2 → upd_cli-0.1.4}/src/audit/cvss.rs +0 -0
- {upd_cli-0.1.2 → upd_cli-0.1.4}/src/cache.rs +0 -0
- {upd_cli-0.1.2 → upd_cli-0.1.4}/src/config.rs +0 -0
- {upd_cli-0.1.2 → upd_cli-0.1.4}/src/interactive.rs +0 -0
- {upd_cli-0.1.2 → upd_cli-0.1.4}/src/output.rs +0 -0
- {upd_cli-0.1.2 → upd_cli-0.1.4}/src/registry/crates_io.rs +0 -0
- {upd_cli-0.1.2 → upd_cli-0.1.4}/src/registry/github_releases.rs +0 -0
- {upd_cli-0.1.2 → upd_cli-0.1.4}/src/registry/go_proxy.rs +0 -0
- {upd_cli-0.1.2 → upd_cli-0.1.4}/src/registry/mock.rs +0 -0
- {upd_cli-0.1.2 → upd_cli-0.1.4}/src/registry/mod.rs +0 -0
- {upd_cli-0.1.2 → upd_cli-0.1.4}/src/registry/npm.rs +0 -0
- {upd_cli-0.1.2 → upd_cli-0.1.4}/src/registry/nuget.rs +0 -0
- {upd_cli-0.1.2 → upd_cli-0.1.4}/src/registry/pypi.rs +0 -0
- {upd_cli-0.1.2 → upd_cli-0.1.4}/src/registry/rubygems.rs +0 -0
- {upd_cli-0.1.2 → upd_cli-0.1.4}/src/registry/terraform.rs +0 -0
- {upd_cli-0.1.2 → upd_cli-0.1.4}/src/registry/utils.rs +0 -0
- {upd_cli-0.1.2 → upd_cli-0.1.4}/src/updater/cargo_toml.rs +0 -0
- {upd_cli-0.1.2 → upd_cli-0.1.4}/src/updater/csproj.rs +0 -0
- {upd_cli-0.1.2 → upd_cli-0.1.4}/src/updater/gemfile.rs +0 -0
- {upd_cli-0.1.2 → upd_cli-0.1.4}/src/updater/github_actions.rs +0 -0
- {upd_cli-0.1.2 → upd_cli-0.1.4}/src/updater/go_mod.rs +0 -0
- {upd_cli-0.1.2 → upd_cli-0.1.4}/src/updater/mise.rs +0 -0
- {upd_cli-0.1.2 → upd_cli-0.1.4}/src/updater/pre_commit.rs +0 -0
- {upd_cli-0.1.2 → upd_cli-0.1.4}/src/updater/pyproject.rs +0 -0
- {upd_cli-0.1.2 → upd_cli-0.1.4}/src/updater/requirements.rs +0 -0
- {upd_cli-0.1.2 → upd_cli-0.1.4}/src/updater/terraform.rs +0 -0
- {upd_cli-0.1.2 → upd_cli-0.1.4}/src/version/pep440.rs +0 -0
- {upd_cli-0.1.2 → upd_cli-0.1.4}/src/version/semver_util.rs +0 -0
- {upd_cli-0.1.2 → upd_cli-0.1.4}/src/version/tag.rs +0 -0
- {upd_cli-0.1.2 → upd_cli-0.1.4}/tests/audit_offline.rs +0 -0
- {upd_cli-0.1.2 → upd_cli-0.1.4}/tests/audit_sarif.rs +0 -0
- {upd_cli-0.1.2 → upd_cli-0.1.4}/tests/audit_severity.rs +0 -0
- {upd_cli-0.1.2 → upd_cli-0.1.4}/tests/bump_filter.rs +0 -0
- {upd_cli-0.1.2 → upd_cli-0.1.4}/tests/cooldown_e2e.rs +0 -0
- {upd_cli-0.1.2 → upd_cli-0.1.4}/tests/exit_codes.rs +0 -0
- {upd_cli-0.1.2 → upd_cli-0.1.4}/tests/fix_audit.rs +0 -0
- {upd_cli-0.1.2 → upd_cli-0.1.4}/tests/format_json.rs +0 -0
- {upd_cli-0.1.2 → upd_cli-0.1.4}/tests/help_text.rs +0 -0
- {upd_cli-0.1.2 → upd_cli-0.1.4}/tests/interactive_tty.rs +0 -0
- {upd_cli-0.1.2 → upd_cli-0.1.4}/tests/invalid_positional.rs +0 -0
- {upd_cli-0.1.2 → upd_cli-0.1.4}/tests/no_args_scope.rs +0 -0
- {upd_cli-0.1.2 → upd_cli-0.1.4}/tests/output_streams.rs +0 -0
- {upd_cli-0.1.2 → upd_cli-0.1.4}/tests/package_filter.rs +0 -0
- {upd_cli-0.1.2 → upd_cli-0.1.4}/vership.toml +0 -0
|
@@ -12,6 +12,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
## [0.1.4](https://github.com/rvben/upd/compare/v0.1.3...v0.1.4) - 2026-04-28
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
|
|
21
|
+
- **discovery**: respect .gitignore for hidden ecosystem files and add --no-ignore ([bd44269](https://github.com/rvben/upd/commit/bd442692e4e7c5ab96e1163128ac8f274a771f30))
|
|
22
|
+
|
|
23
|
+
## [0.1.3](https://github.com/rvben/upd/compare/v0.1.2...v0.1.3) - 2026-04-25
|
|
24
|
+
|
|
25
|
+
### Added
|
|
26
|
+
|
|
27
|
+
- **lock**: scope lockfile regeneration to the packages upd actually changed ([6b6cfa6](https://github.com/rvben/upd/commit/6b6cfa6fe3e8e8d787e78c7afea24883ebe67833))
|
|
28
|
+
- **npm**: classify and rewrite comparator-range specs ([a60979a](https://github.com/rvben/upd/commit/a60979acd7edb7eb6adfac554a590412a6cf8271))
|
|
29
|
+
|
|
30
|
+
### Fixed
|
|
31
|
+
|
|
32
|
+
- **lock**: include config pins in targeted regenerate and update CLI help ([207fdf9](https://github.com/rvben/upd/commit/207fdf94af95c74865f828f258ec9cfa61d22085))
|
|
33
|
+
- **npm**: preserve upper bound when pinning comparator-range specs ([fb7c863](https://github.com/rvben/upd/commit/fb7c863de9f07201af310ab13a59696d17fcb766))
|
|
34
|
+
- **npm**: apply config policy and cooldown to comparator-range updates ([be095dd](https://github.com/rvben/upd/commit/be095dd30443dd547fb6913dae551cbd530cb019))
|
|
35
|
+
- **npm**: update comparator-range specs via constraint-aware resolution ([4481b8b](https://github.com/rvben/upd/commit/4481b8bad6a02c6f761cf489a547b5546099e871))
|
|
36
|
+
- **audit**: order fix versions numerically, not lexicographically ([069eb1d](https://github.com/rvben/upd/commit/069eb1dc8771640376957339e40ada7b60a82b0a))
|
|
37
|
+
|
|
15
38
|
## [0.1.2](https://github.com/rvben/upd/compare/v0.1.1...v0.1.2) - 2026-04-24
|
|
16
39
|
|
|
17
40
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: upd-cli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.4
|
|
4
4
|
Classifier: Development Status :: 4 - Beta
|
|
5
5
|
Classifier: Environment :: Console
|
|
6
6
|
Classifier: Intended Audience :: Developers
|
|
@@ -46,7 +46,10 @@ pipx run --spec upd-cli upd --apply
|
|
|
46
46
|
|
|
47
47
|
- **Multi-ecosystem**: Python, Node.js, Rust, Go, Ruby, .NET, Terraform, GitHub Actions, pre-commit, Mise/asdf
|
|
48
48
|
- **Fast**: Parallel registry requests for all dependencies
|
|
49
|
-
- **Constraint-aware**: Respects
|
|
49
|
+
- **Constraint-aware**: Respects `>=2.0,<3` (Python), `~> 7.1` (Ruby), and `^2.0.0` / `~2.0.0` (npm, Cargo).
|
|
50
|
+
For npm, comparator ranges such as `">=1.0.0 <2.0.0"` are rewritten with a **bump strategy**: the lower
|
|
51
|
+
bound moves to the highest version satisfying the constraint, preserving the upper bound. Hyphen
|
|
52
|
+
(`"1 - 2"`) and OR (`"^1 || ^2"`) ranges are reported as warnings and left untouched.
|
|
50
53
|
- **Smart caching**: 24-hour version cache for faster subsequent runs
|
|
51
54
|
- **Update filters**: Filter by bump level with `--only-bump <major|minor|patch>` (repeatable) or cap with `--max-bump`
|
|
52
55
|
- **Interactive mode**: Approve updates individually with `-i`
|
|
@@ -54,7 +57,11 @@ pipx run --spec upd-cli upd --apply
|
|
|
54
57
|
- **Major warnings**: Highlights breaking changes with `(MAJOR)`
|
|
55
58
|
- **Format-preserving**: Keeps formatting, comments, and structure
|
|
56
59
|
- **Pre-release aware**: Updates pre-releases to newer pre-releases
|
|
57
|
-
- **Gitignore-aware**:
|
|
60
|
+
- **Gitignore-aware**: Honors `.gitignore`, `.git/info/exclude`, and the global
|
|
61
|
+
gitignore — even outside a git repo. Hidden directories are pruned by default;
|
|
62
|
+
`upd` only opens the dotfiles it actually updates (`.github/workflows`,
|
|
63
|
+
`.pre-commit-config.yaml`, `.mise.toml`, `.tool-versions`). Use `--no-ignore`
|
|
64
|
+
to walk every file regardless.
|
|
58
65
|
- **Version alignment**: Align package versions across multiple files
|
|
59
66
|
- **Security auditing**: Check dependencies for known vulnerabilities via OSV
|
|
60
67
|
- **Config file support**: Ignore or pin packages via `.updrc.toml`
|
|
@@ -758,6 +765,7 @@ Global flags (accepted on every subcommand):
|
|
|
758
765
|
| `--full-precision` | | Output full versions |
|
|
759
766
|
| `--no-cache` | | Disable version cache |
|
|
760
767
|
| `--no-color` | | Disable colored output |
|
|
768
|
+
| `--no-ignore` | | Disable `.gitignore` filtering during discovery |
|
|
761
769
|
| `--lock` | | Regenerate lockfiles after updates |
|
|
762
770
|
| `--config <FILE>` | `-c` | Use a specific config file |
|
|
763
771
|
| `--show-config` | | Print effective configuration and exit |
|
|
@@ -767,6 +775,33 @@ Global flags (accepted on every subcommand):
|
|
|
767
775
|
|
|
768
776
|
Subcommands: `update` (default), `align`, `audit`, `clean-cache`, `self-update`.
|
|
769
777
|
|
|
778
|
+
#### Commands run by `--lock`
|
|
779
|
+
|
|
780
|
+
`upd --lock` runs the narrowest per-ecosystem refresh command that
|
|
781
|
+
updates only the packages `upd` just rewrote. Targeted forms are used
|
|
782
|
+
wherever the package manager supports them; targeting falls back to
|
|
783
|
+
`--lockfile-only` flags where no per-package form exists; otherwise
|
|
784
|
+
the manifest-wide refresh command is used.
|
|
785
|
+
|
|
786
|
+
| Ecosystem | Lockfile | Command |
|
|
787
|
+
|-----------|--------------------------|------------------------------------------------|
|
|
788
|
+
| Python | `poetry.lock` | `poetry lock --no-update` |
|
|
789
|
+
| Python | `uv.lock` | `uv lock` |
|
|
790
|
+
| Node | `package-lock.json` | `npm install --package-lock-only` |
|
|
791
|
+
| Node | `yarn.lock` | `yarn install --mode update-lockfile` (Yarn 2+)|
|
|
792
|
+
| Node | `pnpm-lock.yaml` | `pnpm install --lockfile-only` |
|
|
793
|
+
| Node | `bun.lockb` | `bun install` |
|
|
794
|
+
| Rust | `Cargo.lock` | `cargo update -p <changed> -p <changed> …` |
|
|
795
|
+
| Go | `go.sum` | `go mod tidy` (no targeted form) |
|
|
796
|
+
| Ruby | `Gemfile.lock` | `bundle lock --update <changed> …` |
|
|
797
|
+
| .NET | `packages.lock.json` | `dotnet restore` (no targeted form) |
|
|
798
|
+
| Terraform | `.terraform.lock.hcl` | `terraform providers lock` (no targeted form) |
|
|
799
|
+
|
|
800
|
+
Manifests whose `upd` pass produced zero changes have their lockfile
|
|
801
|
+
refresh skipped entirely. A directory where only config pins were
|
|
802
|
+
applied is still refreshed, and the changed-package list includes
|
|
803
|
+
those pinned packages so `cargo update -p <pkg>` / `bundle lock --update <pkg>` stay scoped.
|
|
804
|
+
|
|
770
805
|
Stable `audit`-specific flags:
|
|
771
806
|
|
|
772
807
|
| Flag | Purpose |
|
|
@@ -23,7 +23,10 @@ pipx run --spec upd-cli upd --apply
|
|
|
23
23
|
|
|
24
24
|
- **Multi-ecosystem**: Python, Node.js, Rust, Go, Ruby, .NET, Terraform, GitHub Actions, pre-commit, Mise/asdf
|
|
25
25
|
- **Fast**: Parallel registry requests for all dependencies
|
|
26
|
-
- **Constraint-aware**: Respects
|
|
26
|
+
- **Constraint-aware**: Respects `>=2.0,<3` (Python), `~> 7.1` (Ruby), and `^2.0.0` / `~2.0.0` (npm, Cargo).
|
|
27
|
+
For npm, comparator ranges such as `">=1.0.0 <2.0.0"` are rewritten with a **bump strategy**: the lower
|
|
28
|
+
bound moves to the highest version satisfying the constraint, preserving the upper bound. Hyphen
|
|
29
|
+
(`"1 - 2"`) and OR (`"^1 || ^2"`) ranges are reported as warnings and left untouched.
|
|
27
30
|
- **Smart caching**: 24-hour version cache for faster subsequent runs
|
|
28
31
|
- **Update filters**: Filter by bump level with `--only-bump <major|minor|patch>` (repeatable) or cap with `--max-bump`
|
|
29
32
|
- **Interactive mode**: Approve updates individually with `-i`
|
|
@@ -31,7 +34,11 @@ pipx run --spec upd-cli upd --apply
|
|
|
31
34
|
- **Major warnings**: Highlights breaking changes with `(MAJOR)`
|
|
32
35
|
- **Format-preserving**: Keeps formatting, comments, and structure
|
|
33
36
|
- **Pre-release aware**: Updates pre-releases to newer pre-releases
|
|
34
|
-
- **Gitignore-aware**:
|
|
37
|
+
- **Gitignore-aware**: Honors `.gitignore`, `.git/info/exclude`, and the global
|
|
38
|
+
gitignore — even outside a git repo. Hidden directories are pruned by default;
|
|
39
|
+
`upd` only opens the dotfiles it actually updates (`.github/workflows`,
|
|
40
|
+
`.pre-commit-config.yaml`, `.mise.toml`, `.tool-versions`). Use `--no-ignore`
|
|
41
|
+
to walk every file regardless.
|
|
35
42
|
- **Version alignment**: Align package versions across multiple files
|
|
36
43
|
- **Security auditing**: Check dependencies for known vulnerabilities via OSV
|
|
37
44
|
- **Config file support**: Ignore or pin packages via `.updrc.toml`
|
|
@@ -735,6 +742,7 @@ Global flags (accepted on every subcommand):
|
|
|
735
742
|
| `--full-precision` | | Output full versions |
|
|
736
743
|
| `--no-cache` | | Disable version cache |
|
|
737
744
|
| `--no-color` | | Disable colored output |
|
|
745
|
+
| `--no-ignore` | | Disable `.gitignore` filtering during discovery |
|
|
738
746
|
| `--lock` | | Regenerate lockfiles after updates |
|
|
739
747
|
| `--config <FILE>` | `-c` | Use a specific config file |
|
|
740
748
|
| `--show-config` | | Print effective configuration and exit |
|
|
@@ -744,6 +752,33 @@ Global flags (accepted on every subcommand):
|
|
|
744
752
|
|
|
745
753
|
Subcommands: `update` (default), `align`, `audit`, `clean-cache`, `self-update`.
|
|
746
754
|
|
|
755
|
+
#### Commands run by `--lock`
|
|
756
|
+
|
|
757
|
+
`upd --lock` runs the narrowest per-ecosystem refresh command that
|
|
758
|
+
updates only the packages `upd` just rewrote. Targeted forms are used
|
|
759
|
+
wherever the package manager supports them; targeting falls back to
|
|
760
|
+
`--lockfile-only` flags where no per-package form exists; otherwise
|
|
761
|
+
the manifest-wide refresh command is used.
|
|
762
|
+
|
|
763
|
+
| Ecosystem | Lockfile | Command |
|
|
764
|
+
|-----------|--------------------------|------------------------------------------------|
|
|
765
|
+
| Python | `poetry.lock` | `poetry lock --no-update` |
|
|
766
|
+
| Python | `uv.lock` | `uv lock` |
|
|
767
|
+
| Node | `package-lock.json` | `npm install --package-lock-only` |
|
|
768
|
+
| Node | `yarn.lock` | `yarn install --mode update-lockfile` (Yarn 2+)|
|
|
769
|
+
| Node | `pnpm-lock.yaml` | `pnpm install --lockfile-only` |
|
|
770
|
+
| Node | `bun.lockb` | `bun install` |
|
|
771
|
+
| Rust | `Cargo.lock` | `cargo update -p <changed> -p <changed> …` |
|
|
772
|
+
| Go | `go.sum` | `go mod tidy` (no targeted form) |
|
|
773
|
+
| Ruby | `Gemfile.lock` | `bundle lock --update <changed> …` |
|
|
774
|
+
| .NET | `packages.lock.json` | `dotnet restore` (no targeted form) |
|
|
775
|
+
| Terraform | `.terraform.lock.hcl` | `terraform providers lock` (no targeted form) |
|
|
776
|
+
|
|
777
|
+
Manifests whose `upd` pass produced zero changes have their lockfile
|
|
778
|
+
refresh skipped entirely. A directory where only config pins were
|
|
779
|
+
applied is still refreshed, and the changed-package list includes
|
|
780
|
+
those pinned packages so `cargo update -p <pkg>` / `bundle lock --update <pkg>` stay scoped.
|
|
781
|
+
|
|
747
782
|
Stable `audit`-specific flags:
|
|
748
783
|
|
|
749
784
|
| Flag | Purpose |
|
|
@@ -134,7 +134,7 @@ pub fn compute_fix_plan(audit: &AuditResult) -> (HashMap<String, String>, Vec<(S
|
|
|
134
134
|
.vulnerabilities
|
|
135
135
|
.iter()
|
|
136
136
|
.filter_map(|v| v.fixed_version.as_deref())
|
|
137
|
-
.max_by(|a, b|
|
|
137
|
+
.max_by(|a, b| crate::version::compare::compare_versions(a, b));
|
|
138
138
|
|
|
139
139
|
if let Some(version) = max_fixed {
|
|
140
140
|
fixable.insert(name.clone(), version.to_string());
|
|
@@ -144,44 +144,6 @@ pub fn compute_fix_plan(audit: &AuditResult) -> (HashMap<String, String>, Vec<(S
|
|
|
144
144
|
(fixable, unfixable)
|
|
145
145
|
}
|
|
146
146
|
|
|
147
|
-
/// Compare two version strings for ordering, preferring semver but falling back
|
|
148
|
-
/// to lexicographic comparison for non-semver ecosystems.
|
|
149
|
-
fn compare_fix_versions(a: &str, b: &str) -> std::cmp::Ordering {
|
|
150
|
-
match (semver_parse(a), semver_parse(b)) {
|
|
151
|
-
(Some(va), Some(vb)) => va.cmp(&vb),
|
|
152
|
-
_ => a.cmp(b),
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
/// Parse a version string as semver, accepting an optional leading `v`.
|
|
157
|
-
///
|
|
158
|
-
/// Returns `(major, minor, patch, is_stable)` where `is_stable` is 1 for stable
|
|
159
|
-
/// releases and 0 for pre-releases (e.g. `2.0.0-rc1`). This ensures stable
|
|
160
|
-
/// versions beat pre-releases when all numeric components are equal.
|
|
161
|
-
fn semver_parse(v: &str) -> Option<(u64, u64, u64, u8)> {
|
|
162
|
-
let v = v.trim_start_matches('v');
|
|
163
|
-
let parts: Vec<&str> = v.split('.').collect();
|
|
164
|
-
if parts.len() < 2 {
|
|
165
|
-
return None;
|
|
166
|
-
}
|
|
167
|
-
let major: u64 = parts[0].parse().ok()?;
|
|
168
|
-
let minor: u64 = parts[1].parse().ok()?;
|
|
169
|
-
// Patch may carry a pre-release or build suffix (e.g. "0-rc1", "3+build1").
|
|
170
|
-
// Take only the leading digit run so "0-rc1" → patch=0, rest="-rc1".
|
|
171
|
-
let patch_part = parts.get(2).copied().unwrap_or("0");
|
|
172
|
-
let (patch_digits, rest) = patch_part
|
|
173
|
-
.split_once(|c: char| !c.is_ascii_digit())
|
|
174
|
-
.unwrap_or((patch_part, ""));
|
|
175
|
-
let patch: u64 = if patch_digits.is_empty() {
|
|
176
|
-
0
|
|
177
|
-
} else {
|
|
178
|
-
patch_digits.parse().ok()?
|
|
179
|
-
};
|
|
180
|
-
// Stability flag: 1 = stable, 0 = pre-release. Stable wins ties.
|
|
181
|
-
let is_stable: u8 = if rest.is_empty() { 1 } else { 0 };
|
|
182
|
-
Some((major, minor, patch, is_stable))
|
|
183
|
-
}
|
|
184
|
-
|
|
185
147
|
/// Return a sort key for a severity string such that Critical sorts first.
|
|
186
148
|
///
|
|
187
149
|
/// Lower numeric values sort earlier, so Critical = 0, Unknown = 5.
|
|
@@ -858,6 +820,43 @@ mod tests {
|
|
|
858
820
|
assert_eq!(fixable.get("pkg").map(|s| s.as_str()), Some("2.10.0"));
|
|
859
821
|
}
|
|
860
822
|
|
|
823
|
+
#[test]
|
|
824
|
+
fn test_compute_fix_plan_picks_numerically_highest_non_semver_fix() {
|
|
825
|
+
// Regression test for 4-segment versions (e.g. Ruby or multi-segment tags)
|
|
826
|
+
// where the legacy `semver_parse` dropped the 4th segment, making
|
|
827
|
+
// `1.0.0.10` and `1.0.0.9` compare equal. `max_by` then returned the
|
|
828
|
+
// *last* element, so the answer depended on fix_version ordering.
|
|
829
|
+
//
|
|
830
|
+
// The correct "minimum safe fix" must order by full numeric value.
|
|
831
|
+
let mut audit = AuditResult::default();
|
|
832
|
+
audit.vulnerable.push(PackageAuditResult {
|
|
833
|
+
package: Package {
|
|
834
|
+
name: "pkg".to_string(),
|
|
835
|
+
version: "1.0.0.1".to_string(),
|
|
836
|
+
ecosystem: Ecosystem::RubyGems,
|
|
837
|
+
},
|
|
838
|
+
vulnerabilities: vec![
|
|
839
|
+
Vulnerability {
|
|
840
|
+
id: "CVE-A".to_string(),
|
|
841
|
+
summary: None,
|
|
842
|
+
severity: None,
|
|
843
|
+
url: None,
|
|
844
|
+
fixed_version: Some("1.0.0.10".to_string()),
|
|
845
|
+
},
|
|
846
|
+
Vulnerability {
|
|
847
|
+
id: "CVE-B".to_string(),
|
|
848
|
+
summary: None,
|
|
849
|
+
severity: None,
|
|
850
|
+
url: None,
|
|
851
|
+
fixed_version: Some("1.0.0.9".to_string()),
|
|
852
|
+
},
|
|
853
|
+
],
|
|
854
|
+
});
|
|
855
|
+
|
|
856
|
+
let (fixable, _) = compute_fix_plan(&audit);
|
|
857
|
+
assert_eq!(fixable.get("pkg").map(String::as_str), Some("1.0.0.10"));
|
|
858
|
+
}
|
|
859
|
+
|
|
861
860
|
// ─── check_packages_cached unit tests ────────────────────────────────────
|
|
862
861
|
|
|
863
862
|
fn sample_package(name: &str) -> Package {
|
|
@@ -130,8 +130,9 @@ pub struct Cli {
|
|
|
130
130
|
|
|
131
131
|
/// Regenerate lockfiles after updating.
|
|
132
132
|
///
|
|
133
|
-
/// Runs the
|
|
134
|
-
/// `
|
|
133
|
+
/// Runs the narrowest per-ecosystem refresh command that updates only the
|
|
134
|
+
/// packages `upd` just rewrote (e.g. `cargo update -p <pkg>`,
|
|
135
|
+
/// `bundle lock --update <pkg>`, `npm install --package-lock-only`).
|
|
135
136
|
#[arg(long, global = true)]
|
|
136
137
|
pub lock: bool,
|
|
137
138
|
|
|
@@ -182,6 +183,15 @@ pub struct Cli {
|
|
|
182
183
|
value_delimiter = ','
|
|
183
184
|
)]
|
|
184
185
|
pub packages: Vec<String>,
|
|
186
|
+
|
|
187
|
+
/// Disable .gitignore filtering and walk every dependency file in the tree.
|
|
188
|
+
///
|
|
189
|
+
/// By default, `upd` honors `.gitignore`, `.git/info/exclude`, and the
|
|
190
|
+
/// global gitignore when discovering dependency files. Pass `--no-ignore`
|
|
191
|
+
/// to scan files git would ignore. Equivalent to `rg --no-ignore`.
|
|
192
|
+
/// Explicit file paths are always processed regardless of this flag.
|
|
193
|
+
#[arg(long = "no-ignore", global = true)]
|
|
194
|
+
pub no_ignore: bool,
|
|
185
195
|
}
|
|
186
196
|
|
|
187
197
|
#[derive(Subcommand)]
|
|
@@ -841,4 +851,27 @@ mod tests {
|
|
|
841
851
|
assert_eq!(cli.min_age.as_deref(), Some("14d"));
|
|
842
852
|
assert!(matches!(cli.command, Some(Command::Update { .. })));
|
|
843
853
|
}
|
|
854
|
+
|
|
855
|
+
#[test]
|
|
856
|
+
fn test_cli_no_ignore_default_false() {
|
|
857
|
+
let cli = Cli::try_parse_from(["upd"]).unwrap();
|
|
858
|
+
assert!(!cli.no_ignore);
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
#[test]
|
|
862
|
+
fn test_cli_no_ignore_parses() {
|
|
863
|
+
let cli = Cli::try_parse_from(["upd", "--no-ignore"]).unwrap();
|
|
864
|
+
assert!(cli.no_ignore);
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
#[test]
|
|
868
|
+
fn test_cli_no_ignore_is_global_across_subcommands() {
|
|
869
|
+
let cli = Cli::try_parse_from(["upd", "audit", "--no-ignore"]).unwrap();
|
|
870
|
+
assert!(cli.no_ignore);
|
|
871
|
+
assert!(matches!(cli.command, Some(Command::Audit { .. })));
|
|
872
|
+
|
|
873
|
+
let cli = Cli::try_parse_from(["upd", "align", "--no-ignore"]).unwrap();
|
|
874
|
+
assert!(cli.no_ignore);
|
|
875
|
+
assert!(matches!(cli.command, Some(Command::Align { .. })));
|
|
876
|
+
}
|
|
844
877
|
}
|
|
@@ -6,6 +6,7 @@ use anyhow::{Result, anyhow};
|
|
|
6
6
|
use chrono::{DateTime, Duration, Utc};
|
|
7
7
|
|
|
8
8
|
use crate::registry::VersionMeta;
|
|
9
|
+
use crate::version::compare::compare_versions;
|
|
9
10
|
|
|
10
11
|
/// Parse a cooldown duration string.
|
|
11
12
|
///
|
|
@@ -238,46 +239,6 @@ pub fn select(
|
|
|
238
239
|
}
|
|
239
240
|
}
|
|
240
241
|
|
|
241
|
-
/// Version comparison: try semver first, fall back to numeric-aware segment
|
|
242
|
-
/// comparison.
|
|
243
|
-
///
|
|
244
|
-
/// The fallback splits on `.` and `-` and compares integer segments as numbers
|
|
245
|
-
/// (so `1.10 > 1.9` and `v0.10.0.0 > v0.9.0.0`). Lexicographic string compare
|
|
246
|
-
/// would get those wrong, which breaks selection across non-strict-semver
|
|
247
|
-
/// ecosystems like PyPI and multi-segment GitHub tags.
|
|
248
|
-
fn compare_versions(a: &str, b: &str) -> std::cmp::Ordering {
|
|
249
|
-
let stripped_a = a.strip_prefix('v').unwrap_or(a);
|
|
250
|
-
let stripped_b = b.strip_prefix('v').unwrap_or(b);
|
|
251
|
-
if let (Ok(va), Ok(vb)) = (
|
|
252
|
-
semver::Version::parse(stripped_a),
|
|
253
|
-
semver::Version::parse(stripped_b),
|
|
254
|
-
) {
|
|
255
|
-
return va.cmp(&vb);
|
|
256
|
-
}
|
|
257
|
-
compare_loose(stripped_a, stripped_b)
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
fn compare_loose(a: &str, b: &str) -> std::cmp::Ordering {
|
|
261
|
-
let mut a_parts = a.split(['.', '-']);
|
|
262
|
-
let mut b_parts = b.split(['.', '-']);
|
|
263
|
-
loop {
|
|
264
|
-
match (a_parts.next(), b_parts.next()) {
|
|
265
|
-
(None, None) => return std::cmp::Ordering::Equal,
|
|
266
|
-
(None, Some(_)) => return std::cmp::Ordering::Less,
|
|
267
|
-
(Some(_), None) => return std::cmp::Ordering::Greater,
|
|
268
|
-
(Some(x), Some(y)) => {
|
|
269
|
-
let ord = match (x.parse::<u64>(), y.parse::<u64>()) {
|
|
270
|
-
(Ok(nx), Ok(ny)) => nx.cmp(&ny),
|
|
271
|
-
_ => x.cmp(y),
|
|
272
|
-
};
|
|
273
|
-
if ord != std::cmp::Ordering::Equal {
|
|
274
|
-
return ord;
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
|
|
281
242
|
fn is_newer(candidate: &str, current: &str) -> bool {
|
|
282
243
|
compare_versions(candidate, current) == std::cmp::Ordering::Greater
|
|
283
244
|
}
|
|
@@ -25,7 +25,9 @@ pub use registry::{
|
|
|
25
25
|
GitHubReleasesRegistry, NpmRegistry, NuGetRegistry, PyPiRegistry, Registry, RubyGemsRegistry,
|
|
26
26
|
TerraformRegistry, VersionMeta,
|
|
27
27
|
};
|
|
28
|
-
pub use updater::{
|
|
28
|
+
pub use updater::{
|
|
29
|
+
DiscoverOptions, FileType, Lang, UpdateResult, Updater, discover_files, discover_files_with,
|
|
30
|
+
};
|
|
29
31
|
|
|
30
32
|
/// Determine the process exit code given the outcome of a run.
|
|
31
33
|
///
|