diff --git a/exe/perron b/exe/perron new file mode 100755 index 0000000..c69eb01 --- /dev/null +++ b/exe/perron @@ -0,0 +1,124 @@ +#!/usr/bin/env ruby + +require "bundler/setup" + +RAILS_BIN = File.expand_path("bin/rails", Dir.pwd) + +USAGE = <<~USAGE + Usage: perron [options] + + Commands: + perron new APPNAME Create a new Perron-powered Rails app + perron generate NAME Scaffold a new content type (actions: index show) + perron validate Validate all content resources + perron sync_sources Sync all source-backed resources (pass NAME to sync specific) + perron build Build static site (defaults to RAILS_ENV=production) + perron clobber Remove built output + perron deploy Deploy static site + + Examples: + perron new my-site + perron generate Post show index + perron destroy Post + perron sync_sources Tool +USAGE + +def ensure_rails! + return if File.exist?(RAILS_BIN) + + warn "Error: bin/rails not found in #{Dir.pwd}" + warn "Make sure you are running from your Perron project directory." + + exit 1 +end + +def new_command + app_name = ARGV.shift + + unless app_name + warn "Error: app name required" + + puts USAGE + + exit 1 + end + + ENV["LOCATION"] = File.expand_path("lib/perron/install/template.rb", Dir.pwd) + + exec RAILS_BIN, "app:template" +end + +def generate_command + ensure_rails! + + exec RAILS_BIN, "generate", "content", *ARGV +end + +def destroy_command + ensure_rails! + + exec RAILS_BIN, "destroy", "content", *ARGV +end + +def validate_command + ensure_rails! + + exec RAILS_BIN, "perron:validate" +end + +def sync_sources_command + ensure_rails! + + exec RAILS_BIN, "perron:sync_sources", *ARGV +end + +def build_command + ensure_rails! + + ENV["RAILS_ENV"] ||= "production" + + exec RAILS_BIN, "perron:build" +end + +def clobber_command + ensure_rails! + + exec RAILS_BIN, "perron:clobber" +end + +def deploy_command + ensure_rails! + + exec RAILS_BIN, "perron:deploy" +end + +if ARGV.empty? + puts USAGE + + exit 0 +end + +command = ARGV.shift + +if command == "generate" && (ARGV.include?("-h") || ARGV.include?("--help")) + ensure_rails! + + exec RAILS_BIN, "generate", "content", "-h" +end + +case command +when "new" then new_command +when "generate" then generate_command +when "destroy" then destroy_command +when "validate" then validate_command +when "sync_sources" then sync_sources_command +when "build" then build_command +when "clobber" then clobber_command +when "deploy" then deploy_command +else + warn "Unknown command: #{command}" + + puts USAGE + + exit 1 +end diff --git a/lib/perron/install.rb b/lib/perron/install.rb index b47e71d..b4f577b 100644 --- a/lib/perron/install.rb +++ b/lib/perron/install.rb @@ -3,6 +3,11 @@ say "Create Perron initializer" copy_file "#{__dir__}/install/initializer.rb", "config/initializers/perron.rb" +say "Create perron binstub" +copy_file "#{__dir__}/install/perron.tt", "bin/perron", force: true + +run "chmod +x bin/perron" + say "Create content data directory" copy_file "#{__dir__}/install/README.md", "app/content/data/README.md" diff --git a/lib/perron/install/deploy.yml b/lib/perron/install/deploy.yml index cf70b42..7f1df48 100644 --- a/lib/perron/install/deploy.yml +++ b/lib/perron/install/deploy.yml @@ -9,6 +9,6 @@ # site_id: your_site_id before_actions: - - RAILS_ENV=production bundle exec rake perron:build + - bundle exec perron build after_actions: - bundle exec rake perron:clobber diff --git a/lib/perron/install/perron.tt b/lib/perron/install/perron.tt new file mode 100644 index 0000000..2a564b1 --- /dev/null +++ b/lib/perron/install/perron.tt @@ -0,0 +1,2 @@ +#!/usr/bin/env ruby +exec "bundle", "exec", "perron", *ARGV \ No newline at end of file diff --git a/lib/perron/install/template.rb b/lib/perron/install/template.rb new file mode 100644 index 0000000..388acf3 --- /dev/null +++ b/lib/perron/install/template.rb @@ -0,0 +1,87 @@ +gem "perron" unless File.read("Gemfile").include?("perron") + +after_bundle do + unless File.exist?("config/initializers/perron.rb") + rails_command "perron:install" + end + + gsub_file "Gemfile", /gem "sqlite3".*$/, "" + gsub_file "Gemfile", /gem "activerecord".*$/, "" + + say "Remove files" + remove_file "config/database.yml" + remove_file "config/credentials.yml.enc" + remove_file "config/master.key" + remove_file "public/400.html" + remove_file "public/406-unsupported-browser.html" + remove_file "public/422.html" + remove_file "public/500.html" + remove_file "app/controllers/application_controller.rb" + remove_file "app/views/layouts/application.html.erb" + remove_file "README.md" + + run "rm -r app/views/pwa" if File.directory?("app/views/pwa") + + append_to_file ".gitignore", "/output/\n" if File.exist?(".gitignore") + + say "Create application files" + create_file "app/controllers/application_controller.rb", <<~RB + class ApplicationController < ActionController::Base + end + RB + + create_file "app/views/layouts/application.html.erb", <<~ERB + + + + <%= meta_tags %> + + + + <%= yield :head %> + + + + + + <%= stylesheet_link_tag :app %> + + + + + <%= yield %> + + + ERB + + create_file "README.md", <<~MARKDOWN + # #{app_name.titleize} + + TBD + + + ## Development + + ``` + bin/dev + ``` + + + ## Deploy/publish + + ``` + bin/perron build + ``` + MARKDOWN + + markdown_gem = ask("Which markdown parser would you like to use? (commonmarker/kramdown/redcarpet or leave blank to skip):") + + gem_name = markdown_gem.strip.downcase + if %w[commonmarker kramdown redcarpet].include?(gem_name) + uncomment_lines "Gemfile", /gem "#{gem_name}"/ + + Bundler.with_unbundled_env { run "bundle install" } + elsif markdown_gem.present? + say "Invalid markdown parser option. Skipping…", :yellow + end +end diff --git a/lib/perron/tasks/build.rake b/lib/perron/tasks/build.rake index fe23058..825b3bf 100644 --- a/lib/perron/tasks/build.rake +++ b/lib/perron/tasks/build.rake @@ -3,7 +3,7 @@ namespace :perron do task build: :environment do unless Rails.env.production? puts "⚠️ Not running in production mode. Unpublished content will be included in the build." - puts " └─> Run in production mode using: RAILS_ENV=production bin/rails perron:build" + puts " └─> Run in production mode using: bin/perron build" puts "" end diff --git a/perron.gemspec b/perron.gemspec index 7e7c65a..cd4ce10 100644 --- a/perron.gemspec +++ b/perron.gemspec @@ -15,6 +15,8 @@ Gem::Specification.new do |spec| spec.metadata["source_code_uri"] = "https://github.com/Rails-Designer/perron/" spec.files = Dir["{bin,app,lib}/**/*", "Rakefile", "README.md", "perron.gemspec", "Gemfile", "Gemfile.lock"] + spec.bindir = "exe" + spec.executables = ["perron"] spec.required_ruby_version = ">= 3.4.0" diff --git a/test/perron/cli_test.rb b/test/perron/cli_test.rb new file mode 100644 index 0000000..91a99c0 --- /dev/null +++ b/test/perron/cli_test.rb @@ -0,0 +1,58 @@ +require "test_helper" +require "open3" + +class CliTest < ActiveSupport::TestCase + EXE_PATH = File.expand_path("../../exe/perron", __dir__) + + test "shows help without args" do + stdout, stderr, status = Open3.capture3("ruby", EXE_PATH) + + assert_match(/Usage: perron /, stdout + stderr) + assert_match(/perron new APPNAME/, stdout + stderr) + assert_match(/perron generate/, stdout + stderr) + assert_match(/perron build/, stdout + stderr) + assert_match(/perron clobber/, stdout + stderr) + assert_match(/perron deploy/, stdout + stderr) + end + + test "shows help with --help" do + stdout, stderr, status = Open3.capture3("ruby", EXE_PATH, "--help") + + assert_match(/Usage: perron /, stdout + stderr) + end + + test "shows help with -h" do + stdout, stderr, status = Open3.capture3("ruby", EXE_PATH, "-h") + + assert_match(/Usage: perron /, stdout + stderr) + end + + test "errors on unknown command" do + stdout, stderr, status = Open3.capture3("ruby", EXE_PATH, "unknown") + + assert_equal 1, status.exitstatus + assert_match(/Unknown command: unknown/, stdout + stderr) + end + + test "errors on new without app name" do + Dir.mktmpdir do |dir| + Dir.chdir(dir) do + stdout, stderr, status = Open3.capture3("ruby", EXE_PATH, "new") + + assert_equal 1, status.exitstatus + assert_match(/Error: app name required/, stdout + stderr) + end + end + end + + test "errors when bin/rails not found" do + Dir.mktmpdir do |dir| + Dir.chdir(dir) do + stdout, stderr, status = Open3.capture3("ruby", EXE_PATH, "build") + + assert_equal 1, status.exitstatus + assert_match(/bin\/rails not found/, stdout + stderr) + end + end + end +end \ No newline at end of file diff --git a/test/perron/install_test.rb b/test/perron/install_test.rb index 85689c0..a8ece8c 100644 --- a/test/perron/install_test.rb +++ b/test/perron/install_test.rb @@ -8,6 +8,7 @@ def setup FileUtils.mkdir_p(@tmpdir) FileUtils.mkdir_p("#{@tmpdir}/config/initializers") FileUtils.mkdir_p("#{@tmpdir}/app/content/data") + FileUtils.mkdir_p("#{@tmpdir}/bin") File.write("#{@tmpdir}/Gemfile", "source 'https://rubygems.org'\n") File.write("#{@tmpdir}/.gitignore", "") @@ -20,6 +21,12 @@ def test_creates_perron_initializer assert_match(/Perron.configure do |config|/, File.read("#{@tmpdir}/config/initializers/perron.rb")) end + def test_creates_perron_binstub + run_template + + assert File.exist?("#{@tmpdir}/bin/perron") + end + def test_creates_data_folder_with_readme run_template @@ -52,6 +59,8 @@ def run_template Dir.chdir(@tmpdir) do FileUtils.cp("#{@install_dir}/initializer.rb.tt", 'config/initializers/perron.rb') FileUtils.cp("#{@install_dir}/README.md.tt", 'app/content/data/README.md') + FileUtils.cp("#{@install_dir}/perron.tt", 'bin/perron') + FileUtils.chmod("+x", 'bin/perron') File.open("Gemfile", "a") do |file| file.write <<~RUBY diff --git a/test/perron/tasks/build_rake_test.rb b/test/perron/tasks/build_rake_test.rb index 868c24f..921b054 100644 --- a/test/perron/tasks/build_rake_test.rb +++ b/test/perron/tasks/build_rake_test.rb @@ -28,7 +28,7 @@ class BuildTest < ActiveSupport::TestCase end end.first - assert_match "RAILS_ENV=production bin/rails perron:build", output + assert_match "bin/perron build", output end test "does not warn when running in production" do