licensed 2.12.2 → 2.14.3

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: 42da8bbc3e526abe154311e85c5dcb1ffaeba7a631e3b6eab6dbb976a94d9c4d
4
- data.tar.gz: b0884fb5ae8a8c332c8ea8684075a57e3efe86c6026656279eb2ffe0ae50105a
3
+ metadata.gz: 33331f661a6431bafe0966bfc183f734c9775412a23a9d952b0c232482254899
4
+ data.tar.gz: d6fc25e79d8ca274eb3ca1dbf22bc187fa6a0ac1547b08777aafde2fd71612dd
5
5
  SHA512:
6
- metadata.gz: e5d14b32dffb12a412090348429a116f9732bb6413bc35fb677dbf248e8af415ca531d7980bafeaa5a81ed2f083e798821cd21be7fd8eed1037a6659f24d4693
7
- data.tar.gz: 411785733af02c33cc0b239980558f79389b8bfdc144bf862530fde111dc3ad0c8fd64040b9fdecba022ae93158287b0f45e26a10719567dda3022024a60728b
6
+ metadata.gz: 7572045fdc55e4c53fd5230baed547ff4d20aa7b5bcea226f7f57035eacdf335e2ec364e3df9ad9bce6222fe46fc134066979c6cbc8a42b283070bd0a6695f74
7
+ data.tar.gz: cc6ed29bffe75f17dd0c25d449afa83f3122caf442bcad6582cbc07a93c28389e5c4543ea9dea5e67f94797a24e223658cf7e4671f165f0502e385424a8402c2
@@ -1,18 +1,12 @@
1
- name: Create release
1
+ name: Build and publish release assets
2
2
 
3
- on: create
3
+ on:
4
+ release:
5
+ types: [created]
4
6
 
5
7
  jobs:
6
- tag_filter:
7
- runs-on: ubuntu-latest
8
- if: startsWith(github.ref, 'refs/tags/')
9
- steps:
10
- - run: exit 0
11
-
12
8
  package_linux:
13
9
  runs-on: ubuntu-latest
14
- needs: tag_filter
15
-
16
10
  steps:
17
11
  - uses: actions/checkout@v2
18
12
  - name: Set up Ruby 2.6
@@ -23,17 +17,15 @@ jobs:
23
17
  - name: Build package
24
18
  run: script/packages/linux
25
19
  env:
26
- VERSION: ${{github.event.ref}}
20
+ VERSION: ${{github.event.release.tag_name}}
27
21
 
28
22
  - uses: actions/upload-artifact@v2
29
23
  with:
30
- name: ${{github.event.ref}}-linux
31
- path: pkg/${{github.event.ref}}/licensed-${{github.event.ref}}-linux-x64.tar.gz
24
+ name: ${{github.event.release.tag_name}}-linux
25
+ path: pkg/${{github.event.release.tag_name}}/licensed-${{github.event.release.tag_name}}-linux-x64.tar.gz
32
26
 
33
27
  package_mac:
34
28
  runs-on: macOS-latest
35
- needs: tag_filter
36
-
37
29
  steps:
38
30
  - uses: actions/checkout@v2
39
31
  - name: Set up Ruby 2.6
@@ -44,17 +36,15 @@ jobs:
44
36
  - name: Build package
45
37
  run: script/packages/mac
46
38
  env:
47
- VERSION: ${{github.event.ref}}
39
+ VERSION: ${{github.event.release.tag_name}}
48
40
 
49
41
  - uses: actions/upload-artifact@v2
50
42
  with:
51
- name: ${{github.event.ref}}-darwin
52
- path: pkg/${{github.event.ref}}/licensed-${{github.event.ref}}-darwin-x64.tar.gz
43
+ name: ${{github.event.release.tag_name}}-darwin
44
+ path: pkg/${{github.event.release.tag_name}}/licensed-${{github.event.release.tag_name}}-darwin-x64.tar.gz
53
45
 
54
46
  build_gem:
55
47
  runs-on: ubuntu-latest
56
- needs: tag_filter
57
-
58
48
  steps:
59
49
  - uses: actions/checkout@v2
60
50
  - name: Set up Ruby 2.6
@@ -63,25 +53,16 @@ jobs:
63
53
  ruby-version: 2.6.x
64
54
 
65
55
  - name: Build gem
66
- run: gem build *.gemspec
56
+ run: gem build licensed.gemspec -o licensed-${{github.event.release.tag_name}}.gem
67
57
 
68
58
  - uses: actions/upload-artifact@v2
69
59
  with:
70
- name: ${{github.event.ref}}-gem
71
- path: licensed-${{github.event.ref}}.gem
72
-
73
- create_release:
74
- runs-on: ubuntu-latest
75
- needs: [package_linux, package_mac, build_gem]
76
- steps:
77
- - uses: Roang-zero1/github-create-release-action@v1.0.2
78
- env:
79
- GITHUB_TOKEN: ${{ secrets.API_AUTH_TOKEN }}
80
- VERSION_REGEX: "^[[:digit:]]+\\.[[:digit:]]+\\.[[:digit:]]+"
60
+ name: ${{github.event.release.tag_name}}-gem
61
+ path: licensed-${{github.event.release.tag_name}}.gem
81
62
 
82
63
  upload_packages:
83
64
  runs-on: ubuntu-latest
84
- needs: [create_release]
65
+ needs: [package_linux, package_mac, build_gem]
85
66
 
86
67
  steps:
87
68
  - name: Set up Ruby 2.6
@@ -92,32 +73,45 @@ jobs:
92
73
  - name: Download linux package
93
74
  uses: actions/download-artifact@v2
94
75
  with:
95
- name: ${{github.event.ref}}-linux
76
+ name: ${{github.event.release.tag_name}}-linux
96
77
 
97
78
  - name: Download macOS package
98
79
  uses: actions/download-artifact@v2
99
80
  with:
100
- name: ${{github.event.ref}}-darwin
81
+ name: ${{github.event.release.tag_name}}-darwin
101
82
 
102
83
  - name: Download gem
103
84
  uses: actions/download-artifact@v2
104
85
  with:
105
- name: ${{github.event.ref}}-gem
86
+ name: ${{github.event.release.tag_name}}-gem
106
87
 
107
- - name: Publish packages to GitHub Release
108
- uses: Roang-zero1/github-upload-release-artifacts-action@v2.0.0
88
+ - name: Publish linux package
89
+ uses: actions/upload-release-asset@v1
90
+ env:
91
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
109
92
  with:
110
- args: licensed-${{github.event.ref}}-linux-x64.tar.gz licensed-${{github.event.ref}}-darwin-x64.tar.gz
93
+ upload_url: ${{ github.event.release.upload_url }}
94
+ asset_path: ./licensed-${{github.event.release.tag_name}}-linux-x64.tar.gz
95
+ asset_name: licensed-${{github.event.release.tag_name}}-linux-x64.tar.gz
96
+ asset_content_type: application/gzip
97
+
98
+ - name: Publish mac package
99
+ uses: actions/upload-release-asset@v1
111
100
  env:
112
- GITHUB_TOKEN: ${{secrets.API_AUTH_TOKEN}}
101
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
102
+ with:
103
+ upload_url: ${{ github.event.release.upload_url }}
104
+ asset_path: ./licensed-${{github.event.release.tag_name}}-darwin-x64.tar.gz
105
+ asset_name: licensed-${{github.event.release.tag_name}}-darwin-x64.tar.gz
106
+ asset_content_type: application/gzip
113
107
 
114
108
  - name: Publish gem to RubyGems
115
109
  run: |
116
110
  mkdir -p $HOME/.gem
117
111
  touch $HOME/.gem/credentials
118
112
  chmod 0600 $HOME/.gem/credentials
119
- printf -- "---\n:rubygems_api_key: ${GEM_HOST_API_KEY}\n" > $HOME/.gem/credentials
113
+ printf -- "---\n:rubygems_api_key: ${RUBYGEMS_API_KEY}\n" > $HOME/.gem/credentials
120
114
  gem push $GEM
121
115
  env:
122
- GEM_HOST_API_KEY: ${{secrets.RUBYGEMS_AUTH_TOKEN}}
123
- GEM: licensed-${{github.event.ref}}.gem
116
+ RUBYGEMS_API_KEY: ${{secrets.RUBYGEMS_AUTH_TOKEN}}
117
+ GEM: licensed-${{github.event.release.tag_name}}.gem
@@ -116,7 +116,7 @@ jobs:
116
116
  runs-on: ubuntu-latest
117
117
  strategy:
118
118
  matrix:
119
- ruby: [ 2.4.x, 2.5.x, 2.6.x ]
119
+ ruby: [ 2.4.x, 2.5.x, 2.6.x, 2.7.x ]
120
120
  steps:
121
121
  - uses: actions/checkout@v2
122
122
  - name: Set up Ruby
@@ -165,7 +165,7 @@ jobs:
165
165
  runs-on: ubuntu-latest
166
166
  strategy:
167
167
  matrix:
168
- go: [ '1.7.x', '1.10.x', '1.11.x', '1.12.x', '1.13.x', '1.14.x' ]
168
+ go: [ '1.10.x', '1.11.x', '1.12.x', '1.13.x', '1.14.x', '1.15.x' ]
169
169
  steps:
170
170
  - uses: actions/checkout@v2
171
171
  - name: Setup go
@@ -6,6 +6,45 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## 2.14.3
10
+ 2020-12-11
11
+
12
+ ## Fixed
13
+ - Auto-generating license text for a known license will no longer raise an error if the found license has no text (:tada: @Eun https://github.com/github/licensed/pull/328)
14
+
15
+ ## 2.14.2
16
+ 2020-11-20
17
+
18
+ ## Fixed
19
+ - Yarn source correctly finds dependency paths on disk (https://github.com/github/licensed/pull/326)
20
+ - Go source better handles finding dependencies that have been vendored (https://github.com/github/licensed/pull/323)
21
+
22
+ ## 2.14.1
23
+ 2020-10-09
24
+
25
+ ### Fixed
26
+ - Shell command output is encoded to UTF8 (https://github.com/github/licensed/pull/319)
27
+
28
+ ## 2.14.0
29
+ 2020-10-04
30
+
31
+ ### Added
32
+ - `reviewed` dependencies can use glob pattern matching (https://github.com/github/licensed/pull/313)
33
+
34
+ ### Fixed
35
+ - Fix configuring source path globs that expand into a single directory (https://github.com/github/licensed/pull/312)
36
+
37
+ ## 2.13.0
38
+ 2020-09-23
39
+
40
+ ### Added
41
+ - `status` command results can be output in YAML and JSON formats (:tada: @julianvilas https://github.com/github/licensed/pull/303)
42
+
43
+ ### Fixed
44
+ - `licensed` no longer crashes when parsing invalid YAML from cached records (https://github.com/github/licensed/pull/306)
45
+ - NPM source will no longer crash when invalid JSON is returned from npm CLI calls (https://github.com/github/licensed/pull/300)
46
+ - Bundler source is fixed to work properly with `gems.rb` lockfiles (https://github.com/github/licensed/pull/299)
47
+
9
48
  ## 2.12.2
10
49
  2020-07-07
11
50
 
@@ -17,7 +56,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
17
56
  2020-06-30
18
57
 
19
58
  ### Fixed
20
- - `licensed` no longer exits an error code when using the `--sources` CLI argument(https://github.com/github/licensed/pull/290)
59
+ - `licensed` no longer exits an error code when using the `--sources` CLI argument (https://github.com/github/licensed/pull/290)
21
60
 
22
61
  ## 2.12.0
23
62
  2020-06-19
@@ -340,4 +379,4 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
340
379
 
341
380
  Initial release :tada:
342
381
 
343
- [Unreleased]: https://github.com/github/licensed/compare/2.12.2...HEAD
382
+ [Unreleased]: https://github.com/github/licensed/compare/2.14.3...HEAD
@@ -39,7 +39,7 @@ Pull requests that include a new dependency source must also
39
39
  ## Releasing
40
40
  If you are the current maintainer of this gem:
41
41
 
42
- 1. Create a branch for the release: git checkout -b cut-release-vxx.xx.xx
42
+ 1. Create a branch for the release: git checkout -b cut-release-xx.xx.xx
43
43
  2. Make sure your local dependencies are up to date: `script/bootstrap`
44
44
  3. Ensure that tests are green: `bundle exec rake test`
45
45
  4. Bump gem version in lib/licensed/version.rb.
@@ -51,15 +51,16 @@ If you are the current maintainer of this gem:
51
51
  2. Install the new gem locally
52
52
  3. Test behavior locally, branch deploy, whatever needs to happen
53
53
  9. Merge github/licensed PR
54
- 10. Tag and push: `git tag x.xx.xx; git push --tags`
54
+ 10. Create a new [github/licensed release](https://github.com/github/licensed/releases)
55
+ - Set the release name and tag to the release version - `x.xx.x`
56
+ - Set the release body to the changelog entries for the release
55
57
 
56
58
  The following steps will happen automatically from a GitHub Actions workflow
57
- after pushing a new tag. In case that fails, the following steps can be performed manually
59
+ after creating the release. In case that fails, the following steps can be performed manually
58
60
 
59
- 11. Push to rubygems.org -- `gem push licensed-x.xx.xx.gem`
61
+ 11. Push the gem from (7) to rubygems.org -- `gem push licensed-x.xx.xx.gem`
60
62
  12. Build packages for new tag: `VERSION=x.xx.xx bundle exec rake package`
61
- 13. Create release for new tag at github/licensed.
62
- 14. Add built packages to new release
63
+ 13. Upload packages from (12) to release from (10)
63
64
 
64
65
  ## Resources
65
66
 
@@ -23,7 +23,7 @@ If a root path is not specified, it will default to using the following, in orde
23
23
 
24
24
  The `source_path` property can use a glob path to share configuration properties across multiple application entrypoints.
25
25
 
26
- For example, there is a common pattern in go projects to include multiple executable entrypoints under folders in `cmd`. Using a glob pattern allows users to avoid manually configuring and maintaining multiple licensed application `source_path`s. Using a glob pattern will also ensure that any new entrypoints matching the pattern are automatically picked up by licensed commands as they are added.
26
+ For example, there is a common pattern in Go projects to include multiple executable entrypoints under folders in `cmd`. Using a glob pattern allows users to avoid manually configuring and maintaining multiple licensed application `source_path`s. Using a glob pattern will also ensure that any new entrypoints matching the pattern are automatically picked up by licensed commands as they are added.
27
27
 
28
28
  ```yml
29
29
  sources:
@@ -118,12 +118,6 @@ ignored:
118
118
  bower:
119
119
  - some-internal-package
120
120
 
121
- go:
122
- # ignore all go packages from import paths starting with github.com/internal-package
123
- # see the `File.fnmatch?` documentation for details on how patterns are matched.
124
- # comparisons use the FNM_CASEFOLD and FNM_PATHNAME flags
125
- - github.com/internal-package/**/*
126
-
127
121
  # These dependencies have licenses not on the `allowed` list and have been reviewed.
128
122
  # They will be cached and checked, but will not raise errors or warnings for a
129
123
  # non-allowed license. Dependencies on this list will still raise errors if
@@ -24,6 +24,26 @@ The setting supports absolute, relative and expandable (e.g. "~") paths. Relati
24
24
 
25
25
  Non-empty `GOPATH` configuration settings will override the `GOPATH` environment variable while enumerating `go` dependencies. The `GOPATH` environment variable is restored once dependencies have been enumerated.
26
26
 
27
+ #### Reviewing and ignoring all packages from a Go module
28
+
29
+ Go's package and module structure has common conventions that documentation and metadata for all packages in a module live in the module root. In this scenario all packages share the same LICENSE information and can be reviewed or ignored at the module level rather than per-package using glob patterns.
30
+
31
+ ```yaml
32
+ reviewed:
33
+ go:
34
+ # review all Go packages from import paths starting with github.com/external-package
35
+ # see the `File.fnmatch?` documentation for details on how patterns are matched.
36
+ # comparisons use the FNM_CASEFOLD and FNM_PATHNAME flags
37
+ - github.com/external-package/**/*
38
+
39
+ ignored:
40
+ go:
41
+ # ignore all Go packages from import paths starting with github.com/internal-package
42
+ # see the `File.fnmatch?` documentation for details on how patterns are matched.
43
+ # comparisons use the FNM_CASEFOLD and FNM_PATHNAME flags
44
+ - github.com/internal-package/**/*
45
+ ```
46
+
27
47
  #### Versioning
28
48
 
29
49
  The go source supports multiple versioning strategies to determine if cached dependency metadata is stale. A version strategy is chosen based on the availability of go module information along with the current app configuration.
@@ -18,12 +18,14 @@ module Licensed
18
18
  end
19
19
 
20
20
  desc "status", "Check status of dependencies' cached licenses"
21
+ method_option :format, enum: ["yaml", "json"],
22
+ desc: "Output format"
21
23
  method_option :config, aliases: "-c", type: :string,
22
24
  desc: "Path to licensed configuration file"
23
25
  method_option :sources, aliases: "-s", type: :array,
24
26
  desc: "Individual source(s) to evaluate. Must also be enabled via configuration."
25
27
  def status
26
- run Licensed::Commands::Status.new(config: config), sources: options[:sources]
28
+ run Licensed::Commands::Status.new(config: config), sources: options[:sources], reporter: options[:format]
27
29
  end
28
30
 
29
31
  desc "list", "List dependencies"
@@ -57,7 +59,7 @@ module Licensed
57
59
  method_option :config, aliases: "-c", type: :string,
58
60
  desc: "Path to licensed configuration file"
59
61
  def env
60
- run Licensed::Commands::Environment.new(config: config), format: options[:format]
62
+ run Licensed::Commands::Environment.new(config: config), reporter: options[:format]
61
63
  end
62
64
 
63
65
  desc "migrate", "Migrate from a previous version of licensed"
@@ -2,12 +2,12 @@
2
2
  module Licensed
3
3
  module Commands
4
4
  class Cache < Command
5
- # Create a reporter to use during a command run
5
+ # Returns the default reporter to use during the command run
6
6
  #
7
7
  # options - The options the command was run with
8
8
  #
9
- # Raises a Licensed::Reporters::CacheReporter
10
- def create_reporter(options)
9
+ # Returns a Licensed::Reporters::CacheReporter
10
+ def default_reporter(options)
11
11
  Licensed::Reporters::CacheReporter.new
12
12
  end
13
13
 
@@ -37,13 +37,29 @@ module Licensed
37
37
  result
38
38
  end
39
39
 
40
- # Create a reporter to use during a command run
40
+ # Creates a reporter to use during a command run
41
41
  #
42
42
  # options - The options the command was run with
43
43
  #
44
- # Raises an error
44
+ # Returns the reporter to use during the command run
45
45
  def create_reporter(options)
46
- raise "`create_reporter` must be implemented by commands"
46
+ return options[:reporter] if options[:reporter].is_a?(Licensed::Reporters::Reporter)
47
+
48
+ if options[:reporter].is_a?(String)
49
+ klass = "#{options[:reporter].capitalize}Reporter"
50
+ return Licensed::Reporters.const_get(klass).new if Licensed::Reporters.const_defined?(klass)
51
+ end
52
+
53
+ default_reporter(options)
54
+ end
55
+
56
+ # Returns the default reporter to use during the command run
57
+ #
58
+ # options - The options the command was run with
59
+ #
60
+ # Raises an error
61
+ def default_reporter(options)
62
+ raise "`default_reporter` must be implemented by commands"
47
63
  end
48
64
 
49
65
  protected
@@ -56,6 +72,12 @@ module Licensed
56
72
  # Returns whether the command succeeded for the application.
57
73
  def run_app(app)
58
74
  reporter.report_app(app) do |report|
75
+ # ensure the app source path exists before evaluation
76
+ if !Dir.exist?(app.source_path)
77
+ report.errors << "No such directory #{app.source_path}"
78
+ next false
79
+ end
80
+
59
81
  Dir.chdir app.source_path do
60
82
  begin
61
83
  # allow additional report data to be given by commands
@@ -125,7 +147,7 @@ module Licensed
125
147
  end
126
148
 
127
149
  evaluate_dependency(app, source, dependency, report)
128
- rescue Licensed::Shell::Error => err
150
+ rescue Licensed::DependencyRecord::Error, Licensed::Shell::Error => err
129
151
  report.errors << err.message
130
152
  false
131
153
  end
@@ -35,13 +35,13 @@ module Licensed
35
35
  end
36
36
  end
37
37
 
38
- def create_reporter(options)
39
- case options[:format]
40
- when "json"
41
- Licensed::Reporters::JsonReporter.new
42
- else
43
- Licensed::Reporters::YamlReporter.new
44
- end
38
+ # Returns the default reporter to use during the command run
39
+ #
40
+ # options - The options the command was run with
41
+ #
42
+ # Returns a Licensed::Reporters::StatusReporter
43
+ def default_reporter(options)
44
+ Licensed::Reporters::YamlReporter.new
45
45
  end
46
46
 
47
47
  protected
@@ -2,12 +2,12 @@
2
2
  module Licensed
3
3
  module Commands
4
4
  class List < Command
5
- # Create a reporter to use during a command run
5
+ # Returns the default reporter to use during the command run
6
6
  #
7
7
  # options - The options the command was run with
8
8
  #
9
9
  # Returns a Licensed::Reporters::ListReporter
10
- def create_reporter(options)
10
+ def default_reporter(options)
11
11
  Licensed::Reporters::ListReporter.new
12
12
  end
13
13
 
@@ -2,12 +2,12 @@
2
2
  module Licensed
3
3
  module Commands
4
4
  class Notices < Command
5
- # Create a reporter to use during a command run
5
+ # Returns the default reporter to use during the command run
6
6
  #
7
7
  # options - The options the command was run with
8
8
  #
9
- # Raises a Licensed::Reporters::CacheReporter
10
- def create_reporter(options)
9
+ # Returns a Licensed::Reporters::CacheReporter
10
+ def default_reporter(options)
11
11
  Licensed::Reporters::NoticesReporter.new
12
12
  end
13
13
 
@@ -4,12 +4,12 @@ require "yaml"
4
4
  module Licensed
5
5
  module Commands
6
6
  class Status < Command
7
- # Create a reporter to use during a command run
7
+ # Returns the default reporter to use during the command run
8
8
  #
9
9
  # options - The options the command was run with
10
10
  #
11
11
  # Returns a Licensed::Reporters::StatusReporter
12
- def create_reporter(options)
12
+ def default_reporter(options)
13
13
  Licensed::Reporters::StatusReporter.new
14
14
  end
15
15
 
@@ -69,7 +69,9 @@ module Licensed
69
69
 
70
70
  # Is the given dependency reviewed?
71
71
  def reviewed?(dependency)
72
- Array(self["reviewed"][dependency["type"]]).include?(dependency["name"])
72
+ Array(self["reviewed"][dependency["type"]]).any? do |pattern|
73
+ File.fnmatch?(pattern, dependency["name"], File::FNM_PATHNAME | File::FNM_CASEFOLD)
74
+ end
73
75
  end
74
76
 
75
77
  # Is the given dependency ignored?
@@ -158,19 +160,22 @@ module Licensed
158
160
  def self.expand_app_source_path(app_config)
159
161
  return app_config if app_config["source_path"].to_s.empty?
160
162
 
163
+ # check if the source path maps to an existing directory
161
164
  source_path = File.expand_path(app_config["source_path"], AppConfiguration.root_for(app_config))
165
+ return app_config if Dir.exist?(source_path)
166
+
167
+ # try to expand the source path for glob patterns
162
168
  expanded_source_paths = Dir.glob(source_path).select { |p| File.directory?(p) }
163
- # return the original configuration if glob didn't result in multiple paths
164
- return app_config if expanded_source_paths.size <= 1
169
+ configs = expanded_source_paths.map { |path| app_config.merge("source_path" => path) }
165
170
 
166
- # map the expanded paths to new application configurations
167
- expanded_source_paths.map do |path|
168
- config = app_config.merge("source_path" => path)
171
+ # if no directories are found for the source path, return the original config
172
+ return app_config if configs.size == 0
169
173
 
170
- # update configured values for name and cache_path for uniqueness.
171
- # this is only needed when values are explicitly set, AppConfiguration
172
- # will handle configurations that don't have these explicitly set
173
- dir_name = File.basename(path)
174
+ # update configured values for name and cache_path for uniqueness.
175
+ # this is only needed when values are explicitly set, AppConfiguration
176
+ # will handle configurations that don't have these explicitly set
177
+ configs.each do |config|
178
+ dir_name = File.basename(config["source_path"])
174
179
  config["name"] = "#{config["name"]}-#{dir_name}" if config["name"]
175
180
 
176
181
  # if a cache_path is set and is not marked as shared, append the app name
@@ -178,9 +183,9 @@ module Licensed
178
183
  if config["cache_path"] && config["shared_cache"] != true
179
184
  config["cache_path"] = File.join(config["cache_path"], dir_name)
180
185
  end
181
-
182
- config
183
186
  end
187
+
188
+ configs
184
189
  end
185
190
 
186
191
  # Find a default configuration file in the given directory.
@@ -142,6 +142,7 @@ module Licensed
142
142
  def generated_license_contents
143
143
  return unless license
144
144
  return if license.key == "other"
145
+ return if license.text.nil?
145
146
 
146
147
  # strip copyright clauses and any extra newlines
147
148
  # many package managers don't provide enough information to
@@ -5,6 +5,8 @@ require "licensee"
5
5
 
6
6
  module Licensed
7
7
  class DependencyRecord
8
+ class Error < StandardError; end
9
+
8
10
  class License
9
11
  attr_reader :text, :sources
10
12
  def initialize(content)
@@ -46,6 +48,8 @@ module Licensed
46
48
  notices: data.delete("notices"),
47
49
  metadata: data
48
50
  )
51
+ rescue Psych::SyntaxError => e
52
+ raise Licensed::DependencyRecord::Error.new(e.message)
49
53
  end
50
54
 
51
55
  def_delegators :@metadata, :[], :[]=
@@ -9,11 +9,12 @@ module Licensed
9
9
  def self.execute(cmd, *args, allow_failure: false, env: {})
10
10
  stdout, stderr, status = Open3.capture3(env, cmd, *args)
11
11
 
12
- if status.success? || allow_failure
13
- stdout.strip
14
- else
15
- raise Error.new([cmd, *args], status.exitstatus, stderr)
12
+ if !status.success? && !allow_failure
13
+ raise Error.new([cmd, *args], status.exitstatus, encode_content(stderr))
16
14
  end
15
+
16
+ # ensure that returned data is properly encoded
17
+ encode_content(stdout.strip)
17
18
  end
18
19
 
19
20
  # Executes a command and returns a boolean value indicating if the command
@@ -55,5 +56,21 @@ module Licensed
55
56
  end.join(" ")
56
57
  end
57
58
  end
59
+
60
+ private
61
+
62
+ ENCODING = Encoding::UTF_8
63
+ ENCODING_OPTIONS = {
64
+ invalid: :replace,
65
+ undef: :replace,
66
+ replace: "",
67
+ univeral_newline: true
68
+ }.freeze
69
+
70
+ # Ensure that content that is returned from shell commands is in a usable
71
+ # encoding for the rest of the application
72
+ def self.encode_content(content)
73
+ content.encode(ENCODING, **ENCODING_OPTIONS)
74
+ end
58
75
  end
59
76
  end
@@ -74,7 +74,7 @@ module Licensed
74
74
  end
75
75
  end
76
76
 
77
- GEMFILES = %w{Gemfile gems.rb}.freeze
77
+ GEMFILES = { "Gemfile" => "Gemfile.lock", "gems.rb" => "gems.locked" }
78
78
  DEFAULT_WITHOUT_GROUPS = %i{development test}
79
79
 
80
80
  def enabled?
@@ -272,14 +272,15 @@ module Licensed
272
272
 
273
273
  # Returns the path to the Bundler Gemfile
274
274
  def gemfile_path
275
- @gemfile_path ||= GEMFILES.map { |g| config.pwd.join g }
275
+ @gemfile_path ||= GEMFILES.keys
276
+ .map { |g| config.pwd.join g }
276
277
  .find { |f| f.exist? }
277
278
  end
278
279
 
279
280
  # Returns the path to the Bundler Gemfile.lock
280
281
  def lockfile_path
281
282
  return unless gemfile_path
282
- @lockfile_path ||= gemfile_path.dirname.join("#{gemfile_path.basename}.lock")
283
+ @lockfile_path ||= gemfile_path.dirname.join(GEMFILES[gemfile_path.basename.to_s])
283
284
  end
284
285
 
285
286
  # Returns the configured bundler executable to use, or "bundle" by default.
@@ -15,8 +15,7 @@ module Licensed
15
15
  def enumerate_dependencies
16
16
  with_configured_gopath do
17
17
  packages.map do |package|
18
- import_path = non_vendored_path(package["ImportPath"], root_package["ImportPath"])
19
- import_path ||= package["ImportPath"]
18
+ import_path = non_vendored_import_path(package)
20
19
  error = package.dig("Error", "Err") if package["Error"]
21
20
 
22
21
  Dependency.new(
@@ -81,34 +80,26 @@ module Licensed
81
80
  # return true if package self-identifies
82
81
  return true if package["Standard"]
83
82
 
84
- import_path = package["ImportPath"]
83
+ import_path = non_vendored_import_path(package)
85
84
  return false unless import_path
86
85
 
87
- # true if go standard packages includes the import path as given
88
- return true if go_std_packages.include?(import_path)
89
- return true if go_std_packages.include?("vendor/#{import_path}")
90
- return true if go_std_packages.include?(import_path.sub("golang.org", "internal"))
91
-
92
- # additional checks are only for vendored dependencies - return false
93
- # if package isn't vendored
94
- non_vendored_import_path = non_vendored_path(import_path, root_package["ImportPath"])
95
- return false unless non_vendored_import_path
96
-
97
- # return true if any of the go standard packages matches against
98
- # the non-vendored import path
99
- return true if go_std_packages.include?(non_vendored_import_path)
100
- return true if go_std_packages.include?(non_vendored_import_path.sub("golang.org", "internal"))
101
-
102
- # modify the import path to look like the import path `go list` returns for vendored std packages
103
- vendor_path = import_path.sub("#{root_package["ImportPath"]}/", "")
104
- go_std_packages.include?(vendor_path) || go_std_packages.include?(vendor_path.sub("golang.org", "golang_org"))
86
+ # check different variations of the import path to match against
87
+ # what's returned from `go list std`
88
+ [
89
+ import_path,
90
+ import_path.sub("golang.org", "internal"),
91
+ import_path.sub("golang.org", "golang_org"),
92
+ ].any? do |path|
93
+ # true if go standard packages includes the path or "vendor/<path>"
94
+ go_std_packages.include?(path) || go_std_packages.include?("vendor/#{path}")
95
+ end
105
96
  end
106
97
 
107
98
  # Returns whether the package is local to the current project
108
99
  def local_package?(package)
109
- return false unless package && package["ImportPath"]
110
- import_path = package["ImportPath"]
111
- import_path.start_with?(root_package["ImportPath"]) && !import_path.include?("vendor/")
100
+ return false unless package && package["Dir"]
101
+ return false unless File.fnmatch?("#{config.root.to_s}*", package["Dir"])
102
+ vendored_path_parts(package).nil?
112
103
  end
113
104
 
114
105
  # Returns the version for a given package
@@ -155,36 +146,45 @@ module Licensed
155
146
 
156
147
  # search root choices:
157
148
  # 1. module directory if using go modules and directory is available
158
- # 2. vendor folder if package is vendored
159
- # 3. package root value if available
160
- # 4. GOPATH if the package directory is under the gopath
161
- # 5. nil
162
149
  module_dir = package.dig("Module", "Dir")
163
150
  return module_dir if module_dir
164
- return package["Dir"].match("^(.*/vendor)/.*$")[1] if vendored_path?(package["Dir"], config.root)
151
+
152
+ # 2. vendor folder if package is vendored
153
+ parts = vendored_path_parts(package)
154
+ return parts[:vendor_path] if parts
155
+
156
+ # 3. package root value if available
165
157
  return package["Root"] if package["Root"]
158
+
159
+ # 4. GOPATH if the package directory is under the gopath
166
160
  return gopath if package["Dir"]&.start_with?(gopath)
161
+
162
+ # 5. nil
167
163
  nil
168
164
  end
169
165
 
170
- # Returns whether a package is vendored or not based on a base path and
171
- # whether the path contains a vendor component
166
+ # If the package is vendored, returns a Match object containing named
167
+ # :vendor_path and :import_path match groups based on the packages "Dir" value
168
+ #
169
+ # If the package is not vendored, returns nil
172
170
  #
173
- # path - Package path to test
174
- # base - The base path that the input must start with
175
- def vendored_path?(path, base)
176
- return false if path.nil? || base.nil?
177
- path.start_with?(base.to_s) && path.include?("vendor/")
171
+ # package - Package to get vendored path information for
172
+ def vendored_path_parts(package)
173
+ return if package.nil? || package["Dir"].nil?
174
+ package["Dir"].match(/^(?<vendor_path>#{config.root}(\/.+)*\/[^\/]*vendor[^\/]*)\/(?<import_path>.+)$/i)
178
175
  end
179
176
 
180
- # Returns the path parameter without the vendor component if one is found
177
+ # Returns the non-vendored portion of the package import path if vendored,
178
+ # otherwise returns the package's import path as given
181
179
  #
182
- # path - Package path with vendor component
183
- # base - The base path that the input must start with
184
- def non_vendored_path(path, base)
185
- return unless path
186
- return unless vendored_path?(path, base)
187
- path.split("vendor/")[1]
180
+ # package - Package to get the non-vendored import path for
181
+ def non_vendored_import_path(package)
182
+ return if package.nil?
183
+ parts = vendored_path_parts(package)
184
+ return parts[:import_path] if parts
185
+
186
+ # if a package isn't vendored, return the packages "ImportPath"
187
+ package["ImportPath"]
188
188
  end
189
189
 
190
190
  # Returns a hash of information about the package with a given import path
@@ -30,7 +30,7 @@ module Licensed
30
30
  end
31
31
 
32
32
  def packages
33
- root_dependencies = JSON.parse(package_metadata_command)["dependencies"]
33
+ root_dependencies = package_metadata["dependencies"]
34
34
  recursive_dependencies(root_dependencies).each_with_object({}) do |(name, results), hsh|
35
35
  results.uniq! { |package| package["version"] }
36
36
  if results.size == 1
@@ -56,6 +56,18 @@ module Licensed
56
56
  result
57
57
  end
58
58
 
59
+ # Returns parsed package metadata returned from `npm list`
60
+ def package_metadata
61
+ return @package_metadata if defined?(@package_metadata)
62
+
63
+ @package_metadata = begin
64
+ JSON.parse(package_metadata_command)
65
+ rescue JSON::ParserError => e
66
+ raise Licensed::Sources::Source::Error,
67
+ "Licensed was unable to parse the output from 'npm list'. Please run 'npm list --json --long' and check for errors. Error: #{e.message}"
68
+ end
69
+ end
70
+
59
71
  # Returns the output from running `npm list` to get package metadata
60
72
  def package_metadata_command
61
73
  args = %w(--json --long)
@@ -36,7 +36,7 @@ module Licensed
36
36
  def packages
37
37
  return [] if yarn_package_tree.nil?
38
38
  all_dependencies = {}
39
- recursive_dependencies(config.pwd, yarn_package_tree).each do |name, results|
39
+ recursive_dependencies(yarn_package_tree).each do |name, results|
40
40
  results.uniq! { |package| package["version"] }
41
41
  if results.size == 1
42
42
  # if there is only one package for a name, reference it by name
@@ -55,26 +55,34 @@ module Licensed
55
55
 
56
56
  # Recursively parse dependency JSON data. Returns a hash mapping the
57
57
  # package name to it's metadata
58
- def recursive_dependencies(path, dependencies, result = {})
58
+ def recursive_dependencies(dependencies, result = {})
59
59
  dependencies.each do |dependency|
60
60
  # "shadow" indicate a dependency requirement only, not a
61
61
  # resolved package identifier
62
62
  next if dependency["shadow"]
63
63
  name, _, version = dependency["name"].rpartition("@")
64
64
 
65
- # the dependency should be found under the parent's "node_modules" path
66
- dependency_path = path.join("node_modules", name)
67
65
  (result[name] ||= []) << {
68
66
  "id" => dependency["name"],
69
67
  "name" => name,
70
68
  "version" => version,
71
- "path" => dependency_path
69
+ "path" => dependency_paths[dependency["name"]]
72
70
  }
73
- recursive_dependencies(dependency_path, dependency["children"], result)
71
+ recursive_dependencies(dependency["children"], result)
74
72
  end
75
73
  result
76
74
  end
77
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
+
78
86
  # Finds and returns the yarn package tree listing from `yarn list` output
79
87
  def yarn_package_tree
80
88
  return @yarn_package_tree if defined?(@yarn_package_tree)
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
  module Licensed
3
- VERSION = "2.12.2".freeze
3
+ VERSION = "2.14.3".freeze
4
4
 
5
5
  def self.previous_major_versions
6
6
  major_version = Gem::Version.new(Licensed::VERSION).segments.first
@@ -38,5 +38,4 @@ Gem::Specification.new do |spec|
38
38
  spec.add_development_dependency "rubocop", "~> 0.49", "< 0.67"
39
39
  spec.add_development_dependency "rubocop-github", "~> 0.6"
40
40
  spec.add_development_dependency "byebug", "~> 10.0.0"
41
- spec.add_development_dependency "spy", "~> 1.0.0"
42
41
  end
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: 2.12.2
4
+ version: 2.14.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - GitHub
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-07-07 00:00:00.000000000 Z
11
+ date: 2020-12-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: licensee
@@ -218,20 +218,6 @@ dependencies:
218
218
  - - "~>"
219
219
  - !ruby/object:Gem::Version
220
220
  version: 10.0.0
221
- - !ruby/object:Gem::Dependency
222
- name: spy
223
- requirement: !ruby/object:Gem::Requirement
224
- requirements:
225
- - - "~>"
226
- - !ruby/object:Gem::Version
227
- version: 1.0.0
228
- type: :development
229
- prerelease: false
230
- version_requirements: !ruby/object:Gem::Requirement
231
- requirements:
232
- - - "~>"
233
- - !ruby/object:Gem::Version
234
- version: 1.0.0
235
221
  description: Licensed automates extracting and validating the licenses of dependencies.
236
222
  email:
237
223
  - opensource+licensed@github.com