rubion 0.3.0 → 0.3.2

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f3dc20ad2e0d8a10c9819577c5370327e860d1406b866fa02504e472dbb1e673
4
- data.tar.gz: b7954923cc22c872f7a20520db5bf2496af0615886978d2701188357b1e3cdf2
3
+ metadata.gz: a34f7102afe1491a751b94e0d11e22fe854f20bb1c1f5a322473447282bd665a
4
+ data.tar.gz: a7dc977d51a43a5657445ee42afc730be67dd2fe6006e35b5153f47ae13fa30d
5
5
  SHA512:
6
- metadata.gz: c8997bc21f73e51e7e904e8372fd779d85f5b162ff0d7b7187ce4136571343913d6362d61a2cb2839d3c8333f91b3af8724192a6b7bff9ae361c357975d8dce6
7
- data.tar.gz: 8e18d1257ef3cc883c7dba88355c3c7928c60f31bb17d20135b19954ad4ac055b2aeb21338d9453156959330ad3432aa36ab2e7772c315ec9bc8bb81a9946ab2
6
+ metadata.gz: 13e0b27d669216a58580c02dc8f2e5d26a35a0102e9ac7537bb6d6aa6caea7d42e115b4c12ad6cecc4cdd61add4829a0e549b01578c23d150ce90da4ae859001
7
+ data.tar.gz: f09035851f40e23cdac6f3c594f3f99f4579438ea6421c9007295e1cde0707d689b95675e7435c8da17878defe51bec2ad8427b1ec09fc476727cb026ace9859
data/Gemfile CHANGED
@@ -9,3 +9,5 @@ gem "rake", "~> 13.0"
9
9
  gem "rspec", "~> 3.0"
10
10
  gem "rubocop", "~> 1.21"
11
11
 
12
+
13
+
data/LICENSE CHANGED
@@ -20,3 +20,5 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
20
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
21
  SOFTWARE.
22
22
 
23
+
24
+
data/README.md CHANGED
@@ -1,13 +1,15 @@
1
- # 🔒 Rubion
1
+ # Rubion
2
2
 
3
- **Rubion** is a security and version scanner for Ruby and JavaScript projects. It helps you identify vulnerabilities and outdated dependencies in your Ruby gems and NPM packages.
3
+ **Rubion** is a security and version scanner for Ruby and JavaScript projects. It helps you identify vulnerabilities and outdated dependencies in your Ruby gems and NPM/JavaScript packages.
4
+
5
+ <img width="1237" height="671" alt="Screenshot 2025-11-14 at 10 48 12 am" src="https://github.com/user-attachments/assets/a3d93452-c442-416a-9697-de59746e16ad" />
4
6
 
5
7
  ## Features
6
8
 
7
9
  - 📛 **Gem Vulnerabilities**: Scans for known security vulnerabilities in Ruby gems using `bundle-audit`
8
10
  - 📦 **Gem Versions**: Identifies outdated Ruby gems with release dates and version counts
9
- - 📛 **Package Vulnerabilities**: Scans for known security vulnerabilities in NPM packages using `npm audit`
10
- - 📦 **Package Versions**: Identifies outdated NPM packages with release dates and version counts
11
+ - 📛 **Package Vulnerabilities**: Scans for known security vulnerabilities in NPM/JavaScript packages using `npm audit` or `yarn audit`
12
+ - 📦 **Package Versions**: Identifies outdated NPM/JavaScript packages with release dates and version counts
11
13
  - 📊 **Beautiful Reports**: Organized table output with severity icons (🔴 Critical, 🟠 High, 🟡 Medium, 🟢 Low, ⚪ Unknown)
12
14
  - 🚀 **Fast & Efficient**: Parallel API processing (10 concurrent threads) for quick results
13
15
  - ⚡ **Incremental Output**: Shows gem results immediately, then scans packages
@@ -44,8 +46,8 @@ rubion scan
44
46
  This will scan your project for:
45
47
  - Ruby gem vulnerabilities (if `Gemfile.lock` exists)
46
48
  - Outdated Ruby gems with release dates
47
- - NPM package vulnerabilities (if `package.json` exists)
48
- - Outdated NPM packages with release dates
49
+ - NPM/JavaScript package vulnerabilities (if `package.json` exists)
50
+ - Outdated NPM/JavaScript packages with release dates
49
51
 
50
52
  ### Scan options
51
53
 
@@ -126,9 +128,11 @@ Package Versions:
126
128
 
127
129
  - Ruby 2.6 or higher
128
130
  - Bundler (for Ruby gem scanning)
129
- - NPM (optional, for NPM package scanning)
131
+ - NPM or Yarn (optional, for JavaScript package scanning)
130
132
  - `bundler-audit` (optional, for enhanced gem vulnerability detection)
131
133
 
134
+ **Note:** If both npm and yarn are available, Rubion will prompt you to choose which one to use.
135
+
132
136
  ### Installing bundler-audit (recommended)
133
137
 
134
138
  ```bash
@@ -172,10 +176,11 @@ Rubion uses a modular architecture:
172
176
  1. **Scanner** (`lib/rubion/scanner.rb`): Executes various commands to scan for vulnerabilities and outdated versions
173
177
  - `bundle-audit check` for gem vulnerabilities
174
178
  - `bundle outdated --parseable` for gem versions
175
- - `npm audit --json` for package vulnerabilities
176
- - `npm outdated --json` for package versions
179
+ - `npm audit --json` or `yarn audit --json` for package vulnerabilities (auto-detects which is available)
180
+ - `npm outdated --json` or `yarn outdated --json` for package versions (auto-detects which is available)
177
181
  - Fetches release dates and version data from RubyGems.org and NPM registry APIs
178
182
  - Uses parallel processing (10 concurrent threads) for fast API calls
183
+ - Prompts user to choose between npm and yarn if both are available
179
184
 
180
185
  2. **Reporter** (`lib/rubion/reporter.rb`): Formats scan results into beautiful terminal tables using `terminal-table`
181
186
  - Adds severity icons (🔴 🟠 🟡 🟢 ⚪)
data/bin/rubion CHANGED
@@ -5,3 +5,5 @@ require_relative '../lib/rubion'
5
5
 
6
6
  Rubion::CLI.start(ARGV)
7
7
 
8
+
9
+
@@ -4,8 +4,9 @@ require 'terminal-table'
4
4
 
5
5
  module Rubion
6
6
  class Reporter
7
- def initialize(scan_result)
7
+ def initialize(scan_result, sort_by: nil)
8
8
  @result = scan_result
9
+ @sort_by = sort_by
9
10
  end
10
11
 
11
12
  def report
@@ -36,31 +37,6 @@ module Rubion
36
37
 
37
38
  private
38
39
 
39
- def _print_gem_vulnerabilities
40
- puts "Gem Vulnerabilities:\n\n"
41
-
42
- if @result.gem_vulnerabilities.empty?
43
- puts " ✅ No vulnerabilities found!\n\n"
44
- return
45
- end
46
-
47
- table = Terminal::Table.new do |t|
48
- t.headings = %w[Level Name Version Vulnerability]
49
-
50
- @result.gem_vulnerabilities.each do |vuln|
51
- t.add_row [
52
- severity_with_icon(vuln[:severity]),
53
- vuln[:gem],
54
- vuln[:version],
55
- truncate(vuln[:title], 50)
56
- ]
57
- end
58
- end
59
-
60
- puts table
61
- puts "\n"
62
- end
63
-
64
40
  def print_header
65
41
  # Simplified header
66
42
  puts "\n"
@@ -97,10 +73,14 @@ module Rubion
97
73
  return
98
74
  end
99
75
 
76
+ # Sort if sort_by is specified
77
+ versions = @result.gem_versions.dup
78
+ versions = sort_versions(versions, :gem) if @sort_by
79
+
100
80
  table = Terminal::Table.new do |t|
101
81
  t.headings = ['Name', 'Current', 'Date', 'Latest', 'Date', 'Behind By(Time)', 'Behind By(Versions)']
102
82
 
103
- @result.gem_versions.each do |gem|
83
+ versions.each do |gem|
104
84
  t.add_row [
105
85
  gem[:gem],
106
86
  gem[:current],
@@ -150,10 +130,14 @@ module Rubion
150
130
  return
151
131
  end
152
132
 
133
+ # Sort if sort_by is specified
134
+ versions = @result.package_versions.dup
135
+ versions = sort_versions(versions, :package) if @sort_by
136
+
153
137
  table = Terminal::Table.new do |t|
154
- t.headings = ['Name', 'Current', 'Date', 'Latest', 'Date', 'Behind By', 'Versions']
138
+ t.headings = ['Name', 'Current', 'Date', 'Latest', 'Date', 'Behind By(Time)', 'Behind By(Versions)']
155
139
 
156
- @result.package_versions.each do |pkg|
140
+ versions.each do |pkg|
157
141
  t.add_row [
158
142
  pkg[:package],
159
143
  pkg[:current],
@@ -218,7 +202,7 @@ module Rubion
218
202
  def truncate(text, length = 50)
219
203
  return text if text.length <= length
220
204
 
221
- "#{text[0..length - 3]}..."
205
+ "#{text[0..(length - 3)]}..."
222
206
  end
223
207
 
224
208
  def version_difference(current, latest)
@@ -242,5 +226,104 @@ module Rubion
242
226
  rescue StandardError
243
227
  'unknown'
244
228
  end
229
+
230
+ # Sort versions array based on the specified column
231
+ def sort_versions(versions, name_key)
232
+ return versions unless @sort_by
233
+
234
+ column = @sort_by.strip.downcase
235
+ name_key_sym = name_key # :gem or :package
236
+
237
+ # Normalize column name - default to 'name' if not recognized
238
+ normalized_column = case column
239
+ when 'name', 'current', 'date', 'latest',
240
+ 'behind by(time)', 'behind by time', 'time',
241
+ 'behind by(versions)', 'behind by versions', 'versions'
242
+ column
243
+ else
244
+ 'name' # Default to name sorting
245
+ end
246
+
247
+ versions.sort_by do |item|
248
+ case normalized_column
249
+ when 'name'
250
+ item[name_key_sym].to_s.downcase
251
+ when 'current'
252
+ parse_version_for_sort(item[:current])
253
+ when 'date'
254
+ # Sort by current_date (first Date column)
255
+ parse_date_for_sort(item[:current_date])
256
+ when 'latest'
257
+ parse_version_for_sort(item[:latest])
258
+ when 'behind by(time)', 'behind by time', 'time'
259
+ parse_time_for_sort(item[:time_diff])
260
+ when 'behind by(versions)', 'behind by versions', 'versions'
261
+ parse_version_count_for_sort(item[:version_count])
262
+ end
263
+ end
264
+ end
265
+
266
+ # Parse version string for sorting (handles semantic versions)
267
+ def parse_version_for_sort(version_str)
268
+ return [0, 0, 0, ''] if version_str.nil? || version_str == 'N/A' || version_str == 'unknown'
269
+
270
+ # Handle version strings like "1.2.3", "1.2.3.4", "1.2.3-beta", etc.
271
+ parts = version_str.to_s.split(/[.-]/)
272
+ major = parts[0].to_i
273
+ minor = parts[1].to_i
274
+ patch = parts[2].to_i
275
+ suffix = parts[3..-1].join('') if parts.length > 3
276
+
277
+ [major, minor, patch, suffix || '']
278
+ end
279
+
280
+ # Parse date string for sorting (handles M/D/YYYY format)
281
+ def parse_date_for_sort(date_str)
282
+ return [0, 0, 0] if date_str.nil? || date_str == 'N/A'
283
+
284
+ begin
285
+ parts = date_str.split('/').map(&:to_i)
286
+ return [parts[2] || 0, parts[0] || 0, parts[1] || 0] if parts.length == 3
287
+ rescue StandardError
288
+ # If parsing fails, return a date that sorts last
289
+ end
290
+
291
+ [0, 0, 0]
292
+ end
293
+
294
+ # Parse time difference string for sorting (e.g., "1 year", "3 months", "5 days")
295
+ def parse_time_for_sort(time_str)
296
+ return [0, 0] if time_str.nil? || time_str == 'N/A'
297
+
298
+ time_str = time_str.to_s.downcase.strip
299
+
300
+ # Extract number and unit
301
+ match = time_str.match(/(\d+(?:\.\d+)?)\s*(year|month|day|years|months|days)/)
302
+ return [0, 0] unless match
303
+
304
+ value = match[1].to_f
305
+ unit = match[2].to_s.downcase
306
+
307
+ # Convert to days for comparison
308
+ days = case unit
309
+ when /year/
310
+ value * 365
311
+ when /month/
312
+ value * 30
313
+ when /day/
314
+ value
315
+ else
316
+ 0
317
+ end
318
+
319
+ [days.to_i, value]
320
+ end
321
+
322
+ # Parse version count for sorting
323
+ def parse_version_count_for_sort(count)
324
+ return 0 if count.nil? || count == 'N/A' || count == 'unknown'
325
+
326
+ count.to_i
327
+ end
245
328
  end
246
329
  end
@@ -21,9 +21,11 @@ module Rubion
21
21
  end
22
22
  end
23
23
 
24
- def initialize(project_path: Dir.pwd)
24
+ def initialize(project_path: Dir.pwd, package_manager: nil)
25
25
  @project_path = project_path
26
26
  @result = ScanResult.new
27
+ @package_manager = package_manager
28
+ @package_manager_detected = false
27
29
  end
28
30
 
29
31
  def scan
@@ -35,7 +37,7 @@ module Rubion
35
37
  @result
36
38
  end
37
39
 
38
- def scan_incremental(options = { gems: true, packages: true })
40
+ def scan_incremental(options = { gems: true, packages: true, sort_by: nil })
39
41
  puts "🔍 Scanning project at: #{@project_path}\n\n"
40
42
 
41
43
  # Scan and display Ruby gems first (if enabled)
@@ -44,7 +46,7 @@ module Rubion
44
46
 
45
47
  # Print gem results immediately
46
48
  puts "\n"
47
- reporter = Reporter.new(@result)
49
+ reporter = Reporter.new(@result, sort_by: options[:sort_by])
48
50
  reporter.print_gem_vulnerabilities
49
51
  reporter.print_gem_versions
50
52
  end
@@ -74,10 +76,21 @@ module Rubion
74
76
  package_json = File.join(@project_path, 'package.json')
75
77
  return unless File.exist?(package_json)
76
78
 
77
- # Check for vulnerabilities using npm audit
79
+ # Detect package manager if not already set
80
+ unless @package_manager_detected
81
+ @package_manager = @package_manager || detect_package_manager
82
+ @package_manager_detected = true
83
+ end
84
+
85
+ unless @package_manager
86
+ puts " ⚠️ Neither npm nor yarn is available. Skipping package scanning."
87
+ return
88
+ end
89
+
90
+ # Check for vulnerabilities using package manager audit
78
91
  check_npm_vulnerabilities
79
92
 
80
- # Check for outdated versions using npm outdated (will show progress)
93
+ # Check for outdated versions using package manager outdated (will show progress)
81
94
  check_npm_versions
82
95
  end
83
96
 
@@ -113,7 +126,10 @@ module Rubion
113
126
  end
114
127
 
115
128
  def check_npm_vulnerabilities
116
- stdout, stderr, status = Open3.capture3("npm audit --json 2>&1", chdir: @project_path)
129
+ return unless @package_manager
130
+
131
+ command = "#{@package_manager} audit --json 2>&1"
132
+ stdout, stderr, status = Open3.capture3(command, chdir: @project_path)
117
133
 
118
134
  begin
119
135
  data = JSON.parse(stdout)
@@ -122,12 +138,15 @@ module Rubion
122
138
  @result.package_vulnerabilities = []
123
139
  end
124
140
  rescue => e
125
- puts " ⚠️ Could not run npm audit (#{e.message}). Skipping package vulnerability check."
141
+ puts " ⚠️ Could not run #{@package_manager} audit (#{e.message}). Skipping package vulnerability check."
126
142
  @result.package_vulnerabilities = []
127
143
  end
128
144
 
129
145
  def check_npm_versions
130
- stdout, stderr, status = Open3.capture3("npm outdated --json 2>&1", chdir: @project_path)
146
+ return unless @package_manager
147
+
148
+ command = "#{@package_manager} outdated --json 2>&1"
149
+ stdout, stderr, status = Open3.capture3(command, chdir: @project_path)
131
150
 
132
151
  begin
133
152
  data = JSON.parse(stdout) unless stdout.empty?
@@ -136,7 +155,7 @@ module Rubion
136
155
  @result.package_versions = []
137
156
  end
138
157
  rescue => e
139
- puts " ⚠️ Could not run npm outdated (#{e.message}). Skipping package version check."
158
+ puts " ⚠️ Could not run #{@package_manager} outdated (#{e.message}). Skipping package version check."
140
159
  @result.package_versions = []
141
160
  end
142
161
 
@@ -532,6 +551,48 @@ module Rubion
532
551
  end
533
552
  end
534
553
 
554
+ # Detect which package manager is available (npm or yarn)
555
+ def detect_package_manager
556
+ npm_available = check_command_available('npm')
557
+ yarn_available = check_command_available('yarn')
558
+
559
+ if npm_available && yarn_available
560
+ # Both available - prompt user
561
+ prompt_package_manager_choice
562
+ elsif npm_available
563
+ 'npm'
564
+ elsif yarn_available
565
+ 'yarn'
566
+ else
567
+ nil
568
+ end
569
+ end
570
+
571
+ # Check if a command is available in the system
572
+ def check_command_available(command)
573
+ _, _, status = Open3.capture3("which #{command} 2>&1")
574
+ status.success?
575
+ rescue
576
+ false
577
+ end
578
+
579
+ # Prompt user to choose between npm and yarn when both are available
580
+ def prompt_package_manager_choice
581
+ puts "\n Both npm and yarn are available. Which would you like to use?"
582
+ print " Enter 'npm' or 'yarn' (default: npm): "
583
+
584
+ choice = $stdin.gets.chomp.strip.downcase
585
+
586
+ if choice.empty? || choice == 'npm'
587
+ 'npm'
588
+ elsif choice == 'yarn'
589
+ 'yarn'
590
+ else
591
+ puts " ⚠️ Invalid choice. Using npm as default."
592
+ 'npm'
593
+ end
594
+ end
595
+
535
596
  # Fetch all NPM package version info (dates and version list) from NPM registry in one call
536
597
  def fetch_npm_all_versions(package_name)
537
598
  return { versions: {}, version_list: [] } if package_name.nil?
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Rubion
4
- VERSION = "0.3.0"
4
+ VERSION = "0.3.2"
5
5
  end
6
6
 
data/lib/rubion.rb CHANGED
@@ -28,7 +28,7 @@ module Rubion
28
28
  end
29
29
 
30
30
  def self.parse_scan_options(args)
31
- options = { gems: true, packages: true }
31
+ options = { gems: true, packages: true, sort_by: nil }
32
32
 
33
33
  # Check for --gems-only or --packages-only flags
34
34
  if args.include?('--gems-only') || args.include?('-g')
@@ -43,20 +43,28 @@ module Rubion
43
43
  options[:packages] = args.include?('--packages')
44
44
  end
45
45
 
46
+ # Parse --sort-by or -s option
47
+ sort_index = args.index('--sort-by') || args.index('-s')
48
+ if sort_index && args[sort_index + 1]
49
+ options[:sort_by] = args[sort_index + 1]
50
+ end
51
+
46
52
  options
47
53
  end
48
54
 
49
- def self.scan(options = { gems: true, packages: true })
55
+ def self.scan(options = { gems: true, packages: true, sort_by: nil })
50
56
  project_path = Dir.pwd
51
57
 
52
58
  scanner = Scanner.new(project_path: project_path)
53
59
  result = scanner.scan_incremental(options)
54
60
 
55
61
  # Results are already printed incrementally based on options
56
- reporter = Reporter.new(result)
57
-
58
- # Only print package results if packages were scanned
62
+ # Package results are printed in scan_incremental, but we need to ensure
63
+ # they use the same reporter instance with sort_by
64
+ # Actually, scan_incremental handles gem printing, but package printing
65
+ # happens here, so we need a reporter for packages
59
66
  if options[:packages]
67
+ reporter = Reporter.new(result, sort_by: options[:sort_by])
60
68
  reporter.print_package_vulnerabilities
61
69
  reporter.print_package_versions
62
70
  end
@@ -76,13 +84,14 @@ module Rubion
76
84
  --gems, --gem, -g Scan only Ruby gems (skip NPM packages)
77
85
  --packages, --npm, -p Scan only NPM packages (skip Ruby gems)
78
86
  --all, -a Scan both gems and packages (default)
87
+ --sort-by COLUMN, -s COLUMN Sort results by column (Name, Current, Date, Latest, Behind By(Time), Behind By(Versions))
79
88
 
80
89
  DESCRIPTION:
81
90
  Rubion scans your project for:
82
91
  - Ruby gem vulnerabilities (using bundler-audit)
83
92
  - Outdated Ruby gems (using bundle outdated)
84
- - NPM package vulnerabilities (using npm audit)
85
- - Outdated NPM packages (using npm outdated)
93
+ - NPM/JavaScript package vulnerabilities (using npm audit or yarn audit)
94
+ - Outdated NPM/JavaScript packages (using npm outdated or yarn outdated)
86
95
 
87
96
  OUTPUT:
88
97
  Results are displayed in organized tables with:
@@ -101,14 +110,23 @@ module Rubion
101
110
  # Scan only NPM packages
102
111
  rubion scan --packages
103
112
 
113
+ # Sort by name
114
+ rubion scan --sort-by Name
115
+
116
+ # Sort by versions behind
117
+ rubion scan -s "Behind By(Versions)"
118
+
104
119
  # Get help
105
120
  rubion help
106
121
 
107
122
  REQUIREMENTS:
108
123
  - Ruby 2.6+
109
124
  - Bundler (for gem scanning)
110
- - NPM (for package scanning, optional)
125
+ - NPM or Yarn (for package scanning, optional)
111
126
  - bundler-audit (optional, install with: gem install bundler-audit)
127
+
128
+ NOTE:
129
+ If both npm and yarn are available, you will be prompted to choose which one to use.
112
130
 
113
131
  HELP
114
132
  end
metadata CHANGED
@@ -1,13 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubion
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.3.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - bipashant
8
+ autorequire:
8
9
  bindir: bin
9
10
  cert_chain: []
10
- date: 1980-01-02 00:00:00.000000000 Z
11
+ date: 2025-11-14 00:00:00.000000000 Z
11
12
  dependencies:
12
13
  - !ruby/object:Gem::Dependency
13
14
  name: terminal-table
@@ -92,6 +93,7 @@ metadata:
92
93
  source_code_uri: https://github.com/bipashant/rubion
93
94
  changelog_uri: https://github.com/bipashant/rubion/blob/main/CHANGELOG.md
94
95
  bug_tracker_uri: https://github.com/bipashant/rubion/issues
96
+ post_install_message:
95
97
  rdoc_options: []
96
98
  require_paths:
97
99
  - lib
@@ -106,7 +108,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
106
108
  - !ruby/object:Gem::Version
107
109
  version: '0'
108
110
  requirements: []
109
- rubygems_version: 3.7.2
111
+ rubygems_version: 3.4.10
112
+ signing_key:
110
113
  specification_version: 4
111
114
  summary: Security and version scanner for Ruby and JavaScript projects
112
115
  test_files: []