licensed 3.9.1 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +53 -1
- data/CONTRIBUTING.md +0 -2
- data/Gemfile.lock +112 -0
- data/README.md +7 -12
- data/Rakefile +0 -26
- data/docs/configuration/README.md +1 -0
- data/docs/configuration/additional_terms.md +41 -0
- data/docs/configuration.md +7 -0
- data/docs/sources/bundler.md +0 -2
- data/docs/sources/cocoapods.md +17 -0
- data/docs/sources/gradle.md +18 -0
- data/docs/sources/pnpm.md +18 -0
- data/lib/licensed/configuration.rb +6 -1
- data/lib/licensed/dependency.rb +27 -0
- data/lib/licensed/sources/bundler/definition.rb +9 -1
- data/lib/licensed/sources/bundler/missing_specification.rb +2 -2
- data/lib/licensed/sources/bundler.rb +0 -12
- data/lib/licensed/sources/cocoapods.rb +68 -0
- data/lib/licensed/sources/go.rb +1 -36
- data/lib/licensed/sources/gradle.rb +127 -94
- data/lib/licensed/sources/pnpm.rb +52 -0
- data/lib/licensed/sources/source.rb +8 -1
- data/lib/licensed/sources.rb +4 -2
- data/lib/licensed/version.rb +1 -1
- data/licensed.gemspec +13 -13
- metadata +46 -73
- data/docs/packaging.md +0 -53
data/lib/licensed/sources/go.rb
CHANGED
@@ -36,27 +36,13 @@ module Licensed
|
|
36
36
|
|
37
37
|
# Returns an array of dependency package import paths
|
38
38
|
def packages
|
39
|
-
dependency_packages =
|
40
|
-
if go_version < Gem::Version.new("1.11.0")
|
41
|
-
root_package_deps
|
42
|
-
else
|
43
|
-
go_list_deps
|
44
|
-
end
|
45
|
-
|
46
39
|
# don't include go std packages
|
47
40
|
# don't include packages under the root project that aren't vendored
|
48
|
-
|
41
|
+
go_list_deps
|
49
42
|
.reject { |pkg| go_std_package?(pkg) }
|
50
43
|
.reject { |pkg| local_package?(pkg) }
|
51
44
|
end
|
52
45
|
|
53
|
-
# Returns non-ignored packages found from the root packages "Deps" property
|
54
|
-
def root_package_deps
|
55
|
-
# check for ignored packages to avoid raising errors calling `go list`
|
56
|
-
# when ignored package is not found
|
57
|
-
Parallel.map(Array(root_package["Deps"])) { |name| package_info(name) }
|
58
|
-
end
|
59
|
-
|
60
46
|
# Returns the list of dependencies as returned by "go list -json -deps"
|
61
47
|
# available in go 1.11
|
62
48
|
def go_list_deps
|
@@ -188,13 +174,6 @@ module Licensed
|
|
188
174
|
package["ImportPath"]
|
189
175
|
end
|
190
176
|
|
191
|
-
# Returns a hash of information about the package with a given import path
|
192
|
-
#
|
193
|
-
# import_path - Go package import path
|
194
|
-
def package_info(import_path)
|
195
|
-
JSON.parse(package_info_command(import_path))
|
196
|
-
end
|
197
|
-
|
198
177
|
# Returns package information as a JSON string
|
199
178
|
#
|
200
179
|
# args - additional arguments to `go list`, e.g. Go package import path
|
@@ -202,11 +181,6 @@ module Licensed
|
|
202
181
|
Licensed::Shell.execute("go", "list", "-e", "-json", *Array(args)).strip
|
203
182
|
end
|
204
183
|
|
205
|
-
# Returns the info for the package under test
|
206
|
-
def root_package
|
207
|
-
@root_package ||= package_info(".")
|
208
|
-
end
|
209
|
-
|
210
184
|
# Returns whether go source is found
|
211
185
|
def go_source?
|
212
186
|
with_configured_gopath { Licensed::Shell.success?("go", "doc") }
|
@@ -230,15 +204,6 @@ module Licensed
|
|
230
204
|
end
|
231
205
|
end
|
232
206
|
|
233
|
-
# Returns the current version of go available, as a Gem::Version
|
234
|
-
def go_version
|
235
|
-
@go_version ||= begin
|
236
|
-
full_version = Licensed::Shell.execute("go", "version").strip
|
237
|
-
version_string = full_version.gsub(%r{.*go(\d+\.\d+(\.\d+)?).*}, "\\1")
|
238
|
-
Gem::Version.new(version_string)
|
239
|
-
end
|
240
|
-
end
|
241
|
-
|
242
207
|
private
|
243
208
|
|
244
209
|
# Execute a block with ENV["GOPATH"] set to the value of #gopath.
|
@@ -9,53 +9,19 @@ require "fileutils"
|
|
9
9
|
module Licensed
|
10
10
|
module Sources
|
11
11
|
class Gradle < Source
|
12
|
-
|
13
12
|
DEFAULT_CONFIGURATIONS = ["runtime", "runtimeClasspath"].freeze
|
14
13
|
GRADLE_LICENSES_PATH = ".gradle-licenses".freeze
|
15
|
-
|
14
|
+
GRADLE_LICENSES_CSV_NAME = "licenses.csv".freeze
|
16
15
|
class Dependency < Licensed::Dependency
|
17
|
-
GRADLE_LICENSES_CSV_NAME = "licenses.csv".freeze
|
18
|
-
|
19
16
|
class << self
|
20
|
-
# Returns a key to uniquely identify a name and version in the obtained CSV content
|
21
|
-
def csv_key(name:, version:)
|
22
|
-
"#{name}-#{version}"
|
23
|
-
end
|
24
|
-
|
25
|
-
# Loads and caches license report CSV data as a hash of :name-:version => :url pairs
|
26
|
-
#
|
27
|
-
# executable - The gradle executable to run to generate the license report
|
28
|
-
# configurations - The gradle configurations to generate license report for
|
29
|
-
#
|
30
|
-
# Returns a hash of dependency identifiers to their license content URL
|
31
|
-
def load_csv(path, executable, configurations)
|
32
|
-
@csv ||= begin
|
33
|
-
gradle_licenses_dir = File.join(path, GRADLE_LICENSES_PATH)
|
34
|
-
Licensed::Sources::Gradle.gradle_command("generateLicenseReport", path: path, executable: executable, configurations: configurations)
|
35
|
-
CSV.foreach(File.join(gradle_licenses_dir, GRADLE_LICENSES_CSV_NAME), headers: true).each_with_object({}) do |row, hsh|
|
36
|
-
name, _, version = row["artifact"].rpartition(":")
|
37
|
-
key = csv_key(name: name, version: version)
|
38
|
-
hsh[key] = row["moduleLicenseUrl"]
|
39
|
-
end
|
40
|
-
ensure
|
41
|
-
FileUtils.rm_rf(gradle_licenses_dir)
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
# Returns the cached url for the given dependency
|
46
|
-
def url_for(dependency)
|
47
|
-
@csv[csv_key(name: dependency.name, version: dependency.version)]
|
48
|
-
end
|
49
|
-
|
50
17
|
# Cache and return the results of getting the license content.
|
51
18
|
def retrieve_license(url)
|
52
19
|
(@licenses ||= {})[url] ||= Net::HTTP.get(URI(url))
|
53
20
|
end
|
54
21
|
end
|
55
22
|
|
56
|
-
def initialize(name:, version:, path:,
|
57
|
-
@
|
58
|
-
@executable = executable
|
23
|
+
def initialize(name:, version:, path:, url:, metadata: {})
|
24
|
+
@url = url
|
59
25
|
super(name: name, version: version, path: path, metadata: metadata)
|
60
26
|
end
|
61
27
|
|
@@ -68,32 +34,27 @@ module Licensed
|
|
68
34
|
|
69
35
|
# Returns a Licensee::ProjectFiles::LicenseFile for the dependency
|
70
36
|
def project_files
|
71
|
-
|
72
|
-
url = self.class.url_for(self)
|
37
|
+
return [] if @url.nil?
|
73
38
|
|
74
|
-
|
75
|
-
|
76
|
-
license_data = self.class.retrieve_license(url)
|
77
|
-
|
78
|
-
Array(Licensee::ProjectFiles::LicenseFile.new(license_data, { uri: url }))
|
39
|
+
license_data = self.class.retrieve_license(@url)
|
40
|
+
Array(Licensee::ProjectFiles::LicenseFile.new(license_data, { uri: @url }))
|
79
41
|
end
|
80
42
|
end
|
81
43
|
|
82
44
|
def enabled?
|
83
|
-
!
|
45
|
+
!executable.to_s.empty? && File.exist?(config.pwd.join("build.gradle"))
|
84
46
|
end
|
85
47
|
|
86
48
|
def enumerate_dependencies
|
87
|
-
JSON.parse(
|
88
|
-
name = "#{package[
|
49
|
+
JSON.parse(gradle_runner.run("printDependencies", config.source_path)).map do |package|
|
50
|
+
name = "#{package['group']}:#{package['name']}"
|
89
51
|
Dependency.new(
|
90
52
|
name: name,
|
91
53
|
version: package["version"],
|
92
54
|
path: config.pwd,
|
93
|
-
|
94
|
-
configurations: configurations,
|
55
|
+
url: package_url(name: name, version: package["version"]),
|
95
56
|
metadata: {
|
96
|
-
"type"
|
57
|
+
"type" => Gradle.type,
|
97
58
|
}
|
98
59
|
)
|
99
60
|
end
|
@@ -101,15 +62,20 @@ module Licensed
|
|
101
62
|
|
102
63
|
private
|
103
64
|
|
104
|
-
def
|
105
|
-
return @
|
106
|
-
|
107
|
-
|
65
|
+
def executable
|
66
|
+
return @executable if defined?(@executable)
|
67
|
+
|
68
|
+
@executable = begin
|
108
69
|
return gradlew if File.executable?(gradlew)
|
70
|
+
|
109
71
|
"gradle" if Licensed::Shell.tool_available?("gradle")
|
110
72
|
end
|
111
73
|
end
|
112
74
|
|
75
|
+
def gradle_runner
|
76
|
+
@gradle_runner ||= Runner.new(config.pwd, configurations, executable)
|
77
|
+
end
|
78
|
+
|
113
79
|
# Returns the configurations to include in license generation.
|
114
80
|
# Defaults to ["runtime", "runtimeClasspath"]
|
115
81
|
def configurations
|
@@ -122,61 +88,128 @@ module Licensed
|
|
122
88
|
end
|
123
89
|
end
|
124
90
|
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
gradle_build_file = " plugins { id 'com.github.jk1.dependency-license-report' version '1.16' }" + gradle_build_file
|
91
|
+
# Returns the path to the Gradle wrapper.
|
92
|
+
def gradlew
|
93
|
+
@gradlew ||= begin
|
94
|
+
gradlew = config.dig("gradle", "gradlew")
|
95
|
+
config.root.join(gradlew || "gradlew").to_s
|
132
96
|
end
|
133
97
|
end
|
134
98
|
|
135
|
-
|
136
|
-
|
99
|
+
# Returns a key to uniquely identify a name and version in the obtained CSV content
|
100
|
+
def csv_key(name:, version:)
|
101
|
+
"#{name}-#{version}"
|
102
|
+
end
|
103
|
+
|
104
|
+
def package_url(name:, version:)
|
105
|
+
# load and memoize the license report CSV
|
106
|
+
@urls ||= load_csv
|
137
107
|
|
138
|
-
|
139
|
-
|
140
|
-
|
108
|
+
# uniquely identify a name and version in the obtained CSV content
|
109
|
+
@urls["#{name}-#{version}"]
|
110
|
+
end
|
141
111
|
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
112
|
+
def load_csv
|
113
|
+
begin
|
114
|
+
# create the CSV file including dependency license urls using the gradle plugin
|
115
|
+
gradle_licenses_dir = File.join(config.root, GRADLE_LICENSES_PATH)
|
116
|
+
gradle_runner.run("generateLicenseReport", config.source_path)
|
117
|
+
|
118
|
+
# parse the CSV report for dependency license urls
|
119
|
+
CSV.foreach(File.join(gradle_licenses_dir, GRADLE_LICENSES_CSV_NAME), headers: true).each_with_object({}) do |row, hsh|
|
120
|
+
name, _, version = row["artifact"].rpartition(":")
|
121
|
+
key = csv_key(name: name, version: version)
|
122
|
+
hsh[key] = row["moduleLicenseUrl"]
|
148
123
|
end
|
124
|
+
ensure
|
125
|
+
FileUtils.rm_rf(gradle_licenses_dir)
|
149
126
|
end
|
150
127
|
end
|
151
128
|
|
152
|
-
|
153
|
-
|
129
|
+
# Returns the cached url for the given dependency
|
130
|
+
def url_for(dependency)
|
131
|
+
@csv[csv_key(name: dependency.name, version: dependency.version)]
|
132
|
+
end
|
133
|
+
|
134
|
+
# The Gradle::Runner class is a wrapper which provides
|
135
|
+
# an interface to run gradle commands with the init script initialized
|
136
|
+
class Runner
|
137
|
+
def initialize(root_path, configurations, executable)
|
138
|
+
@root_path = root_path
|
139
|
+
@executable = executable
|
140
|
+
@init_script = create_init_script(root_path, configurations)
|
141
|
+
end
|
154
142
|
|
155
|
-
|
156
|
-
|
143
|
+
def run(command, source_path)
|
144
|
+
args = [format_command(command, source_path)]
|
145
|
+
# The configuration cache is an incubating feature that can be activated manually.
|
146
|
+
# The gradle plugin for licenses does not support it so we prevent it to run for gradle version supporting it.
|
147
|
+
args << "--no-configuration-cache" if gradle_version >= "6.6"
|
148
|
+
Licensed::Shell.execute(@executable, "-q", "--init-script", @init_script.path, *args)
|
149
|
+
end
|
157
150
|
|
158
|
-
|
151
|
+
private
|
159
152
|
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
renderers = [new CsvReportRenderer()]
|
164
|
-
filters = [new LicenseBundleNormalizer()]
|
165
|
-
}
|
153
|
+
def gradle_version
|
154
|
+
@gradle_version ||= Licensed::Shell.execute(@executable, "--version").scan(/Gradle [\d+]\.[\d+]/).last.split(" ").last
|
155
|
+
end
|
166
156
|
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
157
|
+
def create_init_script(path, configurations)
|
158
|
+
Dir.chdir(path) do
|
159
|
+
f = Tempfile.new(["init", ".gradle"], @root_path)
|
160
|
+
f.write(
|
161
|
+
<<~EOF
|
162
|
+
import com.github.jk1.license.render.CsvReportRenderer
|
163
|
+
import com.github.jk1.license.filter.LicenseBundleNormalizer
|
164
|
+
final configs = #{configurations.inspect}
|
165
|
+
|
166
|
+
initscript {
|
167
|
+
repositories {
|
168
|
+
maven {
|
169
|
+
url "https://plugins.gradle.org/m2/"
|
174
170
|
}
|
171
|
+
}
|
172
|
+
dependencies {
|
173
|
+
classpath "com.github.jk1:gradle-license-report:#{gradle_version >= "7.0" ? "2.0" : "1.17"}"
|
174
|
+
}
|
175
175
|
}
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
176
|
+
|
177
|
+
allprojects {
|
178
|
+
apply plugin: com.github.jk1.license.LicenseReportPlugin
|
179
|
+
licenseReport {
|
180
|
+
outputDir = "$rootDir/.gradle-licenses"
|
181
|
+
configurations = configs
|
182
|
+
renderers = [new CsvReportRenderer()]
|
183
|
+
filters = [new LicenseBundleNormalizer()]
|
184
|
+
}
|
185
|
+
|
186
|
+
task printDependencies {
|
187
|
+
doLast {
|
188
|
+
def dependencies = []
|
189
|
+
configs.each {
|
190
|
+
configurations[it].resolvedConfiguration.resolvedArtifacts.each { artifact ->
|
191
|
+
def id = artifact.moduleVersion.id
|
192
|
+
dependencies << "{ \\"group\\": \\"${id.group}\\", \\"name\\": \\"${id.name}\\", \\"version\\": \\"${id.version}\\" }"
|
193
|
+
}
|
194
|
+
}
|
195
|
+
println "[${dependencies.join(", ")}]"
|
196
|
+
}
|
197
|
+
}
|
198
|
+
}
|
199
|
+
EOF
|
200
|
+
)
|
201
|
+
f.close
|
202
|
+
f
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
# Prefixes the gradle command with the project name for multi-build projects.
|
207
|
+
def format_command(command, source_path)
|
208
|
+
Dir.chdir(source_path) do
|
209
|
+
path = Licensed::Shell.execute(@executable, "properties", "-Dorg.gradle.logging.level=quiet").scan(/path:.*/).last.split(" ").last
|
210
|
+
path == ":" ? command : "#{path}:#{command}"
|
211
|
+
end
|
212
|
+
end
|
180
213
|
end
|
181
214
|
end
|
182
215
|
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require "json"
|
3
|
+
|
4
|
+
module Licensed
|
5
|
+
module Sources
|
6
|
+
class PNPM < Source
|
7
|
+
# Returns true when pnpm is installed and a pnpm-lock.yaml file is found,
|
8
|
+
# otherwise false
|
9
|
+
def enabled?
|
10
|
+
return false unless Licensed::Shell.tool_available?("pnpm")
|
11
|
+
File.exist?(File.join(config.pwd, "pnpm-lock.yaml"))
|
12
|
+
end
|
13
|
+
|
14
|
+
def enumerate_dependencies
|
15
|
+
packages.map do |package|
|
16
|
+
name_with_version = "#{package["name"]}@#{package["version"]}"
|
17
|
+
Dependency.new(
|
18
|
+
name: name_with_version,
|
19
|
+
version: package["version"],
|
20
|
+
path: package["path"],
|
21
|
+
metadata: {
|
22
|
+
"type" => PNPM.type,
|
23
|
+
"name" => package["name"],
|
24
|
+
"summary" => package["description"],
|
25
|
+
"homepage" => package["homepage"]
|
26
|
+
}
|
27
|
+
)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# Returns package metadata returned from `pnpm licensed list`
|
32
|
+
def packages
|
33
|
+
JSON.parse(package_metadata_command).values.flatten
|
34
|
+
rescue JSON::ParserError => e
|
35
|
+
message = "Licensed was unable to parse the output from 'pnpm licenses list'. JSON Error: #{e.message}"
|
36
|
+
raise Licensed::Sources::Source::Error, message
|
37
|
+
end
|
38
|
+
|
39
|
+
# Returns the output from running `pnpm licenses list` to get package metadata
|
40
|
+
def package_metadata_command
|
41
|
+
args = %w(--json --long)
|
42
|
+
args << "--prod" unless include_non_production?
|
43
|
+
Licensed::Shell.execute("pnpm", "licenses", "list", *args, allow_failure: true)
|
44
|
+
end
|
45
|
+
|
46
|
+
# Returns whether to include non production dependencies based on the licensed configuration settings
|
47
|
+
def include_non_production?
|
48
|
+
config.dig("pnpm", "production_only") == false
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -69,7 +69,9 @@ module Licensed
|
|
69
69
|
# Returns all dependencies that should be evaluated.
|
70
70
|
# Excludes ignored dependencies.
|
71
71
|
def dependencies
|
72
|
-
cached_dependencies
|
72
|
+
cached_dependencies
|
73
|
+
.reject { |d| ignored?(d) }
|
74
|
+
.each { |d| add_additional_terms_from_configuration(d) }
|
73
75
|
end
|
74
76
|
|
75
77
|
# Enumerate all source dependencies. Must be implemented by each source class.
|
@@ -88,6 +90,11 @@ module Licensed
|
|
88
90
|
def cached_dependencies
|
89
91
|
@dependencies ||= enumerate_dependencies.compact
|
90
92
|
end
|
93
|
+
|
94
|
+
# Add any additional_terms for this dependency that have been added to the configuration
|
95
|
+
def add_additional_terms_from_configuration(dependency)
|
96
|
+
dependency.additional_terms.concat config.additional_terms_for_dependency("type" => self.class.type, "name" => dependency.name)
|
97
|
+
end
|
91
98
|
end
|
92
99
|
end
|
93
100
|
end
|
data/lib/licensed/sources.rb
CHANGED
@@ -6,18 +6,20 @@ module Licensed
|
|
6
6
|
require "licensed/sources/bundler"
|
7
7
|
require "licensed/sources/cabal"
|
8
8
|
require "licensed/sources/cargo"
|
9
|
+
require "licensed/sources/cocoapods"
|
9
10
|
require "licensed/sources/composer"
|
10
11
|
require "licensed/sources/dep"
|
11
12
|
require "licensed/sources/git_submodule"
|
12
13
|
require "licensed/sources/go"
|
14
|
+
require "licensed/sources/gradle"
|
13
15
|
require "licensed/sources/manifest"
|
16
|
+
require "licensed/sources/mix"
|
14
17
|
require "licensed/sources/npm"
|
15
18
|
require "licensed/sources/nuget"
|
16
19
|
require "licensed/sources/pip"
|
17
20
|
require "licensed/sources/pipenv"
|
21
|
+
require "licensed/sources/pnpm"
|
18
22
|
require "licensed/sources/swift"
|
19
|
-
require "licensed/sources/gradle"
|
20
|
-
require "licensed/sources/mix"
|
21
23
|
require "licensed/sources/yarn"
|
22
24
|
end
|
23
25
|
end
|
data/lib/licensed/version.rb
CHANGED
data/licensed.gemspec
CHANGED
@@ -21,21 +21,21 @@ Gem::Specification.new do |spec|
|
|
21
21
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
22
22
|
spec.require_paths = ["lib"]
|
23
23
|
|
24
|
-
spec.required_ruby_version = ">= 2.
|
24
|
+
spec.required_ruby_version = ">= 2.6.0"
|
25
25
|
|
26
|
-
spec.add_dependency "licensee", "
|
27
|
-
spec.add_dependency "thor", "
|
26
|
+
spec.add_dependency "licensee", "~> 9.16"
|
27
|
+
spec.add_dependency "thor", "~> 1.2"
|
28
28
|
spec.add_dependency "pathname-common_prefix", "~> 0.0.1"
|
29
|
-
spec.add_dependency "tomlrb", "
|
30
|
-
spec.add_dependency "
|
31
|
-
spec.add_dependency "
|
32
|
-
spec.add_dependency "
|
33
|
-
spec.add_dependency "
|
34
|
-
spec.add_dependency "
|
29
|
+
spec.add_dependency "tomlrb", "~> 2.0"
|
30
|
+
spec.add_dependency "ruby-xxHash", "~> 0.4.0"
|
31
|
+
spec.add_dependency "parallel", "~> 1.22"
|
32
|
+
spec.add_dependency "reverse_markdown", "~> 2.1"
|
33
|
+
spec.add_dependency "json", "~> 2.6"
|
34
|
+
# spec.add_dependency "cocoapods-core", "~> 1.11"
|
35
35
|
|
36
|
-
spec.add_development_dependency "rake", "
|
37
|
-
spec.add_development_dependency "minitest", "~> 5.
|
36
|
+
spec.add_development_dependency "rake", "~> 13.0"
|
37
|
+
spec.add_development_dependency "minitest", "~> 5.17"
|
38
38
|
spec.add_development_dependency "mocha", "~> 2.0"
|
39
|
-
spec.add_development_dependency "rubocop-github", "~> 0.
|
40
|
-
spec.add_development_dependency "byebug", "~> 11.1
|
39
|
+
spec.add_development_dependency "rubocop-github", "~> 0.20"
|
40
|
+
spec.add_development_dependency "byebug", "~> 11.1"
|
41
41
|
end
|