upd-cli 0.1.9__tar.gz → 0.2.0__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.
Files changed (80) hide show
  1. {upd_cli-0.1.9 → upd_cli-0.2.0}/CHANGELOG.md +18 -0
  2. {upd_cli-0.1.9 → upd_cli-0.2.0}/Cargo.lock +1 -1
  3. {upd_cli-0.1.9 → upd_cli-0.2.0}/Cargo.toml +1 -1
  4. {upd_cli-0.1.9 → upd_cli-0.2.0}/PKG-INFO +1 -1
  5. {upd_cli-0.1.9 → upd_cli-0.2.0}/fixtures/clispec-v0.2.json +26 -0
  6. {upd_cli-0.1.9 → upd_cli-0.2.0}/src/lib.rs +6 -4
  7. {upd_cli-0.1.9 → upd_cli-0.2.0}/src/schema.rs +31 -17
  8. {upd_cli-0.1.9 → upd_cli-0.2.0}/tests/exit_codes.rs +5 -5
  9. {upd_cli-0.1.9 → upd_cli-0.2.0}/tests/fix_audit.rs +5 -5
  10. {upd_cli-0.1.9 → upd_cli-0.2.0}/.mise.toml +0 -0
  11. {upd_cli-0.1.9 → upd_cli-0.2.0}/.pre-commit-config.yaml +0 -0
  12. {upd_cli-0.1.9 → upd_cli-0.2.0}/.pre-commit-hooks.yaml +0 -0
  13. {upd_cli-0.1.9 → upd_cli-0.2.0}/.rumdl.toml +0 -0
  14. {upd_cli-0.1.9 → upd_cli-0.2.0}/LICENSE +0 -0
  15. {upd_cli-0.1.9 → upd_cli-0.2.0}/Makefile +0 -0
  16. {upd_cli-0.1.9 → upd_cli-0.2.0}/README.md +0 -0
  17. {upd_cli-0.1.9 → upd_cli-0.2.0}/assets/logo-wide.svg +0 -0
  18. {upd_cli-0.1.9 → upd_cli-0.2.0}/assets/logo.svg +0 -0
  19. {upd_cli-0.1.9 → upd_cli-0.2.0}/pyproject.toml +0 -0
  20. {upd_cli-0.1.9 → upd_cli-0.2.0}/python/upd_cli/__init__.py +0 -0
  21. {upd_cli-0.1.9 → upd_cli-0.2.0}/python/upd_cli/__main__.py +0 -0
  22. {upd_cli-0.1.9 → upd_cli-0.2.0}/python/upd_cli/py.typed +0 -0
  23. {upd_cli-0.1.9 → upd_cli-0.2.0}/rust-toolchain.toml +0 -0
  24. {upd_cli-0.1.9 → upd_cli-0.2.0}/src/align.rs +0 -0
  25. {upd_cli-0.1.9 → upd_cli-0.2.0}/src/audit/cache.rs +0 -0
  26. {upd_cli-0.1.9 → upd_cli-0.2.0}/src/audit/cvss.rs +0 -0
  27. {upd_cli-0.1.9 → upd_cli-0.2.0}/src/audit/mod.rs +0 -0
  28. {upd_cli-0.1.9 → upd_cli-0.2.0}/src/bin/upd-cli.rs +0 -0
  29. {upd_cli-0.1.9 → upd_cli-0.2.0}/src/cache.rs +0 -0
  30. {upd_cli-0.1.9 → upd_cli-0.2.0}/src/cli.rs +0 -0
  31. {upd_cli-0.1.9 → upd_cli-0.2.0}/src/config.rs +0 -0
  32. {upd_cli-0.1.9 → upd_cli-0.2.0}/src/cooldown.rs +0 -0
  33. {upd_cli-0.1.9 → upd_cli-0.2.0}/src/http.rs +0 -0
  34. {upd_cli-0.1.9 → upd_cli-0.2.0}/src/interactive.rs +0 -0
  35. {upd_cli-0.1.9 → upd_cli-0.2.0}/src/lockfile.rs +0 -0
  36. {upd_cli-0.1.9 → upd_cli-0.2.0}/src/main.rs +0 -0
  37. {upd_cli-0.1.9 → upd_cli-0.2.0}/src/output.rs +0 -0
  38. {upd_cli-0.1.9 → upd_cli-0.2.0}/src/registry/crates_io.rs +0 -0
  39. {upd_cli-0.1.9 → upd_cli-0.2.0}/src/registry/github_releases.rs +0 -0
  40. {upd_cli-0.1.9 → upd_cli-0.2.0}/src/registry/go_proxy.rs +0 -0
  41. {upd_cli-0.1.9 → upd_cli-0.2.0}/src/registry/mock.rs +0 -0
  42. {upd_cli-0.1.9 → upd_cli-0.2.0}/src/registry/mod.rs +0 -0
  43. {upd_cli-0.1.9 → upd_cli-0.2.0}/src/registry/npm.rs +0 -0
  44. {upd_cli-0.1.9 → upd_cli-0.2.0}/src/registry/nuget.rs +0 -0
  45. {upd_cli-0.1.9 → upd_cli-0.2.0}/src/registry/pypi.rs +0 -0
  46. {upd_cli-0.1.9 → upd_cli-0.2.0}/src/registry/rubygems.rs +0 -0
  47. {upd_cli-0.1.9 → upd_cli-0.2.0}/src/registry/terraform.rs +0 -0
  48. {upd_cli-0.1.9 → upd_cli-0.2.0}/src/registry/utils.rs +0 -0
  49. {upd_cli-0.1.9 → upd_cli-0.2.0}/src/updater/cargo_toml.rs +0 -0
  50. {upd_cli-0.1.9 → upd_cli-0.2.0}/src/updater/csproj.rs +0 -0
  51. {upd_cli-0.1.9 → upd_cli-0.2.0}/src/updater/gemfile.rs +0 -0
  52. {upd_cli-0.1.9 → upd_cli-0.2.0}/src/updater/github_actions.rs +0 -0
  53. {upd_cli-0.1.9 → upd_cli-0.2.0}/src/updater/go_mod.rs +0 -0
  54. {upd_cli-0.1.9 → upd_cli-0.2.0}/src/updater/mise.rs +0 -0
  55. {upd_cli-0.1.9 → upd_cli-0.2.0}/src/updater/mod.rs +0 -0
  56. {upd_cli-0.1.9 → upd_cli-0.2.0}/src/updater/npm_range.rs +0 -0
  57. {upd_cli-0.1.9 → upd_cli-0.2.0}/src/updater/package_json.rs +0 -0
  58. {upd_cli-0.1.9 → upd_cli-0.2.0}/src/updater/pre_commit.rs +0 -0
  59. {upd_cli-0.1.9 → upd_cli-0.2.0}/src/updater/pyproject.rs +0 -0
  60. {upd_cli-0.1.9 → upd_cli-0.2.0}/src/updater/requirements.rs +0 -0
  61. {upd_cli-0.1.9 → upd_cli-0.2.0}/src/updater/terraform.rs +0 -0
  62. {upd_cli-0.1.9 → upd_cli-0.2.0}/src/version/compare.rs +0 -0
  63. {upd_cli-0.1.9 → upd_cli-0.2.0}/src/version/mod.rs +0 -0
  64. {upd_cli-0.1.9 → upd_cli-0.2.0}/src/version/pep440.rs +0 -0
  65. {upd_cli-0.1.9 → upd_cli-0.2.0}/src/version/semver_util.rs +0 -0
  66. {upd_cli-0.1.9 → upd_cli-0.2.0}/src/version/tag.rs +0 -0
  67. {upd_cli-0.1.9 → upd_cli-0.2.0}/tests/audit_offline.rs +0 -0
  68. {upd_cli-0.1.9 → upd_cli-0.2.0}/tests/audit_sarif.rs +0 -0
  69. {upd_cli-0.1.9 → upd_cli-0.2.0}/tests/audit_severity.rs +0 -0
  70. {upd_cli-0.1.9 → upd_cli-0.2.0}/tests/bump_filter.rs +0 -0
  71. {upd_cli-0.1.9 → upd_cli-0.2.0}/tests/cooldown_e2e.rs +0 -0
  72. {upd_cli-0.1.9 → upd_cli-0.2.0}/tests/discovery_no_ignore.rs +0 -0
  73. {upd_cli-0.1.9 → upd_cli-0.2.0}/tests/format_json.rs +0 -0
  74. {upd_cli-0.1.9 → upd_cli-0.2.0}/tests/help_text.rs +0 -0
  75. {upd_cli-0.1.9 → upd_cli-0.2.0}/tests/interactive_tty.rs +0 -0
  76. {upd_cli-0.1.9 → upd_cli-0.2.0}/tests/invalid_positional.rs +0 -0
  77. {upd_cli-0.1.9 → upd_cli-0.2.0}/tests/no_args_scope.rs +0 -0
  78. {upd_cli-0.1.9 → upd_cli-0.2.0}/tests/output_streams.rs +0 -0
  79. {upd_cli-0.1.9 → upd_cli-0.2.0}/tests/package_filter.rs +0 -0
  80. {upd_cli-0.1.9 → upd_cli-0.2.0}/vership.toml +0 -0
@@ -19,6 +19,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
19
19
 
20
20
 
21
21
 
22
+
23
+
24
+ ## [0.2.0](https://github.com/rvben/upd/compare/v0.1.10...v0.2.0) - 2026-06-11
25
+
26
+ ### Breaking Changes
27
+
28
+ - **audit**: give vulnerabilities_found its own exit code 6 as a declared outcome ([0037dc4](https://github.com/rvben/upd/commit/0037dc4e075f7d3cca7e51096fc11d07e1aa1cdb))
29
+
30
+ ### Added
31
+
32
+ - **audit**: give vulnerabilities_found its own exit code 6 as a declared outcome ([0037dc4](https://github.com/rvben/upd/commit/0037dc4e075f7d3cca7e51096fc11d07e1aa1cdb))
33
+
34
+ ## [0.1.10](https://github.com/rvben/upd/compare/v0.1.9...v0.1.10) - 2026-06-11
35
+
36
+ ### Added
37
+
38
+ - **schema**: declare updates_available as an outcome, not an error kind ([4f55a02](https://github.com/rvben/upd/commit/4f55a02e3b80384f19199691302f96ba4deb9246))
39
+
22
40
  ## [0.1.9](https://github.com/rvben/upd/compare/v0.1.8...v0.1.9) - 2026-06-11
23
41
 
24
42
  ### Added
@@ -2015,7 +2015,7 @@ checksum = "e9df2af067a7953e9c3831320f35c1cc0600c30d44d9f7a12b01db1cd88d6b47"
2015
2015
 
2016
2016
  [[package]]
2017
2017
  name = "upd"
2018
- version = "0.1.9"
2018
+ version = "0.2.0"
2019
2019
  dependencies = [
2020
2020
  "anyhow",
2021
2021
  "async-trait",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "upd"
3
- version = "0.1.9"
3
+ version = "0.2.0"
4
4
  edition = "2024"
5
5
  rust-version = "1.95.0"
6
6
  description = "A fast dependency updater for Python, Node.js, Rust, Go, Ruby, Terraform, GitHub Actions, pre-commit, and Mise projects"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: upd-cli
3
- Version: 0.1.9
3
+ Version: 0.2.0
4
4
  Classifier: Development Status :: 4 - Beta
5
5
  Classifier: Environment :: Console
6
6
  Classifier: Intended Audience :: Developers
@@ -38,6 +38,11 @@
38
38
  "description": "The finite set of `kind` values the tool emits in structured errors. Consumers can write exhaustive handlers against this set.",
39
39
  "type": "array",
40
40
  "items": { "$ref": "#/$defs/error" }
41
+ },
42
+ "outcomes": {
43
+ "description": "Documented non-zero exit codes that signal a data state rather than a failure (the diff/grep convention). An outcome exit writes no error envelope; stdout carries the result. Codes must not overlap with the exit codes declared in `errors`.",
44
+ "type": "array",
45
+ "items": { "$ref": "#/$defs/outcome" }
41
46
  }
42
47
  },
43
48
  "$defs": {
@@ -106,6 +111,27 @@
106
111
  "description": { "type": "string" }
107
112
  }
108
113
  },
114
+ "outcome": {
115
+ "type": "object",
116
+ "required": ["code", "name"],
117
+ "properties": {
118
+ "code": {
119
+ "description": "The exit code the tool returns for this outcome. Consumers can branch on it without parsing anything.",
120
+ "type": "integer",
121
+ "minimum": 1,
122
+ "maximum": 255
123
+ },
124
+ "name": {
125
+ "description": "Stable identifier for the outcome. Snake_case by convention.",
126
+ "type": "string",
127
+ "minLength": 1,
128
+ "pattern": "^[a-z][a-z0-9_]*$"
129
+ },
130
+ "description": {
131
+ "type": "string"
132
+ }
133
+ }
134
+ },
109
135
  "error": {
110
136
  "type": "object",
111
137
  "required": ["kind"],
@@ -53,15 +53,17 @@ pub fn decide_exit_code(non_mutating: bool, has_pending_updates: bool, has_error
53
53
  ///
54
54
  /// - `2` — scan errors occurred; errors take precedence over vulnerability
55
55
  /// findings so that CI can distinguish a broken scan from a clean one.
56
- /// - `3` — vulnerabilities were found and `no_fail` is `false`; distinct from
57
- /// the update exit codes (1 = pending updates, 2 = errors) so callers can
58
- /// handle each condition independently.
56
+ /// - `6` — vulnerabilities were found and `no_fail` is `false`; a dedicated
57
+ /// code, distinct from the update exit codes (1 = pending updates,
58
+ /// 2 = errors) and from the error exit codes declared in the schema, so
59
+ /// callers can branch on the exit code alone. Declared as the
60
+ /// `vulnerabilities_found` outcome in the schema.
59
61
  /// - `0` — no vulnerabilities found, or `no_fail` suppresses the non-zero exit.
60
62
  pub fn decide_audit_exit_code(vuln_count: usize, error_count: usize, no_fail: bool) -> i32 {
61
63
  if error_count > 0 {
62
64
  2
63
65
  } else if vuln_count > 0 && !no_fail {
64
- 3
66
+ 6
65
67
  } else {
66
68
  0
67
69
  }
@@ -251,13 +251,19 @@ fn build_schema() -> Value {
251
251
  ]
252
252
  }
253
253
  ],
254
- "errors": [
254
+ "outcomes": [
255
255
  {
256
- "kind": "updates_available",
257
- "description": "Updates are available (dry-run mode only). Run with --apply to write changes",
258
- "exit_code": 1,
259
- "retryable": false
256
+ "code": 1,
257
+ "name": "updates_available",
258
+ "description": "Updates are available (dry-run mode only); the report is on stdout. Not an error. Run with --apply to write changes"
260
259
  },
260
+ {
261
+ "code": 6,
262
+ "name": "vulnerabilities_found",
263
+ "description": "Security vulnerabilities found during audit; the report is on stdout. Not an error. Use --no-fail to exit 0 instead"
264
+ }
265
+ ],
266
+ "errors": [
261
267
  {
262
268
  "kind": "io_error",
263
269
  "description": "File read or write failed, or a required path does not exist",
@@ -281,12 +287,6 @@ fn build_schema() -> Value {
281
287
  "description": "Version conflict detected between files",
282
288
  "exit_code": 5,
283
289
  "retryable": false
284
- },
285
- {
286
- "kind": "vulnerabilities_found",
287
- "description": "Security vulnerabilities found during audit (use --no-fail to suppress non-zero exit)",
288
- "exit_code": 3,
289
- "retryable": false
290
290
  }
291
291
  ]
292
292
  })
@@ -358,18 +358,32 @@ mod tests {
358
358
  }
359
359
 
360
360
  #[test]
361
- fn schema_declares_updates_available_error_with_exit_code_1() {
361
+ fn schema_declares_updates_available_outcome_with_code_1() {
362
362
  let s = build_schema();
363
- let errors = s["errors"].as_array().expect("errors must be an array");
364
- let updates_available = errors
363
+ let outcomes = s["outcomes"].as_array().expect("outcomes must be an array");
364
+ let updates_available = outcomes
365
365
  .iter()
366
- .find(|e| e["kind"].as_str() == Some("updates_available"))
367
- .expect("must declare an 'updates_available' error kind");
366
+ .find(|o| o["name"].as_str() == Some("updates_available"))
367
+ .expect("must declare an 'updates_available' outcome");
368
368
  assert_eq!(
369
- updates_available["exit_code"].as_u64(),
369
+ updates_available["code"].as_u64(),
370
370
  Some(1),
371
371
  "updates_available must map to exit code 1 (the dry-run signal)"
372
372
  );
373
+ let errors = s["errors"].as_array().expect("errors must be an array");
374
+ assert!(
375
+ !errors
376
+ .iter()
377
+ .any(|e| e["kind"].as_str() == Some("updates_available")),
378
+ "updates_available is an outcome, not an error kind"
379
+ );
380
+ for outcome in outcomes {
381
+ let code = outcome["code"].as_u64().expect("outcome must have a code");
382
+ assert!(
383
+ !errors.iter().any(|e| e["exit_code"].as_u64() == Some(code)),
384
+ "outcome code {code} must not overlap with error exit codes"
385
+ );
386
+ }
373
387
  }
374
388
 
375
389
  #[test]
@@ -344,8 +344,8 @@ fn decide_audit_exit_code_clean() {
344
344
  #[test]
345
345
  fn decide_audit_exit_code_vulns_without_no_fail() {
346
346
  use upd::decide_audit_exit_code;
347
- assert_eq!(decide_audit_exit_code(1, 0, false), 3);
348
- assert_eq!(decide_audit_exit_code(162, 0, false), 3);
347
+ assert_eq!(decide_audit_exit_code(1, 0, false), 6);
348
+ assert_eq!(decide_audit_exit_code(162, 0, false), 6);
349
349
  }
350
350
 
351
351
  /// Unit test: vulns found, --no-fail present → exit 0.
@@ -388,7 +388,7 @@ fn audit_on_empty_workspace_exits_zero() {
388
388
  /// A wiremock server stands in for the OSV API and reports one vulnerability
389
389
  /// for `requests==1.0.0`.
390
390
  #[tokio::test]
391
- async fn audit_with_vulns_exits_three() {
391
+ async fn audit_with_vulns_exits_six() {
392
392
  use wiremock::matchers::{method, path};
393
393
  use wiremock::{Mock, MockServer, ResponseTemplate};
394
394
 
@@ -422,8 +422,8 @@ async fn audit_with_vulns_exits_three() {
422
422
  );
423
423
 
424
424
  assert_eq!(
425
- code, 3,
426
- "audit with vulns must exit 3 (no --no-fail); stderr: {stderr}"
425
+ code, 6,
426
+ "audit with vulns must exit 6, the vulnerabilities_found outcome (no --no-fail); stderr: {stderr}"
427
427
  );
428
428
  }
429
429
 
@@ -159,9 +159,9 @@ async fn fix_audit_dry_run_exits_1_and_leaves_file_unchanged() {
159
159
  }
160
160
 
161
161
  /// When a vulnerability has no `fixed_version`, emit a warning and don't touch the file.
162
- /// Falls through to normal audit exit code (3 = vulnerable, !no_fail).
162
+ /// Falls through to normal audit exit code (6 = vulnerabilities_found outcome, !no_fail).
163
163
  #[tokio::test]
164
- async fn fix_audit_no_fixed_version_warns_and_exits_3() {
164
+ async fn fix_audit_no_fixed_version_warns_and_exits_6() {
165
165
  use wiremock::matchers::{method, path};
166
166
  use wiremock::{Mock, MockServer, ResponseTemplate};
167
167
 
@@ -198,10 +198,10 @@ async fn fix_audit_no_fixed_version_warns_and_exits_3() {
198
198
  );
199
199
 
200
200
  // No fixable packages → falls through to normal audit exit code.
201
- // Normal audit with vulnerabilities and !no_fail → exit 3.
201
+ // Normal audit with vulnerabilities and !no_fail → exit 6.
202
202
  assert_eq!(
203
- code, 3,
204
- "should exit 3 (vulnerable, no fix available); stdout: {stdout}\nstderr: {stderr}"
203
+ code, 6,
204
+ "should exit 6 (vulnerabilities_found outcome, no fix available); stdout: {stdout}\nstderr: {stderr}"
205
205
  );
206
206
 
207
207
  let content = fs::read_to_string(&req_path).unwrap();
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes