types_from_serializers 0.1.2 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
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