bundle_update_interactive 0.1.2 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5528866f5f55fbafe54d180eb107216cb51960f6e4f2a19a6ab0d5747ba0a4c8
4
- data.tar.gz: 1cf3a85d39c210a5b8e7f7788002411b652d504f8d4fb839c027085dcad56524
3
+ metadata.gz: 24de9c6dde2b3514ffad21a64256359200e2bb717b1a0385caa8d032776d5fe6
4
+ data.tar.gz: 4a1a1c65bd89e4c7e17d563c2a854c80e07edeb7d04e6b4ffdf3cbef1d47ddee
5
5
  SHA512:
6
- metadata.gz: fd77e2d7fdbdce228366b613b0c1d68571bd8b75bf42299b216b14a2c4adb119b82dea42c82e879ade62241974006655f5e67594bc7e6b5f6d25a082efe0e774
7
- data.tar.gz: 071b0269b26bf0d1b49fe65962a921846f0ad55ff5dff553ab4d174e1bac33f9000eba9257c6d89e7f376ab305cc1f24ac0b73202f4dfa5d1a12b4e5b02f3c7c
6
+ metadata.gz: 493421b5621ff128b56ed96041fd4c1e776de8c0160f330d8458d40963248e2c2e779dc64c7f7afd390c2f98d62a6203b3495534e7b57051f79add6edc729715
7
+ data.tar.gz: 638b235f21664177f8b9de29a0b69b9229ef1b57fc985c4ba6c73678ca2142a926caff18626a809b4ab4fdd2aeff4573e7a4137c471ca569d62b95e55322e0ec
data/README.md CHANGED
@@ -5,13 +5,15 @@
5
5
  [![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/mattbrictson/bundle_update_interactive/ci.yml)](https://github.com/mattbrictson/bundle_update_interactive/actions/workflows/ci.yml)
6
6
  [![Code Climate maintainability](https://img.shields.io/codeclimate/maintainability/mattbrictson/bundle_update_interactive)](https://codeclimate.com/github/mattbrictson/bundle_update_interactive)
7
7
 
8
- This gem adds an `update-interactive` command to [Bundler](https://bundler.io).
8
+ **This gem adds an `update-interactive` command to [Bundler](https://bundler.io).** Run it to see what gems can be updated, then pick and choose which ones to update. If you've used `yarn upgrade-interactive`, the interface should be very familiar.
9
9
 
10
- https://github.com/user-attachments/assets/3ec11073-b365-4f92-be76-60c9ac73d1be
10
+ <img src="images/update-interactive.png" alt="Screenshot of update-interactive UI" width="1154" />
11
11
 
12
12
  ---
13
13
 
14
14
  - [Quick start](#quick-start)
15
+ - [Features](#features)
16
+ - [Prior art](#prior-art)
15
17
  - [Support](#support)
16
18
  - [License](#license)
17
19
  - [Code of conduct](#code-of-conduct)
@@ -37,6 +39,85 @@ Or the shorthand:
37
39
  bundle ui
38
40
  ```
39
41
 
42
+ ## Features
43
+
44
+ ### Semver highlighting
45
+
46
+ `bundle update-interactive` highlights each gem according the severity of its version upgrade.
47
+
48
+ <img src="images/semver.png" alt="Severities are in red, yellow, and green" width="480" />
49
+
50
+ Gems sourced from Git repositories are highlighted in cyan, regardless of the semver change, due to the fact that new commits pulled from the Git repo may not yet be officially released. In this case the semver information is unknown.
51
+
52
+ `bundle update-interactive` also highlights the exact portion of the version number that has changed, so you can quickly scan gem versions for important differences.
53
+
54
+ <img src="images/version-highlight.png" alt="Screenshot of highlighted version numbers" width="70" />
55
+
56
+ ### Security vulnerabilities
57
+
58
+ `bundle update-interactive` uses [bundler-audit](https://github.com/rubysec/bundler-audit) internally to search for outdated gems that have known security vulnerabilities. These gems are highlighted prominently with white text on a red background.
59
+
60
+ <img src="images/security.png" alt="Screenshot of security vulnerability highlighted in red" width="402" />
61
+
62
+ Some gems, notably `rails`, are composed of smaller gems like `actionpack`, `activesupport`, `railties`, etc. Because of how these component gem versions are constrained, you cannot update just one of them; they all must be updated together.
63
+
64
+ Therefore, if any Rails component has a security vulnerability, `bundle update-interactive` will automatically roll up that information into a single `rails` line item, so you can select it and upgrade all of its components in one shot.
65
+
66
+ ### Changelogs
67
+
68
+ `bundle update-interactive` will do its best to find an appropriate changelog for each gem.
69
+
70
+ It prefers the `changelog_uri` [metadata](https://guides.rubygems.org/specification-reference/#metadata) published in the gem itself. However, this metadata field is optional, and many gem authors do not provide it.
71
+
72
+ As a fallback, `bundle update-interactive` will check if the gem's source code is hosted on GitHub, and scans the GitHub repo for obvious changelog files like `CHANGELOG.md`, `NEWS`, etc. Finally, if the project is actively documenting versions using GitHub Releases, the Releases URL will be used.
73
+
74
+ If you discover a gem that is missing a changelog in `bundle update-interactive`, [log an issue](https://github.com/mattbrictson/bundle_update_interactive/issues) and I'll see if the algorithm can be improved.
75
+
76
+ ### Git diffs
77
+
78
+ If your `Gemfile` sources a gem from a GitHub repo like this:
79
+
80
+ ```ruby
81
+ gem "rails", github: "rails/rails", branch: "7-1-stable"
82
+ ```
83
+
84
+ Then `bundle update-interactive` will show a GitHub diff link instead of a changelog, so you can see exactly what changed when the gem is updated. For example:
85
+
86
+ https://github.com/rails/rails/compare/5a8d894...77dfa65
87
+
88
+ Currently only GitHub repos are supported, but I'm considering adding GitLab and BitBucket as well.
89
+
90
+ ### Conservative updates
91
+
92
+ `bundle update-interactive` updates the gems you select by running `bundle update --conservative [GEMS...]`. This means that only those specific gems will be updated. Indirect dependencies shared with other gems will not be affected.
93
+
94
+ <img src="images/conservative.png" alt="Screenshot of gems being updated" width="762" />
95
+
96
+ An exception is made for "meta gems" like `rails` that are composed of dependencies locked at exact versions. For example, if you chose to upgrade `rails`, the actual command issued to Bundler will be:
97
+
98
+ ```
99
+ bundle update --conservative \
100
+ rails \
101
+ actioncable \
102
+ actionmailbox \
103
+ actionmailer \
104
+ actionpack \
105
+ actiontext \
106
+ actionview \
107
+ activejob \
108
+ activemodel \
109
+ activerecord \
110
+ activestorage \
111
+ activesupport \
112
+ railties
113
+ ```
114
+
115
+ ## Prior art
116
+
117
+ This project was inspired by [yarn upgrade-interactive](https://classic.yarnpkg.com/lang/en/docs/cli/upgrade-interactive/), and borrows many of its interface ideas.
118
+
119
+ Before creating `bundle update-interactive`, I published [bundleup](https://github.com/mattbrictson/bundleup), a gem that serves a similar purpose but with a simpler, non-interactive approach.
120
+
40
121
  ## Support
41
122
 
42
123
  If you want to report a bug, or have ideas, feedback or questions about the gem, [let me know via GitHub issues](https://github.com/mattbrictson/bundle_update_interactive/issues/new) and I will do my best to provide a helpful answer. Happy hacking!
@@ -52,3 +133,7 @@ Everyone interacting in this project’s codebases, issue trackers, chat rooms a
52
133
  ## Contribution guide
53
134
 
54
135
  Pull requests are welcome!
136
+
137
+ To test your locally cloned version of `bundle update-interactive`, run `rake install`. This will install the gem and its executable so that you can try it out on other local projects.
138
+
139
+ Before submitting a PR, make sure to run `rake` to see if there are any RuboCop or test failures.
@@ -5,8 +5,8 @@ require "json"
5
5
 
6
6
  GITHUB_PATTERN = %r{^(?:https?://)?github\.com/([^/]+/[^/]+)(?:\.git)?/?}.freeze
7
7
  URI_KEYS = %w[source_code_uri homepage_uri bug_tracker_uri wiki_uri].freeze
8
- FILE_PATTERN = /(?:changelog|changes|history|news|release)/.freeze
9
- EXT_PATTERN = /(?:md|txt|rdoc)/.freeze
8
+ FILE_PATTERN = /changelog|changes|history|news|release/i.freeze
9
+ EXT_PATTERN = /md|txt|rdoc/i.freeze
10
10
 
11
11
  module BundleUpdateInteractive
12
12
  class ChangelogLocator
@@ -39,6 +39,8 @@ class BundleUpdateInteractive::CLI
39
39
  exit(130)
40
40
  }
41
41
  )
42
+ add_keybindings
43
+
42
44
  @pastel = BundleUpdateInteractive.pastel
43
45
  end
44
46
 
@@ -51,6 +53,13 @@ class BundleUpdateInteractive::CLI
51
53
 
52
54
  attr_reader :pastel, :table, :tty_prompt, :title
53
55
 
56
+ def add_keybindings
57
+ tty_prompt.on(:keypress) do |event|
58
+ tty_prompt.trigger(:keyup) if %w[k p].include?(event.value)
59
+ tty_prompt.trigger(:keydown) if %w[j n].include?(event.value)
60
+ end
61
+ end
62
+
54
63
  def help
55
64
  [
56
65
  pastel.dim("\nPress <space> to select, ↑/↓ move, <ctrl-a> all, <ctrl-r> reverse, <enter> to finish."),
@@ -28,7 +28,7 @@ class BundleUpdateInteractive::CLI
28
28
 
29
29
  def render
30
30
  lines = [render_header]
31
- rows.each_key { |name| lines << render_gem(name) }
31
+ rows.keys.sort.each { |name| lines << render_gem(name) }
32
32
  lines.join("\n")
33
33
  end
34
34
 
@@ -2,19 +2,21 @@
2
2
 
3
3
  module BundleUpdateInteractive
4
4
  class OutdatedGem
5
- attr_reader :current_lockfile_entry, :updated_lockfile_entry, :gemfile_groups
6
- attr_writer :vulnerable
5
+ attr_accessor :name,
6
+ :gemfile_groups,
7
+ :git_source_uri,
8
+ :current_version,
9
+ :current_git_version,
10
+ :updated_version,
11
+ :updated_git_version
7
12
 
8
- def initialize(current_lockfile_entry:, updated_lockfile_entry:, gemfile_groups:)
9
- @current_lockfile_entry = current_lockfile_entry
10
- @updated_lockfile_entry = updated_lockfile_entry
11
- @gemfile_groups = gemfile_groups
12
- @changelog_locator = ChangelogLocator.new
13
+ attr_writer :rubygems_source, :vulnerable
14
+
15
+ def initialize(**attrs)
13
16
  @vulnerable = nil
14
- end
17
+ @changelog_locator = ChangelogLocator.new
15
18
 
16
- def name
17
- current_lockfile_entry.name
19
+ attrs.each { |name, value| public_send(:"#{name}=", value) }
18
20
  end
19
21
 
20
22
  def semver_change
@@ -25,13 +27,17 @@ module BundleUpdateInteractive
25
27
  @vulnerable
26
28
  end
27
29
 
30
+ def rubygems_source?
31
+ @rubygems_source
32
+ end
33
+
28
34
  def changelog_uri
29
35
  return @changelog_uri if defined?(@changelog_uri)
30
36
 
31
37
  @changelog_uri =
32
- if git_version_changed?
38
+ if git_version_changed? && github_repo
33
39
  "https://github.com/#{github_repo}/compare/#{current_git_version}...#{updated_git_version}"
34
- elsif updated_lockfile_entry.rubygems_source?
40
+ elsif rubygems_source?
35
41
  changelog_locator.find_changelog_uri(name: name, version: updated_version.to_s)
36
42
  else
37
43
  begin
@@ -42,22 +48,6 @@ module BundleUpdateInteractive
42
48
  end
43
49
  end
44
50
 
45
- def current_version
46
- current_lockfile_entry.version
47
- end
48
-
49
- def updated_version
50
- updated_lockfile_entry.version
51
- end
52
-
53
- def current_git_version
54
- current_lockfile_entry.git_version
55
- end
56
-
57
- def updated_git_version
58
- updated_lockfile_entry.git_version
59
- end
60
-
61
51
  def git_version_changed?
62
52
  current_git_version && updated_git_version && current_git_version != updated_git_version
63
53
  end
@@ -69,8 +59,7 @@ module BundleUpdateInteractive
69
59
  def github_repo
70
60
  return nil unless updated_git_version
71
61
 
72
- updated_lockfile_entry.git_source_uri.to_s[%r{^(?:git@github.com:|https://github.com/)([^/]+/[^/]+?)(:?\.git)?(?:$|/)}i,
73
- 1]
62
+ git_source_uri.to_s[%r{^(?:git@github.com:|https://github.com/)([^/]+/[^/]+?)(:?\.git)?(?:$|/)}i, 1]
74
63
  end
75
64
  end
76
65
  end
@@ -21,16 +21,12 @@ module BundleUpdateInteractive
21
21
 
22
22
  def initialize(gemfile:, current_lockfile:, updated_lockfile:)
23
23
  @current_lockfile = current_lockfile
24
- outdated_names = current_lockfile.entries.each_with_object([]) do |current_entry, arr|
25
- updated_entry = updated_lockfile[current_entry.name]
26
- arr << current_entry.name if current_entry.older_than?(updated_entry)
27
- end
28
- @outdated_gems ||= outdated_names.sort.each_with_object({}) do |name, hash|
29
- hash[name] = OutdatedGem.new(
30
- current_lockfile_entry: current_lockfile[name],
31
- updated_lockfile_entry: updated_lockfile[name],
32
- gemfile_groups: gemfile[name]&.groups
33
- )
24
+ @outdated_gems ||= current_lockfile.entries.each_with_object({}) do |current_lockfile_entry, hash|
25
+ name = current_lockfile_entry.name
26
+ updated_lockfile_entry = updated_lockfile[name]
27
+ next unless current_lockfile_entry.older_than?(updated_lockfile_entry)
28
+
29
+ hash[name] = build_outdated_gem(current_lockfile_entry, updated_lockfile_entry, gemfile[name]&.groups)
34
30
  end.freeze
35
31
  end
36
32
 
@@ -39,12 +35,14 @@ module BundleUpdateInteractive
39
35
  end
40
36
 
41
37
  def updateable_gems
42
- outdated_gems.reject { |_, gem| gem.current_lockfile_entry.exact_dependency? }
38
+ @updateable_gems ||= outdated_gems.reject do |name, _|
39
+ current_lockfile[name].exact_dependency?
40
+ end.freeze
43
41
  end
44
42
 
45
43
  def expand_gems_with_exact_dependencies(*gem_names)
46
44
  gem_names.flatten!
47
- gem_names.flat_map { [_1, *outdated_gems[_1].current_lockfile_entry.exact_dependencies] }.uniq
45
+ gem_names.flat_map { |name| [name, *current_lockfile[name].exact_dependencies] }.uniq
48
46
  end
49
47
 
50
48
  def scan_for_vulnerabilities!
@@ -68,5 +66,18 @@ module BundleUpdateInteractive
68
66
  private
69
67
 
70
68
  attr_reader :current_lockfile
69
+
70
+ def build_outdated_gem(current_lockfile_entry, updated_lockfile_entry, gemfile_groups)
71
+ OutdatedGem.new(
72
+ name: current_lockfile_entry.name,
73
+ gemfile_groups: gemfile_groups,
74
+ rubygems_source: updated_lockfile_entry.rubygems_source?,
75
+ git_source_uri: updated_lockfile_entry.git_source_uri&.to_s,
76
+ current_version: current_lockfile_entry.version.to_s,
77
+ current_git_version: current_lockfile_entry.git_version&.strip,
78
+ updated_version: updated_lockfile_entry.version.to_s,
79
+ updated_git_version: updated_lockfile_entry.git_version&.strip
80
+ )
81
+ end
71
82
  end
72
83
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module BundleUpdateInteractive
4
- VERSION = "0.1.2"
4
+ VERSION = "0.2.1"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bundle_update_interactive
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matt Brictson
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-07-11 00:00:00.000000000 Z
11
+ date: 2024-07-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler