forked from refinery/refinerycms
/
refinerycms
executable file
·471 lines (392 loc) · 17.2 KB
/
refinerycms
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
#!/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
RAILS_MINOR_VERSION = '3.0'
# Load refinerycms
require File.expand_path(File.dirname(__FILE__) << "/../lib/refinery")
# Load other required libraries
require 'pathname'
require 'fileutils'
require 'optparse'
module Refinery
class AppGenerator
def initialize(input)
# Default options
@input = input
@options = {
:confirm => false,
:database => {
:adapter => 'sqlite3',
:host => 'localhost',
:ident => false,
:password => nil,
:username => 'root',
:skip => false
},
:force => false,
:gems => [],
:heroku => false,
:testing => false,
:rails => {
:version => nil
}
}
@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 bundle exec 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('-h', '--database-host HOST', String, "Set the database hostname", " (default localhost)") do |host|
@options[:database][:host] = host
end
opts.on('--skip-db', "Skip any database creation or migration tasks") do
@options[:database][:skip] = true
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("-c", "--confirm", "Confirm any prompts that require input") do
@options[:confirm] = true
end
opts.on('-r', "--rails-version VERSION", String, "Override the version of rails used to generate your application.") do |version|
@options[:rails][:version] = version
end
opts.on('-t', '--testing', "Automatically set up the project with refinerycms-testing support.") do
@options[:testing] = 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!
# Run any database migrations
migrate! unless @options[:database][:skip]
# Deploy to a hosting provider
deploy!
# 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
rails_version_in_path = run_command('rails --version', {:cd => false, :bundler => false, :puts => false}).to_s.gsub(/(Rails |\n)/, '')
@rails_version_to_use = @options[:rails][:version] || rails_version_in_path
if @rails_version_to_use !~ %r{\b#{RAILS_MINOR_VERSION}}
puts "\nRails #{@rails_version_to_use} is not supported by Refinery #{::Refinery.version}, " \
"please use Rails #{RAILS_MINOR_VERSION}.x instead."
puts "\nYou can tell Refinery CMS an installed and compatible rails version to use like so:\n"
puts "\nrefinerycms #{@app_name} --rails-version #{RAILS_MINOR_VERSION}"
puts "\n"
exit(1)
end
# 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 refinerycms test testing rails).include?(@input.first.downcase)
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!
# Generate a rails application
rails_command = "rails _#{@rails_version_to_use}_ new \"#{@app_path}\""
rails_command << " --database #{@options[:database][:adapter]}"
rails_command << " --force" if @options[:force]
rails_command << " --skip-test-unit --skip-prototype"
rails_command << " --skip-bundle" # Rails automatically installs the bundle, but so do we!
rails_command << " -m http://jruby.org" if defined? JRUBY_VERSION
rails_output = run_command(rails_command, {:cd => false, :bundler => 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 "\nGenerating Rails application failed. Exiting..."
if run_command('gem list rails', {:cd => false, :puts => false, :ruby => false}) !~ %r{[( ]#{@rails_version_to_use}(.0)*[,\)]}
puts "\nDo you have Rails #{@rails_version_to_use} installed?"
end
puts "\n"
exit(1)
else
if defined? JRUBY_VERSION
find_and_replace(@app_path.join('Gemfile'), /['|"]sqlite3['|"]/, "'activerecord-jdbcsqlite3-adapter'")
end
# Remove rails from the Gemfile so that Refinery can manage it
find_and_replace('Gemfile', %r{^gem 'rails'}, "# gem 'rails'")
# Override database host
if @options[:database][:host] != 'localhost' && (adapter = @options[:database][:adapter]) != 'sqlite3'
adapter = 'mysql2' if adapter == 'mysql'
find_and_replace('config/database.yml', "\n adapter: #{adapter}", "\n adapter: #{adapter}\n host: #{@options[:database][:host]}")
end
# Override database 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
if @options[:testing] # Enable testing
refinery_gems.gsub!("# gem 'refinerycms-testing'", "gem 'refinerycms-testing'")
end
app_gemfile = @app_path.join('Gemfile')
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 fog gem for Heroku
find_and_replace('Gemfile', "# gem 'fog'", "gem 'fog'") if @options[:heroku]
# Automate
puts "Installing gem requirements using bundler..\n"
# Install!
run_command("bundle install", {:fail => "Unable to install necessary gems"})
end
def migrate!
unless @options[:database][:adapter] == 'sqlite3'
# Ensure the database exists so that queries like .table_exists? don't fail.
puts "\nCreating a new database.."
# Warn about incorrect username or password.
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
run_command("rake -f \"#{@app_path.join('Rakefile')}\" db:create", {
:fail => "Unable to create the application's database",
:bundler => true
})
end
generators!
puts "\n\nSetting up your development database..\n"
run_command("rake -f \"#{@app_path.join('Rakefile')}\" db:migrate", :bundler => true)
end
def generators!
# Run the newly activated Refinery CMS generator.
puts "\n\nPreparing your application using the refinerycms generator..\n"
run_command("rails generate refinerycms", {
:cd => true,
:fail => "Could not run the refinerycms generator successfully.",
:bundler => true
})
if @options[:testing]
puts "\n\nAdding testing support files using the refinerycms_testing generator..\n"
run_command("rails generate refinerycms_testing", {
:cd => true,
:fail => "Could not run the refinerycms_testing generator successfully.",
:bundler => true
})
end
end
def deploy!
# Deploy to Heroku
hosting = nil
hosting = "Heroku" if @options[:heroku]
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]}")
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:migrate")
if @options[:heroku]
puts "\n\nRestarting servers...\n"
run_command("#{hosting.downcase} restart")
end
end
# End automation
end
def output!
# Construct helpful output messages
note = ["\n=== ACTION REQUIRED ==="]
if @options[:database][:skip]
note << "Because you elected to skip database creation and migration in the installer"
note << "you will need to run the following tasks manually to maintain correct operation:"
note << "\ncd #{@app_path}"
note << "bundle exec rake db:create"
note << "bundle exec rails generate refinerycms"
note << "bundle exec rake db:migrate"
note << "\n---------\n"
end
note << "Now you can launch your webserver using:"
note << "\ncd #{@app_path}"
note << "rails server"
note << "\nThis will launch the built-in webserver at port 3000."
note << "You can now see your site running in your browser at http://localhost:3000"
if @options[:heroku]
note << "\nIf you want files and images to work on heroku, you will need setup S3:"
note << "heroku config:add S3_BUCKET=XXXXXXXXX S3_KEY=XXXXXXXXX S3_SECRET=XXXXXXXXXX S3_REGION=XXXXXXXXXX"
end
note << "\nThanks for installing Refinery, enjoy creating your new application!"
note << "---------\n\n"
# finally, output.
puts note.join("\n")
end
private :bundle!, :deploy!, :generate!, :generators!, :migrate!, :output!, :validate!
def run_command(command, options = {})
require 'rbconfig'
options = {:cd => true, :puts => true, :fail => nil, :ruby => true, :bundler => false}.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 << "bundle exec" if options[:bundler]
to_run << command
if Refinery::WINDOWS
to_run = ['"'] + to_run + ['"']
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$}