licensed 2.9.0 → 2.11.1

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.
@@ -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