class Object
Public Instance Methods
apache_site(*args)
click to toggle source
# File lib/geordi/commands/apache_site.rb, line 2 def apache_site(*args) site = args.shift old_cwd = Dir.pwd Dir.chdir '/etc/apache2/sites-available/' # show available sites if no site was passed as argument unless site puts 'ERROR: Argument site is missing.' puts 'Please call: apache-site my-site' puts puts 'Available sites:' Dir.new('.').each do |file| puts "- #{file}" if file != '.' && file != '..' end exit end has_default = File.exist?('default') exec "sudo a2dissite \*; sudo a2ensite #{'default ' if has_default}#{site} && sudo apache2ctl restart" Dir.chdir old_cwd end
branch()
click to toggle source
# File lib/geordi/commands/branch.rb, line 11 def branch require 'geordi/gitlinear' Gitlinear.new.branch(from_master: options.from_master) Hint.did_you_know [ :commit, [:branch, :from_master], ] end
bundle_install()
click to toggle source
# File lib/geordi/commands/bundle_install.rb, line 2 def bundle_install if File.exist?('Gemfile') && !system('bundle check > /dev/null 2>&1') Interaction.announce 'Bundling' Util.run!('bundle install') end end
capistrano(*args)
click to toggle source
# File lib/geordi/commands/capistrano.rb, line 6 def capistrano(*args) targets = Dir['config/deploy/*.rb'].map { |file| File.basename(file, '.rb') }.sort Interaction.note 'Found the following deploy targets:' puts targets Interaction.prompt('Continue?', 'n', /y|yes/) || Interaction.fail('Cancelled.') targets << nil if targets.empty? # default target targets.each do |stage| Interaction.announce 'Target: ' + (stage || '(default)') command = "bundle exec cap #{stage} " + args.join(' ') Interaction.note_cmd command Util.run!(command, fail_message: 'Capistrano failed. Have a look!') end Hint.did_you_know [ :deploy, :rake, ] end
chromedriver_update()
click to toggle source
# File lib/geordi/commands/chromedriver_update.rb, line 18 def chromedriver_update require 'geordi/chromedriver_updater' ChromedriverUpdater.new.run(options) Hint.did_you_know [ 'Geordi can automatically keep chromedriver up-to-date. See `geordi help chromedriver-update`.', ] unless options.quiet_if_matching end
clean()
click to toggle source
# File lib/geordi/commands/clean.rb, line 2 def clean Interaction.announce 'Removing tempfiles' for pattern in %w[ webrat-* capybara-* tmp/webrat-* tmp/capybara-* tmp/rtex/* log/*.log ] Interaction.note pattern puts `rm -vfR #{pattern}` end Interaction.announce 'Finding recursively and removing backup files' %w[*~].each do |pattern| Interaction.note pattern `find . -name #{pattern} -exec rm {} ';'` end Hint.did_you_know [ :remove_executable_flags, ] end
commit(*git_args)
click to toggle source
# File lib/geordi/commands/commit.rb, line 11 def commit(*git_args) require 'geordi/gitlinear' Gitlinear.new.commit(git_args) Hint.did_you_know [ :branch, :deploy, ] end
console(target = 'development', *_args)
click to toggle source
# File lib/geordi/commands/console.rb, line 17 def console(target = 'development', *_args) require 'geordi/remote' Hint.did_you_know [ :shelll, [:console, :select_server], 'You only need to type the unique prefix of a command to run it. `geordi con` will work as well.', ] if target == 'development' invoke_geordi 'bundle_install' invoke_geordi 'yarn_install' Interaction.announce 'Opening a local Rails console' command = Util.console_command(target) # Exec has better behavior on Ctrl + C Util.run!(command, exec: true) else Interaction.announce 'Opening a Rails console on ' + target Geordi::Remote.new(target).console(options) end end
create_database_yml()
click to toggle source
# File lib/geordi/commands/create_database_yml.rb, line 2 def create_database_yml real_yml = 'config/database.yml' sample_yml = 'config/database.sample.yml' if File.exist?(sample_yml) && !File.exist?(real_yml) Interaction.announce 'Creating ' + real_yml sample = File.read(sample_yml) adapter = sample.match(/adapter: (\w+)/).captures.first print "Please enter your #{adapter} password: " db_password = STDIN.gets.strip real = sample.gsub(/password:.*$/, "password: #{db_password}") File.open(real_yml, 'w') { |f| f.write(real) } Interaction.note "Created #{real_yml}." end end
create_databases()
click to toggle source
# File lib/geordi/commands/create_databases.rb, line 2 def create_databases invoke_geordi 'create_database_yml' invoke_geordi 'bundle_install' Interaction.announce 'Creating databases' if File.exist?('config/database.yml') command = [] command << Util.binstub_or_fallback('rake') command << 'db:create:all' command << 'parallel:create' if Util.file_containing?('Gemfile', /parallel_tests/) Util.run!(command) else puts 'config/database.yml does not exist. Nothing to do.' end end
cucumber(*args)
click to toggle source
# File lib/geordi/commands/cucumber.rb, line 27 def cucumber(*args) if args.empty? # This is not testable as there is no way to stub `git` :( if options.modified? modified_features = `git status --short`.split("\n").map do |line| indicators = line.slice!(0..2) # Remove leading indicators line if line.include?('.feature') && !indicators.include?('D') end.compact args = modified_features end if options.containing matching_features = `grep -lri '#{options.containing}' --include=*.feature features/`.split("\n") args = matching_features.uniq end end if File.directory?('features') require 'geordi/cucumber' settings = Geordi::Settings.new invoke_geordi 'bundle_install' invoke_geordi 'yarn_install' if settings.auto_update_chromedriver invoke_geordi 'chromedriver_update', quiet_if_matching: true, exit_on_failure: false end arguments = args arguments << '--format' << 'pretty' << '--backtrace' if options.debug # Parallel run of all given features + reruns ############################## Interaction.announce 'Running features' normal_run_successful = Geordi::Cucumber.new.run(arguments, verbose: options.verbose) unless normal_run_successful arguments << '--profile' << 'rerun' # Reruns (options.rerun + 1).times do |i| Interaction.fail 'Features failed.' if i == options.rerun # All reruns done? Interaction.announce "Rerun ##{i + 1} of #{options.rerun}" break if Geordi::Cucumber.new.run(arguments, verbose: options.verbose, parallel: false) end end Interaction.success 'Features green.' Hint.did_you_know [ :rspec, [:cucumber, :modified], [:cucumber, :containing], [:cucumber, :debug], [:cucumber, :rerun], 'Geordi can automatically keep chromedriver up-to-date. See `geordi help chromedriver-update`.', 'You only need to type the unique prefix of a command to run it. `geordi cuc` will work as well.', ] else Interaction.note 'Cucumber not employed.' end end
delete_dumps(*locations)
click to toggle source
# File lib/geordi/commands/delete_dumps.rb, line 12 def delete_dumps(*locations) Interaction.announce 'Retrieving dump files' dump_files = [] if locations.empty? locations = [ File.join(Dir.home, 'dumps'), Dir.pwd ] end locations.map! &File.method(:expand_path) Interaction.note "Looking in #{locations.join(', ')}" locations.each do |dir| directory = File.expand_path(dir) unless File.directory? File.realdirpath(directory) Interaction.warn "Directory #{directory} does not exist. Skipping." next end dump_files.concat Dir.glob("#{directory}/**/*.dump") end deletable_dumps = dump_files.flatten.uniq.sort.select &File.method(:file?) if deletable_dumps.empty? Interaction.warn 'No dump files found.' else puts deletable_dumps Interaction.prompt('Delete these files?', 'n', /y|yes/) || Interaction.fail('Cancelled.') deletable_dumps.each &File.method(:delete) Interaction.success 'Done.' end Hint.did_you_know [ :clean, :drop_databases, :dump, ] end
deploy(target_stage = nil)
click to toggle source
# File lib/geordi/commands/deploy.rb, line 38 def deploy(target_stage = nil) # Set/Infer default values branch_stage_map = { 'master' => 'staging', 'production' => 'production' } if target_stage && !Util.deploy_targets.include?(target_stage) # Target stage autocompletion from available stages target_stage = Util.deploy_targets.find { |t| t.start_with? target_stage } target_stage || Interaction.warn('Given deployment stage not found') end # Ask for required information target_stage ||= Interaction.prompt 'Deployment stage:', branch_stage_map.fetch(Util.current_branch, 'staging') if options.current_branch stage_file = "config/deploy/#{target_stage}.rb" Util.file_containing?(stage_file, 'DEPLOY_BRANCH') || Interaction.fail(<<~ERROR) To deploy from the current branch, configure #{stage_file} to respect the environment variable DEPLOY_BRANCH. Example: set :branch, ENV['DEPLOY_BRANCH'] || 'master' ERROR source_branch = target_branch = Util.current_branch else source_branch = Interaction.prompt 'Source branch:', Util.current_branch target_branch = Interaction.prompt 'Deploy branch:', branch_stage_map.invert.fetch(target_stage, 'master') end merge_needed = (source_branch != target_branch) push_needed = merge_needed || `git cherry -v | wc -l`.strip.to_i > 0 push_needed = false if Util.testing? # Hard to test Interaction.announce "Checking whether your #{source_branch} branch is ready" ############ Util.run!("git checkout #{source_branch}") if (`git status -s | wc -l`.strip != '0') && !Util.testing? Interaction.warn "Your #{source_branch} branch holds uncommitted changes." Interaction.prompt('Continue anyway?', 'n', /y|yes/) || raise('Cancelled.') else Interaction.note 'All good.' end if merge_needed Interaction.announce "Checking what's in your #{target_branch} branch right now" ####### Util.run!("git checkout #{target_branch} && git pull") end Interaction.announce 'You are about to:' ################################################# Interaction.note "Merge branch #{source_branch} into #{target_branch}" if merge_needed if push_needed Interaction.note 'Push these commits:' if push_needed Util.run!("git --no-pager log origin/#{target_branch}..#{source_branch} --oneline") end Interaction.note "Deploy to #{target_stage}" Interaction.note "From current branch #{source_branch}" if options.current_branch if Interaction.prompt('Go ahead with the deployment?', 'n', /y|yes/) puts git_call = [] git_call << "git merge #{source_branch}" if merge_needed git_call << 'git push' if push_needed invoke_geordi 'bundle_install' capistrano_call = "cap #{target_stage} deploy" capistrano_call << ':migrations' unless Util.gem_major_version('capistrano') == 3 || options.no_migrations capistrano_call = "bundle exec #{capistrano_call}" if Util.file_containing?('Gemfile', /capistrano/) capistrano_call = "DEPLOY_BRANCH=#{source_branch} #{capistrano_call}" if options.current_branch if git_call.any? Util.run!(git_call.join(' && '), show_cmd: true) end Util.run!(capistrano_call, show_cmd: true) Interaction.success 'Deployment complete.' Hint.did_you_know [ :capistrano, :security_update, ] else Util.run!("git checkout #{source_branch}") Interaction.fail 'Deployment cancelled.' end end
drop_databases()
click to toggle source
# File lib/geordi/commands/drop_databases.rb, line 28 def drop_databases require 'geordi/db_cleaner' Interaction.fail '-P and -M are mutually exclusive' if options.postgres_only && options.mysql_only mysql_flags = nil postgres_flags = nil unless options.mysql.nil? begin mysql_port = Integer(options.mysql) mysql_flags = "--port=#{mysql_port} --protocol=TCP" rescue AttributeError unless File.exist? options.mysql Interaction.fail "Path #{options.mysql} is not a valid MySQL socket" end mysql_flags = "--socket=#{options.mysql}" end end unless options.postgres.nil? postgres_flags = "--port=#{options.postgres}" end unless options.sudo Interaction.note 'Assuming your local user has permission to drop databases. Run with `--sudo` to use sudo.' end extra_flags = { 'mysql' => mysql_flags, 'postgres' => postgres_flags, } cleaner = DBCleaner.new(extra_flags, sudo: options.sudo) cleaner.clean_mysql unless options.postgres_only cleaner.clean_postgres unless options.mysql_only Hint.did_you_know [ :delete_dumps, ] end
dump(target = nil, *_args)
click to toggle source
# File lib/geordi/commands/dump.rb, line 30 def dump(target = nil, *_args) require 'geordi/dump_loader' require 'geordi/remote' database = options[:database] ? "#{options[:database]} " : '' if target.nil? # Local … if options.load # … dump loading Interaction.fail 'Missing a dump file.' if options.load == 'load' File.exist?(options.load) || raise('Could not find the given dump file: ' + options.load) loader = DumpLoader.new(options.load, options.database) Interaction.announce "Sourcing dump into the #{loader.config['database']} db" loader.load Interaction.success "Your #{loader.config['database']} database has now the data of #{options.load}." else # … dump creation Interaction.announce 'Dumping the development database' Util.run!("dumple development #{database}") Interaction.success "Successfully dumped the #{database}development database." end else # Remote dumping … database_label = options[:database] ? " (#{database}database)" : "" Interaction.announce "Dumping the database of #{target}#{database_label}" dump_path = Geordi::Remote.new(target).dump(options) if options.load # … and dump loading loader = DumpLoader.new(dump_path, options.database) Interaction.announce "Sourcing dump into the #{loader.config['database']} db" loader.load Util.run! "rm #{dump_path}" Interaction.note "Dump file removed" Interaction.success "Your #{loader.config['database']} database has now the data of #{target}#{database_label}." end end Hint.did_you_know [ :delete_dumps, :drop_databases, :migrate, 'Geordi can load a dump directly into the local database if passed a Capistrano stage and the option -l. See `geordi help dump`.', ] end
migrate()
click to toggle source
# File lib/geordi/commands/migrate.rb, line 10 def migrate if File.directory?('db/migrate') invoke_geordi 'bundle_install' invoke_geordi 'yarn_install' Interaction.announce 'Migrating' if Util.file_containing?('Gemfile', /parallel_tests/) Interaction.note 'Development and parallel test databases' puts Util.run!([Util.binstub_or_fallback('rake'), 'db:migrate', 'parallel:prepare']) else invoke_geordi 'rake', 'db:migrate' end else Interaction.note 'No migrations directory found.' end end
png_optimize(path)
click to toggle source
# File lib/geordi/commands/png_optimize.rb, line 10 def png_optimize(path) require 'fileutils' Interaction.announce 'Optimizing .png files' if `which pngcrush`.strip.empty? Interaction.fail 'Please install pngcrush first (sudo apt-get install pngcrush)' end po = PngOptimizer.new if File.directory?(path) po.batch_optimize_inplace(path) elsif File.file?(path) po.optimize_inplace(path) else Interaction.fail 'Neither directory nor file: ' + path end Interaction.success 'PNG optimization completed.' end
rake(*args)
click to toggle source
# File lib/geordi/commands/rake.rb, line 12 def rake(*args) invoke_geordi 'bundle_install' %w[development test cucumber].each do |env| # update long_desc when changing this if File.exist? "config/environments/#{env}.rb" command = [] command << Util.binstub_or_fallback('rake') command += args command << "RAILS_ENV=#{env}" Util.run!(command, show_cmd: true) end end Hint.did_you_know [ :capistrano, ] end
remove_executable_flags()
click to toggle source
# File lib/geordi/commands/remove_executable_flags.rb, line 2 def remove_executable_flags Interaction.announce 'Removing executable-flags' patterns = %w[ *.rb *.html *.erb *.haml *.yml *.css *.sass *.rake *.png *.jpg *.gif *.pdf *.txt *.rdoc *.feature Rakefile VERSION README Capfile ] patterns.each do |pattern| Interaction.note pattern `find . -name "#{pattern}" -exec chmod -x {} ';'` end Interaction.success 'Done.' Hint.did_you_know [ :clean, ] end
rspec(*files)
click to toggle source
# File lib/geordi/commands/rspec.rb, line 11 def rspec(*files) if File.exist?('spec/spec_helper.rb') require 'geordi/settings' settings = Geordi::Settings.new invoke_geordi 'bundle_install' invoke_geordi 'yarn_install' if settings.auto_update_chromedriver && Util.gem_available?('selenium-webdriver') invoke_geordi 'chromedriver_update', quiet_if_matching: true, exit_on_failure: false end Interaction.announce 'Running specs' if Util.file_containing?('Gemfile', /parallel_tests/) && files.empty? Interaction.note 'All specs at once (using parallel_tests)' Util.run!([Util.binstub_or_fallback('rake'), 'parallel:spec'], fail_message: 'Specs failed.') else # tell which specs will be run if files.empty? files << 'spec/' Interaction.note 'All specs in spec/' else Interaction.note 'Only: ' + files.join(', ') end command = if File.exist?('script/spec') ['bundle exec spec -c'] # RSpec 1 else [Util.binstub_or_fallback('rspec')] end command << '-r rspec_spinner -f RspecSpinner::Bar' if Util.file_containing?('Gemfile', /rspec_spinner/) command << files.join(' ') puts Util.run!(command.join(' '), fail_message: 'Specs failed.') end Hint.did_you_know [ :cucumber, 'Geordi can automatically keep chromedriver up-to-date. See `geordi help chromedriver-update`.', 'You only need to type the unique prefix of a command to run it. `geordi rs` will work as well.', ] else Interaction.note 'RSpec not employed.' end end
security_update(step = 'prepare')
click to toggle source
# File lib/geordi/commands/security_update.rb, line 30 def security_update(step = 'prepare') case step when 'prepare' Interaction.announce 'Preparing for security update' Interaction.warn 'Please read https://makandracards.com/makandra/1587 before applying security updates!' Interaction.note 'About to checkout production and pull.' Interaction.prompt('Continue?', 'y', /y|yes/) || Interaction.fail('Cancelled.') Util.run!('git checkout production', show_cmd: true) Util.run!('git pull', show_cmd: true) Interaction.success 'Successfully prepared for security update' puts Interaction.note 'Please apply the security update now and commit your changes.' Interaction.note 'When you are done, run `geordi security-update finish`.' when 'f', 'finish' # ensure everything is committed if Util.testing? puts 'Util.run! git status --porcelain' else `git status --porcelain`.empty? || Interaction.fail('Please commit your changes before finishing the update.') end Interaction.announce 'Finishing security update' Interaction.note 'Working directory clean.' Interaction.prompt('Have you successfully run all tests?', 'n', /y|yes/) || Interaction.fail('Please run tests first.') Interaction.note 'About to: push production, checkout & pull master, merge production, push master.' Interaction.prompt('Continue?', 'n', /y|yes/) || Interaction.fail('Cancelled.') Util.run!('git push', show_cmd: true) Util.run!('git checkout master', show_cmd: true) Util.run!('git pull', show_cmd: true) Util.run!('git merge production', show_cmd: true) Util.run!('git push', show_cmd: true) Interaction.announce 'Deployment' deploy = (Util.gem_major_version('capistrano') == 3) ? 'deploy' : 'deploy:migrations' all_deploy_targets = Util.deploy_targets Interaction.fail 'There are no deploy targets!' if all_deploy_targets.empty? if all_deploy_targets.include?('staging') Interaction.note 'There is a staging environment.' Interaction.prompt('Deploy staging now?', 'y', /y|yes/) || Interaction.fail('Cancelled.') Interaction.announce 'Deploy staging' Util.run! "bundle exec cap staging #{deploy}", show_cmd: true Interaction.prompt('Is the deployment log okay and the application is still running on staging?', 'y', /y|yes/) || Interaction.fail('Please fix the deployment issues on staging before you continue.') else Interaction.note 'There is no staging environment.' end deploy_targets_without_staging = all_deploy_targets.select { |target| target != 'staging' } if deploy_targets_without_staging.empty? Interaction.note 'There are no other stages.' else puts Interaction.note 'Found the following other stages:' puts deploy_targets_without_staging puts Interaction.prompt('Deploy other stages now?', 'y', /y|yes/) || Interaction.fail('Cancelled.') deploy_targets_without_staging.each do |target| Interaction.announce "Deploy #{target}" Util.run!("bundle exec cap #{target} #{deploy}", show_cmd: true) end Interaction.prompt('Is the application still running on all other stages and the logs are okay?', 'y', /y|yes/) || Interaction.fail('Please fix the application immediately!') end Interaction.success 'Successfully pushed and deployed security update' puts Interaction.note 'Now send an email to customer and project lead, informing them about the update.' Interaction.note 'Do not forget to make a joblog on a security budget, if available.' end end
server(port = nil)
click to toggle source
# File lib/geordi/commands/server.rb, line 8 def server(port = nil) Hint.did_you_know [ [:server, :public], ] invoke_geordi 'bundle_install' invoke_geordi 'yarn_install' require 'geordi/util' Interaction.announce 'Booting a development server' port ||= options.port Interaction.note "URL: http://#{File.basename(Dir.pwd)}.daho.im:#{port}" puts command = Util.server_command command << ' -b 0.0.0.0' if options.public command << ' -p ' << port Util.run!(command) end
setup()
click to toggle source
# File lib/geordi/commands/setup.rb, line 17 def setup if File.exist? 'bin/setup' Interaction.announce 'Running bin/setup' Interaction.note "Geordi's own setup routine is skipped" Util.run!('bin/setup') else invoke_geordi 'create_databases' invoke_geordi 'migrate' end Interaction.success 'Successfully set up the project.' Hint.did_you_know [ :update, :security_update, [:setup, :dump], [:setup, :test], ] unless options.dump || options.test invoke_geordi 'dump', options.dump, load: true if options.dump invoke_geordi 'tests' if options.test end
shelll(target, *_args)
click to toggle source
This method has a triple ‘l’ because :shell is a Thor reserved word. However, it can still be called with ‘geordi shell` :)
# File lib/geordi/commands/shell.rb, line 15 def shelll(target, *_args) require 'geordi/remote' Hint.did_you_know [ :console, 'You only need to type the unique prefix of a command to run it. `geordi sh` will work as well.', ] Interaction.announce 'Opening a shell on ' + target Geordi::Remote.new(target).shell(options) end
tests(*args)
click to toggle source
# File lib/geordi/commands/tests.rb, line 10 def tests(*args) if args.any? args, opts = Thor::Options.split(args) error_message = "When passing arguments, the first argument must be either an RSpec or a Cucumber path." if args.empty? Interaction.fail error_message else rspec_paths = args.select { |a| Util.rspec_path?(a) } cucumber_paths = args.select { |a| Util.cucumber_path?(a) } invoke('rspec', rspec_paths, opts) if rspec_paths.any? invoke('cucumber', cucumber_paths, opts) if cucumber_paths.any? end else rake_result = invoke_geordi 'with_rake' # Since `rake` usually is configured to run all tests, only run them if `rake` # did not perform if rake_result == :did_not_perform invoke_geordi 'unit' invoke_geordi 'rspec' invoke_geordi 'cucumber' end Interaction.success 'Successfully ran tests.' end Hint.did_you_know [ :deploy, ] end
unit()
click to toggle source
# File lib/geordi/commands/unit.rb, line 8 def unit if File.exist?('test/test_helper.rb') invoke_geordi 'bundle_install' invoke_geordi 'yarn_install' Interaction.announce 'Running Test::Unit' if Util.file_containing?('Gemfile', /parallel_tests/) Interaction.note 'All unit tests at once (using parallel_tests)' Util.run!([Util.binstub_or_fallback('rake'), 'parallel:test'], fail_message: 'Test::Unit failed.') else Util.run!([Util.binstub_or_fallback('rake'), 'test'], fail_message: 'Test::Unit failed.') end else Interaction.note 'Test::Unit not employed.' end end
update()
click to toggle source
# File lib/geordi/commands/update.rb, line 12 def update old_ruby_version = File.read('.ruby-version').chomp Interaction.announce 'Updating repository' Util.run!('git pull', show_cmd: true) ruby_version = File.read('.ruby-version').chomp ruby_version_changed = !ruby_version.empty? && (ruby_version != old_ruby_version) if ruby_version_changed puts Interaction.warn 'Ruby version changed during git pull. Please run again to use the new version.' exit(1) else invoke_geordi 'migrate' Interaction.success 'Successfully updated the project.' Hint.did_you_know [ :setup, [:update, :dump], [:update, :test], ] unless options.dump || options.test invoke_geordi 'dump', options.dump, load: true if options.dump invoke_geordi 'tests' if options.test end end
version()
click to toggle source
# File lib/geordi/commands/version.rb, line 2 def version require 'geordi/version' puts 'Geordi ' + Geordi::VERSION end
with_rake()
click to toggle source
# File lib/geordi/commands/with_rake.rb, line 2 def with_rake if Util.file_containing?('Rakefile', /^task.+default.+(spec|test|feature)/) invoke_geordi 'bundle_install' invoke_geordi 'yarn_install' Interaction.announce 'Running tests with `rake`' Util.run!(Util.binstub_or_fallback('rake')) else Interaction.note '`rake` does not run tests.' :did_not_perform end end
yarn_install()
click to toggle source
# File lib/geordi/commands/yarn_install.rb, line 3 def yarn_install if File.exist?('package.json') && !system('yarn check --integrity > /dev/null 2>&1') Interaction.announce 'Yarn install' Util.run!('yarn install') end end