feature_map 1.2.4 → 1.2.6

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.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +4 -4
  3. data/bin/bundle +115 -0
  4. data/bin/docs +19 -1
  5. data/bin/featuremap +3 -0
  6. data/bin/jekyll +27 -0
  7. data/bin/readme +1 -0
  8. data/bin/rspec +27 -0
  9. data/bin/rubocop +27 -0
  10. data/lib/feature_map/cli.rb +45 -7
  11. data/lib/feature_map/code_features.rb +1 -0
  12. data/lib/feature_map/commit.rb +1 -0
  13. data/lib/feature_map/configuration.rb +1 -0
  14. data/lib/feature_map/constants.rb +1 -0
  15. data/lib/feature_map/mapper.rb +1 -0
  16. data/lib/feature_map/output_color.rb +1 -0
  17. data/lib/feature_map/private/additional_metrics_file.rb +1 -0
  18. data/lib/feature_map/private/assignment_applicator.rb +1 -0
  19. data/lib/feature_map/private/assignment_mappers/feature_definition_assignment.rb +14 -0
  20. data/lib/feature_map/private/assignments_file.rb +1 -0
  21. data/lib/feature_map/private/code_cov.rb +1 -0
  22. data/lib/feature_map/private/cyclomatic_complexity_calculator.rb +1 -0
  23. data/lib/feature_map/private/docs/index.html +99 -94
  24. data/lib/feature_map/private/documentation_site.rb +1 -0
  25. data/lib/feature_map/private/extension_loader.rb +1 -0
  26. data/lib/feature_map/private/feature_assigner.rb +1 -0
  27. data/lib/feature_map/private/feature_metrics_calculator.rb +1 -0
  28. data/lib/feature_map/private/feature_plugins/assignment.rb +1 -0
  29. data/lib/feature_map/private/glob_cache.rb +1 -0
  30. data/lib/feature_map/private/health_calculator.rb +1 -0
  31. data/lib/feature_map/private/lines_of_code_calculator.rb +1 -0
  32. data/lib/feature_map/private/metrics_file.rb +1 -0
  33. data/lib/feature_map/private/percentile_metrics_calculator.rb +1 -0
  34. data/lib/feature_map/private/release_notification_builder.rb +1 -0
  35. data/lib/feature_map/private/simple_cov_resultsets.rb +30 -0
  36. data/lib/feature_map/private/test_coverage_file.rb +1 -0
  37. data/lib/feature_map/private/test_pyramid/jest_mapper.rb +44 -0
  38. data/lib/feature_map/private/test_pyramid/mapper.rb +33 -0
  39. data/lib/feature_map/private/test_pyramid/rspec_mapper.rb +54 -0
  40. data/lib/feature_map/private/test_pyramid_file.rb +12 -56
  41. data/lib/feature_map/private/todo_inspector.rb +1 -0
  42. data/lib/feature_map/private.rb +20 -5
  43. data/lib/feature_map/validator.rb +1 -0
  44. data/lib/feature_map.rb +5 -0
  45. metadata +24 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '097b75ea1f00eafc127a2e43452ae185b1e3682d209c39ec96ab07c3ef75fe20'
4
- data.tar.gz: eefa91bf65f4c9abcbddc8924bc80597596522f9d9a489ff60d02ed21a7ab77e
3
+ metadata.gz: 20d96ad4e61fb28fc9891cf32dd7e6102dbfb6d48d4a114973390cb5fc2fc805
4
+ data.tar.gz: 951a84a6a993a8b03b7899c51b5e55e3ab3fcf356f90e122574658a3696da5cd
5
5
  SHA512:
6
- metadata.gz: 3ae23faf8405ea7436cdd10cc20c08673bff4447997eef2849ba3e2261c9edf50be6715ccbe0829adfe099fd91c7f18e829d29dd11aefe8238ee5bc126fa55a2
7
- data.tar.gz: 43c0a7372afa58dbb1bc301f2331ef9f6fe51d466d90e9223953ee7050548251ad6512dd7e62b5d41fe266280e6d1d02735651a468bb10db76bab208eaaeaab3
6
+ metadata.gz: 3256049f99d24a887bc0644cbb64a1c7bcd8a305b23b019a864d68f84c101b342e742d236dfe85d1739cbc88dc4a42130b7fc25e5fc4de744a939f2ea3a77934
7
+ data.tar.gz: 57f6b8091fa32823b5f199c0f0cad7fa8b0be3b15eabb9ecee7f16e2357be7234cd2931a8f2a6ecf984792b7a56103f9956a3241c2cae9130c0cfe48e9baf7da
data/README.md CHANGED
@@ -31,8 +31,8 @@ Contributions are welcome and appreciated. Here's how to get started:
31
31
 
32
32
  - clone repo: `$ git clone git@github.com:Beyond-Finance/feature_map.git`
33
33
  - install dependencies: `$ bundle install`
34
- - run tests: `$ bundle exec rspec`
35
- - run Rubocop: `$ bundle exec rubocop`
34
+ - run tests: `$ bin/rspec`
35
+ - run Rubocop: `$ bin/rubocop`
36
36
 
37
37
  That's it! Assuming you can complete all of these steps without any error or issues, you should be good to go.
38
38
 
@@ -42,13 +42,13 @@ The documentation site is a React application which is built on the Vite framewo
42
42
 
43
43
  Compilation of the build asset is done via `npm run build` from within the [docs](./docs) folder. This compiles the React app into a single static file which is placed in [./lib/feature_map/private/docs/index.html](./lib/feature_map/private/docs/index.html]).
44
44
 
45
- The documentation site may be run locally to aid in development via `bin/docs`. It uses sample data found in [docs/src/data/sample_config.js](./docs/src/data/sample_config.js).
45
+ The documentation site may be run locally to aid in development via `bin/docs`. It will generate test coverage and metrics data, and make it available to the docs site running in development mode.
46
46
 
47
47
  More information on the development of the documentation site may be found in the [Docs Readme](./docs/README.md).
48
48
 
49
49
  ### README Site
50
50
 
51
- The README site is built with Jekyll and TailwindCSS and is hosted via GitHub Pages at: https://beyond-finance.github.io/feature_map. It can be run locally to aid in development via `bin/readme`.
51
+ The README site is built with Jekyll and TailwindCSS and is hosted via GitHub Pages at: [https://beyond-finance.github.io/feature_map](https://beyond-finance.github.io/feature_map/). It can be run locally to aid in development via `bin/readme`.
52
52
 
53
53
  ### Publication
54
54
 
data/bin/bundle ADDED
@@ -0,0 +1,115 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'bundle' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ require 'rubygems'
12
+
13
+ m = Module.new do
14
+ module_function
15
+
16
+ def invoked_as_script?
17
+ File.expand_path($PROGRAM_NAME) == File.expand_path(__FILE__)
18
+ end
19
+
20
+ def env_var_version
21
+ ENV.fetch('BUNDLER_VERSION', nil)
22
+ end
23
+
24
+ def cli_arg_version
25
+ return unless invoked_as_script? # don't want to hijack other binstubs
26
+ return unless 'update'.start_with?(ARGV.first || ' ') # must be running `bundle update`
27
+
28
+ bundler_version = nil
29
+ update_index = nil
30
+ ARGV.each_with_index do |a, i|
31
+ if update_index && update_index.succ == i && a =~ Gem::Version::ANCHORED_VERSION_PATTERN
32
+ bundler_version = a
33
+ end
34
+ next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/
35
+
36
+ bundler_version = Regexp.last_match(1)
37
+ update_index = i
38
+ end
39
+ bundler_version
40
+ end
41
+
42
+ def gemfile
43
+ gemfile = ENV.fetch('BUNDLE_GEMFILE', nil)
44
+ return gemfile if gemfile && !gemfile.empty?
45
+
46
+ File.expand_path('../Gemfile', __dir__)
47
+ end
48
+
49
+ def lockfile
50
+ lockfile =
51
+ case File.basename(gemfile)
52
+ when 'gems.rb' then gemfile.sub(/\.rb$/, '.locked')
53
+ else "#{gemfile}.lock"
54
+ end
55
+ File.expand_path(lockfile)
56
+ end
57
+
58
+ def lockfile_version
59
+ return unless File.file?(lockfile)
60
+
61
+ lockfile_contents = File.read(lockfile)
62
+ return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/
63
+
64
+ Regexp.last_match(1)
65
+ end
66
+
67
+ def bundler_requirement
68
+ @bundler_requirement ||=
69
+ env_var_version ||
70
+ cli_arg_version ||
71
+ bundler_requirement_for(lockfile_version)
72
+ end
73
+
74
+ def bundler_requirement_for(version)
75
+ return "#{Gem::Requirement.default}.a" unless version
76
+
77
+ bundler_gem_version = Gem::Version.new(version)
78
+
79
+ bundler_gem_version.approximate_recommendation
80
+ end
81
+
82
+ def load_bundler!
83
+ ENV['BUNDLE_GEMFILE'] ||= gemfile
84
+
85
+ activate_bundler
86
+ end
87
+
88
+ def activate_bundler
89
+ gem_error = activation_error_handling do
90
+ gem 'bundler', bundler_requirement
91
+ end
92
+ return if gem_error.nil?
93
+
94
+ require_error = activation_error_handling do
95
+ require 'bundler/version'
96
+ end
97
+ return if require_error.nil? && Gem::Requirement.new(bundler_requirement).satisfied_by?(Gem::Version.new(Bundler::VERSION))
98
+
99
+ warn "Activating bundler (#{bundler_requirement}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_requirement}'`"
100
+ exit 42
101
+ end
102
+
103
+ def activation_error_handling
104
+ yield
105
+ nil
106
+ rescue StandardError, LoadError => e
107
+ e
108
+ end
109
+ end
110
+
111
+ m.load_bundler!
112
+
113
+ if m.invoked_as_script?
114
+ load Gem.bin_path('bundler', 'bundle')
115
+ end
data/bin/docs CHANGED
@@ -5,5 +5,23 @@ if ! gem list foreman -i --silent; then
5
5
  gem install foreman
6
6
  fi
7
7
 
8
- cd docs && npm install && cd ..
8
+ COVERAGE_FILE="coverage/.resultset.json"
9
+
10
+ if [ ! -f $COVERAGE_FILE ] || [ $(find $COVERAGE_FILE -mtime +7 -print 2>/dev/null) ]; then
11
+ echo "###############################################"
12
+ echo "# No test coverage file found. Regenerating. #"
13
+ echo "###############################################"
14
+
15
+ bin/rspec
16
+ bin/featuremap test_coverage --use-simplecov --simplecov-path $COVERAGE_FILE
17
+ else
18
+ echo "#############################################################"
19
+ echo "# Using recent coverage file. If you'd like to regenerate: #"
20
+ echo "# > rm $COVERAGE_FILE #"
21
+ echo "#############################################################"
22
+ fi
23
+
24
+ bin/featuremap docs
25
+ cat .feature_map/docs/feature-map-config.js | sed 's/window\.FEATURE_MAP_CONFIG = /export const sampleConfig = /' > docs/sample_config.js
26
+ cd docs && npm install && npm run lint:fix && cd ..
9
27
  exec foreman start -f Procfile.docs "$@"
data/bin/featuremap CHANGED
@@ -1,4 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
+ # NOTE: bundler/setup is required to reference local library code
4
+ # rather than a system installed version of feature_map.
5
+ require 'bundler/setup'
3
6
  require 'feature_map'
4
7
  FeatureMap::Cli.run!(ARGV)
data/bin/jekyll ADDED
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'jekyll' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
12
+
13
+ bundle_binstub = File.expand_path('bundle', __dir__)
14
+
15
+ if File.file?(bundle_binstub)
16
+ if File.read(bundle_binstub, 300).include?('This file was generated by Bundler')
17
+ load(bundle_binstub)
18
+ else
19
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
20
+ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
21
+ end
22
+ end
23
+
24
+ require 'rubygems'
25
+ require 'bundler/setup'
26
+
27
+ load Gem.bin_path('jekyll', 'jekyll')
data/bin/readme CHANGED
@@ -6,4 +6,5 @@ if ! gem list foreman -i --silent; then
6
6
  fi
7
7
 
8
8
  cp ./lib/feature_map/private/docs/index.html ./readme/example-docs-site.html
9
+ cd readme && npm i && cd ..
9
10
  exec foreman start -f Procfile.readme "$@"
data/bin/rspec ADDED
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'rspec' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
12
+
13
+ bundle_binstub = File.expand_path('bundle', __dir__)
14
+
15
+ if File.file?(bundle_binstub)
16
+ if File.read(bundle_binstub, 300).include?('This file was generated by Bundler')
17
+ load(bundle_binstub)
18
+ else
19
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
20
+ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
21
+ end
22
+ end
23
+
24
+ require 'rubygems'
25
+ require 'bundler/setup'
26
+
27
+ load Gem.bin_path('rspec-core', 'rspec')
data/bin/rubocop ADDED
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'rubocop' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
12
+
13
+ bundle_binstub = File.expand_path('bundle', __dir__)
14
+
15
+ if File.file?(bundle_binstub)
16
+ if File.read(bundle_binstub, 300).include?('This file was generated by Bundler')
17
+ load(bundle_binstub)
18
+ else
19
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
20
+ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
21
+ end
22
+ end
23
+
24
+ require 'rubygems'
25
+ require 'bundler/setup'
26
+
27
+ load Gem.bin_path('rubocop', 'rubocop')
@@ -1,3 +1,4 @@
1
+ # @feature Core Library
1
2
  require 'optparse'
2
3
  require 'pathname'
3
4
  require 'fileutils'
@@ -152,30 +153,67 @@ module FeatureMap
152
153
  end
153
154
 
154
155
  def self.test_coverage!(argv)
156
+ options = {
157
+ source: :codecov,
158
+ simplecov_paths: []
159
+ }
160
+
155
161
  parser = OptionParser.new do |opts|
156
162
  opts.banner = <<~MSG
157
163
  Usage: bin/featuremap test_coverage [options] [code_cov_commit_sha].
158
- Note: Requires environment variable `CODECOV_API_TOKEN`.
164
+
165
+ Options:
166
+ --use-simplecov Use SimpleCov instead of CodeCov
167
+ --simplecov-path PATH Path to a SimpleCov resultset.json file (can be specified multiple times)
168
+
169
+ Note:#{' '}
170
+ - CodeCov mode requires environment variable `CODECOV_API_TOKEN`
171
+ - CodeCov mode uses the provided commit SHA or defaults to the latest commit on main
172
+ - SimpleCov mode requires at least one path to be specified with --simplecov-path
173
+ - SimpleCov and CodeCov modes cannot be used together
159
174
  MSG
160
175
 
176
+ opts.on('--use-simplecov', 'Use SimpleCov instead of CodeCov') do
177
+ options[:source] = :simplecov
178
+ end
179
+
180
+ opts.on('--simplecov-path PATH', 'Use SimpleCov JSON resultset file instead of CodeCov. May be specified multiple times.') do |path|
181
+ options[:simplecov_paths] << path
182
+ end
183
+
161
184
  opts.on('--help', 'Shows this prompt') do
162
185
  puts opts
163
186
  exit
164
187
  end
165
188
  end
189
+
166
190
  args = parser.order!(argv)
167
191
  parser.parse!(args)
168
192
  non_flag_args = argv.reject { |arg| arg.start_with?('--') }
169
193
  custom_commit_sha = non_flag_args[0]
170
194
 
171
- code_cov_token = ENV.fetch('CODECOV_API_TOKEN', '')
172
- raise 'Please specify a CodeCov API token in your environment as `CODECOV_API_TOKEN`' if code_cov_token.empty?
195
+ case options[:source]
196
+ when :codecov
197
+ code_cov_token = ENV.fetch('CODECOV_API_TOKEN', '')
198
+ raise 'Please specify a CodeCov API token in your environment as `CODECOV_API_TOKEN`' if code_cov_token.empty?
199
+
200
+ # If no commit SHA was provided in the CLI command args, use the most recent commit of the main branch in the upstream remote.
201
+ commit_sha = custom_commit_sha || `git log -1 --format=%H origin/main`.chomp
202
+ puts "Pulling test coverage statistics for commit #{commit_sha}"
173
203
 
174
- # If no commit SHA was providid in the CLI command args, use the most recent commit of the main branch in the upstream remote.
175
- commit_sha = custom_commit_sha || `git log -1 --format=%H origin/main`.chomp
176
- puts "Pulling test coverage statistics for commit #{commit_sha}"
204
+ FeatureMap.gather_test_coverage!(commit_sha, code_cov_token)
205
+ when :simplecov
206
+ missing_paths = options[:simplecov_paths].reject { |path| File.exist?(path) }
207
+ raise 'Error: When using --use-simplecov, you must specify at least one path with --simplecov-path.' if options[:simplecov_paths].empty?
208
+ raise "SimpleCov results file not found: #{missing_paths.join(', ')}" if missing_paths.any?
209
+ raise 'Error: Cannot specify a commit SHA when using --simplecov. These options are incompatible.' if custom_commit_sha
177
210
 
178
- FeatureMap.gather_test_coverage!(commit_sha, code_cov_token)
211
+ puts "Gathering test coverage statistics from SimpleCov files: #{options[:simplecov_paths].join(', ')}"
212
+
213
+ FeatureMap.gather_simplecov_test_coverage!(options[:simplecov_paths])
214
+ else
215
+ raise 'Invalid source'
216
+ end
179
217
 
180
218
  puts OutputColor.green('FeatureMap test coverage statistics collected.')
181
219
  puts 'View the resulting test coverage for each feature in .feature_map/test-coverage.yml'
@@ -1,3 +1,4 @@
1
+ # @feature Code Features
1
2
  # frozen_string_literal: true
2
3
 
3
4
  require 'yaml'
@@ -1,3 +1,4 @@
1
+ # @feature Core Library
1
2
  module FeatureMap
2
3
  class Commit
3
4
  attr_reader :sha
@@ -1,3 +1,4 @@
1
+ # @feature Core Library
1
2
  module FeatureMap
2
3
  class Configuration
3
4
  attr_reader :assigned_globs
@@ -1,3 +1,4 @@
1
+ # @feature Core Library
1
2
  # frozen_string_literal: true
2
3
 
3
4
  module FeatureMap
@@ -1,3 +1,4 @@
1
+ # @feature Core Library
1
2
  # frozen_string_literal: true
2
3
 
3
4
  module FeatureMap
@@ -1,3 +1,4 @@
1
+ # @feature Core Library
1
2
  # frozen_string_literal: true
2
3
 
3
4
  module OutputColor
@@ -1,3 +1,4 @@
1
+ # @feature Metrics Storage
1
2
  # frozen_string_literal: true
2
3
 
3
4
  module FeatureMap
@@ -1,3 +1,4 @@
1
+ # @feature Feature Assignment
1
2
  # frozen_string_literal: true
2
3
 
3
4
  module FeatureMap
@@ -13,6 +13,13 @@ module FeatureMap
13
13
  return @@map_files_to_features if @@map_files_to_features&.keys && @@map_files_to_features.keys.count.positive?
14
14
 
15
15
  @@map_files_to_features = CodeFeatures.all.each_with_object({}) do |feature, map| # rubocop:disable Style/ClassVars
16
+ # NOTE: The FeatureDefinitionAssignment naively assumes that all
17
+ # features will have a definition yaml file. This comes from
18
+ # the CodeOwnership implementation which does require these
19
+ # files to exist. This is not true in repositories using the
20
+ # feature_definitions.csv style of feature definition.
21
+ next if feature.config_yml.nil?
22
+
16
23
  map[feature.config_yml] = feature
17
24
  end
18
25
  end
@@ -27,6 +34,13 @@ module FeatureMap
27
34
  return {} if Private.configuration.ignore_feature_definitions
28
35
 
29
36
  CodeFeatures.all.each_with_object({}) do |feature, map|
37
+ # NOTE: The FeatureDefinitionAssignment naively assumes that all
38
+ # features will have a definition yaml file. This comes from
39
+ # the CodeOwnership implementation which does require these
40
+ # files to exist. This is not true in repositories using the
41
+ # feature_definitions.csv style of feature definition.
42
+ next if feature.config_yml.nil?
43
+
30
44
  map[feature.config_yml] = feature
31
45
  end
32
46
  end
@@ -1,3 +1,4 @@
1
+ # @feature Feature Assignment
1
2
  # frozen_string_literal: true
2
3
 
3
4
  require 'code_ownership'
@@ -1,3 +1,4 @@
1
+ # @feature Testing Tools
1
2
  # frozen_string_literal: true
2
3
 
3
4
  require 'faraday'
@@ -1,3 +1,4 @@
1
+ # @feature Metrics Calculation
1
2
  # frozen_string_literal: true
2
3
 
3
4
  require 'parser/current'