pipefitter 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +58 -0
- data/Rakefile +1 -0
- data/bin/pipefitter +9 -0
- data/lib/pipefitter/checksum.rb +32 -0
- data/lib/pipefitter/cli.rb +104 -0
- data/lib/pipefitter/compiler.rb +56 -0
- data/lib/pipefitter/compressor.rb +34 -0
- data/lib/pipefitter/error.rb +4 -0
- data/lib/pipefitter/inventory.rb +45 -0
- data/lib/pipefitter/logger.rb +14 -0
- data/lib/pipefitter/version.rb +3 -0
- data/lib/pipefitter.rb +156 -0
- data/pipefitter.gemspec +19 -0
- data/spec/pipefitter/checksum_spec.rb +24 -0
- data/spec/pipefitter/cli_spec.rb +54 -0
- data/spec/pipefitter/compiler_spec.rb +19 -0
- data/spec/pipefitter/inventory_spec.rb +25 -0
- data/spec/pipefitter_spec.rb +82 -0
- data/spec/spec_helper.rb +22 -0
- data/spec/support/stubbed_rails_app/Gemfile +38 -0
- data/spec/support/stubbed_rails_app/Rakefile +14 -0
- data/spec/support/stubbed_rails_app/app/assets/images/rails.png +0 -0
- data/spec/support/stubbed_rails_app/app/assets/javascripts/application.js +15 -0
- data/spec/support/stubbed_rails_app/app/assets/stylesheets/application.css +13 -0
- data/spec/support/stubbed_rails_app/lib/assets/.gitkeep +0 -0
- data/spec/support/stubbed_rails_app/public/.gitkeep +0 -0
- data/spec/support/stubbed_rails_app/vendor/assets/javascripts/.gitkeep +0 -0
- data/spec/support/stubbed_rails_app/vendor/assets/stylesheets/.gitkeep +0 -0
- data/spec/support/stubbed_rails_app/vendor/plugins/.gitkeep +0 -0
- metadata +101 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Lee Jones
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
# Pipefitter
|
2
|
+
|
3
|
+
A command-line tool that avoids unnecessary compiles when using the Rails Asset Pipeline.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'pipefitter'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install pipefitter
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
The `pipefitter` command is a smart wrapper to use when compiling your assets:
|
22
|
+
|
23
|
+
$ pipefitter
|
24
|
+
Running `bundle exec rake assets:precompile`...
|
25
|
+
Finished compiling assets!
|
26
|
+
|
27
|
+
It will automatically check if something changed that would require another compile:
|
28
|
+
|
29
|
+
$ pipefitter
|
30
|
+
Skipped compile because no changes were detected.
|
31
|
+
|
32
|
+
You can archive a compile in case it can be reused later (ex. switching back and forth between branches)
|
33
|
+
|
34
|
+
$ pipefitter --archive
|
35
|
+
$ rm -rf public/assets # oh no!
|
36
|
+
$ pipefitter
|
37
|
+
Used compiled assests from local archive!
|
38
|
+
# boom. didn't even need to recompile
|
39
|
+
|
40
|
+
Run a custom compile command:
|
41
|
+
|
42
|
+
$ pipefitter --command script/awesome_compile
|
43
|
+
Running `script/awesome_compile`...
|
44
|
+
Finished compiling assets!
|
45
|
+
|
46
|
+
If something seems out of sorts with the change detection, you can force asset compilation:
|
47
|
+
|
48
|
+
$ pipefitter --force
|
49
|
+
Running `bundle exec rake assets:precompile`...
|
50
|
+
Finished compiling assets!
|
51
|
+
|
52
|
+
## Contributing
|
53
|
+
|
54
|
+
1. Fork it
|
55
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
56
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
57
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
58
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/bin/pipefitter
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'pipefitter/error'
|
2
|
+
|
3
|
+
class Pipefitter
|
4
|
+
class Checksum
|
5
|
+
attr_reader :paths
|
6
|
+
|
7
|
+
def initialize(paths)
|
8
|
+
@paths = Array(paths)
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.checksum(paths)
|
12
|
+
new(paths).checksum
|
13
|
+
end
|
14
|
+
|
15
|
+
def checksum
|
16
|
+
verify_paths
|
17
|
+
`find #{paths.join(' ')} -type f -exec md5 -q {} + | md5 -q`.strip
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def verify_paths
|
23
|
+
paths.each do |path|
|
24
|
+
unless File.exists?(path)
|
25
|
+
raise PathNotFound, "Could not find #{path}"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class PathNotFound < Pipefitter::Error; end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
class Pipefitter
|
4
|
+
class Cli
|
5
|
+
attr_reader :arguments
|
6
|
+
|
7
|
+
def self.run(arguments, options = {})
|
8
|
+
new(arguments, options).run
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize(arguments, options = {})
|
12
|
+
@arguments = arguments
|
13
|
+
@options = options
|
14
|
+
end
|
15
|
+
|
16
|
+
def run
|
17
|
+
if help_requested?
|
18
|
+
logger.info help_text
|
19
|
+
else
|
20
|
+
Pipefitter.compile(path, compiler_options)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def path
|
27
|
+
path_argument || environment.fetch(:PWD)
|
28
|
+
end
|
29
|
+
|
30
|
+
def path_argument
|
31
|
+
arguments.reject do |arg|
|
32
|
+
arg =~ /\A\-\-/ || arg == command_argument
|
33
|
+
end.first
|
34
|
+
end
|
35
|
+
|
36
|
+
def environment
|
37
|
+
@environment ||= begin
|
38
|
+
environment = options.fetch(:environment)
|
39
|
+
symbolize_keys(environment)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def compiler_options
|
44
|
+
c_options = { :logger => logger }
|
45
|
+
if arguments.include?('--force')
|
46
|
+
c_options[:force] = true
|
47
|
+
end
|
48
|
+
if arguments.include?('--archive')
|
49
|
+
c_options[:archive] = true
|
50
|
+
end
|
51
|
+
if arguments.include?('--command')
|
52
|
+
c_options[:command] = command_argument
|
53
|
+
end
|
54
|
+
c_options
|
55
|
+
end
|
56
|
+
|
57
|
+
def options
|
58
|
+
@options_with_symbolized_keys ||= symbolize_keys(@options)
|
59
|
+
end
|
60
|
+
|
61
|
+
def command_argument
|
62
|
+
@command_argument ||= begin
|
63
|
+
if arguments.include?('--command')
|
64
|
+
arguments[arguments.index('--command') + 1]
|
65
|
+
else
|
66
|
+
nil
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def symbolize_keys(hash)
|
72
|
+
hash.inject({}) do |memo, (k, v)|
|
73
|
+
memo[k.to_sym] = v
|
74
|
+
memo
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def logger
|
79
|
+
@logger ||= options.fetch(:logger, Pipefitter::Logger.new)
|
80
|
+
end
|
81
|
+
|
82
|
+
def help_requested?
|
83
|
+
arguments.include?('--help')
|
84
|
+
end
|
85
|
+
|
86
|
+
def help_text
|
87
|
+
<<-EOS
|
88
|
+
# Pipefitter
|
89
|
+
|
90
|
+
## Usage
|
91
|
+
|
92
|
+
pipefitter [project path] [options]
|
93
|
+
|
94
|
+
### Arguments
|
95
|
+
|
96
|
+
* project root - optional project path to compile (default: current working directory)
|
97
|
+
* --force - forces a compile even when none is needed
|
98
|
+
* --archive - archives the compile assets to tmp in the project root
|
99
|
+
* --help - this help text :)
|
100
|
+
EOS
|
101
|
+
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
class Pipefitter
|
2
|
+
class Compiler
|
3
|
+
attr_reader :base_path
|
4
|
+
|
5
|
+
def initialize(base_path, options = {})
|
6
|
+
@options = options
|
7
|
+
@base_path = base_path
|
8
|
+
end
|
9
|
+
|
10
|
+
def compile
|
11
|
+
logger.info "Running `#{compile_command}`..."
|
12
|
+
result = `cd #{base_path} && #{compile_command} 2>&1`.chomp
|
13
|
+
status = $?.to_i == 0
|
14
|
+
log_result(result, status)
|
15
|
+
status
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def compile_command
|
21
|
+
options[:command] || default_compile_command
|
22
|
+
end
|
23
|
+
|
24
|
+
def default_compile_command
|
25
|
+
if using_bundler?
|
26
|
+
'bundle exec rake assets:precompile'
|
27
|
+
else
|
28
|
+
'rake assets:precompile'
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def using_bundler?
|
33
|
+
File.exists?(gemfile)
|
34
|
+
end
|
35
|
+
|
36
|
+
def gemfile
|
37
|
+
gemfile = File.join(base_path, 'Gemfile')
|
38
|
+
end
|
39
|
+
|
40
|
+
def options
|
41
|
+
@options
|
42
|
+
end
|
43
|
+
|
44
|
+
def log_result(result, status = true)
|
45
|
+
if status
|
46
|
+
logger.info result unless result == ''
|
47
|
+
else
|
48
|
+
logger.error result unless result == ''
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def logger
|
53
|
+
@logger ||= options.fetch(:logger, Pipefitter::Logger.new)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
class Pipefitter
|
2
|
+
class Compressor
|
3
|
+
attr_reader :base_path
|
4
|
+
|
5
|
+
def initialize(base_path, target_path = nil)
|
6
|
+
@base_path = base_path
|
7
|
+
end
|
8
|
+
|
9
|
+
def compress(filename = nil)
|
10
|
+
`cd #{base_path}/public && tar -czf #{target(filename)} #{source}`
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def source
|
16
|
+
'./assets'
|
17
|
+
end
|
18
|
+
|
19
|
+
def target(filename = nil)
|
20
|
+
target_filename = filename || default_target_filename
|
21
|
+
File.join(target_path, target_filename)
|
22
|
+
end
|
23
|
+
|
24
|
+
def target_path
|
25
|
+
@target_path ||= begin
|
26
|
+
File.join(base_path, 'tmp', 'pipefitter')
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def default_target_filename
|
31
|
+
"#{Time.now.to_i}.tar.gz"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
class Pipefitter
|
5
|
+
class Inventory
|
6
|
+
attr_reader :path
|
7
|
+
|
8
|
+
def initialize(path)
|
9
|
+
@path = path
|
10
|
+
FileUtils.mkdir_p(path)
|
11
|
+
end
|
12
|
+
|
13
|
+
def put(key, value)
|
14
|
+
data[key] = value
|
15
|
+
save
|
16
|
+
end
|
17
|
+
|
18
|
+
def get(key)
|
19
|
+
data[key]
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def data
|
25
|
+
@data ||= begin
|
26
|
+
if File.exists?(data_file)
|
27
|
+
YAML.load_file(data_file) || {}
|
28
|
+
else
|
29
|
+
FileUtils.touch(data_file)
|
30
|
+
{}
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def data_file
|
36
|
+
File.join(path, 'inventory.yml')
|
37
|
+
end
|
38
|
+
|
39
|
+
def save
|
40
|
+
File.open(data_file, 'w+') do |file|
|
41
|
+
file.write(data.to_yaml)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
class Pipefitter
|
4
|
+
class Logger < ::Logger
|
5
|
+
def initialize(logger = STDOUT)
|
6
|
+
super(logger)
|
7
|
+
original_formatter = ::Logger::Formatter.new
|
8
|
+
self.formatter = proc { |severity, datetime, progname, message|
|
9
|
+
"#{message}\n"
|
10
|
+
}
|
11
|
+
self
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
data/lib/pipefitter.rb
ADDED
@@ -0,0 +1,156 @@
|
|
1
|
+
lib = File.expand_path('../../lib', __FILE__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
require 'pipefitter/version'
|
4
|
+
require 'fileutils'
|
5
|
+
|
6
|
+
class Pipefitter
|
7
|
+
autoload 'Compiler', 'pipefitter/compiler'
|
8
|
+
autoload 'Checksum', 'pipefitter/checksum'
|
9
|
+
autoload 'Cli', 'pipefitter/cli'
|
10
|
+
autoload 'Compressor', 'pipefitter/compressor'
|
11
|
+
autoload 'Inventory', 'pipefitter/inventory'
|
12
|
+
autoload 'Error', 'pipefitter/error'
|
13
|
+
autoload 'Logger', 'pipefitter/logger'
|
14
|
+
|
15
|
+
attr_reader :base_path
|
16
|
+
|
17
|
+
def self.compile(base_path, options = {})
|
18
|
+
new(base_path, options).compile
|
19
|
+
end
|
20
|
+
|
21
|
+
def initialize(base_path, options = {})
|
22
|
+
@base_path = base_path
|
23
|
+
@options = options
|
24
|
+
end
|
25
|
+
|
26
|
+
def compile
|
27
|
+
setup
|
28
|
+
compile_if_necessary
|
29
|
+
end
|
30
|
+
|
31
|
+
def source_checksum
|
32
|
+
Checksum.checksum(source_paths)
|
33
|
+
end
|
34
|
+
|
35
|
+
def artifact_checksum
|
36
|
+
Checksum.checksum(artifact_paths)
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def compile_if_necessary
|
42
|
+
if assets_need_compiling?
|
43
|
+
use_archive_or_compile
|
44
|
+
else
|
45
|
+
logger.info 'Skipped compile because no changes were detected.'
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def use_archive_or_compile
|
50
|
+
if inventory_can_be_used?
|
51
|
+
move_archived_assets_into_place
|
52
|
+
logger.info 'Used compiled assests from local archive!'
|
53
|
+
else
|
54
|
+
compile_and_record_checksum
|
55
|
+
logger.info 'Finished compiling assets!'
|
56
|
+
archive
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def compile_and_record_checksum
|
61
|
+
if compiler.compile
|
62
|
+
inventory.put(source_checksum, artifact_checksum)
|
63
|
+
else
|
64
|
+
raise CompilationError
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def inventory_can_be_used?
|
69
|
+
! compile_forced? && inventory_contains_compiled_assets?
|
70
|
+
end
|
71
|
+
|
72
|
+
def archive
|
73
|
+
if archiving_enabled?
|
74
|
+
logger.info 'Started archiving assets...'
|
75
|
+
compressor.compress("#{source_checksum}.tar.gz")
|
76
|
+
logger.info 'Finished archiving assets!'
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def compiler
|
81
|
+
@compiler ||= Compiler.new(base_path,
|
82
|
+
:logger => logger,
|
83
|
+
:command => options[:command]
|
84
|
+
)
|
85
|
+
end
|
86
|
+
|
87
|
+
def compressor
|
88
|
+
@compressor ||= Compressor.new(base_path, :logger => logger)
|
89
|
+
end
|
90
|
+
|
91
|
+
def inventory
|
92
|
+
@inventory ||= Inventory.new(workspace)
|
93
|
+
end
|
94
|
+
|
95
|
+
def assets_need_compiling?
|
96
|
+
compile_forced? || inventory.get(source_checksum) != artifact_checksum
|
97
|
+
end
|
98
|
+
|
99
|
+
def archiving_enabled?
|
100
|
+
@archiving_enabled ||= options.fetch(:archive, false)
|
101
|
+
end
|
102
|
+
|
103
|
+
def workspace
|
104
|
+
File.join(base_path, 'tmp', 'pipefitter')
|
105
|
+
end
|
106
|
+
|
107
|
+
def setup
|
108
|
+
FileUtils.mkdir_p(workspace)
|
109
|
+
artifact_paths.each { |path| FileUtils.mkdir_p(path) }
|
110
|
+
end
|
111
|
+
|
112
|
+
def source_paths
|
113
|
+
paths = %w{
|
114
|
+
Gemfile
|
115
|
+
Gemfile.lock
|
116
|
+
app/assets
|
117
|
+
lib/assets
|
118
|
+
vendor/assets
|
119
|
+
}.map { |p| File.join(base_path, p) }
|
120
|
+
end
|
121
|
+
|
122
|
+
def artifact_paths
|
123
|
+
%w{
|
124
|
+
public/assets
|
125
|
+
}.map { |p| File.join(base_path, p) }
|
126
|
+
end
|
127
|
+
|
128
|
+
def inventory_contains_compiled_assets?
|
129
|
+
inventory_path = File.join(workspace, "#{source_checksum}.tar.gz")
|
130
|
+
inventory.get(source_checksum) && File.exists?(inventory_path)
|
131
|
+
end
|
132
|
+
|
133
|
+
def move_archived_assets_into_place
|
134
|
+
inventory_path = File.join(workspace, "#{source_checksum}.tar.gz")
|
135
|
+
FileUtils.rm_rf("#{base_path}/public/assets")
|
136
|
+
`cd #{base_path}/public && tar -xzf #{inventory_path}`
|
137
|
+
expected_artifact_checksum = inventory.get(source_checksum)
|
138
|
+
if expected_artifact_checksum != artifact_checksum
|
139
|
+
raise CompilationError, 'Archived assets did match stored checksum!'
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def compile_forced?
|
144
|
+
options.fetch(:force, false)
|
145
|
+
end
|
146
|
+
|
147
|
+
def options
|
148
|
+
@options
|
149
|
+
end
|
150
|
+
|
151
|
+
def logger
|
152
|
+
@logger ||= options.fetch(:logger, Pipefitter::Logger.new)
|
153
|
+
end
|
154
|
+
|
155
|
+
class CompilationError < Pipefitter::Error; end
|
156
|
+
end
|
data/pipefitter.gemspec
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'pipefitter/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "pipefitter"
|
8
|
+
gem.version = Pipefitter::VERSION
|
9
|
+
gem.authors = ["Lee Jones"]
|
10
|
+
gem.email = ["scribblethink@gmail.com"]
|
11
|
+
gem.description = %q{A command-line tool that avoids unnecessary compiles when using the Rails Asset Pipeline.}
|
12
|
+
gem.summary = %q{Pipefitter answers the age old question of "To compile or not to compile?"}
|
13
|
+
gem.homepage = "https://github.com/leejones/pipefitter"
|
14
|
+
|
15
|
+
gem.files = `git ls-files`.split($/)
|
16
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
|
+
gem.require_paths = ["lib"]
|
19
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require File.expand_path('../../spec_helper.rb', __FILE__)
|
2
|
+
|
3
|
+
describe Pipefitter::Checksum do
|
4
|
+
include Pipefitter::SpecHelper
|
5
|
+
|
6
|
+
let(:test_root) { '/tmp/pipefitter_tests' }
|
7
|
+
let(:rails_root) { "#{test_root}/stubbed_rails_app" }
|
8
|
+
|
9
|
+
before(:each) do
|
10
|
+
stub_rails_app(test_root, :destroy_initially => true)
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'checksums a group of files and directories' do
|
14
|
+
paths = %w{
|
15
|
+
Gemfile
|
16
|
+
Gemfile.lock
|
17
|
+
app/assets
|
18
|
+
lib/assets
|
19
|
+
vendor/assets
|
20
|
+
}.map { |p| File.join(rails_root, p) }
|
21
|
+
checksum = Pipefitter::Checksum.new(paths)
|
22
|
+
checksum.checksum.should eql('63af33df99e1f88bff6d3696f4ae6686')
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require File.expand_path('../../spec_helper.rb', __FILE__)
|
2
|
+
|
3
|
+
describe Pipefitter::Cli do
|
4
|
+
include Pipefitter::SpecHelper
|
5
|
+
|
6
|
+
it 'compiles a path' do
|
7
|
+
Pipefitter.should_receive(:compile).with('/tmp/pipefitter_app', kind_of(Hash)).and_return(true)
|
8
|
+
environment = { :PWD => '/tmp' }
|
9
|
+
Pipefitter::Cli.run(['/tmp/pipefitter_app'], :environment => environment)
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'compiles the PWD' do
|
13
|
+
Pipefitter.should_receive(:compile).with('/tmp/pipefitter_app', kind_of(Hash)).and_return(true)
|
14
|
+
environment = { :PWD => '/tmp/pipefitter_app' }
|
15
|
+
Pipefitter::Cli.run([], :environment => environment)
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'forces a compile' do
|
19
|
+
Pipefitter.should_receive(:compile).with('/tmp/pipefitter_app', {
|
20
|
+
:logger => kind_of(Logger),
|
21
|
+
:force => true
|
22
|
+
}).and_return(true)
|
23
|
+
environment = { :PWD => '/tmp/pipefitter_app' }
|
24
|
+
Pipefitter::Cli.run(['--force'], :environment => environment)
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'archives' do
|
28
|
+
Pipefitter.should_receive(:compile).with('/tmp/pipefitter_app', {
|
29
|
+
:logger => kind_of(Logger),
|
30
|
+
:archive => true
|
31
|
+
}).and_return(true)
|
32
|
+
environment = { :PWD => '/tmp/pipefitter_app' }
|
33
|
+
Pipefitter::Cli.run(['--archive'], :environment => environment)
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'helps' do
|
37
|
+
Pipefitter.should_not_receive(:compile)
|
38
|
+
environment = { :PWD => '/tmp/pipefitter_app' }
|
39
|
+
null_logger.should_receive(:info).with(/Usage/)
|
40
|
+
Pipefitter::Cli.run(['--help'], {
|
41
|
+
:environment => environment,
|
42
|
+
:logger => null_logger
|
43
|
+
})
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'uses a custom command' do
|
47
|
+
Pipefitter.should_receive(:compile).with('/tmp/pipefitter_app', {
|
48
|
+
:logger => kind_of(Logger),
|
49
|
+
:command => 'script/precompile_assets'
|
50
|
+
}).and_return(true)
|
51
|
+
environment = { :PWD => '/tmp/pipefitter_app' }
|
52
|
+
Pipefitter::Cli.run(['--command', 'script/precompile_assets'], :environment => environment)
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require File.expand_path('../../spec_helper.rb', __FILE__)
|
2
|
+
require 'logger'
|
3
|
+
|
4
|
+
describe Pipefitter::Compiler do
|
5
|
+
include Pipefitter::SpecHelper
|
6
|
+
|
7
|
+
let(:test_root) { '/tmp/pipefitter_tests' }
|
8
|
+
let(:rails_root) { "#{test_root}/stubbed_rails_app" }
|
9
|
+
|
10
|
+
before(:each) do
|
11
|
+
stub_rails_app(test_root, :destroy_initially => true)
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'compiles assets' do
|
15
|
+
compiler = Pipefitter::Compiler.new(rails_root, :logger => null_logger)
|
16
|
+
compiler.compile
|
17
|
+
File.exists?("#{rails_root}/public/assets/manifest.yml").should be_true
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require File.expand_path('../../../lib/pipefitter.rb', __FILE__)
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
describe Pipefitter::Inventory do
|
5
|
+
before(:each) do
|
6
|
+
cleanup_inventory
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'stores a record of a compilation' do
|
10
|
+
inventory = Pipefitter::Inventory.new('/tmp/pipefitter_tests')
|
11
|
+
inventory.put('source_checksum', 'artifact_checksum').should be_true
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'retrieves a record of a compilation' do
|
15
|
+
inventory = Pipefitter::Inventory.new('/tmp/pipefitter_tests')
|
16
|
+
inventory.put('source_checksum', 'artifact_checksum')
|
17
|
+
inventory.get('source_checksum').should eql('artifact_checksum')
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def cleanup_inventory
|
23
|
+
FileUtils.rm_rf('/tmp/pipefitter_tests')
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require File.expand_path('../spec_helper.rb', __FILE__)
|
2
|
+
require 'yaml'
|
3
|
+
|
4
|
+
describe Pipefitter do
|
5
|
+
include Pipefitter::SpecHelper
|
6
|
+
|
7
|
+
let(:test_root) { '/tmp/pipefitter_tests' }
|
8
|
+
let(:rails_root) { "#{test_root}/stubbed_rails_app" }
|
9
|
+
|
10
|
+
before(:each) do
|
11
|
+
stub_rails_app(test_root, :destroy_initially => true)
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'compiles assets' do
|
15
|
+
Pipefitter.compile(rails_root, :logger => null_logger)
|
16
|
+
manifest_file = "#{rails_root}/public/assets/manifest.yml"
|
17
|
+
File.exists?(manifest_file).should be_true
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'records a checksum' do
|
21
|
+
Pipefitter.compile(rails_root, :logger => null_logger)
|
22
|
+
inventory_file = "#{rails_root}/tmp/pipefitter/inventory.yml"
|
23
|
+
checksums = YAML.load_file(inventory_file)
|
24
|
+
checksums.has_key?('63af33df99e1f88bff6d3696f4ae6686').should be_true
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'stores an archived copy of compiled assets' do
|
28
|
+
Pipefitter.compile(rails_root, :archive => true, :logger => null_logger)
|
29
|
+
archive_file = "#{rails_root}/tmp/pipefitter/63af33df99e1f88bff6d3696f4ae6686.tar.gz"
|
30
|
+
File.exists?(archive_file).should be_true
|
31
|
+
`cd #{test_root} && tar -xzf #{archive_file}`
|
32
|
+
archived_assets_checksum = `find #{test_root}/assets -type f -exec md5 -q {} + | md5 -q`.strip
|
33
|
+
compiled_assets_checksum = `find #{rails_root}/public/assets -type f -exec md5 -q {} + | md5 -q`.strip
|
34
|
+
archived_assets_checksum.should eql(compiled_assets_checksum)
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'uses an archived copy of compiled assets when available' do
|
38
|
+
Pipefitter.compile(rails_root, :archive => true, :logger => null_logger)
|
39
|
+
original_checksum = Pipefitter::Checksum.new("#{rails_root}/public/assets").checksum
|
40
|
+
FileUtils.rm_rf("#{rails_root}/public/assets/manifest.yml")
|
41
|
+
compiler_stub = stub
|
42
|
+
Pipefitter::Compiler.stub(:new => compiler_stub)
|
43
|
+
compiler_stub.should_not_receive(:compile)
|
44
|
+
Pipefitter.compile(rails_root, :archive => true, :logger => null_logger)
|
45
|
+
final_checksum = Pipefitter::Checksum.new("#{rails_root}/public/assets").checksum
|
46
|
+
final_checksum.should eql(original_checksum)
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'only compiles when needed' do
|
50
|
+
FileUtils.mkdir_p("#{rails_root}/tmp/pipefitter")
|
51
|
+
File.open("#{rails_root}/tmp/pipefitter/inventory.yml", 'w+') do |file|
|
52
|
+
file.write({
|
53
|
+
'63af33df99e1f88bff6d3696f4ae6686' => 'd41d8cd98f00b204e9800998ecf8427e'
|
54
|
+
}.to_yaml)
|
55
|
+
end
|
56
|
+
compiler_stub = stub
|
57
|
+
Pipefitter::Compiler.stub(:new => compiler_stub)
|
58
|
+
compiler_stub.should_not_receive(:compile)
|
59
|
+
Pipefitter.compile(rails_root, :logger => null_logger)
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'does not record a checksum if the compile fails' do
|
63
|
+
FileUtils.rm("#{rails_root}/Rakefile")
|
64
|
+
expect { Pipefitter.compile(rails_root, :logger => null_logger) }.to raise_error(Pipefitter::CompilationError)
|
65
|
+
File.exists?("#{rails_root}/tmp/pipefitter/checksum.txt").should be_false
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'forces a compile' do
|
69
|
+
Pipefitter.compile(rails_root, :logger => null_logger)
|
70
|
+
compiler_stub = stub
|
71
|
+
Pipefitter::Compiler.stub(:new => compiler_stub)
|
72
|
+
compiler_stub.should_receive(:compile).and_return(true)
|
73
|
+
Pipefitter.compile(rails_root, :force => true, :logger => null_logger)
|
74
|
+
end
|
75
|
+
|
76
|
+
it 'uses a custom compile command', :focus => true do
|
77
|
+
compiler_stub = stub
|
78
|
+
Pipefitter::Compiler.should_receive(:new).with('/tmp/pipefitter_tests/stubbed_rails_app', :logger => kind_of(Logger), :command => 'script/precompile_assets').and_return(compiler_stub)
|
79
|
+
compiler_stub.should_receive(:compile).and_return(true)
|
80
|
+
Pipefitter.compile(rails_root, :command => 'script/precompile_assets', :logger => null_logger)
|
81
|
+
end
|
82
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require File.expand_path('../../lib/pipefitter.rb', __FILE__)
|
2
|
+
require 'fileutils'
|
3
|
+
require 'logger'
|
4
|
+
|
5
|
+
class Pipefitter
|
6
|
+
module SpecHelper
|
7
|
+
private
|
8
|
+
|
9
|
+
def stub_rails_app(base_working_directory, options = {})
|
10
|
+
if options[:destroy_initially]
|
11
|
+
FileUtils.rm_rf(base_working_directory)
|
12
|
+
end
|
13
|
+
FileUtils.mkdir_p(base_working_directory)
|
14
|
+
app_source_path = File.expand_path('../support/stubbed_rails_app', __FILE__)
|
15
|
+
FileUtils.cp_r(app_source_path, base_working_directory)
|
16
|
+
end
|
17
|
+
|
18
|
+
def null_logger
|
19
|
+
@null_logger ||= ::Logger.new('/dev/null')
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
gem 'rails', '3.2.12'
|
4
|
+
|
5
|
+
# Bundle edge Rails instead:
|
6
|
+
# gem 'rails', :git => 'git://github.com/rails/rails.git'
|
7
|
+
|
8
|
+
gem 'sqlite3'
|
9
|
+
|
10
|
+
|
11
|
+
# Gems used only for assets and not required
|
12
|
+
# in production environments by default.
|
13
|
+
group :assets do
|
14
|
+
gem 'sass-rails', '~> 3.2.3'
|
15
|
+
gem 'coffee-rails', '~> 3.2.1'
|
16
|
+
|
17
|
+
# See https://github.com/sstephenson/execjs#readme for more supported runtimes
|
18
|
+
# gem 'therubyracer', :platforms => :ruby
|
19
|
+
|
20
|
+
gem 'uglifier', '>= 1.0.3'
|
21
|
+
end
|
22
|
+
|
23
|
+
gem 'jquery-rails'
|
24
|
+
|
25
|
+
# To use ActiveModel has_secure_password
|
26
|
+
# gem 'bcrypt-ruby', '~> 3.0.0'
|
27
|
+
|
28
|
+
# To use Jbuilder templates for JSON
|
29
|
+
# gem 'jbuilder'
|
30
|
+
|
31
|
+
# Use unicorn as the app server
|
32
|
+
# gem 'unicorn'
|
33
|
+
|
34
|
+
# Deploy with Capistrano
|
35
|
+
# gem 'capistrano'
|
36
|
+
|
37
|
+
# To use debugger
|
38
|
+
# gem 'debugger'
|
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
namespace :assets do
|
5
|
+
task :precompile do
|
6
|
+
rails_root = File.expand_path('..', __FILE__)
|
7
|
+
asset_root = File.join(rails_root, 'public', 'assets')
|
8
|
+
FileUtils.mkdir_p(File.join(asset_root))
|
9
|
+
File.open(File.join(asset_root, 'manifest.yml'), 'w+') do |file|
|
10
|
+
file.write('list of compiled assets...')
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
Binary file
|
@@ -0,0 +1,15 @@
|
|
1
|
+
// This is a manifest file that'll be compiled into application.js, which will include all the files
|
2
|
+
// listed below.
|
3
|
+
//
|
4
|
+
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
|
5
|
+
// or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
|
6
|
+
//
|
7
|
+
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
|
8
|
+
// the compiled file.
|
9
|
+
//
|
10
|
+
// WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD
|
11
|
+
// GO AFTER THE REQUIRES BELOW.
|
12
|
+
//
|
13
|
+
//= require jquery
|
14
|
+
//= require jquery_ujs
|
15
|
+
//= require_tree .
|
@@ -0,0 +1,13 @@
|
|
1
|
+
/*
|
2
|
+
* This is a manifest file that'll be compiled into application.css, which will include all the files
|
3
|
+
* listed below.
|
4
|
+
*
|
5
|
+
* Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
|
6
|
+
* or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.
|
7
|
+
*
|
8
|
+
* You're free to add application-wide styles to this file and they'll appear at the top of the
|
9
|
+
* compiled file, but it's generally better to create a new file per style scope.
|
10
|
+
*
|
11
|
+
*= require_self
|
12
|
+
*= require_tree .
|
13
|
+
*/
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
metadata
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: pipefitter
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.1.0
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Lee Jones
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2013-03-24 00:00:00 Z
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: A command-line tool that avoids unnecessary compiles when using the Rails Asset Pipeline.
|
17
|
+
email:
|
18
|
+
- scribblethink@gmail.com
|
19
|
+
executables:
|
20
|
+
- pipefitter
|
21
|
+
extensions: []
|
22
|
+
|
23
|
+
extra_rdoc_files: []
|
24
|
+
|
25
|
+
files:
|
26
|
+
- .gitignore
|
27
|
+
- Gemfile
|
28
|
+
- LICENSE.txt
|
29
|
+
- README.md
|
30
|
+
- Rakefile
|
31
|
+
- bin/pipefitter
|
32
|
+
- lib/pipefitter.rb
|
33
|
+
- lib/pipefitter/checksum.rb
|
34
|
+
- lib/pipefitter/cli.rb
|
35
|
+
- lib/pipefitter/compiler.rb
|
36
|
+
- lib/pipefitter/compressor.rb
|
37
|
+
- lib/pipefitter/error.rb
|
38
|
+
- lib/pipefitter/inventory.rb
|
39
|
+
- lib/pipefitter/logger.rb
|
40
|
+
- lib/pipefitter/version.rb
|
41
|
+
- pipefitter.gemspec
|
42
|
+
- spec/pipefitter/checksum_spec.rb
|
43
|
+
- spec/pipefitter/cli_spec.rb
|
44
|
+
- spec/pipefitter/compiler_spec.rb
|
45
|
+
- spec/pipefitter/inventory_spec.rb
|
46
|
+
- spec/pipefitter_spec.rb
|
47
|
+
- spec/spec_helper.rb
|
48
|
+
- spec/support/stubbed_rails_app/Gemfile
|
49
|
+
- spec/support/stubbed_rails_app/Rakefile
|
50
|
+
- spec/support/stubbed_rails_app/app/assets/images/rails.png
|
51
|
+
- spec/support/stubbed_rails_app/app/assets/javascripts/application.js
|
52
|
+
- spec/support/stubbed_rails_app/app/assets/stylesheets/application.css
|
53
|
+
- spec/support/stubbed_rails_app/lib/assets/.gitkeep
|
54
|
+
- spec/support/stubbed_rails_app/public/.gitkeep
|
55
|
+
- spec/support/stubbed_rails_app/vendor/assets/javascripts/.gitkeep
|
56
|
+
- spec/support/stubbed_rails_app/vendor/assets/stylesheets/.gitkeep
|
57
|
+
- spec/support/stubbed_rails_app/vendor/plugins/.gitkeep
|
58
|
+
homepage: https://github.com/leejones/pipefitter
|
59
|
+
licenses: []
|
60
|
+
|
61
|
+
post_install_message:
|
62
|
+
rdoc_options: []
|
63
|
+
|
64
|
+
require_paths:
|
65
|
+
- lib
|
66
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
67
|
+
none: false
|
68
|
+
requirements:
|
69
|
+
- - ">="
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
version: "0"
|
72
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ">="
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: "0"
|
78
|
+
requirements: []
|
79
|
+
|
80
|
+
rubyforge_project:
|
81
|
+
rubygems_version: 1.8.24
|
82
|
+
signing_key:
|
83
|
+
specification_version: 3
|
84
|
+
summary: Pipefitter answers the age old question of "To compile or not to compile?"
|
85
|
+
test_files:
|
86
|
+
- spec/pipefitter/checksum_spec.rb
|
87
|
+
- spec/pipefitter/cli_spec.rb
|
88
|
+
- spec/pipefitter/compiler_spec.rb
|
89
|
+
- spec/pipefitter/inventory_spec.rb
|
90
|
+
- spec/pipefitter_spec.rb
|
91
|
+
- spec/spec_helper.rb
|
92
|
+
- spec/support/stubbed_rails_app/Gemfile
|
93
|
+
- spec/support/stubbed_rails_app/Rakefile
|
94
|
+
- spec/support/stubbed_rails_app/app/assets/images/rails.png
|
95
|
+
- spec/support/stubbed_rails_app/app/assets/javascripts/application.js
|
96
|
+
- spec/support/stubbed_rails_app/app/assets/stylesheets/application.css
|
97
|
+
- spec/support/stubbed_rails_app/lib/assets/.gitkeep
|
98
|
+
- spec/support/stubbed_rails_app/public/.gitkeep
|
99
|
+
- spec/support/stubbed_rails_app/vendor/assets/javascripts/.gitkeep
|
100
|
+
- spec/support/stubbed_rails_app/vendor/assets/stylesheets/.gitkeep
|
101
|
+
- spec/support/stubbed_rails_app/vendor/plugins/.gitkeep
|