licensed 3.3.0 → 3.4.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +47 -1
- data/Rakefile +6 -3
- data/docs/adding_a_new_source.md +32 -3
- data/docs/commands/README.md +1 -1
- data/docs/commands/status.md +14 -10
- data/docs/sources/yarn.md +5 -4
- data/lib/licensed/reporters/status_reporter.rb +1 -1
- data/lib/licensed/sources/manifest.rb +17 -22
- data/lib/licensed/sources/npm.rb +17 -7
- data/lib/licensed/sources/nuget.rb +2 -2
- data/lib/licensed/sources/source.rb +22 -3
- data/lib/licensed/sources/yarn/berry.rb +69 -0
- data/lib/licensed/sources/yarn/v1.rb +136 -0
- data/lib/licensed/sources/yarn.rb +28 -124
- 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: 3d7cec159ef0a5af9df07ac13ba8f540897d1039436d39d361ad2948f305f857
|
4
|
+
data.tar.gz: 1e7b7b50ee7715c41e0b5774104039e471be2d749645a38265d3930d51cd81ab
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5c32f95d211dece04fea6c8dff48525593a8348d36dea980f0815159922b5b813270d0ac8b4f6425a9cbcf9437cbf145693f18411b733c917f56ef1b495cca77
|
7
|
+
data.tar.gz: '095b85ceea926a975b18b8001bebca68343dba8550ed2533ccc7eb3860424f707b0756cc121e8c6ad2fc7715c734232f43175513e5005a5c6f535551c3831f6f'
|
data/CHANGELOG.md
CHANGED
@@ -6,6 +6,52 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
|
6
6
|
|
7
7
|
## [Unreleased]
|
8
8
|
|
9
|
+
## 3.4.2
|
10
|
+
|
11
|
+
2022-01-17
|
12
|
+
|
13
|
+
### Fixed
|
14
|
+
|
15
|
+
- The yarn source will no longer evaluate package.json files that do not represent project dependencies (https://github.com/github/licensed/pull/439)
|
16
|
+
|
17
|
+
## 3.4.1
|
18
|
+
|
19
|
+
2022-01-07
|
20
|
+
|
21
|
+
### Fixed
|
22
|
+
|
23
|
+
- Malformed package.json files will no longer crash yarn dependency detection (https://github.com/github/licensed/pull/431)
|
24
|
+
|
25
|
+
## 3.4.0
|
26
|
+
|
27
|
+
2021-12-14
|
28
|
+
|
29
|
+
### Added
|
30
|
+
|
31
|
+
- New Yarn enumerator with support for berry versions (https://github.com/github/licensed/pull/423)
|
32
|
+
|
33
|
+
### Fixed
|
34
|
+
|
35
|
+
- Error handling cases return correct values in the Yarn enumerator (https://github.com/github/licensed/pull/425)
|
36
|
+
- Fixed link in command documentation (:tada: @chibicco https://github.com/github/licensed/pull/416)
|
37
|
+
- Fixed minor backwards compatibility issue for Ruby 2.3 support (:tada: @dzunk https://github.com/github/licensed/pull/414)
|
38
|
+
|
39
|
+
### Changed
|
40
|
+
|
41
|
+
- Licensed's own dependencies are cached in the repository and kept up to date with GitHub Actions (https://github.com/github/licensed/pull/421)
|
42
|
+
|
43
|
+
## 3.3.1
|
44
|
+
|
45
|
+
2021-10-07
|
46
|
+
|
47
|
+
### Fixed
|
48
|
+
|
49
|
+
- Fix evaluation of peer dependencies with npm 7 (:tada: @manuelpuyol https://github.com/github/licensed/pull/411)
|
50
|
+
|
51
|
+
### Changed
|
52
|
+
|
53
|
+
- Manifest source evaluation performance improvements (https://github.com/github/licensed/pull/407)
|
54
|
+
|
9
55
|
## 3.3.0
|
10
56
|
|
11
57
|
2021-09-18
|
@@ -509,4 +555,4 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
|
509
555
|
|
510
556
|
Initial release :tada:
|
511
557
|
|
512
|
-
[Unreleased]: https://github.com/github/licensed/compare/3.
|
558
|
+
[Unreleased]: https://github.com/github/licensed/compare/3.4.2...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|
|
@@ -60,6 +62,7 @@ namespace :test do
|
|
60
62
|
t.libs << "lib"
|
61
63
|
t.test_files = FileList["test/**/*_test.rb"].exclude("test/fixtures/**/*_test.rb")
|
62
64
|
.exclude("test/sources/*_test.rb")
|
65
|
+
.exclude("test/sources/**/*_test.rb")
|
63
66
|
end
|
64
67
|
end
|
65
68
|
|
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/commands/status.md
CHANGED
@@ -39,30 +39,34 @@ The following data is reported for each dependency when the YAML or JSON report
|
|
39
39
|
|
40
40
|
### cached dependency record not found
|
41
41
|
|
42
|
-
|
43
|
-
|
42
|
+
*Cause:* A dependency was found while running `licensed status` that does not have a corresponding cached metadata file
|
43
|
+
|
44
|
+
*Resolution:* Run `licensed cache` to update the metadata cache and create the missing metadata file
|
44
45
|
|
45
46
|
### cached dependency record out of date
|
46
47
|
|
47
|
-
|
48
|
-
|
48
|
+
*Cause:* A dependency was found while running `licensed status` with a different version than is contained in the dependency's cached metadata file
|
49
|
+
|
50
|
+
*Resolution:* Run `licensed cache` to update the out-of-date metadata files
|
49
51
|
|
50
52
|
### missing license text
|
51
53
|
|
52
|
-
|
53
|
-
|
54
|
+
*Cause:* A license determination was made, e.g. from package metadata, but no license text was found.
|
55
|
+
|
56
|
+
*Resolution:* Manually verify whether the dependency includes a file containing license text. If the dependency code that was downloaded locally does not contain the license text, please check the dependency source at the version listed in the dependency's cached metadata file to see if there is license text that can be used.
|
54
57
|
|
55
58
|
If the dependency does not include license text but does specify that it uses a specific license, please copy the standard license text from a [well known source](https://opensource.org/licenses).
|
56
59
|
|
57
60
|
### license text has changed and needs re-review. if the new text is ok, remove the `review_changed_license` flag from the cached record
|
58
61
|
|
59
|
-
|
60
|
-
|
62
|
+
*Cause:* A dependency that is set as [reviewed] in the licensed configuration file has substantially changed and should be re-reviewed.
|
63
|
+
|
64
|
+
*Resolution:* Review the changes to the license text and classification, along with other metadata contained in the cached file for the dependency. If the dependency is still allowable for use in your project, remove the `review_changed_license` key from the cached record file.
|
61
65
|
|
62
66
|
### license needs review
|
63
67
|
|
64
|
-
|
65
|
-
|
68
|
+
*Cause:* A dependency is using a license that is not in the configured [allowed list of licenses][allowed], and the dependency has not been marked [ignored] or [reviewed].
|
69
|
+
*Resolution:* Review the dependency's usage and specified license with someone familiar with OSS licensing and compliance rules to determine whether the dependency is allowable. Some common resolutions:
|
66
70
|
|
67
71
|
1. The dependency's specified license text differed enough from the standard license text that it was not recognized and classified as `other`. If, with human review, the license text is recognizable then update the `license: other` value in the cached metadata file to the correct license.
|
68
72
|
- An updated classification will persist through version upgrades until the detected license contents have changed. The determination is made by [licensee/licensee](https://github.com/licensee/licensee), the library which this tool uses to detect and classify license contents.
|
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
|
@@ -61,7 +61,7 @@ module Licensed
|
|
61
61
|
manifest.each_with_object({}) do |(src, package_name), hsh|
|
62
62
|
next if src.nil? || src.empty?
|
63
63
|
hsh[package_name] ||= []
|
64
|
-
hsh[package_name] << File.
|
64
|
+
hsh[package_name] << File.absolute_path(src, config.root)
|
65
65
|
end
|
66
66
|
end
|
67
67
|
|
@@ -130,19 +130,17 @@ module Licensed
|
|
130
130
|
@configured_dependencies ||= begin
|
131
131
|
dependencies = config.dig("manifest", "dependencies")&.dup || {}
|
132
132
|
|
133
|
-
dependencies.
|
133
|
+
dependencies.each_with_object({}) do |(name, patterns), hsh|
|
134
134
|
# map glob pattern(s) listed for the dependency to a listing
|
135
135
|
# of files that match the patterns and are not excluded
|
136
|
-
|
136
|
+
hsh[name] = files_from_pattern_list(patterns) & included_files
|
137
137
|
end
|
138
|
-
|
139
|
-
dependencies
|
140
138
|
end
|
141
139
|
end
|
142
140
|
|
143
141
|
# Returns the set of project files that are included in dependency evaluation
|
144
142
|
def included_files
|
145
|
-
@
|
143
|
+
@included_files ||= tracked_files - files_from_pattern_list(config.dig("manifest", "exclude"))
|
146
144
|
end
|
147
145
|
|
148
146
|
# Finds and returns all files in the project that match
|
@@ -151,26 +149,23 @@ module Licensed
|
|
151
149
|
return Set.new if patterns.nil? || patterns.empty?
|
152
150
|
|
153
151
|
# evaluate all patterns from the project root
|
154
|
-
|
155
|
-
|
156
|
-
if pattern
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
files + Dir.glob(pattern, File::FNM_DOTMATCH)
|
164
|
-
end
|
152
|
+
Array(patterns).each_with_object(Set.new) do |pattern, files|
|
153
|
+
if pattern.start_with?("!")
|
154
|
+
# if the pattern is an exclusion, remove all matching files
|
155
|
+
# from the result
|
156
|
+
files.subtract(Dir.glob(pattern[1..-1], File::FNM_DOTMATCH, base: config.root))
|
157
|
+
else
|
158
|
+
# if the pattern is an inclusion, add all matching files
|
159
|
+
# to the result
|
160
|
+
files.merge(Dir.glob(pattern, File::FNM_DOTMATCH, base: config.root))
|
165
161
|
end
|
166
162
|
end
|
167
163
|
end
|
168
164
|
|
169
|
-
# Returns all tracked files in the project
|
170
|
-
def
|
171
|
-
|
172
|
-
|
173
|
-
.delete_if { |f| !File.exist?(File.join(Licensed::Git.repository_root, f)) }
|
165
|
+
# Returns all tracked files in the project as the intersection of what git tracks and the files in the project
|
166
|
+
def tracked_files
|
167
|
+
@tracked_files ||= Set.new(Array(Licensed::Git.files)) &
|
168
|
+
Set.new(Dir.glob("**/*", File::FNM_DOTMATCH, base: config.root))
|
174
169
|
end
|
175
170
|
|
176
171
|
class Dependency < Licensed::Dependency
|
data/lib/licensed/sources/npm.rb
CHANGED
@@ -23,10 +23,6 @@ module Licensed
|
|
23
23
|
end
|
24
24
|
end
|
25
25
|
|
26
|
-
def self.type
|
27
|
-
"npm"
|
28
|
-
end
|
29
|
-
|
30
26
|
def enabled?
|
31
27
|
Licensed::Shell.tool_available?("npm") && File.exist?(config.pwd.join("package.json"))
|
32
28
|
end
|
@@ -66,15 +62,17 @@ module Licensed
|
|
66
62
|
|
67
63
|
# Recursively parse dependency JSON data. Returns a hash mapping the
|
68
64
|
# package name to it's metadata
|
69
|
-
def recursive_dependencies(dependencies, result = {})
|
65
|
+
def recursive_dependencies(dependencies, result = {}, parent = nil)
|
70
66
|
dependencies.each do |name, dependency|
|
71
|
-
next if dependency
|
67
|
+
next if missing_peer?(parent, dependency, name)
|
72
68
|
next if yarn_lock_present && dependency["missing"]
|
73
69
|
next if dependency["extraneous"] && dependency["missing"]
|
74
70
|
|
75
71
|
dependency["name"] = name
|
72
|
+
dependency["version"] ||= extract_version(parent, name) if dependency["missing"]
|
73
|
+
|
76
74
|
(result[name] ||= []) << dependency
|
77
|
-
recursive_dependencies(dependency["dependencies"] || {}, result)
|
75
|
+
recursive_dependencies(dependency["dependencies"] || {}, result, dependency)
|
78
76
|
end
|
79
77
|
result
|
80
78
|
end
|
@@ -135,6 +133,18 @@ module Licensed
|
|
135
133
|
def include_non_production?
|
136
134
|
config.dig("npm", "production_only") == false
|
137
135
|
end
|
136
|
+
|
137
|
+
def missing_peer?(parent, dependency, name)
|
138
|
+
dependency["peerMissing"] || (dependency["missing"] && peer_dependency(parent, name))
|
139
|
+
end
|
140
|
+
|
141
|
+
def peer_dependency(parent, name)
|
142
|
+
parent&.dig("peerDependencies", name)
|
143
|
+
end
|
144
|
+
|
145
|
+
def extract_version(parent, name)
|
146
|
+
parent&.dig("_dependencies", name) || peer_dependency(parent, name)
|
147
|
+
end
|
138
148
|
end
|
139
149
|
end
|
140
150
|
end
|
@@ -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,69 @@
|
|
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 the output from running `yarn list` to get project dependencies
|
63
|
+
def yarn_info_command
|
64
|
+
args = %w(--json --manifest --recursive --all)
|
65
|
+
Licensed::Shell.execute("yarn", "info", *args)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,136 @@
|
|
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
|
+
# Finds and returns the yarn package tree listing from `yarn list` output
|
77
|
+
def yarn_package_tree
|
78
|
+
return @yarn_package_tree if defined?(@yarn_package_tree)
|
79
|
+
@yarn_package_tree = begin
|
80
|
+
# parse all lines of output to json and find one that is "type": "tree"
|
81
|
+
tree = yarn_list_command.lines
|
82
|
+
.map(&:strip)
|
83
|
+
.map(&JSON.method(:parse))
|
84
|
+
.find { |json| json["type"] == "tree" }
|
85
|
+
tree&.dig("data", "trees")
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# Returns a mapping of unique dependency identifiers to urls
|
90
|
+
def dependency_urls
|
91
|
+
@dependency_urls ||= begin
|
92
|
+
table = yarn_licenses_command.lines
|
93
|
+
.map(&:strip)
|
94
|
+
.map(&JSON.method(:parse))
|
95
|
+
.find { |json| json["type"] == "table" }
|
96
|
+
return {} if table.nil?
|
97
|
+
|
98
|
+
head = table.dig("data", "head")
|
99
|
+
return {} if head.nil?
|
100
|
+
|
101
|
+
name_index = head.index YARN_NAME_HEAD
|
102
|
+
version_index = head.index YARN_VERSION_HEAD
|
103
|
+
url_index = head.index YARN_URL_HEAD
|
104
|
+
return {} if name_index.nil? || version_index.nil? || url_index.nil?
|
105
|
+
|
106
|
+
body = table.dig("data", "body")
|
107
|
+
return {} if body.nil?
|
108
|
+
|
109
|
+
body.each_with_object({}) do |row, hsh|
|
110
|
+
id = "#{row[name_index]}@#{row[version_index]}"
|
111
|
+
hsh[id] = row[url_index]
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
# Returns the output from running `yarn list` to get project dependencies
|
117
|
+
def yarn_list_command
|
118
|
+
args = %w(--json -s --no-progress)
|
119
|
+
args << "--production" unless include_non_production?
|
120
|
+
Licensed::Shell.execute("yarn", "list", *args, allow_failure: true)
|
121
|
+
end
|
122
|
+
|
123
|
+
# Returns the output from running `yarn licenses list` to get project urls
|
124
|
+
def yarn_licenses_command
|
125
|
+
args = %w(--json -s --no-progress)
|
126
|
+
args << "--production" unless include_non_production?
|
127
|
+
Licensed::Shell.execute("yarn", "licenses", "list", *args, allow_failure: true)
|
128
|
+
end
|
129
|
+
|
130
|
+
# Returns whether to include non production dependencies based on the licensed configuration settings
|
131
|
+
def include_non_production?
|
132
|
+
config.dig("yarn", "production_only") == false
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
@@ -1,146 +1,50 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
require "json"
|
3
2
|
|
4
3
|
module Licensed
|
5
4
|
module Sources
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
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?
|
5
|
+
module Yarn
|
6
|
+
module ClassMethods
|
7
|
+
def type
|
8
|
+
"yarn"
|
9
|
+
end
|
18
10
|
end
|
19
11
|
|
20
|
-
def
|
21
|
-
|
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
|
12
|
+
def self.included(klass)
|
13
|
+
klass.extend ClassMethods
|
33
14
|
end
|
34
15
|
|
35
|
-
|
36
|
-
|
37
|
-
return
|
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
|
16
|
+
def enabled?
|
17
|
+
return unless Licensed::Shell.tool_available?("yarn")
|
18
|
+
return unless self.class.version_requirement.satisfied_by?(yarn_version)
|
52
19
|
|
53
|
-
|
20
|
+
config.pwd.join("package.json").exist? && config.pwd.join("yarn.lock").exist?
|
54
21
|
end
|
55
22
|
|
56
|
-
|
57
|
-
|
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
|
23
|
+
def yarn_version
|
24
|
+
Gem::Version.new(Licensed::Shell.execute("yarn", "-v"))
|
74
25
|
end
|
75
26
|
|
76
27
|
# Returns a hash that maps all dependency names to their location on disk
|
77
28
|
# by parsing every package.json file under node_modules.
|
78
29
|
def dependency_paths
|
79
|
-
@dependency_paths ||=
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
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]
|
30
|
+
@dependency_paths ||= [
|
31
|
+
*Dir.glob(config.pwd.join("**/node_modules/*/package.json")),
|
32
|
+
*Dir.glob(config.pwd.join("**/node_modules/@*/*/package.json"))
|
33
|
+
].each_with_object({}) do |file, hsh|
|
34
|
+
begin
|
35
|
+
dirname = File.dirname(file)
|
36
|
+
json = JSON.parse(File.read(file))
|
37
|
+
hsh["#{json["name"]}@#{json["version"]}"] = dirname
|
38
|
+
rescue JSON::ParserError
|
39
|
+
# don't crash execution if there is a problem parsing a package.json file
|
40
|
+
# if the bad package.json file relates to a package that licensed should be reporting on
|
41
|
+
# then this will still result in an error about a missing package
|
122
42
|
end
|
123
43
|
end
|
124
44
|
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
45
|
end
|
145
46
|
end
|
146
47
|
end
|
48
|
+
|
49
|
+
require "licensed/sources/yarn/v1"
|
50
|
+
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.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- GitHub
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-01-17 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
|