percy-client 0.0.1 → 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 +4 -4
- data/.gitignore +9 -30
- data/.rspec +3 -0
- data/.travis.yml +7 -0
- data/Gemfile +3 -1
- data/Guardfile +14 -0
- data/LICENSE +2 -2
- data/README.md +34 -2
- data/Rakefile +1 -1
- data/lib/percy.rb +37 -0
- data/lib/percy/client.rb +47 -0
- data/lib/percy/client/builds.rb +32 -0
- data/lib/percy/client/connection.rb +50 -0
- data/lib/percy/client/local_git.rb +50 -0
- data/lib/percy/client/resources.rb +54 -0
- data/lib/percy/client/snapshots.rb +22 -0
- data/lib/percy/client/version.rb +5 -0
- data/percy-client.gemspec +29 -0
- data/spec/cassettes/Percy_Client_Builds/_create_build/creates_a_build.yml +64 -0
- data/spec/cassettes/Percy_Client_Builds/_finalize_build/finalizes_a_build.yml +121 -0
- data/spec/cassettes/Percy_Client_Resources/_upload_resource/returns_true_with_success.yml +237 -0
- data/spec/cassettes/Percy_Client_Snapshots/_create_snapshot/creates_a_build.yml +121 -0
- data/spec/cassettes/Percy_Client_Snapshots/_create_snapshot/fails_if_no_resources_are_given.yml +122 -0
- data/spec/lib/percy/client/builds_spec.rb +19 -0
- data/spec/lib/percy/client/connection_spec.rb +16 -0
- data/spec/lib/percy/client/local_git_spec.rb +21 -0
- data/spec/lib/percy/client/resources_spec.rb +40 -0
- data/spec/lib/percy/client/snapshots_spec.rb +26 -0
- data/spec/lib/percy/client_spec.rb +17 -0
- data/spec/spec_helper.rb +34 -0
- data/spec/support/test_helpers.rb +7 -0
- data/spec/support/vcr_setup.rb +27 -0
- metadata +116 -9
- data/lib/perceptual.rb +0 -5
- data/lib/perceptual/version.rb +0 -3
- data/perceptual.gemspec +0 -23
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a82de11a13b2a403586e6e50adc8afeda0bc32e1
|
4
|
+
data.tar.gz: 79849f84e7c401e6e310f4208a6db745f1f3e1e1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 33a045cccc8e025003506b6a0d1a7504bf1bec9d3b718dc77b6fde44eb829a183e77c3471b085796b136eb0a1a9b48c928895ef36ab01430b65e1c7711af4f05
|
7
|
+
data.tar.gz: 223a6d07e57d67d95248d1bd3d385b396663d6435e6f8cdfbb025cca4699fa77ad9f016195f21b87f008869f782d53d97161c85a5240e93549af6fca2d81c67a
|
data/.gitignore
CHANGED
@@ -1,35 +1,14 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
/.config
|
1
|
+
/.bundle/
|
2
|
+
/Gemfile.lock
|
4
3
|
/coverage/
|
5
|
-
/
|
4
|
+
/doc/
|
6
5
|
/pkg/
|
7
6
|
/spec/reports/
|
8
|
-
/test/tmp/
|
9
|
-
/test/version_tmp/
|
10
7
|
/tmp/
|
8
|
+
*.bundle
|
9
|
+
*.so
|
10
|
+
*.o
|
11
|
+
*.a
|
12
|
+
*.rbc
|
13
|
+
mkmf.log
|
11
14
|
.DS_Store
|
12
|
-
|
13
|
-
## Specific to RubyMotion:
|
14
|
-
.dat*
|
15
|
-
.repl_history
|
16
|
-
build/
|
17
|
-
|
18
|
-
## Documentation cache and generated files:
|
19
|
-
/.yardoc/
|
20
|
-
/_yardoc/
|
21
|
-
/doc/
|
22
|
-
/rdoc/
|
23
|
-
|
24
|
-
## Environment normalisation:
|
25
|
-
/.bundle/
|
26
|
-
/lib/bundler/man/
|
27
|
-
|
28
|
-
# for a library or gem, you might want to ignore these files since the code is
|
29
|
-
# intended to run in multiple environments; otherwise, check them in:
|
30
|
-
# Gemfile.lock
|
31
|
-
# .ruby-version
|
32
|
-
# .ruby-gemset
|
33
|
-
|
34
|
-
# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
|
35
|
-
.rvmrc
|
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
CHANGED
data/Guardfile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
guard :rspec, cmd: "bundle exec rspec" do
|
2
|
+
require "guard/rspec/dsl"
|
3
|
+
dsl = Guard::RSpec::Dsl.new(self)
|
4
|
+
|
5
|
+
# RSpec files
|
6
|
+
rspec = dsl.rspec
|
7
|
+
watch(rspec.spec_helper) { rspec.spec_dir }
|
8
|
+
watch(rspec.spec_support) { rspec.spec_dir }
|
9
|
+
watch(rspec.spec_files)
|
10
|
+
|
11
|
+
# Ruby files
|
12
|
+
ruby = dsl.ruby
|
13
|
+
dsl.watch_spec_files_for(ruby.lib_files)
|
14
|
+
end
|
data/LICENSE
CHANGED
@@ -1,6 +1,6 @@
|
|
1
|
-
|
1
|
+
Copyright (c) 2015 Perceptual Inc.
|
2
2
|
|
3
|
-
|
3
|
+
The MIT License (MIT)
|
4
4
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
6
|
of this software and associated documentation files (the "Software"), to deal
|
data/README.md
CHANGED
@@ -1,2 +1,34 @@
|
|
1
|
-
#
|
2
|
-
|
1
|
+
# Percy::Client
|
2
|
+
|
3
|
+
[](https://travis-ci.org/percy/percy-client)
|
4
|
+
[](http://badge.fury.io/rb/percy-client)
|
5
|
+
|
6
|
+
(in development, coming soon)
|
7
|
+
|
8
|
+
## Installation
|
9
|
+
|
10
|
+
Add this line to your application's Gemfile:
|
11
|
+
|
12
|
+
```ruby
|
13
|
+
gem 'percy-client'
|
14
|
+
```
|
15
|
+
|
16
|
+
And then execute:
|
17
|
+
|
18
|
+
$ bundle
|
19
|
+
|
20
|
+
Or install it yourself as:
|
21
|
+
|
22
|
+
$ gem install percy-client
|
23
|
+
|
24
|
+
## Usage
|
25
|
+
|
26
|
+
...
|
27
|
+
|
28
|
+
## Contributing
|
29
|
+
|
30
|
+
1. Fork it ( https://github.com/percy/percy-client/fork )
|
31
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
32
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
33
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
34
|
+
5. Create a new Pull Request
|
data/Rakefile
CHANGED
@@ -1,2 +1,2 @@
|
|
1
|
-
require
|
1
|
+
require 'bundler/gem_tasks'
|
2
2
|
|
data/lib/percy.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'percy/client'
|
2
|
+
|
3
|
+
module Percy
|
4
|
+
class << self
|
5
|
+
attr_accessor :access_token
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.options
|
9
|
+
{
|
10
|
+
access_token: access_token,
|
11
|
+
}
|
12
|
+
end
|
13
|
+
|
14
|
+
# API client based on configured options.
|
15
|
+
#
|
16
|
+
# @return [Percy::Client] API client.
|
17
|
+
def self.client
|
18
|
+
@client = Percy::Client.new(options) unless defined?(@client)
|
19
|
+
@client
|
20
|
+
end
|
21
|
+
|
22
|
+
# @private
|
23
|
+
def self.respond_to_missing?(method_name, include_private = false)
|
24
|
+
client.respond_to?(method_name, include_private)
|
25
|
+
end if RUBY_VERSION >= '1.9'
|
26
|
+
|
27
|
+
# @private
|
28
|
+
def self.respond_to?(method_name, include_private = false)
|
29
|
+
client.respond_to?(method_name, include_private) || super
|
30
|
+
end if RUBY_VERSION < '1.9'
|
31
|
+
|
32
|
+
def self.method_missing(method_name, *args, &block)
|
33
|
+
return super if !client.respond_to?(method_name)
|
34
|
+
client.send(method_name, *args, &block)
|
35
|
+
end
|
36
|
+
private :method_missing
|
37
|
+
end
|
data/lib/percy/client.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'percy/client/connection'
|
3
|
+
require 'percy/client/local_git'
|
4
|
+
require 'percy/client/version'
|
5
|
+
require 'percy/client/builds'
|
6
|
+
require 'percy/client/snapshots'
|
7
|
+
require 'percy/client/resources'
|
8
|
+
|
9
|
+
module Percy
|
10
|
+
class Client
|
11
|
+
include Percy::Client::Connection
|
12
|
+
include Percy::Client::LocalGit
|
13
|
+
include Percy::Client::Builds
|
14
|
+
include Percy::Client::Snapshots
|
15
|
+
include Percy::Client::Resources
|
16
|
+
|
17
|
+
class Error < Exception; end
|
18
|
+
class ClientError < Error
|
19
|
+
attr_accessor :env
|
20
|
+
def initialize(env, *args)
|
21
|
+
@env = env
|
22
|
+
super(*args)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
API_BASE_URL = ENV['PERCY_API'] || 'https://percy.io'
|
27
|
+
API_VERSION = ENV['PERCY_API_VERSION'] || 'v1'
|
28
|
+
|
29
|
+
attr_accessor :access_token
|
30
|
+
|
31
|
+
def initialize(options = {})
|
32
|
+
@access_token = options[:access_token] || ENV['PERCY_TOKEN']
|
33
|
+
end
|
34
|
+
|
35
|
+
def base_url
|
36
|
+
API_BASE_URL
|
37
|
+
end
|
38
|
+
|
39
|
+
def base_path
|
40
|
+
"/api/#{API_VERSION}"
|
41
|
+
end
|
42
|
+
|
43
|
+
def full_base
|
44
|
+
"#{base_url}#{base_path}"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Percy
|
2
|
+
class Client
|
3
|
+
module Builds
|
4
|
+
def create_build(repo_slug)
|
5
|
+
commit = Percy.current_local_commit
|
6
|
+
data = {
|
7
|
+
'data' => {
|
8
|
+
'type' => 'builds',
|
9
|
+
'attributes' => {
|
10
|
+
'commit-sha' => commit[:sha],
|
11
|
+
'commit-branch' => commit[:branch],
|
12
|
+
'commit-committed-at' => commit[:committed_at],
|
13
|
+
'commit-author-name' => commit[:author_name],
|
14
|
+
'commit-author-email' => commit[:author_email],
|
15
|
+
'commit-committer-name' => commit[:committer_name],
|
16
|
+
'commit-committer-email' => commit[:committer_email],
|
17
|
+
'commit-message' => commit[:message],
|
18
|
+
'pull-request-number' => nil,
|
19
|
+
},
|
20
|
+
}
|
21
|
+
}
|
22
|
+
post("#{full_base}/repos/#{repo_slug}/builds/", data)
|
23
|
+
end
|
24
|
+
|
25
|
+
def finalize_build(build_id)
|
26
|
+
post("#{full_base}/builds/#{build_id}/finalize", {})
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
|
3
|
+
module Percy
|
4
|
+
class Client
|
5
|
+
module Connection
|
6
|
+
class FaradayNiceErrorMiddleware < Faraday::Response::Middleware
|
7
|
+
CLIENT_ERROR_STATUS_RANGE = 400...600
|
8
|
+
|
9
|
+
def on_complete(env)
|
10
|
+
case env[:status]
|
11
|
+
when 407
|
12
|
+
# Mimic the behavior that we get with proxy requests with HTTPS.
|
13
|
+
raise Faraday::Error::ConnectionFailed, %{407 "Proxy Authentication Required "}
|
14
|
+
when CLIENT_ERROR_STATUS_RANGE
|
15
|
+
raise Percy::Client::ClientError.new(
|
16
|
+
env, "Got #{env.status} (#{env.method.upcase} #{env.url}):\n#{env.body}")
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def connection
|
22
|
+
return @connection if defined?(@connection)
|
23
|
+
@connection = Faraday.new(url: base_url) do |faraday|
|
24
|
+
faraday.request :token_auth, @access_token if @access_token
|
25
|
+
|
26
|
+
faraday.use Faraday::Adapter::HTTPClient
|
27
|
+
faraday.use Percy::Client::Connection::FaradayNiceErrorMiddleware
|
28
|
+
end
|
29
|
+
@connection
|
30
|
+
end
|
31
|
+
|
32
|
+
def get(path)
|
33
|
+
response = connection.get do |request|
|
34
|
+
request.url(path)
|
35
|
+
request.headers['Content-Type'] = 'application/vnd.api+json'
|
36
|
+
end
|
37
|
+
JSON.parse(response.body)
|
38
|
+
end
|
39
|
+
|
40
|
+
def post(path, data)
|
41
|
+
response = connection.post do |request|
|
42
|
+
request.url(path)
|
43
|
+
request.headers['Content-Type'] = 'application/vnd.api+json'
|
44
|
+
request.body = data.to_json
|
45
|
+
end
|
46
|
+
JSON.parse(response.body)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Percy
|
2
|
+
class Client
|
3
|
+
module LocalGit
|
4
|
+
GIT_FORMAT_LINES = [
|
5
|
+
'COMMIT_SHA:%H',
|
6
|
+
'AUTHOR_DATE:%ai',
|
7
|
+
'AUTHOR_NAME:%an',
|
8
|
+
'AUTHOR_EMAIL:%ae',
|
9
|
+
'COMMITTER_NAME:%an',
|
10
|
+
'COMMITTER_EMAIL:%ae',
|
11
|
+
'COMMITTER_DATE:%ai',
|
12
|
+
# Note: order is important, this must come last because the regex is a multiline match.
|
13
|
+
'COMMIT_MESSAGE:%B'
|
14
|
+
].freeze
|
15
|
+
|
16
|
+
class Error < Exception; end
|
17
|
+
class NoLocalRepo < Exception; end
|
18
|
+
|
19
|
+
def current_local_commit
|
20
|
+
commit = ENV['PERCY_COMMIT'] || 'HEAD'
|
21
|
+
branch = ENV['PERCY_BRANCH'] || `git rev-parse --abbrev-ref HEAD`.strip
|
22
|
+
if branch == ''
|
23
|
+
raise Percy::Client::LocalGit::NoLocalRepo.new('No local git repository found.')
|
24
|
+
end
|
25
|
+
|
26
|
+
format = GIT_FORMAT_LINES.join('%n') # "git show" format uses %n for newlines.
|
27
|
+
output = `git show --quiet #{commit} --format="#{format}"`.strip
|
28
|
+
data = {
|
29
|
+
sha: output.match(/COMMIT_SHA:(.*)/)[1],
|
30
|
+
branch: branch,
|
31
|
+
committed_at: output.match(/AUTHOR_DATE:(.*)/)[1],
|
32
|
+
author_name: output.match(/AUTHOR_NAME:(.*)/)[1],
|
33
|
+
author_email: output.match(/AUTHOR_EMAIL:(.*)/)[1],
|
34
|
+
committer_name: output.match(/COMMITTER_NAME:(.*)/)[1],
|
35
|
+
committer_email: output.match(/COMMITTER_EMAIL:(.*)/)[1],
|
36
|
+
message: output.match(/COMMIT_MESSAGE:(.*)/m)[1],
|
37
|
+
}
|
38
|
+
end
|
39
|
+
|
40
|
+
def current_local_repo
|
41
|
+
origin_url = `git config --get remote.origin.url`
|
42
|
+
if origin_url == ''
|
43
|
+
raise Percy::Client::LocalGit::NoLocalRepo.new('No local git repository found.')
|
44
|
+
end
|
45
|
+
match = origin_url.match(Regexp.new('[:/]([^/]+\/[^/]+)\.git'))
|
46
|
+
match[1]
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'base64'
|
2
|
+
require 'digest'
|
3
|
+
|
4
|
+
module Percy
|
5
|
+
class Client
|
6
|
+
|
7
|
+
# A simple data container object used to pass data to create_snapshot.
|
8
|
+
class Resource
|
9
|
+
attr_accessor :sha
|
10
|
+
attr_accessor :resource_url
|
11
|
+
attr_accessor :is_root
|
12
|
+
attr_accessor :mimetype
|
13
|
+
|
14
|
+
def initialize(sha, resource_url, options = {})
|
15
|
+
@sha = sha
|
16
|
+
@resource_url = resource_url
|
17
|
+
@is_root = options[:is_root]
|
18
|
+
@mimetype = options[:mimetype]
|
19
|
+
end
|
20
|
+
|
21
|
+
def serialize
|
22
|
+
{
|
23
|
+
'type' => 'resources',
|
24
|
+
'id' => sha,
|
25
|
+
'resource-url' => resource_url,
|
26
|
+
'mimetype' => mimetype,
|
27
|
+
'is-root' => is_root,
|
28
|
+
}
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
module Resources
|
33
|
+
def upload_resource(build_id, content)
|
34
|
+
sha = Digest::SHA256.hexdigest(content)
|
35
|
+
data = {
|
36
|
+
'data' => {
|
37
|
+
'type' => 'resources',
|
38
|
+
'attributes' => {
|
39
|
+
'id' => sha,
|
40
|
+
'base64-content' => Base64.strict_encode64(content),
|
41
|
+
},
|
42
|
+
},
|
43
|
+
}
|
44
|
+
begin
|
45
|
+
post("#{full_base}/builds/#{build_id}/resources/", data)
|
46
|
+
rescue Percy::Client::ClientError => e
|
47
|
+
raise e if e.env.status != 409
|
48
|
+
STDERR.puts "[percy] Warning: unnecessary resource reuploaded with SHA-256: #{sha}"
|
49
|
+
end
|
50
|
+
true
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Percy
|
2
|
+
class Client
|
3
|
+
module Snapshots
|
4
|
+
def create_snapshot(build_id, resources, options = {})
|
5
|
+
raise ArgumentError.new('resources must be an iterable') if !resources.respond_to?(:each)
|
6
|
+
name = options[:name]
|
7
|
+
data = {
|
8
|
+
'data' => {
|
9
|
+
'type' => 'snapshots',
|
10
|
+
'attributes' => {
|
11
|
+
'name' => name,
|
12
|
+
},
|
13
|
+
'links' => {
|
14
|
+
'resources' => resources.map { |r| r.serialize },
|
15
|
+
},
|
16
|
+
},
|
17
|
+
}
|
18
|
+
post("#{full_base}/builds/#{build_id}/snapshots/", data)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|