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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9e0f1018fd5d37ab4171501db8961c7895e796f1f41bf292c773f6d39fa08832
4
- data.tar.gz: 3ab825f73e774cb8d13bb61772c878501e25b42f7e1b3ad35ae2a7a6a31e2d32
3
+ metadata.gz: 6dbce7bf8d558500341875958f875183e18668765f812d1ef8110f51c521cc7d
4
+ data.tar.gz: c297302a9425f27f0491307d7597201430ddf94eff38d80cf0b0760f16186296
5
5
  SHA512:
6
- metadata.gz: 31b6a138ee7ae3c95f173f2df1f2168469b285aeff266c350c27a95b51021fa2deeb43bea1f9b28a0f9a4114a6ff4af9f82e47e64f04dcf314c88dfa9d042936
7
- data.tar.gz: ccd0cb33a718fedae63e46e83bc7495d6b3520d7bfe6b190e2021e98f0f986ed36ca970dd25a71acbcc9017fd1b4b7579fb2399cf6727c92c2a13a1f38d84d80
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.9.3...HEAD
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: ["string?", nullable: true] # optional and nullable
122
+ typelize status: [:string?, nullable: true] # optional and nullable
123
123
 
124
124
  # Also works with keyless typelize
125
- typelize "string?"
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
- # String and class constant can be mixed
171
- typelize item: ["Namespace::UserResource", CommentResource]
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: ["TextBlock", "ImageBlock"]
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: ["string", "Date", optional: true, nullable: true, multi: true, enum: %w[foo bar], comment: "Attribute description", deprecated: "Use `another_attribute` instead"]
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
 
@@ -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
- options = parse_type_declaration(attrs)
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
@@ -36,7 +36,20 @@ module Typelizer
36
36
  end
37
37
 
38
38
  def filename
39
- name.gsub("::", "/")
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
@@ -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
- case type_def
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: 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('./' + interface.filename) %>
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('./' + interface.filename) %>
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
- parsed = type_defs.map { |t| parse(t) }
45
- types = parsed.flat_map { |p| Array(p[:type]) }
46
-
47
- parsed.each do |p|
48
- options[:optional] = true if p[:optional]
49
- options[:multi] = true if p[:multi]
50
- options[:nullable] = true if p[:nullable]
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
- if types.size == 1
56
- {type: types.first}.merge(options)
57
- else
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)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Typelizer
4
- VERSION = "0.9.3"
4
+ VERSION = "0.11.0"
5
5
  end
@@ -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
- return unless File.directory?(config.output_dir)
46
+ def cleanup_stale_files(written_files, interfaces)
47
+ output_dirs = output_dirs_for(interfaces)
48
48
 
49
- existing_files = Dir[File.join(config.output_dir, "**/*.ts")]
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
- write_file("#{interface.filename}.ts", interface.fingerprint) do
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(config.output_dir, filename)
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(config.output_dir)
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)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: typelizer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.3
4
+ version: 0.11.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Svyatoslav Kryukov