license_finder 7.0.1 → 7.2.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.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/.github/dependabot.yml +7 -0
  3. data/.pre-commit-hooks.yaml +10 -0
  4. data/.rubocop.yml +5 -1
  5. data/CHANGELOG.md +41 -0
  6. data/CONTRIBUTING.md +1 -0
  7. data/Dockerfile +129 -122
  8. data/README.md +53 -14
  9. data/Rakefile +1 -1
  10. data/VERSION +1 -1
  11. data/ci/pipelines/pull-request.yml.erb +29 -32
  12. data/ci/pipelines/release.yml.erb +17 -41
  13. data/ci/scripts/run-tests.sh +20 -4
  14. data/ci/tasks/rubocop.yml +3 -3
  15. data/ci/tasks/update-changelog.yml +2 -2
  16. data/dlf +6 -1
  17. data/lib/license_finder/cli/base.rb +2 -0
  18. data/lib/license_finder/cli/licenses.rb +8 -3
  19. data/lib/license_finder/cli/main.rb +3 -1
  20. data/lib/license_finder/configuration.rb +8 -0
  21. data/lib/license_finder/core.rb +4 -2
  22. data/lib/license_finder/decision_applier.rb +1 -1
  23. data/lib/license_finder/decisions.rb +24 -6
  24. data/lib/license_finder/license/definitions.rb +129 -19
  25. data/lib/license_finder/license/templates/AGPL3.txt +661 -0
  26. data/lib/license_finder/license/templates/Apache2.txt +0 -2
  27. data/lib/license_finder/license/templates/Artistic.txt +128 -0
  28. data/lib/license_finder/license/templates/CC01_alt.txt +31 -0
  29. data/lib/license_finder/license/templates/CDDL1_1.txt +123 -0
  30. data/lib/license_finder/license/templates/CPL1.txt +217 -0
  31. data/lib/license_finder/license/templates/EPL2.txt +80 -0
  32. data/lib/license_finder/license/templates/Unlicense.txt +24 -0
  33. data/lib/license_finder/license/text.rb +4 -0
  34. data/lib/license_finder/license.rb +1 -1
  35. data/lib/license_finder/manual_licenses.rb +79 -0
  36. data/lib/license_finder/package.rb +1 -0
  37. data/lib/license_finder/package_manager.rb +2 -1
  38. data/lib/license_finder/package_managers/cargo.rb +1 -1
  39. data/lib/license_finder/package_managers/conan.rb +50 -8
  40. data/lib/license_finder/package_managers/dep.rb +43 -41
  41. data/lib/license_finder/package_managers/dotnet.rb +5 -2
  42. data/lib/license_finder/package_managers/go_dep.rb +1 -1
  43. data/lib/license_finder/package_managers/go_workspace.rb +3 -2
  44. data/lib/license_finder/package_managers/maven.rb +18 -10
  45. data/lib/license_finder/package_managers/npm.rb +14 -1
  46. data/lib/license_finder/package_managers/nuget.rb +5 -0
  47. data/lib/license_finder/package_managers/pip.rb +1 -1
  48. data/lib/license_finder/package_managers/pnpm.rb +126 -0
  49. data/lib/license_finder/package_managers/yarn.rb +69 -20
  50. data/lib/license_finder/package_utils/conan_info_parser.rb +2 -2
  51. data/lib/license_finder/package_utils/conan_info_parser_v2.rb +82 -0
  52. data/lib/license_finder/package_utils/license_files.rb +12 -2
  53. data/lib/license_finder/package_utils/licensing.rb +2 -1
  54. data/lib/license_finder/package_utils/maven_dependency_finder.rb +43 -1
  55. data/lib/license_finder/package_utils/notice_files.rb +14 -3
  56. data/lib/license_finder/package_utils/possible_license_file.rb +8 -2
  57. data/lib/license_finder/package_utils/pypi.rb +3 -1
  58. data/lib/license_finder/packages/maven_package.rb +13 -1
  59. data/lib/license_finder/packages/npm_package.rb +56 -9
  60. data/lib/license_finder/packages/pnpm_package.rb +13 -0
  61. data/lib/license_finder/printer.rb +2 -2
  62. data/lib/license_finder/reports/csv_report.rb +10 -1
  63. data/lib/license_finder/scanner.rb +3 -3
  64. data/license_finder.gemspec +12 -11
  65. metadata +54 -28
@@ -16,6 +16,8 @@ module LicenseFinder
16
16
  NEWLINE_CHARACTER = /\n+/.freeze
17
17
  QUOTE_COMMENT_CHARACTER = /^\s*>+/.freeze
18
18
  ESCAPED_QUOTES = /\\"/.freeze
19
+ SPECIAL_CHARACTERS = /§/.freeze
20
+ SPECIAL_DASHES = /–/.freeze
19
21
 
20
22
  def self.normalize_punctuation(text)
21
23
  text.dup.force_encoding('UTF-8')
@@ -26,6 +28,8 @@ module LicenseFinder
26
28
  .gsub(NEWLINE_CHARACTER, ' ')
27
29
  .gsub(ESCAPED_QUOTES, '"')
28
30
  .gsub(QUOTES, '"')
31
+ .gsub(SPECIAL_CHARACTERS, '?')
32
+ .gsub(SPECIAL_DASHES, '-')
29
33
  .strip
30
34
  rescue ArgumentError => _e
31
35
  text
@@ -85,7 +85,7 @@ module LicenseFinder
85
85
  attr_reader :short_name, :pretty_name, :other_names, :spdx_id, :matcher
86
86
 
87
87
  def names
88
- ([short_name, pretty_name] + other_names).uniq
88
+ ([short_name, pretty_name, spdx_id] + other_names).uniq
89
89
  end
90
90
  end
91
91
 
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LicenseFinder
4
+ class ManualLicenses
5
+ def initialize
6
+ @all_versions = {}
7
+ @specific_versions = {}
8
+ end
9
+
10
+ def licenses_of(name, version = nil)
11
+ return @all_versions[name] if @all_versions[name]
12
+
13
+ if version && @specific_versions[name] && @specific_versions[name][version]
14
+ @specific_versions[name][version]
15
+ else
16
+ Set.new
17
+ end
18
+ end
19
+
20
+ def assign_to_all_versions(name, lic)
21
+ # Ex: licenses add foo_gem MIT => Adds MIT at "all" versions for this gem
22
+
23
+ @all_versions[name] ||= Set.new
24
+ @all_versions[name] << to_license(lic)
25
+
26
+ @specific_versions.delete(name)
27
+ end
28
+
29
+ def assign_to_specific_versions(name, lic, versions)
30
+ # Ex: licenses add foo_gem MIT --version=1.0 => Adds MIT at only 1.0 for this gem
31
+
32
+ @specific_versions[name] ||= {}
33
+ versions.each do |version|
34
+ @specific_versions[name][version] ||= Set.new
35
+ @specific_versions[name][version] << to_license(lic)
36
+ end
37
+
38
+ @all_versions.delete(name)
39
+ end
40
+
41
+ def unassign_from_all_versions(name, lic = nil)
42
+ if lic
43
+ # Ex: licenses remove foo_gem MIT => Removes MIT at all versions for this gem
44
+ @all_versions[name]&.delete(to_license(lic))
45
+
46
+ @specific_versions[name]&.each_value do |licenses|
47
+ licenses.delete(to_license(lic))
48
+ end
49
+ else
50
+ # Ex: licenses remove foo_gem => Removes all licenses for all versions of the gem
51
+ @all_versions.delete(name)
52
+ @specific_versions.delete(name)
53
+ end
54
+ end
55
+
56
+ def unassign_from_specific_versions(name, lic, versions)
57
+ return unless @specific_versions[name]
58
+
59
+ versions.each do |version|
60
+ if @specific_versions[name][version]
61
+ if lic
62
+ # Ex: licenses remove foo_gem MIT --version=1.0 => Removes MIT at only 1.0 for this gem
63
+ @specific_versions[name][version].delete(to_license(lic))
64
+ @specific_versions[name].delete(version) if @specific_versions[name][version].empty?
65
+ else
66
+ # Ex: licenses remove foo_gem --version=1.0 => Removes all licenses at only 1.0 for the gem
67
+ @specific_versions[name].delete(version)
68
+ end
69
+ end
70
+ end
71
+ end
72
+
73
+ private
74
+
75
+ def to_license(lic)
76
+ License.find_by_name(lic)
77
+ end
78
+ end
79
+ end
@@ -187,6 +187,7 @@ require 'license_finder/packages/merged_package'
187
187
  require 'license_finder/packages/nuget_package'
188
188
  require 'license_finder/packages/conan_package'
189
189
  require 'license_finder/packages/yarn_package'
190
+ require 'license_finder/packages/pnpm_package'
190
191
  require 'license_finder/packages/sbt_package'
191
192
  require 'license_finder/packages/cargo_package'
192
193
  require 'license_finder/packages/composer_package'
@@ -136,7 +136,7 @@ module LicenseFinder
136
136
  FileUtils.mkdir_p @log_directory
137
137
 
138
138
  # replace whitespace with underscores and remove slashes
139
- log_file_name = package_management_command&.gsub(/\s/, '_')&.gsub(%r{/}, '')
139
+ log_file_name = package_management_command&.gsub(/\s/, '_')&.delete('/')
140
140
  log_file = File.join(@log_directory, "prepare_#{log_file_name || 'errors'}.log")
141
141
 
142
142
  File.open(log_file, 'w') do |f|
@@ -158,6 +158,7 @@ require 'license_finder/package_managers/go_modules'
158
158
  require 'license_finder/package_managers/trash'
159
159
  require 'license_finder/package_managers/bundler'
160
160
  require 'license_finder/package_managers/npm'
161
+ require 'license_finder/package_managers/pnpm'
161
162
  require 'license_finder/package_managers/yarn'
162
163
  require 'license_finder/package_managers/pip'
163
164
  require 'license_finder/package_managers/pipenv'
@@ -6,7 +6,7 @@ module LicenseFinder
6
6
  class Cargo < PackageManager
7
7
  def current_packages
8
8
  cargo_output.map do |package|
9
- path = Dir.glob("#{Dir.home}/.cargo/registry/src/**/#{package['name']}-#{package['version']}").first
9
+ path = Dir.glob("#{Dir.home}/.cargo/registry/src/*/#{package['name']}-#{package['version']}").first
10
10
  CargoPackage.new(package, logger: logger, install_path: path)
11
11
  end
12
12
  end
@@ -1,27 +1,69 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'license_finder/package_utils/conan_info_parser'
4
+ require 'license_finder/package_utils/conan_info_parser_v2'
4
5
 
5
6
  module LicenseFinder
6
7
  class Conan < PackageManager
7
8
  def possible_package_paths
8
- [project_path.join('conanfile.txt')]
9
+ [project_path.join('conanfile.txt'), project_path.join('conanfile.py')]
9
10
  end
10
11
 
11
- def current_packages
12
- install_command = 'conan install .'
12
+ def license_file_is_good?(license_file_path)
13
+ !license_file_path.nil? && File.file?(license_file_path)
14
+ end
15
+
16
+ def license_file(project_path, name)
17
+ candidates = Dir.glob("#{project_path}/licenses/#{name}/**/LICENSE*")
18
+ candidates.each do |candidate|
19
+ return candidate if license_file_is_good?(candidate)
20
+ end
21
+ nil
22
+ end
23
+
24
+ def deps_list_conan_v1(project_path)
13
25
  info_command = 'conan info .'
14
- Dir.chdir(project_path) { Cmd.run(install_command) }
15
26
  info_output, _stderr, _status = Dir.chdir(project_path) { Cmd.run(info_command) }
27
+ return nil if info_output.empty?
16
28
 
17
29
  info_parser = ConanInfoParser.new
30
+ info_parser.parse(info_output)
31
+ end
32
+
33
+ def deps_list_conan_v2(project_path)
34
+ info_command = 'conan graph info .'
35
+ info_output, stderr, _status = Dir.chdir(project_path) { Cmd.run(info_command) }
36
+ if info_output.empty?
37
+ return if stderr.empty?
38
+
39
+ info_output = stderr
40
+ end
41
+ info_parser = ConanInfoParserV2.new
42
+ info_parser.parse(info_output)
43
+ end
44
+
45
+ def deps_list(project_path)
46
+ deps = deps_list_conan_v1(project_path)
47
+ deps = deps_list_conan_v2(project_path) if deps.nil? || deps.empty?
48
+ deps
49
+ end
50
+
51
+ def current_packages
52
+ install_command = 'conan install .'
53
+ Dir.chdir(project_path) { Cmd.run(install_command) }
54
+
55
+ deps = deps_list(project_path)
56
+ return [] if deps.nil?
18
57
 
19
- deps = info_parser.parse(info_output)
20
58
  deps.map do |dep|
21
59
  name, version = dep['name'].split('/')
22
- url = dep['URL']
23
- license_file_path = Dir.glob("#{project_path}/licenses/#{name}/**/LICENSE*").first
24
- ConanPackage.new(name, version, File.open(license_file_path).read, url) unless name == 'conanfile.txt'
60
+ license_file_path = license_file(project_path, name)
61
+
62
+ next unless license_file_is_good?(license_file_path)
63
+
64
+ url = dep['homepage']
65
+ url = dep['url'] if url.nil?
66
+ ConanPackage.new(name, version, File.open(license_file_path).read, url)
25
67
  end.compact
26
68
  end
27
69
  end
@@ -1,43 +1,45 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'tomlrb'
4
-
5
- module LicenseFinder
6
- class Dep < PackageManager
7
- def possible_package_paths
8
- [project_path.join('Gopkg.lock')]
9
- end
10
-
11
- def current_packages
12
- toml = Tomlrb.load_file(detected_package_path)
13
- projects = toml['projects']
14
-
15
- return [] if projects.nil?
16
-
17
- projects.map do |project|
18
- GoPackage.from_dependency({
19
- 'ImportPath' => project['name'],
20
- 'InstallPath' => project_path.join('vendor', project['name']),
21
- 'Rev' => project['revision'],
22
- 'Homepage' => repo_name(project['name'])
23
- }, nil, true)
24
- end
25
- end
26
-
27
- def repo_name(name)
28
- name.split('/')[0..2].join('/')
29
- end
30
-
31
- def self.takes_priority_over
32
- Go15VendorExperiment
33
- end
34
-
35
- def prepare_command
36
- 'dep ensure -vendor-only'
37
- end
38
-
39
- def package_management_command
40
- 'dep'
41
- end
42
- end
43
- end
3
+ # Dep has been deprecated since 2020
4
+ #
5
+ # require 'tomlrb'
6
+ #
7
+ # module LicenseFinder
8
+ # class Dep < PackageManager
9
+ # def possible_package_paths
10
+ # [project_path.join('Gopkg.lock')]
11
+ # end
12
+ #
13
+ # def current_packages
14
+ # toml = Tomlrb.load_file(detected_package_path)
15
+ # projects = toml['projects']
16
+ #
17
+ # return [] if projects.nil?
18
+ #
19
+ # projects.map do |project|
20
+ # GoPackage.from_dependency({
21
+ # 'ImportPath' => project['name'],
22
+ # 'InstallPath' => project_path.join('vendor', project['name']),
23
+ # 'Rev' => project['revision'],
24
+ # 'Homepage' => repo_name(project['name'])
25
+ # }, nil, true)
26
+ # end
27
+ # end
28
+ #
29
+ # def repo_name(name)
30
+ # name.split('/')[0..2].join('/')
31
+ # end
32
+ #
33
+ # def self.takes_priority_over
34
+ # Go15VendorExperiment
35
+ # end
36
+ #
37
+ # def prepare_command
38
+ # 'dep ensure -vendor-only'
39
+ # end
40
+ #
41
+ # def package_management_command
42
+ # 'dep'
43
+ # end
44
+ # end
45
+ # end
@@ -42,9 +42,13 @@ module LicenseFinder
42
42
  end
43
43
 
44
44
  def read_license_urls
45
- possible_spec_paths.flat_map do |path|
45
+ raw_licenses = possible_spec_paths.flat_map do |path|
46
46
  Nuget.nuspec_license_urls(File.read(path)) if File.exist? path
47
47
  end.compact
48
+
49
+ raw_licenses&.map! do |license|
50
+ license.gsub('https://licenses.nuget.org/', '')
51
+ end
48
52
  end
49
53
 
50
54
  def ==(other)
@@ -61,7 +65,6 @@ module LicenseFinder
61
65
  package_metadatas = asset_files
62
66
  .flat_map { |path| AssetFile.new(path).dependencies }
63
67
  .uniq { |d| [d.name, d.version] }
64
-
65
68
  package_metadatas.map do |d|
66
69
  path = Dir.glob("#{Dir.home}/.nuget/packages/#{d.name.downcase}/#{d.version}").first
67
70
  NugetPackage.new(d.name, d.version, spec_licenses: d.read_license_urls, install_path: path)
@@ -56,7 +56,7 @@ module LicenseFinder
56
56
  packages_grouped_by_revision = all_packages.group_by { |package| package['Rev'] }
57
57
  result = []
58
58
 
59
- packages_grouped_by_revision.each do |_sha, packages_in_group|
59
+ packages_grouped_by_revision.each_value do |packages_in_group|
60
60
  all_paths_in_group = packages_in_group.map { |p| p['ImportPath'] }
61
61
  common_paths = CommonPathHelper.longest_common_paths(all_paths_in_group)
62
62
  package_info = packages_in_group.first
@@ -51,11 +51,12 @@ module LicenseFinder
51
51
  def active?
52
52
  return false if @strict_matching
53
53
 
54
+ # Dep has been deprecated since 2020
54
55
  godep = LicenseFinder::GoDep.new(project_path: Pathname(project_path))
55
- dep = LicenseFinder::Dep.new(project_path: Pathname(project_path))
56
56
  # go workspace is only active if GoDep wasn't. There are some projects
57
57
  # that will use the .envrc and have a Godep folder as well.
58
- !!(!godep.active? && !dep.active? && envrc_path && ENVRC_REGEXP.match(IO.read(envrc_path)))
58
+ # !!(!godep.active? && !dep.active? && envrc_path && ENVRC_REGEXP.match(IO.read(envrc_path)))
59
+ !!(!godep.active? && envrc_path && ENVRC_REGEXP.match(IO.read(envrc_path)))
59
60
  end
60
61
 
61
62
  private
@@ -13,22 +13,20 @@ module LicenseFinder
13
13
  end
14
14
 
15
15
  def current_packages
16
+ # Generate a file "target/generated-resources/licenses.xml" that contains a list of
17
+ # dependencies including their groupId, artifactId, version and license (name, file, url).
18
+ # The license file downloaded this way, however, is a generic one without author information.
19
+ # This file also does not contain further information about the package like its name,
20
+ # description or website URL.
16
21
  command = "#{package_management_command} org.codehaus.mojo:license-maven-plugin:download-licenses"
17
22
  command += " -Dlicense.excludedScopes=#{@ignored_groups.to_a.join(',')}" if @ignored_groups && !@ignored_groups.empty?
18
23
  command += " #{@maven_options}" unless @maven_options.nil?
19
24
  _stdout, stderr, status = Dir.chdir(project_path) { Cmd.run(command) }
20
25
  raise "Command '#{command}' failed to execute: #{stderr}" unless status.success?
21
26
 
22
- dependencies = MavenDependencyFinder.new(project_path).dependencies
23
- packages = dependencies.flat_map do |xml|
24
- options = {
25
- 'GroupTags' => { 'licenses' => 'license', 'dependencies' => 'dependency' },
26
- 'ForceArray' => %w[license dependency]
27
- }
28
- contents = XmlSimple.xml_in(xml, options)['dependencies']
29
- contents.map do |dep|
30
- MavenPackage.new(dep, logger: logger, include_groups: @include_groups)
31
- end
27
+ dependencies = MavenDependencyFinder.new(project_path, maven_repository_path).dependencies
28
+ packages = dependencies.map do |dep|
29
+ MavenPackage.new(dep, logger: logger, include_groups: @include_groups)
32
30
  end
33
31
  packages.uniq
34
32
  end
@@ -57,5 +55,15 @@ module LicenseFinder
57
55
 
58
56
  stdout.include?('null object or invalid expression')
59
57
  end
58
+
59
+ # Look up the path of the Maven repository (e.g. ~/.m2)
60
+ def maven_repository_path
61
+ command = "#{package_management_command} help:evaluate -Dexpression=settings.localRepository -q -DforceStdout"
62
+ command += " #{@maven_options}" unless @maven_options.nil?
63
+ stdout, stderr, status = Dir.chdir(project_path) { Cmd.run(command) }
64
+ raise "Command '#{command}' failed to execute: #{stderr}" unless status.success?
65
+
66
+ Pathname(stdout)
67
+ end
60
68
  end
61
69
  end
@@ -39,7 +39,7 @@ module LicenseFinder
39
39
  private
40
40
 
41
41
  def npm_json
42
- command = "#{package_management_command} list --json --long#{production_flag}"
42
+ command = "#{package_management_command} list --json --long#{all_flag}#{production_flag}"
43
43
  command += " #{@npm_options}" unless @npm_options.nil?
44
44
  stdout, stderr, status = Dir.chdir(project_path) { Cmd.run(command) }
45
45
  # we can try and continue if we got an exit status 1 - unmet peer dependency
@@ -53,5 +53,18 @@ module LicenseFinder
53
53
 
54
54
  @ignored_groups.include?('devDependencies') ? ' --production' : ''
55
55
  end
56
+
57
+ def all_flag
58
+ npm_version >= 7 ? ' --all' : ''
59
+ end
60
+
61
+ def npm_version
62
+ command = "#{package_management_command} -v"
63
+ stdout, stderr, status = Dir.chdir(project_path) { Cmd.run(command) }
64
+ raise "Command '#{command}' failed to execute: #{stderr}" unless status.success?
65
+
66
+ version = stdout.split('.').map(&:to_i)
67
+ version[0]
68
+ end
56
69
  end
57
70
  end
@@ -51,6 +51,10 @@ module LicenseFinder
51
51
  def current_packages
52
52
  dependencies.each_with_object({}) do |dep, memo|
53
53
  licenses = license_urls(dep)
54
+ licenses&.map! do |license|
55
+ license.gsub('https://licenses.nuget.org/', '')
56
+ end
57
+
54
58
  path = Dir.glob("#{Dir.home}/.nuget/packages/#{dep.name.downcase}/#{dep.version}").first
55
59
 
56
60
  memo[dep.name] ||= NugetPackage.new(dep.name, dep.version, spec_licenses: licenses, install_path: path)
@@ -60,6 +64,7 @@ module LicenseFinder
60
64
 
61
65
  def license_urls(dep)
62
66
  files = Dir["**/#{dep.name}.#{dep.version}.nupkg"]
67
+
63
68
  return nil if files.empty?
64
69
 
65
70
  file = files.first
@@ -4,7 +4,7 @@ require 'json'
4
4
 
5
5
  module LicenseFinder
6
6
  class Pip < PackageManager
7
- DEFAULT_VERSION = '2'
7
+ DEFAULT_VERSION = '3'
8
8
 
9
9
  def initialize(options = {})
10
10
  super
@@ -0,0 +1,126 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'tempfile'
5
+
6
+ module LicenseFinder
7
+ class PNPM < PackageManager
8
+ def initialize(options = {})
9
+ super
10
+ @pnpm_options = options[:pnpm_options]
11
+ end
12
+
13
+ SHELL_COMMAND = 'pnpm licenses list --json --long'
14
+
15
+ def possible_package_paths
16
+ [project_path.join('pnpm-lock.yaml')]
17
+ end
18
+
19
+ def self.takes_priority_over
20
+ NPM
21
+ end
22
+
23
+ def current_packages
24
+ # check if the minimum version of PNPM is met
25
+ raise 'The minimum PNPM version is not met, requires 7.17.0 or later' unless supported_pnpm?
26
+
27
+ # check if the project directory has workspace file
28
+ cmd = PNPM::SHELL_COMMAND.to_s
29
+ cmd += ' --no-color'
30
+ cmd += ' --recursive' unless project_has_workspaces == false
31
+ cmd += " --dir #{project_path}" unless project_path.nil?
32
+ cmd += " #{@pnpm_options}" unless @pnpm_options.nil?
33
+
34
+ stdout, stderr, status = Cmd.run(cmd)
35
+ raise "Command '#{cmd}' failed to execute: #{stderr}" unless status.success?
36
+
37
+ json_objects = JSON.parse(stdout)
38
+ get_pnpm_packages(json_objects)
39
+ end
40
+
41
+ def get_pnpm_packages(json_objects)
42
+ packages = []
43
+ incompatible_packages = []
44
+
45
+ json_objects.map do |_, value|
46
+ value.each do |pkg|
47
+ name = pkg['name']
48
+
49
+ if pkg['version']
50
+ version = pkg['version']
51
+ elsif pkg['versions']
52
+ version = pkg['versions'][0]
53
+ end
54
+
55
+ license = pkg['license']
56
+ homepage = pkg['vendorUrl']
57
+ author = pkg['vendorName']
58
+ module_path = pkg['path']
59
+
60
+ package = PNPMPackage.new(
61
+ name,
62
+ version,
63
+ spec_licenses: [license],
64
+ homepage: homepage,
65
+ authors: author,
66
+ install_path: module_path
67
+ )
68
+ packages << package
69
+ end
70
+ end
71
+
72
+ packages + incompatible_packages.uniq
73
+ end
74
+
75
+ def package_management_command
76
+ 'pnpm'
77
+ end
78
+
79
+ def prepare_command
80
+ 'pnpm install --no-lockfile --ignore-scripts'
81
+ end
82
+
83
+ def prepare
84
+ prep_cmd = "#{prepare_command}#{production_flag}"
85
+ _stdout, stderr, status = Dir.chdir(project_path) { Cmd.run(prep_cmd) }
86
+
87
+ return if status.success?
88
+
89
+ log_errors stderr
90
+ raise "Prepare command '#{prep_cmd}' failed" unless @prepare_no_fail
91
+ end
92
+
93
+ private
94
+
95
+ def project_has_workspaces
96
+ Dir.chdir(project_path) do
97
+ return File.file?('pnpm-workspace.yaml')
98
+ end
99
+ end
100
+
101
+ # PNPM introduced the licenses command in 7.17.0
102
+ def supported_pnpm?
103
+ Dir.chdir(project_path) do
104
+ version_string, stderr_str, status = Cmd.run('pnpm --version')
105
+ raise "Command 'pnpm -v' failed to execute: #{stderr_str}" unless status.success?
106
+
107
+ version = version_string.split('.').map(&:to_i)
108
+ major = version[0]
109
+ minor = version[1]
110
+ patch = version[1]
111
+
112
+ return true if major > 7
113
+ return true if major == 7 && minor > 17
114
+ return true if major == 7 && minor == 17 && patch >= 0
115
+
116
+ return false
117
+ end
118
+ end
119
+
120
+ def production_flag
121
+ return '' if @ignored_groups.nil?
122
+
123
+ @ignored_groups.include?('devDependencies') ? ' --prod' : ''
124
+ end
125
+ end
126
+ end