license_patrol 1.0.0 → 1.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6d2e13b85fa48187c33aa76ef484cbdf3022e10551286d94ad79e5e53811d6a2
4
- data.tar.gz: d763c2015d71fa8f20ba67258151e013886ed057c6a4c1c8e1831ec27f43106c
3
+ metadata.gz: bfadea16931d270ad27f48e5db7d22d95751c39335299a0375b037b192d88925
4
+ data.tar.gz: acba13bf06366eb312bde648d1aefc653576ff2fa67cae75a2cbddf9d697a84b
5
5
  SHA512:
6
- metadata.gz: a011b12567263048cdba247fc4cd8270e2183a8ba6baf314c0342e65ca15cb73d0c12ab83c8bbd2f0cdc30d39823e3dc3ec409379f7433fe27d969e22e794cda
7
- data.tar.gz: 5eafee911a325c2fa65263deaceb81159a198f495d543c0cccdcb0a0782a99cc3bd66762307521f927ec04ffec8f1999d37480e035efcbc3795d50814ba0adc3
6
+ metadata.gz: bd3c0356c36aa7aae5ed0891ce8e4b4baff329fdbbd21f26316f87f13d6de4daf9f4d3b6ed1831f4b1cbac9a7763f0224ef371d9cc2a87abf8ea67e760ad8a10
7
+ data.tar.gz: ba987bba63a5b6161407ad3fb73c904d15e6d4d2438cf6ce8cfea6e867dfb7435e8cfab1be6c7f2b3c2bb34bd19ff955de05155356d0cff3519c58863f7ac89d
@@ -0,0 +1,2 @@
1
+ # Managed by https://github.com/powerhome/software. Do not make changes here, they will be reverted.
2
+ * @powerhome/heroes-for-hire
@@ -0,0 +1,35 @@
1
+ # Managed by https://github.com/powerhome/software. Do not make changes here, they will be reverted.
2
+
3
+ name: 'Close stale issues and PRs'
4
+ on:
5
+ workflow_dispatch:
6
+ schedule:
7
+ - cron: '37 21 * * *'
8
+
9
+ jobs:
10
+ stale:
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - uses: actions/stale@4391f3da665fdf50b6810c1a66712fb9ba21aa93 # v11.0.0
14
+ with:
15
+ operations-per-run: 200
16
+ delete-branch: true
17
+ stale-pr-message: >
18
+ This issue has been automatically marked as stale because it has not had
19
+ recent activity. It will be closed in 24 hours if no further activity occurs.
20
+
21
+ If this change is desirable, please accelerate completing it. If it is not,
22
+ please close the PR. If you're blocked on something, please ensure there's
23
+ a reference to this PR in a story on your team's board so the team will
24
+ follow up, and consider closing the PR for now.
25
+
26
+ Please do not artificially extend the deadline with a dummy comment. If
27
+ necessary, provide a status update, such as "this change is being actively
28
+ tested".
29
+
30
+ Thank you for your contributions and your collaboration in reducing WIP
31
+ and cycle time.
32
+ days-before-stale: 14
33
+ days-before-close: 3
34
+ days-before-issue-stale: -1
35
+ exempt-pr-labels: dependencies
data/CHANGELOG.md CHANGED
@@ -1,4 +1,17 @@
1
- # [1.0.0] / Unreleased
1
+ # [1.2.0] / 2026-10-28
2
+
3
+ ### Fixed
4
+ * Yarn 2, 3 and 4 projects scan without the third-party licenses plugin, reading packages from the built-in `yarn info` - [6a46a083](https://github.com/powerhome/license_patrol/commit/6a46a083) - Carlos Palhares
5
+
6
+ ### Changed
7
+ * Yarn 2+ no longer reports workspaces as packages, and `--prepare` only runs `yarn install` - [6a46a083](https://github.com/powerhome/license_patrol/commit/6a46a083) - Carlos Palhares
8
+
9
+ # [1.1.0] / 2026-10-21
10
+
11
+ ### Added
12
+ * `bundler_gemfiles` option to scan many gemfiles, such as Appraisal's `gemfiles/*.gemfile`, in one run - [5d7e1372](https://github.com/powerhome/license_patrol/commit/5d7e1372) - Carlos Palhares
13
+
14
+ # [1.0.0] / 2026-10-21
2
15
 
3
16
  First release as `license_patrol`, forked from LicenseFinder 7.2.1. Earlier entries are LicenseFinder's history.
4
17
 
@@ -1068,3 +1081,5 @@ Bugfixes:
1068
1081
  [7.1.0]: https://github.com/pivotal/LicenseFinder/compare/v7.0.1...v7.1.0
1069
1082
  [7.2.0]: https://github.com/pivotal/LicenseFinder/compare/v7.1.0...v7.2.0
1070
1083
  [7.2.1]: https://github.com/pivotal/LicenseFinder/compare/v7.2.0...v7.2.1
1084
+ [1.0.0]: https://github.com/powerhome/license_patrol/releases/tag/v1.0.0
1085
+ [1.1.0]: https://github.com/powerhome/license_patrol/compare/v1.0.0...v1.1.0
data/README.md CHANGED
@@ -48,7 +48,7 @@ and give you an actionable exception report.
48
48
  * Objective-C, Swift (via Carthage, CocoaPods \[0.39 and below. See [CocoaPods Specs Repo Sharding](http://blog.cocoapods.org/Sharding/)\]) and Swift Package Manager(SPM)
49
49
  * Elixir (via `mix`)
50
50
  * Golang (via `gvt`, `glide`,`dep`, `trash` and `govendor`)
51
- * JavaScript (via `yarn`)
51
+ * JavaScript (via `yarn` 1.x through 4.x)
52
52
  * C++/C (via `conan`)
53
53
  * Scala (via `sbt`)
54
54
  * Rust (via `cargo`)
@@ -328,7 +328,7 @@ be approved. The project name at the top of the report can be set with
328
328
  `license_patrol project_name add`.
329
329
 
330
330
  ### Note:
331
- When using the yarn package manager, when a node_module's package.json doesn't
331
+ When using Yarn 1, when a node_module's package.json doesn't
332
332
  explicitly declare a license, yarn indicates that it has inferred the license based
333
333
  on some keywords in other files by appending an asterisk to the license name. If you
334
334
  see a * at the end of the license name, this is intended.
@@ -466,6 +466,18 @@ If you store rebar dependencies in a custom directory (by setting `deps_dir` in
466
466
  You can also invoke a custom Mix script `remix` with `--mix_command remix` and
467
467
  set `--mix_deps_dir` to fetch Mix dependencies from a custom directory.
468
468
 
469
+ By default, `bundler` dependencies are read from the gemfile in `BUNDLE_GEMFILE`,
470
+ or from `Gemfile`. If your project locks dependencies in more than one gemfile
471
+ (e.g. when using [Appraisal](https://github.com/thoughtbot/appraisal)), pass all
472
+ of them to `--bundler_gemfiles`, relative to the project path. Glob patterns are
473
+ accepted, as long as they are quoted:
474
+
475
+ ```
476
+ $ license_patrol --bundler_gemfiles Gemfile 'gemfiles/*.gemfile'
477
+ ```
478
+
479
+ Every gem locked by any of the gemfiles will be reported, once per version.
480
+
469
481
  ### Narrow down Package Manager
470
482
 
471
483
  By default, license_patrol will check for all supported package managers,
@@ -492,6 +504,9 @@ rebar_command: './rebarw'
492
504
  rebar_deps_dir: './rebar_deps'
493
505
  mix_command: './mixw'
494
506
  mix_deps_dir: './mix_deps'
507
+ bundler_gemfiles:
508
+ - Gemfile
509
+ - gemfiles/*.gemfile
495
510
  enabled_package_managers:
496
511
  - bundler
497
512
  - gradle
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.0.0
1
+ 1.2.0
@@ -40,6 +40,7 @@ module LicensePatrol
40
40
  :project_path,
41
41
  :decisions_file,
42
42
  :enabled_package_managers,
43
+ :bundler_gemfiles,
43
44
  :go_full_version,
44
45
  :gradle_command,
45
46
  :gradle_include_groups,
@@ -24,6 +24,10 @@ module LicensePatrol
24
24
  'junit' => JunitReport
25
25
  }.freeze
26
26
 
27
+ class_option :bundler_gemfiles,
28
+ type: :array,
29
+ desc: "Gemfiles to be scanned, relative to the project path. Accepts glob patterns (e.g. 'gemfiles/*.gemfile' for Appraisal).
30
+ Only meaningful if used with a Ruby/bundler project. Defaults to BUNDLE_GEMFILE, or 'Gemfile'."
27
31
  class_option :go_full_version, desc: 'Whether dependency version should include full version. Only meaningful if used with a Go project. Defaults to false.'
28
32
  class_option :gradle_include_groups, desc: 'Whether dependency name should include group id. Only meaningful if used with a Java/gradle project. Defaults to false.'
29
33
  class_option :gradle_command,
@@ -77,6 +77,10 @@ module LicensePatrol
77
77
  get(:gradle_command)
78
78
  end
79
79
 
80
+ def bundler_gemfiles
81
+ get(:bundler_gemfiles)
82
+ end
83
+
80
84
  def go_full_version
81
85
  get(:go_full_version)
82
86
  end
@@ -95,6 +95,7 @@ module LicensePatrol
95
95
  log_directory: File.join(config.log_directory, project_name),
96
96
  ignored_groups: decisions.ignored_groups,
97
97
  enabled_package_manager_ids: config.enabled_package_manager_ids,
98
+ bundler_gemfiles: config.bundler_gemfiles,
98
99
  go_full_version: config.go_full_version,
99
100
  gradle_command: config.gradle_command,
100
101
  gradle_include_groups: config.gradle_include_groups,
@@ -2,23 +2,22 @@
2
2
 
3
3
  require 'bundler'
4
4
  require 'securerandom'
5
+ require 'shellwords'
5
6
 
6
7
  module LicensePatrol
7
8
  class Bundler < PackageManager
8
9
  def initialize(options = {})
9
10
  super
10
11
  @ignored_groups = options[:ignored_groups]
12
+ @gemfiles = Array(options[:bundler_gemfiles]) unless options[:bundler_gemfiles].nil?
11
13
  @definition = options[:definition] # dependency injection for tests
12
14
  end
13
15
 
14
16
  def current_packages
15
- with_gemfile do
16
- logger.debug self.class, "including groups #{included_groups.inspect}"
17
- details.map do |gem_detail, bundle_detail|
18
- BundlerPackage.new(gem_detail, bundle_detail, logger: logger).tap do |package|
19
- log_package_dependencies package
20
- end
21
- end
17
+ packages = existing_gemfile_paths.flat_map { |gemfile_path| current_packages_for(gemfile_path) }
18
+
19
+ packages.group_by { |package| [package.name, package.version] }.map do |_, same_packages|
20
+ merge_groups(same_packages)
22
21
  end
23
22
  end
24
23
 
@@ -32,68 +31,81 @@ module LicensePatrol
32
31
  gem_path = "lf-bundler-gems-#{SecureRandom.uuid}"
33
32
  logger.info self.class, "Running bundle install for #{Dir.pwd} with path #{gem_path}", color: :blue
34
33
 
35
- "bundle install #{ignored_groups_argument} --path #{gem_path}".strip
34
+ return "bundle install #{ignored_groups_argument} --path #{gem_path}".strip if existing_gemfile_paths.size <= 1
35
+
36
+ existing_gemfile_paths.map do |gemfile_path|
37
+ gemfile_argument = "--gemfile #{Shellwords.escape(relative_to_project(gemfile_path))}"
38
+ "bundle install #{gemfile_argument} #{ignored_groups_argument} --path #{gem_path}".squeeze(' ')
39
+ end.join(' && ')
36
40
  end
37
41
 
38
42
  def possible_package_paths
39
- [gemfile_path]
43
+ gemfile_paths
40
44
  end
41
45
 
42
46
  private
43
47
 
44
48
  attr_reader :ignored_groups
45
49
 
46
- def definition
47
- return @definition if @definition
50
+ def current_packages_for(gemfile_path)
51
+ with_gemfile(gemfile_path) do
52
+ definition = definition_for(gemfile_path)
53
+ included_groups = definition.groups - ignored_groups.map(&:to_sym)
54
+ logger.debug self.class, "including groups #{included_groups.inspect} from #{gemfile_path}"
48
55
 
49
- @definition = ::Bundler::Definition.build(detected_package_path, lockfile_path, nil) if detected_package_path
56
+ details(definition, included_groups, gemfile_path).map do |gem_detail, bundle_detail|
57
+ BundlerPackage.new(gem_detail, bundle_detail, logger: logger).tap do |package|
58
+ log_package_dependencies package
59
+ end
60
+ end
61
+ end
50
62
  end
51
63
 
52
- def with_gemfile
53
- previous_gemfile = ENV['BUNDLE_GEMFILE']
54
- ENV['BUNDLE_GEMFILE'] = gemfile_path.to_s
55
- yield
56
- ensure
57
- ENV['BUNDLE_GEMFILE'] = previous_gemfile
64
+ def definition_for(gemfile_path)
65
+ @definition || ::Bundler::Definition.build(gemfile_path, lockfile_path(gemfile_path), nil)
58
66
  end
59
67
 
60
- def details
61
- gem_details.map do |gem_detail|
68
+ def details(definition, included_groups, gemfile_path)
69
+ bundler_details = definition.dependencies
70
+
71
+ gem_details(definition, included_groups, gemfile_path).map do |gem_detail|
62
72
  bundle_detail = bundler_details.detect { |bundler_detail| bundler_detail.name == gem_detail.name }
63
73
  [gem_detail, bundle_detail]
64
74
  end
65
75
  end
66
76
 
67
- def gem_details
68
- return @gem_details if @gem_details
69
-
77
+ def gem_details(definition, included_groups, gemfile_path)
70
78
  # clear gem paths before running specs_for
71
79
  Gem.clear_paths
72
- if bundler_config_path_found
80
+ if bundler_config_path_found(gemfile_path)
73
81
  ::Bundler.reset!
74
82
  ::Bundler.configure
75
83
  end
76
- @gem_details = definition&.specs_for(included_groups) || []
84
+ definition.specs_for(included_groups)
77
85
  end
78
86
 
79
- def bundler_details
80
- @bundler_details ||= definition&.dependencies || []
81
- end
82
-
83
- def included_groups
84
- definition_groups - ignored_groups.map(&:to_sym)
87
+ # The same gem (name and version) may be locked by several gemfiles,
88
+ # in which case it should be reported once, but under all its groups.
89
+ def merge_groups(packages)
90
+ packages.first.tap do |package|
91
+ package.groups.concat(packages.drop(1).flat_map(&:groups)).uniq!
92
+ end
85
93
  end
86
94
 
87
- def definition_groups
88
- definition&.groups || []
95
+ def with_gemfile(gemfile_path)
96
+ previous_gemfile = ENV['BUNDLE_GEMFILE']
97
+ ENV['BUNDLE_GEMFILE'] = gemfile_path.to_s
98
+ yield
99
+ ensure
100
+ ENV['BUNDLE_GEMFILE'] = previous_gemfile
89
101
  end
90
102
 
91
- def lockfile_path
103
+ def lockfile_path(gemfile_path)
92
104
  Pathname("#{gemfile_path}.lock")
93
105
  end
94
106
 
95
107
  # Bundler keeps the config (e.g. BUNDLE_PATH for vendored gems) next to the gemfile
96
- def bundler_config_path_found
108
+ def bundler_config_path_found(gemfile_path)
97
109
  config_file = gemfile_path.dirname.join('.bundle/config')
98
110
 
99
111
  return false unless File.exist?(config_file)
@@ -114,14 +126,41 @@ module LicensePatrol
114
126
  end
115
127
  end
116
128
 
117
- def gemfile_path
118
- @gemfile_path ||= project_path.join(gemfile)
129
+ # All the gemfiles to be scanned, from the `bundler_gemfiles` option
130
+ # (which accepts glob patterns, e.g. `gemfiles/*.gemfile` for Appraisal),
131
+ # or the one in BUNDLE_GEMFILE, or `Gemfile`, relative to the project path.
132
+ def gemfile_paths
133
+ @gemfile_paths ||= gemfiles.flat_map { |gemfile| expand_gemfile(gemfile) }.uniq
134
+ end
135
+
136
+ def existing_gemfile_paths
137
+ gemfile_paths.select(&:exist?)
138
+ end
139
+
140
+ def gemfiles
141
+ @gemfiles || [default_gemfile]
142
+ end
143
+
144
+ def relative_to_project(gemfile_path)
145
+ gemfile_path.expand_path.relative_path_from(project_path.expand_path).to_s
146
+ end
147
+
148
+ def expand_gemfile(gemfile)
149
+ return [project_path.join(gemfile)] unless glob?(gemfile)
150
+
151
+ Dir.glob(gemfile, base: project_path.to_s).sort
152
+ .reject { |path| path.end_with?('.lock') }
153
+ .map { |path| project_path.join(path) }
154
+ end
155
+
156
+ def glob?(gemfile)
157
+ gemfile.match?(/[*?\[{]/)
119
158
  end
120
159
 
121
160
  # BUNDLE_GEMFILE is taken as relative to the project path. Through `bundle exec`
122
161
  # it gets expanded to an absolute path, which may point to another project
123
162
  # (e.g. when using --project_path), in which case only its basename is kept.
124
- def gemfile
163
+ def default_gemfile
125
164
  gemfile = ENV['BUNDLE_GEMFILE']
126
165
  return 'Gemfile' if gemfile.nil? || gemfile.empty?
127
166
  return gemfile unless Pathname(gemfile).absolute?
@@ -8,30 +8,17 @@ module LicensePatrol
8
8
  end
9
9
 
10
10
  SHELL_COMMAND = 'yarn licenses list --recursive --json'
11
+ BERRY_SHELL_COMMAND = 'yarn info --all --recursive --json --extra manifest'
11
12
 
12
13
  def possible_package_paths
13
14
  [project_path.join('yarn.lock')]
14
15
  end
15
16
 
16
17
  def current_packages
17
- # the licenses plugin supports the classic production flag
18
- cmd = "#{Yarn::SHELL_COMMAND}#{classic_yarn_production_flag}"
19
18
  if yarn_version == 1
20
- cmd += ' --no-progress'
21
- cmd += " --cwd #{project_path}" unless project_path.nil?
22
- cmd += " #{@yarn_options}" unless @yarn_options.nil?
23
- end
24
-
25
- stdout, stderr, status = Dir.chdir(project_path) { Cmd.run(cmd) }
26
- raise "Command '#{cmd}' failed to execute: #{stderr}" unless status.success?
27
-
28
- json_strings = stdout.encode('ASCII', invalid: :replace, undef: :replace, replace: '?').split("\n")
29
- json_objects = json_strings.map { |json_object| JSON.parse(json_object) }
30
-
31
- if yarn_version == 1
32
- get_yarn1_packages(json_objects)
19
+ get_yarn1_packages(run_json_command(classic_shell_command))
33
20
  else
34
- get_yarn_packages(json_objects)
21
+ get_yarn_packages(run_json_command(BERRY_SHELL_COMMAND))
35
22
  end
36
23
  end
37
24
 
@@ -62,20 +49,28 @@ module LicensePatrol
62
49
 
63
50
  private
64
51
 
52
+ # devDependencies are left out when reading the packages, so all of them are installed
65
53
  def yarn_prepare_command
66
- "#{yarn_plugin_production_command}yarn install && yarn plugin import https://raw.githubusercontent.com/mhassan1/yarn-plugin-licenses/#{yarn_licenses_plugin_version}/bundles/@yarnpkg/plugin-licenses.js"
54
+ 'yarn install'
67
55
  end
68
56
 
69
57
  def classic_yarn_prepare_command
70
58
  "yarn install --ignore-engines --ignore-scripts#{classic_yarn_production_flag}"
71
59
  end
72
60
 
73
- def yarn_licenses_plugin_version
74
- if yarn_version == 2
75
- 'v0.6.0'
76
- else
77
- 'v0.7.2'
78
- end
61
+ def classic_shell_command
62
+ cmd = "#{Yarn::SHELL_COMMAND}#{classic_yarn_production_flag} --no-progress"
63
+ cmd += " --cwd #{project_path}" unless project_path.nil?
64
+ cmd += " #{@yarn_options}" unless @yarn_options.nil?
65
+ cmd
66
+ end
67
+
68
+ def run_json_command(cmd)
69
+ stdout, stderr, status = Dir.chdir(project_path) { Cmd.run(cmd) }
70
+ raise "Command '#{cmd}' failed to execute: #{stderr}" unless status.success?
71
+
72
+ json_strings = stdout.encode('ASCII', invalid: :replace, undef: :replace, replace: '?').split("\n")
73
+ json_strings.map { |json_object| JSON.parse(json_object) }
79
74
  end
80
75
 
81
76
  def yarn_version
@@ -88,40 +83,64 @@ module LicensePatrol
88
83
  end
89
84
  end
90
85
 
86
+ # Yarn 2+ prints one line per package in the lockfile, keyed by its locator
87
+ # (e.g. "@babel/core@npm:7.18.6"). Workspaces are the project's own code,
88
+ # so only the packages they depend on are reported.
91
89
  def get_yarn_packages(json_objects)
92
- packages = []
93
- incompatible_packages = []
94
- json_objects.each do |json_object|
95
- license = json_object['value']
96
- body = json_object['children']
97
-
98
- body.each do |package_name, vendor_info|
99
- valid_match = %r{(?<name>@?[\w/.-]+)@(?<manager>\D*):\D*(?<version>(\d+\.?)+)} =~ package_name.to_s
100
- valid_match = %r{(?<name>@?[\w/.-]+)@virtual:.+#(\D*):\D*(?<version>(\d+\.?)+)} =~ package_name.to_s if manager.eql?('virtual')
101
-
102
- if valid_match
103
- homepage = vendor_info['children']['vendorUrl']
104
- author = vendor_info['children']['vendorName']
105
- package = YarnPackage.new(
106
- name,
107
- version,
108
- spec_licenses: [license],
109
- homepage: homepage,
110
- authors: author,
111
- install_path: project_path.join(modules_folder, name)
112
- )
113
- packages << package
114
- end
115
-
116
- incompatible_match = %r{(?<name>@?[\w/.-]+)@[a-z]*:(?<version>(\.))} =~ package_name.to_s
117
- if incompatible_match
118
- package = YarnPackage.new(name, version, spec_licenses: [license])
119
- incompatible_packages.push(package)
120
- end
90
+ infos = json_objects.map { |json_object| [json_object['value'], json_object['children']] }.to_h
91
+ locators = ignore_dev_dependencies? ? production_locators(infos) : infos.keys
92
+
93
+ packages = locators.reject { |locator| workspace_locator?(locator) }.map do |locator|
94
+ name = locator_name(locator)
95
+ info = infos[locator]
96
+ manifest = info['Manifest'] || {}
97
+ YarnPackage.new(
98
+ name,
99
+ info['Version'],
100
+ spec_licenses: [manifest['License']].compact,
101
+ homepage: manifest['Homepage'],
102
+ install_path: project_path.join(modules_folder, name)
103
+ )
104
+ end
105
+
106
+ # a patched package (e.g. typescript) is listed next to the package it patches
107
+ packages.uniq { |package| [package.name, package.version] }
108
+ end
109
+
110
+ # Walks the dependencies of every workspace, skipping the ones they declare
111
+ # in devDependencies, like "yarn workspaces focus --all --production".
112
+ def production_locators(infos)
113
+ seen = Set.new
114
+ pending = infos.keys.select { |locator| workspace_locator?(locator) }
115
+ until pending.empty?
116
+ locator = devirtualize(pending.shift)
117
+ next unless infos.key?(locator) && seen.add?(locator)
118
+
119
+ dev_dependencies = workspace_locator?(locator) ? workspace_dev_dependencies(locator) : []
120
+ (infos[locator]['Dependencies'] || []).each do |dependency|
121
+ pending << dependency['locator'] unless dev_dependencies.include?(locator_name(dependency['descriptor']))
121
122
  end
122
123
  end
124
+ seen.to_a
125
+ end
123
126
 
124
- packages + incompatible_packages.uniq
127
+ def workspace_dev_dependencies(locator)
128
+ workspace_path = locator.delete_prefix("#{locator_name(locator)}@workspace:")
129
+ manifest = JSON.parse(File.read(project_path.join(workspace_path, 'package.json')))
130
+ (manifest['devDependencies'] || {}).keys - (manifest['dependencies'] || {}).keys
131
+ end
132
+
133
+ def locator_name(locator)
134
+ locator[%r{\A(@[^/@]+/)?[^@]+}]
135
+ end
136
+
137
+ def workspace_locator?(locator)
138
+ locator.start_with?("#{locator_name(locator)}@workspace:")
139
+ end
140
+
141
+ # "react-dom@virtual:c6e8...#npm:18.2.0" is "react-dom@npm:18.2.0" with its peer dependencies resolved
142
+ def devirtualize(locator)
143
+ locator.sub(/@virtual:[^#]*#/, '@')
125
144
  end
126
145
 
127
146
  def get_yarn1_packages(json_objects)
@@ -181,15 +200,11 @@ module LicensePatrol
181
200
  end
182
201
 
183
202
  def classic_yarn_production_flag
184
- return '' if @ignored_groups.nil?
185
-
186
- @ignored_groups.include?('devDependencies') ? ' --production' : ''
203
+ ignore_dev_dependencies? ? ' --production' : ''
187
204
  end
188
205
 
189
- def yarn_plugin_production_command
190
- return '' if @ignored_groups.nil?
191
-
192
- @ignored_groups.include?('devDependencies') ? 'yarn plugin import workspace-tools && yarn workspaces focus --all --production && ' : ''
206
+ def ignore_dev_dependencies?
207
+ !@ignored_groups.nil? && @ignored_groups.include?('devDependencies')
193
208
  end
194
209
  end
195
210
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: license_patrol
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ryan Collins
@@ -396,9 +396,11 @@ extensions: []
396
396
  extra_rdoc_files: []
397
397
  files:
398
398
  - ".force-build"
399
+ - ".github/CODEOWNERS"
399
400
  - ".github/dependabot.yml"
400
401
  - ".github/workflows/install-test-tools.sh"
401
402
  - ".github/workflows/license_patrol.yml"
403
+ - ".github/workflows/stale.yml"
402
404
  - ".gitignore"
403
405
  - ".pre-commit-hooks.yaml"
404
406
  - ".rspec"