percy-client 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Build Status](https://travis-ci.org/percy/percy-client.svg?branch=master)](https://travis-ci.org/percy/percy-client)
|
4
|
+
[![Gem Version](https://badge.fury.io/rb/percy-client.svg)](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
|