licensed 3.3.1 → 3.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +19 -1
- data/Rakefile +5 -3
- data/docs/adding_a_new_source.md +32 -3
- data/docs/commands/README.md +1 -1
- data/docs/sources/yarn.md +5 -4
- data/lib/licensed/reporters/status_reporter.rb +1 -1
- data/lib/licensed/sources/npm.rb +0 -4
- data/lib/licensed/sources/nuget.rb +2 -2
- data/lib/licensed/sources/source.rb +22 -3
- data/lib/licensed/sources/yarn/berry.rb +79 -0
- data/lib/licensed/sources/yarn/v1.rb +146 -0
- data/lib/licensed/sources/yarn.rb +15 -130
- data/lib/licensed/version.rb +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a3cb5bf8c20bf1177466536fbc30bc29135a9e76933e225af8353ba8eb89205d
|
4
|
+
data.tar.gz: 26acda6f4b9d90c2457725ecda8f0b00bc7fc793a4993a1e6aa9a6810a995d04
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5d71df74f6fca5c309230b18a7423f608cdf61ca0a5ddf9f45116b75681196a15ecee70d4d56877841b82c1d75d43fa573182ec300f1976a1e815f169de1598f
|
7
|
+
data.tar.gz: 645c4bf3cbb162e46061b730f947f0299336506fa425640f215e0e96ccb89124ef0e8a9725e6b9044fcd1c8e38c8809b7f5a911479d6583cc097b15d1f4a48c3
|
data/CHANGELOG.md
CHANGED
@@ -6,6 +6,24 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
|
6
6
|
|
7
7
|
## [Unreleased]
|
8
8
|
|
9
|
+
## 3.4.0
|
10
|
+
|
11
|
+
2021-12-14
|
12
|
+
|
13
|
+
### Added
|
14
|
+
|
15
|
+
- New Yarn enumerator with support for berry versions (https://github.com/github/licensed/pull/423)
|
16
|
+
|
17
|
+
### Fixed
|
18
|
+
|
19
|
+
- Error handling cases return correct values in the Yarn enumerator (https://github.com/github/licensed/pull/425)
|
20
|
+
- Fixed link in command documentation (:tada: @chibicco https://github.com/github/licensed/pull/416)
|
21
|
+
- Fixed minor backwards compatibility issue for Ruby 2.3 support (:tada: @dzunk https://github.com/github/licensed/pull/414)
|
22
|
+
|
23
|
+
### Changed
|
24
|
+
|
25
|
+
- Licensed's own dependencies are cached in the repository and kept up to date with GitHub Actions (https://github.com/github/licensed/pull/421)
|
26
|
+
|
9
27
|
## 3.3.1
|
10
28
|
|
11
29
|
2021-10-07
|
@@ -521,4 +539,4 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
|
521
539
|
|
522
540
|
Initial release :tada:
|
523
541
|
|
524
|
-
[Unreleased]: https://github.com/github/licensed/compare/3.
|
542
|
+
[Unreleased]: https://github.com/github/licensed/compare/3.4.0...HEAD
|
data/Rakefile
CHANGED
@@ -2,13 +2,16 @@
|
|
2
2
|
require "bundler/gem_tasks"
|
3
3
|
require "rake/testtask"
|
4
4
|
require "rubocop/rake_task"
|
5
|
+
require "licensed"
|
5
6
|
|
6
7
|
desc "Run source setup scripts"
|
7
8
|
task :setup, [:arguments] do |task, args|
|
8
9
|
arguments = args[:arguments].to_s.split
|
9
10
|
force = arguments.include?("-f") ? "-f" : ""
|
10
11
|
|
11
|
-
Dir["script/source-setup
|
12
|
+
Dir["script/source-setup/**/*"].each do |script|
|
13
|
+
next if File.directory?(script)
|
14
|
+
|
12
15
|
# green
|
13
16
|
puts "\033[32mRunning #{script}.\e[0m"
|
14
17
|
|
@@ -27,8 +30,7 @@ task :setup, [:arguments] do |task, args|
|
|
27
30
|
end
|
28
31
|
end
|
29
32
|
|
30
|
-
|
31
|
-
sources = Dir[sources_search].map { |f| File.basename(f, ".*") }
|
33
|
+
sources = Licensed::Sources::Source.sources.map { |source| source.full_type }
|
32
34
|
|
33
35
|
namespace :test do
|
34
36
|
sources.each do |source|
|
data/docs/adding_a_new_source.md
CHANGED
@@ -13,8 +13,37 @@ Dependency enumerators inherit and override the [`Licensed::Sources::Source`](..
|
|
13
13
|
|
14
14
|
### Optional method overrides
|
15
15
|
|
16
|
-
1. `Licensed::Sources::Source.
|
17
|
-
- Returns the name of the current dependency enumerator as it is found in a licensed configuration file.
|
16
|
+
1. `Licensed::Sources::Source.type_and_version`
|
17
|
+
- Returns the name, and optionally a version, of the current dependency enumerator as it is found in a licensed configuration file. See [the method description](../lib/licensed/sources/source.rb#L38-L41) for more details
|
18
|
+
|
19
|
+
### Implementing an enumerator for a new version of an existing source
|
20
|
+
|
21
|
+
If a package manager introduces breaking changes, it can be easier to build a new implementation rather than making a single class work for all cases. To enable seamless migration between source versions, the implementation for each version of the source enumerator should return the same `.type` and determine whether the version implementation should run in `#enabled?`.
|
22
|
+
|
23
|
+
The sections below describe what was done when adding a new version for the `yarn` source. Following these steps will make sure that the new version implementation follows the expected patterns for local development and test scenarios.
|
24
|
+
|
25
|
+
#### Migrating the file structure for a single source enumerator to enable multiple source enumerator versions
|
26
|
+
|
27
|
+
The following steps will migrate the source to the pattern expected for multi-version source enumerators.
|
28
|
+
|
29
|
+
The enumerators source code file is likely named to closely match the source enumerator, e.g. `lib/licensed/sources/yarn.rb`
|
30
|
+
|
31
|
+
1. Create a new directory matching the name of the source and move the existing enumerator into the new folder with a version descriptive name, e.g. `lib/licensed/sources/yarn/v1.rb`
|
32
|
+
1. Update the source enumerator class name to include a version identifier, e.g. `Licensed::Sources::Yarn::V1`
|
33
|
+
1. Make similar changes for the source's [unit test fixtures](../test/fixtures), [unit test file](../test/sources) and [setup script](../scripts/source-setup), moving these files into subfolders and renaming the files to match the change in (1)
|
34
|
+
- Also be sure to update any references to old paths or class names
|
35
|
+
1. If needed, update the source's `#type_and_version` to include a version value as a second array value
|
36
|
+
- If this isn't already set, the default implementation will return the type and version as the last two part names of the class name, snake cased and with a `/` delimeter, e.g. `yarn/v1`
|
37
|
+
1. Update the source's `#enabled?` method, adding a version check to ensure that the source only runs in the expected scenario
|
38
|
+
1. Add a new generic source file in `lib/licensed/sources` that `require`s the new file, e.g. `lib/licensed/sources/yarn.rb`
|
39
|
+
- This is also an ideal spot to put shared code in a module that can be included in one or more versions of the source enumerator
|
40
|
+
1. Update any references to the source in scripting and GitHub Actions automation to use the new versioned identifier, e.g. `yarn/v1` instead of the unversioned identifier.
|
41
|
+
|
42
|
+
#### Adding a new implementation for the new version of the source
|
43
|
+
|
44
|
+
1. Add the new implementation to the source's `lib/licensed/sources` subfolder.
|
45
|
+
1. If there is shared code that can be reused between multiple source enumerator versions, put it in a module in the source's base file, e.g. `lib/licensed/sources/yarn.rb`. Include the module in the version implementations.
|
46
|
+
1. Ensure that the new version implementation checks for the expected source enumerator version in `#enabled?`
|
18
47
|
|
19
48
|
## Determining if dependencies should be enumerated
|
20
49
|
|
@@ -24,7 +53,7 @@ whether `Licensed::Source::Sources#enumerate_dependencies` should be called on t
|
|
24
53
|
Determining whether dependencies should be enumerated depends on whether all the tools or files needed to find dependencies are present.
|
25
54
|
For example, to enumerate `npm` dependencies the `npm` CLI tool must be found with `Licensed::Shell.tool_available?` and a `package.json` file needs to exist in the licensed app's configured [`source_path`](./configuration.md#configuration-paths).
|
26
55
|
|
27
|
-
### Gating functionality when required tools are not available
|
56
|
+
### Gating functionality when required tools are not available
|
28
57
|
|
29
58
|
When adding new dependency sources, ensure that `script/bootstrap` scripting and tests are only run if the required tooling is available on the development machine.
|
30
59
|
|
data/docs/commands/README.md
CHANGED
@@ -8,7 +8,7 @@ Run `licensed -h` to see help content for running licensed commands.
|
|
8
8
|
- [migrate](migrate.md)
|
9
9
|
- [notices](notices.md)
|
10
10
|
- [status](status.md)
|
11
|
-
- [version](
|
11
|
+
- [version](version.md)
|
12
12
|
|
13
13
|
Most commands accept a `-c`/`--config` option to specify a path to a configuration file or directory. If a directory is specified, `licensed` will look in that directory for a file named (in order of preference):
|
14
14
|
|
data/docs/sources/yarn.md
CHANGED
@@ -2,13 +2,14 @@
|
|
2
2
|
|
3
3
|
The yarn source will detect dependencies when `package.json` and `yarn.lock` are found at an app's `source_path`.
|
4
4
|
|
5
|
-
It uses `yarn
|
5
|
+
It uses the `yarn` CLI commands to enumerate dependencies and gather metadata on each package.
|
6
6
|
|
7
|
-
|
7
|
+
## Including development dependencies
|
8
8
|
|
9
|
-
Yarn versions < 1.3.0 will always include non-production dependencies due to a bug in those yarn
|
9
|
+
**Note** Yarn versions < 1.3.0 will always include non-production dependencies due to a bug in those versions of yarn.
|
10
|
+
**Note** Yarn versions > 2.0 will always include non-production dependencies due to lack of filtering of production vs non-production dependencies in the yarn CLI.
|
10
11
|
|
11
|
-
|
12
|
+
For yarn versions between 1.3.0 and 2.0, the yarn source excludes non-production dependencies by default. To include development and test dependencies in these versions, set `production_only: false` in `.licensed.yml`.
|
12
13
|
|
13
14
|
```yml
|
14
15
|
yarn:
|
@@ -49,7 +49,7 @@ module Licensed
|
|
49
49
|
errored_reports = all_reports.select { |r| r.errors.any? }.to_a
|
50
50
|
|
51
51
|
dependency_count = all_reports.count { |r| r.target.is_a?(Licensed::Dependency) }
|
52
|
-
error_count = errored_reports.
|
52
|
+
error_count = errored_reports.reduce(0) { |count, r| count + r.errors.size }
|
53
53
|
|
54
54
|
if error_count > 0
|
55
55
|
shell.newline
|
data/lib/licensed/sources/npm.rb
CHANGED
@@ -7,8 +7,8 @@ module Licensed
|
|
7
7
|
# Only supports ProjectReference (project.assets.json) style restore used in .NET Core.
|
8
8
|
# Does not currently support packages.config style restore.
|
9
9
|
class NuGet < Source
|
10
|
-
def self.
|
11
|
-
"nuget"
|
10
|
+
def self.type_and_version
|
11
|
+
["nuget"]
|
12
12
|
end
|
13
13
|
|
14
14
|
class NuGetDependency < Licensed::Dependency
|
@@ -19,13 +19,32 @@ module Licensed
|
|
19
19
|
(@sources ||= []) << klass
|
20
20
|
end
|
21
21
|
|
22
|
-
# Returns the source name as the snake cased class name
|
22
|
+
# Returns the source name as the first snake cased class or module name
|
23
|
+
# following "Licensed::Sources::". This is the type that is included
|
24
|
+
# in metadata files and cache paths.
|
25
|
+
# e.g. for `Licensed::Sources::Yarn::V1`, this returns "yarn"
|
23
26
|
def type
|
24
|
-
|
25
|
-
|
27
|
+
type_and_version[0]
|
28
|
+
end
|
29
|
+
|
30
|
+
# Returns the source name as a "/" delimited string of all the module and
|
31
|
+
# class names following "Licensed::Sources::". This is the type that is
|
32
|
+
# used to distinguish multiple versions of a sources from each other.
|
33
|
+
# e.g. for `Licensed::Sources::Yarn::V1`, this returns `yarn/v1`
|
34
|
+
def full_type
|
35
|
+
type_and_version.join("/")
|
36
|
+
end
|
37
|
+
|
38
|
+
# Returns an array that includes the source's type name at the first index, and
|
39
|
+
# optionally a version string for the source as the second index.
|
40
|
+
# Callers should override this function and not `type` or `full_type` when
|
41
|
+
# needing to adjust the default type and version parsing logic
|
42
|
+
def type_and_version
|
43
|
+
self.name.gsub("#{Licensed::Sources.name}::", "")
|
26
44
|
.gsub(/([A-Z\d]+)([A-Z][a-z])/, "\\1_\\2".freeze)
|
27
45
|
.gsub(/([a-z\d])([A-Z])/, "\\1_\\2".freeze)
|
28
46
|
.downcase
|
47
|
+
.split("::")
|
29
48
|
end
|
30
49
|
end
|
31
50
|
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require "json"
|
3
|
+
|
4
|
+
module Licensed
|
5
|
+
module Sources
|
6
|
+
class Yarn::Berry < Source
|
7
|
+
include Licensed::Sources::Yarn
|
8
|
+
|
9
|
+
def self.version_requirement
|
10
|
+
Gem::Requirement.new(">= 2.0")
|
11
|
+
end
|
12
|
+
|
13
|
+
def enumerate_dependencies
|
14
|
+
packages.map do |name, package|
|
15
|
+
Dependency.new(
|
16
|
+
name: name,
|
17
|
+
version: package["version"],
|
18
|
+
path: package["path"],
|
19
|
+
metadata: {
|
20
|
+
"type" => self.class.type,
|
21
|
+
"name" => package["name"],
|
22
|
+
"homepage" => package["homepage"]
|
23
|
+
}
|
24
|
+
)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Finds packages that the current project relies on based on the output from `yarn info`
|
29
|
+
def packages
|
30
|
+
# parse all lines of output to json and find one that is "type": "tree"
|
31
|
+
yarn_info = JSON.parse("[#{yarn_info_command.lines.join(",")}]")
|
32
|
+
mapped_packages = yarn_info.reduce({}) do |accum, package|
|
33
|
+
name, _ = package["value"].rpartition("@")
|
34
|
+
version = package.dig("children", "Version")
|
35
|
+
id = "#{name}-#{version}"
|
36
|
+
|
37
|
+
accum[name] ||= []
|
38
|
+
accum[name] << {
|
39
|
+
"id" => id,
|
40
|
+
"name" => name,
|
41
|
+
"version" => version,
|
42
|
+
"homepage" => package.dig("children", "Manifest", "Homepage"),
|
43
|
+
"path" => dependency_paths[id]
|
44
|
+
}
|
45
|
+
accum
|
46
|
+
end
|
47
|
+
|
48
|
+
mapped_packages.each_with_object({}) do |(name, results), hsh|
|
49
|
+
results.uniq! { |package| package["version"] }
|
50
|
+
if results.size == 1
|
51
|
+
# if there is only one package for a name, reference it by name
|
52
|
+
hsh[name] = results[0]
|
53
|
+
else
|
54
|
+
# if there is more than one package for a name, reference each by id
|
55
|
+
results.each do |package|
|
56
|
+
hsh[package["id"]] = package
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# Returns a hash that maps all dependency names to their location on disk
|
63
|
+
# by parsing every package.json file under node_modules.
|
64
|
+
def dependency_paths
|
65
|
+
@dependency_paths ||= Dir.glob(config.pwd.join("node_modules/**/package.json")).each_with_object({}) do |file, hsh|
|
66
|
+
dirname = File.dirname(file)
|
67
|
+
json = JSON.parse(File.read(file))
|
68
|
+
hsh["#{json["name"]}-#{json["version"]}"] = dirname
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# Returns the output from running `yarn list` to get project dependencies
|
73
|
+
def yarn_info_command
|
74
|
+
args = %w(--json --manifest --recursive --all)
|
75
|
+
Licensed::Shell.execute("yarn", "info", *args)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,146 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require "json"
|
3
|
+
|
4
|
+
module Licensed
|
5
|
+
module Sources
|
6
|
+
class Yarn::V1 < Source
|
7
|
+
include Licensed::Sources::Yarn
|
8
|
+
|
9
|
+
# `yarn licenses list --json` returns data in a table format with header
|
10
|
+
# ordering specified in the output. Look for these specific headers and use
|
11
|
+
# their indices to get data from the table body
|
12
|
+
YARN_NAME_HEAD = "Name".freeze
|
13
|
+
YARN_VERSION_HEAD = "Version".freeze
|
14
|
+
YARN_URL_HEAD = "URL".freeze
|
15
|
+
|
16
|
+
def self.version_requirement
|
17
|
+
Gem::Requirement.new("< 2.0")
|
18
|
+
end
|
19
|
+
|
20
|
+
def enumerate_dependencies
|
21
|
+
packages.map do |name, package|
|
22
|
+
Dependency.new(
|
23
|
+
name: name,
|
24
|
+
version: package["version"],
|
25
|
+
path: package["path"],
|
26
|
+
metadata: {
|
27
|
+
"type" => self.class.type,
|
28
|
+
"name" => package["name"],
|
29
|
+
"homepage" => dependency_urls[package["id"]]
|
30
|
+
}
|
31
|
+
)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Finds packages that the current project relies on
|
36
|
+
def packages
|
37
|
+
return [] if yarn_package_tree.nil?
|
38
|
+
all_dependencies = {}
|
39
|
+
recursive_dependencies(yarn_package_tree).each do |name, results|
|
40
|
+
results.uniq! { |package| package["version"] }
|
41
|
+
if results.size == 1
|
42
|
+
# if there is only one package for a name, reference it by name
|
43
|
+
all_dependencies[name] = results[0]
|
44
|
+
else
|
45
|
+
# if there is more than one package for a name, reference each by
|
46
|
+
# "<name>-<version>"
|
47
|
+
results.each do |package|
|
48
|
+
all_dependencies["#{name}-#{package["version"]}"] = package
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
all_dependencies
|
54
|
+
end
|
55
|
+
|
56
|
+
# Recursively parse dependency JSON data. Returns a hash mapping the
|
57
|
+
# package name to it's metadata
|
58
|
+
def recursive_dependencies(dependencies, result = {})
|
59
|
+
dependencies.each do |dependency|
|
60
|
+
# "shadow" indicate a dependency requirement only, not a
|
61
|
+
# resolved package identifier
|
62
|
+
next if dependency["shadow"]
|
63
|
+
name, _, version = dependency["name"].rpartition("@")
|
64
|
+
|
65
|
+
(result[name] ||= []) << {
|
66
|
+
"id" => dependency["name"],
|
67
|
+
"name" => name,
|
68
|
+
"version" => version,
|
69
|
+
"path" => dependency_paths[dependency["name"]]
|
70
|
+
}
|
71
|
+
recursive_dependencies(dependency["children"], result)
|
72
|
+
end
|
73
|
+
result
|
74
|
+
end
|
75
|
+
|
76
|
+
# Returns a hash that maps all dependency names to their location on disk
|
77
|
+
# by parsing every package.json file under node_modules.
|
78
|
+
def dependency_paths
|
79
|
+
@dependency_paths ||= Dir.glob(config.pwd.join("node_modules/**/package.json")).each_with_object({}) do |file, hsh|
|
80
|
+
dirname = File.dirname(file)
|
81
|
+
json = JSON.parse(File.read(file))
|
82
|
+
hsh["#{json["name"]}@#{json["version"]}"] = dirname
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# Finds and returns the yarn package tree listing from `yarn list` output
|
87
|
+
def yarn_package_tree
|
88
|
+
return @yarn_package_tree if defined?(@yarn_package_tree)
|
89
|
+
@yarn_package_tree = begin
|
90
|
+
# parse all lines of output to json and find one that is "type": "tree"
|
91
|
+
tree = yarn_list_command.lines
|
92
|
+
.map(&:strip)
|
93
|
+
.map(&JSON.method(:parse))
|
94
|
+
.find { |json| json["type"] == "tree" }
|
95
|
+
tree&.dig("data", "trees")
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# Returns a mapping of unique dependency identifiers to urls
|
100
|
+
def dependency_urls
|
101
|
+
@dependency_urls ||= begin
|
102
|
+
table = yarn_licenses_command.lines
|
103
|
+
.map(&:strip)
|
104
|
+
.map(&JSON.method(:parse))
|
105
|
+
.find { |json| json["type"] == "table" }
|
106
|
+
return {} if table.nil?
|
107
|
+
|
108
|
+
head = table.dig("data", "head")
|
109
|
+
return {} if head.nil?
|
110
|
+
|
111
|
+
name_index = head.index YARN_NAME_HEAD
|
112
|
+
version_index = head.index YARN_VERSION_HEAD
|
113
|
+
url_index = head.index YARN_URL_HEAD
|
114
|
+
return {} if name_index.nil? || version_index.nil? || url_index.nil?
|
115
|
+
|
116
|
+
body = table.dig("data", "body")
|
117
|
+
return {} if body.nil?
|
118
|
+
|
119
|
+
body.each_with_object({}) do |row, hsh|
|
120
|
+
id = "#{row[name_index]}@#{row[version_index]}"
|
121
|
+
hsh[id] = row[url_index]
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
# Returns the output from running `yarn list` to get project dependencies
|
127
|
+
def yarn_list_command
|
128
|
+
args = %w(--json -s --no-progress)
|
129
|
+
args << "--production" unless include_non_production?
|
130
|
+
Licensed::Shell.execute("yarn", "list", *args, allow_failure: true)
|
131
|
+
end
|
132
|
+
|
133
|
+
# Returns the output from running `yarn licenses list` to get project urls
|
134
|
+
def yarn_licenses_command
|
135
|
+
args = %w(--json -s --no-progress)
|
136
|
+
args << "--production" unless include_non_production?
|
137
|
+
Licensed::Shell.execute("yarn", "licenses", "list", *args, allow_failure: true)
|
138
|
+
end
|
139
|
+
|
140
|
+
# Returns whether to include non production dependencies based on the licensed configuration settings
|
141
|
+
def include_non_production?
|
142
|
+
config.dig("yarn", "production_only") == false
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
@@ -1,146 +1,31 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
require "json"
|
3
2
|
|
4
3
|
module Licensed
|
5
4
|
module Sources
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
YARN_NAME_HEAD = "Name".freeze
|
11
|
-
YARN_VERSION_HEAD = "Version".freeze
|
12
|
-
YARN_URL_HEAD = "URL".freeze
|
13
|
-
|
14
|
-
def enabled?
|
15
|
-
return unless Licensed::Shell.tool_available?("yarn")
|
16
|
-
|
17
|
-
config.pwd.join("package.json").exist? && config.pwd.join("yarn.lock").exist?
|
18
|
-
end
|
19
|
-
|
20
|
-
def enumerate_dependencies
|
21
|
-
packages.map do |name, package|
|
22
|
-
Dependency.new(
|
23
|
-
name: name,
|
24
|
-
version: package["version"],
|
25
|
-
path: package["path"],
|
26
|
-
metadata: {
|
27
|
-
"type" => Yarn.type,
|
28
|
-
"name" => package["name"],
|
29
|
-
"homepage" => dependency_urls[package["id"]]
|
30
|
-
}
|
31
|
-
)
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
# Finds packages that the current project relies on
|
36
|
-
def packages
|
37
|
-
return [] if yarn_package_tree.nil?
|
38
|
-
all_dependencies = {}
|
39
|
-
recursive_dependencies(yarn_package_tree).each do |name, results|
|
40
|
-
results.uniq! { |package| package["version"] }
|
41
|
-
if results.size == 1
|
42
|
-
# if there is only one package for a name, reference it by name
|
43
|
-
all_dependencies[name] = results[0]
|
44
|
-
else
|
45
|
-
# if there is more than one package for a name, reference each by
|
46
|
-
# "<name>-<version>"
|
47
|
-
results.each do |package|
|
48
|
-
all_dependencies["#{name}-#{package["version"]}"] = package
|
49
|
-
end
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
all_dependencies
|
54
|
-
end
|
55
|
-
|
56
|
-
# Recursively parse dependency JSON data. Returns a hash mapping the
|
57
|
-
# package name to it's metadata
|
58
|
-
def recursive_dependencies(dependencies, result = {})
|
59
|
-
dependencies.each do |dependency|
|
60
|
-
# "shadow" indicate a dependency requirement only, not a
|
61
|
-
# resolved package identifier
|
62
|
-
next if dependency["shadow"]
|
63
|
-
name, _, version = dependency["name"].rpartition("@")
|
64
|
-
|
65
|
-
(result[name] ||= []) << {
|
66
|
-
"id" => dependency["name"],
|
67
|
-
"name" => name,
|
68
|
-
"version" => version,
|
69
|
-
"path" => dependency_paths[dependency["name"]]
|
70
|
-
}
|
71
|
-
recursive_dependencies(dependency["children"], result)
|
5
|
+
module Yarn
|
6
|
+
module ClassMethods
|
7
|
+
def type
|
8
|
+
"yarn"
|
72
9
|
end
|
73
|
-
result
|
74
10
|
end
|
75
11
|
|
76
|
-
|
77
|
-
|
78
|
-
def dependency_paths
|
79
|
-
@dependency_paths ||= Dir.glob(config.pwd.join("node_modules/**/package.json")).each_with_object({}) do |file, hsh|
|
80
|
-
dirname = File.dirname(file)
|
81
|
-
json = JSON.parse(File.read(file))
|
82
|
-
hsh["#{json["name"]}@#{json["version"]}"] = dirname
|
83
|
-
end
|
12
|
+
def self.included(klass)
|
13
|
+
klass.extend ClassMethods
|
84
14
|
end
|
85
15
|
|
86
|
-
|
87
|
-
|
88
|
-
return
|
89
|
-
@yarn_package_tree = begin
|
90
|
-
# parse all lines of output to json and find one that is "type": "tree"
|
91
|
-
tree = yarn_list_command.lines
|
92
|
-
.map(&:strip)
|
93
|
-
.map(&JSON.method(:parse))
|
94
|
-
.find { |json| json["type"] == "tree" }
|
95
|
-
tree&.dig("data", "trees")
|
96
|
-
end
|
97
|
-
end
|
98
|
-
|
99
|
-
# Returns a mapping of unique dependency identifiers to urls
|
100
|
-
def dependency_urls
|
101
|
-
@dependency_urls ||= begin
|
102
|
-
table = yarn_licenses_command.lines
|
103
|
-
.map(&:strip)
|
104
|
-
.map(&JSON.method(:parse))
|
105
|
-
.find { |json| json["type"] == "table" }
|
106
|
-
return [] if table.nil?
|
107
|
-
|
108
|
-
head = table.dig("data", "head")
|
109
|
-
return [] if head.nil?
|
110
|
-
|
111
|
-
name_index = head.index YARN_NAME_HEAD
|
112
|
-
version_index = head.index YARN_VERSION_HEAD
|
113
|
-
url_index = head.index YARN_URL_HEAD
|
114
|
-
return [] if name_index.nil? || version_index.nil? || url_index.nil?
|
115
|
-
|
116
|
-
body = table.dig("data", "body")
|
117
|
-
return [] if body.nil?
|
118
|
-
|
119
|
-
body.each_with_object({}) do |row, hsh|
|
120
|
-
id = "#{row[name_index]}@#{row[version_index]}"
|
121
|
-
hsh[id] = row[url_index]
|
122
|
-
end
|
123
|
-
end
|
124
|
-
end
|
125
|
-
|
126
|
-
# Returns the output from running `yarn list` to get project dependencies
|
127
|
-
def yarn_list_command
|
128
|
-
args = %w(--json -s --no-progress)
|
129
|
-
args << "--production" unless include_non_production?
|
130
|
-
Licensed::Shell.execute("yarn", "list", *args, allow_failure: true)
|
131
|
-
end
|
16
|
+
def enabled?
|
17
|
+
return unless Licensed::Shell.tool_available?("yarn")
|
18
|
+
return unless self.class.version_requirement.satisfied_by?(yarn_version)
|
132
19
|
|
133
|
-
|
134
|
-
def yarn_licenses_command
|
135
|
-
args = %w(--json -s --no-progress)
|
136
|
-
args << "--production" unless include_non_production?
|
137
|
-
Licensed::Shell.execute("yarn", "licenses", "list", *args, allow_failure: true)
|
20
|
+
config.pwd.join("package.json").exist? && config.pwd.join("yarn.lock").exist?
|
138
21
|
end
|
139
22
|
|
140
|
-
|
141
|
-
|
142
|
-
config.dig("yarn", "production_only") == false
|
23
|
+
def yarn_version
|
24
|
+
Gem::Version.new(Licensed::Shell.execute("yarn", "-v"))
|
143
25
|
end
|
144
26
|
end
|
145
27
|
end
|
146
28
|
end
|
29
|
+
|
30
|
+
require "licensed/sources/yarn/v1"
|
31
|
+
require "licensed/sources/yarn/berry"
|
data/lib/licensed/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: licensed
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.
|
4
|
+
version: 3.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- GitHub
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-12-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: licensee
|
@@ -335,6 +335,8 @@ files:
|
|
335
335
|
- lib/licensed/sources/source.rb
|
336
336
|
- lib/licensed/sources/swift.rb
|
337
337
|
- lib/licensed/sources/yarn.rb
|
338
|
+
- lib/licensed/sources/yarn/berry.rb
|
339
|
+
- lib/licensed/sources/yarn/v1.rb
|
338
340
|
- lib/licensed/ui/shell.rb
|
339
341
|
- lib/licensed/version.rb
|
340
342
|
- licensed.gemspec
|