invar 0.7.0 → 0.9.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: 62fcbaf0763469c256af4915bdae35ded8a2691c647356a24bd6e68635bf3030
4
- data.tar.gz: a54dce1295a10292f208318faeefe43b0c279934154f2c6f112e5b22278126a6
3
+ metadata.gz: c7bb218e77d7ce1acc82bf0160cc7386529f7e32e5a56cd2eea608bd2a191ad6
4
+ data.tar.gz: 6b209fa6041be75736d28783d996c8fe0ee1e529bdde7d94dcb8e1bbda23423d
5
5
  SHA512:
6
- metadata.gz: 5c0d9b6ae497f3e1ca0ba6701cdc99c1b8408ceabcdfd1970b67c465576f2cd0439caf92fc6413eb40db21d7289d5f5cab495adeb3499bdeef3c3a4da43a0e2b
7
- data.tar.gz: 206b350e643afabd1892db2cf393b375e7555fe85f65d56705c718757ce172d4360fb9f6c4102b70da64223ee42a9d0ef960298240bce9e755b91512f7d1406a
6
+ metadata.gz: da15b60c94678b1dc9a5e002b3f4bba02d7b2e1a77923483a2a5427839b0cff0fd0e137ce9e1ca847e6b493bb134be97f419a20bef7903adabe8de8b0f163ae9
7
+ data.tar.gz: 2d60ab3a7f5374478fe2ab872083ef4159620097e0cb65575eb6e6956b669c9fceaa86a39c085c637df125cebfc33f9ba52170a1a2389dce713de9d77da09c9d
data/.rubocop.yml CHANGED
@@ -4,17 +4,6 @@ AllCops:
4
4
  Exclude:
5
5
  - 'bin/*'
6
6
 
7
- TargetRubyVersion: 2.7
8
-
9
- Layout/LineLength:
10
- Exclude:
11
- - 'spec/**/*.rb'
12
-
13
- # setting to 6 to match RubyMine autoformat
14
- Layout/FirstArrayElementIndentation:
15
- IndentationWidth: 6
16
-
17
-
18
7
  # rspec blocks are huge by design
19
8
  Metrics/BlockLength:
20
9
  Exclude:
@@ -23,4 +12,3 @@ Metrics/BlockLength:
23
12
  Metrics/ModuleLength:
24
13
  Exclude:
25
14
  - 'spec/**/*.rb'
26
-
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- ruby-2.7.8
1
+ ruby-3.1.4
data/.simplecov ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ SimpleCov.start do
4
+ coverage_dir '.coverage'
5
+ enable_coverage :branch
6
+
7
+ root __dir__
8
+ end
data/Gemfile CHANGED
@@ -2,14 +2,19 @@
2
2
 
3
3
  source 'https://rubygems.org'
4
4
 
5
+ # Gem's dependencies in invar.gemspec
6
+ gemspec
7
+
5
8
  group :development do
6
9
  gem 'bundler', '~> 2.3'
7
- gem 'fakefs', '~> 2.5'
8
10
  gem 'rake', '~> 13.0'
9
- gem 'rspec', '~> 3.12'
10
- gem 'simplecov', '~> 0.22'
11
+ gem 'rubocop', '~> 1.56'
12
+ gem 'rubocop-performance', '~> 1.19'
11
13
  gem 'yard', '~> 0.9'
12
14
  end
13
15
 
14
- # Specify your gem's dependencies in invar.gemspec
15
- gemspec
16
+ group :test do
17
+ gem 'fakefs', '~> 2.5'
18
+ gem 'rspec', '~> 3.12'
19
+ gem 'simplecov', '~> 0.22'
20
+ end
data/README.md CHANGED
@@ -26,7 +26,8 @@ variables have some downsides:
26
26
  * Cannot be easily checked against a schema for early error detection
27
27
  * Ruby's core ENV does not accept symbols as keys (a minor nuisance, but it counts)
28
28
 
29
- > **Fun Fact:** Invar is named for an [alloy used in clockmaking](https://en.wikipedia.org/wiki/Invar) - it's short for "**invar**iable".
29
+ > **Fun Fact:** Invar is named for an [alloy used in clockmaking](https://en.wikipedia.org/wiki/Invar) - it's short
30
+ > for "**invar**iable".
30
31
 
31
32
  ### Features
32
33
 
@@ -216,6 +217,10 @@ To open the file your system's default text editor (eg. nano):
216
217
 
217
218
  bundle exec rake invar:config
218
219
 
220
+ > **Automation tip** You can provide new content over STDIN
221
+ >
222
+ > bundle exec rake invar:config < config_template.yml
223
+
219
224
  #### Edit Secrets File
220
225
 
221
226
  To edit the secrets file, run this and provide the file's encryption key:
@@ -223,7 +228,27 @@ To edit the secrets file, run this and provide the file's encryption key:
223
228
  bundle exec rake invar:secrets
224
229
 
225
230
  The file will be decrypted and opened in your default editor like the config file. Once you have exited the editor, it
226
- will be re-encrypted. Remember to save your changes!
231
+ will be re-encrypted. **Remember to save your changes!**
232
+
233
+ > **Automation tip** You can set the current encryption key in the `LOCKBOX_MASTER_KEY` environment variable:
234
+ >
235
+ > LOCKBOX_MASTER_KEY=sooper_sekret_key_here bundle exec rake invar:secrets
236
+
237
+ > **Automation tip** Like invar:config, you can provide new content over STDIN
238
+ >
239
+ > bundle exec rake invar:secrets < secrets_template.yml
240
+
241
+ #### Rotating the Secrets File Encryption Key
242
+
243
+ To re-encrypt the secrets file, run this and provide the file's current encryption key:
244
+
245
+ bundle exec rake invar:rotate
246
+
247
+ A new encryption key will be generated just like using `invar:init`.
248
+
249
+ > **Automation tip** you can set the current encryption key in the `LOCKBOX_MASTER_KEY` environment variable:
250
+ >
251
+ > LOCKBOX_MASTER_KEY=sooper_sekret_key_here bundle exec rake invar:rotate
227
252
 
228
253
  ### Code
229
254
 
data/RELEASE_NOTES.md CHANGED
@@ -13,6 +13,49 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
13
13
 
14
14
  ### Minor Changes
15
15
 
16
+ * none
17
+
18
+ ### Bugfixes
19
+
20
+ * none
21
+
22
+ ## [0.9.0] - 2023-09-24
23
+
24
+ ### Major Changes
25
+
26
+ * none
27
+
28
+ ### Minor Changes
29
+
30
+ * Added Rake task for secrets file key rotation
31
+ * Added ability for invar:config and invar:secrets to take replacement content over `STDIN` instead of live editing
32
+
33
+ ### Bugfixes
34
+
35
+ * none
36
+
37
+ ## [0.8.0] - 2023-09-13
38
+
39
+ ### Major Changes
40
+
41
+ * none
42
+
43
+ ### Minor Changes
44
+
45
+ * Increased minimum Ruby to 3.1
46
+
47
+ ### Bugfixes
48
+
49
+ * none
50
+
51
+ ## [0.7.0] - 2023-08-06
52
+
53
+ ### Major Changes
54
+
55
+ * none
56
+
57
+ ### Minor Changes
58
+
16
59
  * Tweaked file missing error messages
17
60
  * Extracted testing helper features into a separate module
18
61
  * Added support for multiple after_load hooks
data/invar.gemspec CHANGED
@@ -27,7 +27,7 @@ Gem::Specification.new do |spec|
27
27
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
28
28
  spec.require_paths = ['lib']
29
29
 
30
- spec.required_ruby_version = '>= 2.7'
30
+ spec.required_ruby_version = '>= 3.1'
31
31
 
32
32
  spec.add_dependency 'dry-schema', '>= 1.0'
33
33
  spec.add_dependency 'lockbox', '>= 1.0'
data/lib/invar/errors.rb CHANGED
@@ -1,5 +1,7 @@
1
- module Invar
1
+ # frozen_string_literal: true
2
2
 
3
+ # :nodoc:
4
+ module Invar
3
5
  # Raised when no config file can be found within the search paths.
4
6
  class MissingConfigFileError < RuntimeError
5
7
  end
@@ -41,11 +43,11 @@ module Invar
41
43
  require 'invar/test'
42
44
  HINT
43
45
 
44
- PRETEND_MSG = "Method 'Invar::Scope#pretend' is defined in the testing extension. #{ HINT }"
45
- HOOK_MSG = "Methods 'Invar.after_load and clear_hooks' are defined in the testing extension. #{ HINT }"
46
+ PRETEND_MSG = "Method 'Invar::Scope#pretend' is defined in the testing extension. #{ HINT }".freeze
47
+ HOOK_MSG = "Methods 'Invar.after_load and clear_hooks' are defined in the testing extension. #{ HINT }".freeze
46
48
  end
47
49
 
48
50
  # Raised when schema validation fails
49
51
  class SchemaValidationError < RuntimeError
50
52
  end
51
- end
53
+ end
@@ -9,7 +9,7 @@ module Invar
9
9
  # Verifies a file is secure
10
10
  class PrivateFile
11
11
  extend Forwardable
12
- def_delegators :@delegate_sd_obj, :stat, :to_s, :basename, :==, :chmod
12
+ def_delegators :@delegate_sd_obj, :stat, :to_s, :basename, :dirname, :extname, :==, :chmod, :rename, :write
13
13
 
14
14
  # Mask for limiting to the lowest three octal digits (which store permissions)
15
15
  PERMISSIONS_MASK = 0o777
@@ -30,8 +30,8 @@ module Invar
30
30
  #
31
31
  # @param (see #define)
32
32
  # @see Tasks#define
33
- def self.define(**args, &block)
34
- new.define(**args, &block)
33
+ def self.define(...)
34
+ new.define(...)
35
35
  end
36
36
 
37
37
  # Defines helpful Rake tasks for the given namespace.
@@ -51,7 +51,7 @@ module Invar
51
51
  define_init_task(app_namespace)
52
52
 
53
53
  define_config_task(app_namespace)
54
- define_secrets_task(app_namespace)
54
+ define_secrets_tasks(app_namespace)
55
55
 
56
56
  define_info_tasks(app_namespace)
57
57
  end
@@ -62,8 +62,8 @@ module Invar
62
62
  task :init, [:mode] do |_task, args|
63
63
  mode = args.mode
64
64
 
65
- config = ::Invar::Rake::Tasks::ConfigTask.new(app_namespace)
66
- secrets = ::Invar::Rake::Tasks::SecretTask.new(app_namespace)
65
+ config = ::Invar::Rake::Tasks::ConfigFileHandler.new(app_namespace)
66
+ secrets = ::Invar::Rake::Tasks::SecretsFileHandler.new(app_namespace)
67
67
 
68
68
  case mode
69
69
  when 'config'
@@ -71,7 +71,7 @@ module Invar
71
71
  when 'secrets'
72
72
  config = nil
73
73
  else
74
- raise "unknown mode #{ mode }. Must be one of 'config' or 'secrets'" unless mode.nil?
74
+ raise ArgumentError, "unknown mode '#{ mode }'. Must be one of 'config' or 'secrets'" unless mode.nil?
75
75
  end
76
76
 
77
77
  assert_init_conditions(config&.file_path, secrets&.file_path)
@@ -84,39 +84,44 @@ module Invar
84
84
  def define_config_task(app_namespace)
85
85
  desc 'Edit the config in your default editor'
86
86
  task :configs do
87
- ::Invar::Rake::Tasks::ConfigTask.new(app_namespace).edit
87
+ ::Invar::Rake::Tasks::ConfigFileHandler.new(app_namespace).edit
88
88
  end
89
89
 
90
90
  # alias
91
91
  task config: ['configs']
92
92
  end
93
93
 
94
- def define_secrets_task(app_namespace)
94
+ def define_secrets_tasks(app_namespace)
95
95
  desc 'Edit the encrypted secrets file in your default editor'
96
96
  task :secrets do
97
- ::Invar::Rake::Tasks::SecretTask.new(app_namespace).edit
97
+ ::Invar::Rake::Tasks::SecretsFileHandler.new(app_namespace).edit
98
98
  end
99
99
 
100
100
  # alias
101
101
  task secret: ['secrets']
102
+
103
+ desc 'Encrypt the secrets file with a new generated key'
104
+ task :rotate do
105
+ ::Invar::Rake::Tasks::SecretsFileHandler.new(app_namespace).rotate
106
+ end
102
107
  end
103
108
 
104
109
  def define_info_tasks(app_namespace)
105
110
  desc 'Show directories to be searched for the given namespace'
106
111
  task :paths do
107
- ::Invar::Rake::Tasks::StateTask.new(app_namespace).show_paths
112
+ ::Invar::Rake::Tasks::StatusHandler.new(app_namespace).show_paths
108
113
  end
109
114
  end
110
115
 
111
116
  def assert_init_conditions(config_file, secrets_file)
112
117
  return unless config_file&.exist? || secrets_file&.exist?
113
118
 
114
- msg = if !config_file&.exist?
119
+ msg = if !config_file.exist?
115
120
  <<~MSG
116
121
  Abort: Secrets file already exists (#{ secrets_file })
117
122
  Run this to init only the config file: bundle exec rake tasks invar:init[config]
118
123
  MSG
119
- elsif !secrets_file&.exist?
124
+ elsif !secrets_file.exist?
120
125
  <<~MSG
121
126
  Abort: Config file already exists (#{ config_file })
122
127
  Run this to init only the secrets file: bundle exec rake tasks invar:init[secrets]
@@ -133,7 +138,7 @@ module Invar
133
138
  end
134
139
 
135
140
  # Tasks that use a namespace for file searching
136
- class NamespacedTask
141
+ class NamespacedFileTask
137
142
  def initialize(namespace)
138
143
  @locator = FileLocator.new(namespace)
139
144
  end
@@ -150,11 +155,9 @@ module Invar
150
155
  end
151
156
 
152
157
  # Configuration file actions.
153
- class ConfigTask < NamespacedTask
158
+ class ConfigFileHandler < NamespacedFileTask
154
159
  # Creates a config file in the appropriate location
155
160
  def create
156
- raise 'File already exists' if file_path.exist?
157
-
158
161
  config_dir.mkpath
159
162
  file_path.write CONFIG_TEMPLATE
160
163
  file_path.chmod 0o600
@@ -164,79 +167,108 @@ module Invar
164
167
 
165
168
  # Edits the existing config file in the appropriate location
166
169
  def edit
167
- configs_file = begin
168
- @locator.find('config.yml')
169
- rescue ::Invar::FileLocator::FileNotFoundError => e
170
- warn <<~ERR
171
- Abort: #{ e.message }. Searched in: #{ @locator.search_paths.join(', ') }
172
- #{ CREATE_SUGGESTION }
173
- ERR
174
- exit 1
175
- end
176
-
177
- system(ENV.fetch('EDITOR', 'editor'), configs_file.to_s, exception: true)
178
-
179
- warn "File saved to: #{ configs_file }"
170
+ content = $stdin.tty? ? nil : $stdin.read
171
+ file_path = configs_file
172
+
173
+ if content
174
+ file_path.write content
175
+ else
176
+ system ENV.fetch('EDITOR', 'editor'), file_path.to_s, exception: true
177
+ end
178
+
179
+ warn "File saved to: #{ file_path }"
180
180
  end
181
181
 
182
182
  private
183
183
 
184
+ def configs_file
185
+ @locator.find 'config.yml'
186
+ rescue ::Invar::FileLocator::FileNotFoundError => e
187
+ warn <<~ERR
188
+ Abort: #{ e.message }. Searched in: #{ @locator.search_paths.join(', ') }
189
+ #{ CREATE_SUGGESTION }
190
+ ERR
191
+ exit 1
192
+ end
193
+
184
194
  def filename
185
195
  'config.yml'
186
196
  end
187
197
  end
188
198
 
189
199
  # Secrets file actions.
190
- class SecretTask < NamespacedTask
200
+ class SecretsFileHandler < NamespacedFileTask
191
201
  # Instructions hint for how to handle secret keys.
192
202
  SECRETS_INSTRUCTIONS = <<~INST
193
- Save this key to a secure password manager. You will need it to edit the secrets.yml file.
203
+ Generated key. Save this key to a secure password manager, you will need it to edit the secrets.yml file:
194
204
  INST
195
205
 
196
- # Creates a new encrypted secrets file and prints the generated encryption key to STDOUT
197
- def create
198
- raise 'File already exists' if file_path.exist?
206
+ SWAP_EXT = 'tmp'
199
207
 
208
+ # Creates a new encrypted secrets file and prints the generated encryption key to STDOUT
209
+ def create(content: SECRETS_TEMPLATE)
200
210
  encryption_key = Lockbox.generate_key
201
211
 
202
212
  write_encrypted_file(file_path,
203
213
  encryption_key: encryption_key,
204
- content: SECRETS_TEMPLATE,
214
+ content: content,
205
215
  permissions: PrivateFile::DEFAULT_PERMISSIONS)
206
216
 
207
- warn "Created file: #{ file_path }"
208
-
209
217
  warn SECRETS_INSTRUCTIONS
210
- warn 'Generated key is:'
211
218
  puts encryption_key
212
219
  end
213
220
 
214
- # Opens an editor for the decrypted contents of the secrets file. After closing the editor, the file will be
215
- # updated with the new encrypted contents.
221
+ # Updates the file with new content.
222
+ #
223
+ # Either the content is provided over STDIN or the default editor is opened with the decrypted contents of
224
+ # the secrets file. After closing the editor, the file will be updated with the new encrypted contents.
216
225
  def edit
217
- secrets_file = begin
218
- @locator.find('secrets.yml')
219
- rescue ::Invar::FileLocator::FileNotFoundError => e
220
- warn <<~ERR
221
- Abort: #{ e.message }. Searched in: #{ @locator.search_paths.join(', ') }
222
- #{ CREATE_SUGGESTION }
223
- ERR
224
- exit 1
225
- end
226
-
227
- edit_encrypted_file(secrets_file)
226
+ content = $stdin.tty? ? nil : $stdin.read
227
+
228
+ edit_encrypted_file(secrets_file, content: content)
228
229
 
229
230
  warn "File saved to #{ secrets_file }"
230
231
  end
231
232
 
233
+ def rotate
234
+ file_path = secrets_file
235
+
236
+ decrypted = read_encrypted_file(file_path, encryption_key: determine_key(file_path))
237
+
238
+ swap_file = file_path.dirname / [file_path.basename, SWAP_EXT].join('.')
239
+ file_path.rename swap_file
240
+
241
+ begin
242
+ create content: decrypted
243
+ swap_file.delete
244
+ rescue StandardError
245
+ swap_file.rename file_path.to_s
246
+ end
247
+ end
248
+
232
249
  private
233
250
 
251
+ def secrets_file
252
+ @locator.find 'secrets.yml'
253
+ rescue ::Invar::FileLocator::FileNotFoundError => e
254
+ warn <<~ERR
255
+ Abort: #{ e.message }. Searched in: #{ @locator.search_paths.join(', ') }
256
+ #{ CREATE_SUGGESTION }
257
+ ERR
258
+ exit 1
259
+ end
260
+
234
261
  def filename
235
262
  'secrets.yml'
236
263
  end
237
264
 
265
+ def read_encrypted_file(file_path, encryption_key:)
266
+ lockbox = build_lockbox(encryption_key)
267
+ lockbox.decrypt(file_path.binread)
268
+ end
269
+
238
270
  def write_encrypted_file(file_path, encryption_key:, content:, permissions: nil)
239
- lockbox = Lockbox.new(key: encryption_key)
271
+ lockbox = build_lockbox(encryption_key)
240
272
 
241
273
  encrypted_data = lockbox.encrypt(content)
242
274
 
@@ -244,23 +276,27 @@ module Invar
244
276
  # TODO: replace File.opens with photo_path.binwrite(uri.data) once FakeFS can handle it
245
277
  File.open(file_path.to_s, 'wb') { |f| f.write encrypted_data }
246
278
  file_path.chmod permissions if permissions
279
+
280
+ warn "Saved file: #{ file_path }"
247
281
  end
248
282
 
249
- def edit_encrypted_file(file_path)
283
+ def edit_encrypted_file(file_path, content: nil)
250
284
  encryption_key = determine_key(file_path)
251
285
 
252
- lockbox = build_lockbox(encryption_key)
286
+ content ||= invoke_editor(file_path, encryption_key: encryption_key)
287
+
288
+ write_encrypted_file(file_path, encryption_key: encryption_key, content: content)
289
+ end
253
290
 
254
- file_str = Tempfile.create(file_path.basename.to_s) do |tmp_file|
255
- decrypted = lockbox.decrypt(file_path.binread)
291
+ def invoke_editor(file_path, encryption_key:)
292
+ Tempfile.create(file_path.basename.to_s) do |tmp_file|
293
+ decrypted = read_encrypted_file(file_path, encryption_key: encryption_key)
256
294
 
257
295
  tmp_file.write(decrypted)
258
296
  tmp_file.rewind # rewind needed because file does not get closed after write
259
297
  system(ENV.fetch('EDITOR', 'editor'), tmp_file.path, exception: true)
260
298
  tmp_file.read
261
299
  end
262
-
263
- write_encrypted_file(file_path, encryption_key: encryption_key, content: file_str)
264
300
  end
265
301
 
266
302
  def determine_key(file_path)
@@ -282,7 +318,7 @@ module Invar
282
318
  end
283
319
 
284
320
  # General status tasks
285
- class StateTask < NamespacedTask
321
+ class StatusHandler < NamespacedFileTask
286
322
  # Prints the current paths to be searched in
287
323
  def show_paths
288
324
  warn @locator.search_paths.join("\n")
data/lib/invar/reality.rb CHANGED
@@ -78,8 +78,9 @@ module Invar
78
78
  begin
79
79
  @secrets = Scope.new(load_secrets(locator, decryption_keyfile || DEFAULT_KEY_FILE_NAME))
80
80
  rescue FileLocator::FileNotFoundError
81
+ hint = "Create encrypted secrets.yml in one of these locations: #{ search_paths }"
81
82
  raise MissingSecretsFileError,
82
- "No Invar secrets file found. Create encrypted secrets.yml in one of these locations: #{ search_paths }"
83
+ "No Invar secrets file found. #{ hint }"
83
84
  end
84
85
 
85
86
  freeze
data/lib/invar/scope.rb CHANGED
@@ -26,11 +26,9 @@ module Invar
26
26
  alias [] fetch
27
27
 
28
28
  def method_missing(symbol, *args)
29
- if symbol == :pretend
30
- raise ::Invar::ImmutableRealityError, ::Invar::ImmutableRealityError::PRETEND_MSG
31
- else
32
- super
33
- end
29
+ raise ::Invar::ImmutableRealityError, ::Invar::ImmutableRealityError::PRETEND_MSG if symbol == :pretend
30
+
31
+ super
34
32
  end
35
33
 
36
34
  # Returns a hash representation of this scope and subscopes.
data/lib/invar/test.rb CHANGED
@@ -1,16 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Specifically not calling require 'invar' here to force applications to need to include it themselves,
4
- # preventing the situation where test suites include application dependencies for them and breaking when
3
+ # Specifically not calling require 'invar' in this file to force applications to need to include it themselves,
4
+ # avoiding the situation where test suites include application dependencies for them and breaking when
5
5
  # the app is run without the test suite
6
6
 
7
+ # :nodoc:
7
8
  module Invar
8
9
  # Namespace module containing mixins for parts of the main gem to enable modifications and data control
9
10
  # in automated testing, while remaining immutable in the main gem and real runtime usage.
10
11
  module TestExtension
12
+ # Methods to extend the Reality class
11
13
  module RealityMethods
12
14
  class << self
13
15
  attr_accessor :__after_load_hooks__
16
+
14
17
  RealityMethods.__after_load_hooks__ = []
15
18
  end
16
19
 
data/lib/invar/version.rb CHANGED
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Invar
4
4
  # Current version of the gem
5
- VERSION = '0.7.0'
5
+ VERSION = '0.9.0'
6
6
  end
data/lib/invar.rb CHANGED
@@ -18,9 +18,9 @@ module Invar
18
18
  def method_missing(meth)
19
19
  if [:after_load, :clear_hooks].include? meth
20
20
  raise ::Invar::ImmutableRealityError, ::Invar::ImmutableRealityError::HOOK_MSG
21
- else
22
- super
23
21
  end
22
+
23
+ super
24
24
  end
25
25
  end
26
26
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: invar
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.0
4
+ version: 0.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Robin Miller
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-08-06 00:00:00.000000000 Z
11
+ date: 2023-09-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: dry-schema
@@ -51,6 +51,7 @@ files:
51
51
  - ".rspec"
52
52
  - ".rubocop.yml"
53
53
  - ".ruby-version"
54
+ - ".simplecov"
54
55
  - CODE_OF_CONDUCT.md
55
56
  - Gemfile
56
57
  - LICENSE.txt
@@ -80,14 +81,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
80
81
  requirements:
81
82
  - - ">="
82
83
  - !ruby/object:Gem::Version
83
- version: '2.7'
84
+ version: '3.1'
84
85
  required_rubygems_version: !ruby/object:Gem::Requirement
85
86
  requirements:
86
87
  - - ">="
87
88
  - !ruby/object:Gem::Version
88
89
  version: '0'
89
90
  requirements: []
90
- rubygems_version: 3.4.10
91
+ rubygems_version: 3.4.18
91
92
  signing_key:
92
93
  specification_version: 4
93
94
  summary: Single source of truth for environmental configuration.