diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c0f70b..10dc775 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ ## [Unreleased] +### Changed + +- `mod sync` now disables enabled MODs (including expansion MODs) not listed in the save file by default (#70) +- Add `--keep-unlisted` option to `mod sync` to preserve MODs not listed in the save file (#70) + ## [0.10.0] - 2026-02-21 ### Changed diff --git a/completion/_factorix.bash b/completion/_factorix.bash index afeecd7..e3e8f13 100644 --- a/completion/_factorix.bash +++ b/completion/_factorix.bash @@ -165,7 +165,7 @@ _factorix() { ;; sync) if [[ "$cur" == -* ]]; then - COMPREPLY=($(compgen -W "$global_opts $confirmable_opts -j --jobs" -- "$cur")) + COMPREPLY=($(compgen -W "$global_opts $confirmable_opts -j --jobs --keep-unlisted" -- "$cur")) else COMPREPLY=($(compgen -f -X '!*.zip' -- "$cur")) fi diff --git a/completion/_factorix.fish b/completion/_factorix.fish index 1ed1cf5..58d5cc7 100644 --- a/completion/_factorix.fish +++ b/completion/_factorix.fish @@ -228,6 +228,7 @@ complete -c factorix -n "__factorix_using_subcommand mod search" -l json -d 'Out # mod sync options complete -c factorix -n "__factorix_using_subcommand mod sync" -s y -l yes -d 'Skip confirmation prompts' complete -c factorix -n "__factorix_using_subcommand mod sync" -s j -l jobs -d 'Number of parallel downloads' -r +complete -c factorix -n "__factorix_using_subcommand mod sync" -l keep-unlisted -d 'Keep MODs not listed in save file enabled' complete -c factorix -n "__factorix_using_subcommand mod sync" -ra '(__fish_complete_suffix .zip)' # mod changelog subcommands diff --git a/completion/_factorix.zsh b/completion/_factorix.zsh index 26023ca..f9e3b5c 100644 --- a/completion/_factorix.zsh +++ b/completion/_factorix.zsh @@ -256,6 +256,7 @@ _factorix_mod() { $global_opts \ $confirmable_opts \ '(-j --jobs)'{-j,--jobs}'[Number of parallel downloads]:jobs:' \ + '--keep-unlisted[Keep MODs not listed in save file enabled]' \ '1:save file:_files -g "*.zip"' ;; changelog) diff --git a/doc/components/cli.md b/doc/components/cli.md index 2717520..c7539a0 100644 --- a/doc/components/cli.md +++ b/doc/components/cli.md @@ -374,10 +374,15 @@ Show detailed MOD information from Factorio MOD Portal. Synchronize MOD states from a save file. +**Options**: +- `-j`, `--jobs` - Number of parallel downloads (default: 4) +- `--keep-unlisted` - Keep MODs not listed in the save file enabled (default: disable them) + **Features**: - Extracts MOD information from save file - Downloads missing MODs concurrently -- Enables MODs to match save file state +- Enables/disables MODs to match save file state +- Disables enabled MODs (including expansion MODs) not listed in the save file (unless `--keep-unlisted` is specified) - Preserves existing MOD files when possible **Use case**: Set up MOD environment to match a specific save file diff --git a/doc/factorix.1 b/doc/factorix.1 index d3bc6c7..3b9d7d4 100644 --- a/doc/factorix.1 +++ b/doc/factorix.1 @@ -178,12 +178,16 @@ Disable all MOD(s) except base. Validate MOD dependencies. Reports missing, disabled, or incompatible dependencies. .SS factorix mod sync SAVE_FILE Sync MOD states and startup settings from a save file. +Enabled MODs not listed in the save file are disabled by default. .TP .BR \-y ", " \-\-yes Skip confirmation prompts. .TP .BR \-j ", " \-\-jobs =\fIVALUE\fR Number of parallel downloads (default: 4). +.TP +.B \-\-keep\-unlisted +Keep MODs not listed in the save file enabled. .SS factorix mod changelog add ENTRY Add an entry to MOD changelog. .TP diff --git a/lib/factorix/cli/commands/mod/sync.rb b/lib/factorix/cli/commands/mod/sync.rb index 4b658d3..1ffabbd 100644 --- a/lib/factorix/cli/commands/mod/sync.rb +++ b/lib/factorix/cli/commands/mod/sync.rb @@ -26,19 +26,21 @@ class Sync < Base desc "Sync MOD states and startup settings from a save file" example [ - "save.zip # Sync MOD(s) from save file", - "-j 8 save.zip # Use 8 parallel downloads" + "save.zip # Sync MOD(s) from save file", + "-j 8 save.zip # Use 8 parallel downloads", + "--keep-unlisted save.zip # Keep MOD(s) not in save file enabled" ] argument :save_file, required: true, desc: "Path to Factorio save file (.zip)" option :jobs, aliases: ["-j"], default: "4", desc: "Number of parallel downloads" + option :keep_unlisted, type: :flag, default: false, desc: "Keep MOD(s) not listed in save file enabled" # Execute the sync command # # @param save_file [String] Path to save file # @param jobs [Integer] Number of parallel downloads # @return [void] - def call(save_file:, jobs: "4", **) + def call(save_file:, jobs: "4", keep_unlisted: false, **) jobs = Integer(jobs) # Load save file say "Loading save file: #{save_file}", prefix: :info @@ -79,6 +81,7 @@ def call(save_file:, jobs: "4", **) # Update mod-list.json update_mod_list(mod_list, save_data.mods) + disable_unlisted_mods(mod_list, save_data.mods) unless keep_unlisted backup_if_exists(runtime.mod_list_path) mod_list.save say "Updated mod-list.json", prefix: :success @@ -265,6 +268,23 @@ def call(save_file:, jobs: "4", **) end end + # Disable enabled MODs that are not listed in the save file + # + # @param mod_list [MODList] Current MOD list + # @param save_mods [Hash] MODs from save file + # @return [void] + private def disable_unlisted_mods(mod_list, save_mods) + mod_list.each_mod do |mod| + next if mod.base? + next unless mod_list.enabled?(mod) + next if save_mods.key?(mod.name) + + mod_list.disable(mod) + say "Disabled #{mod} (not listed in save file)", prefix: :info + logger.debug("Disabled unlisted MOD", mod_name: mod.name) + end + end + # Update mod-settings.dat with startup settings from save file # # @param startup_settings [MODSettings::Section] Startup settings from save file diff --git a/spec/factorix/cli/commands/mod/sync_spec.rb b/spec/factorix/cli/commands/mod/sync_spec.rb index c743b8a..d38351d 100644 --- a/spec/factorix/cli/commands/mod/sync_spec.rb +++ b/spec/factorix/cli/commands/mod/sync_spec.rb @@ -31,9 +31,51 @@ end let(:mod_list) { Factorix::MODList.new } - let(:installed_mods) { [] } let(:graph) { instance_double(Factorix::Dependency::Graph) } + let(:base_info) do + Factorix::InfoJSON[ + name: "base", + version: base_mod_version, + title: "Base MOD", + author: "Wube", + description: "Base game", + factorio_version: "2.0", + dependencies: [] + ] + end + + let(:test_mod_info) do + Factorix::InfoJSON[ + name: "test-mod", + version: Factorix::MODVersion.from_string("1.0.0"), + title: "Test MOD", + author: "Test Author", + description: "Test description", + factorio_version: "2.0", + dependencies: [] + ] + end + + let(:installed_mods) do + [ + Factorix::InstalledMOD[ + mod: Factorix::MOD[name: "base"], + version: base_mod_version, + form: Factorix::InstalledMOD::DIRECTORY_FORM, + path: Pathname("/path/to/base"), + info: base_info + ], + Factorix::InstalledMOD[ + mod: Factorix::MOD[name: "test-mod"], + version: Factorix::MODVersion.from_string("1.0.0"), + form: Factorix::InstalledMOD::ZIP_FORM, + path: Pathname("/path/to/test-mod_1.0.0.zip"), + info: test_mod_info + ] + ] + end + before do allow(runtime).to receive_messages(running?: false, mod_dir:, mod_list_path:, mod_settings_path:) allow(Factorix::SaveFile).to receive(:load).and_return(save_data) @@ -63,58 +105,38 @@ describe "#call" do context "when all MODs from save file are already installed" do - let(:base_info) do - Factorix::InfoJSON[ - name: "base", - version: base_mod_version, - title: "Base MOD", - author: "Wube", - description: "Base game", - factorio_version: "2.0", - dependencies: [] - ] + before do + mod_list.add(Factorix::MOD[name: "base"], enabled: true, version: base_mod_version) + mod_list.add(Factorix::MOD[name: "test-mod"], enabled: true, version: Factorix::MODVersion.from_string("1.0.0")) end - let(:test_mod_info) do - Factorix::InfoJSON[ - name: "test-mod", - version: Factorix::MODVersion.from_string("1.0.0"), - title: "Test MOD", - author: "Test Author", - description: "Test description", - factorio_version: "2.0", - dependencies: [] - ] - end + it "updates mod-list.json" do + run_command(command, %W[#{save_file_path}]) - let(:installed_mods) do - [ - Factorix::InstalledMOD[ - mod: Factorix::MOD[name: "base"], - version: base_mod_version, - form: Factorix::InstalledMOD::DIRECTORY_FORM, - path: Pathname("/path/to/base"), - info: base_info - ], - Factorix::InstalledMOD[ - mod: Factorix::MOD[name: "test-mod"], - version: Factorix::MODVersion.from_string("1.0.0"), - form: Factorix::InstalledMOD::ZIP_FORM, - path: Pathname("/path/to/test-mod_1.0.0.zip"), - info: test_mod_info - ] - ] + expect(mod_list).to have_received(:save).with(no_args) end + end + context "when there are enabled MODs not listed in the save file" do before do mod_list.add(Factorix::MOD[name: "base"], enabled: true, version: base_mod_version) mod_list.add(Factorix::MOD[name: "test-mod"], enabled: true, version: Factorix::MODVersion.from_string("1.0.0")) + mod_list.add(Factorix::MOD[name: "extra-mod"], enabled: true) + mod_list.add(Factorix::MOD[name: "space-age"], enabled: true) end - it "updates mod-list.json" do + it "disables unlisted regular and expansion MODs by default" do run_command(command, %W[#{save_file_path}]) - expect(mod_list).to have_received(:save).with(no_args) + expect(mod_list.enabled?(Factorix::MOD[name: "extra-mod"])).to be false + expect(mod_list.enabled?(Factorix::MOD[name: "space-age"])).to be false + end + + it "keeps unlisted MODs enabled when --keep-unlisted is given" do + run_command(command, %W[--keep-unlisted #{save_file_path}]) + + expect(mod_list.enabled?(Factorix::MOD[name: "extra-mod"])).to be true + expect(mod_list.enabled?(Factorix::MOD[name: "space-age"])).to be true end end end