#!/usr/bin/env ruby # Load bundler begin require 'rubygems' require 'bundler' rescue LoadError puts "\n=== ACTION REQUIRED ===\n\n" puts "Could not load the bundler gem. This is a required dependency of Refinery CMS." puts "Please install it with `gem install bundler`.\n\n" exit(1) end # Let the application have a constant it can detect installation with. REFINERYCMS_INSTALLER = true # Load refinerycms require Pathname.new(File.expand_path(File.dirname(__FILE__) << "/..")).join('lib', 'refinery') # Load other required libraries require 'pathname' require 'fileutils' require 'optparse' module Refinery class AppGenerator def initialize(input) # Default options @input = input @options = { :database => { :adapter => 'sqlite3', :ident => false, :username => 'root', :password => nil }, :force => false, :heroku => false, :duostack => false, :confirm => false, :gems => [] } @optparse = OptionParser.new do |opts| opts.banner = "Purpose: Installs Refinery CMS to the specified directory" opts.banner << "\nUsage: #{opts.program_name} /path/to/project [options]" opts.separator "" opts.separator "Specific Options:" # Bail out if --update is specified. opts.on('--update') do |update| puts "\nYou specified --update which is a command that has been removed." puts "To update your application:" puts "- Change the version of the 'refinerycms' gem in your Gemfile." puts "- Run bundle install" puts "- Run rails generate refinerycms --update" puts "\n" exit(1) end # Rails supports more options, but Refinery is only tested on these three databases = %w(mysql postgresql sqlite3) opts.on("-d DATABASE", "--database DATABASE", databases, "Select the database (default sqlite3)", " #{databases.join('/')}") do |db| @options[:database][:adapter] = db end opts.on("--ident", "Use ident database authentication (for mysql or postgresql)") do @options[:database][:ident] = true end opts.on("-u USERNAME", '--database-username USERNAME', String, "Set the database username", ' (default root)') do |username| @options[:database][:username] = username end opts.on("-p PASSWORD", '--database-password PASSWORD', String, "Set the database password", " (default '')") do |password| @options[:database][:password] = password end opts.on("-g", "--gems gem1,gem2,gem3", Array, "Additional gems to install") do |gems| @options[:gems] = gems.reject {|g| g.to_s =~ /^refinerycms/}.map {|g| "gem '#{g.to_s}'"} end opts.on("-f", "--force", "Force overwriting of directory") do @options[:force] = true end opts.on("--heroku [APP_NAME]", "Set up and deploy to Heroku") do |app_name| @options[:heroku] = app_name || '' end opts.on("--duostack APP_NAME", String, "Set up and deploy to Duostack") do |app_name| @options[:duostack] = app_name end opts.on("-c", "--confirm", "Confirm any prompts that require input") do @options[:confirm] = true end opts.separator "" opts.separator "Common options:" opts.on_tail("-h", "--help", "Display this screen") do puts opts exit end opts.on_tail("-v", "--version", "Display the version") do puts Refinery.version exit end end end def run! # Grab input and ensure that the path doesn't exist already and other checks. validate! # Generate a Refinery CMS application generate! # Bundle the application which activates Refinery CMS bundle! # Ensure the database exists so that queries like .table_exists? don't fail. puts "\nCreating a new database.." # Warn about incorrect username or password. unless @options[:database][:adapter] == 'sqlite3' if @options[:database][:ident] note = "NOTE: If ident authentication fails then the installer will stall or fail here.\n\n" else note = "NOTE: if your database username is not '#{@options[:database][:username]}'" note << " or your password is not '#{@options[:database][:password]}' then the installer will stall here.\n\n" end puts note end run_command("rake -f \"#{@app_path.join('Rakefile')}\" db:create", { :fail => "Unable to create the application's database" }) # Run the newly activated Refinery CMS generator. run_command("rails generate refinerycms", { :cd => true, :fail => "Could not run the refinerycms generator successfully." }) # Output helpful messages to user output! end def validate! # Check for valid input begin @optparse.parse!(@input) rescue OptionParser::ParseError => pe puts pe puts "\n" puts @optparse exit(1) end # Ensure only one path is specified unless @input.size == 1 puts "Please specify a single path to install Refinery CMS" puts "\n" puts @optparse exit(1) end # Get the name and path of the new application @app_path = Pathname.new(File.expand_path(@input.first)) @app_name = @app_path.to_s.split(File::SEPARATOR).last # Get the refinery path based on this file @refinery_path = Pathname.new(File.expand_path('../../', __FILE__)) if @app_path == @refinery_path puts "\nPlease generate your new project from outside the Refinery directory and any Rails directory." puts "\n" exit(1) elsif %w(refinery test testing).include?(@input.first) puts "\nYou can't use '#{@input.first}' as a name for your project, this is a reserved word that will cause conflicts." puts "Please choose another name for your new project." puts "\n" exit(1) elsif @app_path.directory? && !@options[:force] puts "\nThe directory '#{@app_path}' that you specified already exists." puts "Use --force to overwrite an existing directory." puts "\n" exit(1) elsif @options[:heroku] if @options[:heroku].to_s.include?('_') or @options[:heroku].to_s.length > 30 message = ["\nThe application name '#{@options[:heroku]}' that you specified is invalid for Heroku."] suggested_name = @options[:heroku].to_s if suggested_name.include?('_') message << "This is because it contains underscores which Heroku does not allow." suggested_name.gsub!(/_/, '-') end if suggested_name.length > 30 message << "This is#{" also" unless suggested_name.nil?} because it is longer than 30 characters." suggested_name = suggested_name[0..29] end if @options[:force] or @options[:confirm] @options[:heroku] = suggested_name else message << "Please choose another name like '#{suggested_name}'" message << "Or use --confirm to automatically use '#{suggested_name}'" message << "\n" puts message.join("\n") exit(1) end end end end def generate!(rails_command = nil) if rails_command.to_s.length == 0 # Generate a rails application rails_command = "rails new \"#{@app_path}\"" rails_command << " --database #{@options[:database][:adapter]}" rails_command << " --force" if @options[:force] rails_command << " --skip-test-unit --skip-prototype" rails_command << " -m http://jruby.org" if defined? JRUBY_VERSION end rails_output = run_command(rails_command, {:cd => false}) # Detect non-success or a blank rails output or starting with "Can't initialize" or "Error" if !$?.success? or rails_output.to_s.length == 0 or rails_output =~ /^(Can't\ initialize|Error)/ puts "Generating Rails application failed. Exiting..." exit(1) else if defined? JRUBY_VERSION find_and_replace(@app_path.join('Gemfile'), /['|"]sqlite3['|"]/, "'activerecord-jdbcsqlite3-adapter'") end # Override username and password if @options[:database][:ident] find_and_replace('config/database.yml', %r{username:}, '#username:') find_and_replace('config/database.yml', %r{password:}, '#password:') else find_and_replace('config/database.yml', %r{username:.*}, "username: #{@options[:database][:username]}") find_and_replace('config/database.yml', %r{password:.*}, "password: #{@options[:database][:password]}") end puts "\n---------" puts "Refinery successfully installed in '#{@app_path}'!\n\n" end end def bundle! # Insert the current REFINERY CMS section (you shouldn't put anything in here). refinery_gemfile_contents = Refinery.root.join('Gemfile').read refinery_gems = refinery_gemfile_contents.match(/# REFINERY CMS =+.*# END REFINERY CMS =+/m)[0] refinery_gems.gsub!("# gem 'refinerycms'", "gem 'refinerycms'") # Enable refinerycms refinery_gems.gsub!("gem 'refinerycms-testing'", "# gem 'refinerycms-testing'") # Disable testing app_gemfile = @app_path.join('Gemfile') FileUtils::cp app_gemfile, "#{app_gemfile}.backup" refinery_user_defined_gems = refinery_gemfile_contents.match(/# USER DEFINED(.*)# END USER DEFINED/m) refinery_user_defined_gems = refinery_user_defined_gems[1] unless refinery_user_defined_gems.nil? app_gemfile.open('a') do |f| f.write "\n#{refinery_gems}\n" @options[:gems] = ([refinery_user_defined_gems] | [@options[:gems]]).flatten.compact f.write "\n# USER DEFINED\n#{@options[:gems].join("\n")}\n# END USER DEFINED" if @options[:gems].any? end # Specify the correct version of the Refinery CMS gem (may be git source). src = Refinery.version !~ /\.pre$/ ? "'= #{Refinery.version}'" : ":git => 'git://github.com/resolve/refinerycms'" find_and_replace('Gemfile', %r{gem 'refinerycms',.*}, "gem 'refinerycms', #{src}") # Add in AWS-S3 for Heroku find_and_replace('Gemfile', "# gem 'aws-s3', :require => 'aws/s3'", "gem 'aws-s3', :require => 'aws/s3'") if @options[:heroku] # Automate # TODO: Check exit codes to see whether or not these worked puts "Installing gem requirements using bundler..\n" # Install! run_command("bundle install", {:fail => "Unable to install necessary gems"}) end def output! puts "\n\nSetting up your development database..\n" run_command("rake -f \"#{@app_path.join('Rakefile')}\" db:migrate") # Deploy to Heroku hosting = nil hosting = "Heroku" if @options[:heroku] hosting = "Duostack" if @options[:duostack] unless hosting.nil? puts "\n\nInitializing and committing to git..\n" run_command("git init && git add . && git commit -am 'Initial Commit'", :ruby => false) puts "\n\nCreating #{hosting} app..\n" run_command("#{hosting.downcase} create #{@options[:heroku] || @options[:duostack]}") puts "\n\nPushing to #{hosting} (this takes time, be patient)..\n" run_command("git push #{hosting.downcase} master", :ruby => false) puts "\n\nSetting up the #{hosting} database..\n" run_command("#{hosting.downcase} rake db:setup") if @options[:heroku] puts "\n\nRestarting servers...\n" run_command("#{hosting.downcase} restart") end end # End automation # Output helpful messages puts "\n=== ACTION REQUIRED ===" puts "Now you can launch your webserver using:" puts "\ncd #{@app_path}" puts "rails server" puts "\nThis will launch the built-in webserver at port 3000." puts "You can now see your site running in your browser at http://localhost:3000" if @options[:heroku] puts "\nIf you want files and images to work on heroku, you will need setup S3:" puts "heroku config:add S3_BUCKET=XXXXXXXXX S3_KEY=XXXXXXXXX S3_SECRET=XXXXXXXXXX" end puts "\nThanks for installing Refinery, enjoy creating your new application!" puts "---------\n\n" end private :validate!, :generate!, :bundle!, :output! def run_command(command, options = {}) require 'rbconfig' options = {:cd => true, :puts => true, :fail => nil, :ruby => true}.merge(options) to_run = %w() to_run << "cd \"#{@app_path}\" &&" if options[:cd] # Sometimes we want to exclude the ruby runtime executable from the front # e.g. when shelling to git if options[:ruby] exe = File.join(RbConfig::CONFIG['bindir'], RbConfig::CONFIG['RUBY_INSTALL_NAME']) to_run << "#{exe} -S " end to_run << command if Refinery::WINDOWS to_run = %w(cmd /c) | to_run.map{|c| c.gsub(/\//m, '\\')} end to_run = to_run.join(' ') output = [] if options[:puts] puts "Running: #{to_run}" IO.popen(to_run) do |t| while (line = t.gets) output << line puts line end end else output << `#{to_run}` end if $?.success? or options[:fail].nil? output.join("\n") else puts "\nFailed to generate application." puts "Message: #{options[:fail]}" unless options[:fail].nil? puts "Exiting...\n\n" exit(1) end end def find_and_replace(file, find, replace) (contents = @app_path.join(file).read).gsub!(find, replace) (@app_path + file).open("w") do |f| f.puts contents end end protected :run_command, :find_and_replace end end Refinery::AppGenerator.new(ARGV).run! if $0 =~ %r{refinerycms$}