circle-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 +4 -4
- data/LICENSE.txt +1 -1
- data/README.md +37 -12
- data/bin/circle +0 -5
- data/circle-cli.gemspec +8 -3
- data/lib/circle/cli.rb +5 -129
- data/lib/circle/cli/app.rb +192 -0
- data/lib/circle/cli/build.rb +83 -0
- data/lib/circle/cli/model.rb +43 -0
- data/lib/circle/cli/project.rb +52 -0
- data/lib/circle/cli/repo.rb +50 -0
- data/lib/circle/cli/step.rb +26 -0
- data/lib/circle/cli/version.rb +2 -2
- data/spec/cassettes/get.yml +71 -0
- data/spec/cassettes/recent_builds.yml +68 -0
- data/spec/cassettes/recent_builds_branch.yml +72 -0
- data/spec/cassettes/tests.yml +81 -0
- data/spec/circle/cli/build_spec.rb +105 -0
- data/spec/circle/cli/project_spec.rb +34 -0
- data/spec/circle/cli/repo_spec.rb +66 -0
- data/spec/circle/cli/step_spec.rb +41 -0
- data/spec/spec_helper.rb +12 -1
- metadata +97 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8ec32b3c7353d7ccd09c6f94ff30f62860a529b2
|
4
|
+
data.tar.gz: 9bd7e701530d2cc98374ed6af4d918a0d7edf85f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c2e59afcad27182a8db025642bccb4781ae31099983f6ff4c7db216383c3b902463c1a101eb88fcd7a787bd6ae679534af30d0736ec005354a898b5036c617ae
|
7
|
+
data.tar.gz: 8575aeb713849ecb4c4692c14935d3efc02801918911f4f7c5cb0e0de04e1213b091553e8298d6ed8c468109fac5472d4a92e2ab47f37d16194631a888ac5690
|
data/LICENSE.txt
CHANGED
data/README.md
CHANGED
@@ -1,7 +1,14 @@
|
|
1
1
|
# Circle CLI
|
2
|
-
|
3
2
|
Command line tools for Circle CI
|
4
3
|
|
4
|
+
[](https://badge.fury.io/rb/circle-cli)
|
5
|
+
[](https://circleci.com/gh/circle-cli/circle-cli)
|
6
|
+
[](https://gemnasium.com/codeurge/circle-cli)
|
7
|
+
[](https://codeclimate.com/github/codeurge/circle-cli)
|
8
|
+
[](https://codeclimate.com/github/codeurge/circle-cli/coverage)
|
9
|
+
|
10
|
+

|
11
|
+
|
5
12
|
## Installation
|
6
13
|
|
7
14
|
$ gem install circle-cli
|
@@ -14,36 +21,54 @@ Command line tools for Circle CI
|
|
14
21
|
$ gem install circle-cli
|
15
22
|
```
|
16
23
|
|
17
|
-
2. Add your
|
24
|
+
2. Add your CircleCI token
|
18
25
|
|
19
26
|
```
|
20
|
-
$ circle
|
27
|
+
$ circle token <your token>
|
21
28
|
```
|
22
29
|
|
23
|
-
|
30
|
+
## Usage
|
31
|
+
|
32
|
+
- Get the CI status for the HEAD of your current branch
|
24
33
|
|
25
34
|
```
|
26
|
-
$ circle
|
35
|
+
$ circle
|
27
36
|
```
|
28
|
-
|
29
|
-
## Usage
|
30
37
|
|
31
|
-
-
|
38
|
+
- Start a new CI build for the HEAD of your current branch
|
32
39
|
|
33
40
|
```
|
34
|
-
$ circle
|
41
|
+
$ circle build
|
42
|
+
```
|
43
|
+
|
44
|
+
- Watch the most recent CI build for your current branch
|
45
|
+
|
35
46
|
```
|
36
|
-
|
47
|
+
$ circle watch
|
48
|
+
```
|
49
|
+
|
50
|
+
- Cancel the most recent CI build for your current branch
|
51
|
+
|
52
|
+
```
|
53
|
+
$ circle cancel
|
54
|
+
```
|
55
|
+
|
37
56
|
- Open the results page for the latest CI run
|
38
57
|
|
39
58
|
```
|
40
59
|
$ circle open
|
41
60
|
```
|
42
61
|
|
62
|
+
- For additional help
|
63
|
+
|
64
|
+
```
|
65
|
+
$ circle help
|
66
|
+
```
|
67
|
+
|
43
68
|
## Contributing
|
44
69
|
|
45
|
-
1. Fork it ( http://github.com/
|
46
|
-
2. Create your feature branch (`git checkout -b my-new-feature`)
|
70
|
+
1. Fork it ( http://github.com/codeurge/circle-cli/fork )
|
71
|
+
2. Create your feature branch with your initials (`git checkout -b dsk/my-new-feature`)
|
47
72
|
3. Commit your changes (`git commit -am 'Add some feature'`)
|
48
73
|
4. Push to the branch (`git push origin my-new-feature`)
|
49
74
|
5. Create new Pull Request
|
data/bin/circle
CHANGED
data/circle-cli.gemspec
CHANGED
@@ -6,8 +6,8 @@ require 'circle/cli/version'
|
|
6
6
|
Gem::Specification.new do |spec|
|
7
7
|
spec.name = 'circle-cli'
|
8
8
|
spec.version = Circle::CLI::VERSION
|
9
|
-
spec.authors = ['Jean Boussier']
|
10
|
-
spec.email = ['jean.boussier@gmail.com']
|
9
|
+
spec.authors = ['Derek Keith', 'Jean Boussier']
|
10
|
+
spec.email = ['derek@codeurge.com', 'jean.boussier@gmail.com']
|
11
11
|
spec.summary = %q{Command line tools for Circle CI}
|
12
12
|
spec.description = %q{A bunch of commands useful to deal with Circle CI without leaving your terminal.}
|
13
13
|
spec.homepage = ''
|
@@ -20,9 +20,14 @@ Gem::Specification.new do |spec|
|
|
20
20
|
|
21
21
|
spec.add_dependency 'circleci'
|
22
22
|
spec.add_dependency 'rugged', '>= 0.23'
|
23
|
-
spec.add_dependency '
|
23
|
+
spec.add_dependency 'gitable'
|
24
|
+
spec.add_dependency 'launchy'
|
25
|
+
spec.add_dependency 'thor'
|
24
26
|
|
25
27
|
spec.add_development_dependency 'bundler', '~> 1.5'
|
26
28
|
spec.add_development_dependency 'rake'
|
27
29
|
spec.add_development_dependency 'rspec'
|
30
|
+
spec.add_development_dependency 'vcr'
|
31
|
+
spec.add_development_dependency 'webmock'
|
32
|
+
spec.add_development_dependency 'simplecov'
|
28
33
|
end
|
data/lib/circle/cli.rb
CHANGED
@@ -1,135 +1,11 @@
|
|
1
|
+
require 'thor'
|
1
2
|
require 'circle/cli/version'
|
2
|
-
require '
|
3
|
-
require 'rugged'
|
4
|
-
require 'octokit'
|
5
|
-
require 'circleci'
|
3
|
+
require 'circle/cli/app'
|
6
4
|
|
7
5
|
module Circle
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
def run(*args)
|
13
|
-
new.dispatch!(*args)
|
14
|
-
end
|
15
|
-
|
16
|
-
end
|
17
|
-
|
18
|
-
def initialize
|
19
|
-
@options = {}
|
20
|
-
@parser ||= OptionParser.new do |opts|
|
21
|
-
# future options will go here
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
def dispatch!(argv)
|
26
|
-
@parser.parse!
|
27
|
-
command, *args = argv
|
28
|
-
command = 'status' unless command
|
29
|
-
|
30
|
-
method = "run_#{command.gsub('-', '_')}"
|
31
|
-
if respond_to?(method)
|
32
|
-
send(method, *args)
|
33
|
-
else
|
34
|
-
abort!("Unknown command: #{command}")
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
def run_status
|
39
|
-
if last_status && last_status.state == 'success'
|
40
|
-
puts last_status.state
|
41
|
-
exit(0)
|
42
|
-
elsif last_status
|
43
|
-
puts last_status.state
|
44
|
-
else
|
45
|
-
puts 'unknown'
|
46
|
-
end
|
47
|
-
|
48
|
-
exit(1)
|
6
|
+
module CLI
|
7
|
+
def self.run(*args)
|
8
|
+
App.start(*args)
|
49
9
|
end
|
50
|
-
|
51
|
-
def run_open
|
52
|
-
unless last_status
|
53
|
-
puts 'No CI run found'
|
54
|
-
end
|
55
|
-
open(last_status.rels[:target].href)
|
56
|
-
end
|
57
|
-
|
58
|
-
def run_token(token=nil)
|
59
|
-
if token
|
60
|
-
repository.config['circleci.token'] = token
|
61
|
-
else
|
62
|
-
puts circle_token
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
def run_github_token(token=nil)
|
67
|
-
if token
|
68
|
-
repository.config['github.token'] = token
|
69
|
-
else
|
70
|
-
puts github_token
|
71
|
-
end
|
72
|
-
end
|
73
|
-
|
74
|
-
private
|
75
|
-
|
76
|
-
def open(url)
|
77
|
-
`open '#{url}'`
|
78
|
-
end
|
79
|
-
|
80
|
-
def last_status
|
81
|
-
unless defined?(@last_status)
|
82
|
-
@last_status = github_client.statuses(github_repo, head).first
|
83
|
-
end
|
84
|
-
@last_status
|
85
|
-
end
|
86
|
-
|
87
|
-
def github_repo
|
88
|
-
if origin.url =~ %r{git@github.com:(\w+/[^.]*)\.git}
|
89
|
-
$1
|
90
|
-
else
|
91
|
-
raise "Unsupported repo url format #{origin.url.inspect}" # TODO: support other formats, mainly https
|
92
|
-
end
|
93
|
-
end
|
94
|
-
|
95
|
-
def origin
|
96
|
-
@origin ||= repository.remotes.find { |r| r.name == 'origin' }
|
97
|
-
end
|
98
|
-
|
99
|
-
def head
|
100
|
-
repository.head.target_id
|
101
|
-
end
|
102
|
-
|
103
|
-
def repository
|
104
|
-
@repository ||= Rugged::Repository.new('.') # TODO: allow to be called from a subdirectory
|
105
|
-
end
|
106
|
-
|
107
|
-
def github_client
|
108
|
-
@github_client ||= Octokit::Client.new(access_token: github_token)
|
109
|
-
end
|
110
|
-
|
111
|
-
def configure_circle_ci_client
|
112
|
-
CircleCi.configure do |config|
|
113
|
-
config.token = circle_token
|
114
|
-
end
|
115
|
-
end
|
116
|
-
|
117
|
-
def github_token # TODO: github-token should probably be in a global config
|
118
|
-
repository.config['github.token'] || abort!(%{Missing GitHub token.
|
119
|
-
You can create one here: https://github.com/settings/tokens/new
|
120
|
-
And add it with the following command: $ circle github-token YOUR_TOKEN})
|
121
|
-
end
|
122
|
-
|
123
|
-
def circle_token
|
124
|
-
repository.config['circleci.token'] || abort!(%{Missing CircleCI token.
|
125
|
-
You can create one here: https://circleci.com/account/api
|
126
|
-
And add it with the following command: $ circle token YOUR_TOKEN})
|
127
|
-
end
|
128
|
-
|
129
|
-
def abort!(message, code=1)
|
130
|
-
STDERR.puts(message)
|
131
|
-
exit(code)
|
132
|
-
end
|
133
|
-
|
134
10
|
end
|
135
11
|
end
|
@@ -0,0 +1,192 @@
|
|
1
|
+
require 'launchy'
|
2
|
+
require 'circle/cli/repo'
|
3
|
+
require 'circle/cli/project'
|
4
|
+
|
5
|
+
module Circle
|
6
|
+
module CLI
|
7
|
+
class App < Thor
|
8
|
+
CIRCLE_URL = 'https://circleci.com/account/api'
|
9
|
+
|
10
|
+
LOGIN_HELP = <<-EOMSG
|
11
|
+
1. Press [enter], and you'll be taken CircleCI.
|
12
|
+
2. Enter a name for your new token.
|
13
|
+
3. Click 'Create new token'.
|
14
|
+
4. Come back to your prompt and paste in your new token.
|
15
|
+
5. Press enter to complete the process.
|
16
|
+
EOMSG
|
17
|
+
|
18
|
+
NO_TOKEN_MESSAGE = <<-EOMSG
|
19
|
+
CircleCI token hasn't been configured. Run the following command to login:
|
20
|
+
|
21
|
+
$ circle login
|
22
|
+
EOMSG
|
23
|
+
|
24
|
+
default_task :status
|
25
|
+
class_option :repo, default: '.', desc: 'path to repo'
|
26
|
+
|
27
|
+
desc 'status', 'show CircleCI build result'
|
28
|
+
method_option :branch, desc: 'branch name'
|
29
|
+
def status
|
30
|
+
validate_repo!
|
31
|
+
validate_latest!
|
32
|
+
display_status
|
33
|
+
end
|
34
|
+
|
35
|
+
desc 'watch', 'watch your build'
|
36
|
+
method_option :branch, desc: 'branch name'
|
37
|
+
method_option :poll, default: 5, desc: 'polling frequency', type: :numeric
|
38
|
+
def watch
|
39
|
+
validate_repo!
|
40
|
+
validate_latest!
|
41
|
+
watching -> { project.latest.preload } do
|
42
|
+
display_status
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
desc 'overview', 'list recent builds and their statuses for all branches'
|
47
|
+
method_option :watch, desc: 'watch the list of builds'
|
48
|
+
method_option :poll, default: 5, desc: 'polling frequency', type: :numeric
|
49
|
+
def overview
|
50
|
+
validate_repo!
|
51
|
+
abort! 'No recent builds.' if project.recent_builds.empty?
|
52
|
+
show_overview = -> { display_builds(project.recent_builds) }
|
53
|
+
|
54
|
+
if options[:watch]
|
55
|
+
watching(-> { project.recent_builds }, &show_overview)
|
56
|
+
else
|
57
|
+
show_overview
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
desc 'open', 'open CircleCI build'
|
62
|
+
method_option :branch, desc: 'branch name'
|
63
|
+
def open
|
64
|
+
validate_repo!
|
65
|
+
validate_latest!
|
66
|
+
Launchy.open latest[:build_url]
|
67
|
+
end
|
68
|
+
|
69
|
+
desc 'build', 'trigger a build on circle ci'
|
70
|
+
method_option :branch, desc: 'branch name'
|
71
|
+
def build
|
72
|
+
validate_repo!
|
73
|
+
project.build!
|
74
|
+
say "A build has been triggered.\n\n", :green
|
75
|
+
invoke :watch
|
76
|
+
end
|
77
|
+
|
78
|
+
desc 'cancel', 'cancel most recent build'
|
79
|
+
method_option :branch, desc: 'branch name'
|
80
|
+
def cancel
|
81
|
+
validate_repo!
|
82
|
+
validate_latest!
|
83
|
+
latest.cancel! unless latest.finished?
|
84
|
+
invoke :status
|
85
|
+
say "\nThe build has been cancelled.", :red unless latest.finished?
|
86
|
+
end
|
87
|
+
|
88
|
+
desc 'token', 'view or edit CircleCI token'
|
89
|
+
def token(value = nil)
|
90
|
+
if value
|
91
|
+
repo.circle_token = value
|
92
|
+
elsif value = repo.circle_token
|
93
|
+
say value
|
94
|
+
else
|
95
|
+
say NO_TOKEN_MESSAGE, :yellow
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
desc 'login', 'login to Circle CI'
|
100
|
+
def login
|
101
|
+
say LOGIN_HELP, :yellow
|
102
|
+
ask set_color("\nPress [enter] to open CircleCI", :blue)
|
103
|
+
Launchy.open(CIRCLE_URL)
|
104
|
+
value = ask set_color('Enter your token:', :blue)
|
105
|
+
repo.circle_token = value
|
106
|
+
say "\nYour token has been set to '#{value}'.", :green
|
107
|
+
end
|
108
|
+
|
109
|
+
private
|
110
|
+
|
111
|
+
def repo
|
112
|
+
@repo ||= Repo.new(options)
|
113
|
+
end
|
114
|
+
|
115
|
+
def project
|
116
|
+
@project ||= Project.new(repo)
|
117
|
+
end
|
118
|
+
|
119
|
+
def latest
|
120
|
+
project.latest
|
121
|
+
end
|
122
|
+
|
123
|
+
def validate_repo!
|
124
|
+
abort! "Unsupported repo url format #{repo.uri}" unless repo.uri.github?
|
125
|
+
abort! NO_TOKEN_MESSAGE unless repo.circle_token
|
126
|
+
end
|
127
|
+
|
128
|
+
def validate_latest!
|
129
|
+
abort! 'No CircleCI builds found.' unless project.latest
|
130
|
+
end
|
131
|
+
|
132
|
+
def abort!(message)
|
133
|
+
abort set_color(message, :red)
|
134
|
+
end
|
135
|
+
|
136
|
+
def watching(preloader)
|
137
|
+
loop do
|
138
|
+
yield
|
139
|
+
sleep options[:poll]
|
140
|
+
project.clear_cache!
|
141
|
+
preloader.call
|
142
|
+
system('clear') || system('cls')
|
143
|
+
end
|
144
|
+
rescue Interrupt
|
145
|
+
exit 0
|
146
|
+
end
|
147
|
+
|
148
|
+
def display(description, value, color)
|
149
|
+
status = set_color description.ljust(15), :bold
|
150
|
+
result = set_color value.to_s, color
|
151
|
+
say "#{status} #{result}"
|
152
|
+
end
|
153
|
+
|
154
|
+
def display_status
|
155
|
+
say "#{latest[:subject]}\n\n", :cyan if latest[:subject]
|
156
|
+
display 'Build status', latest.status, latest.color
|
157
|
+
display 'Started at', latest.formatted_start_time, latest.color
|
158
|
+
display 'Finished at', latest.formatted_stop_time, latest.color
|
159
|
+
display 'Compare', latest[:compare], latest.color if latest[:compare]
|
160
|
+
display_steps latest.steps unless latest.steps.empty?
|
161
|
+
display_failures latest.failing_tests unless latest.failing_tests.empty?
|
162
|
+
exit 1 if latest.failed?
|
163
|
+
exit 0 if latest.finished?
|
164
|
+
end
|
165
|
+
|
166
|
+
def display_steps(steps)
|
167
|
+
say "\nSteps:", :bold
|
168
|
+
|
169
|
+
print_table steps.map { |step|
|
170
|
+
[set_color(step[:name], step.color), step.duration]
|
171
|
+
}
|
172
|
+
end
|
173
|
+
|
174
|
+
def display_failures(failures)
|
175
|
+
say "\nFailing specs:", :bold
|
176
|
+
|
177
|
+
print_table failures.map { |spec|
|
178
|
+
[set_color(spec['file'], :red), spec['name']]
|
179
|
+
}
|
180
|
+
end
|
181
|
+
|
182
|
+
def display_builds(builds)
|
183
|
+
print_table builds.map { |build|
|
184
|
+
branch = set_color(build[:branch], :bold)
|
185
|
+
status = set_color(build.status, build.color)
|
186
|
+
started = build.formatted_start_time
|
187
|
+
[branch, status, build.subject, started]
|
188
|
+
}
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|