module SvelteOnRails::Installer::Utils
def self.add_line_to_file(file_path, line)
def self.add_line_to_file(file_path, line) file_content = File.exist?(file_path) ? File.read(file_path) : "" if file_content.match?(/#{line}/) puts "#{line} already present in #{file_path}, nothing changed here." return end File.open(file_path, 'a') do |file| file.puts(line) end puts "added #{line} to #{file_path}." rescue StandardError => e puts "Error: #{e.message}" end
def self.add_route(route)
def self.add_route(route) file_path = 'config/routes.rb' # Read the file content content = File.read(file_path) # Split content into lines lines = content.lines # Find the index of Rails.application.routes.draw do ind = -1 lines.each_with_index do |line, i| if line.match?(/^\s*Rails.application.routes.draw\s*do[\s\S]+$/) ind = i end end # Insert if ind >= 0 lines.insert(ind + 1, route) else raise "ERROR: Could not find Rails.application.routes.draw do" end # Write the modified content back to the file begin File.write(file_path, lines.map { |l| l.gsub(/\n/, '') }.join("\n")) puts "Successfully inserted «root '#{route}'» into '#{file_path}'" rescue => e raise "Error writing to #{file_path} => «#{e.message}»" end end
def self.ask_yn(question)
def self.ask_yn(question) begin puts "#{question} (y/n)" continue = STDIN.gets.chomp.downcase[0] end until ['y', 'n'].include?(continue) continue == 'y' end
def self.check_file_exists(file_path)
def self.check_file_exists(file_path) unless File.exist?(file_path) raise "ERROR: File not found: #{file_path}" end end
def self.check_file_not_exists(file_path)
def self.check_file_not_exists(file_path) if File.exist?(file_path) raise "ERROR: File already exists: #{file_path}" end end
def self.check_folder_exists(folder_path)
def self.check_folder_exists(folder_path) unless File.exist?(folder_path) raise "ERROR: Folder not found: #{folder_path}" end end
def self.check_folder_not_exists(folder_path)
def self.check_folder_not_exists(folder_path) if File.exist?(folder_path) raise "ERROR: Folder already exists: #{folder_path}" end end
def self.create_file(file_path)
def self.create_file(file_path) unless File.exist?(file_path) FileUtils.touch(file_path) puts "Created empty file at file://#{file_path}" end end
def self.create_folder(folder)
def self.create_folder(folder) if Dir.exist?(folder) puts "Folder already exists: #{folder}" else FileUtils.mkdir_p(folder) puts "Created folder: #{folder}" end end
def self.create_javascript_initializer
def self.create_javascript_initializer config_path = Rails.root.join("app", "frontend", "initializers", "svelte.js") if File.exist?(config_path) puts "Initializer already exists: file://#{config_path}" else File.write(config_path, <<~JAVASCRIPT) import { initializeSvelteComponents, cleanupSvelteComponents } from '@csedl/svelte-on-rails'; const components = import.meta.glob('/javascript/components/**/*.svelte', { eager: true }); const componentsRoot = '/javascript/components'; // Initialize Svelte components initializeSvelteComponents(componentsRoot, components, true); // Turbo event listener for page load document.addEventListener('turbo:load', () => { initializeSvelteComponents(componentsRoot, components, true); }); // Turbo event listener for cleanup before page cache document.addEventListener('turbo:before-cache', () => { cleanupSvelteComponents(false); }); JAVASCRIPT puts "Created initializer file at file://#{config_path}" end end
def self.create_svelte_hello_world
def self.create_svelte_hello_world file_path = Rails.root.join("app", "frontend", "javascript", "components", "HelloWorld.svelte") if File.exist?(file_path) puts "Hello World file already exists: file://#{file_path}" else File.write(file_path, <<~HTML) <script> export let items let count = 0; function increment() { count += 1; } </script> <h1>Greetings from svelte</h1> <button on:click={increment}>Increment: {count}</button> <ul> {#each items as item} <li>{item}</li> {/each} </ul> <style> button { background-color: darkred; color: white; padding: 10px; border: none; } </style> HTML puts "Hello World file at file://#{file_path}" end end
def self.install_npm_package
def self.install_npm_package package_name = "@csedl/svelte-on-rails@latest" puts "Installing #{package_name} via npm..." if system("npm install #{package_name}") puts "#{package_name} successfully installed." else abort "Failed to install #{package_name}. Please ensure npm is installed and try running 'npm install #{package_name}' manually." end end
def self.install_turbo
def self.install_turbo pkg_js = Rails.root.join("package.json") package_name = "@hotwired/turbo-rails" file_content = File.exist?(pkg_js) ? File.read(pkg_js) : "" if file_content.match?(/#{package_name}/) puts "#{package_name} is already present in package.json, assuming that it is set up well and working." else puts "Installing #{package_name} via npm..." if system("npm install #{package_name}") puts "#{package_name} successfully installed." add_line_to_file(Rails.root.join("app", "frontend", "entrypoints", "application.js"), "import '#{package_name}';") else abort "Failed to install #{package_name}. Please ensure npm is installed and try running 'npm install #{package_name}' manually." end end end
def self.remove_files(file_paths)
def self.remove_files(file_paths) file_paths.each do |f| if File.exist?(f) if File.directory?(f) Dir.delete(f) puts " • Removed directory: #{f}" else File.delete(f) puts " • Removed file: #{f}" end else puts " • File/Path not found so not removed: #{f}" end end end
def self.remove_line_from_file(file_path, string_to_find)
def self.remove_line_from_file(file_path, string_to_find) # Read the file content content = File.read(file_path) # Split content into lines lines = content.lines found_lines = [] modified_content = [] lines.each do |line| if line.match(/^[\s\S]+#{string_to_find}[\s\S]+$/) found_lines.push(line) else modified_content.push(line) end end utils = SvelteOnRails::Installer::Utils if found_lines.present? && utils.ask_yn("Remove lines\n • #{found_lines.join("\n • ")}\n from #{file_path}?") # Write the modified content back to the file begin File.write(file_path, modified_content.map{|l|l.gsub(/\n/, '')}.join("\n")) puts "Successfully removed #{found_lines.length} #{'line'.pluralize(found_lines.length)}." rescue => e raise "Error writing to #{file_path} => «#{e.message}»" end else end end
def self.route_exists?(target_route)
def self.route_exists?(target_route) # check if exists # Ensure the Rails environment is loaded # require File.expand_path("../config/environment", __dir__) # Get all routes from the Rails application routes = Rails.application.routes.routes # Check if the route exists routes.any? do |route| # Extract the path spec and remove any optional parts or constraints path = route.path.spec.to_s # Clean up the path to match the format (remove leading/trailing slashes, etc.) cleaned_path = path.gsub(/\(.*\)/, "").gsub(/^\/|\/$/, "") # Check if the cleaned path matches the target route cleaned_path == target_route end end
def self.run_command(command)
def self.run_command(command) Dir.chdir(Rails.root) do stdout, stderr, status = Open3.capture3(command) if stderr.present? puts "Error running command «#{command}»:" raise stderr else puts "#{command} => Success" end end end
def self.which_root_route
def self.which_root_route # Check if the root route is active (uncommented) or commented out routes = File.read(Rails.root.join('config', 'routes.rb')) m = routes.match(/^\s*root\s+['"]([^'"]+)['"]/m) if m m.to_s.match(/^\s*root\s*['"]([^'"]*)['"]/)[1] end end
def self.write_templates(template_paths)
def self.write_templates(template_paths) existing = template_paths.select { |p| File.exist?(p) } verb = 'Created' if existing.present? begin puts "#{'Template'.pluralize(existing.length)} already exists:\n#{existing.join("\n")}.\nOverwrite? (y/n)" continue = STDIN.gets.chomp.downcase[0] end until ['y', 'n'].include?(continue) if continue == 'n' puts "Skipping write #{'template'.pluralize(template_paths.length)}." return end verb = 'Overwrote' end template_paths.each do |p| source_path = File.expand_path('rails_template', __dir__) + '/' + p FileUtils.mkdir_p(File.dirname(p)) FileUtils.cp(source_path, p) end puts "#{verb} #{'file'.pluralize(template_paths.length)}:\n#{template_paths.join("\n")}" end