licensed 2.9.0 → 2.11.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/release.yml +11 -12
- data/.github/workflows/test.yml +69 -50
- data/.gitignore +2 -0
- data/CHANGELOG.md +42 -4
- data/README.md +11 -7
- data/docs/commands.md +6 -0
- data/docs/configuration.md +20 -0
- data/docs/sources/nuget.md +14 -0
- data/lib/licensed/cli.rb +7 -0
- data/lib/licensed/commands.rb +1 -0
- data/lib/licensed/commands/cache.rb +47 -17
- data/lib/licensed/commands/notices.rb +35 -0
- data/lib/licensed/configuration.rb +12 -2
- data/lib/licensed/dependency.rb +1 -1
- data/lib/licensed/reporters.rb +1 -0
- data/lib/licensed/reporters/cache_reporter.rb +10 -10
- data/lib/licensed/reporters/list_reporter.rb +5 -5
- data/lib/licensed/reporters/notices_reporter.rb +77 -0
- data/lib/licensed/reporters/reporter.rb +3 -0
- data/lib/licensed/reporters/status_reporter.rb +7 -7
- data/lib/licensed/sources.rb +1 -0
- data/lib/licensed/sources/cabal.rb +2 -2
- data/lib/licensed/sources/go.rb +2 -2
- data/lib/licensed/sources/npm.rb +1 -0
- data/lib/licensed/sources/nuget.rb +212 -0
- data/lib/licensed/version.rb +1 -1
- data/licensed.gemspec +3 -2
- data/script/source-setup/nuget +17 -0
- metadata +25 -6
@@ -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 { |
|
18
|
+
errored_reports = all_reports.select { |r| r.errors.any? }.to_a
|
19
19
|
|
20
|
-
dependency_count = all_reports.select { |
|
21
|
-
error_count = errored_reports.sum { |
|
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 |
|
27
|
-
display_metadata =
|
26
|
+
errored_reports.each do |r|
|
27
|
+
display_metadata = r.map { |k, v| "#{k}: #{v}" }.join(", ")
|
28
28
|
|
29
|
-
shell.error "* #{
|
29
|
+
shell.error "* #{r.name}"
|
30
30
|
shell.error " #{display_metadata}" unless display_metadata.empty?
|
31
|
-
|
31
|
+
r.errors.each do |error|
|
32
32
|
shell.error " - #{error}"
|
33
33
|
end
|
34
34
|
shell.newline
|
data/lib/licensed/sources.rb
CHANGED
@@ -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,
|
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
|
-
|
118
|
+
hsh[key] = value
|
119
119
|
end
|
120
120
|
end
|
121
121
|
|
data/lib/licensed/sources/go.rb
CHANGED
@@ -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
|
128
|
+
contents_version(*contents_version_arguments)
|
129
129
|
end
|
130
130
|
end
|
131
131
|
|
data/lib/licensed/sources/npm.rb
CHANGED
@@ -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
|
data/lib/licensed/version.rb
CHANGED
data/licensed.gemspec
CHANGED
@@ -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.
|
27
|
-
spec.add_dependency "thor", "
|
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.
|
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-
|
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.
|
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.
|
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
|