├── .gemtest ├── .gitignore ├── .rspec ├── .travis.yml ├── .yardopts ├── CHANGELOG.md ├── Gemfile ├── Guardfile ├── LICENSE.md ├── README.md ├── Rakefile ├── capistrano-local-precompile.gemspec ├── lib ├── capistrano-local-precompile.rb └── capistrano │ ├── capistrano-local-precompile │ └── tasks.rb │ └── local_precompile.rb └── spec ├── configuration_spec.rb ├── integration_spec.rb └── spec_helper.rb /.gemtest: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stve/capistrano-local-precompile/40cbbcf376fd1a7636f7c66b3448446ca7a82dba/.gemtest -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | *.sw[a-p] 4 | *.tmproj 5 | *.tmproject 6 | *.un~ 7 | *~ 8 | .DS_Store 9 | .Spotlight-V100 10 | .Trashes 11 | ._* 12 | .bundle 13 | .config 14 | .directory 15 | .elc 16 | .emacs.desktop 17 | .emacs.desktop.lock 18 | .redcar 19 | .yardoc 20 | Desktop.ini 21 | Gemfile.lock 22 | Icon? 23 | InstalledFiles 24 | Session.vim 25 | Thumbs.db 26 | \#*\# 27 | _yardoc 28 | auto-save-list 29 | coverage 30 | doc 31 | lib/bundler/man 32 | pkg 33 | pkg/* 34 | rdoc 35 | spec/reports 36 | test/tmp 37 | test/version_tmp 38 | tmp 39 | tmtags 40 | tramp 41 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --backtrace 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | bundler_args: --without development 2 | language: ruby 3 | rvm: 4 | - 2.0.0 5 | - 2.1.0 6 | - 2.2.0 7 | - 2.3.0 8 | -------------------------------------------------------------------------------- /.yardopts: -------------------------------------------------------------------------------- 1 | --markup markdown 2 | - 3 | LICENSE.md 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 1.2.0 / 2019-02-19 2 | ## Added 3 | - `dry-run` flag 4 | 5 | # 1.1.5 / 2019-02-04 6 | ## Fixed 7 | - Fetching precompile env 8 | 9 | ## Added 10 | - ssh port to rsync command 11 | - rsync parallel execution 12 | 13 | # 1.1.4 / 2018-09-04 14 | ## Fixed 15 | - 2nd argument being ignored on OSX 16 | 17 | # 1.1.3 / 2018-07-02 18 | ## Removed 19 | - Rails capistrano assets dependency 20 | 21 | # 1.1.2 / 2018-05-31 22 | ## Added 23 | - Cleanup packs directory when complete 24 | 25 | # 1.1.1 / 2018-04-21 26 | ## Added 27 | - Support for capistrano 3.8+ 28 | 29 | # 1.1.0 / 2017-09-18 30 | ## Added 31 | - Support for [Webpacker](https://github.com/rails/webpacker ) gem 32 | 33 | # 0.1.0 / 2017-05-20 34 | 35 | * [ENHANCEMENT] Compatible with Capistrano v3 36 | 37 | # 0.0.5 / ? 38 | 39 | * ? 40 | 41 | # 0.0.4 / 2014-03-27 42 | 43 | * [BUGFIX] Tighten capistrano dependency to avoid issues with capistrano 3.0.0 44 | 45 | # 0.0.3 / 2014-03-07 46 | 47 | * [BUGFIX] Fix issue where capistrano clean_expired deletes all assets (#4) 48 | * [BUGFIX] Use proper rails_env when compiling assets 49 | * [BUGFIX] Explicitly declare MIT license 50 | 51 | # 0.0.2 / 2013-10-04 52 | 53 | * [BUGFIX] Fix rsync issue requing extra backslash 54 | 55 | # 0.0.1 / 2013-09-15 56 | 57 | * Initial version 58 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'rake' 4 | gem 'yard' 5 | 6 | group :development do 7 | gem 'kramdown' 8 | gem 'guard-rspec' 9 | end 10 | 11 | group :test do 12 | gem 'rspec' 13 | gem 'capistrano-spec' 14 | gem 'simplecov', :require => false 15 | end 16 | 17 | gemspec 18 | -------------------------------------------------------------------------------- /Guardfile: -------------------------------------------------------------------------------- 1 | guard 'rspec', :version => 2 do 2 | watch(%r{^spec/.+_spec\.rb$}) 3 | watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" } 4 | watch('spec/spec_helper.rb') { "spec" } 5 | end 6 | 7 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Steve Agalloco 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Capistrano Local Precompile 2 | 3 | If your Rails apps are anything like mine, one of the slowest parts of your deployment is waiting for asset pipeline precompilation. It's sometimes so slow, it's painful. So I went searching for some solutions. [turbo-sprockets](https://github.com/ndbroadbent/turbo-sprockets-rails3) helped, but it's not a silver bullet. This gem isn't a silver bullet either, but it can help. Capistrano Local Precompile takes a different approach. It builds your assets locally and rsync's them to your web server(s). 4 | 5 | ## Usage 6 | 7 | Add capistrano-local-precompile to your Gemfile: 8 | 9 | ```ruby 10 | group :development do 11 | # Capistrano v2 should use '~> 0.0.5' 12 | # Capistrano v3 should use '~> 1.0.0' 13 | # Capistrano v3.8+ should use '~> 1.2.0' 14 | gem 'capistrano-local-precompile', '~> 1.2.0', require: false 15 | end 16 | ``` 17 | 18 | Then add the following line to your `Capfile`: 19 | 20 | ```ruby 21 | require 'capistrano/local_precompile' 22 | ``` 23 | 24 | Remove the following line from your `Capfile`: 25 | 26 | ```ruby 27 | require 'capistrano/rails/assets' 28 | ``` 29 | 30 | Here's the full set of configurable options: 31 | 32 | ```ruby 33 | set :precompile_env # default: fetch(:rails_env) || 'production' 34 | set :assets_dir # default: "public/assets" 35 | set :rsync_cmd # default: "rsync -av --delete" 36 | ``` 37 | 38 | Capistrano supports **dry run** mode. In that case the `rsync` command will not actually be run but only shown in stdout: 39 | 40 | ``` 41 | cap production deploy --dry-run 42 | ``` 43 | 44 | ## Acknowledgement 45 | 46 | This gem is derived from gists by [uhlenbrock][] and [keighl][]. 47 | 48 | [uhlenbrock]: https://gist.github.com/uhlenbrock/1477596 49 | [keighl]: https://gist.github.com/keighl/4338134 50 | 51 | ## Contributing 52 | 53 | Pull requests welcome: fork, make a topic branch, commit (squash when possible) *with tests* and I'll happily consider. 54 | 55 | ## Copyright 56 | 57 | Copyright (c) 2019 Steve Agalloco / Tom Caflisch. See [LICENSE](LICENSE.md) for detail 58 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env rake 2 | 3 | require 'bundler/gem_tasks' 4 | 5 | require 'rspec/core/rake_task' 6 | RSpec::Core::RakeTask.new(:spec) 7 | 8 | task :default => :spec 9 | task :test => :spec 10 | 11 | require 'yard' 12 | namespace :doc do 13 | YARD::Rake::YardocTask.new do |task| 14 | task.files = ['LICENSE.md', 'lib/**/*.rb'] 15 | task.options = ['--markup', 'markdown'] 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /capistrano-local-precompile.gemspec: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | 5 | Gem::Specification.new do |gem| 6 | gem.name = 'capistrano-local-precompile' 7 | gem.version = '1.2.0' 8 | gem.homepage = 'https://github.com/spagalloco/capistrano-local-precompile' 9 | 10 | gem.author = "Steve Agalloco, Tom Caflisch" 11 | gem.email = 'steve.agalloco@gmail.com, tomcaflisch@gmail.com' 12 | gem.description = 'Local asset-pipeline precompilation for Capstrano' 13 | gem.summary = gem.description 14 | 15 | gem.license = 'MIT' 16 | 17 | gem.add_dependency 'capistrano', '>=3.8' 18 | 19 | gem.files = %w(.yardopts LICENSE.md README.md Rakefile capistrano-local-precompile.gemspec) 20 | gem.files += Dir.glob("lib/**/*.rb") 21 | gem.files += Dir.glob("spec/**/*") 22 | 23 | gem.test_files = Dir.glob("spec/**/*") 24 | 25 | gem.require_paths = ['lib'] 26 | end 27 | -------------------------------------------------------------------------------- /lib/capistrano-local-precompile.rb: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /lib/capistrano/capistrano-local-precompile/tasks.rb: -------------------------------------------------------------------------------- 1 | load File.expand_path('../../tasks/local_precompile.cap', __FILE__) 2 | -------------------------------------------------------------------------------- /lib/capistrano/local_precompile.rb: -------------------------------------------------------------------------------- 1 | namespace :load do 2 | task :defaults do 3 | set :assets_dir, "public/assets" 4 | set :packs_dir, "public/packs" 5 | set :rsync_cmd, "rsync -av --delete" 6 | set :assets_role, "web" 7 | 8 | after "bundler:install", "deploy:assets:prepare" 9 | after "deploy:assets:prepare", "deploy:assets:rsync" 10 | after "deploy:assets:rsync", "deploy:assets:cleanup" 11 | end 12 | end 13 | 14 | namespace :deploy do 15 | namespace :assets do 16 | desc "Remove all local precompiled assets" 17 | task :cleanup do 18 | run_locally do 19 | execute "rm", "-rf", fetch(:assets_dir) 20 | execute "rm", "-rf", fetch(:packs_dir) 21 | end 22 | end 23 | 24 | desc "Actually precompile the assets locally" 25 | task :prepare do 26 | run_locally do 27 | precompile_env = fetch(:precompile_env) || fetch(:rails_env) || 'production' 28 | with rails_env: precompile_env do 29 | execute "rake", "assets:clean" 30 | execute "rake", "assets:precompile" 31 | end 32 | end 33 | end 34 | 35 | desc "Performs rsync to app servers" 36 | task :rsync do 37 | on roles(fetch(:assets_role)), in: :parallel do |server| 38 | run_locally do 39 | remote_shell = %(-e "ssh -p #{server.port}") if server.port 40 | 41 | commands = [] 42 | commands << "#{fetch(:rsync_cmd)} #{remote_shell} ./#{fetch(:assets_dir)}/ #{server.user}@#{server.hostname}:#{release_path}/#{fetch(:assets_dir)}/" if Dir.exists?(fetch(:assets_dir)) 43 | commands << "#{fetch(:rsync_cmd)} #{remote_shell} ./#{fetch(:packs_dir)}/ #{server.user}@#{server.hostname}:#{release_path}/#{fetch(:packs_dir)}/" if Dir.exists?(fetch(:packs_dir)) 44 | 45 | commands.each do |command| 46 | if dry_run? 47 | SSHKit.config.output.info command 48 | else 49 | execute command 50 | end 51 | end 52 | end 53 | end 54 | end 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /spec/configuration_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Capistrano::LocalPrecompile, "configuration" do 4 | before do 5 | @configuration = Capistrano::Configuration.new 6 | @configuration.load do 7 | def rails_env; 'production'; end 8 | def rake; 'rake'; end 9 | def asset_env; "RAILS_GROUPS=assets"; end 10 | end 11 | Capistrano::LocalPrecompile.load_into(@configuration) 12 | end 13 | 14 | it "defines precompile_env" do 15 | expect(@configuration.fetch(:precompile_env)).to eq('production') 16 | end 17 | 18 | it "defines precompile_cmd" do 19 | cmd = 'RAILS_ENV=production RAILS_GROUPS=assets rake assets:precompile' 20 | expect(@configuration.fetch(:precompile_cmd)).to eq(cmd) 21 | end 22 | 23 | it "defines cleanexpired_cmd" do 24 | cmd = 'RAILS_ENV=production RAILS_GROUPS=assets rake assets:clean_expired' 25 | expect(@configuration.fetch(:cleanexpired_cmd)).to eq(cmd) 26 | end 27 | 28 | it "defines assets_dir" do 29 | expect(@configuration.fetch(:assets_dir)).to eq('public/assets') 30 | end 31 | 32 | it "defines rsync_cmd" do 33 | expect(@configuration.fetch(:rsync_cmd)).to eq('rsync -av --delete') 34 | end 35 | 36 | it "performs deploy:assets:prepare before deploy:assets:precompile" do 37 | expect(@configuration).to callback('deploy:assets:prepare'). 38 | before('deploy:assets:precompile') 39 | end 40 | 41 | it "performs deploy:assets:remove before deploy:assets:precompile" do 42 | expect(@configuration).to callback('deploy:assets:remove'). 43 | before('deploy:assets:symlink') 44 | end 45 | 46 | it "performs deploy:assets:cleanup after deploy:assets:precompile" do 47 | expect(@configuration).to callback('deploy:assets:cleanup'). 48 | after('deploy:assets:precompile') 49 | end 50 | 51 | it "performs deploy:assets:remove_manifest before deploy:assets:symlink" do 52 | expect(@configuration).to callback('deploy:assets:remove_manifest'). 53 | before('deploy:assets:symlink') 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /spec/integration_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Capistrano::LocalPrecompile, "integration" do 4 | before do 5 | @configuration = Capistrano::Configuration.new 6 | @configuration.load do 7 | def rails_env; 'production'; end 8 | def rake; 'rake'; end 9 | def asset_env; "RAILS_GROUPS=assets"; end 10 | end 11 | Capistrano::LocalPrecompile.load_into(@configuration) 12 | end 13 | 14 | describe 'cleanup task' do 15 | it 'removes the asset files from public/assets' do 16 | expect(@configuration).to receive(:run_locally). 17 | with('rm -rf public/assets') 18 | 19 | @configuration.find_and_execute_task('deploy:assets:cleanup') 20 | end 21 | end 22 | 23 | describe 'prepare task' do 24 | it 'invokes the precompile command' do 25 | expect(@configuration).to receive(:run_locally). 26 | with('RAILS_ENV=production RAILS_GROUPS=assets rake assets:precompile').once 27 | 28 | @configuration.find_and_execute_task('deploy:assets:prepare') 29 | end 30 | end 31 | 32 | describe 'remove manifest task' do 33 | it 'invokes the precompile command' do 34 | allow(@configuration).to receive(:shared_path).and_return('/tmp/shared') 35 | allow(@configuration).to receive(:shared_assets_prefix).and_return('assets') 36 | 37 | expect(@configuration).to receive(:run). 38 | with('rm -f /tmp/shared/assets/manifest*').once 39 | 40 | @configuration.find_and_execute_task('deploy:assets:remove_manifest') 41 | end 42 | end 43 | 44 | describe 'precompile task' do 45 | let(:servers) { %w(10.0.1.1 10.0.1.2) } 46 | 47 | before do 48 | allow(@configuration).to receive(:run_locally). 49 | with("ls public/assets/manifest*").and_return("public/assets/manifest.yml").once 50 | allow(@configuration).to receive(:run_locally). 51 | with('RAILS_ENV=production RAILS_GROUPS=assets rake assets:precompile').once 52 | allow(@configuration).to receive(:run_locally). 53 | with('rm -rf public/assets').once 54 | 55 | 56 | allow(@configuration).to receive(:user).and_return('root') 57 | allow(@configuration).to receive(:assets_role).and_return('app') 58 | allow(@configuration).to receive(:find_servers).and_return(servers) 59 | allow(@configuration).to receive(:release_path).and_return('/tmp') 60 | end 61 | 62 | it 'rsyncs the local asset files to the server' do 63 | expect(@configuration).to receive(:run_locally).with(/rsync -av/).exactly(4).times 64 | 65 | @configuration.find_and_execute_task('deploy:assets:precompile') 66 | end 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | unless ENV['CI'] 3 | require 'simplecov' 4 | SimpleCov.start do 5 | add_group 'Capistrano', 'lib/capistrano' 6 | add_group 'Specs', 'spec/*' 7 | add_filter '.bundle' 8 | end 9 | end 10 | 11 | require 'capistrano-local-precompile' 12 | require 'rspec' 13 | require 'capistrano-spec' 14 | 15 | RSpec.configure do |config| 16 | config.include Capistrano::Spec::Matchers 17 | config.include Capistrano::Spec::Helpers 18 | 19 | config.expect_with :rspec do |c| 20 | c.syntax = :expect 21 | end 22 | end 23 | --------------------------------------------------------------------------------