workarea-upgrade 3.0.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 +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
|