strong_versions 0.3.2 → 0.4.0
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 +4 -4
- data/Makefile +1 -0
- data/README.md +7 -2
- data/bin/strong_versions +33 -4
- data/bin/strong_versions.rb +35 -0
- data/config/locales/en.yml +1 -0
- data/doc/images/strong-versions-example.png +0 -0
- data/lib/strong_versions.rb +1 -0
- data/lib/strong_versions/dependencies.rb +43 -1
- data/lib/strong_versions/dependency.rb +42 -20
- data/lib/strong_versions/dependency_finder.rb +1 -0
- data/lib/strong_versions/errors.rb +6 -0
- data/lib/strong_versions/suggestion.rb +20 -14
- data/lib/strong_versions/terminal.rb +30 -8
- data/lib/strong_versions/version.rb +1 -1
- data/strong_versions.gemspec +1 -1
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f73f7623edb5e3a662b18ea78cd67822dfdbcc81bd6eb0e9332e80bd1eda57c6
|
4
|
+
data.tar.gz: 9ed40edc5e76826e6458848fadd2b92a5b9f0985cefa0a3e56943c176e32edbb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 17fc0b16150395edf3d57daed7893803fd012311620285ad3a57df5330cfb28b1c1eebde904ece4484c5651c203dc27eeeb34f4699d79e6d443954020da23f81
|
7
|
+
data.tar.gz: 67d7d4dd885f70cf4877e72180311545606abc33d8191039c95c9afdcc2d2ef8a081222ab4fa56207e23e7830fa9adda0269444c093d8983e4c227119f9fe82a
|
data/Makefile
CHANGED
data/README.md
CHANGED
@@ -28,7 +28,7 @@ The benefit of applying this standard is that, if all gems follow [Semantic Vers
|
|
28
28
|
Add the gem to your `Gemfile`
|
29
29
|
|
30
30
|
```ruby
|
31
|
-
gem 'strong_versions', '~> 0.
|
31
|
+
gem 'strong_versions', '~> 0.4.0'
|
32
32
|
```
|
33
33
|
|
34
34
|
And rebuild your bundle:
|
@@ -39,7 +39,7 @@ $ bundle install
|
|
39
39
|
|
40
40
|
Or install yourself:
|
41
41
|
```bash
|
42
|
-
$ gem install strong_versions -v '0.
|
42
|
+
$ gem install strong_versions -v '0.4.0'
|
43
43
|
```
|
44
44
|
|
45
45
|
## Usage
|
@@ -54,6 +54,11 @@ The executable will output all non-passing gems and will return an exit code of
|
|
54
54
|
|
55
55
|

|
56
56
|
|
57
|
+
If you are feeling brave, auto-correct is available:
|
58
|
+
```bash
|
59
|
+
$ bundle exec strong_versions -a
|
60
|
+
```
|
61
|
+
|
57
62
|
### Exclusions
|
58
63
|
|
59
64
|
<a name="ignore"></a>You can tell _StrongVersions_ to ignore any of your gems (e.g. those that don't follow _semantic versioning_) by adding them to the `ignore` section of `.strong_versions.yml` in your project root, e.g.:
|
data/bin/strong_versions
CHANGED
@@ -1,14 +1,43 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'optparse'
|
5
|
+
require 'shellwords'
|
2
6
|
|
3
7
|
require 'strong_versions'
|
4
8
|
|
9
|
+
original_args = ARGV.dup
|
10
|
+
|
11
|
+
options = {}
|
12
|
+
OptionParser.new do |opts|
|
13
|
+
opts.banner = 'Usage: strong_versions [options]'
|
14
|
+
|
15
|
+
opts.on('-a', '--auto-correct', 'Auto-correct (use with caution)') do |_v|
|
16
|
+
options[:auto_correct] = true
|
17
|
+
end
|
18
|
+
|
19
|
+
opts.on('--no-auto-correct', 'Disable auto-correct') do |_v|
|
20
|
+
options[:auto_correct] = false
|
21
|
+
end
|
22
|
+
end.parse!
|
23
|
+
|
24
|
+
def dependencies
|
25
|
+
StrongVersions::DependencyFinder.new.dependencies
|
26
|
+
end
|
27
|
+
|
5
28
|
config_path = Bundler.root.join('.strong_versions.yml')
|
6
29
|
config = StrongVersions::Config.new(config_path)
|
7
|
-
|
8
|
-
valid = StrongVersions::Dependencies.new(dependencies).validate!(
|
30
|
+
validated = StrongVersions::Dependencies.new(dependencies).validate!(
|
9
31
|
except: config.exceptions,
|
10
|
-
on_failure: 'warn'
|
32
|
+
on_failure: 'warn',
|
33
|
+
auto_correct: options[:auto_correct]
|
11
34
|
)
|
12
35
|
|
13
|
-
|
36
|
+
if options[:auto_correct]
|
37
|
+
# Re-evaluate
|
38
|
+
args = original_args.map { |arg| Shellwords.escape(arg) }.join(' ')
|
39
|
+
exec "#{$PROGRAM_NAME} #{args} --no-auto-correct"
|
40
|
+
end
|
41
|
+
|
42
|
+
exit 0 if validated
|
14
43
|
exit 1
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
|
3
|
+
require 'strong_versions'
|
4
|
+
|
5
|
+
options = {}
|
6
|
+
OptionParser.new do |opts|
|
7
|
+
opts.banner = "Usage: strong_versions [options]"
|
8
|
+
|
9
|
+
opts.on("-a", "--auto-correct", "Auto-correct (use with caution)") do |v|
|
10
|
+
options[:auto_correct] = true
|
11
|
+
end
|
12
|
+
end.parse!
|
13
|
+
|
14
|
+
def dependencies
|
15
|
+
StrongVersions::DependencyFinder.new.dependencies
|
16
|
+
end
|
17
|
+
|
18
|
+
config_path = Bundler.root.join('.strong_versions.yml')
|
19
|
+
config = StrongVersions::Config.new(config_path)
|
20
|
+
validated = StrongVersions::Dependencies.new(dependencies).validate!(
|
21
|
+
except: config.exceptions,
|
22
|
+
on_failure: 'warn',
|
23
|
+
auto_correct: options[:auto_correct]
|
24
|
+
)
|
25
|
+
|
26
|
+
revalidated = false
|
27
|
+
revalidated = StrongVersions::Dependencies.new(dependencies).validate!(
|
28
|
+
except: config.exceptions,
|
29
|
+
on_failure: 'warn',
|
30
|
+
auto_correct: false
|
31
|
+
) if options[:auto_correct]
|
32
|
+
|
33
|
+
exit 0 if validated or revalidated
|
34
|
+
exit 1
|
35
|
+
|
data/config/locales/en.yml
CHANGED
@@ -13,3 +13,4 @@ en:
|
|
13
13
|
unknown_on_failure: "StrongVersions: Unknown value for `on_failure` in .strong_versions.yml: '%{on_failure}'. Expected one of %{expected}"
|
14
14
|
suggested: 'Suggested: '
|
15
15
|
version_not_specified: "[not specified]"
|
16
|
+
no-suggestion: "[unable to detect installed gem version]"
|
Binary file
|
data/lib/strong_versions.rb
CHANGED
@@ -9,6 +9,7 @@ require 'strong_versions/config'
|
|
9
9
|
require 'strong_versions/dependency'
|
10
10
|
require 'strong_versions/dependency_finder'
|
11
11
|
require 'strong_versions/dependencies'
|
12
|
+
require 'strong_versions/errors'
|
12
13
|
require 'strong_versions/suggestion'
|
13
14
|
require 'strong_versions/terminal'
|
14
15
|
require 'strong_versions/version'
|
@@ -11,19 +11,23 @@ module StrongVersions
|
|
11
11
|
end
|
12
12
|
|
13
13
|
def validate!(options = {})
|
14
|
+
auto_correct = options.delete(:auto_correct) { false }
|
14
15
|
if validate(options)
|
15
16
|
summary
|
16
17
|
return true
|
17
18
|
end
|
18
19
|
|
20
|
+
return update_gemfile if auto_correct
|
21
|
+
|
19
22
|
raise_or_warn(options.fetch(:on_failure, 'raise'))
|
20
23
|
summary
|
21
24
|
false
|
22
25
|
end
|
23
26
|
|
24
27
|
def validate(options = {})
|
28
|
+
unsafe_autocorrect_error if options[:auto_correct]
|
25
29
|
@dependencies.each do |dependency|
|
26
|
-
next if options.fetch(:except).include?(dependency.name)
|
30
|
+
next if options.fetch(:except, []).include?(dependency.name)
|
27
31
|
next if dependency.valid?
|
28
32
|
|
29
33
|
@invalid_gems.push(dependency) unless dependency.valid?
|
@@ -33,10 +37,48 @@ module StrongVersions
|
|
33
37
|
|
34
38
|
private
|
35
39
|
|
40
|
+
def unsafe_autocorrect_error
|
41
|
+
raise UnsafeAutoCorrectError, 'Must use #validate! for autocorrect'
|
42
|
+
end
|
43
|
+
|
36
44
|
def summary
|
37
45
|
@terminal.summary(@dependencies.size, @invalid_gems.size)
|
38
46
|
end
|
39
47
|
|
48
|
+
def update_gemfile
|
49
|
+
updated = 0
|
50
|
+
@dependencies.each do |dependency|
|
51
|
+
next unless dependency.updatable?
|
52
|
+
|
53
|
+
updated += 1 if update_dependency(dependency)
|
54
|
+
end
|
55
|
+
@terminal.update_summary(updated)
|
56
|
+
end
|
57
|
+
|
58
|
+
def update_dependency(dependency)
|
59
|
+
path = dependency.gemfile
|
60
|
+
content = File.read(path)
|
61
|
+
update = replace_gem_definition(dependency, content)
|
62
|
+
return false if content == update
|
63
|
+
|
64
|
+
File.write(path, update)
|
65
|
+
@terminal.gem_update(path, dependency)
|
66
|
+
true
|
67
|
+
end
|
68
|
+
|
69
|
+
def replace_gem_definition(dependency, content)
|
70
|
+
regex = gem_regex(dependency.name)
|
71
|
+
match = content.match(regex)
|
72
|
+
return content unless match
|
73
|
+
|
74
|
+
indent = match.captures.first
|
75
|
+
content.gsub(regex, "#{indent}#{dependency.suggested_definition}")
|
76
|
+
end
|
77
|
+
|
78
|
+
def gem_regex(name)
|
79
|
+
/^(\s*)gem\s+['"]#{name}['"].*$/
|
80
|
+
end
|
81
|
+
|
40
82
|
def raise_or_warn(on_failure)
|
41
83
|
case on_failure
|
42
84
|
when 'raise'
|
@@ -10,9 +10,11 @@ module StrongVersions
|
|
10
10
|
@errors = []
|
11
11
|
@lockfile = lockfile || default_lockfile
|
12
12
|
|
13
|
-
versions.each
|
14
|
-
|
15
|
-
|
13
|
+
versions.each { |operator, version| validate_version(operator, version) }
|
14
|
+
end
|
15
|
+
|
16
|
+
def gemfile
|
17
|
+
Pathname.new(@dependency.gemfile) if @dependency.respond_to?(:gemfile)
|
16
18
|
end
|
17
19
|
|
18
20
|
def valid?
|
@@ -23,16 +25,31 @@ module StrongVersions
|
|
23
25
|
Suggestion.new(lockfile_version)
|
24
26
|
end
|
25
27
|
|
28
|
+
def suggested_definition
|
29
|
+
guards = guard_versions.map { |op, version| "'#{op} #{version}'" }
|
30
|
+
"gem '#{@name}', #{[suggestion, *guards].join(', ')}"
|
31
|
+
end
|
32
|
+
|
26
33
|
def definition
|
27
|
-
versions.map
|
34
|
+
versions.map do |operator, version|
|
35
|
+
next t('version_not_specified') if operator == '>=' && version == '0'
|
36
|
+
|
37
|
+
"'#{operator} #{version}'"
|
38
|
+
end.join(', ')
|
39
|
+
end
|
40
|
+
|
41
|
+
def updatable?
|
42
|
+
gemfile && !suggestion.missing? && !path_source?
|
28
43
|
end
|
29
44
|
|
30
45
|
private
|
31
46
|
|
32
47
|
def versions
|
33
|
-
@dependency.requirements_list.map
|
34
|
-
|
35
|
-
|
48
|
+
@dependency.requirements_list.map { |version| parse_version(version) }
|
49
|
+
end
|
50
|
+
|
51
|
+
def guard_versions
|
52
|
+
versions.reject { |op, version| redundant?(op, version) }
|
36
53
|
end
|
37
54
|
|
38
55
|
def parse_version(requirement)
|
@@ -76,14 +93,25 @@ module StrongVersions
|
|
76
93
|
def check_valid_version(version)
|
77
94
|
return if valid_version?(version)
|
78
95
|
|
79
|
-
value =
|
80
|
-
I18n.t('strong_versions.version_not_specified')
|
81
|
-
else
|
82
|
-
version
|
83
|
-
end
|
96
|
+
value = version == '0' ? t('version_not_specified') : version
|
84
97
|
@errors << { type: :version, value: value }
|
85
98
|
end
|
86
99
|
|
100
|
+
def redundant?(operator, version)
|
101
|
+
return false unless operator.start_with?('>') || pessimistic?(operator)
|
102
|
+
|
103
|
+
multiply_version(version) <= multiply_version(suggestion.version)
|
104
|
+
end
|
105
|
+
|
106
|
+
def multiply_version(version)
|
107
|
+
# Support extremely precise versions e.g. '1.2.3.4.5.6.7.8.9'
|
108
|
+
components = version.split('.').map(&:to_i)
|
109
|
+
components += [0] * (10 - components.size)
|
110
|
+
components.reverse.each_with_index.map do |component, index|
|
111
|
+
component * 10.pow(index + 1)
|
112
|
+
end.sum
|
113
|
+
end
|
114
|
+
|
87
115
|
def pessimistic?(operator)
|
88
116
|
operator == '~>'
|
89
117
|
end
|
@@ -107,14 +135,8 @@ module StrongVersions
|
|
107
135
|
@dependency.source.is_a?(Bundler::Source::Path)
|
108
136
|
end
|
109
137
|
|
110
|
-
def
|
111
|
-
|
112
|
-
end
|
113
|
-
|
114
|
-
def any_pessimistic?
|
115
|
-
versions.any? do |_version, operator|
|
116
|
-
%w[< <= ~>].include?(operator)
|
117
|
-
end
|
138
|
+
def t(name)
|
139
|
+
I18n.t("strong_versions.#{name}")
|
118
140
|
end
|
119
141
|
end
|
120
142
|
end
|
@@ -3,7 +3,11 @@
|
|
3
3
|
module StrongVersions
|
4
4
|
class Suggestion
|
5
5
|
def initialize(version)
|
6
|
-
|
6
|
+
return if version.nil?
|
7
|
+
|
8
|
+
@parts = version.split('.')
|
9
|
+
# Treat '4.3.2.1' as '4.3.2'
|
10
|
+
@parts.pop if standard?(@parts.first(3)) && @parts.size == 4
|
7
11
|
end
|
8
12
|
|
9
13
|
def to_s
|
@@ -12,6 +16,16 @@ module StrongVersions
|
|
12
16
|
"'~> #{version}'"
|
13
17
|
end
|
14
18
|
|
19
|
+
def version
|
20
|
+
return nil unless standard?
|
21
|
+
|
22
|
+
major, minor, patch = @parts
|
23
|
+
return "#{major}.#{minor}" if stable?
|
24
|
+
return "#{major}.#{minor}.#{patch}" if unstable?
|
25
|
+
|
26
|
+
raise 'Unexpected condition met'
|
27
|
+
end
|
28
|
+
|
15
29
|
def missing?
|
16
30
|
return false if stable?
|
17
31
|
return false if unstable?
|
@@ -21,15 +35,6 @@ module StrongVersions
|
|
21
35
|
|
22
36
|
private
|
23
37
|
|
24
|
-
def version
|
25
|
-
major, minor, patch = @parts if standard?
|
26
|
-
|
27
|
-
return "#{major}.#{minor}" if stable?
|
28
|
-
return "#{major}.#{minor}.#{patch}" if unstable?
|
29
|
-
|
30
|
-
nil
|
31
|
-
end
|
32
|
-
|
33
38
|
def unstable?
|
34
39
|
standard? && @parts.first.to_i.zero?
|
35
40
|
end
|
@@ -38,12 +43,13 @@ module StrongVersions
|
|
38
43
|
standard? && @parts.first.to_i >= 1
|
39
44
|
end
|
40
45
|
|
41
|
-
def standard?
|
42
|
-
return false if
|
43
|
-
return false unless @parts.size == 3
|
46
|
+
def standard?(parts = @parts)
|
47
|
+
return false if parts.nil?
|
44
48
|
return false unless numeric?
|
49
|
+
return true if [2, 3].include?(parts.size)
|
50
|
+
return true if parts.size == 3 && unstable?
|
45
51
|
|
46
|
-
|
52
|
+
false
|
47
53
|
end
|
48
54
|
|
49
55
|
def numeric?
|
@@ -7,7 +7,27 @@ module StrongVersions
|
|
7
7
|
end
|
8
8
|
|
9
9
|
def warn(string)
|
10
|
-
puts(color(string, :
|
10
|
+
puts(color(string, :bright, :red))
|
11
|
+
end
|
12
|
+
|
13
|
+
def gem_update(path, gem)
|
14
|
+
relpath = path.relative_path_from(Pathname.new(Dir.pwd))
|
15
|
+
output = [
|
16
|
+
color("[#{relpath}] ", :cyan),
|
17
|
+
color(gem.suggested_definition, :green),
|
18
|
+
color(' (was: ', :default),
|
19
|
+
color(gem.definition, :red),
|
20
|
+
color(')', :default)
|
21
|
+
].join
|
22
|
+
puts(output)
|
23
|
+
end
|
24
|
+
|
25
|
+
def update_summary(updated)
|
26
|
+
output = [
|
27
|
+
"#{updated} gem definitions ",
|
28
|
+
color('updated', :green)
|
29
|
+
].join
|
30
|
+
puts("\n#{output}")
|
11
31
|
end
|
12
32
|
|
13
33
|
def summary(count, failed)
|
@@ -67,13 +87,15 @@ module StrongVersions
|
|
67
87
|
|
68
88
|
def suggestion(gem)
|
69
89
|
suggested = ' ' + t('errors.suggested')
|
70
|
-
puts(
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
)
|
90
|
+
puts(color("#{suggested}%{suggestion}", :default,
|
91
|
+
suggestion: suggestion_definition(gem)))
|
92
|
+
end
|
93
|
+
|
94
|
+
def suggestion_definition(gem)
|
95
|
+
unidentified = gem.suggestion.to_s.empty?
|
96
|
+
return [t('no-suggestion'), :yellow] if unidentified
|
97
|
+
|
98
|
+
[gem.suggestion.to_s, :green]
|
77
99
|
end
|
78
100
|
|
79
101
|
def name_and_definition(gem)
|
data/strong_versions.gemspec
CHANGED
@@ -8,7 +8,7 @@ Gem::Specification.new do |spec|
|
|
8
8
|
spec.name = 'strong_versions'
|
9
9
|
spec.version = StrongVersions::VERSION
|
10
10
|
spec.authors = ['Bob Farrell']
|
11
|
-
spec.email = ['
|
11
|
+
spec.email = ['oss@bob.frl']
|
12
12
|
|
13
13
|
spec.summary = 'Enforce strict versioning on your Gemfile'
|
14
14
|
spec.description = 'Ensure your gems are appropriately versioned'
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: strong_versions
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Bob Farrell
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-01-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: i18n
|
@@ -138,7 +138,7 @@ dependencies:
|
|
138
138
|
version: 0.60.0
|
139
139
|
description: Ensure your gems are appropriately versioned
|
140
140
|
email:
|
141
|
-
-
|
141
|
+
- oss@bob.frl
|
142
142
|
executables:
|
143
143
|
- strong_versions
|
144
144
|
extensions: []
|
@@ -161,6 +161,7 @@ files:
|
|
161
161
|
- bin/rubocop
|
162
162
|
- bin/setup
|
163
163
|
- bin/strong_versions
|
164
|
+
- bin/strong_versions.rb
|
164
165
|
- config/locales/en.yml
|
165
166
|
- doc/images/ci-pipeline.png
|
166
167
|
- doc/images/strong-versions-example.png
|
@@ -169,6 +170,7 @@ files:
|
|
169
170
|
- lib/strong_versions/dependencies.rb
|
170
171
|
- lib/strong_versions/dependency.rb
|
171
172
|
- lib/strong_versions/dependency_finder.rb
|
173
|
+
- lib/strong_versions/errors.rb
|
172
174
|
- lib/strong_versions/suggestion.rb
|
173
175
|
- lib/strong_versions/terminal.rb
|
174
176
|
- lib/strong_versions/version.rb
|