licensed 2.9.1 → 2.12.0
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.
- 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 -1
- data/README.md +11 -7
- data/docs/commands.md +14 -0
- data/docs/configuration.md +20 -0
- data/docs/sources/nuget.md +14 -0
- data/lib/licensed/cli.rb +22 -3
- data/lib/licensed/commands.rb +1 -0
- data/lib/licensed/commands/cache.rb +51 -14
- data/lib/licensed/commands/command.rb +12 -4
- data/lib/licensed/commands/list.rb +19 -0
- data/lib/licensed/commands/notices.rb +54 -0
- data/lib/licensed/commands/status.rb +19 -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 +21 -5
- data/lib/licensed/reporters/notices_reporter.rb +99 -0
- data/lib/licensed/reporters/reporter.rb +3 -0
- data/lib/licensed/reporters/status_reporter.rb +24 -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 +2 -1
- data/script/source-setup/nuget +17 -0
- metadata +23 -4
@@ -15,6 +15,25 @@ 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
|
22
|
+
#
|
23
|
+
# app - The application configuration for the source
|
24
|
+
# source - A dependency source enumerator
|
25
|
+
#
|
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
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
18
37
|
# Verifies that a cached record exists, is up to date and
|
19
38
|
# has license data that complies with the licensed configuration.
|
20
39
|
#
|
@@ -108,7 +108,12 @@ module Licensed
|
|
108
108
|
def detect_cache_path(options, inherited_options)
|
109
109
|
return options["cache_path"] unless options["cache_path"].to_s.empty?
|
110
110
|
|
111
|
-
|
111
|
+
# if cache_path and shared_cache are both set in inherited_options,
|
112
|
+
# don't append the app name to the cache path
|
113
|
+
cache_path = inherited_options["cache_path"]
|
114
|
+
return cache_path if cache_path && inherited_options["shared_cache"] == true
|
115
|
+
|
116
|
+
cache_path ||= DEFAULT_CACHE_PATH
|
112
117
|
File.join(cache_path, self["name"])
|
113
118
|
end
|
114
119
|
|
@@ -167,7 +172,12 @@ module Licensed
|
|
167
172
|
# will handle configurations that don't have these explicitly set
|
168
173
|
dir_name = File.basename(path)
|
169
174
|
config["name"] = "#{config["name"]}-#{dir_name}" if config["name"]
|
170
|
-
|
175
|
+
|
176
|
+
# if a cache_path is set and is not marked as shared, append the app name
|
177
|
+
# to the end of the cache path to make a unique cache path for the app
|
178
|
+
if config["cache_path"] && config["shared_cache"] != true
|
179
|
+
config["cache_path"] = File.join(config["cache_path"], dir_name)
|
180
|
+
end
|
171
181
|
|
172
182
|
config
|
173
183
|
end
|
data/lib/licensed/dependency.rb
CHANGED
@@ -74,7 +74,7 @@ module Licensed
|
|
74
74
|
def license_contents
|
75
75
|
files = matched_files.reject { |f| f == package_file }
|
76
76
|
.group_by(&:content)
|
77
|
-
.map { |content,
|
77
|
+
.map { |content, sources| { "sources" => license_content_sources(sources), "text" => content } }
|
78
78
|
|
79
79
|
files << generated_license_contents if files.empty?
|
80
80
|
files.compact
|
data/lib/licensed/reporters.rb
CHANGED
@@ -27,32 +27,32 @@ module Licensed
|
|
27
27
|
shell.info " #{source.class.type}"
|
28
28
|
result = yield report
|
29
29
|
|
30
|
-
warning_reports = report.all_reports.select { |
|
30
|
+
warning_reports = report.all_reports.select { |r| r.warnings.any? }.to_a
|
31
31
|
if warning_reports.any?
|
32
32
|
shell.newline
|
33
33
|
shell.warn " * Warnings:"
|
34
|
-
warning_reports.each do |
|
35
|
-
display_metadata =
|
34
|
+
warning_reports.each do |r|
|
35
|
+
display_metadata = r.map { |k, v| "#{k}: #{v}" }.join(", ")
|
36
36
|
|
37
|
-
shell.warn " * #{
|
37
|
+
shell.warn " * #{r.name}"
|
38
38
|
shell.warn " #{display_metadata}" unless display_metadata.empty?
|
39
|
-
|
39
|
+
r.warnings.each do |warning|
|
40
40
|
shell.warn " - #{warning}"
|
41
41
|
end
|
42
42
|
shell.newline
|
43
43
|
end
|
44
44
|
end
|
45
45
|
|
46
|
-
errored_reports = report.all_reports.select { |
|
46
|
+
errored_reports = report.all_reports.select { |r| r.errors.any? }.to_a
|
47
47
|
if errored_reports.any?
|
48
48
|
shell.newline
|
49
49
|
shell.error " * Errors:"
|
50
|
-
errored_reports.each do |
|
51
|
-
display_metadata =
|
50
|
+
errored_reports.each do |r|
|
51
|
+
display_metadata = r.map { |k, v| "#{k}: #{v}" }.join(", ")
|
52
52
|
|
53
|
-
shell.error " * #{
|
53
|
+
shell.error " * #{r.name}"
|
54
54
|
shell.error " #{display_metadata}" unless display_metadata.empty?
|
55
|
-
|
55
|
+
r.errors.each do |error|
|
56
56
|
shell.error " - #{error}"
|
57
57
|
end
|
58
58
|
shell.newline
|
@@ -28,16 +28,32 @@ module Licensed
|
|
28
28
|
shell.info " #{source.class.type}"
|
29
29
|
result = yield report
|
30
30
|
|
31
|
-
|
31
|
+
warning_reports = report.all_reports.select { |r| r.warnings.any? }.to_a
|
32
|
+
if warning_reports.any?
|
33
|
+
shell.newline
|
34
|
+
shell.warn " * Warnings:"
|
35
|
+
warning_reports.each do |r|
|
36
|
+
display_metadata = r.map { |k, v| "#{k}: #{v}" }.join(", ")
|
37
|
+
|
38
|
+
shell.warn " * #{r.name}"
|
39
|
+
shell.warn " #{display_metadata}" unless display_metadata.empty?
|
40
|
+
r.warnings.each do |warning|
|
41
|
+
shell.warn " - #{warning}"
|
42
|
+
end
|
43
|
+
shell.newline
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
errored_reports = report.all_reports.select { |r| r.errors.any? }.to_a
|
32
48
|
if errored_reports.any?
|
33
49
|
shell.newline
|
34
50
|
shell.error " * Errors:"
|
35
|
-
errored_reports.each do |
|
36
|
-
display_metadata =
|
51
|
+
errored_reports.each do |r|
|
52
|
+
display_metadata = r.map { |k, v| "#{k}: #{v}" }.join(", ")
|
37
53
|
|
38
|
-
shell.error " * #{
|
54
|
+
shell.error " * #{r.name}"
|
39
55
|
shell.error " #{display_metadata}" unless display_metadata.empty?
|
40
|
-
|
56
|
+
r.errors.each do |error|
|
41
57
|
shell.error " - #{error}"
|
42
58
|
end
|
43
59
|
shell.newline
|
@@ -0,0 +1,99 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Licensed
|
4
|
+
module Reporters
|
5
|
+
class NoticesReporter < Reporter
|
6
|
+
TEXT_SEPARATOR = "\n\n#{("-" * 5)}\n\n".freeze
|
7
|
+
LICENSE_SEPARATOR = "\n#{("*" * 5)}\n".freeze
|
8
|
+
|
9
|
+
# Reports on an application configuration in a notices command run
|
10
|
+
#
|
11
|
+
# app - An application configuration
|
12
|
+
#
|
13
|
+
# Returns the result of the yielded method
|
14
|
+
# Note - must be called from inside the `report_run` scope
|
15
|
+
def report_app(app)
|
16
|
+
super do |report|
|
17
|
+
filename = app["shared_cache"] ? "NOTICE.#{app["name"]}" : "NOTICE"
|
18
|
+
path = app.cache_path.join(filename)
|
19
|
+
shell.info "Writing notices for #{app["name"]} to #{path}"
|
20
|
+
|
21
|
+
result = yield report
|
22
|
+
|
23
|
+
File.open(path, "w") do |file|
|
24
|
+
file << "THIRD PARTY NOTICES\n"
|
25
|
+
file << LICENSE_SEPARATOR
|
26
|
+
file << report.all_reports
|
27
|
+
.map { |r| notices(r) }
|
28
|
+
.compact
|
29
|
+
.join(LICENSE_SEPARATOR)
|
30
|
+
end
|
31
|
+
|
32
|
+
result
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
# Reports on a dependency source enumerator in a notices command run.
|
38
|
+
# Shows warnings encountered during the run.
|
39
|
+
#
|
40
|
+
# source - A dependency source enumerator
|
41
|
+
#
|
42
|
+
# Returns the result of the yielded method
|
43
|
+
# Note - must be called from inside the `report_run` scope
|
44
|
+
def report_source(source)
|
45
|
+
super do |report|
|
46
|
+
result = yield report
|
47
|
+
|
48
|
+
report.warnings.each do |warning|
|
49
|
+
shell.warn "* #{report.name}: #{warning}"
|
50
|
+
end
|
51
|
+
|
52
|
+
result
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# Reports on a dependency in a notices command run.
|
57
|
+
#
|
58
|
+
# dependency - An application dependency
|
59
|
+
#
|
60
|
+
# Returns the result of the yielded method
|
61
|
+
# Note - must be called from inside the `report_run` scope
|
62
|
+
def report_dependency(dependency)
|
63
|
+
super do |report|
|
64
|
+
result = yield report
|
65
|
+
report.warnings.each do |warning|
|
66
|
+
shell.warn "* #{report.name}: #{warning}"
|
67
|
+
end
|
68
|
+
result
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# Returns notices information for a dependency report
|
73
|
+
def notices(report)
|
74
|
+
return unless report.target.is_a?(Licensed::Dependency)
|
75
|
+
|
76
|
+
cached_record = report["cached_record"]
|
77
|
+
return unless cached_record
|
78
|
+
|
79
|
+
texts = cached_record.licenses.map(&:text)
|
80
|
+
cached_record.notices.each do |notice|
|
81
|
+
case notice
|
82
|
+
when Hash
|
83
|
+
texts << notice["text"]
|
84
|
+
when String
|
85
|
+
texts << notice
|
86
|
+
else
|
87
|
+
shell.warn "* unable to parse notices for #{report.target.name}"
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
<<~NOTICE
|
92
|
+
#{cached_record["name"]}@#{cached_record["version"]}
|
93
|
+
|
94
|
+
#{texts.map(&:strip).reject(&:empty?).compact.join(TEXT_SEPARATOR)}
|
95
|
+
NOTICE
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -15,20 +15,37 @@ 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
|
19
18
|
|
20
|
-
|
21
|
-
|
19
|
+
warning_reports = all_reports.select { |r| r.warnings.any? }.to_a
|
20
|
+
if warning_reports.any?
|
21
|
+
shell.newline
|
22
|
+
shell.warn "Warnings:"
|
23
|
+
warning_reports.each do |r|
|
24
|
+
display_metadata = r.map { |k, v| "#{k}: #{v}" }.join(", ")
|
25
|
+
|
26
|
+
shell.warn "* #{r.name}"
|
27
|
+
shell.warn " #{display_metadata}" unless display_metadata.empty?
|
28
|
+
r.warnings.each do |warning|
|
29
|
+
shell.warn " - #{warning}"
|
30
|
+
end
|
31
|
+
shell.newline
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
errored_reports = all_reports.select { |r| r.errors.any? }.to_a
|
36
|
+
|
37
|
+
dependency_count = all_reports.select { |r| r.target.is_a?(Licensed::Dependency) }.size
|
38
|
+
error_count = errored_reports.sum { |r| r.errors.size }
|
22
39
|
|
23
40
|
if error_count > 0
|
24
41
|
shell.newline
|
25
42
|
shell.error "Errors:"
|
26
|
-
errored_reports.each do |
|
27
|
-
display_metadata =
|
43
|
+
errored_reports.each do |r|
|
44
|
+
display_metadata = r.map { |k, v| "#{k}: #{v}" }.join(", ")
|
28
45
|
|
29
|
-
shell.error "* #{
|
46
|
+
shell.error "* #{r.name}"
|
30
47
|
shell.error " #{display_metadata}" unless display_metadata.empty?
|
31
|
-
|
48
|
+
r.errors.each do |error|
|
32
49
|
shell.error " - #{error}"
|
33
50
|
end
|
34
51
|
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
|