# frozen_string_literal: true require "rails/generators" module Rails module Generators class DevcontainerGenerator < Base # :nodoc: class_option :app_name, type: :string, default: "rails_app", desc: "Name of the app" class_option :database, enum: Database::DATABASES, type: :string, default: "sqlite3", desc: "Include configuration for selected database" class_option :redis, type: :boolean, default: true, desc: "Include configuration for Redis" class_option :system_test, type: :boolean, default: true, desc: "Include configuration for System Tests" class_option :active_storage, type: :boolean, default: true, desc: "Include configuration for Active Storage" class_option :node, type: :boolean, default: false, desc: "Include configuration for Node" class_option :dev, type: :boolean, default: false, desc: "For applications pointing to a local Rails checkout" source_paths << File.expand_path(File.join(base_name, "app", "templates"), base_root) def create_devcontainer empty_directory ".devcontainer" template "devcontainer/devcontainer.json", ".devcontainer/devcontainer.json" template "devcontainer/Dockerfile", ".devcontainer/Dockerfile" template "devcontainer/compose.yaml", ".devcontainer/compose.yaml" end def update_application_system_test_case return unless options[:system_test] return unless File.exist?("test/application_system_test_case.rb") gsub_file("test/application_system_test_case.rb", /^\s*driven_by\b.*/, system_test_configuration) end def update_database_yml # Only postgresql has devcontainer specific configuration, so only update database.yml if we are using postgres return unless options[:database] == "postgresql" template("config/databases/#{options[:database]}.yml", "config/database.yml") end private def devcontainer? true end def app_name options[:app_name] end def dependencies return @dependencies if @dependencies @dependencies = [] @dependencies << "selenium" if options[:system_test] @dependencies << "redis" if options[:redis] @dependencies << database.name if database.service @dependencies end def container_env return @container_env if @container_env @container_env = {} @container_env["CAPYBARA_SERVER_PORT"] = "45678" if options[:system_test] @container_env["SELENIUM_HOST"] = "selenium" if options[:system_test] @container_env["REDIS_URL"] = "redis://redis:6379/1" if options[:redis] @container_env["DB_HOST"] = database.name if database.service @container_env end def volumes return @volumes if @volumes @volumes = [] @volumes << "redis-data" if options[:redis] @volumes << database.volume if database.volume @volumes end def features return @features if @features @features = { "ghcr.io/devcontainers/features/github-cli:1" => {} } @features["ghcr.io/rails/devcontainer/features/activestorage"] = {} if options[:active_storage] @features["ghcr.io/devcontainers/features/node:1"] = {} if options[:node] @features.merge!(database.feature) if database.feature @features end def mounts return @mounts if @mounts @mounts = [] @mounts << local_rails_mount if options[:dev] @mounts end def forward_ports return @forward_ports if @forward_ports @forward_ports = [3000] @forward_ports << database.port if database.port @forward_ports << 6379 if options[:redis] @forward_ports end def database @database ||= Database.build(options[:database]) end def devcontainer_db_service_yaml(**options) return unless service = database.service { database.name => service }.to_yaml(**options)[4..-1] end def local_rails_mount { type: "bind", source: Rails::Generators::RAILS_DEV_PATH, target: Rails::Generators::RAILS_DEV_PATH } end def system_test_configuration optimize_indentation(<<-'RUBY', 2) if ENV["CAPYBARA_SERVER_PORT"] served_by host: "rails-app", port: ENV["CAPYBARA_SERVER_PORT"] driven_by :selenium, using: :headless_chrome, screen_size: [ 1400, 1400 ], options: { browser: :remote, url: "http://#{ENV["SELENIUM_HOST"]}:4444" } else driven_by :selenium, using: :headless_chrome, screen_size: [ 1400, 1400 ] end RUBY end end end end