workarea-upgrade 3.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.editorconfig +20 -0
- data/.github/ISSUE_TEMPLATE/bug_report.md +37 -0
- data/.github/ISSUE_TEMPLATE/documentation-request.md +17 -0
- data/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
- data/.gitignore +11 -0
- data/.rspec +2 -0
- data/.rubocop.yml +327 -0
- data/CHANGELOG.md +285 -0
- data/Gemfile +17 -0
- data/README.md +210 -0
- data/Rakefile +30 -0
- data/bin/rails +18 -0
- data/exe/workarea_upgrade +7 -0
- data/lib/workarea/upgrade.rb +26 -0
- data/lib/workarea/upgrade/cli.rb +289 -0
- data/lib/workarea/upgrade/diff.rb +87 -0
- data/lib/workarea/upgrade/diff/current_app.rb +15 -0
- data/lib/workarea/upgrade/diff/gem_diff.rb +99 -0
- data/lib/workarea/upgrade/diff/workarea_file.rb +43 -0
- data/lib/workarea/upgrade/engine.rb +12 -0
- data/lib/workarea/upgrade/gemfile.rb +102 -0
- data/lib/workarea/upgrade/report.rb +176 -0
- data/lib/workarea/upgrade/report_card.rb +94 -0
- data/lib/workarea/upgrade/version.rb +5 -0
- data/workarea-upgrade.gemspec +25 -0
- metadata +112 -0
@@ -0,0 +1,99 @@
|
|
1
|
+
module Workarea
|
2
|
+
module Upgrade
|
3
|
+
class Diff
|
4
|
+
class GemDiff
|
5
|
+
def initialize(from_root, to_root, options = {})
|
6
|
+
@from_root = from_root
|
7
|
+
@to_root = to_root
|
8
|
+
@options = options
|
9
|
+
end
|
10
|
+
|
11
|
+
def all
|
12
|
+
@all ||= from_files.reduce([]) do |results, from_file|
|
13
|
+
if to_file = find_to_file(from_file.relative_path)
|
14
|
+
diff = diff_files(from_file, to_file)
|
15
|
+
results << diff unless diff.blank?
|
16
|
+
end
|
17
|
+
|
18
|
+
results
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def added
|
23
|
+
@added ||= to_files.reduce([]) do |results, to_file|
|
24
|
+
from_file = find_from_file(to_file.relative_path)
|
25
|
+
results << to_file.relative_path if from_file.blank?
|
26
|
+
results
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def removed
|
31
|
+
@removed ||= from_files.reduce([]) do |results, from_file|
|
32
|
+
to_file = find_to_file(from_file.relative_path)
|
33
|
+
results << from_file.relative_path if to_file.blank?
|
34
|
+
results
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def overridden
|
39
|
+
@overridden ||= find_app_diff(CurrentApp.files)
|
40
|
+
end
|
41
|
+
|
42
|
+
def decorated
|
43
|
+
@decorated ||= find_app_diff(CurrentApp.decorators)
|
44
|
+
end
|
45
|
+
|
46
|
+
def customized_files
|
47
|
+
CurrentApp.files.map(&:relative_path) +
|
48
|
+
CurrentApp.decorators.map(&:relative_path)
|
49
|
+
end
|
50
|
+
|
51
|
+
def for_current_app
|
52
|
+
@for_current_app ||= overridden + decorated
|
53
|
+
end
|
54
|
+
|
55
|
+
def from_files
|
56
|
+
@from_files ||= WorkareaFile.find_files(@from_root)
|
57
|
+
end
|
58
|
+
|
59
|
+
def to_files
|
60
|
+
@to_files ||= WorkareaFile.find_files(@to_root)
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def diff_files(from_file, to_file)
|
66
|
+
Diffy::Diff.new(
|
67
|
+
from_file.absolute_path,
|
68
|
+
to_file.absolute_path,
|
69
|
+
source: 'files',
|
70
|
+
context: @options[:context].presence || 5,
|
71
|
+
include_diff_info: true
|
72
|
+
).to_s(@options[:format].presence || :text)
|
73
|
+
end
|
74
|
+
|
75
|
+
def find_from_file(relative_path)
|
76
|
+
from_files.detect { |file| file.relative_path == relative_path }
|
77
|
+
end
|
78
|
+
|
79
|
+
def find_to_file(relative_path)
|
80
|
+
to_files.detect { |file| file.relative_path == relative_path }
|
81
|
+
end
|
82
|
+
|
83
|
+
def find_app_diff(files)
|
84
|
+
files.reduce([]) do |results, decorated_file|
|
85
|
+
from_file = find_from_file(decorated_file.relative_path)
|
86
|
+
to_file = find_to_file(decorated_file.relative_path)
|
87
|
+
|
88
|
+
if from_file.present? && to_file.present?
|
89
|
+
diff = diff_files(from_file, to_file)
|
90
|
+
results << diff unless diff.blank?
|
91
|
+
end
|
92
|
+
|
93
|
+
results
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Workarea
|
2
|
+
module Upgrade
|
3
|
+
class Diff
|
4
|
+
class WorkareaFile
|
5
|
+
attr_reader :relative_path
|
6
|
+
|
7
|
+
def self.find_files(root)
|
8
|
+
Dir.glob("#{root}/**/*")
|
9
|
+
.map { |f| f.gsub("#{root}/", '') }
|
10
|
+
.select do |relative|
|
11
|
+
!File.directory?("#{root}/#{relative}") &&
|
12
|
+
!relative.include?('decorators/') &&
|
13
|
+
(relative.include?('app/') || relative.include?('lib/')) &&
|
14
|
+
!relative.end_with?('.decorator')
|
15
|
+
end
|
16
|
+
.map { |relative| new(root, relative) }
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.find_decorators(root)
|
20
|
+
Dir.glob("#{root}/app/decorators/**/*")
|
21
|
+
.reject { |file| File.directory?(file) }
|
22
|
+
.map { |file| file.gsub(root, '') }
|
23
|
+
.map { |file| file.gsub('/app/decorators', 'app') }
|
24
|
+
.map { |file| file.gsub('_decorator.rb', '.rb') }
|
25
|
+
.map { |file| new(nil, file) } +
|
26
|
+
Dir.glob("#{root}/app/**/*.decorator")
|
27
|
+
.map { |file| file.gsub("#{root}/", '') }
|
28
|
+
.map { |file| file.gsub('.decorator', '.rb') }
|
29
|
+
.map { |file| new(nil, file) }
|
30
|
+
end
|
31
|
+
|
32
|
+
def initialize(root, relative_path)
|
33
|
+
@root = root
|
34
|
+
@relative_path = relative_path
|
35
|
+
end
|
36
|
+
|
37
|
+
def absolute_path
|
38
|
+
"#{@root}/#{@relative_path}"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
module Workarea
|
2
|
+
module Upgrade
|
3
|
+
class Gemfile
|
4
|
+
def initialize(filename = 'Gemfile')
|
5
|
+
@filename = filename
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.diff(gemfile, gemfile_next)
|
9
|
+
gemfile_next.installed.reject do |gem|
|
10
|
+
gemfile.installed.to_h[gem.first] != gem.second
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def exist?
|
15
|
+
File.exist?(@filename)
|
16
|
+
end
|
17
|
+
|
18
|
+
def lockfile_exist?
|
19
|
+
File.exist?("#{@filename}.lock")
|
20
|
+
end
|
21
|
+
|
22
|
+
def workarea
|
23
|
+
installed.select { |g| g.first == 'workarea' }
|
24
|
+
end
|
25
|
+
|
26
|
+
def workarea_version
|
27
|
+
workarea.first.last
|
28
|
+
end
|
29
|
+
|
30
|
+
def plugins(ignored = [])
|
31
|
+
installed.reject do |gem|
|
32
|
+
gem.first == 'workarea' || ignored.include?(gem.first)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def check_install
|
37
|
+
Bundler.with_original_env do
|
38
|
+
system("bundle install --gemfile #{@filename} > /dev/null")
|
39
|
+
$? == 0
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def install!
|
44
|
+
Bundler.with_original_env do
|
45
|
+
`bundle install --gemfile #{@filename}`
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def installed
|
50
|
+
@installed ||=
|
51
|
+
install!
|
52
|
+
.split("\n")
|
53
|
+
.select { |g| g.start_with?('Using workarea') }
|
54
|
+
.map(&:split)
|
55
|
+
.map { |g| [g[1], g[2]] }
|
56
|
+
.reject { |g| core_engines.include?(g.first) }
|
57
|
+
end
|
58
|
+
|
59
|
+
def all_gems(ignored = [])
|
60
|
+
installed.reject { |g| ignored.include?(g.first) }
|
61
|
+
end
|
62
|
+
|
63
|
+
def outdated
|
64
|
+
@outdated ||=
|
65
|
+
Bundler.with_original_env do
|
66
|
+
`bundle outdated --parseable`
|
67
|
+
.split("\n")
|
68
|
+
.select { |g| g.start_with?('workarea') }
|
69
|
+
.map { |g| g.gsub(/([^\s]*)\s*\(newest\s([^,]*).*/, '\\1|\\2') }
|
70
|
+
.map { |g| g.split('|') }
|
71
|
+
.reject { |g| core_engines.include?(g.first) }
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def list
|
76
|
+
@list ||=
|
77
|
+
Bundler.with_original_env do
|
78
|
+
`bundle list`
|
79
|
+
.split("\n")
|
80
|
+
.select { |g| g.start_with?(' * workarea') }
|
81
|
+
.map { |g| g.gsub(/[ *]+([^\s]*)\s*\(([^)]*).*/, '\\1|\\2') }
|
82
|
+
.map { |g| g.split('|') }
|
83
|
+
.reject { |g| core_engines.include?(g.first) }
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
def core_engines
|
90
|
+
%w[
|
91
|
+
admin
|
92
|
+
ci
|
93
|
+
core
|
94
|
+
storefront
|
95
|
+
testing
|
96
|
+
api-admin
|
97
|
+
api-storefront
|
98
|
+
].map { |e| "workarea-#{e}" }
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,176 @@
|
|
1
|
+
module Workarea
|
2
|
+
module Upgrade
|
3
|
+
class Report
|
4
|
+
CATEGORIES = %w(
|
5
|
+
assets
|
6
|
+
controllers
|
7
|
+
helpers
|
8
|
+
mailers
|
9
|
+
middleware
|
10
|
+
models
|
11
|
+
queries
|
12
|
+
seeds
|
13
|
+
services
|
14
|
+
view_models
|
15
|
+
views
|
16
|
+
workers
|
17
|
+
)
|
18
|
+
|
19
|
+
def initialize(diff)
|
20
|
+
@diff = diff
|
21
|
+
end
|
22
|
+
|
23
|
+
def results
|
24
|
+
CATEGORIES.inject({}) do |memo, category|
|
25
|
+
memo[category] = calculate_grade(category)
|
26
|
+
memo
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def customized_percents
|
31
|
+
@customized_percents ||= CATEGORIES.inject({}) do |memo, category|
|
32
|
+
percent_customized = customized_totals[category] / changed_totals[category].to_f
|
33
|
+
percent_customized *= 100
|
34
|
+
|
35
|
+
memo[category] = percent_customized.nan? ? 0 : percent_customized.round
|
36
|
+
memo
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# If a file was decorated or overridden and removed, this is the
|
41
|
+
# biggest pain point.
|
42
|
+
def worst_files
|
43
|
+
@worst_files ||= CATEGORIES.inject({}) do |memo, category|
|
44
|
+
memo[category] = customized_files_now_missing
|
45
|
+
.select { |f| f.include?(category) }
|
46
|
+
.count
|
47
|
+
|
48
|
+
memo
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def changed_totals
|
53
|
+
@changed_totals ||= CATEGORIES.inject({}) do |memo, category|
|
54
|
+
memo[category] = @diff.all.select { |f| f.include?(category) }.count
|
55
|
+
memo
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def customized_totals
|
60
|
+
@customized_totals ||= CATEGORIES.inject({}) do |memo, category|
|
61
|
+
memo[category] = @diff.for_current_app.select { |f| f.include?(category) }.count
|
62
|
+
memo
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def calculate_grade(category)
|
67
|
+
customized_total = customized_totals[category]
|
68
|
+
return 'A' if customized_total <= 3
|
69
|
+
|
70
|
+
percent_customized = customized_percents[category]
|
71
|
+
return 'A' if percent_customized < 5
|
72
|
+
|
73
|
+
score = worst_files[category]
|
74
|
+
score += percent_customized
|
75
|
+
|
76
|
+
if score.between?(0, 9)
|
77
|
+
'A'
|
78
|
+
elsif score.between?(10, 24)
|
79
|
+
'B'
|
80
|
+
elsif score.between?(25, 34)
|
81
|
+
'C'
|
82
|
+
elsif score.between?(35, 44)
|
83
|
+
'D'
|
84
|
+
else
|
85
|
+
'F'
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def diff_stats
|
90
|
+
[
|
91
|
+
{
|
92
|
+
status: ">>> #{pluralize(@diff.all.length, 'file')}",
|
93
|
+
message: 'modified in Workarea',
|
94
|
+
color: :yellow
|
95
|
+
},
|
96
|
+
{
|
97
|
+
status: ">>> #{pluralize(@diff.overridden.length, 'file')}",
|
98
|
+
message: 'overridden in your application may be be affected',
|
99
|
+
color: :yellow
|
100
|
+
},
|
101
|
+
{
|
102
|
+
status: ">>> #{pluralize(@diff.decorated.length, 'file')}",
|
103
|
+
message: 'decorated in your application may be be affected',
|
104
|
+
color: :yellow
|
105
|
+
},
|
106
|
+
{
|
107
|
+
status: "+++ #{pluralize(@diff.added.length, "file")}",
|
108
|
+
message: 'added to Workarea',
|
109
|
+
color: :green
|
110
|
+
},
|
111
|
+
{
|
112
|
+
status: "--- #{pluralize(@diff.removed.length, 'file')}",
|
113
|
+
message: 'removed from Workarea',
|
114
|
+
color: :red
|
115
|
+
}
|
116
|
+
]
|
117
|
+
end
|
118
|
+
|
119
|
+
def report_card_stats
|
120
|
+
results.map do |category, grade|
|
121
|
+
color = :yellow
|
122
|
+
color = :green if grade.in?(%w(A B))
|
123
|
+
color = :red if grade == 'F'
|
124
|
+
|
125
|
+
{
|
126
|
+
status: "Grade: #{grade}",
|
127
|
+
message: category,
|
128
|
+
color: color
|
129
|
+
}
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def breakdown_customized_stats
|
134
|
+
results
|
135
|
+
.reject { |category, _grade| customized_totals[category] == 0 }
|
136
|
+
.map do |category, _grade|
|
137
|
+
{
|
138
|
+
status: category,
|
139
|
+
message: <<~MESSAGE.gsub(/\n/, ' '),
|
140
|
+
#{pluralize(customized_totals[category], 'file')}
|
141
|
+
(#{customized_percents[category]}%) overridden or decorated
|
142
|
+
in this application have been changed in Workarea
|
143
|
+
MESSAGE
|
144
|
+
color: :yellow
|
145
|
+
}
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def breakdown_worst_files_stats
|
150
|
+
results
|
151
|
+
.reject { |category, _grade| worst_files[category] == 0 }
|
152
|
+
.map do |category, _grade|
|
153
|
+
{
|
154
|
+
status: category,
|
155
|
+
message: <<~MESSAGE.gsub(/\n/, ' '),
|
156
|
+
#{pluralize(worst_files[category], 'file')} overridden or
|
157
|
+
decorated in this application have been moved or removed from
|
158
|
+
Workarea
|
159
|
+
MESSAGE
|
160
|
+
color: :red
|
161
|
+
}
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
private
|
166
|
+
|
167
|
+
def customized_files_now_missing
|
168
|
+
@diff.customized_files & @diff.removed
|
169
|
+
end
|
170
|
+
|
171
|
+
def pluralize(*args)
|
172
|
+
ActionController::Base.helpers.pluralize(*args)
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|