licensed 2.9.2 → 2.12.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- 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 +24 -6
@@ -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
|