typelizer 0.9.3 → 0.11.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 +4 -4
- data/CHANGELOG.md +33 -3
- data/README.md +53 -7
- data/lib/typelizer/config.rb +4 -0
- data/lib/typelizer/dsl.rb +1 -22
- data/lib/typelizer/interface.rb +16 -1
- data/lib/typelizer/openapi.rb +14 -8
- data/lib/typelizer/serializer_plugins/alba/block_attribute_collector.rb +1 -11
- data/lib/typelizer/serializer_plugins/alba.rb +2 -1
- data/lib/typelizer/templates/index.ts.erb +2 -2
- data/lib/typelizer/type_parser.rb +29 -12
- data/lib/typelizer/version.rb +1 -1
- data/lib/typelizer/writer.rb +19 -12
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6dbce7bf8d558500341875958f875183e18668765f812d1ef8110f51c521cc7d
|
|
4
|
+
data.tar.gz: c297302a9425f27f0491307d7597201430ddf94eff38d80cf0b0760f16186296
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5509e82f421cc1f52411b073431a246134d5bdcfe53eb5f308554492da3f2dbc78c708cf45c89651a749d5ad2aa1e0a59c119287f0c0ce8af19a10e374d4efbb
|
|
7
|
+
data.tar.gz: 37ba94237e3992f96542f33ef4376db48a1c5397907d01fbd7a619102a412db58418e36822d46f522f2ebeb672922591a46cbb112e0be2103bf7119d241e2bbe
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,33 @@ and this project adheres to [Semantic Versioning].
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.11.0] - 2026-03-26
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- Per-serializer `output_dir` override via `typelizer_config`. Interfaces are written to their configured directory while the shared `index.ts` barrel generates correct relative import paths. ([@skryukov])
|
|
15
|
+
|
|
16
|
+
- `filename_mapper` configuration to decouple generated file paths from TypeScript type names. Useful for mirroring Ruby module namespaces as nested directories. ([@skryukov], [@rdavid1099])
|
|
17
|
+
|
|
18
|
+
```ruby
|
|
19
|
+
Typelizer.configure do |config|
|
|
20
|
+
config.filename_mapper = ->(name) { name.gsub("::", "/") }
|
|
21
|
+
end
|
|
22
|
+
# Alba::UserSerializer → types/Alba/User.ts (type name stays AlbaUser)
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### Fixed
|
|
26
|
+
|
|
27
|
+
- Walk over inline association properties to determine imports. ([@skryukov])
|
|
28
|
+
- Fix `config.type_mapping` override not applied to OpenAPI schema generation. ([@skryukov])
|
|
29
|
+
- Fix key transformation for Alba traits. ([@skryukov])
|
|
30
|
+
|
|
31
|
+
## [0.10.0] - 2026-03-02
|
|
32
|
+
|
|
33
|
+
### Changed
|
|
34
|
+
|
|
35
|
+
- **Breaking:** Arrays of strings in `typelize` now produce string literal unions (`'active' | 'inactive'`) instead of type reference unions. Use symbols for type references: `typelize status: [:string, :number]`. ([@skryukov])
|
|
36
|
+
|
|
10
37
|
## [0.9.3] - 2026-02-27
|
|
11
38
|
|
|
12
39
|
### Fixed
|
|
@@ -420,20 +447,23 @@ and this project adheres to [Semantic Versioning].
|
|
|
420
447
|
|
|
421
448
|
[@davidrunger]: https://github.com/davidrunger
|
|
422
449
|
[@Envek]: https://github.com/Envek
|
|
423
|
-
[@jonmarkgo]: https://github.com/jonmarkgo
|
|
424
450
|
[@hkamberovic]: https://github.com/hkamberovic
|
|
451
|
+
[@jonmarkgo]: https://github.com/jonmarkgo
|
|
425
452
|
[@kristinemcbride]: https://github.com/kristinemcbride
|
|
426
453
|
[@nkriege]: https://github.com/nkriege
|
|
427
454
|
[@NOX73]: https://github.com/NOX73
|
|
428
455
|
[@okuramasafumi]: https://github.com/okuramasafumi
|
|
429
456
|
[@patvice]: https://github.com/patvice
|
|
430
457
|
[@pgiblock]: https://github.com/pgiblock
|
|
431
|
-
[@prog-supdex]: https://github.com/prog-supdex
|
|
432
458
|
[@PedroAugustoRamalhoDuarte]: https://github.com/PedroAugustoRamalhoDuarte
|
|
459
|
+
[@prog-supdex]: https://github.com/prog-supdex
|
|
460
|
+
[@rdavid1099]: https://github.com/rdavid1099
|
|
433
461
|
[@skryukov]: https://github.com/skryukov
|
|
434
462
|
[@ventsislaf]: https://github.com/ventsislaf
|
|
435
463
|
|
|
436
|
-
[Unreleased]: https://github.com/skryukov/typelizer/compare/v0.
|
|
464
|
+
[Unreleased]: https://github.com/skryukov/typelizer/compare/v0.11.0...HEAD
|
|
465
|
+
[0.11.0]: https://github.com/skryukov/typelizer/compare/v0.10.0...v0.11.0
|
|
466
|
+
[0.10.0]: https://github.com/skryukov/typelizer/compare/v0.9.3...v0.10.0
|
|
437
467
|
[0.9.3]: https://github.com/skryukov/typelizer/compare/v0.9.2...v0.9.3
|
|
438
468
|
[0.9.2]: https://github.com/skryukov/typelizer/compare/v0.9.1...v0.9.2
|
|
439
469
|
[0.9.1]: https://github.com/skryukov/typelizer/compare/v0.9.0...v0.9.1
|
data/README.md
CHANGED
|
@@ -119,10 +119,10 @@ class PostResource < ApplicationResource
|
|
|
119
119
|
typelize categories: "string?[]" # optional array of strings (categories?: Array<string>)
|
|
120
120
|
|
|
121
121
|
# Shortcuts can be combined with explicit options
|
|
122
|
-
typelize status: [
|
|
122
|
+
typelize status: [:string?, nullable: true] # optional and nullable
|
|
123
123
|
|
|
124
124
|
# Also works with keyless typelize
|
|
125
|
-
typelize
|
|
125
|
+
typelize :string?
|
|
126
126
|
attribute :nickname do |user|
|
|
127
127
|
user.nickname
|
|
128
128
|
end
|
|
@@ -167,8 +167,8 @@ class PostResource < ApplicationResource
|
|
|
167
167
|
typelize target: "UserResource | CommentResource"
|
|
168
168
|
attribute :target
|
|
169
169
|
|
|
170
|
-
#
|
|
171
|
-
typelize item:
|
|
170
|
+
# Pipe-delimited string with namespaced serializer
|
|
171
|
+
typelize item: "Namespace::UserResource | CommentResource"
|
|
172
172
|
attribute :item
|
|
173
173
|
end
|
|
174
174
|
```
|
|
@@ -183,8 +183,8 @@ class PostResource < ApplicationResource
|
|
|
183
183
|
typelize content: "TextBlock | ImageBlock"
|
|
184
184
|
attribute :content
|
|
185
185
|
|
|
186
|
-
# Works with arrays too
|
|
187
|
-
typelize sections: [
|
|
186
|
+
# Works with arrays of symbols too
|
|
187
|
+
typelize sections: [:TextBlock, :ImageBlock]
|
|
188
188
|
attribute :sections
|
|
189
189
|
end
|
|
190
190
|
```
|
|
@@ -200,10 +200,39 @@ type Post = {
|
|
|
200
200
|
}
|
|
201
201
|
```
|
|
202
202
|
|
|
203
|
+
String arrays are treated as string literal unions — useful for enums and state machines:
|
|
204
|
+
|
|
205
|
+
```ruby
|
|
206
|
+
class PostResource < ApplicationResource
|
|
207
|
+
attributes :id, :title
|
|
208
|
+
|
|
209
|
+
# Array of strings — generates string literal union type
|
|
210
|
+
typelize status: ["draft", "published", "archived"]
|
|
211
|
+
attribute :status
|
|
212
|
+
|
|
213
|
+
# Works with Rails enums and state machines
|
|
214
|
+
typelize review_state: ReviewStateMachine.states.keys
|
|
215
|
+
attribute :review_state
|
|
216
|
+
end
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
This generates:
|
|
220
|
+
|
|
221
|
+
```typescript
|
|
222
|
+
type Post = {
|
|
223
|
+
id: number;
|
|
224
|
+
title: string;
|
|
225
|
+
status: 'draft' | 'published' | 'archived';
|
|
226
|
+
review_state: 'pending' | 'approved' | 'rejected';
|
|
227
|
+
}
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
> **Note:** In arrays, **strings** become string literal types (`'a'`), while **symbols** and **class constants** become type references (`A`). You can mix them: `[:number, "auto"]` produces `number | 'auto'`.
|
|
231
|
+
|
|
203
232
|
For more complex type definitions, use the full API:
|
|
204
233
|
|
|
205
234
|
```ruby
|
|
206
|
-
typelize attribute_name: [
|
|
235
|
+
typelize attribute_name: [:string, :Date, optional: true, nullable: true, multi: true, enum: %w[foo bar], comment: "Attribute description", deprecated: "Use `another_attribute` instead"]
|
|
207
236
|
```
|
|
208
237
|
|
|
209
238
|
### Alba Traits
|
|
@@ -599,6 +628,16 @@ class PostResource < ApplicationResource
|
|
|
599
628
|
end
|
|
600
629
|
```
|
|
601
630
|
|
|
631
|
+
You can also override `output_dir` per serializer to place its generated file in a different directory:
|
|
632
|
+
|
|
633
|
+
```ruby
|
|
634
|
+
class Admin::UserResource < ApplicationResource
|
|
635
|
+
typelizer_config do |c|
|
|
636
|
+
c.output_dir = Rails.root.join("app/javascript/types/admin")
|
|
637
|
+
end
|
|
638
|
+
end
|
|
639
|
+
```
|
|
640
|
+
|
|
602
641
|
### Option reference
|
|
603
642
|
|
|
604
643
|
```ruby
|
|
@@ -606,6 +645,13 @@ Typelizer.configure do |config|
|
|
|
606
645
|
# Name to type mapping for serializer classes
|
|
607
646
|
config.serializer_name_mapper = ->(serializer) { ... }
|
|
608
647
|
|
|
648
|
+
# Custom file path mapping (decouples filename from type name)
|
|
649
|
+
# Receives the mapped name (output of serializer_name_mapper) and returns a file path.
|
|
650
|
+
# When nil (default), filename is derived from the type name.
|
|
651
|
+
# Example: ->(name) { name.gsub("::", "/") }
|
|
652
|
+
# Alba::UserSerializer → types/Alba/User.ts (type name stays AlbaUser)
|
|
653
|
+
config.filename_mapper = nil
|
|
654
|
+
|
|
609
655
|
# Maps serializers to their corresponding model classes
|
|
610
656
|
config.serializer_model_mapper = ->(serializer) { ... }
|
|
611
657
|
|
data/lib/typelizer/config.rb
CHANGED
|
@@ -41,6 +41,7 @@ module Typelizer
|
|
|
41
41
|
# Config keys that don't affect file content (runtime behavior, or effects captured via properties).
|
|
42
42
|
CONFIGS_NOT_AFFECTING_OUTPUT = %i[
|
|
43
43
|
serializer_name_mapper
|
|
44
|
+
filename_mapper
|
|
44
45
|
serializer_model_mapper
|
|
45
46
|
properties_transformer
|
|
46
47
|
model_plugin
|
|
@@ -56,6 +57,7 @@ module Typelizer
|
|
|
56
57
|
|
|
57
58
|
Config = Struct.new(
|
|
58
59
|
:serializer_name_mapper,
|
|
60
|
+
:filename_mapper,
|
|
59
61
|
:serializer_model_mapper,
|
|
60
62
|
:properties_transformer,
|
|
61
63
|
:properties_sort_order,
|
|
@@ -95,6 +97,8 @@ module Typelizer
|
|
|
95
97
|
name.sub(/(Serializer|Resource)\z/, "")
|
|
96
98
|
end,
|
|
97
99
|
|
|
100
|
+
filename_mapper: nil,
|
|
101
|
+
|
|
98
102
|
serializer_model_mapper: lambda do |serializer|
|
|
99
103
|
base_class = serializer_name_mapper.call(serializer)
|
|
100
104
|
Object.const_get(base_class) if Object.const_defined?(base_class)
|
data/lib/typelizer/dsl.rb
CHANGED
|
@@ -87,8 +87,7 @@ module Typelizer
|
|
|
87
87
|
attributes.each do |name, attrs|
|
|
88
88
|
next unless name
|
|
89
89
|
|
|
90
|
-
|
|
91
|
-
store_type(attribute_name, name, options)
|
|
90
|
+
store_type(attribute_name, name, TypeParser.parse_declaration(attrs))
|
|
92
91
|
end
|
|
93
92
|
end
|
|
94
93
|
|
|
@@ -112,26 +111,6 @@ module Typelizer
|
|
|
112
111
|
end
|
|
113
112
|
end
|
|
114
113
|
end
|
|
115
|
-
|
|
116
|
-
def parse_type_declaration(attrs)
|
|
117
|
-
attrs = [attrs] if attrs && !attrs.is_a?(Array)
|
|
118
|
-
options = attrs.last.is_a?(Hash) ? attrs.pop : {}
|
|
119
|
-
|
|
120
|
-
if attrs.any?
|
|
121
|
-
parsed_types = attrs.map { |t| TypeParser.parse(t) }
|
|
122
|
-
all_types = parsed_types.flat_map { |p| Array(p[:type]) }
|
|
123
|
-
parsed_types.each do |parsed|
|
|
124
|
-
options[:optional] = true if parsed[:optional]
|
|
125
|
-
options[:multi] = true if parsed[:multi]
|
|
126
|
-
options[:nullable] = true if parsed[:nullable]
|
|
127
|
-
end
|
|
128
|
-
options[:nullable] = true if all_types.delete(:null)
|
|
129
|
-
# Unwrap single-element arrays: typelize field: ["string"] behaves like typelize field: "string"
|
|
130
|
-
options[:type] = (all_types.size == 1) ? all_types.first : all_types
|
|
131
|
-
end
|
|
132
|
-
|
|
133
|
-
options
|
|
134
|
-
end
|
|
135
114
|
end
|
|
136
115
|
end
|
|
137
116
|
end
|
data/lib/typelizer/interface.rb
CHANGED
|
@@ -36,7 +36,20 @@ module Typelizer
|
|
|
36
36
|
end
|
|
37
37
|
|
|
38
38
|
def filename
|
|
39
|
-
|
|
39
|
+
if config.filename_mapper
|
|
40
|
+
config.filename_mapper.call(config.serializer_name_mapper.call(serializer))
|
|
41
|
+
else
|
|
42
|
+
name.gsub("::", "/")
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def index_path(index_dir)
|
|
47
|
+
iface_dir = config.output_dir.to_s
|
|
48
|
+
if iface_dir != index_dir
|
|
49
|
+
Pathname.new(File.join(iface_dir, filename)).relative_path_from(Pathname.new(index_dir)).to_s
|
|
50
|
+
else
|
|
51
|
+
"./#{filename}"
|
|
52
|
+
end
|
|
40
53
|
end
|
|
41
54
|
|
|
42
55
|
def root_key
|
|
@@ -166,6 +179,8 @@ module Typelizer
|
|
|
166
179
|
props.flat_map do |prop|
|
|
167
180
|
if prop.nested_properties&.any?
|
|
168
181
|
[prop] + collect_all_properties(prop.nested_properties)
|
|
182
|
+
elsif prop.type.is_a?(Interface) && prop.type.inline?
|
|
183
|
+
[prop] + collect_all_properties(prop.type.properties)
|
|
169
184
|
else
|
|
170
185
|
[prop]
|
|
171
186
|
end
|
data/lib/typelizer/openapi.rb
CHANGED
|
@@ -31,21 +31,22 @@ module Typelizer
|
|
|
31
31
|
def schema_for(interface, openapi_version: "3.0")
|
|
32
32
|
validate_version!(openapi_version)
|
|
33
33
|
|
|
34
|
+
type_mapping = interface.respond_to?(:config) ? interface.config.type_mapping : Typelizer.configuration.type_mapping
|
|
34
35
|
required_props = interface.properties.reject(&:optional).map(&:name)
|
|
35
36
|
schema = {
|
|
36
37
|
type: :object,
|
|
37
|
-
properties: interface.properties.to_h { |prop| [prop.name, property_schema(prop, openapi_version: openapi_version)] }
|
|
38
|
+
properties: interface.properties.to_h { |prop| [prop.name, property_schema(prop, openapi_version: openapi_version, type_mapping: type_mapping)] }
|
|
38
39
|
}
|
|
39
40
|
schema[:required] = required_props if required_props.any?
|
|
40
41
|
schema
|
|
41
42
|
end
|
|
42
43
|
|
|
43
|
-
def property_schema(property, openapi_version: "3.0")
|
|
44
|
+
def property_schema(property, openapi_version: "3.0", type_mapping: Typelizer.configuration.type_mapping)
|
|
44
45
|
if property.type.is_a?(Array)
|
|
45
46
|
return union_schema(property, openapi_version: openapi_version)
|
|
46
47
|
end
|
|
47
48
|
|
|
48
|
-
definition = base_type(property, openapi_version: openapi_version)
|
|
49
|
+
definition = base_type(property, openapi_version: openapi_version, type_mapping: type_mapping)
|
|
49
50
|
ref = definition.delete("$ref")
|
|
50
51
|
|
|
51
52
|
definition = if ref
|
|
@@ -171,7 +172,7 @@ module Typelizer
|
|
|
171
172
|
definition
|
|
172
173
|
end
|
|
173
174
|
|
|
174
|
-
def base_type(property, openapi_version:)
|
|
175
|
+
def base_type(property, openapi_version:, type_mapping:)
|
|
175
176
|
if property.type.respond_to?(:properties)
|
|
176
177
|
if property.type.respond_to?(:inline?) && property.type.inline?
|
|
177
178
|
schema_for(property.type, openapi_version: openapi_version)
|
|
@@ -179,8 +180,9 @@ module Typelizer
|
|
|
179
180
|
{"$ref" => "#/components/schemas/#{property.type.name}"}
|
|
180
181
|
end
|
|
181
182
|
elsif property.type.nil? && property.respond_to?(:nested_properties) && property.nested_properties&.any?
|
|
182
|
-
nested_schema(property, openapi_version: openapi_version)
|
|
183
|
-
elsif property.column_type && COLUMN_TYPE_MAP.key?(property.column_type)
|
|
183
|
+
nested_schema(property, openapi_version: openapi_version, type_mapping: type_mapping)
|
|
184
|
+
elsif property.column_type && COLUMN_TYPE_MAP.key?(property.column_type) &&
|
|
185
|
+
!type_mapping_overridden?(property, type_mapping)
|
|
184
186
|
result = COLUMN_TYPE_MAP[property.column_type].dup
|
|
185
187
|
result[:type] = :string if property.enum
|
|
186
188
|
result
|
|
@@ -194,16 +196,20 @@ module Typelizer
|
|
|
194
196
|
end
|
|
195
197
|
end
|
|
196
198
|
|
|
197
|
-
def nested_schema(property, openapi_version:)
|
|
199
|
+
def nested_schema(property, openapi_version:, type_mapping:)
|
|
198
200
|
required = property.nested_properties.reject(&:optional).map(&:name)
|
|
199
201
|
schema = {
|
|
200
202
|
type: :object,
|
|
201
|
-
properties: property.nested_properties.to_h { |p| [p.name, property_schema(p, openapi_version: openapi_version)] }
|
|
203
|
+
properties: property.nested_properties.to_h { |p| [p.name, property_schema(p, openapi_version: openapi_version, type_mapping: type_mapping)] }
|
|
202
204
|
}
|
|
203
205
|
schema[:required] = required if required.any?
|
|
204
206
|
schema
|
|
205
207
|
end
|
|
206
208
|
|
|
209
|
+
def type_mapping_overridden?(property, type_mapping)
|
|
210
|
+
type_mapping[property.column_type] != TYPE_MAPPING[property.column_type]
|
|
211
|
+
end
|
|
212
|
+
|
|
207
213
|
def v31?(openapi_version)
|
|
208
214
|
openapi_version.to_s == "3.1"
|
|
209
215
|
end
|
|
@@ -111,17 +111,7 @@ module Typelizer
|
|
|
111
111
|
private
|
|
112
112
|
|
|
113
113
|
def normalize_typelize(type_def, **options)
|
|
114
|
-
|
|
115
|
-
when Array
|
|
116
|
-
# [:string, nullable: true] or ['string?', nullable: true]
|
|
117
|
-
type, *rest = type_def
|
|
118
|
-
opts = rest.first || {}
|
|
119
|
-
TypeParser.parse(type, **opts)
|
|
120
|
-
when Symbol, String
|
|
121
|
-
TypeParser.parse(type_def, **options)
|
|
122
|
-
else
|
|
123
|
-
options
|
|
124
|
-
end
|
|
114
|
+
TypeParser.parse_declaration(type_def, **options)
|
|
125
115
|
end
|
|
126
116
|
end
|
|
127
117
|
end
|
|
@@ -61,11 +61,12 @@ module Typelizer
|
|
|
61
61
|
def build_collected_property(name, attr)
|
|
62
62
|
case attr
|
|
63
63
|
when BlockAttributeCollector::BlockAssociation
|
|
64
|
+
prop_name = has_transform_key?(serializer) ? fetch_key(serializer, name) : name
|
|
64
65
|
with_traits = Array(attr.with_traits) if attr.with_traits
|
|
65
66
|
resource = attr.resource || infer_resource_from_name(name)
|
|
66
67
|
|
|
67
68
|
Property.new(
|
|
68
|
-
name:
|
|
69
|
+
name: prop_name,
|
|
69
70
|
type: resource ? context.interface_for(resource) : nil,
|
|
70
71
|
optional: false,
|
|
71
72
|
nullable: false,
|
|
@@ -5,8 +5,8 @@ export type { <%= ImportSorter.sort(enums.map(&:enum_type_name), imports_sort_or
|
|
|
5
5
|
<%- interfaces.each do |interface| -%>
|
|
6
6
|
<%- sorted_traits = ImportSorter.sort(interface.trait_interfaces.map(&:name), interface.config.imports_sort_order) -%>
|
|
7
7
|
<%- if interface.config.verbatim_module_syntax -%>
|
|
8
|
-
export type { <%= interface.name %><%= ", " + sorted_traits.join(", ") if sorted_traits.any? %> } from <%= interface.quote(
|
|
8
|
+
export type { <%= interface.name %><%= ", " + sorted_traits.join(", ") if sorted_traits.any? %> } from <%= interface.quote(interface.index_path(index_dir)) %>
|
|
9
9
|
<%- else -%>
|
|
10
|
-
export type { default as <%= interface.name %><%= ", " + sorted_traits.join(", ") if sorted_traits.any? %> } from <%= interface.quote(
|
|
10
|
+
export type { default as <%= interface.name %><%= ", " + sorted_traits.join(", ") if sorted_traits.any? %> } from <%= interface.quote(interface.index_path(index_dir)) %>
|
|
11
11
|
<%- end -%>
|
|
12
12
|
<%- end -%>
|
|
@@ -10,6 +10,17 @@ module Typelizer
|
|
|
10
10
|
TYPE_PATTERN = /\A(.+?)(\?)?(\[\])?(\?)?\z/
|
|
11
11
|
|
|
12
12
|
class << self
|
|
13
|
+
def parse_declaration(attrs, **options)
|
|
14
|
+
return options.merge(attrs) if attrs.is_a?(Hash)
|
|
15
|
+
return parse(attrs, **options) unless attrs.is_a?(Array)
|
|
16
|
+
|
|
17
|
+
options = attrs.last.merge(options) if attrs.last.is_a?(Hash)
|
|
18
|
+
types = attrs.reject { |t| t.is_a?(Hash) }
|
|
19
|
+
return options if types.empty?
|
|
20
|
+
|
|
21
|
+
parse((types.size == 1) ? types.first : types, **options)
|
|
22
|
+
end
|
|
23
|
+
|
|
13
24
|
def parse(type_def, **options)
|
|
14
25
|
return options if type_def.nil?
|
|
15
26
|
return parse_array(type_def, **options) if type_def.is_a?(Array)
|
|
@@ -41,22 +52,28 @@ module Typelizer
|
|
|
41
52
|
private
|
|
42
53
|
|
|
43
54
|
def parse_array(type_defs, **options)
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
55
|
+
raise ArgumentError, "Empty array passed to typelize" if type_defs.empty?
|
|
56
|
+
|
|
57
|
+
types = []
|
|
58
|
+
type_defs.each do |t|
|
|
59
|
+
if t.is_a?(String)
|
|
60
|
+
types << :"'#{t}'"
|
|
61
|
+
else
|
|
62
|
+
parsed = parse(t)
|
|
63
|
+
types.concat(Array(parsed[:type]))
|
|
64
|
+
options[:optional] = true if parsed[:optional]
|
|
65
|
+
options[:multi] = true if parsed[:multi]
|
|
66
|
+
options[:nullable] = true if parsed[:nullable]
|
|
67
|
+
end
|
|
51
68
|
end
|
|
52
69
|
|
|
53
70
|
options[:nullable] = true if types.delete(:null)
|
|
71
|
+
wrap_type(types, **options)
|
|
72
|
+
end
|
|
54
73
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
{type: types}.merge(options)
|
|
59
|
-
end
|
|
74
|
+
def wrap_type(types, **options)
|
|
75
|
+
type = (types.size == 1) ? types.first : types
|
|
76
|
+
{type: type}.merge(options)
|
|
60
77
|
end
|
|
61
78
|
|
|
62
79
|
def parse_union(type_str, **options)
|
data/lib/typelizer/version.rb
CHANGED
data/lib/typelizer/writer.rb
CHANGED
|
@@ -12,7 +12,7 @@ module Typelizer
|
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
def call(interfaces, force:)
|
|
15
|
-
cleanup_output_dir if force
|
|
15
|
+
cleanup_output_dir(interfaces) if force
|
|
16
16
|
|
|
17
17
|
valid_interfaces = interfaces.reject(&:empty?)
|
|
18
18
|
return [] if valid_interfaces.empty?
|
|
@@ -27,7 +27,7 @@ module Typelizer
|
|
|
27
27
|
|
|
28
28
|
written_files << write_index(valid_interfaces, enums: enums)
|
|
29
29
|
|
|
30
|
-
cleanup_stale_files(written_files) unless force
|
|
30
|
+
cleanup_stale_files(written_files, valid_interfaces) unless force
|
|
31
31
|
|
|
32
32
|
Typelizer.logger.debug("Generated #{written_files.size} TypeScript files in #{config.output_dir}")
|
|
33
33
|
|
|
@@ -43,10 +43,10 @@ module Typelizer
|
|
|
43
43
|
|
|
44
44
|
attr_reader :config, :template_cache
|
|
45
45
|
|
|
46
|
-
def cleanup_stale_files(written_files)
|
|
47
|
-
|
|
46
|
+
def cleanup_stale_files(written_files, interfaces)
|
|
47
|
+
output_dirs = output_dirs_for(interfaces)
|
|
48
48
|
|
|
49
|
-
existing_files = Dir[File.join(
|
|
49
|
+
existing_files = output_dirs.flat_map { |dir| Dir[File.join(dir, "**/*.ts")] }
|
|
50
50
|
stale_files = existing_files - written_files
|
|
51
51
|
|
|
52
52
|
File.delete(*stale_files) unless stale_files.empty?
|
|
@@ -70,22 +70,23 @@ module Typelizer
|
|
|
70
70
|
fingerprint = [
|
|
71
71
|
enums.map(&:enum_type_name),
|
|
72
72
|
interfaces.map { |i|
|
|
73
|
-
[i.name, i.filename, i.trait_interfaces.map(&:name), CONFIGS_AFFECTING_INDEX_OUTPUT.map { |key| i.config.public_send(key) }]
|
|
73
|
+
[i.name, i.filename, i.index_path(config.output_dir.to_s), i.trait_interfaces.map(&:name), CONFIGS_AFFECTING_INDEX_OUTPUT.map { |key| i.config.public_send(key) }]
|
|
74
74
|
}
|
|
75
75
|
].inspect
|
|
76
76
|
write_file("index.ts", fingerprint) do
|
|
77
|
-
render_template("index.ts.erb", interfaces: interfaces, enums: enums, imports_sort_order: config.imports_sort_order, prefer_double_quotes: config.prefer_double_quotes)
|
|
77
|
+
render_template("index.ts.erb", interfaces: interfaces, enums: enums, index_dir: config.output_dir.to_s, imports_sort_order: config.imports_sort_order, prefer_double_quotes: config.prefer_double_quotes)
|
|
78
78
|
end
|
|
79
79
|
end
|
|
80
80
|
|
|
81
81
|
def write_interface(interface)
|
|
82
|
-
|
|
82
|
+
output_dir = interface.config.output_dir
|
|
83
|
+
write_file("#{interface.filename}.ts", interface.fingerprint, output_dir: output_dir) do
|
|
83
84
|
render_template("interface.ts.erb", interface: interface)
|
|
84
85
|
end
|
|
85
86
|
end
|
|
86
87
|
|
|
87
|
-
def write_file(filename, fingerprint)
|
|
88
|
-
output_file = File.join(
|
|
88
|
+
def write_file(filename, fingerprint, output_dir: config.output_dir)
|
|
89
|
+
output_file = File.join(output_dir, filename)
|
|
89
90
|
existing_content = File.exist?(output_file) ? File.read(output_file) : nil
|
|
90
91
|
digest = render_template("fingerprint.ts.erb", fingerprint: fingerprint)
|
|
91
92
|
|
|
@@ -104,8 +105,14 @@ module Typelizer
|
|
|
104
105
|
template_cache[template].call(**context)
|
|
105
106
|
end
|
|
106
107
|
|
|
107
|
-
def cleanup_output_dir
|
|
108
|
-
FileUtils.rm_rf(
|
|
108
|
+
def cleanup_output_dir(interfaces)
|
|
109
|
+
output_dirs_for(interfaces).each { |dir| FileUtils.rm_rf(dir) }
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def output_dirs_for(interfaces)
|
|
113
|
+
dirs = interfaces.filter_map { |i| i.config.output_dir.to_s unless i.empty? }
|
|
114
|
+
dirs << config.output_dir.to_s
|
|
115
|
+
dirs.uniq
|
|
109
116
|
end
|
|
110
117
|
|
|
111
118
|
def cleanup_partial_writes(partial_files)
|