cli-kit 5.2.0 → 5.3.1

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: a71589e7905e5dc0152e3f34baada5c16289f98af365aa7f4f795cc2fee3c3d1
4
+ data.tar.gz: 796526fbab7044920aa42f5d42ca86c6f17a28c9285c344fa210dda10f0e8397
5
5
  SHA512:
6
- metadata.gz: c689fdcf5ae697be38b21ce60e973874c0d6a7f5c0eeaa085b8b93e635a14cbcd3d6af229d0dcb2c7f00084ca58845c3055c687cd6e7fbd4bc8a1902a5582bd2
7
- data.tar.gz: 153bf5c66de83e3d332944dfdc6fa9b1d52d4d5413ff80e4cac4b5aafbf0eddc888e22b2e50cf7914b43d1a90f56c23e2f41aaa7ec3987e7c51276092ad6f132
6
+ metadata.gz: 645d68f82d0147a7176b8a29d5d75f42848456528c06e3db2798823fc9cd732317f3935ddd5d4572e580d13186d6b17267d168f654ef57a19eaec5510074f64d
7
+ data.tar.gz: 5c9250e416b97184027b1742bfb88ee05873baf07d5d8bdfdcb6d5b24102eea0498914ee29ee3056a27dcb557879209c85c36f7c80ea53682b3b580cb887b773
@@ -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.1)
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,120 @@
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. Diagnostics contain
18
+ # only changed section/key names, never their values. Config contents
19
+ # are not retained on the exception, to keep them out of error reports.
20
+ class ConfigWriteError < SystemCallError
21
+ class << self
22
+ # Ruby's +SystemCallError.===+ uses errno-based matching,
23
+ # which means +rescue ConfigWriteError+ would otherwise fail
24
+ # to catch a ConfigWriteError instance (it would match
25
+ # +Errno::EACCES+ or +Errno::EPERM+ instead, based on the
26
+ # wrapped errno). Override with plain class-hierarchy
27
+ # matching so +rescue ConfigWriteError+ works as callers
28
+ # expect. +rescue SystemCallError+ still matches via normal
29
+ # inheritance.
30
+ #: (untyped other) -> bool
31
+ def ===(other)
32
+ other.is_a?(self)
33
+ end
34
+
35
+ # +SystemCallError.new+ has a factory-style signature defined
36
+ # in Sorbet's stdlib RBI (+(String | Integer?) ->
37
+ # SystemCallError+) which doesn't match this subclass's
38
+ # four-argument constructor. Override +.new+ here so type
39
+ # checkers and callers see the real signature, and allocate
40
+ # the instance manually to bypass +SystemCallError+'s
41
+ # factory behaviour.
42
+ #: (String config_path, Hash[String, Hash[String, String]] old_config, Hash[String, Hash[String, String]] new_config, SystemCallError cause) -> ConfigWriteError
43
+ def new(config_path, old_config, new_config, cause)
44
+ instance = allocate
45
+ instance.__send__(:initialize, config_path, old_config, new_config, cause)
46
+ instance
47
+ end
48
+ end
49
+
50
+ #: String
51
+ attr_reader :config_path
52
+
53
+ #: String
54
+ attr_reader :diff
55
+
56
+ # rubocop:disable Lint/MissingSuper
57
+ # +SystemCallError#initialize+ has a factory-style signature that
58
+ # cannot be invoked from a plain subclass (it expects a matching
59
+ # Errno constant on the class). We bypass it deliberately and
60
+ # initialize via Exception so we just get a message-only
61
+ # exception that +rescue SystemCallError+ still catches via
62
+ # inheritance. +super+ would not work here.
63
+ #: (String config_path, Hash[String, Hash[String, String]] old_config, Hash[String, Hash[String, String]] new_config, SystemCallError cause) -> void
64
+ def initialize(config_path, old_config, new_config, cause)
65
+ @config_path = config_path
66
+ @diff = build_diff(old_config, new_config)
67
+ @wrapped_errno = cause.errno
68
+ message = <<~MSG.rstrip
69
+ Could not write to #{config_path}: #{cause.message}
70
+
71
+ Attempted changes (values and unchanged keys omitted):
72
+ #{diff}
73
+ MSG
74
+ Exception.instance_method(:initialize).bind(self).call(message)
75
+ end
76
+ # rubocop:enable Lint/MissingSuper
77
+
78
+ # Preserve the original errno from the wrapped Errno so callers
79
+ # that inspect +errno+ continue to see the real underlying code.
80
+ #: -> Integer?
81
+ def errno
82
+ @wrapped_errno
83
+ end
84
+
85
+ private
86
+
87
+ #: (Hash[String, Hash[String, String]] old_config, Hash[String, Hash[String, String]] new_config) -> String
88
+ def build_diff(old_config, new_config)
89
+ lines = []
90
+ (old_config.keys | new_config.keys).each do |section|
91
+ old_section = old_config[section] || {}
92
+ new_section = new_config[section] || {}
93
+
94
+ changes = []
95
+ (old_section.keys | new_section.keys).each do |key|
96
+ old_val = old_section[key]
97
+ new_val = new_section[key]
98
+ next if old_val == new_val
99
+
100
+ changes << "- #{key}" if old_val
101
+ changes << "+ #{key}" if new_val
102
+ end
103
+ next if changes.empty?
104
+
105
+ prefix = if old_section.empty?
106
+ '+ '
107
+ elsif new_section.empty?
108
+ '- '
109
+ else
110
+ ' '
111
+ end
112
+ lines << "#{prefix}#{section}"
113
+ lines.concat(changes)
114
+ end
115
+ lines.join("\n")
116
+ end
117
+ end
118
+
11
119
  #: (tool_name: String) -> void
12
120
  def initialize(tool_name:)
13
121
  @tool_name = tool_name
@@ -129,8 +237,80 @@ module CLI
129
237
  all_configs.each do |section, sub_config|
130
238
  all_configs.delete(section) if sub_config.empty?
131
239
  end
132
- FileUtils.mkdir_p(File.dirname(file))
133
- File.write(file, to_s)
240
+
241
+ # Capture +file+ and +to_s+ up front so they are visible (and
242
+ # known to be non-nil) inside the rescue block below.
243
+ config_path = file
244
+ new_content = to_s
245
+
246
+ begin
247
+ # Always write atomically (tmpfile + rename) so readers never
248
+ # observe a partial write. If +config_path+ is a symlink (e.g.
249
+ # a nix/home-manager-managed config), resolve it so the rename
250
+ # replaces the real file at the end of the chain and leaves
251
+ # the managed symlink itself intact.
252
+ target_path = resolved_write_path(config_path)
253
+ target_dir = File.dirname(target_path)
254
+ FileUtils.mkdir_p(target_dir)
255
+ write_config_atomic(target_path, target_dir, new_content)
256
+ rescue Errno::EACCES, Errno::EPERM, Errno::EROFS => e
257
+ # +EROFS+ (read-only filesystem) is the canonical error raised
258
+ # when the underlying filesystem is mounted read-only — common
259
+ # for nix/home-manager configs that live in +/nix/store+. Wrap
260
+ # it the same way as +EACCES+/+EPERM+ so callers always see
261
+ # the diff and any +rescue ConfigWriteError+ handler matches.
262
+ old_config = read_config_for_diff(config_path)
263
+ raise(ConfigWriteError.new(config_path, old_config, all_configs, e))
264
+ end
265
+ end
266
+
267
+ # Write +new_content+ into +config_path+ atomically, via a tmpfile
268
+ # in the same directory followed by rename. Readers never observe
269
+ # a partial write.
270
+ #: (String config_path, String config_dir, String new_content) -> void
271
+ def write_config_atomic(config_path, config_dir, new_content)
272
+ tmpfile = Tempfile.new([File.basename(config_path), '.tmp'], config_dir)
273
+ tmpfile_path = tmpfile.path #: as !nil
274
+ begin
275
+ tmpfile.write(new_content)
276
+ tmpfile.close
277
+ # Configs may contain credentials. Keep them private to the
278
+ # owner, including when replacing a previously shared file.
279
+ # Preserve stricter owner permissions on existing files and
280
+ # respect the umask when creating new ones.
281
+ mode = if File.exist?(config_path)
282
+ File.stat(config_path).mode & 0o600
283
+ else
284
+ 0o600 & ~File.umask
285
+ end
286
+ File.chmod(mode, tmpfile_path)
287
+ File.rename(tmpfile_path, config_path)
288
+ rescue Exception # rubocop:disable Lint/RescueException
289
+ tmpfile.close unless tmpfile.closed?
290
+ File.unlink(tmpfile_path) if File.exist?(tmpfile_path)
291
+ raise
292
+ end
293
+ end
294
+
295
+ #: (String config_path) -> Hash[String, Hash[String, String]]
296
+ def read_config_for_diff(config_path)
297
+ CLI::Kit::Ini.new(config: File.read(config_path)).tap(&:parse).ini
298
+ rescue SystemCallError
299
+ {}
300
+ end
301
+
302
+ # Resolve +config_path+ through any symlinks so the atomic rename
303
+ # in +write_config_atomic+ replaces the real file at the end of
304
+ # the chain instead of clobbering the symlink with a regular file.
305
+ # For broken symlinks, fall back to +config_path+ itself so we
306
+ # still create a config there.
307
+ #: (String config_path) -> String
308
+ def resolved_write_path(config_path)
309
+ return config_path unless File.symlink?(config_path)
310
+
311
+ File.realpath(config_path)
312
+ rescue Errno::ENOENT
313
+ config_path
134
314
  end
135
315
  end
136
316
  end
@@ -6,11 +6,83 @@ 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
+ # @sealed
21
+ class System < Fault
22
+ # @override
23
+ #: -> String
24
+ def to_s = 'system'
25
+ end
26
+ SYSTEM = System.new.freeze #: System
27
+
28
+ # The default fault for exceptions that have not been explicitly attributed.
29
+ # Nothing should reference this constant directly — it exists only as the
30
+ # default return value of Exception#fault. Use rubocop to enforce this.
31
+ #
32
+ # Inherits from System so that `fault.is_a?(System)` and checks like
33
+ # `bug?` (which uses `Fault::System ===`) treat unattributed errors as
34
+ # system errors by default.
35
+ class ImplicitlySystem < System
36
+ # @override
37
+ #: -> String
38
+ def to_s = 'implicitly_system'
39
+ end
40
+ IMPLICITLY_SYSTEM = ImplicitlySystem.new.freeze #: ImplicitlySystem
41
+
42
+ # A problem with the project's configuration (dev.yml, zone.nix, etc.).
43
+ class Project < Fault
44
+ # @override
45
+ #: -> String
46
+ def to_s = 'project'
47
+ end
48
+ PROJECT = Project.new.freeze #: Project
49
+
50
+ # User error: incorrect CLI usage, expired tokens, bad arguments, etc.
51
+ class User < Fault
52
+ # @override
53
+ #: -> String
54
+ def to_s = 'user'
55
+ end
56
+ USER = User.new.freeze #: User
57
+
58
+ # Not a real failure — e.g. user cancellation, interrupt, or conditions
59
+ # wildly out of anyone's control (no disk space). Doesn't require fixing
60
+ # or tracking.
61
+ class Terroir < Fault
62
+ # @override
63
+ #: -> String
64
+ def to_s = 'terroir'
65
+ end
66
+ TERROIR = Terroir.new.freeze #: Terroir
67
+ end
68
+
69
+ #: -> Fault
70
+ def fault = Fault::IMPLICITLY_SYSTEM
71
+
72
+ #: (Fault fault) -> void
73
+ def fault!(fault)
74
+ singleton_class.define_method(:fault) { fault }
75
+ end
76
+
77
+ #: (Fault fault) -> self
78
+ def with_fault(fault)
79
+ fault!(fault)
80
+ self
12
81
  end
13
82
 
83
+ #: -> bool
84
+ def bug? = (Fault::System === fault)
85
+
14
86
  #: -> bool
15
87
  def silent?
16
88
  false
@@ -26,3 +98,44 @@ class Exception
26
98
  singleton_class.define_method(:silent?) { silent }
27
99
  end
28
100
  end
101
+
102
+ # -- Custom fault defaults for Ruby stdlib exceptions --------------------------
103
+ #
104
+ # Normally, fault attribution belongs at the raise site (via `fault:` kwarg).
105
+ # These classes are different: Ruby itself raises them (e.g. the kernel sends
106
+ # SIGINT, the OS returns ENOSPC), so there is no raise site we control. We
107
+ # override the default fault at the class level instead.
108
+ #
109
+ # All of these are environmental conditions ("terroir") — nobody's fault,
110
+ # nothing to fix, shouldn't be reported as bugs.
111
+
112
+ class Interrupt # Ctrl-C / SIGINT
113
+ #: -> Exception::Fault
114
+ def fault = Exception::Fault::TERROIR
115
+ end
116
+
117
+ class SignalException # SIGTERM, SIGHUP, etc.
118
+ #: -> Exception::Fault
119
+ def fault = Exception::Fault::TERROIR
120
+ end
121
+
122
+ module Errno
123
+ class ENOSPC # disk full
124
+ #: -> Exception::Fault
125
+ def fault = Exception::Fault::TERROIR
126
+ end
127
+ end
128
+
129
+ module Errno
130
+ class EPIPE # broken pipe
131
+ #: -> Exception::Fault
132
+ def fault = Exception::Fault::TERROIR
133
+ end
134
+ end
135
+
136
+ module Errno
137
+ class EIO # I/O error (e.g. terminal disconnected)
138
+ #: -> Exception::Fault
139
+ def fault = Exception::Fault::TERROIR
140
+ end
141
+ 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.1'
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.1
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
  - - ">="