cli-kit 5.2.0 → 5.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c397a745de33f27b897f8fd2048874be2f711a56db37ca17f87c5ad169e4f062
4
- data.tar.gz: 80332fe8bd9a5dd5fb459f55d2f655a539cc95b13c4a2df174abafaaa4b7c924
3
+ metadata.gz: c406f8c2cf0b1c558c25cd0e6af9e4dd1de520f8a304e8762ee4e2086d72ab15
4
+ data.tar.gz: c916e638ee9dafd16b5ad646ba43ed1d226fe386488e1a0072a7b5f9107713f5
5
5
  SHA512:
6
- metadata.gz: c689fdcf5ae697be38b21ce60e973874c0d6a7f5c0eeaa085b8b93e635a14cbcd3d6af229d0dcb2c7f00084ca58845c3055c687cd6e7fbd4bc8a1902a5582bd2
7
- data.tar.gz: 153bf5c66de83e3d332944dfdc6fa9b1d52d4d5413ff80e4cac4b5aafbf0eddc888e22b2e50cf7914b43d1a90f56c23e2f41aaa7ec3987e7c51276092ad6f132
6
+ metadata.gz: c111015f730162b6ccf7f6f83d45782b27bdc044739cdc100146e08468acc4888ae363a9703f7c044b14e248a291f82d0c8f77cd7986bc2539f52d361b17356f
7
+ data.tar.gz: 9f8716992e5a263a8657a2e20035e679eb953cf790727283855804e6d433d5c53b412ec8f34b4fe5b09b6b82a98067ab76b32377c0108203d2c31d6f08143ca8
@@ -0,0 +1,20 @@
1
+ changelog:
2
+ exclude:
3
+ labels:
4
+ - dependencies
5
+ authors:
6
+ - dependabot
7
+ categories:
8
+ - title: 🚧 Breaking Changes
9
+ labels:
10
+ - breaking-change
11
+ - title: ✨ Enhancements
12
+ labels:
13
+ - enhancement
14
+ - feature
15
+ - title: 🐛 Bug Fixes
16
+ labels:
17
+ - bugfix
18
+ - title: 🛠 Other Changes
19
+ labels:
20
+ - "*"
@@ -0,0 +1,52 @@
1
+ name: Release gem on RubyGems.org and create GitHub release
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - 'v[0-9]+.[0-9]+.[0-9]+'
7
+
8
+ jobs:
9
+ release_gem:
10
+ name: Release gem on RubyGems.org
11
+ if: github.repository == 'Shopify/cli-kit'
12
+ runs-on: ubuntu-latest
13
+
14
+ permissions:
15
+ contents: write
16
+ id-token: write
17
+
18
+ environment: release
19
+
20
+ steps:
21
+ - name: Checkout repository
22
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
23
+ with:
24
+ persist-credentials: false
25
+
26
+ - name: Set up Ruby
27
+ uses: ruby/setup-ruby@95ef2b042f9d7a56d8268cba8559e2842e2ad01b # v1.321.0
28
+ with:
29
+ bundler-cache: true
30
+
31
+ - name: Release gem on RubyGems.org
32
+ uses: rubygems/release-gem@7f9650160c1a4e7989fdc9855807bdbd421d8b6b # v1.4.1
33
+
34
+ release_github:
35
+ name: Create GitHub release
36
+ if: github.repository == 'Shopify/cli-kit'
37
+ needs: release_gem
38
+ runs-on: ubuntu-latest
39
+
40
+ permissions:
41
+ contents: write
42
+
43
+ steps:
44
+ - name: Checkout repository
45
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
46
+
47
+ - name: Create GitHub release
48
+ run: |
49
+ tag_name="$(git describe --tags --abbrev=0)"
50
+ gh release create "${tag_name}" --verify-tag --generate-notes
51
+ env:
52
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -30,7 +30,7 @@ jobs:
30
30
  strategy:
31
31
  matrix:
32
32
  os: [macos-latest, ubuntu-latest]
33
- ruby-version: ['3.1', '3.2', '3.3', '3.4']
33
+ ruby-version: ['3.2', '3.3', '3.4']
34
34
 
35
35
  runs-on: ${{ matrix.os }}
36
36
  env:
data/.rubocop.yml CHANGED
@@ -7,6 +7,7 @@ inherit_from:
7
7
  plugins: rubocop-sorbet
8
8
 
9
9
  AllCops:
10
+ TargetRubyVersion: 3.2
10
11
  Exclude:
11
12
  - gen/template/**/*
12
13
  - vendor/**/*
data/Gemfile.lock CHANGED
@@ -1,8 +1,9 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- cli-kit (5.2.0)
4
+ cli-kit (5.3.0)
5
5
  cli-ui (~> 2.4)
6
+ logger (~> 1.7)
6
7
 
7
8
  GEM
8
9
  remote: https://rubygems.org/
data/cli-kit.gemspec CHANGED
@@ -26,8 +26,9 @@ Gem::Specification.new do |spec|
26
26
  spec.require_paths = ['lib']
27
27
 
28
28
  spec.add_runtime_dependency('cli-ui', '~> 2.4')
29
+ spec.add_runtime_dependency('logger', '~> 1.7')
29
30
 
30
- spec.required_ruby_version = '>= 3.0'
31
+ spec.required_ruby_version = '>= 3.2'
31
32
 
32
33
  spec.add_development_dependency('bundler', '~> 2.1')
33
34
  spec.add_development_dependency('minitest', '~> 5.0')
@@ -2,12 +2,131 @@
2
2
 
3
3
  require 'cli/kit'
4
4
  require 'fileutils'
5
+ require 'tempfile'
5
6
 
6
7
  module CLI
7
8
  module Kit
8
9
  class Config
9
10
  XDG_CONFIG_HOME = 'XDG_CONFIG_HOME'
10
11
 
12
+ # Raised when the config file cannot be written (e.g. read-only
13
+ # directory, read-only filesystem, nix/home-manager-managed symlink
14
+ # pointing into +/nix/store+).
15
+ #
16
+ # Inherits from SystemCallError so existing `rescue SystemCallError`
17
+ # handlers around +Config#set+ continue to match. The message contains
18
+ # only the keys that actually changed; unchanged keys (which may
19
+ # include sensitive values such as API tokens) are intentionally
20
+ # excluded so the failure message can never leak secrets through
21
+ # stderr or exception reports.
22
+ class ConfigWriteError < SystemCallError
23
+ class << self
24
+ # Ruby's +SystemCallError.===+ uses errno-based matching,
25
+ # which means +rescue ConfigWriteError+ would otherwise fail
26
+ # to catch a ConfigWriteError instance (it would match
27
+ # +Errno::EACCES+ or +Errno::EPERM+ instead, based on the
28
+ # wrapped errno). Override with plain class-hierarchy
29
+ # matching so +rescue ConfigWriteError+ works as callers
30
+ # expect. +rescue SystemCallError+ still matches via normal
31
+ # inheritance.
32
+ #: (untyped other) -> bool
33
+ def ===(other)
34
+ other.is_a?(self)
35
+ end
36
+
37
+ # +SystemCallError.new+ has a factory-style signature defined
38
+ # in Sorbet's stdlib RBI (+(String | Integer?) ->
39
+ # SystemCallError+) which doesn't match this subclass's
40
+ # four-argument constructor. Override +.new+ here so type
41
+ # checkers and callers see the real signature, and allocate
42
+ # the instance manually to bypass +SystemCallError+'s
43
+ # factory behaviour.
44
+ #: (String config_path, String old_content, String new_content, SystemCallError cause) -> ConfigWriteError
45
+ def new(config_path, old_content, new_content, cause)
46
+ instance = allocate
47
+ instance.__send__(:initialize, config_path, old_content, new_content, cause)
48
+ instance
49
+ end
50
+ end
51
+
52
+ #: String
53
+ attr_reader :config_path
54
+
55
+ #: String
56
+ attr_reader :old_content
57
+
58
+ #: String
59
+ attr_reader :new_content
60
+
61
+ # rubocop:disable Lint/MissingSuper
62
+ # +SystemCallError#initialize+ has a factory-style signature that
63
+ # cannot be invoked from a plain subclass (it expects a matching
64
+ # Errno constant on the class). We bypass it deliberately and
65
+ # initialize via Exception so we just get a message-only
66
+ # exception that +rescue SystemCallError+ still catches via
67
+ # inheritance. +super+ would not work here.
68
+ #: (String config_path, String old_content, String new_content, SystemCallError cause) -> void
69
+ def initialize(config_path, old_content, new_content, cause)
70
+ @config_path = config_path
71
+ @old_content = old_content
72
+ @new_content = new_content
73
+ @wrapped_errno = cause.errno
74
+ message = <<~MSG.rstrip
75
+ Could not write to #{config_path}: #{cause.message}
76
+
77
+ Attempted changes (unchanged keys omitted):
78
+ #{diff}
79
+ MSG
80
+ Exception.instance_method(:initialize).bind(self).call(message)
81
+ end
82
+ # rubocop:enable Lint/MissingSuper
83
+
84
+ # Preserve the original errno from the wrapped Errno so callers
85
+ # that inspect +errno+ continue to see the real underlying code.
86
+ #: -> Integer?
87
+ def errno
88
+ @wrapped_errno
89
+ end
90
+
91
+ # A line-by-line diff of only the sections/keys that changed
92
+ # between +old_content+ and +new_content+. Unchanged keys are
93
+ # omitted so that sensitive values stored elsewhere in the config
94
+ # are never included in the failure message.
95
+ #: -> String
96
+ def diff
97
+ old_ini = CLI::Kit::Ini.new(config: @old_content).tap(&:parse).ini
98
+ new_ini = CLI::Kit::Ini.new(config: @new_content).tap(&:parse).ini
99
+
100
+ lines = []
101
+ (old_ini.keys | new_ini.keys).each do |section|
102
+ old_section = old_ini[section] || {}
103
+ new_section = new_ini[section] || {}
104
+
105
+ changes = []
106
+ (old_section.keys | new_section.keys).each do |key|
107
+ old_val = old_section[key]
108
+ new_val = new_section[key]
109
+ next if old_val == new_val
110
+
111
+ changes << "- #{key} = #{old_val}" if old_val
112
+ changes << "+ #{key} = #{new_val}" if new_val
113
+ end
114
+ next if changes.empty?
115
+
116
+ prefix = if old_section.empty?
117
+ '+ '
118
+ elsif new_section.empty?
119
+ '- '
120
+ else
121
+ ' '
122
+ end
123
+ lines << "#{prefix}#{section}"
124
+ lines.concat(changes)
125
+ end
126
+ lines.join("\n")
127
+ end
128
+ end
129
+
11
130
  #: (tool_name: String) -> void
12
131
  def initialize(tool_name:)
13
132
  @tool_name = tool_name
@@ -129,8 +248,83 @@ module CLI
129
248
  all_configs.each do |section, sub_config|
130
249
  all_configs.delete(section) if sub_config.empty?
131
250
  end
132
- FileUtils.mkdir_p(File.dirname(file))
133
- File.write(file, to_s)
251
+
252
+ # Capture +file+ and +to_s+ up front so they are visible (and
253
+ # known to be non-nil) inside the rescue block below.
254
+ config_path = file
255
+ new_content = to_s
256
+
257
+ begin
258
+ # Always write atomically (tmpfile + rename) so readers never
259
+ # observe a partial write. If +config_path+ is a symlink (e.g.
260
+ # a nix/home-manager-managed config), resolve it so the rename
261
+ # replaces the real file at the end of the chain and leaves
262
+ # the managed symlink itself intact.
263
+ target_path = resolved_write_path(config_path)
264
+ target_dir = File.dirname(target_path)
265
+ FileUtils.mkdir_p(target_dir)
266
+ write_config_atomic(target_path, target_dir, new_content)
267
+ rescue Errno::EACCES, Errno::EPERM, Errno::EROFS => e
268
+ # +EROFS+ (read-only filesystem) is the canonical error raised
269
+ # when the underlying filesystem is mounted read-only — common
270
+ # for nix/home-manager configs that live in +/nix/store+. Wrap
271
+ # it the same way as +EACCES+/+EPERM+ so callers always see
272
+ # the diff and any +rescue ConfigWriteError+ handler matches.
273
+ old_content = read_config_for_diff(config_path)
274
+ raise(ConfigWriteError.new(config_path, old_content, new_content, e))
275
+ end
276
+ end
277
+
278
+ # Write +new_content+ into +config_path+ atomically, via a tmpfile
279
+ # in the same directory followed by rename. Readers never observe
280
+ # a partial write.
281
+ #: (String config_path, String config_dir, String new_content) -> void
282
+ def write_config_atomic(config_path, config_dir, new_content)
283
+ tmpfile = Tempfile.new([File.basename(config_path), '.tmp'], config_dir)
284
+ tmpfile_path = tmpfile.path #: as !nil
285
+ begin
286
+ tmpfile.write(new_content)
287
+ tmpfile.close
288
+ # Tempfile defaults to 0o600. Match the permissions a plain
289
+ # +File.write+ would have produced: preserve the existing
290
+ # mode when the config is being updated, and use the
291
+ # umask-adjusted default (matching +open(2)+ for new files)
292
+ # otherwise. This avoids silently tightening permissions on
293
+ # an existing config and avoids creating new configs with
294
+ # the more restrictive Tempfile default.
295
+ mode = if File.exist?(config_path)
296
+ File.stat(config_path).mode
297
+ else
298
+ 0o666 & ~File.umask
299
+ end
300
+ File.chmod(mode, tmpfile_path)
301
+ File.rename(tmpfile_path, config_path)
302
+ rescue Exception # rubocop:disable Lint/RescueException
303
+ tmpfile.close unless tmpfile.closed?
304
+ File.unlink(tmpfile_path) if File.exist?(tmpfile_path)
305
+ raise
306
+ end
307
+ end
308
+
309
+ #: (String config_path) -> String
310
+ def read_config_for_diff(config_path)
311
+ File.read(config_path)
312
+ rescue SystemCallError
313
+ ''
314
+ end
315
+
316
+ # Resolve +config_path+ through any symlinks so the atomic rename
317
+ # in +write_config_atomic+ replaces the real file at the end of
318
+ # the chain instead of clobbering the symlink with a regular file.
319
+ # For broken symlinks, fall back to +config_path+ itself so we
320
+ # still create a config there.
321
+ #: (String config_path) -> String
322
+ def resolved_write_path(config_path)
323
+ return config_path unless File.symlink?(config_path)
324
+
325
+ File.realpath(config_path)
326
+ rescue Errno::ENOENT
327
+ config_path
134
328
  end
135
329
  end
136
330
  end
@@ -6,11 +6,82 @@ class Exception
6
6
  # don't. I'm not sure why. If you, the reader, want to take some time to
7
7
  # figure it out, go ahead and refactor to that.
8
8
 
9
- #: -> bool
10
- def bug?
11
- true
9
+ # Fault represents who is responsible for an error. It is used to attribute
10
+ # exceptions to the right owner for observability and triage.
11
+ #
12
+ # @abstract
13
+ # @sealed
14
+ class Fault
15
+ # @abstract
16
+ #: -> String
17
+ def to_s = raise NotImplementedError, 'abstract method called'
18
+
19
+ # A bug in the tool itself (dev, tec, cli-kit, etc.).
20
+ class System < Fault
21
+ # @override
22
+ #: -> String
23
+ def to_s = 'system'
24
+ end
25
+ SYSTEM = System.new.freeze #: System
26
+
27
+ # The default fault for exceptions that have not been explicitly attributed.
28
+ # Nothing should reference this constant directly — it exists only as the
29
+ # default return value of Exception#fault. Use rubocop to enforce this.
30
+ #
31
+ # Inherits from System so that `fault.is_a?(System)` and checks like
32
+ # `bug?` (which uses `Fault::System ===`) treat unattributed errors as
33
+ # system errors by default.
34
+ class ImplicitlySystem < System
35
+ # @override
36
+ #: -> String
37
+ def to_s = 'implicitly_system'
38
+ end
39
+ IMPLICITLY_SYSTEM = ImplicitlySystem.new.freeze #: ImplicitlySystem
40
+
41
+ # A problem with the project's configuration (dev.yml, zone.nix, etc.).
42
+ class Project < Fault
43
+ # @override
44
+ #: -> String
45
+ def to_s = 'project'
46
+ end
47
+ PROJECT = Project.new.freeze #: Project
48
+
49
+ # User error: incorrect CLI usage, expired tokens, bad arguments, etc.
50
+ class User < Fault
51
+ # @override
52
+ #: -> String
53
+ def to_s = 'user'
54
+ end
55
+ USER = User.new.freeze #: User
56
+
57
+ # Not a real failure — e.g. user cancellation, interrupt, or conditions
58
+ # wildly out of anyone's control (no disk space). Doesn't require fixing
59
+ # or tracking.
60
+ class Terroir < Fault
61
+ # @override
62
+ #: -> String
63
+ def to_s = 'terroir'
64
+ end
65
+ TERROIR = Terroir.new.freeze #: Terroir
66
+ end
67
+
68
+ #: -> Fault
69
+ def fault = Fault::IMPLICITLY_SYSTEM
70
+
71
+ #: (Fault fault) -> void
72
+ def fault!(fault)
73
+ singleton_class.define_method(:fault) { fault }
74
+ end
75
+
76
+ #: (Fault fault) -> self
77
+ def with_fault(fault)
78
+ fault!(fault)
79
+ self
12
80
  end
13
81
 
82
+ #: -> bool
83
+ def bug? = (Fault::System === fault)
84
+
14
85
  #: -> bool
15
86
  def silent?
16
87
  false
@@ -26,3 +97,44 @@ class Exception
26
97
  singleton_class.define_method(:silent?) { silent }
27
98
  end
28
99
  end
100
+
101
+ # -- Custom fault defaults for Ruby stdlib exceptions --------------------------
102
+ #
103
+ # Normally, fault attribution belongs at the raise site (via `fault:` kwarg).
104
+ # These classes are different: Ruby itself raises them (e.g. the kernel sends
105
+ # SIGINT, the OS returns ENOSPC), so there is no raise site we control. We
106
+ # override the default fault at the class level instead.
107
+ #
108
+ # All of these are environmental conditions ("terroir") — nobody's fault,
109
+ # nothing to fix, shouldn't be reported as bugs.
110
+
111
+ class Interrupt # Ctrl-C / SIGINT
112
+ #: -> Exception::Fault
113
+ def fault = Exception::Fault::TERROIR
114
+ end
115
+
116
+ class SignalException # SIGTERM, SIGHUP, etc.
117
+ #: -> Exception::Fault
118
+ def fault = Exception::Fault::TERROIR
119
+ end
120
+
121
+ module Errno
122
+ class ENOSPC # disk full
123
+ #: -> Exception::Fault
124
+ def fault = Exception::Fault::TERROIR
125
+ end
126
+ end
127
+
128
+ module Errno
129
+ class EPIPE # broken pipe
130
+ #: -> Exception::Fault
131
+ def fault = Exception::Fault::TERROIR
132
+ end
133
+ end
134
+
135
+ module Errno
136
+ class EIO # I/O error (e.g. terminal disconnected)
137
+ #: -> Exception::Fault
138
+ def fault = Exception::Fault::TERROIR
139
+ end
140
+ end
@@ -84,7 +84,7 @@ module CLI
84
84
  rescue Interrupt => e # Ctrl-C
85
85
  # transform message, prevent bugsnag
86
86
  exc = e.exception('Interrupt')
87
- CLI::Kit.raise(exc, bug: false)
87
+ CLI::Kit.raise(exc, fault: Exception::Fault::TERROIR)
88
88
  rescue Errno::ENOSPC => e
89
89
  # transform message, prevent bugsnag
90
90
  message = if @tool_name
@@ -93,7 +93,7 @@ module CLI
93
93
  'Your disk is full - free space is required to operate'
94
94
  end
95
95
  exc = e.exception(message)
96
- CLI::Kit.raise(exc, bug: false)
96
+ CLI::Kit.raise(exc, fault: Exception::Fault::TERROIR)
97
97
  end
98
98
  # If SystemExit was raised, e.g. `exit()`, then
99
99
  # return whatever status is attached to the exception
@@ -114,12 +114,38 @@ module CLI
114
114
  e.bug? ? CLI::Kit::EXIT_BUG : CLI::Kit::EXIT_FAILURE_BUT_NOT_BUG
115
115
  end
116
116
 
117
+ #: (Exception? error) -> bool
118
+ def caused_by_benign_signal?(error)
119
+ current = error #: Exception?
120
+ while current
121
+ return true if current.is_a?(SignalException) && SIGNALS_THAT_ARENT_BUGS.include?(current.message)
122
+
123
+ current = current.cause
124
+ end
125
+ false
126
+ end
127
+
128
+ # Walks the cause chain looking for anything that should be reported as a
129
+ # bug. A terroir wrapper (e.g. Errno::EPIPE raised by a finalizer) can
130
+ # mask a real bug as its cause, so we can't only inspect the outermost
131
+ # exception. A non-benign signal (e.g. SIGSEGV) also counts as a bug.
132
+ #: (Exception? error) -> bool
133
+ def caused_by_bug?(error)
134
+ current = error #: Exception?
135
+ while current
136
+ return true if current.bug?
137
+ if current.is_a?(SignalException) && !SIGNALS_THAT_ARENT_BUGS.include?(current.message)
138
+ return true
139
+ end
140
+
141
+ current = current.cause
142
+ end
143
+ false
144
+ end
145
+
117
146
  #: (Exception? error) -> Exception?
118
147
  def exception_for_submission(error)
119
- # happens on normal non-error termination
120
- return if error.nil?
121
-
122
- return unless error.bug?
148
+ return if error.nil? || caused_by_benign_signal?(error) || !caused_by_bug?(error)
123
149
 
124
150
  case error
125
151
  when SignalException
@@ -2,6 +2,6 @@
2
2
 
3
3
  module CLI
4
4
  module Kit
5
- VERSION = '5.2.0'
5
+ VERSION = '5.3.0'
6
6
  end
7
7
  end
data/lib/cli/kit.rb CHANGED
@@ -109,7 +109,7 @@ module CLI
109
109
  # `depth` is used to trim leading elements of the backtrace. If you wrap
110
110
  # this method in your own wrapper, you'll want to pass `depth: 2`, for
111
111
  # example.
112
- #: (?(Class | String | Exception) exception, ?untyped string, ?Array[String]? array, ?cause: Exception?, ?bug: bool?, ?silent: bool?, ?depth: Integer) -> bot
112
+ #: (?(Class | String | Exception) exception, ?untyped string, ?Array[String]? array, ?cause: Exception?, ?bug: bool?, ?silent: bool?, ?fault: Exception::Fault?, ?depth: Integer) -> bot
113
113
  def raise(
114
114
  # default arguments
115
115
  exception = UNTYPED_NIL,
@@ -117,7 +117,7 @@ module CLI
117
117
  array = UNTYPED_NIL,
118
118
  cause: $ERROR_INFO,
119
119
  # new arguments
120
- bug: nil, silent: nil, depth: 1
120
+ bug: nil, silent: nil, fault: nil, depth: 1
121
121
  )
122
122
  k = Kernel #: as untyped
123
123
  if array
@@ -132,6 +132,7 @@ module CLI
132
132
  rescue Exception => e # rubocop:disable Lint/RescueException
133
133
  e.bug!(bug) unless bug.nil?
134
134
  e.silent!(silent) unless silent.nil?
135
+ e.fault!(fault) if fault
135
136
  Kernel.raise(e, cause: cause)
136
137
  end
137
138
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cli-kit
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.2.0
4
+ version: 5.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Burke Libbey
@@ -26,6 +26,20 @@ dependencies:
26
26
  - - "~>"
27
27
  - !ruby/object:Gem::Version
28
28
  version: '2.4'
29
+ - !ruby/object:Gem::Dependency
30
+ name: logger
31
+ requirement: !ruby/object:Gem::Requirement
32
+ requirements:
33
+ - - "~>"
34
+ - !ruby/object:Gem::Version
35
+ version: '1.7'
36
+ type: :runtime
37
+ prerelease: false
38
+ version_requirements: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - "~>"
41
+ - !ruby/object:Gem::Version
42
+ version: '1.7'
29
43
  - !ruby/object:Gem::Dependency
30
44
  name: bundler
31
45
  requirement: !ruby/object:Gem::Requirement
@@ -81,7 +95,9 @@ extra_rdoc_files: []
81
95
  files:
82
96
  - ".github/CODEOWNERS"
83
97
  - ".github/dependabot.yml"
98
+ - ".github/release.yml"
84
99
  - ".github/workflows/cla.yml"
100
+ - ".github/workflows/release.yml"
85
101
  - ".github/workflows/ruby.yml"
86
102
  - ".gitignore"
87
103
  - ".rubocop.sorbet.yml"
@@ -164,7 +180,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
164
180
  requirements:
165
181
  - - ">="
166
182
  - !ruby/object:Gem::Version
167
- version: '3.0'
183
+ version: '3.2'
168
184
  required_rubygems_version: !ruby/object:Gem::Requirement
169
185
  requirements:
170
186
  - - ">="