Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/VERSIONS
Original file line number Diff line number Diff line change
Expand Up @@ -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)
-------------------
Expand Down
29 changes: 19 additions & 10 deletions lib/automatic/cli.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand All @@ -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
82 changes: 81 additions & 1 deletion spec/lib/automatic/cli_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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'))
Expand Down Expand Up @@ -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
Expand Down
Loading