factorix 0.5.0 → 0.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 +25 -0
- data/README.md +1 -1
- data/lib/factorix/api/mod_download_api.rb +1 -1
- data/lib/factorix/api/mod_info.rb +2 -2
- data/lib/factorix/api/mod_management_api.rb +1 -1
- data/lib/factorix/api_credential.rb +1 -1
- data/lib/factorix/cli/commands/backup_support.rb +1 -1
- data/lib/factorix/cli/commands/base.rb +3 -3
- data/lib/factorix/cli/commands/cache/evict.rb +3 -3
- data/lib/factorix/cli/commands/cache/stat.rb +15 -15
- data/lib/factorix/cli/commands/command_wrapper.rb +5 -5
- data/lib/factorix/cli/commands/completion.rb +2 -3
- data/lib/factorix/cli/commands/confirmable.rb +1 -1
- data/lib/factorix/cli/commands/download_support.rb +2 -2
- data/lib/factorix/cli/commands/mod/check.rb +1 -1
- data/lib/factorix/cli/commands/mod/disable.rb +1 -1
- data/lib/factorix/cli/commands/mod/download.rb +5 -4
- data/lib/factorix/cli/commands/mod/edit.rb +9 -9
- data/lib/factorix/cli/commands/mod/enable.rb +1 -1
- data/lib/factorix/cli/commands/mod/image/add.rb +2 -2
- data/lib/factorix/cli/commands/mod/image/edit.rb +1 -1
- data/lib/factorix/cli/commands/mod/image/list.rb +4 -4
- data/lib/factorix/cli/commands/mod/install.rb +5 -4
- data/lib/factorix/cli/commands/mod/list.rb +7 -7
- data/lib/factorix/cli/commands/mod/search.rb +11 -9
- data/lib/factorix/cli/commands/mod/settings/dump.rb +3 -3
- data/lib/factorix/cli/commands/mod/settings/restore.rb +2 -2
- data/lib/factorix/cli/commands/mod/show.rb +20 -20
- data/lib/factorix/cli/commands/mod/sync.rb +6 -5
- data/lib/factorix/cli/commands/mod/uninstall.rb +1 -1
- data/lib/factorix/cli/commands/mod/update.rb +7 -6
- data/lib/factorix/cli/commands/mod/upload.rb +6 -6
- data/lib/factorix/cli/commands/path.rb +2 -2
- data/lib/factorix/cli/commands/version.rb +1 -1
- data/lib/factorix/{application.rb → container.rb} +8 -85
- data/lib/factorix/dependency/parser.rb +1 -1
- data/lib/factorix/http/client.rb +3 -3
- data/lib/factorix/info_json.rb +2 -2
- data/lib/factorix/mod_list.rb +2 -2
- data/lib/factorix/mod_settings.rb +2 -2
- data/lib/factorix/runtime/user_configurable.rb +9 -9
- data/lib/factorix/service_credential.rb +3 -3
- data/lib/factorix/version.rb +1 -1
- data/lib/factorix.rb +118 -1
- data/sig/factorix/container.rbs +15 -0
- data/sig/factorix.rbs +99 -0
- metadata +4 -4
- data/sig/factorix/application.rbs +0 -86
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1eb581275b20274cebfa09669eb5f0111ed00f358d5a68947333b5990eb04d56
|
|
4
|
+
data.tar.gz: 2a57a400c94476e860c2647af1e8fb3f845b231b1e377e265bfcc1d1de11d7ef
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9134480ff3cd2675216f6fc9407bb0719467c2488a43449c727294f5871289cb955b6b5a20257314e11d562a60e2eea8edf81bf72c8df739055675142dff8ad5
|
|
7
|
+
data.tar.gz: 15630f358ac2dbe3ae85b3a0798fdea532b7d9cba43352ffc982c74cf6e5eea1a264329f9543c26100618c7d3e45c2b6cc8a69bf5aca203333a2c77157b68571
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,30 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.6.0] - 2026-01-18
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- Reorganize configuration and DI container interfaces (#7, #9)
|
|
8
|
+
- `Factorix::Application` renamed to `Factorix::Container` (DI container only)
|
|
9
|
+
- Configuration interface (`config`, `configure`, `load_config`) moved to `Factorix` module
|
|
10
|
+
- Use `Factorix.configure { |c| ... }` instead of `Factorix::Container.configure { |c| ... }`
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- Fix integer CLI options being parsed as strings after dry-cli 1.4.0 update (#12)
|
|
15
|
+
|
|
16
|
+
### Deprecated
|
|
17
|
+
|
|
18
|
+
- `Factorix::Application` still works but emits deprecation warnings; will be removed in v1.0
|
|
19
|
+
- DI methods (`[]`, `resolve`, `register`) delegate to `Factorix::Container`
|
|
20
|
+
- Configuration methods (`config`, `configure`) delegate to `Factorix`
|
|
21
|
+
|
|
22
|
+
## [0.5.1] - 2026-01-13
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
|
|
26
|
+
- Fix `completion` command failing without arguments after dry-cli 1.4.0 update (#4)
|
|
27
|
+
|
|
3
28
|
## [0.5.0] - 2025-12-26
|
|
4
29
|
|
|
5
30
|
### Added
|
data/README.md
CHANGED
|
@@ -65,7 +65,7 @@ $EDITOR ~/.config/factorix/config.rb
|
|
|
65
65
|
|
|
66
66
|
**Example configuration:**
|
|
67
67
|
```ruby
|
|
68
|
-
Factorix
|
|
68
|
+
Factorix.configure do |config|
|
|
69
69
|
config.runtime.executable_path = "/Applications/Factorio.app/Contents/MacOS/factorio"
|
|
70
70
|
config.runtime.user_dir = "#{Dir.home}/Library/Application Support/factorio"
|
|
71
71
|
config.runtime.data_dir = "/Applications/Factorio.app/Contents/data"
|
|
@@ -51,7 +51,7 @@ module Factorix
|
|
|
51
51
|
return @service_credential if defined?(@service_credential)
|
|
52
52
|
|
|
53
53
|
@service_credential_mutex.synchronize do
|
|
54
|
-
@service_credential ||=
|
|
54
|
+
@service_credential ||= Container[:service_credential]
|
|
55
55
|
end
|
|
56
56
|
end
|
|
57
57
|
|
|
@@ -114,7 +114,7 @@ module Factorix
|
|
|
114
114
|
|
|
115
115
|
URI(value)
|
|
116
116
|
rescue URI::InvalidURIError => e
|
|
117
|
-
|
|
117
|
+
Container[:logger].warn("Skipping invalid URI '#{value}': #{e.message}")
|
|
118
118
|
nil
|
|
119
119
|
end
|
|
120
120
|
end
|
|
@@ -145,7 +145,7 @@ module Factorix
|
|
|
145
145
|
Release[**r]
|
|
146
146
|
rescue RangeError => e
|
|
147
147
|
# Skip releases with invalid version numbers
|
|
148
|
-
|
|
148
|
+
Container[:logger].warn("Skipping release #{name}@#{r[:version]}: #{e.message}")
|
|
149
149
|
nil
|
|
150
150
|
end
|
|
151
151
|
}
|
|
@@ -22,7 +22,7 @@ module Factorix
|
|
|
22
22
|
# @return [APICredential] new instance with API key from environment
|
|
23
23
|
# @raise [CredentialError] if API key is not set in environment
|
|
24
24
|
def self.load
|
|
25
|
-
logger =
|
|
25
|
+
logger = Container["logger"]
|
|
26
26
|
logger.debug "Loading API credentials from environment"
|
|
27
27
|
|
|
28
28
|
api_key = ENV.fetch(ENV_API_KEY, nil)
|
|
@@ -15,7 +15,7 @@ module Factorix
|
|
|
15
15
|
# @param base [Class] the class prepending this module
|
|
16
16
|
def self.prepended(base)
|
|
17
17
|
base.class_eval do
|
|
18
|
-
option :backup_extension,
|
|
18
|
+
option :backup_extension, default: ".bak", desc: "Backup file extension"
|
|
19
19
|
end
|
|
20
20
|
end
|
|
21
21
|
|
|
@@ -68,8 +68,8 @@ module Factorix
|
|
|
68
68
|
def self.backup_support! = prepend BackupSupport
|
|
69
69
|
|
|
70
70
|
# Common options available to all commands
|
|
71
|
-
option :config_path,
|
|
72
|
-
option :log_level,
|
|
71
|
+
option :config_path, aliases: ["-c"], desc: "Path to configuration file"
|
|
72
|
+
option :log_level, values: %w[debug info warn error fatal], desc: "Set log level"
|
|
73
73
|
option :quiet, type: :flag, default: false, aliases: ["-q"], desc: "Suppress non-essential output"
|
|
74
74
|
|
|
75
75
|
private def say(message, prefix: "")
|
|
@@ -78,7 +78,7 @@ module Factorix
|
|
|
78
78
|
resolved_prefix = EMOJI_PREFIXES.fetch(prefix) { prefix.to_s }
|
|
79
79
|
output = resolved_prefix.empty? ? message : "#{resolved_prefix} #{message}"
|
|
80
80
|
style = STYLES.fetch(prefix, PLAIN)
|
|
81
|
-
puts style[output]
|
|
81
|
+
out.puts style[output]
|
|
82
82
|
end
|
|
83
83
|
|
|
84
84
|
private def quiet?
|
|
@@ -36,7 +36,7 @@ module Factorix
|
|
|
36
36
|
|
|
37
37
|
option :all, type: :flag, default: false, desc: "Remove all entries"
|
|
38
38
|
option :expired, type: :flag, default: false, desc: "Remove expired entries only"
|
|
39
|
-
option :older_than,
|
|
39
|
+
option :older_than, default: nil, desc: "Remove entries older than AGE (e.g., 30s, 5m, 2h, 7d)"
|
|
40
40
|
|
|
41
41
|
# Execute the cache evict command
|
|
42
42
|
#
|
|
@@ -95,7 +95,7 @@ module Factorix
|
|
|
95
95
|
# @return [Array<Symbol>] resolved cache names
|
|
96
96
|
# @raise [InvalidArgumentError] if unknown cache name specified
|
|
97
97
|
private def resolve_cache_names(caches)
|
|
98
|
-
all_caches =
|
|
98
|
+
all_caches = Factorix.config.cache.values.keys
|
|
99
99
|
|
|
100
100
|
return all_caches if caches.nil? || caches.empty?
|
|
101
101
|
|
|
@@ -114,7 +114,7 @@ module Factorix
|
|
|
114
114
|
# @param expired [Boolean] remove expired entries only
|
|
115
115
|
# @return [Hash] eviction result with :count and :size
|
|
116
116
|
private def evict_cache(name, all:, expired:)
|
|
117
|
-
config =
|
|
117
|
+
config = Factorix.config.cache.public_send(name)
|
|
118
118
|
cache_dir = config.dir
|
|
119
119
|
ttl = config.ttl
|
|
120
120
|
|
|
@@ -8,7 +8,7 @@ module Factorix
|
|
|
8
8
|
module Cache
|
|
9
9
|
# Display cache statistics
|
|
10
10
|
#
|
|
11
|
-
# This command
|
|
11
|
+
# This command outout.puts statistics for all cache stores
|
|
12
12
|
# in a human-readable or JSON format.
|
|
13
13
|
#
|
|
14
14
|
# @example
|
|
@@ -42,18 +42,18 @@ module Factorix
|
|
|
42
42
|
logger.debug("Collecting cache statistics")
|
|
43
43
|
|
|
44
44
|
@now = Time.now
|
|
45
|
-
cache_names =
|
|
45
|
+
cache_names = Factorix.config.cache.values.keys
|
|
46
46
|
stats = cache_names.to_h {|name| [name, collect_stats(name)] }
|
|
47
47
|
|
|
48
48
|
if json
|
|
49
|
-
puts JSON.pretty_generate(stats)
|
|
49
|
+
out.puts JSON.pretty_generate(stats)
|
|
50
50
|
else
|
|
51
51
|
output_text(stats)
|
|
52
52
|
end
|
|
53
53
|
end
|
|
54
54
|
|
|
55
55
|
private def collect_stats(name)
|
|
56
|
-
config =
|
|
56
|
+
config = Factorix.config.cache.public_send(name)
|
|
57
57
|
cache_dir = config.dir
|
|
58
58
|
|
|
59
59
|
entries = scan_entries(cache_dir, config.ttl)
|
|
@@ -142,8 +142,8 @@ module Factorix
|
|
|
142
142
|
# @return [void]
|
|
143
143
|
private def output_text(stats)
|
|
144
144
|
stats.each_with_index do |(name, data), index|
|
|
145
|
-
puts if index > 0
|
|
146
|
-
puts "#{name}:"
|
|
145
|
+
out.puts if index > 0
|
|
146
|
+
out.puts "#{name}:"
|
|
147
147
|
output_cache_stats(data)
|
|
148
148
|
end
|
|
149
149
|
end
|
|
@@ -153,26 +153,26 @@ module Factorix
|
|
|
153
153
|
# @param data [Hash] cache statistics
|
|
154
154
|
# @return [void]
|
|
155
155
|
private def output_cache_stats(data)
|
|
156
|
-
puts " Directory: #{data[:directory]}"
|
|
157
|
-
puts " TTL: #{format_ttl(data[:ttl])}"
|
|
158
|
-
puts " Max file size: #{format_size(data[:max_file_size])}"
|
|
159
|
-
puts " Compression: #{format_compression(data[:compression_threshold])}"
|
|
156
|
+
out.puts " Directory: #{data[:directory]}"
|
|
157
|
+
out.puts " TTL: #{format_ttl(data[:ttl])}"
|
|
158
|
+
out.puts " Max file size: #{format_size(data[:max_file_size])}"
|
|
159
|
+
out.puts " Compression: #{format_compression(data[:compression_threshold])}"
|
|
160
160
|
|
|
161
161
|
entries = data[:entries]
|
|
162
162
|
valid_pct = entries[:total] > 0 ? (Float(entries[:valid]) / entries[:total] * 100) : 0.0
|
|
163
|
-
puts " Entries: #{entries[:valid]} / #{entries[:total]} (#{"%.1f" % valid_pct}% valid)"
|
|
163
|
+
out.puts " Entries: #{entries[:valid]} / #{entries[:total]} (#{"%.1f" % valid_pct}% valid)"
|
|
164
164
|
|
|
165
165
|
size = data[:size]
|
|
166
|
-
puts " Size: #{format_size(size[:total])} (avg #{format_size(size[:avg])})"
|
|
166
|
+
out.puts " Size: #{format_size(size[:total])} (avg #{format_size(size[:avg])})"
|
|
167
167
|
|
|
168
168
|
age = data[:age]
|
|
169
169
|
if age[:oldest]
|
|
170
|
-
puts " Age: #{format_duration(age[:newest])} - #{format_duration(age[:oldest])} (avg #{format_duration(age[:avg])})"
|
|
170
|
+
out.puts " Age: #{format_duration(age[:newest])} - #{format_duration(age[:oldest])} (avg #{format_duration(age[:avg])})"
|
|
171
171
|
else
|
|
172
|
-
puts " Age: -"
|
|
172
|
+
out.puts " Age: -"
|
|
173
173
|
end
|
|
174
174
|
|
|
175
|
-
puts " Stale locks: #{data[:stale_locks]}"
|
|
175
|
+
out.puts " Stale locks: #{data[:stale_locks]}"
|
|
176
176
|
end
|
|
177
177
|
|
|
178
178
|
# Format TTL value for display
|
|
@@ -23,14 +23,14 @@ module Factorix
|
|
|
23
23
|
super
|
|
24
24
|
rescue Error => e
|
|
25
25
|
# Expected errors (validation failures, missing dependencies, etc.)
|
|
26
|
-
log =
|
|
26
|
+
log = Container[:logger]
|
|
27
27
|
log.warn(e.message)
|
|
28
28
|
log.debug(e)
|
|
29
29
|
say "Error: #{e.message}", prefix: :error unless @quiet
|
|
30
30
|
raise # Re-raise for exe/factorix to handle exit code
|
|
31
31
|
rescue => e
|
|
32
32
|
# Unexpected errors (bugs, system failures, etc.)
|
|
33
|
-
log =
|
|
33
|
+
log = Container[:logger]
|
|
34
34
|
log.error(e)
|
|
35
35
|
say "Unexpected error: #{e.message}", prefix: :error unless @quiet
|
|
36
36
|
raise # Re-raise for exe/factorix to handle exit code
|
|
@@ -40,7 +40,7 @@ module Factorix
|
|
|
40
40
|
path = resolve_config_path(explicit_path)
|
|
41
41
|
return unless path
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
Factorix.load_config(path)
|
|
44
44
|
end
|
|
45
45
|
|
|
46
46
|
# Resolves which config path to use
|
|
@@ -50,14 +50,14 @@ module Factorix
|
|
|
50
50
|
return Pathname(explicit_path) if explicit_path
|
|
51
51
|
return Pathname(ENV.fetch("FACTORIX_CONFIG")) if ENV["FACTORIX_CONFIG"]
|
|
52
52
|
|
|
53
|
-
default_path =
|
|
53
|
+
default_path = Container[:runtime].factorix_config_path
|
|
54
54
|
default_path.exist? ? default_path : nil
|
|
55
55
|
end
|
|
56
56
|
|
|
57
57
|
# Sets the application logger's level
|
|
58
58
|
# @param level [String] log level (debug, info, warn, error, fatal)
|
|
59
59
|
private def log_level!(level)
|
|
60
|
-
logger =
|
|
60
|
+
logger = Container[:logger]
|
|
61
61
|
level_constant = Logger.const_get(level.upcase)
|
|
62
62
|
|
|
63
63
|
# Change only the File backend (first backend) level
|
|
@@ -28,9 +28,8 @@ module Factorix
|
|
|
28
28
|
desc "Generate shell completion script"
|
|
29
29
|
|
|
30
30
|
argument :shell,
|
|
31
|
-
type: :string,
|
|
32
31
|
required: false,
|
|
33
|
-
values: SUPPORTED_SHELLS.keys,
|
|
32
|
+
values: [nil] + SUPPORTED_SHELLS.keys,
|
|
34
33
|
desc: "Shell type. Defaults to current shell from $SHELL"
|
|
35
34
|
|
|
36
35
|
example [
|
|
@@ -53,7 +52,7 @@ module Factorix
|
|
|
53
52
|
script_path = COMPLETION_DIR / SUPPORTED_SHELLS[shell_type]
|
|
54
53
|
raise ConfigurationError, "#{shell_type.capitalize} completion script not found" unless script_path.exist?
|
|
55
54
|
|
|
56
|
-
puts script_path.read
|
|
55
|
+
out.puts script_path.read
|
|
57
56
|
end
|
|
58
57
|
|
|
59
58
|
# Detect shell type from SHELL environment variable
|
|
@@ -41,7 +41,7 @@ module Factorix
|
|
|
41
41
|
raise InvalidOperationError, "Cannot prompt for confirmation in quiet mode. Use --yes to proceed automatically."
|
|
42
42
|
end
|
|
43
43
|
|
|
44
|
-
print "#{message} [y/N] "
|
|
44
|
+
out.print "#{message} [y/N] "
|
|
45
45
|
response = $stdin.gets&.strip&.downcase
|
|
46
46
|
|
|
47
47
|
# Only explicit y or yes means yes (default is no for safety)
|
|
@@ -91,13 +91,13 @@ module Factorix
|
|
|
91
91
|
# @param jobs [Integer] Number of parallel downloads
|
|
92
92
|
# @return [void]
|
|
93
93
|
private def download_mods(targets, jobs)
|
|
94
|
-
multi_presenter = Progress::MultiPresenter.new(title: "\u{1F4E5}\u{FE0E} Downloads")
|
|
94
|
+
multi_presenter = Progress::MultiPresenter.new(title: "\u{1F4E5}\u{FE0E} Downloads", output: err)
|
|
95
95
|
|
|
96
96
|
pool = Concurrent::FixedThreadPool.new(jobs)
|
|
97
97
|
|
|
98
98
|
futures = targets.map {|target|
|
|
99
99
|
Concurrent::Future.execute(executor: pool) do
|
|
100
|
-
thread_portal =
|
|
100
|
+
thread_portal = Container[:portal]
|
|
101
101
|
thread_downloader = thread_portal.mod_download_api.downloader
|
|
102
102
|
|
|
103
103
|
presenter = multi_presenter.register(
|
|
@@ -24,7 +24,7 @@ module Factorix
|
|
|
24
24
|
# @return [void]
|
|
25
25
|
def call(**)
|
|
26
26
|
mod_list = MODList.load
|
|
27
|
-
presenter = Progress::Presenter.new(title: "\u{1F50D}\u{FE0E} Scanning MOD(s)", output:
|
|
27
|
+
presenter = Progress::Presenter.new(title: "\u{1F50D}\u{FE0E} Scanning MOD(s)", output: err)
|
|
28
28
|
handler = Progress::ScanHandler.new(presenter)
|
|
29
29
|
installed_mods = InstalledMOD.all(handler:)
|
|
30
30
|
graph = Dependency::Graph::Builder.build(installed_mods:, mod_list:)
|
|
@@ -39,7 +39,7 @@ module Factorix
|
|
|
39
39
|
|
|
40
40
|
# Without validation to allow fixing issues
|
|
41
41
|
mod_list = MODList.load
|
|
42
|
-
presenter = Progress::Presenter.new(title: "\u{1F50D}\u{FE0E} Scanning MOD(s)", output:
|
|
42
|
+
presenter = Progress::Presenter.new(title: "\u{1F50D}\u{FE0E} Scanning MOD(s)", output: err)
|
|
43
43
|
handler = Progress::ScanHandler.new(presenter)
|
|
44
44
|
installed_mods = InstalledMOD.all(handler:)
|
|
45
45
|
graph = Dependency::Graph::Builder.build(installed_mods:, mod_list:)
|
|
@@ -29,8 +29,8 @@ module Factorix
|
|
|
29
29
|
]
|
|
30
30
|
|
|
31
31
|
argument :mod_specs, type: :array, required: true, desc: "MOD specifications (name@version or name@latest or name)"
|
|
32
|
-
option :directory,
|
|
33
|
-
option :jobs,
|
|
32
|
+
option :directory, aliases: ["-d"], default: ".", desc: "Download directory"
|
|
33
|
+
option :jobs, aliases: ["-j"], default: "4", desc: "Number of parallel downloads"
|
|
34
34
|
option :recursive, type: :flag, aliases: ["-r"], default: false, desc: "Include required dependencies recursively"
|
|
35
35
|
|
|
36
36
|
# Execute the download command
|
|
@@ -40,7 +40,8 @@ module Factorix
|
|
|
40
40
|
# @param jobs [Integer] Number of parallel downloads
|
|
41
41
|
# @param recursive [Boolean] Include required dependencies recursively
|
|
42
42
|
# @return [void]
|
|
43
|
-
def call(mod_specs:, directory: ".", jobs: 4, recursive: false, **)
|
|
43
|
+
def call(mod_specs:, directory: ".", jobs: "4", recursive: false, **)
|
|
44
|
+
jobs = Integer(jobs)
|
|
44
45
|
download_dir = Pathname(directory).expand_path
|
|
45
46
|
|
|
46
47
|
raise DirectoryNotFoundError, "Download directory does not exist: #{download_dir}" unless download_dir.exist?
|
|
@@ -69,7 +70,7 @@ module Factorix
|
|
|
69
70
|
# @param recursive [Boolean] Include dependencies
|
|
70
71
|
# @return [Array<Hash>] Download targets with MOD info and releases
|
|
71
72
|
private def plan_download(mod_specs, download_dir, jobs, recursive)
|
|
72
|
-
presenter = Progress::Presenter.new(title: "\u{1F50D}\u{FE0E} Fetching MOD info", output:
|
|
73
|
+
presenter = Progress::Presenter.new(title: "\u{1F50D}\u{FE0E} Fetching MOD info", output: err)
|
|
73
74
|
|
|
74
75
|
target_infos = fetch_target_mod_info(mod_specs, jobs, presenter)
|
|
75
76
|
|
|
@@ -19,16 +19,16 @@ module Factorix
|
|
|
19
19
|
"some-mod --deprecated # Mark as deprecated"
|
|
20
20
|
]
|
|
21
21
|
|
|
22
|
-
argument :mod_name,
|
|
23
|
-
option :description,
|
|
24
|
-
option :summary,
|
|
25
|
-
option :title,
|
|
26
|
-
option :category,
|
|
22
|
+
argument :mod_name, required: true, desc: "MOD name"
|
|
23
|
+
option :description, desc: "Markdown description"
|
|
24
|
+
option :summary, desc: "Brief description"
|
|
25
|
+
option :title, desc: "MOD title"
|
|
26
|
+
option :category, desc: "MOD category"
|
|
27
27
|
option :tags, type: :array, desc: "Array of tags"
|
|
28
|
-
option :license,
|
|
29
|
-
option :homepage,
|
|
30
|
-
option :source_url,
|
|
31
|
-
option :faq,
|
|
28
|
+
option :license, desc: "License identifier"
|
|
29
|
+
option :homepage, desc: "Homepage URL"
|
|
30
|
+
option :source_url, desc: "Repository URL"
|
|
31
|
+
option :faq, desc: "FAQ text"
|
|
32
32
|
option :deprecated, type: :boolean, desc: "Deprecation flag"
|
|
33
33
|
|
|
34
34
|
# Execute the edit command
|
|
@@ -33,7 +33,7 @@ module Factorix
|
|
|
33
33
|
def call(mod_names:, **)
|
|
34
34
|
# Load current state (without validation to allow fixing issues)
|
|
35
35
|
mod_list = MODList.load
|
|
36
|
-
presenter = Progress::Presenter.new(title: "\u{1F50D}\u{FE0E} Scanning MOD(s)", output:
|
|
36
|
+
presenter = Progress::Presenter.new(title: "\u{1F50D}\u{FE0E} Scanning MOD(s)", output: err)
|
|
37
37
|
handler = Progress::ScanHandler.new(presenter)
|
|
38
38
|
installed_mods = InstalledMOD.all(handler:)
|
|
39
39
|
graph = Dependency::Graph::Builder.build(installed_mods:, mod_list:)
|
|
@@ -18,8 +18,8 @@ module Factorix
|
|
|
18
18
|
"some-mod screenshot.png # Add image to MOD"
|
|
19
19
|
]
|
|
20
20
|
|
|
21
|
-
argument :mod_name,
|
|
22
|
-
argument :image_file,
|
|
21
|
+
argument :mod_name, required: true, desc: "MOD name"
|
|
22
|
+
argument :image_file, required: true, desc: "Path to image file"
|
|
23
23
|
|
|
24
24
|
# Execute the add command
|
|
25
25
|
#
|
|
@@ -18,7 +18,7 @@ module Factorix
|
|
|
18
18
|
"some-mod abc123 def456 # Set image order (IDs from 'image list')"
|
|
19
19
|
]
|
|
20
20
|
|
|
21
|
-
argument :mod_name,
|
|
21
|
+
argument :mod_name, required: true, desc: "MOD name"
|
|
22
22
|
argument :image_ids, type: :array, required: true, desc: "Image IDs in desired order"
|
|
23
23
|
|
|
24
24
|
# Execute the edit command
|
|
@@ -19,7 +19,7 @@ module Factorix
|
|
|
19
19
|
"some-mod --json # List images in JSON format"
|
|
20
20
|
]
|
|
21
21
|
|
|
22
|
-
argument :mod_name,
|
|
22
|
+
argument :mod_name, required: true, desc: "MOD name"
|
|
23
23
|
|
|
24
24
|
option :json, type: :flag, default: false, desc: "Output in JSON format"
|
|
25
25
|
|
|
@@ -45,7 +45,7 @@ module Factorix
|
|
|
45
45
|
end
|
|
46
46
|
|
|
47
47
|
if json
|
|
48
|
-
puts JSON.pretty_generate(images)
|
|
48
|
+
out.puts JSON.pretty_generate(images)
|
|
49
49
|
else
|
|
50
50
|
output_table(images)
|
|
51
51
|
end
|
|
@@ -60,10 +60,10 @@ module Factorix
|
|
|
60
60
|
id_width = [images.map {|i| i[:id].length }.max, 2].max
|
|
61
61
|
thumb_width = [images.map {|i| i[:thumbnail].length }.max, 9].max
|
|
62
62
|
|
|
63
|
-
puts "%-#{id_width}s %-#{thumb_width}s %s" % %w[ID THUMBNAIL URL]
|
|
63
|
+
out.puts "%-#{id_width}s %-#{thumb_width}s %s" % %w[ID THUMBNAIL URL]
|
|
64
64
|
|
|
65
65
|
images.each do |image|
|
|
66
|
-
puts "%-#{id_width}s %-#{thumb_width}s %s" % [image[:id], image[:thumbnail], image[:url]]
|
|
66
|
+
out.puts "%-#{id_width}s %-#{thumb_width}s %s" % [image[:id], image[:thumbnail], image[:url]]
|
|
67
67
|
end
|
|
68
68
|
end
|
|
69
69
|
end
|
|
@@ -34,17 +34,18 @@ module Factorix
|
|
|
34
34
|
]
|
|
35
35
|
|
|
36
36
|
argument :mod_specs, type: :array, required: true, desc: "MOD specifications (name@version or name@latest or name)"
|
|
37
|
-
option :jobs,
|
|
37
|
+
option :jobs, aliases: ["-j"], default: "4", desc: "Number of parallel downloads"
|
|
38
38
|
|
|
39
39
|
# Execute the install command
|
|
40
40
|
#
|
|
41
41
|
# @param mod_specs [Array<String>] MOD specifications
|
|
42
42
|
# @param jobs [Integer] Number of parallel downloads
|
|
43
43
|
# @return [void]
|
|
44
|
-
def call(mod_specs:, jobs: 4, **)
|
|
44
|
+
def call(mod_specs:, jobs: "4", **)
|
|
45
|
+
jobs = Integer(jobs)
|
|
45
46
|
# Load current state (without validation to allow fixing issues)
|
|
46
47
|
mod_list = MODList.load
|
|
47
|
-
presenter = Progress::Presenter.new(title: "\u{1F50D}\u{FE0E} Scanning MOD(s)", output:
|
|
48
|
+
presenter = Progress::Presenter.new(title: "\u{1F50D}\u{FE0E} Scanning MOD(s)", output: err)
|
|
48
49
|
handler = Progress::ScanHandler.new(presenter)
|
|
49
50
|
installed_mods = InstalledMOD.all(handler:)
|
|
50
51
|
graph = Dependency::Graph::Builder.build(installed_mods:, mod_list:)
|
|
@@ -128,7 +129,7 @@ module Factorix
|
|
|
128
129
|
# @return [Array<Hash>] Installation targets with MOD info and releases
|
|
129
130
|
private def plan_installation(mod_specs, graph, jobs)
|
|
130
131
|
# Create progress presenter for info fetching
|
|
131
|
-
presenter = Progress::Presenter.new(title: "\u{1F50D}\u{FE0E} Fetching MOD info", output:
|
|
132
|
+
presenter = Progress::Presenter.new(title: "\u{1F50D}\u{FE0E} Fetching MOD info", output: err)
|
|
132
133
|
|
|
133
134
|
# Phase 1: Fetch info for target MODs
|
|
134
135
|
target_infos = fetch_target_mod_info(mod_specs, jobs, presenter)
|
|
@@ -80,7 +80,7 @@ module Factorix
|
|
|
80
80
|
validate_filter_options!(enabled:, disabled:, errors:, outdated:)
|
|
81
81
|
|
|
82
82
|
mod_list = MODList.load
|
|
83
|
-
presenter = Progress::Presenter.new(title: "\u{1F50D}\u{FE0E} Scanning MOD(s)", output:
|
|
83
|
+
presenter = Progress::Presenter.new(title: "\u{1F50D}\u{FE0E} Scanning MOD(s)", output: err)
|
|
84
84
|
handler = Progress::ScanHandler.new(presenter)
|
|
85
85
|
installed_mods = InstalledMOD.all(handler:)
|
|
86
86
|
graph = Dependency::Graph::Builder.build(installed_mods:, mod_list:)
|
|
@@ -226,7 +226,7 @@ module Factorix
|
|
|
226
226
|
}
|
|
227
227
|
|
|
228
228
|
# Only show progress for MOD(s) that need API calls
|
|
229
|
-
presenter = Progress::Presenter.new(title: "\u{1F50D}\u{FE0E} Checking for updates", output:
|
|
229
|
+
presenter = Progress::Presenter.new(title: "\u{1F50D}\u{FE0E} Checking for updates", output: err)
|
|
230
230
|
presenter.start(total: regular_mods.size)
|
|
231
231
|
|
|
232
232
|
pool = Concurrent::FixedThreadPool.new(DEFAULT_JOBS)
|
|
@@ -307,19 +307,19 @@ module Factorix
|
|
|
307
307
|
latest_width = [mod_infos.map {|m| m.latest_version&.to_s&.length || 0 }.max, 6].max
|
|
308
308
|
|
|
309
309
|
# Header with LATEST column
|
|
310
|
-
puts "%-#{name_width}s %-#{version_width}s %-#{latest_width}s %s" % %w[NAME VERSION LATEST STATUS]
|
|
310
|
+
out.puts "%-#{name_width}s %-#{version_width}s %-#{latest_width}s %s" % %w[NAME VERSION LATEST STATUS]
|
|
311
311
|
|
|
312
312
|
# Rows with LATEST column
|
|
313
313
|
mod_infos.each do |info|
|
|
314
|
-
puts "%-#{name_width}s %-#{version_width}s %-#{latest_width}s %s" % [info.name, info.version, info.latest_version, info.status]
|
|
314
|
+
out.puts "%-#{name_width}s %-#{version_width}s %-#{latest_width}s %s" % [info.name, info.version, info.latest_version, info.status]
|
|
315
315
|
end
|
|
316
316
|
else
|
|
317
317
|
# Header
|
|
318
|
-
puts "%-#{name_width}s %-#{version_width}s %s" % %w[NAME VERSION STATUS]
|
|
318
|
+
out.puts "%-#{name_width}s %-#{version_width}s %s" % %w[NAME VERSION STATUS]
|
|
319
319
|
|
|
320
320
|
# Rows
|
|
321
321
|
mod_infos.each do |info|
|
|
322
|
-
puts "%-#{name_width}s %-#{version_width}s %s" % [info.name, info.version, info.status]
|
|
322
|
+
out.puts "%-#{name_width}s %-#{version_width}s %s" % [info.name, info.version, info.status]
|
|
323
323
|
end
|
|
324
324
|
end
|
|
325
325
|
|
|
@@ -363,7 +363,7 @@ module Factorix
|
|
|
363
363
|
end
|
|
364
364
|
}
|
|
365
365
|
|
|
366
|
-
puts JSON.pretty_generate(data)
|
|
366
|
+
out.puts JSON.pretty_generate(data)
|
|
367
367
|
end
|
|
368
368
|
end
|
|
369
369
|
end
|
|
@@ -29,11 +29,11 @@ module Factorix
|
|
|
29
29
|
argument :mod_names, type: :array, required: false, default: [], desc: "MOD names to search"
|
|
30
30
|
|
|
31
31
|
option :hide_deprecated, type: :boolean, default: true, desc: "Hide deprecated MOD(s)"
|
|
32
|
-
option :page,
|
|
33
|
-
option :page_size,
|
|
34
|
-
option :sort,
|
|
35
|
-
option :sort_order,
|
|
36
|
-
option :version,
|
|
32
|
+
option :page, default: "1", desc: "Page number"
|
|
33
|
+
option :page_size, default: "25", desc: "Results per page (max 500)"
|
|
34
|
+
option :sort, values: %w[name created_at updated_at], desc: "Sort field"
|
|
35
|
+
option :sort_order, values: %w[asc desc], desc: "Sort order"
|
|
36
|
+
option :version, desc: "Filter by Factorio version (default: installed version)"
|
|
37
37
|
option :json, type: :flag, default: false, desc: "Output in JSON format"
|
|
38
38
|
|
|
39
39
|
# Execute the search command
|
|
@@ -47,7 +47,9 @@ module Factorix
|
|
|
47
47
|
# @param version [String, nil] Factorio version filter
|
|
48
48
|
# @param json [Boolean] Output in JSON format
|
|
49
49
|
# @return [void]
|
|
50
|
-
def call(mod_names: [], hide_deprecated: true, page: 1, page_size: 25, sort: nil, sort_order: nil, version: nil, json: false, **)
|
|
50
|
+
def call(mod_names: [], hide_deprecated: true, page: "1", page_size: "25", sort: nil, sort_order: nil, version: nil, json: false, **)
|
|
51
|
+
page = Integer(page)
|
|
52
|
+
page_size = Integer(page_size)
|
|
51
53
|
version ||= default_factorio_version
|
|
52
54
|
|
|
53
55
|
mods = portal.list_mods(*mod_names, hide_deprecated: hide_deprecated || nil, page:, page_size:, sort:, sort_order:, version:)
|
|
@@ -60,7 +62,7 @@ module Factorix
|
|
|
60
62
|
end
|
|
61
63
|
|
|
62
64
|
private def output_json(mods)
|
|
63
|
-
puts JSON.pretty_generate(mods.map {|mod| mod_to_hash(mod) })
|
|
65
|
+
out.puts JSON.pretty_generate(mods.map {|mod| mod_to_hash(mod) })
|
|
64
66
|
end
|
|
65
67
|
|
|
66
68
|
private def mod_to_hash(mod)
|
|
@@ -99,10 +101,10 @@ module Factorix
|
|
|
99
101
|
headers = %w[NAME TITLE CATEGORY OWNER LATEST]
|
|
100
102
|
widths = headers.map.with_index {|h, i| [h.length, *rows.map {|r| r[i].to_s.length }].max }
|
|
101
103
|
|
|
102
|
-
puts format_table_row(headers, widths)
|
|
104
|
+
out.puts format_table_row(headers, widths)
|
|
103
105
|
|
|
104
106
|
rows.each do |row|
|
|
105
|
-
puts format_table_row(row, widths)
|
|
107
|
+
out.puts format_table_row(row, widths)
|
|
106
108
|
end
|
|
107
109
|
|
|
108
110
|
say "#{mods.size} MOD(s) found", prefix: :info
|
|
@@ -22,8 +22,8 @@ module Factorix
|
|
|
22
22
|
"/path/to/mod-settings.dat -o out.json # Dump specific file"
|
|
23
23
|
]
|
|
24
24
|
|
|
25
|
-
argument :settings_file,
|
|
26
|
-
option :output,
|
|
25
|
+
argument :settings_file, required: false, desc: "Path to mod-settings.dat file"
|
|
26
|
+
option :output, aliases: ["-o"], desc: "Output file path"
|
|
27
27
|
|
|
28
28
|
# Execute the dump command
|
|
29
29
|
#
|
|
@@ -43,7 +43,7 @@ module Factorix
|
|
|
43
43
|
if output
|
|
44
44
|
Pathname(output).write(output_string)
|
|
45
45
|
else
|
|
46
|
-
puts output_string
|
|
46
|
+
out.puts output_string
|
|
47
47
|
end
|
|
48
48
|
end
|
|
49
49
|
|
|
@@ -24,8 +24,8 @@ module Factorix
|
|
|
24
24
|
" # Restore from stdin"
|
|
25
25
|
]
|
|
26
26
|
|
|
27
|
-
argument :settings_file,
|
|
28
|
-
option :input,
|
|
27
|
+
argument :settings_file, required: false, desc: "Path to mod-settings.dat file to write"
|
|
28
|
+
option :input, aliases: ["-i"], desc: "Input file path"
|
|
29
29
|
|
|
30
30
|
# Execute the restore command
|
|
31
31
|
#
|