From 7ca07c800259c40f3562b08c12cc2c71796419f8 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 5 Sep 2026 06:53:59 +0000 Subject: [PATCH] Fix inspect feed selection and scaffold recovery Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_019yrav786J6wJ8aTkxhgSbY --- doc/VERSIONS | 1 + lib/automatic/cli.rb | 29 +++++++----- spec/lib/automatic/cli_spec.rb | 82 +++++++++++++++++++++++++++++++++- 3 files changed, 101 insertions(+), 11 deletions(-) diff --git a/doc/VERSIONS b/doc/VERSIONS index 6f49b9a..1ea82cb 100644 --- a/doc/VERSIONS +++ b/doc/VERSIONS @@ -7,6 +7,7 @@ v26.09 (Release Date: TBD) - Add FilterLimit, FilterBatch and FilterPresent for limiting, batching and required-field filtering. - Add per-fetch interval handling to FilterFullFeed, FilterImageSource, and FilterDescriptionLink. - Make doc/PLUGINS.md section 6 the single source of truth for the current plugin catalogue. +- Fix CLI contract drift so inspect parses the first discovered feed and scaffold restores missing bundled siteinfo and example configuration without overwriting existing user data. v26.08 (2026-08-22) ------------------- diff --git a/lib/automatic/cli.rb b/lib/automatic/cli.rb index 658883f..f400825 100644 --- a/lib/automatic/cli.rb +++ b/lib/automatic/cli.rb @@ -5,7 +5,7 @@ # License:: The GPL version 3, or LGPL version 3 (Dual License). # Contact:: idnanashi@gmail.com # Created:: Aug 14, 2026 -# Updated:: Aug 19, 2026 +# Updated:: Sep 5, 2026 # Copyright:: Copyright (c) 2012-2026 Automatic Ruby Developers. # # Everything that belongs to being a command: option parsing, the subcommands, @@ -178,16 +178,12 @@ def scaffold(_argv) create_dir(File.join(Automatic.user_dir, 'db')) assets = File.join(Automatic.user_dir, 'assets') - if create_dir(assets) - FileUtils.cp_r(File.join(@root_dir, 'assets', 'siteinfo'), - File.join(assets, 'siteinfo')) - end + create_dir(assets) + copy_bundled(File.join(@root_dir, 'assets', 'siteinfo'), File.join(assets, 'siteinfo')) config = Automatic.user_config_dir - if create_dir(config) - FileUtils.cp_r(File.join(@root_dir, 'config'), - File.join(config, 'example')) - end + create_dir(config) + copy_bundled(File.join(@root_dir, 'config'), File.join(config, 'example')) end def unscaffold(_argv) @@ -221,7 +217,7 @@ def inspect_url(argv) raise Automatic::Error, "no feed found at #{url}" if feeds.empty? @stdout.puts feeds.pretty_inspect - @stdout.puts Automatic::FeedParser.get_url(feeds.pop).pretty_inspect + @stdout.puts Automatic::FeedParser.get_url(feeds.first).pretty_inspect end def opmlparser(argv) @@ -244,5 +240,18 @@ def create_dir(path) @stdout.puts "Creating #{path}" true end + + # Bundled initial data (siteinfo, example config) is copied whenever its + # own destination is missing, independent of whether the parent user + # directory already existed. That is what lets a re-run of scaffold heal a + # partially-scaffolded user directory instead of only ever populating a + # brand new one. See doc/DEPLOYMENT.md and doc/POLICY.md on scaffold only + # adding what is missing. + def copy_bundled(src, dest) + return if File.exist?(dest) + + FileUtils.cp_r(src, dest) + @stdout.puts "Creating #{dest}" + end end end diff --git a/spec/lib/automatic/cli_spec.rb b/spec/lib/automatic/cli_spec.rb index d1771a2..4115f4c 100644 --- a/spec/lib/automatic/cli_spec.rb +++ b/spec/lib/automatic/cli_spec.rb @@ -5,7 +5,7 @@ # License:: The GPL version 3, or LGPL version 3 (Dual License). # Contact:: idnanashi@gmail.com # Created:: Aug 14, 2026 -# Updated:: Aug 19, 2026 +# Updated:: Sep 5, 2026 # Copyright:: Copyright (c) 2012-2026 Automatic Ruby Developers. require File.expand_path(File.join(File.dirname(__FILE__), '../../spec_helper')) @@ -90,6 +90,86 @@ def run(*argv, root_dir: APP_ROOT) expect(err.string).to match(%r{no feed found at https://example\.com/}) expect(out.string).to be_empty end + + it "parses the first feed when several are discovered" do + stub_const("Feedbag", Class.new) + allow(Automatic).to receive(:require_optional) + allow(Feedbag).to receive(:find) + .and_return(["https://example.com/first", "https://example.com/second"]) + allow(Automatic::FeedParser).to receive(:get_url).and_return("parsed") + + expect(run("inspect", "https://example.com/")).to eq Automatic::CLI::EXIT_SUCCESS + expect(Automatic::FeedParser).to have_received(:get_url).with("https://example.com/first") + end + end + + describe "the scaffold subcommand" do + around do |example| + Dir.mktmpdir("automatic-ruby-scaffold-spec") do |dir| + @scaffold_user_dir = dir + Automatic.user_dir = dir + example.run + end + Automatic.user_dir = nil + end + + def run_scaffold + run("scaffold", root_dir: APP_ROOT) + end + + it "creates the missing bundled siteinfo when the assets directory already exists" do + FileUtils.mkdir_p(File.join(@scaffold_user_dir, "assets")) + + expect(run_scaffold).to eq Automatic::CLI::EXIT_SUCCESS + + siteinfo = File.join(@scaffold_user_dir, "assets", "siteinfo") + expect(File.directory?(siteinfo)).to be true + expect(Dir.children(siteinfo)).not_to be_empty + end + + it "creates the missing bundled example configuration when the config directory already exists" do + FileUtils.mkdir_p(Automatic.user_config_dir) + + expect(run_scaffold).to eq Automatic::CLI::EXIT_SUCCESS + + example_config = File.join(Automatic.user_config_dir, "example") + expect(File.directory?(example_config)).to be true + expect(Dir.children(example_config)).not_to be_empty + end + + it "does not overwrite an existing siteinfo directory" do + siteinfo = File.join(@scaffold_user_dir, "assets", "siteinfo") + FileUtils.mkdir_p(siteinfo) + File.write(File.join(siteinfo, "marker.txt"), "existing user data") + + expect(run_scaffold).to eq Automatic::CLI::EXIT_SUCCESS + + expect(File.read(File.join(siteinfo, "marker.txt"))).to eq "existing user data" + end + + it "does not overwrite an existing example configuration directory" do + example_config = File.join(Automatic.user_config_dir, "example") + FileUtils.mkdir_p(example_config) + File.write(File.join(example_config, "marker.txt"), "existing user data") + + expect(run_scaffold).to eq Automatic::CLI::EXIT_SUCCESS + + expect(File.read(File.join(example_config, "marker.txt"))).to eq "existing user data" + end + + it "leaves existing user data untouched when run repeatedly" do + run_scaffold + + siteinfo = File.join(@scaffold_user_dir, "assets", "siteinfo") + example_config = File.join(Automatic.user_config_dir, "example") + File.write(File.join(siteinfo, "marker.txt"), "kept across reruns") + File.write(File.join(example_config, "marker.txt"), "kept across reruns") + + expect(run_scaffold).to eq Automatic::CLI::EXIT_SUCCESS + + expect(File.read(File.join(siteinfo, "marker.txt"))).to eq "kept across reruns" + expect(File.read(File.join(example_config, "marker.txt"))).to eq "kept across reruns" + end end describe "running a recipe" do