Icarus-Mod-Tools 2.5.1 → 2.6.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 +17 -0
- data/Gemfile.lock +1 -1
- data/lib/icarus/mod/cli/sync.rb +52 -0
- data/lib/icarus/mod/firestore.rb +9 -1
- data/lib/icarus/mod/tools/baseinfo.rb +3 -1
- data/lib/icarus/mod/tools/sync/mods.rb +3 -3
- data/lib/icarus/mod/tools/sync/tools.rb +2 -2
- data/lib/icarus/mod/version.rb +1 -1
- 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: f02e414cd84c6117e8ff650a1048c16eac751dae9cfe99a52a17a3e58682d1b0
|
|
4
|
+
data.tar.gz: f14a5bf74a4fcd74be9c71aba1debf378d45af6bc54290b35c2d20236e17d3af
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 636dfd397c992d7dd15d464584734f7e599417e1145f0cf139ffcf2f8eec9e409b473c4deae9abae453a42abbfe1f04138b36440669ead6db5c4386b6e33b4ca
|
|
7
|
+
data.tar.gz: b5085429a592dfc1bd8bbf0fde991ade384afcd88aedd50c6f0969a8a786b4c3e98c740fda3053aa48c52ea734dfe74381d2d8797dde0b0e563abc467ec3e75d
|
data/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,23 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
## History (reverse chronological order)
|
|
6
6
|
|
|
7
|
+
### v2.6.0 - 2026-03-04
|
|
8
|
+
|
|
9
|
+
- Add `imt sync cleanup` command to remove duplicate entries from mods and tools collections
|
|
10
|
+
- Groups entries by name+author and keeps the most recently updated
|
|
11
|
+
- Supports `--dry-run` to preview what would be deleted
|
|
12
|
+
- Fix `find_info` to match by both name AND author, not just name
|
|
13
|
+
- Previously, mods/tools with the same name but different authors could cause incorrect deletion logic
|
|
14
|
+
- Now consistent with `info_array` deduplication and `find_by_type` lookups
|
|
15
|
+
|
|
16
|
+
### v2.5.2 - 2026-02-09
|
|
17
|
+
|
|
18
|
+
- Fix duplicate entries created during sync operations
|
|
19
|
+
- Update Firestore cache after creating new documents to prevent duplicate creation within the same sync run
|
|
20
|
+
- Deduplicate info arrays by name+author before processing (handles duplicate/equivalent modinfo URLs)
|
|
21
|
+
- Strip empty file entries (e.g., `pak: ""`) from database writes
|
|
22
|
+
- Fix `id=` setter on Baseinfo (was silently no-op via method_missing, now uses attr_accessor)
|
|
23
|
+
|
|
7
24
|
### v2.5.1 - 2026-01-31
|
|
8
25
|
|
|
9
26
|
- Fix `imt remove repo` failing to find repositories stored as full URLs
|
data/Gemfile.lock
CHANGED
data/lib/icarus/mod/cli/sync.rb
CHANGED
|
@@ -52,11 +52,63 @@ module Icarus
|
|
|
52
52
|
sync_list(:tools)
|
|
53
53
|
end
|
|
54
54
|
|
|
55
|
+
desc "cleanup", "Remove duplicate entries from mods and tools collections"
|
|
56
|
+
def cleanup
|
|
57
|
+
cleanup_duplicates(:mods)
|
|
58
|
+
cleanup_duplicates(:tools)
|
|
59
|
+
end
|
|
60
|
+
|
|
55
61
|
no_commands do
|
|
56
62
|
def firestore
|
|
57
63
|
$firestore ||= Firestore.new
|
|
58
64
|
end
|
|
59
65
|
|
|
66
|
+
def cleanup_duplicates(type)
|
|
67
|
+
singular_type = type.to_s.chomp("s").to_sym
|
|
68
|
+
collection = firestore.send(type)
|
|
69
|
+
|
|
70
|
+
puts "Scanning #{type} for duplicates..." if verbose?
|
|
71
|
+
|
|
72
|
+
# Group by [name, author]
|
|
73
|
+
grouped = collection.group_by { |item| [item.name, item.author] }
|
|
74
|
+
duplicates = grouped.select { |_, items| items.length > 1 }
|
|
75
|
+
|
|
76
|
+
if duplicates.empty?
|
|
77
|
+
puts "No duplicate #{type} found." if verbose?
|
|
78
|
+
return
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
puts "Found #{duplicates.length} duplicate #{singular_type}(s) to clean up:" if verbose?
|
|
82
|
+
|
|
83
|
+
duplicates.each do |key, items|
|
|
84
|
+
name, author = key
|
|
85
|
+
# Sort by updated_at descending, keep the most recent
|
|
86
|
+
sorted = items.sort_by { |item| item.updated_at || Time.at(0) }.reverse
|
|
87
|
+
keeper = sorted.first
|
|
88
|
+
to_delete = sorted[1..]
|
|
89
|
+
|
|
90
|
+
puts " #{author}/#{name}: #{items.length} entries" if verbose?
|
|
91
|
+
puts " Keeping: #{keeper.id} (updated: #{keeper.updated_at})" if verbose?
|
|
92
|
+
|
|
93
|
+
to_delete.each do |item|
|
|
94
|
+
if options[:dry_run]
|
|
95
|
+
puts Paint[" Would delete: #{item.id} (updated: #{item.updated_at})", :yellow] if verbose?
|
|
96
|
+
else
|
|
97
|
+
puts " Deleting: #{item.id} (updated: #{item.updated_at})" if verbose?
|
|
98
|
+
response = firestore.delete(singular_type, item)
|
|
99
|
+
puts " #{success_or_failure(response)}" if verbose > 1
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
deleted_count = duplicates.values.sum { |items| items.length - 1 }
|
|
105
|
+
if options[:dry_run]
|
|
106
|
+
puts Paint["Dry run; no changes made. Would have deleted #{deleted_count} duplicate #{type}.", :yellow] if verbose?
|
|
107
|
+
else
|
|
108
|
+
puts "Deleted #{deleted_count} duplicate #{type}." if verbose?
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
60
112
|
def success_or_failure(status)
|
|
61
113
|
format("%<status>10s", status: status ? Paint["Success", :green] : Paint["Failure", :red])
|
|
62
114
|
end
|
data/lib/icarus/mod/firestore.rb
CHANGED
|
@@ -106,7 +106,15 @@ module Icarus
|
|
|
106
106
|
|
|
107
107
|
return @client.doc("#{collections.send(type)}/#{doc_id}").set(payload.to_h, merge:) if doc_id
|
|
108
108
|
|
|
109
|
-
@client.col(collections.send(type)).add(payload.to_h)
|
|
109
|
+
doc_ref = @client.col(collections.send(type)).add(payload.to_h)
|
|
110
|
+
|
|
111
|
+
# Update cache to prevent duplicate creation within the same sync run
|
|
112
|
+
payload.id = doc_ref.document_id
|
|
113
|
+
cache_var = :"@#{type}"
|
|
114
|
+
cached_collection = instance_variable_get(cache_var)
|
|
115
|
+
cached_collection&.push(payload)
|
|
116
|
+
|
|
117
|
+
doc_ref
|
|
110
118
|
end
|
|
111
119
|
|
|
112
120
|
def pluralize(type)
|
|
@@ -5,7 +5,8 @@ module Icarus
|
|
|
5
5
|
module Tools
|
|
6
6
|
# Base class for Modinfo and Toolinfo
|
|
7
7
|
class Baseinfo
|
|
8
|
-
attr_reader :data
|
|
8
|
+
attr_reader :data
|
|
9
|
+
attr_accessor :id, :created_at, :updated_at
|
|
9
10
|
|
|
10
11
|
HASHKEYS = %i[name author version compatibility description files imageURL readmeURL].freeze
|
|
11
12
|
|
|
@@ -78,6 +79,7 @@ module Icarus
|
|
|
78
79
|
db_hash = HASHKEYS.each_with_object({}) { |key, hash| hash[key] = @data[key] }
|
|
79
80
|
|
|
80
81
|
db_hash[:version] = "1.0" if version.nil?
|
|
82
|
+
db_hash[:files] = db_hash[:files]&.reject { |_, url| url.nil? || url.to_s.strip.empty? }
|
|
81
83
|
|
|
82
84
|
db_hash
|
|
83
85
|
end
|
|
@@ -21,14 +21,14 @@ module Icarus
|
|
|
21
21
|
|
|
22
22
|
def info_array
|
|
23
23
|
@info_array ||= @firestore.modinfo.map do |url|
|
|
24
|
-
retrieve_from_url(url)[:mods].map { |mod| Icarus::Mod::Tools::Modinfo.new(mod) if
|
|
24
|
+
retrieve_from_url(url)[:mods].map { |mod| Icarus::Mod::Tools::Modinfo.new(mod) if /[a-z0-9]+/i.match?(mod[:name]) }
|
|
25
25
|
rescue Icarus::Mod::Tools::Sync::RequestFailed
|
|
26
26
|
warn "Skipped; Failed to retrieve #{url}"
|
|
27
27
|
next
|
|
28
28
|
rescue JSON::ParserError => e
|
|
29
29
|
warn "Skipped; Invalid JSON in #{url}: #{e.message}"
|
|
30
30
|
next
|
|
31
|
-
end.flatten.compact
|
|
31
|
+
end.flatten.compact.uniq { |mod| [mod.name, mod.author] }
|
|
32
32
|
end
|
|
33
33
|
|
|
34
34
|
def find(modinfo)
|
|
@@ -36,7 +36,7 @@ module Icarus
|
|
|
36
36
|
end
|
|
37
37
|
|
|
38
38
|
def find_info(modinfo)
|
|
39
|
-
@info_array.find { |mod| mod.name == modinfo.name }
|
|
39
|
+
@info_array.find { |mod| mod.name == modinfo.name && mod.author == modinfo.author }
|
|
40
40
|
end
|
|
41
41
|
|
|
42
42
|
def update(modinfo)
|
|
@@ -30,7 +30,7 @@ module Icarus
|
|
|
30
30
|
rescue JSON::ParserError => e
|
|
31
31
|
warn "Skipped; Invalid JSON in #{url}: #{e.message}"
|
|
32
32
|
next
|
|
33
|
-
end.flatten.compact
|
|
33
|
+
end.flatten.compact.uniq { |tool| [tool.name, tool.author] }
|
|
34
34
|
end
|
|
35
35
|
|
|
36
36
|
def find(toolinfo)
|
|
@@ -38,7 +38,7 @@ module Icarus
|
|
|
38
38
|
end
|
|
39
39
|
|
|
40
40
|
def find_info(toolinfo)
|
|
41
|
-
@info_array.find { |tool| tool.name == toolinfo.name }
|
|
41
|
+
@info_array.find { |tool| tool.name == toolinfo.name && tool.author == toolinfo.author }
|
|
42
42
|
end
|
|
43
43
|
|
|
44
44
|
def update(toolinfo)
|
data/lib/icarus/mod/version.rb
CHANGED