types_from_serializers 2.6.1 → 2.7.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: dcf37c6e48a063c85bcbfef2e383c92ac841bc69e6fbacd817a3ebeb4f60b722
4
- data.tar.gz: dc83b8c53f250040db99e380bef6abb2b973981f8007b2b1151f75f6789b8d1a
3
+ metadata.gz: 0c7231c333c07c7cc5c16f2934c5c6cdc59b6633ad93b1a97f254d489f43af95
4
+ data.tar.gz: 9165a3c8fd6d7fb50ef7f4c26a3d6fb8d33c60cc6a2397657777f97fde10940a
5
5
  SHA512:
6
- metadata.gz: a289339bfa3f5cd7b980cd72fc663f5ca2d92b838feb82ee9612a5d7664de2fba1864a81490d2b2899ea59b7a7c5c8c53094b2ab017062cfa2dd16468bdaba64
7
- data.tar.gz: 7f9ae782d1bd177a1148c7eb977f749c800ff00af86bfe49e0575ab87af36cb7e6dc44d05b2637393f444bd3060f5cfd190bebe55c5a7d6c904cac9955880d4b
6
+ metadata.gz: 71add1bc036f519427612789520ddea812be40d1d2daac7ad1e6553d7d2d4c4d9981f94eff342dfbe951315b9c3cb91a7d6ca960d1e8139e247ff11a6e48a9c6
7
+ data.tar.gz: 8ddfa2c04c39db9ec8940c799a3c38494a69ead2ac4e656494b8784db4d2c4f418fa0a05338301f04bd6d3cd6ae11830e6e02f0a98897b5a19a969e59c14676c
data/CHANGELOG.md CHANGED
@@ -1,6 +1,25 @@
1
- ## [2.6.1](https://github.com/ElMassimo/types_from_serializers/compare/types_from_serializers@2.6.0...types_from_serializers@2.6.1) (2026-07-03)
1
+ # [2.7.0](https://github.com/ElMassimo/types_from_serializers/compare/types_from_serializers@2.6.1...types_from_serializers@2.7.0) (2026-10-19)
2
+
3
+
4
+ ### Features
5
+
6
+ * replace generated cache-key headers with content comparison ([#37](https://github.com/ElMassimo/types_from_serializers/issues/37)) ([1902824](https://github.com/ElMassimo/types_from_serializers/commit/19028240524cfff7007f04030b2cb968b856abec))
7
+
8
+
2
9
 
10
+ # [2.7.0](https://github.com/ElMassimo/types_from_serializers/compare/types_from_serializers@2.6.1...types_from_serializers@2.7.0) (2026-10-17)
11
+
12
+
13
+ ### Features
14
+
15
+ * generated files no longer start with a `// TypesFromSerializers CacheKey` header. Files are compared by content and only written when they changed, which removes a frequent source of merge conflicts. Run `bin/rake types_from_serializers:generate` once after upgrading to drop the header from existing files.
16
+ * in development, only the serializers whose files were modified are regenerated, together with their subclasses and the serializers that flat-embed them, found by scanning the serializer files; adding or removing a serializer file still triggers a full pass.
17
+ * `SERIALIZER_TYPES_FORCE` now only controls whether the output dir is cleaned before generating.
18
+
19
+
20
+ ## [2.6.1](https://github.com/ElMassimo/types_from_serializers/compare/types_from_serializers@2.6.0...types_from_serializers@2.6.1) (2026-07-03)
3
21
 
22
+ * Render nullable boolean columns with default value of false as not optional on rails 8.1 [#36](https://github.com/ElMassimo/types_from_serializers/pull/36)
4
23
 
5
24
  # [2.6.0](https://github.com/ElMassimo/types_from_serializers/compare/types_from_serializers@2.5.0...types_from_serializers@2.6.0) (2026-05-06)
6
25
 
@@ -32,7 +32,7 @@ module TypesFromSerializers
32
32
  attribute type: type, **options
33
33
  end
34
34
 
35
- private
35
+ private
36
36
 
37
37
  # Override: Remove unnecessary options in production, types are only
38
38
  # used when generating code in development.
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "digest"
4
3
  require "fileutils"
5
4
  require "pathname"
6
5
 
@@ -152,7 +151,7 @@ module TypesFromSerializers
152
151
  TS
153
152
  end
154
153
 
155
- protected
154
+ protected
156
155
 
157
156
  def pathname
158
157
  @pathname ||= Pathname.new(filename)
@@ -249,7 +248,7 @@ module TypesFromSerializers
249
248
  @modified.clear
250
249
  end
251
250
 
252
- private
251
+ private
253
252
 
254
253
  def track_changes(dirs)
255
254
  Listen.to(*dirs, only: %r{.rb$}) do |modified, added, removed|
@@ -263,8 +262,6 @@ module TypesFromSerializers
263
262
  class << self
264
263
  using SerializerRefinements
265
264
 
266
- attr_reader :force_generation
267
-
268
265
  # Public: Registry of named configurations for multi-app setups.
269
266
  def configs
270
267
  @configs ||= {}
@@ -283,19 +280,14 @@ module TypesFromSerializers
283
280
  end
284
281
 
285
282
  # Public: Generates code for all serializers in the app.
283
+ #
284
+ # force - Deletes the output dir first, so that files for serializers that
285
+ # were renamed or removed don't linger.
286
286
  def generate(force: ENV["SERIALIZER_TYPES_FORCE"])
287
- @force_generation = force
288
287
  config.output_dir.rmtree if force && config.output_dir.exist?
289
-
290
- if config.namespace
291
- load_serializers(all_serializer_files) if force
292
- else
293
- generate_index_file
294
- end
295
-
296
- loaded_serializers.each do |serializer|
297
- generate_interface_for(serializer)
298
- end
288
+ load_serializers(all_serializer_files)
289
+ generate_index_file unless config.namespace
290
+ generate_interfaces
299
291
  end
300
292
 
301
293
  # Public: Generates types for all registered configurations (default + named).
@@ -306,28 +298,29 @@ module TypesFromSerializers
306
298
  end
307
299
  end
308
300
 
301
+ # Public: Generates code only for serializers whose files changed since the
302
+ # last run, which avoids loading every serializer on each reload.
303
+ #
304
+ # When files were only modified, the index can't have changed (it's derived
305
+ # from file names), so only the modified serializers and the ones that
306
+ # depend on them are regenerated. Added or removed files require a full pass.
309
307
  def generate_changed
310
308
  chg = changes_for(config)
311
- if chg.updated?
312
- config.output_dir.rmtree if chg.any_removed?
313
- load_serializers(chg.modified_files)
314
- generate
315
- chg.clear
309
+ return unless chg.updated?
310
+
311
+ if chg.only_modified?
312
+ load_serializers_and_dependents(chg.modified_files)
313
+ generate_interfaces
314
+ else
315
+ generate(force: chg.any_removed?)
316
316
  end
317
+ chg.clear
317
318
  end
318
319
 
319
320
  # Public: Like generate_changed but for all registered configurations.
320
321
  def generate_all_changed
321
322
  all_configs_list.each do |cfg|
322
- chg = changes_for(cfg)
323
- if chg.updated?
324
- with_active_config(cfg) do
325
- config.output_dir.rmtree if chg.any_removed?
326
- load_serializers(chg.modified_files)
327
- generate
328
- chg.clear
329
- end
330
- end
323
+ with_active_config(cfg) { generate_changed }
331
324
  end
332
325
  end
333
326
 
@@ -335,16 +328,14 @@ module TypesFromSerializers
335
328
  def generate_interface_for(serializer)
336
329
  interface = serializer.ts_interface
337
330
 
338
- write_if_changed(filename: interface.filename, cache_key: interface.inspect, extension: config.namespace ? "d.ts" : "ts") {
331
+ write_if_changed(filename: interface.filename, extension: config.namespace ? "d.ts" : "ts") {
339
332
  serializer_interface_content(interface)
340
333
  }
341
334
  end
342
335
 
343
336
  # Internal: Allows to import all serializer types from a single file.
344
337
  def generate_index_file
345
- cache_key = all_serializer_files.map { |file| file.delete_prefix(root.to_s) }.join
346
- write_if_changed(filename: "index", cache_key: cache_key) {
347
- load_serializers(all_serializer_files)
338
+ write_if_changed(filename: "index") {
348
339
  serializers_index_content(loaded_serializers)
349
340
  }
350
341
  end
@@ -365,7 +356,7 @@ module TypesFromSerializers
365
356
  all_configs_list.map { |cfg| changes_for(cfg) }
366
357
  end
367
358
 
368
- private
359
+ private
369
360
 
370
361
  def root
371
362
  defined?(Rails) ? Rails.root : Pathname.new(Dir.pwd)
@@ -390,6 +381,51 @@ module TypesFromSerializers
390
381
  @config = saved
391
382
  end
392
383
 
384
+ def generate_interfaces
385
+ loaded_serializers.each { |serializer| generate_interface_for(serializer) }
386
+ end
387
+
388
+ # Internal: Requires the given serializer files, plus every serializer file
389
+ # whose interface depends on a serializer they define, transitively. Rails
390
+ # unloads all classes before to_prepare, so subclasses and flat associations
391
+ # of a modified serializer would otherwise stay unloaded, and their files
392
+ # stale.
393
+ def load_serializers_and_dependents(files)
394
+ loaded = Set.new
395
+ pending = files.to_a
396
+ until pending.empty?
397
+ load_serializers(pending)
398
+ loaded.merge(pending)
399
+ names = serializer_names_defined_in(pending)
400
+ pending = names.empty? ? [] : serializer_files_depending_on(names).reject { |file| loaded.include?(file) }
401
+ end
402
+ end
403
+
404
+ def serializer_names_defined_in(files)
405
+ base_serializers = config.base_serializers.filter_map(&:safe_constantize)
406
+ (base_serializers + base_serializers.flat_map(&:descendants))
407
+ .select { |serializer| files.include?(Object.const_source_location(serializer.name)&.first) }
408
+ .map { |serializer| serializer.name.demodulize }
409
+ .uniq
410
+ end
411
+
412
+ # Internal: Files whose serializers inherit from, or flat-embed, one of the
413
+ # named serializers: the only references that change a generated interface,
414
+ # since plain associations only import the other interface by name.
415
+ #
416
+ # A file is scanned as text rather than loaded, so this is cheap. Flat
417
+ # associations are matched loosely (a flat call anywhere plus the name
418
+ # anywhere) so that multi-line calls aren't missed.
419
+ def serializer_files_depending_on(names)
420
+ alternatives = names.map { |name| Regexp.escape(name) }.join("|")
421
+ inherits = /<\s*(?:\w+::)*(?:#{alternatives})\b/
422
+ mentions = /\b(?:#{alternatives})\b/
423
+ all_serializer_files.select { |file|
424
+ source = File.read(file)
425
+ source.match?(inherits) || (source.include?("flat") && source.match?(mentions))
426
+ }
427
+ end
428
+
393
429
  def all_serializer_files
394
430
  config.serializers_dirs.flat_map { |dir| Dir["#{dir}/**/*.rb"] }.sort
395
431
  end
@@ -463,26 +499,21 @@ module TypesFromSerializers
463
499
  )
464
500
  end
465
501
 
466
- # Internal: Writes if the file does not exist or the cache key has changed.
467
- # The cache strategy consists of a comment on the first line of the file.
502
+ # Internal: Writes the file unless it already has the same content, which
503
+ # keeps mtimes stable so file watchers don't react to no-op generations.
468
504
  #
469
- # Yields to receive the rendered file content when it needs to.
470
- def write_if_changed(filename:, cache_key:, extension: "ts")
505
+ # Yields to receive the rendered file content.
506
+ def write_if_changed(filename:, extension: "ts")
471
507
  filename = config.output_dir.join("#{filename}.#{extension}")
508
+ content = yield
509
+ return if filename.exist? && filename.read == content
510
+
472
511
  FileUtils.mkdir_p(filename.dirname)
473
- cache_key_comment = "// TypesFromSerializers CacheKey #{Digest::MD5.hexdigest(cache_key)}\n"
474
- File.open(filename, "a+") { |file|
475
- if stale?(file, cache_key_comment)
476
- file.truncate(0)
477
- file.write(cache_key_comment)
478
- file.write(yield)
479
- end
480
- }
512
+ filename.write(content)
481
513
  end
482
514
 
483
515
  def serializers_index_content(serializers)
484
516
  <<~TS
485
- //
486
517
  // DO NOT MODIFY: This file was automatically generated by TypesFromSerializers.
487
518
  #{serializers.reject(&:inline_serializer?).map { |s|
488
519
  "export type { default as #{s.ts_name} } from './#{s.ts_filename}'"
@@ -496,7 +527,6 @@ module TypesFromSerializers
496
527
 
497
528
  def standard_interface_definition(interface)
498
529
  <<~TS
499
- //
500
530
  // DO NOT MODIFY: This file was automatically generated by TypesFromSerializers.
501
531
  #{interface.used_imports.join}
502
532
  export default #{interface.as_typescript}
@@ -505,7 +535,6 @@ module TypesFromSerializers
505
535
 
506
536
  def declaration_interface_definition(interface)
507
537
  <<~TS
508
- //
509
538
  // DO NOT MODIFY: This file was automatically generated by TypesFromSerializers.
510
539
  #{interface.used_imports.empty? ? "export {}\n" : interface.used_imports.join}
511
540
  declare global {
@@ -515,10 +544,5 @@ module TypesFromSerializers
515
544
  }
516
545
  TS
517
546
  end
518
-
519
- # Internal: Returns true if the cache key has changed since the last codegen.
520
- def stale?(file, cache_key_comment)
521
- @force_generation || file.gets != cache_key_comment
522
- end
523
547
  end
524
548
  end
@@ -2,5 +2,5 @@
2
2
 
3
3
  module TypesFromSerializers
4
4
  # Public: This library adheres to semantic versioning.
5
- VERSION = "2.6.1"
5
+ VERSION = "2.7.0"
6
6
  end
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: types_from_serializers
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.6.1
4
+ version: 2.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Máximo Mussini
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2026-07-03 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: railties
@@ -84,16 +83,16 @@ dependencies:
84
83
  name: simplecov
85
84
  requirement: !ruby/object:Gem::Requirement
86
85
  requirements:
87
- - - "<"
86
+ - - "~>"
88
87
  - !ruby/object:Gem::Version
89
- version: '0.18'
88
+ version: '0.22'
90
89
  type: :development
91
90
  prerelease: false
92
91
  version_requirements: !ruby/object:Gem::Requirement
93
92
  requirements:
94
- - - "<"
93
+ - - "~>"
95
94
  - !ruby/object:Gem::Version
96
- version: '0.18'
95
+ version: '0.22'
97
96
  - !ruby/object:Gem::Dependency
98
97
  name: standard
99
98
  requirement: !ruby/object:Gem::Requirement
@@ -228,7 +227,6 @@ homepage: https://github.com/ElMassimo/types_from_serializers
228
227
  licenses:
229
228
  - MIT
230
229
  metadata: {}
231
- post_install_message:
232
230
  rdoc_options: []
233
231
  require_paths:
234
232
  - lib
@@ -243,8 +241,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
243
241
  - !ruby/object:Gem::Version
244
242
  version: '0'
245
243
  requirements: []
246
- rubygems_version: 3.5.16
247
- signing_key:
244
+ rubygems_version: 4.0.20
248
245
  specification_version: 4
249
246
  summary: Generate TypeScript interfaces from your JSON serializers.
250
247
  test_files: []