licensed 3.1.0 → 3.2.3

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 (59) hide show
  1. checksums.yaml +4 -4
  2. data/.github/dependabot.yml +19 -0
  3. data/.github/workflows/release.yml +4 -4
  4. data/.github/workflows/test.yml +169 -48
  5. data/.ruby-version +1 -1
  6. data/CHANGELOG.md +51 -1
  7. data/README.md +25 -80
  8. data/docker/Dockerfile.build-linux +1 -1
  9. data/docs/adding_a_new_source.md +11 -8
  10. data/docs/commands/README.md +59 -0
  11. data/docs/commands/cache.md +35 -0
  12. data/docs/commands/env.md +10 -0
  13. data/docs/commands/list.md +23 -0
  14. data/docs/commands/migrate.md +10 -0
  15. data/docs/commands/notices.md +12 -0
  16. data/docs/commands/status.md +74 -0
  17. data/docs/commands/version.md +3 -0
  18. data/docs/configuration/README.md +11 -0
  19. data/docs/configuration/allowed_licenses.md +17 -0
  20. data/docs/configuration/application_name.md +63 -0
  21. data/docs/configuration/application_source.md +64 -0
  22. data/docs/configuration/configuration_root.md +27 -0
  23. data/docs/configuration/configuring_multiple_apps.md +58 -0
  24. data/docs/configuration/dependency_source_enumerators.md +28 -0
  25. data/docs/configuration/ignoring_dependencies.md +19 -0
  26. data/docs/configuration/metadata_cache.md +106 -0
  27. data/docs/configuration/reviewing_dependencies.md +18 -0
  28. data/docs/configuration.md +9 -173
  29. data/lib/licensed/cli.rb +2 -2
  30. data/lib/licensed/commands/cache.rb +21 -20
  31. data/lib/licensed/commands/command.rb +108 -73
  32. data/lib/licensed/commands/environment.rb +12 -11
  33. data/lib/licensed/commands/list.rb +0 -19
  34. data/lib/licensed/commands/notices.rb +0 -19
  35. data/lib/licensed/commands/status.rb +13 -15
  36. data/lib/licensed/configuration.rb +77 -7
  37. data/lib/licensed/report.rb +44 -0
  38. data/lib/licensed/reporters/cache_reporter.rb +48 -64
  39. data/lib/licensed/reporters/json_reporter.rb +19 -21
  40. data/lib/licensed/reporters/list_reporter.rb +45 -58
  41. data/lib/licensed/reporters/notices_reporter.rb +33 -46
  42. data/lib/licensed/reporters/reporter.rb +37 -104
  43. data/lib/licensed/reporters/status_reporter.rb +58 -56
  44. data/lib/licensed/reporters/yaml_reporter.rb +19 -21
  45. data/lib/licensed/sources/bundler/definition.rb +36 -0
  46. data/lib/licensed/sources/bundler/missing_specification.rb +10 -7
  47. data/lib/licensed/sources/bundler.rb +34 -70
  48. data/lib/licensed/sources/dep.rb +2 -2
  49. data/lib/licensed/sources/go.rb +3 -3
  50. data/lib/licensed/sources/gradle.rb +2 -2
  51. data/lib/licensed/sources/helpers/content_versioning.rb +2 -1
  52. data/lib/licensed/sources/npm.rb +4 -3
  53. data/lib/licensed/sources/nuget.rb +1 -2
  54. data/lib/licensed/version.rb +1 -1
  55. data/lib/licensed.rb +1 -0
  56. data/licensed.gemspec +4 -4
  57. data/script/source-setup/go +1 -1
  58. metadata +45 -13
  59. data/docs/commands.md +0 -95
data/lib/licensed/cli.rb CHANGED
@@ -20,12 +20,12 @@ module Licensed
20
20
  end
21
21
 
22
22
  desc "status", "Check status of dependencies' cached licenses"
23
- method_option :format, enum: ["yaml", "json"],
24
- desc: "Output format"
25
23
  method_option :config, aliases: "-c", type: :string,
26
24
  desc: "Path to licensed configuration file"
27
25
  method_option :sources, aliases: "-s", type: :array,
28
26
  desc: "Individual source(s) to evaluate. Must also be enabled via configuration."
27
+ method_option :format, aliases: "-f", enum: ["yaml", "json"],
28
+ desc: "Output format"
29
29
  def status
30
30
  run Licensed::Commands::Status.new(config: config), sources: options[:sources], reporter: options[:format]
31
31
  end
@@ -11,6 +11,8 @@ module Licensed
11
11
  Licensed::Reporters::CacheReporter.new
12
12
  end
13
13
 
14
+ protected
15
+
14
16
  # Run the command.
15
17
  # Removes any cached records that don't match a current application
16
18
  # dependency.
@@ -18,20 +20,15 @@ module Licensed
18
20
  # options - Options to run the command with
19
21
  #
20
22
  # Returns whether the command was a success
21
- def run(**options)
22
- begin
23
- result = super
23
+ def run_command(report)
24
+ super do |result|
24
25
  clear_stale_cached_records if result
25
-
26
- result
27
- ensure
28
- cache_paths.clear
29
- files.clear
30
26
  end
27
+ ensure
28
+ cache_paths.clear
29
+ files.clear
31
30
  end
32
31
 
33
- protected
34
-
35
32
  # Run the command for all enumerated dependencies found in a dependency source,
36
33
  # recording results in a report.
37
34
  # Enumerating dependencies in the source is skipped if a :sources option
@@ -41,17 +38,14 @@ module Licensed
41
38
  # source - A dependency source enumerator
42
39
  #
43
40
  # Returns whether the command succeeded for the dependency source enumerator
44
- def run_source(app, source)
45
- super do |report|
46
- if Array(options[:sources]).any? && !options[:sources].include?(source.class.type)
47
- report.warnings << "skipped source"
48
- next :skip
49
- end
41
+ def run_source(app, source, report)
42
+ result = super
50
43
 
51
- # add the full cache path to the list of cache paths
52
- # that should be cleaned up after the command run
53
- cache_paths << app.cache_path.join(source.class.type)
54
- end
44
+ # add the full cache path to the list of cache paths
45
+ # that should be cleaned up after the command run
46
+ cache_paths << app.cache_path.join(source.class.type) unless result == :skipped
47
+
48
+ result
55
49
  end
56
50
 
57
51
  # Cache dependency record data.
@@ -70,6 +64,12 @@ module Licensed
70
64
 
71
65
  filename = app.cache_path.join(source.class.type, "#{dependency.name}.#{DependencyRecord::EXTENSION}")
72
66
  cached_record = Licensed::DependencyRecord.read(filename)
67
+
68
+ report["cached"] = false
69
+ report["license"] = cached_record["license"] if cached_record
70
+ report["filename"] = filename.to_s
71
+ report["version"] = dependency.version
72
+
73
73
  if options[:force] || save_dependency_record?(dependency, cached_record)
74
74
  if dependency.record.matches?(cached_record)
75
75
  # use the cached license value if the license text wasn't updated
@@ -82,6 +82,7 @@ module Licensed
82
82
 
83
83
  dependency.record.save(filename)
84
84
  report["cached"] = true
85
+ report["license"] = dependency.record["license"]
85
86
  end
86
87
 
87
88
  if !dependency.exist?
@@ -18,23 +18,12 @@ module Licensed
18
18
  def run(**options)
19
19
  @options = options
20
20
  @reporter = create_reporter(options)
21
- begin
22
- result = reporter.report_run(self) do |report|
23
- # allow additional report data to be given by commands
24
- if block_given?
25
- next true if (yield report) == :skip
26
- end
27
-
28
- config.apps.sort_by { |app| app["name"] }
29
- .map { |app| run_app(app) }
30
- .all?
31
- end
32
- ensure
33
- @options = nil
34
- @reporter = nil
35
- end
36
21
 
37
- result
22
+ command_report = Licensed::Report.new(name: nil, target: self)
23
+ run_command(command_report)
24
+ ensure
25
+ @options = nil
26
+ @reporter = nil
38
27
  end
39
28
 
40
29
  # Creates a reporter to use during a command run
@@ -64,36 +53,65 @@ module Licensed
64
53
 
65
54
  protected
66
55
 
56
+ # Run the command for all application configurations
57
+ #
58
+ # report - A Licensed::Report object for this command
59
+ #
60
+ # Returns whether the command succeeded
61
+ def run_command(report)
62
+ reporter.begin_report_command(self, report)
63
+ apps = config.apps.sort_by { |app| app["name"] }
64
+ results = apps.map do |app|
65
+ app_report = Licensed::Report.new(name: app["name"], target: app)
66
+ report.reports << app_report
67
+ run_app(app, app_report)
68
+ end
69
+
70
+ result = results.all?
71
+
72
+ yield(result) if block_given?
73
+
74
+ result
75
+ ensure
76
+ reporter.end_report_command(self, report)
77
+ end
78
+
67
79
  # Run the command for all enabled sources for an application configuration,
68
80
  # recording results in a report.
69
81
  #
70
82
  # app - An application configuration
83
+ # report - A report object for this application
71
84
  #
72
85
  # Returns whether the command succeeded for the application.
73
- def run_app(app)
74
- reporter.report_app(app) do |report|
75
- # ensure the app source path exists before evaluation
76
- if !Dir.exist?(app.source_path)
77
- report.errors << "No such directory #{app.source_path}"
78
- next false
79
- end
86
+ def run_app(app, report)
87
+ reporter.begin_report_app(app, report)
80
88
 
81
- Dir.chdir app.source_path do
82
- begin
83
- # allow additional report data to be given by commands
84
- if block_given?
85
- next true if (yield report) == :skip
86
- end
87
-
88
- app.sources.select(&:enabled?)
89
- .sort_by { |source| source.class.type }
90
- .map { |source| run_source(app, source) }.all?
91
- rescue Licensed::Shell::Error => err
92
- report.errors << err.message
93
- false
94
- end
89
+ # ensure the app source path exists before evaluation
90
+ if !Dir.exist?(app.source_path)
91
+ report.errors << "No such directory #{app.source_path}"
92
+ return false
93
+ end
94
+
95
+ Dir.chdir app.source_path do
96
+ sources = app.sources.select(&:enabled?)
97
+ .sort_by { |source| source.class.type }
98
+ results = sources.map do |source|
99
+ source_report = Licensed::Report.new(name: [report.name, source.class.type].join("."), target: source)
100
+ report.reports << source_report
101
+ run_source(app, source, source_report)
95
102
  end
103
+
104
+ result = results.all?
105
+
106
+ yield(result) if block_given?
107
+
108
+ result
96
109
  end
110
+ rescue Licensed::Shell::Error => err
111
+ report.errors << err.message
112
+ false
113
+ ensure
114
+ reporter.end_report_app(app, report)
97
115
  end
98
116
 
99
117
  # Run the command for all enumerated dependencies found in a dependency source,
@@ -101,27 +119,40 @@ module Licensed
101
119
  #
102
120
  # app - The application configuration for the source
103
121
  # source - A dependency source enumerator
122
+ # report - A report object for this source
104
123
  #
105
- # Returns whether the command succeeded for the dependency source enumerator
106
- def run_source(app, source)
107
- reporter.report_source(source) do |report|
108
- begin
109
- # allow additional report data to be given by commands
110
- if block_given?
111
- next true if (yield report) == :skip
112
- end
113
-
114
- source.dependencies.sort_by { |dependency| dependency.name }
115
- .map { |dependency| run_dependency(app, source, dependency) }
116
- .all?
117
- rescue Licensed::Shell::Error => err
118
- report.errors << err.message
119
- false
120
- rescue Licensed::Sources::Source::Error => err
121
- report.errors << err.message
122
- false
123
- end
124
+ # Returns whether the command succeeded, failed, or was skipped for the dependency source enumerator
125
+ def run_source(app, source, report)
126
+ reporter.begin_report_source(source, report)
127
+
128
+ if !sources_overrides.empty? && !sources_overrides.include?(source.class.type)
129
+ report.warnings << "skipped source"
130
+
131
+ # return a symbol to speficy the source was skipped.
132
+ # This is truthy and will result in the source being considered successful
133
+ return :skipped
134
+ end
135
+
136
+ dependencies = source.dependencies.sort_by { |dependency| dependency.name }
137
+ results = dependencies.map do |dependency|
138
+ dependency_report = Licensed::Report.new(name: [report.name, dependency.name].join("."), target: dependency)
139
+ report.reports << dependency_report
140
+ run_dependency(app, source, dependency, dependency_report)
124
141
  end
142
+
143
+ result = results.all?
144
+
145
+ yield(result) if block_given?
146
+
147
+ result
148
+ rescue Licensed::Shell::Error => err
149
+ report.errors << err.message
150
+ false
151
+ rescue Licensed::Sources::Source::Error => err
152
+ report.errors << err.message
153
+ false
154
+ ensure
155
+ reporter.end_report_source(source, report)
125
156
  end
126
157
 
127
158
  # Run the command for a dependency, evaluating the dependency and
@@ -131,27 +162,27 @@ module Licensed
131
162
  # app - The application configuration for the dependency
132
163
  # source - The dependency source enumerator for the dependency
133
164
  # dependency - An application dependency
165
+ # report - A report object for this dependency
134
166
  #
135
167
  # Returns whether the command succeeded for the dependency
136
- def run_dependency(app, source, dependency)
137
- reporter.report_dependency(dependency) do |report|
138
- if dependency.errors?
139
- report.errors.concat(dependency.errors)
140
- return false
141
- end
142
-
143
- begin
144
- # allow additional report data to be given by commands
145
- if block_given?
146
- next true if (yield report) == :skip
147
- end
168
+ def run_dependency(app, source, dependency, report)
169
+ reporter.begin_report_dependency(dependency, report)
148
170
 
149
- evaluate_dependency(app, source, dependency, report)
150
- rescue Licensed::DependencyRecord::Error, Licensed::Shell::Error => err
151
- report.errors << err.message
152
- false
153
- end
171
+ if dependency.errors?
172
+ report.errors.concat(dependency.errors)
173
+ return false
154
174
  end
175
+
176
+ result = evaluate_dependency(app, source, dependency, report)
177
+
178
+ yield(result) if block_given?
179
+
180
+ result
181
+ rescue Licensed::DependencyRecord::Error, Licensed::Shell::Error => err
182
+ report.errors << err.message
183
+ false
184
+ ensure
185
+ reporter.end_report_dependency(dependency, report)
155
186
  end
156
187
 
157
188
  # Evaluate a dependency for the command. Must be implemented by a command implementation.
@@ -165,6 +196,10 @@ module Licensed
165
196
  def evaluate_dependency(app, source, dependency, report)
166
197
  raise "`evaluate_dependency` must be implemented by a command"
167
198
  end
199
+
200
+ def sources_overrides
201
+ @sources_overrides = Array(options[:sources])
202
+ end
168
203
  end
169
204
  end
170
205
  end
@@ -29,12 +29,6 @@ module Licensed
29
29
  end
30
30
  end
31
31
 
32
- def run(**options)
33
- super do |report|
34
- report["git_repo"] = Licensed::Git.git_repo?
35
- end
36
- end
37
-
38
32
  # Returns the default reporter to use during the command run
39
33
  #
40
34
  # options - The options the command was run with
@@ -46,11 +40,18 @@ module Licensed
46
40
 
47
41
  protected
48
42
 
49
- def run_app(app)
50
- reporter.report_app(app) do |report|
51
- report.merge! AppEnvironment.new(app).to_h
52
- true
53
- end
43
+ def run_command(report)
44
+ report["git_repo"] = Licensed::Git.git_repo?
45
+ super
46
+ end
47
+
48
+ def run_app(app, report)
49
+ report.merge! AppEnvironment.new(app).to_h
50
+ super
51
+ end
52
+
53
+ def run_source(app, source, report)
54
+ true
54
55
  end
55
56
  end
56
57
  end
@@ -13,25 +13,6 @@ module Licensed
13
13
 
14
14
  protected
15
15
 
16
- # Run the command for all enumerated dependencies found in a dependency source,
17
- # recording results in a report.
18
- # Enumerating dependencies in the source is skipped if a :sources option
19
- # is provided and the evaluated `source.class.type` is not in the :sources values
20
- #
21
- # app - The application configuration for the source
22
- # source - A dependency source enumerator
23
- #
24
- # Returns whether the command succeeded for the dependency source enumerator
25
- def run_source(app, source)
26
- super do |report|
27
- next if Array(options[:sources]).empty?
28
- next if options[:sources].include?(source.class.type)
29
-
30
- report.warnings << "skipped source"
31
- :skip
32
- end
33
- end
34
-
35
16
  # Listing dependencies requires no extra work.
36
17
  #
37
18
  # app - The application configuration for the dependency
@@ -13,25 +13,6 @@ module Licensed
13
13
 
14
14
  protected
15
15
 
16
- # Run the command for all enumerated dependencies found in a dependency source,
17
- # recording results in a report.
18
- # Enumerating dependencies in the source is skipped if a :sources option
19
- # is provided and the evaluated `source.class.type` is not in the :sources values
20
- #
21
- # app - The application configuration for the source
22
- # source - A dependency source enumerator
23
- #
24
- # Returns whether the command succeeded for the dependency source enumerator
25
- def run_source(app, source)
26
- super do |report|
27
- next if Array(options[:sources]).empty?
28
- next if options[:sources].include?(source.class.type)
29
-
30
- report.warnings << "skipped source"
31
- :skip
32
- end
33
- end
34
-
35
16
  # Load stored dependency record data to add to the notices report.
36
17
  #
37
18
  # app - The application configuration for the dependency
@@ -15,22 +15,17 @@ module Licensed
15
15
 
16
16
  protected
17
17
 
18
- # Run the command for all enumerated dependencies found in a dependency source,
19
- # recording results in a report.
20
- # Enumerating dependencies in the source is skipped if a :sources option
21
- # is provided and the evaluated `source.class.type` is not in the :sources values
18
+ # Run the comand and set an error message to review the documentation
19
+ # when any errors have been reported
22
20
  #
23
- # app - The application configuration for the source
24
- # source - A dependency source enumerator
21
+ # report - A Licensed::Report object for this command
25
22
  #
26
- # Returns whether the command succeeded for the dependency source enumerator
27
- def run_source(app, source)
28
- super do |report|
29
- next if Array(options[:sources]).empty?
30
- next if options[:sources].include?(source.class.type)
31
-
32
- report.warnings << "skipped source"
33
- :skip
23
+ # Returns whether the command succeeded based on the call to super
24
+ def run_command(report)
25
+ super do |result|
26
+ next if result
27
+
28
+ report.errors << "Licensed found errors during source enumeration. Please see https://github.com/github/licensed/tree/master/docs/commands/status.md#status-errors-and-resolutions for possible resolutions."
34
29
  end
35
30
  end
36
31
 
@@ -47,11 +42,14 @@ module Licensed
47
42
  def evaluate_dependency(app, source, dependency, report)
48
43
  filename = app.cache_path.join(source.class.type, "#{dependency.name}.#{DependencyRecord::EXTENSION}")
49
44
  report["filename"] = filename
45
+ report["version"] = dependency.version
50
46
 
51
47
  cached_record = cached_record(filename)
52
48
  if cached_record.nil?
49
+ report["license"] = nil
53
50
  report.errors << "cached dependency record not found"
54
51
  else
52
+ report["license"] = cached_record["license"]
55
53
  report.errors << "cached dependency record out of date" if cached_record["version"] != dependency.version
56
54
  report.errors << "missing license text" if cached_record.licenses.empty?
57
55
  if cached_record["review_changed_license"]
@@ -61,7 +59,7 @@ module Licensed
61
59
  end
62
60
  end
63
61
 
64
- report.errors.empty?
62
+ report["allowed"] = report.errors.empty?
65
63
  end
66
64
 
67
65
  # Returns true if a cached record needs further review based on the