license_finder 7.0.1 → 7.2.0

Sign up to get free protection for your applications and to get access to all the features.
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