licensed 2.9.0 → 2.11.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -47,6 +47,9 @@ module Licensed
47
47
 
48
48
  def initialize(shell = Licensed::UI::Shell.new)
49
49
  @shell = shell
50
+ @run_report = nil
51
+ @app_report = nil
52
+ @source_report = nil
50
53
  end
51
54
 
52
55
  # Generate a report for a licensed command execution
@@ -15,20 +15,20 @@ module Licensed
15
15
  result = yield report
16
16
 
17
17
  all_reports = report.all_reports
18
- errored_reports = all_reports.select { |report| report.errors.any? }.to_a
18
+ errored_reports = all_reports.select { |r| r.errors.any? }.to_a
19
19
 
20
- dependency_count = all_reports.select { |report| report.target.is_a?(Licensed::Dependency) }.size
21
- error_count = errored_reports.sum { |report| report.errors.size }
20
+ dependency_count = all_reports.select { |r| r.target.is_a?(Licensed::Dependency) }.size
21
+ error_count = errored_reports.sum { |r| r.errors.size }
22
22
 
23
23
  if error_count > 0
24
24
  shell.newline
25
25
  shell.error "Errors:"
26
- errored_reports.each do |report|
27
- display_metadata = report.map { |k, v| "#{k}: #{v}" }.join(", ")
26
+ errored_reports.each do |r|
27
+ display_metadata = r.map { |k, v| "#{k}: #{v}" }.join(", ")
28
28
 
29
- shell.error "* #{report.name}"
29
+ shell.error "* #{r.name}"
30
30
  shell.error " #{display_metadata}" unless display_metadata.empty?
31
- report.errors.each do |error|
31
+ r.errors.each do |error|
32
32
  shell.error " - #{error}"
33
33
  end
34
34
  shell.newline
@@ -11,6 +11,7 @@ module Licensed
11
11
  require "licensed/sources/go"
12
12
  require "licensed/sources/manifest"
13
13
  require "licensed/sources/npm"
14
+ require "licensed/sources/nuget"
14
15
  require "licensed/sources/pip"
15
16
  require "licensed/sources/pipenv"
16
17
  require "licensed/sources/gradle"
@@ -111,11 +111,11 @@ module Licensed
111
111
  info = package_info_command(id).strip
112
112
  return missing_package(id) if info.empty?
113
113
 
114
- info.lines.each_with_object({}) do |line, info|
114
+ info.lines.each_with_object({}) do |line, hsh|
115
115
  key, value = line.split(":", 2).map(&:strip)
116
116
  next unless key && value
117
117
 
118
- info[key] = value
118
+ hsh[key] = value
119
119
  end
120
120
  end
121
121
 
@@ -120,12 +120,12 @@ module Licensed
120
120
  return go_mod["Version"] if go_mod
121
121
 
122
122
  package_directory = package["Dir"]
123
- return unless package_directory
123
+ return unless package_directory && File.exist?(package_directory)
124
124
 
125
125
  # find most recent git SHA for a package, or nil if SHA is
126
126
  # not available
127
127
  Dir.chdir package_directory do
128
- contents_version *contents_version_arguments
128
+ contents_version(*contents_version_arguments)
129
129
  end
130
130
  end
131
131
 
@@ -48,6 +48,7 @@ module Licensed
48
48
  # package name to it's metadata
49
49
  def recursive_dependencies(dependencies, result = {})
50
50
  dependencies.each do |name, dependency|
51
+ next if dependency["peerMissing"]
51
52
  next if yarn_lock_present && dependency["missing"]
52
53
  (result[name] ||= []) << dependency
53
54
  recursive_dependencies(dependency["dependencies"] || {}, result)
@@ -0,0 +1,212 @@
1
+ # frozen_string_literal: true
2
+ require "json"
3
+ require "reverse_markdown"
4
+
5
+ module Licensed
6
+ module Sources
7
+ # Only supports ProjectReference (project.assets.json) style restore used in .NET Core.
8
+ # Does not currently support packages.config style restore.
9
+ class NuGet < Source
10
+ def self.type
11
+ "nuget"
12
+ end
13
+
14
+ class NuGetDependency < Licensed::Dependency
15
+ LICENSE_FILE_REGEX = /<license\s*type\s*=\s*\"\s*file\s*\"\s*>\s*(.*)\s*<\/license>/ix.freeze
16
+ LICENSE_URL_REGEX = /<licenseUrl>\s*(.*)\s*<\/licenseUrl>/ix.freeze
17
+ PROJECT_URL_REGEX = /<projectUrl>\s*(.*)\s*<\/projectUrl>/ix.freeze
18
+ PROJECT_DESC_REGEX = /<description>\s*(.*)\s*<\/description>/ix.freeze
19
+
20
+ # Returns the metadata that represents this dependency. This metadata
21
+ # is written to YAML in the dependencys cached text file
22
+ def license_metadata
23
+ super.tap do |record_metadata|
24
+ record_metadata["homepage"] = project_url if project_url
25
+ record_metadata["summary"] = description if description
26
+ end
27
+ end
28
+
29
+ def nuspec_path
30
+ name = @metadata["name"]
31
+ File.join(self.path, "#{name.downcase}.nuspec")
32
+ end
33
+
34
+ def nuspec_contents
35
+ return @nuspec_contents if defined?(@nuspec_contents)
36
+ @nuspec_contents = begin
37
+ return unless nuspec_path && File.exist?(nuspec_path)
38
+ File.read(nuspec_path)
39
+ end
40
+ end
41
+
42
+ def project_url
43
+ return @project_url if defined?(@project_url)
44
+ @project_url = begin
45
+ return unless nuspec_contents
46
+ match = nuspec_contents.match PROJECT_URL_REGEX
47
+ match[1] if match && match[1]
48
+ end
49
+ end
50
+
51
+ def description
52
+ return @description if defined?(@description)
53
+ @description = begin
54
+ return unless nuspec_contents
55
+ match = nuspec_contents.match PROJECT_DESC_REGEX
56
+ match[1] if match && match[1]
57
+ end
58
+ end
59
+
60
+ def project_files
61
+ @nuget_project_files ||= begin
62
+ files = super().flatten.compact
63
+
64
+ # Only include the local file if it's a file licensee didn't already detect
65
+ nuspec_license_filename = File.basename(nuspec_local_license_file.filename) if nuspec_local_license_file
66
+ if nuspec_license_filename && files.none? { |file| File.basename(file.filename) == nuspec_license_filename }
67
+ files.push(nuspec_local_license_file)
68
+ end
69
+
70
+ # Only download licenseUrl if no recognized license was found locally
71
+ if files.none? { |file| file.license && file.license.key != "other" }
72
+ files.push(nuspec_remote_license_file)
73
+ end
74
+
75
+ files.compact
76
+ end
77
+ end
78
+
79
+ # Look for a <license type="file"> element in the nuspec that points to an
80
+ # on-disk license file (which licensee may not find due to a non-standard filename)
81
+ def nuspec_local_license_file
82
+ return @nuspec_local_license_file if defined?(@nuspec_local_license_file)
83
+ return unless nuspec_contents
84
+
85
+ match = nuspec_contents.match LICENSE_FILE_REGEX
86
+ return unless match && match[1]
87
+
88
+ license_path = File.join(File.dirname(nuspec_path), match[1])
89
+ return unless File.exist?(license_path)
90
+
91
+ license_data = File.read(license_path)
92
+ @nuspec_local_license_file = Licensee::ProjectFiles::LicenseFile.new(license_data, license_path)
93
+ end
94
+
95
+ # Look for a <licenseUrl> element in the nuspec that either is known to contain a license identifier
96
+ # in the URL, or points to license text on the internet that can be downloaded.
97
+ def nuspec_remote_license_file
98
+ return @nuspec_remote_license_file if defined?(@nuspec_remote_license_file)
99
+ return unless nuspec_contents
100
+
101
+ match = nuspec_contents.match LICENSE_URL_REGEX
102
+ return unless match && match[1]
103
+
104
+ # Attempt to fetch the license content
105
+ license_content = self.class.retrieve_license(match[1])
106
+ @nuspec_remote_license_file = Licensee::ProjectFiles::LicenseFile.new(license_content, { uri: match[1] }) if license_content
107
+ end
108
+
109
+ class << self
110
+ def strip_html(html)
111
+ return unless html
112
+
113
+ return html unless html.downcase.include?("<html")
114
+ ReverseMarkdown.convert(html, unknown_tags: :bypass)
115
+ end
116
+
117
+ def ignored_url?(url)
118
+ # Many Microsoft packages that now use <license> use this for <licenseUrl>
119
+ # No need to fetch this page - it just contains NuGet documentation
120
+ url == "https://aka.ms/deprecateLicenseUrl"
121
+ end
122
+
123
+ def text_content_url(url)
124
+ # Convert github file URLs to raw URLs
125
+ return url unless match = url.match(/https?:\/\/(?:www\.)?github.com\/([^\/]+)\/([^\/]+)\/blob\/(.*)/i)
126
+ "https://github.com/#{match[1]}/#{match[2]}/raw/#{match[3]}"
127
+ end
128
+
129
+ def retrieve_license(url)
130
+ return unless url
131
+ return if ignored_url?(url)
132
+
133
+ # Transform URLs that are known to return HTML but have a corresponding text-based URL
134
+ text_url = text_content_url(url)
135
+
136
+ raw_content = fetch_content(text_url)
137
+ strip_html(raw_content)
138
+ end
139
+
140
+ def fetch_content(url, redirect_limit = 5)
141
+ url = URI.parse(url) if url.instance_of? String
142
+ return @response_by_url[url] if (@response_by_url ||= {}).key?(url)
143
+ return if redirect_limit == 0
144
+
145
+ begin
146
+ response = Net::HTTP.get_response(url)
147
+ case response
148
+ when Net::HTTPSuccess then
149
+ @response_by_url[url] = response.body
150
+ when Net::HTTPRedirection then
151
+ redirect_url = URI.parse(response["location"])
152
+ if redirect_url.relative?
153
+ redirect_url = url + redirect_url
154
+ end
155
+ # The redirect might be to a URL that requires transformation, i.e. a github file
156
+ redirect_url = text_content_url(redirect_url.to_s)
157
+ @response_by_url[url] = fetch_content(redirect_url, redirect_limit - 1)
158
+ end
159
+ rescue
160
+ # Host might no longer exist or some other error, ignore
161
+ end
162
+ end
163
+ end
164
+ end
165
+
166
+ def project_assets_file_path
167
+ File.join(config.pwd, "project.assets.json")
168
+ end
169
+
170
+ def project_assets_file
171
+ return @project_assets_file if defined?(@project_assets_file)
172
+ @project_assets_file = File.read(project_assets_file_path)
173
+ end
174
+
175
+ def enabled?
176
+ File.exist?(project_assets_file_path)
177
+ end
178
+
179
+ # Inspect project.assets.json files for package references.
180
+ # Ideally we'd use `dotnet list package` instead, but its output isn't
181
+ # easily machine readable and doesn't contain everything we need.
182
+ def enumerate_dependencies
183
+ json = JSON.parse(project_assets_file)
184
+ nuget_packages_dir = json["project"]["restore"]["packagesPath"]
185
+ json["targets"].each_with_object({}) do |(_, target), dependencies|
186
+ target.each do |reference_key, reference|
187
+ # Ignore project references
188
+ next unless reference["type"] == "package"
189
+ package_id_parts = reference_key.partition("/")
190
+ name = package_id_parts[0]
191
+ version = package_id_parts[-1]
192
+ id = "#{name}-#{version}"
193
+
194
+ # Already know this package from another target
195
+ next if dependencies.key?(id)
196
+
197
+ path = File.join(nuget_packages_dir, json["libraries"][reference_key]["path"])
198
+ dependencies[id] = NuGetDependency.new(
199
+ name: id,
200
+ version: version,
201
+ path: path,
202
+ metadata: {
203
+ "type" => NuGet.type,
204
+ "name" => name
205
+ }
206
+ )
207
+ end
208
+ end.values
209
+ end
210
+ end
211
+ end
212
+ end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
  module Licensed
3
- VERSION = "2.9.0".freeze
3
+ VERSION = "2.11.1".freeze
4
4
 
5
5
  def self.previous_major_versions
6
6
  major_version = Gem::Version.new(Licensed::VERSION).segments.first
@@ -23,13 +23,14 @@ Gem::Specification.new do |spec|
23
23
 
24
24
  spec.required_ruby_version = ">= 2.3.0"
25
25
 
26
- spec.add_dependency "licensee", ">= 9.13.1", "< 10.0.0"
27
- spec.add_dependency "thor", "~> 0.19"
26
+ spec.add_dependency "licensee", ">= 9.14.0", "< 10.0.0"
27
+ spec.add_dependency "thor", ">= 0.19"
28
28
  spec.add_dependency "pathname-common_prefix", "~> 0.0.1"
29
29
  spec.add_dependency "tomlrb", "~> 1.2"
30
30
  spec.add_dependency "bundler", ">= 1.10"
31
31
  spec.add_dependency "ruby-xxHash", "~> 0.4"
32
32
  spec.add_dependency "parallel", ">= 0.18.0"
33
+ spec.add_dependency "reverse_markdown", "~> 1.0"
33
34
 
34
35
  spec.add_development_dependency "rake", ">= 12.3.3"
35
36
  spec.add_development_dependency "minitest", "~> 5.8"
@@ -0,0 +1,17 @@
1
+ #!/bin/bash
2
+ set -e
3
+
4
+ if [ -z "$(which dotnet)" ]; then
5
+ echo "A local dotnet installation is required for dotnet/nuget development." >&2
6
+ exit 127
7
+ fi
8
+
9
+ # setup test fixtures
10
+ BASE_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
11
+ cd $BASE_PATH/test/fixtures/nuget
12
+
13
+ if [ "$1" == "-f" ]; then
14
+ dotnet clean
15
+ fi
16
+
17
+ dotnet restore
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: licensed
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.9.0
4
+ version: 2.11.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - GitHub
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-03-19 00:00:00.000000000 Z
11
+ date: 2020-06-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: licensee
@@ -16,7 +16,7 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: 9.13.1
19
+ version: 9.14.0
20
20
  - - "<"
21
21
  - !ruby/object:Gem::Version
22
22
  version: 10.0.0
@@ -26,7 +26,7 @@ dependencies:
26
26
  requirements:
27
27
  - - ">="
28
28
  - !ruby/object:Gem::Version
29
- version: 9.13.1
29
+ version: 9.14.0
30
30
  - - "<"
31
31
  - !ruby/object:Gem::Version
32
32
  version: 10.0.0
@@ -34,14 +34,14 @@ dependencies:
34
34
  name: thor
35
35
  requirement: !ruby/object:Gem::Requirement
36
36
  requirements:
37
- - - "~>"
37
+ - - ">="
38
38
  - !ruby/object:Gem::Version
39
39
  version: '0.19'
40
40
  type: :runtime
41
41
  prerelease: false
42
42
  version_requirements: !ruby/object:Gem::Requirement
43
43
  requirements:
44
- - - "~>"
44
+ - - ">="
45
45
  - !ruby/object:Gem::Version
46
46
  version: '0.19'
47
47
  - !ruby/object:Gem::Dependency
@@ -114,6 +114,20 @@ dependencies:
114
114
  - - ">="
115
115
  - !ruby/object:Gem::Version
116
116
  version: 0.18.0
117
+ - !ruby/object:Gem::Dependency
118
+ name: reverse_markdown
119
+ requirement: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - "~>"
122
+ - !ruby/object:Gem::Version
123
+ version: '1.0'
124
+ type: :runtime
125
+ prerelease: false
126
+ version_requirements: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - "~>"
129
+ - !ruby/object:Gem::Version
130
+ version: '1.0'
117
131
  - !ruby/object:Gem::Dependency
118
132
  name: rake
119
133
  requirement: !ruby/object:Gem::Requirement
@@ -257,6 +271,7 @@ files:
257
271
  - docs/sources/manifests.md
258
272
  - docs/sources/mix.md
259
273
  - docs/sources/npm.md
274
+ - docs/sources/nuget.md
260
275
  - docs/sources/pip.md
261
276
  - docs/sources/pipenv.md
262
277
  - docs/sources/stack.md
@@ -269,6 +284,7 @@ files:
269
284
  - lib/licensed/commands/command.rb
270
285
  - lib/licensed/commands/environment.rb
271
286
  - lib/licensed/commands/list.rb
287
+ - lib/licensed/commands/notices.rb
272
288
  - lib/licensed/commands/status.rb
273
289
  - lib/licensed/configuration.rb
274
290
  - lib/licensed/dependency.rb
@@ -280,6 +296,7 @@ files:
280
296
  - lib/licensed/reporters/cache_reporter.rb
281
297
  - lib/licensed/reporters/json_reporter.rb
282
298
  - lib/licensed/reporters/list_reporter.rb
299
+ - lib/licensed/reporters/notices_reporter.rb
283
300
  - lib/licensed/reporters/reporter.rb
284
301
  - lib/licensed/reporters/status_reporter.rb
285
302
  - lib/licensed/reporters/yaml_reporter.rb
@@ -297,6 +314,7 @@ files:
297
314
  - lib/licensed/sources/manifest.rb
298
315
  - lib/licensed/sources/mix.rb
299
316
  - lib/licensed/sources/npm.rb
317
+ - lib/licensed/sources/nuget.rb
300
318
  - lib/licensed/sources/pip.rb
301
319
  - lib/licensed/sources/pipenv.rb
302
320
  - lib/licensed/sources/source.rb
@@ -320,6 +338,7 @@ files:
320
338
  - script/source-setup/go
321
339
  - script/source-setup/mix
322
340
  - script/source-setup/npm
341
+ - script/source-setup/nuget
323
342
  - script/source-setup/pip
324
343
  - script/source-setup/pipenv
325
344
  - script/source-setup/yarn