types_from_serializers 0.1.2 → 2.0.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: 942530179ec9019d6555a26ff060ac31188b0e28655fbd8c562133d43e4c91b5
4
- data.tar.gz: 826b1d915ff118d970d1972f91ed76c6fdf85870c37460dad400ffca6f54f568
3
+ metadata.gz: 107bcbbe1708fd38efdb7943673c11112350eeee1e457e67e50934641e8081e7
4
+ data.tar.gz: dbb8a8732f99088318ae2d7c16b11dc219bb96357347d1d2174b106216e82599
5
5
  SHA512:
6
- metadata.gz: 1d2350e94ca4c20fff3518a137197d49c1172aa4432d1c8f8ffba419b9bcb98f37de72f2b3982fbe90a5e2c3601a9123a0fc2e6b0bf3a1339800d9cce1681aa8
7
- data.tar.gz: 48eb7d7f7f90c7686a45515f585d536476b8ca8a6cd9d069c461ea6456859b87d7adefb4a0b94b5261f7b9d29b786f3f74c794f636ad000373bde0523a14c2b7
6
+ metadata.gz: d6e0dc57e64062a6934f0e3f336412c897ec4fe5d7497b65536bf45c59c270f39e278fc183f299bfe0cdc6b55769e652d3f6b153bb298bc2f311a0edf2a8e3ad
7
+ data.tar.gz: 00f07fd96b3e9fa907b457969d227e641cfcce1c9e2f67a56bb9f5d9f07b8222d8861144737eb443508ed50024d31992836a3285a1c739e9588817912a6ca168
data/CHANGELOG.md CHANGED
@@ -1,3 +1,25 @@
1
+ ## [2.0.0](https://github.com/ElMassimo/types_from_serializers/compare/types_from_serializers@0.1.2...types_from_serializers@2.0.0) (2023-04-02)
2
+
3
+ This version adds support for `oj_serializers-2.0.2`, supporting all changes in:
4
+
5
+ - https://github.com/ElMassimo/oj_serializers/pull/9
6
+
7
+ ### Features ✨
8
+
9
+ - Now keys will match the [`transform_keys`](https://github.com/ElMassimo/oj_serializers#transforming-attribute-keys-) configuration instead of always being camelized
10
+ - Support for [`flat_one`](https://github.com/ElMassimo/oj_serializers#composing-serializers-)
11
+ - Use relative paths for imports to make the output configuration more flexible
12
+ - Define the order of properties in the interface with `sort_properties_by`
13
+
14
+ ## [0.1.3](https://github.com/ElMassimo/types_from_serializers/compare/types_from_serializers@0.1.2...types_from_serializers@0.1.3) (2022-07-12)
15
+
16
+
17
+ ### Features
18
+
19
+ * apply the sql mapping fallback as the default ([64898c4](https://github.com/ElMassimo/types_from_serializers/commit/64898c4e3a3f83ea67294f2200f253cd2a64aea9))
20
+
21
+
22
+
1
23
  ## [0.1.2](https://github.com/ElMassimo/types_from_serializers/compare/types_from_serializers@0.1.1...types_from_serializers@0.1.2) (2022-07-12)
2
24
 
3
25
 
@@ -3,7 +3,7 @@
3
3
  require "active_support/concern"
4
4
 
5
5
  # Internal: A DSL to specify types for serializer attributes.
6
- module TypesFromSerializer
6
+ module TypesFromSerializers
7
7
  module DSL
8
8
  extend ActiveSupport::Concern
9
9
 
@@ -16,74 +16,30 @@ module TypesFromSerializer
16
16
  def object_as(name, model: nil, types_from: nil)
17
17
  # NOTE: Avoid taking memory for type information that won't be used.
18
18
  if Rails.env.development?
19
- model ||= name.is_a?(Symbol) ? name : try(:_serializer_model_name)
20
- @_serializer_model_name = model || name
21
- @_serializer_types_from = types_from if types_from
19
+ model ||= name.is_a?(Symbol) ? name : try(:_serializer_model_name) || name
20
+ define_singleton_method(:_serializer_model_name) { model }
21
+ define_singleton_method(:_serializer_types_from) { types_from } if types_from
22
22
  end
23
23
 
24
24
  super(name)
25
25
  end
26
26
 
27
- # Public: Like `attributes`, but providing type information for each field.
28
- def typed_attributes(attrs)
29
- attributes(*attrs.keys)
30
-
31
- # NOTE: Avoid taking memory for type information that won't be used.
32
- if Rails.env.development?
33
- _typed_attributes.update(attrs.map { |key, type|
34
- [key.to_s, type.is_a?(Hash) ? type : {type: type}]
35
- }.to_h)
36
- end
37
- end
38
-
39
- # Public: Allows to specify the type for a serializer method that will
40
- # be defined immediately after calling this method.
41
- def type(type = :unknown, optional: false)
42
- @_current_attribute_type = {type: type, optional: optional}
43
- end
44
-
45
- # Internal: Intercept a method definition, tying a type that was
46
- # previously specified to the name of the attribute.
47
- def method_added(name)
48
- super(name)
49
- if @_current_attribute_type
50
- serializer_attributes name
51
-
52
- # NOTE: Avoid taking memory for type information that won't be used.
53
- if Rails.env.development?
54
- _typed_attributes[name.to_s] = @_current_attribute_type
55
- end
56
-
57
- @_current_attribute_type = nil
58
- end
27
+ # Public: Shortcut for typing a serializer attribute.
28
+ #
29
+ # It specifies the type for a serializer method that will be defined
30
+ # immediately after calling this method.
31
+ def type(type, **options)
32
+ attribute type: type, **options
59
33
  end
60
34
 
61
- # NOTE: Avoid taking memory for type information that won't be used.
62
- if Rails.env.development?
63
- # Internal: Contains type information for serializer attributes.
64
- def _typed_attributes
65
- unless defined?(@_typed_attributes)
66
- @_typed_attributes = superclass.try(:_typed_attributes)&.dup || {}
67
- end
68
- @_typed_attributes
69
- end
70
-
71
- # Internal: The name of the model that will be serialized by this
72
- # serializer, used to infer field types from the SQL columns.
73
- def _serializer_model_name
74
- unless defined?(@_serializer_model_name)
75
- @_serializer_model_name = superclass.try(:_serializer_model_name)
76
- end
77
- @_serializer_model_name
78
- end
35
+ private
79
36
 
80
- # Internal: The TypeScript interface that will be used by default to
81
- # infer the serializer field types when not explicitly provided.
82
- def _serializer_types_from
83
- unless defined?(@_serializer_types_from)
84
- @_serializer_types_from = superclass.try(:_serializer_types_from)
85
- end
86
- @_serializer_types_from
37
+ # Override: Remove unnecessary options in production, types are only
38
+ # used when generating code in development.
39
+ unless Rails.env.development?
40
+ def add_attribute(name, options)
41
+ options.except!(:type, :optional)
42
+ super
87
43
  end
88
44
  end
89
45
  end
@@ -6,32 +6,7 @@ require "pathname"
6
6
 
7
7
  # Public: Automatically generates TypeScript interfaces for Ruby serializers.
8
8
  module TypesFromSerializers
9
- # Internal: The configuration for TypeScript generation.
10
- Config = Struct.new(
11
- :base_serializers,
12
- :serializers_dirs,
13
- :output_dir,
14
- :name_from_serializer,
15
- :native_types,
16
- :sql_to_typescript_type_mapping,
17
- keyword_init: true,
18
- )
19
-
20
- # Internal: The type metadata for a serializer.
21
- SerializerMetadata = Struct.new(
22
- :attributes,
23
- :associations,
24
- :model_name,
25
- :types_from,
26
- keyword_init: true,
27
- )
28
-
29
- # Internal: The type metadata for a serializer field.
30
- FieldMetadata = Struct.new(:name, :type, :optional, :many, keyword_init: true) do
31
- def typescript_name
32
- name.to_s.camelize(:lower)
33
- end
34
- end
9
+ DEFAULT_TRANSFORM_KEYS = ->(key) { key.camelize(:lower).chomp("?") }
35
10
 
36
11
  # Internal: Extensions that simplify the implementation of the generator.
37
12
  module SerializerRefinements
@@ -54,91 +29,185 @@ module TypesFromSerializers
54
29
 
55
30
  refine Class do
56
31
  # Internal: Name of the TypeScript interface.
57
- def typescript_interface_name
32
+ def ts_name
58
33
  TypesFromSerializers.config.name_from_serializer.call(name).tr_s(":", "")
59
34
  end
60
35
 
61
36
  # Internal: The base name of the TypeScript file to be written.
62
- def typescript_interface_basename
37
+ def ts_filename
63
38
  TypesFromSerializers.config.name_from_serializer.call(name).gsub("::", "/")
64
39
  end
65
40
 
66
- # Internal: A first pass of gathering types for the serializer fields.
67
- def typescript_metadata
68
- SerializerMetadata.new(
69
- model_name: _serializer_model_name,
70
- types_from: _serializer_types_from,
71
- attributes: _attributes.map { |key, options|
72
- typed_attrs = _typed_attributes.fetch(key, {})
73
- FieldMetadata.new(
74
- **typed_attrs,
75
- name: key,
76
- optional: typed_attrs[:optional] || options.key?(:if),
77
- )
78
- },
79
- associations: _associations.map { |key, options|
80
- FieldMetadata.new(
81
- name: options.fetch(:root, key),
82
- type: options.fetch(:serializer),
83
- optional: options.key?(:if),
84
- many: options.fetch(:write_method) == :write_many,
85
- )
86
- },
87
- )
41
+ # Internal: The columns corresponding to the serializer model, if it's a
42
+ # record.
43
+ def model_columns
44
+ @model_columns ||= _serializer_model_name&.to_model.try(:columns_hash) || {}
88
45
  end
89
46
 
90
- # Internal: Infers field types by checking the SQL columns for the model
91
- # serialized, or from a TypeScript interface if provided.
92
- def typescript_infer_types(metadata)
93
- model = metadata.model_name&.to_model
94
- interface = metadata.types_from
95
-
96
- metadata.attributes.reject(&:type).each do |meta|
97
- if model&.respond_to?(:columns_hash) && (column = model.columns_hash[meta.name.to_s])
98
- meta[:type] = TypesFromSerializers.config.sql_to_typescript_type_mapping[column.type]
99
- meta[:optional] ||= column.null
100
- elsif interface
101
- meta[:type] = "#{interface}['#{meta.typescript_name}']"
102
- end
47
+ # Internal: The TypeScript properties of the serialzeir interface.
48
+ def ts_properties
49
+ @ts_properties ||= begin
50
+ types_from = try(:_serializer_types_from)
51
+
52
+ prepare_attributes(
53
+ sort_by: TypesFromSerializers.config.sort_properties_by,
54
+ transform_keys: TypesFromSerializers.config.transform_keys || try(:_transform_keys) || DEFAULT_TRANSFORM_KEYS,
55
+ )
56
+ .flat_map { |key, options|
57
+ if options[:association] == :flat
58
+ options.fetch(:serializer).ts_properties
59
+ else
60
+ Property.new(
61
+ name: key,
62
+ type: options[:serializer] || options[:type],
63
+ optional: options[:optional] || options.key?(:if),
64
+ multi: options[:association] == :many,
65
+ column_name: options.fetch(:value_from),
66
+ ).tap do |property|
67
+ property.infer_type_from(model_columns, types_from)
68
+ end
69
+ end
70
+ }
103
71
  end
104
72
  end
105
73
 
106
- def typescript_imports(metadata)
107
- assoc_imports = metadata.associations.map { |meta|
108
- [meta.type.typescript_interface_name, "~/types/serializers/#{meta.type.typescript_interface_basename}"]
74
+ # Internal: A first pass of gathering types for the serializer attributes.
75
+ def ts_interface
76
+ @ts_interface ||= Interface.new(
77
+ name: ts_name,
78
+ filename: ts_filename,
79
+ properties: ts_properties,
80
+ )
81
+ end
82
+ end
83
+ end
84
+
85
+ # Internal: The configuration for TypeScript generation.
86
+ Config = Struct.new(
87
+ :base_serializers,
88
+ :serializers_dirs,
89
+ :output_dir,
90
+ :custom_types_dir,
91
+ :name_from_serializer,
92
+ :global_types,
93
+ :sort_properties_by,
94
+ :sql_to_typescript_type_mapping,
95
+ :skip_serializer_if,
96
+ :transform_keys,
97
+ keyword_init: true,
98
+ ) do
99
+ def relative_custom_types_dir
100
+ @relative_custom_types_dir ||= (custom_types_dir || output_dir.parent).relative_path_from(output_dir)
101
+ end
102
+
103
+ def unknown_type
104
+ sql_to_typescript_type_mapping.default
105
+ end
106
+ end
107
+
108
+ # Internal: Information to generate a TypeScript interface for a serializer.
109
+ Interface = Struct.new(
110
+ :name,
111
+ :filename,
112
+ :properties,
113
+ keyword_init: true,
114
+ ) do
115
+ using SerializerRefinements
116
+
117
+ def inspect
118
+ to_h.inspect
119
+ end
120
+
121
+ # Internal: Returns a list of imports for types used in this interface.
122
+ def used_imports
123
+ association_serializers, attribute_types = properties.map(&:type).compact.uniq
124
+ .partition { |type| type.respond_to?(:ts_interface) }
125
+
126
+ serializer_type_imports = association_serializers.map(&:ts_interface)
127
+ .map { |type| [type.name, relative_path(type.pathname, pathname)] }
128
+
129
+ custom_type_imports = attribute_types
130
+ .flat_map { |type| extract_typescript_types(type.to_s) }
131
+ .uniq
132
+ .reject { |type| global_type?(type) }
133
+ .map { |type|
134
+ type_path = TypesFromSerializers.config.relative_custom_types_dir.join(type)
135
+ [type, relative_path(type_path, pathname)]
109
136
  }
110
137
 
111
- attr_imports = metadata.attributes
112
- .flat_map { |meta| extract_typescript_types(meta.type.to_s) }
113
- .uniq
114
- .reject { |type| typescript_native_type?(type) }
115
- .map { |type|
116
- [type, "~/types/#{type}"]
117
- }
118
-
119
- (assoc_imports + attr_imports).uniq.map { |interface, filename|
120
- "import type #{interface} from '#{filename}'\n"
121
- }.uniq
122
- end
138
+ (custom_type_imports + serializer_type_imports)
139
+ .map { |interface, filename| "import type #{interface} from '#{filename}'\n" }
140
+ end
123
141
 
124
- # Internal: Extracts any types inside generics or array types.
125
- def extract_typescript_types(type)
126
- type.split(/[<>\[\],\s|]+/)
127
- end
142
+ def as_typescript
143
+ <<~TS
144
+ interface #{name} {
145
+ #{properties.index_by(&:name).values.map(&:as_typescript).join("\n ")}
146
+ }
147
+ TS
148
+ end
149
+
150
+ protected
151
+
152
+ def pathname
153
+ @pathname ||= Pathname.new(filename)
154
+ end
155
+
156
+ # Internal: Calculates a relative path that can be used in an import.
157
+ def relative_path(target_path, importer_path)
158
+ path = target_path.relative_path_from(importer_path.parent).to_s
159
+ path.start_with?(".") ? path : "./#{path}"
160
+ end
128
161
 
129
- # NOTE: Treat uppercase names as custom types.
130
- # Lowercase names would be native types, such as :string and :boolean.
131
- def typescript_native_type?(type)
132
- type[0] == type[0].downcase || TypesFromSerializers.config.native_types.include?(type)
162
+ # Internal: Extracts any types inside generics or array types.
163
+ def extract_typescript_types(type)
164
+ type.split(/[<>\[\],\s|]+/)
165
+ end
166
+
167
+ # NOTE: Treat uppercase names as custom types.
168
+ # Lowercase names would be native types, such as :string and :boolean.
169
+ def global_type?(type)
170
+ type[0] == type[0].downcase || TypesFromSerializers.config.global_types.include?(type)
171
+ end
172
+ end
173
+
174
+ # Internal: The type metadata for a serializer attribute.
175
+ Property = Struct.new(
176
+ :name,
177
+ :type,
178
+ :optional,
179
+ :multi,
180
+ :column_name,
181
+ keyword_init: true,
182
+ ) do
183
+ using SerializerRefinements
184
+
185
+ def inspect
186
+ to_h.inspect
187
+ end
188
+
189
+ # Internal: Infers the property's type by checking a corresponding SQL
190
+ # column, or falling back to a TypeScript interface if provided.
191
+ def infer_type_from(columns_hash, ts_interface)
192
+ if type
193
+ type
194
+ elsif (column = columns_hash[column_name.to_s])
195
+ self.multi = true if column.try(:array)
196
+ self.optional = true if column.null && !column.default
197
+ self.type = TypesFromSerializers.config.sql_to_typescript_type_mapping[column.type]
198
+ elsif ts_interface
199
+ self.type = "#{ts_interface}['#{name}']"
133
200
  end
201
+ end
134
202
 
135
- def typescript_fields(metadata)
136
- (metadata.attributes + metadata.associations).map { |meta|
137
- type = meta.type.is_a?(Class) ? meta.type.typescript_interface_name : meta.type || :unknown
138
- type = meta.many ? "#{type}[]" : type
139
- " #{meta.typescript_name}#{"?" if meta.optional}: #{type}"
140
- }
203
+ def as_typescript
204
+ type_str = if type.respond_to?(:ts_name)
205
+ type.ts_name
206
+ else
207
+ type || TypesFromSerializers.config.unknown_type
141
208
  end
209
+
210
+ "#{name}#{"?" if optional}: #{type_str}#{"[]" if multi}"
142
211
  end
143
212
  end
144
213
 
@@ -199,6 +268,7 @@ module TypesFromSerializers
199
268
  # Public: Generates code for all serializers in the app.
200
269
  def generate(force: ENV["SERIALIZER_TYPES_FORCE"])
201
270
  @force_generation = force
271
+ config.output_dir.rmtree if force && config.output_dir.exist?
202
272
  generate_index_file
203
273
 
204
274
  loaded_serializers.each do |serializer|
@@ -217,12 +287,10 @@ module TypesFromSerializers
217
287
 
218
288
  # Internal: Defines a TypeScript interface for the serializer.
219
289
  def generate_interface_for(serializer)
220
- metadata = serializer.typescript_metadata
221
- filename = serializer.typescript_interface_basename
290
+ interface = serializer.ts_interface
222
291
 
223
- write_if_changed(filename: filename, cache_key: metadata.inspect) {
224
- serializer.typescript_infer_types(metadata)
225
- serializer_interface_content(serializer, metadata)
292
+ write_if_changed(filename: interface.filename, cache_key: interface.inspect) {
293
+ serializer_interface_content(interface)
226
294
  }
227
295
  end
228
296
 
@@ -236,8 +304,11 @@ module TypesFromSerializers
236
304
  end
237
305
 
238
306
  # Internal: Checks if it should avoid generating an interface.
239
- def skip_serializer?(name)
240
- name.include?("BaseSerializer") || name.in?(config.base_serializers)
307
+ def skip_serializer?(serializer)
308
+ serializer.name.in?(config.base_serializers) ||
309
+ config.skip_serializer_if.call(serializer) ||
310
+ # NOTE: Ignore inline serializers.
311
+ serializer.ts_name.include?("Serializer")
241
312
  end
242
313
 
243
314
  # Internal: Returns an object compatible with FileUpdateChecker.
@@ -268,7 +339,7 @@ module TypesFromSerializers
268
339
  .flat_map(&:descendants)
269
340
  .uniq
270
341
  .sort_by(&:name)
271
- .reject { |s| skip_serializer?(s.name) }
342
+ .reject { |s| skip_serializer?(s) }
272
343
  rescue NameError
273
344
  raise ArgumentError, "Please ensure all your serializers extend BaseSerializer, or configure `config.base_serializers`."
274
345
  end
@@ -288,12 +359,18 @@ module TypesFromSerializers
288
359
  name_from_serializer: ->(name) { name.delete_suffix("Serializer") },
289
360
 
290
361
  # Types that don't need to be imported in TypeScript.
291
- native_types: [
362
+ global_types: [
292
363
  "Array",
293
364
  "Record",
294
365
  "Date",
295
366
  ].to_set,
296
367
 
368
+ # Allows to choose a different sort order, alphabetical by default.
369
+ sort_properties_by: :name,
370
+
371
+ # Allows to avoid generating a serializer.
372
+ skip_serializer_if: ->(serializer) { false },
373
+
297
374
  # Maps SQL column types to TypeScript native and custom types.
298
375
  sql_to_typescript_type_mapping: {
299
376
  boolean: :boolean,
@@ -306,6 +383,9 @@ module TypesFromSerializers
306
383
  }.tap do |types|
307
384
  types.default = :unknown
308
385
  end,
386
+
387
+ # Allows to transform keys, useful when converting objects client-side.
388
+ transform_keys: nil,
309
389
  )
310
390
  end
311
391
 
@@ -330,18 +410,18 @@ module TypesFromSerializers
330
410
  <<~TS
331
411
  //
332
412
  // DO NOT MODIFY: This file was automatically generated by TypesFromSerializers.
333
- #{serializers.map { |s| "export type { default as #{s.typescript_interface_name} } from './#{s.typescript_interface_basename}'" }.join("\n")}
413
+ #{serializers.map { |s|
414
+ "export type { default as #{s.ts_name} } from './#{s.ts_filename}'"
415
+ }.join("\n")}
334
416
  TS
335
417
  end
336
418
 
337
- def serializer_interface_content(serializer, metadata)
419
+ def serializer_interface_content(interface)
338
420
  <<~TS
339
421
  //
340
422
  // DO NOT MODIFY: This file was automatically generated by TypesFromSerializers.
341
- #{serializer.typescript_imports(metadata).join}
342
- export default interface #{serializer.typescript_interface_name} {
343
- #{serializer.typescript_fields(metadata).join("\n")}
344
- }
423
+ #{interface.used_imports.join}
424
+ export default #{interface.as_typescript}
345
425
  TS
346
426
  end
347
427
 
@@ -36,8 +36,11 @@ class TypesFromSerializers::Railtie < Rails::Railtie
36
36
  desc "Generates TypeScript interfaces for each serializer in the app."
37
37
  task generate: :environment do
38
38
  require_relative "generator"
39
+ start_time = Time.zone.now
40
+ print "Generating TypeScript interfaces..."
39
41
  serializers = TypesFromSerializers.generate(force: true)
40
- puts "Generated TypeScript interfaces for #{serializers.size} serializers:"
42
+ puts "completed in #{(Time.zone.now - start_time).round(2)} seconds.\n"
43
+ puts "Found #{serializers.size} serializers:"
41
44
  puts serializers.map { |s| "\t#{s.name}" }.join("\n")
42
45
  end
43
46
  end
@@ -2,5 +2,5 @@
2
2
 
3
3
  module TypesFromSerializers
4
4
  # Public: This library adheres to semantic versioning.
5
- VERSION = "0.1.2"
5
+ VERSION = "2.0.0"
6
6
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: types_from_serializers
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Máximo Mussini
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-07-12 00:00:00.000000000 Z
11
+ date: 2023-04-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: railties
@@ -17,9 +17,6 @@ dependencies:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
19
  version: '5.1'
20
- - - "<"
21
- - !ruby/object:Gem::Version
22
- version: '8'
23
20
  type: :runtime
24
21
  prerelease: false
25
22
  version_requirements: !ruby/object:Gem::Requirement
@@ -27,23 +24,26 @@ dependencies:
27
24
  - - ">="
28
25
  - !ruby/object:Gem::Version
29
26
  version: '5.1'
30
- - - "<"
31
- - !ruby/object:Gem::Version
32
- version: '8'
33
27
  - !ruby/object:Gem::Dependency
34
28
  name: oj_serializers
35
29
  requirement: !ruby/object:Gem::Requirement
36
30
  requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 2.0.2
37
34
  - - "~>"
38
35
  - !ruby/object:Gem::Version
39
- version: '1.0'
36
+ version: '2.0'
40
37
  type: :runtime
41
38
  prerelease: false
42
39
  version_requirements: !ruby/object:Gem::Requirement
43
40
  requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: 2.0.2
44
44
  - - "~>"
45
45
  - !ruby/object:Gem::Version
46
- version: '1.0'
46
+ version: '2.0'
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: listen
49
49
  requirement: !ruby/object:Gem::Requirement