percy-cli 0.0.2 → 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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a79b07a50003692a4126836dca74ca72787c479c
4
- data.tar.gz: 44b4607a5fe3267aae10e772c7b7057f33ded2eb
3
+ metadata.gz: 7b25363b82da9f2043e7e5c748bc63d7e7e84535
4
+ data.tar.gz: f511e267c41c23f9b3cb04263ae6491919a811ce
5
5
  SHA512:
6
- metadata.gz: 29b2632be605f99a12265a798b7afb40552934bc7445f514a48723ed9842d84672d1524a2f7f1f32fdc1f2615043ed96d14bdb526c3165961a03a0e5590a4549
7
- data.tar.gz: 14b7f5941ca563a9bf5c3df841638a162d5c16dd3e3ede57fc644e9fe1190d39ba63dc6e10893f6e6a1e3a644c387e6f652a0bad0f57b0a1035b0b829cd57639
6
+ metadata.gz: 7b230e067e0ec50a21f27bd6fe0e53152f229a6502f1df9db3e5c50168f0091558d1bf73f5447de4468fef190420c8ee04b8909f7fa3df5161b8a766b4977b6a
7
+ data.tar.gz: 117c24852676b9870e6baebd3120697db9c1d4ffd609f79819cca57547d7e479cba3a370629897b17f07bf66265eef32b12acd0ee3b74091e90a6e4234ea3d6d
data/README.md CHANGED
@@ -1,5 +1,8 @@
1
1
  # Percy CLI
2
2
 
3
+ [![Build Status](https://travis-ci.org/percy/percy-cli.svg?branch=master)](https://travis-ci.org/percy/percy-cli)
4
+ [![Gem Version](https://badge.fury.io/rb/percy-cli.svg)](http://badge.fury.io/rb/percy-cli)
5
+
3
6
  Command-line interface for [Percy](https://percy.io).
4
7
 
5
8
  ## Installation
data/bin/percy CHANGED
@@ -3,4 +3,4 @@
3
3
  require 'commander'
4
4
  require 'percy/cli'
5
5
 
6
- Percy::Cli.new.run if $0 == __FILE__
6
+ Percy::Cli.new.run
@@ -1,6 +1,9 @@
1
1
  require 'find'
2
2
  require 'digest'
3
3
  require 'uri'
4
+ require 'thread/pool'
5
+
6
+ Thread::Pool.abort_on_exception = true
4
7
 
5
8
  module Percy
6
9
  class Cli
@@ -44,9 +47,9 @@ module Percy
44
47
  # resource path
45
48
  "(?:/[^\\s\"']*)?"
46
49
  )
47
- HTML_REMOTE_URL_REGEX = Regexp.new("<link.*?href=['\"](" + REMOTE_URL_REGEX_STRING + ")")
50
+ HTML_REMOTE_URL_REGEX = Regexp.new("(<link.*?href=['\"](" + REMOTE_URL_REGEX_STRING + ")[^>]+)")
48
51
 
49
- # Match all url("https://...") calls, with whitespace and quote variatinos.
52
+ # Match all url("https://...") styles, with whitespace and quote variatinos.
50
53
  CSS_REMOTE_URL_REGEX = Regexp.new(
51
54
  "url\\s*\\([\"'\s]*(" + REMOTE_URL_REGEX_STRING + ")[\"'\s]*\\)"
52
55
  )
@@ -55,9 +58,8 @@ module Percy
55
58
  repo = options[:repo] || Percy.config.repo
56
59
  strip_prefix = File.absolute_path(options[:strip_prefix] || root_dir)
57
60
  autoload_remote_resources = options[:autoload_remote_resources] || false
58
-
59
- # Create a Percy build for the snapshots.
60
- build = Percy.create_build(repo)
61
+ num_threads = options[:threads] || 10
62
+ snapshot_limit = options[:snapshot_limit]
61
63
 
62
64
  # Find all the static files in the given root directory.
63
65
  root_paths = find_root_paths(root_dir, snapshots_regex: options[:snapshots_regex])
@@ -70,14 +72,42 @@ module Percy
70
72
  related_resources += build_remote_resources(remote_urls)
71
73
  end
72
74
 
75
+ all_resources = root_resources + related_resources
76
+
77
+ if root_resources.empty?
78
+ say "No root resource files found. Are there HTML files in the given directory?"
79
+ exit(-1)
80
+ end
81
+
82
+ say 'Creating build...'
83
+ build = Percy.create_build(repo, resources: related_resources)
84
+
85
+ say 'Uploading build resources...'
86
+ upload_missing_resources(build, build, all_resources, {num_threads: num_threads})
87
+
73
88
  # Upload a snapshot for every root resource, and associate the related_resources.
89
+ output_lock = Mutex.new
90
+ snapshot_thread_pool = Thread.pool(num_threads)
91
+ total = snapshot_limit ? [root_resources.length, snapshot_limit].min : root_resources.length
74
92
  root_resources.each_with_index do |root_resource, i|
75
- say "Uploading snapshot (#{i+1}/#{root_resources.length}): #{root_resource.resource_url}"
76
- upload_snapshot(build, root_resource, related_resources)
93
+ break if snapshot_limit && i + 1 > snapshot_limit
94
+ snapshot_thread_pool.process do
95
+ output_lock.synchronize do
96
+ say "Uploading snapshot (#{i+1}/#{total}): #{root_resource.resource_url}"
97
+ end
98
+ snapshot = Percy.create_snapshot(build['data']['id'], [root_resource])
99
+ upload_missing_resources(build, snapshot, all_resources, {num_threads: num_threads})
100
+ Percy.finalize_snapshot(snapshot['data']['id'])
101
+ end
77
102
  end
103
+ snapshot_thread_pool.wait
104
+ snapshot_thread_pool.shutdown
78
105
 
79
106
  # Finalize the build.
107
+ say 'Finalizing build...'
80
108
  Percy.finalize_build(build['data']['id'])
109
+ say "Done! Percy is now processing, you can view the visual diffs here:"
110
+ say build['data']['attributes']['web-url']
81
111
  end
82
112
 
83
113
  private
@@ -121,13 +151,16 @@ module Percy
121
151
  case extension
122
152
  when '.html'
123
153
  content = File.read(path)
124
- urls += content.scan(HTML_REMOTE_URL_REGEX).map { |match| maybe_add_protocol(match[0]) }
154
+ urls += content.scan(HTML_REMOTE_URL_REGEX).map do |match|
155
+ next if !match[0].include?('stylesheet') # Only include links with rel="stylesheet".
156
+ maybe_add_protocol(match[1])
157
+ end
125
158
  when '.css'
126
159
  content = File.read(path)
127
160
  urls += content.scan(CSS_REMOTE_URL_REGEX).map { |match| maybe_add_protocol(match[0]) }
128
161
  end
129
162
  end
130
- urls.uniq
163
+ urls.compact.uniq
131
164
  end
132
165
 
133
166
  def maybe_add_protocol(url)
@@ -156,8 +189,8 @@ module Percy
156
189
  remote_urls.length,
157
190
  title: 'Fetching remote resources...',
158
191
  format: ':title |:progress_bar| :percent_complete% complete - :url',
159
- width: 40,
160
- complete_message: nil,
192
+ width: 20,
193
+ complete_message: "Fetched #{remote_urls.length} remote resources.",
161
194
  )
162
195
 
163
196
  remote_urls.each do |url|
@@ -169,7 +202,7 @@ module Percy
169
202
  next
170
203
  end
171
204
  if response.status != 200
172
- say_error "Remote resource failed, skipping: #{url}"
205
+ say_error "Remote resource failed, skipping (#{response.status}): #{url}"
173
206
  next
174
207
  end
175
208
 
@@ -179,34 +212,37 @@ module Percy
179
212
  resources
180
213
  end
181
214
 
182
- def upload_snapshot(build, root_resource, related_resources)
183
- all_resources = [root_resource] + related_resources
184
-
185
- # Create the snapshot for this page. For simplicity, include all non-HTML resources in the
186
- # snapshot as related resources. May seem inefficient, but they will only be uploaded once.
187
- snapshot = Percy.create_snapshot(build['data']['id'], all_resources)
188
-
215
+ # Uploads missing resources either for a build or snapshot.
216
+ def upload_missing_resources(build, obj, potential_resources, options = {})
189
217
  # Upload the content for any missing resources.
190
- missing_resources = snapshot['data']['relationships']['missing-resources']['data']
218
+ missing_resources = obj['data']['relationships']['missing-resources']['data']
191
219
  bar = Commander::UI::ProgressBar.new(
192
220
  missing_resources.length,
193
221
  title: 'Uploading resources...',
194
222
  format: ':title |:progress_bar| :percent_complete% complete - :resource_url',
195
- width: 40,
223
+ width: 20,
196
224
  complete_message: nil,
197
225
  )
226
+ output_lock = Mutex.new
227
+ uploader_thread_pool = Thread.pool(options[:num_threads] || 10)
198
228
  missing_resources.each do |missing_resource|
199
- missing_resource_sha = missing_resource['id']
200
- resource = all_resources.find { |r| r.sha == missing_resource_sha }
201
- path = resource.resource_url
202
- bar.increment resource_url: resource.resource_url
203
-
204
- # Remote resources are stored in 'content', local resources are read from the filesystem.
205
- content = resource.content || File.read("#{resource.path}")
206
-
207
- Percy.upload_resource(build['data']['id'], content)
229
+ uploader_thread_pool.process do
230
+ missing_resource_sha = missing_resource['id']
231
+ resource = potential_resources.find { |r| r.sha == missing_resource_sha }
232
+ path = resource.resource_url
233
+ output_lock.synchronize do
234
+ bar.increment resource_url: resource.resource_url
235
+ end
236
+
237
+ # Remote resources are stored in 'content', local resources are read from the filesystem.
238
+ content = resource.content || File.read("#{resource.path}")
239
+
240
+ Percy.upload_resource(build['data']['id'], content)
241
+ end
208
242
  end
243
+ uploader_thread_pool.wait
244
+ uploader_thread_pool.shutdown
209
245
  end
210
246
  end
211
247
  end
212
- end
248
+ end
@@ -1,5 +1,5 @@
1
1
  module Percy
2
2
  class Cli
3
- VERSION = '0.0.2'
3
+ VERSION = '0.1.0'
4
4
  end
5
5
  end
data/lib/percy/cli.rb CHANGED
@@ -8,6 +8,9 @@ module Percy
8
8
  include Commander::Methods
9
9
  include Percy::Cli::Snapshot
10
10
 
11
+ DEFAULT_NUM_THREADS = 10
12
+ MAX_NUM_THREADS = 50
13
+
11
14
  def say(*args)
12
15
  $terminal.say(*args)
13
16
  end
@@ -38,13 +41,25 @@ module Percy
38
41
  '--snapshots_regex REGEX',
39
42
  String,
40
43
  'Regular expression for matching the files to snapshot. Defaults to: "\.(html|htm)$"'
44
+ c.option \
45
+ '--snapshot_limit NUM',
46
+ Integer,
47
+ "Max number of snapshots to upload, useful for testing. Default is unlimited."
41
48
  c.option \
42
49
  '--autoload_remote_resources',
43
50
  'Attempts to parse HTML and CSS for remote resources, fetch them, and include in ' +
44
51
  'snapshots. This can be very useful if your static website relies on remote resources.'
52
+ c.option \
53
+ '--threads NUM',
54
+ Integer,
55
+ "Number of threads in pools for snapshot and resource uploads. " +
56
+ "Defaults to #{DEFAULT_NUM_THREADS}, max #{MAX_NUM_THREADS}."
45
57
 
46
58
  c.action do |args, options|
47
59
  options.default autoload_remote_resources: false
60
+ options.default threads: DEFAULT_NUM_THREADS
61
+ options.threads = MAX_NUM_THREADS if options.threads > MAX_NUM_THREADS
62
+
48
63
  raise OptionParser::MissingArgument, 'root folder path is required' if args.empty?
49
64
  if args.length > 1
50
65
  raise OptionParser::MissingArgument, 'only a single root folder path can be given'
data/percy-cli.gemspec CHANGED
@@ -19,8 +19,9 @@ Gem::Specification.new do |spec|
19
19
  spec.require_paths = ['lib']
20
20
 
21
21
  spec.add_dependency 'commander', '~> 4'
22
- spec.add_dependency 'percy-client', '~> 0.2'
22
+ spec.add_dependency 'percy-client', '>= 0.2.4'
23
23
  spec.add_dependency 'faraday', '>= 0.8'
24
+ spec.add_dependency 'thread', '~> 0.2'
24
25
 
25
26
  spec.add_development_dependency 'bundler', '~> 1.7'
26
27
  spec.add_development_dependency 'rake', '~> 10.0'
@@ -12,6 +12,8 @@
12
12
 
13
13
  <link rel="stylesheet" type="text/css" href="http://example.com:12345/test-diff-tag-order.css">
14
14
 
15
+ <link rel="author" href="https://example.com/should-not-be-included"/>
16
+
15
17
  <h1>Hello World!</h1>
16
18
 
17
19
  Just some text, should not be included:
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: percy-cli
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Perceptual Inc.
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-06-05 00:00:00.000000000 Z
11
+ date: 2015-06-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: commander
@@ -28,16 +28,16 @@ dependencies:
28
28
  name: percy-client
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - "~>"
31
+ - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: '0.2'
33
+ version: 0.2.4
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - "~>"
38
+ - - ">="
39
39
  - !ruby/object:Gem::Version
40
- version: '0.2'
40
+ version: 0.2.4
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: faraday
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -52,6 +52,20 @@ dependencies:
52
52
  - - ">="
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0.8'
55
+ - !ruby/object:Gem::Dependency
56
+ name: thread
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '0.2'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '0.2'
55
69
  - !ruby/object:Gem::Dependency
56
70
  name: bundler
57
71
  requirement: !ruby/object:Gem::Requirement