gemfury 0.12.0.rc1
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 +7 -0
- data/README.md +87 -0
- data/bin/fury +10 -0
- data/bin/gemfury +2 -0
- data/lib/faraday/adapter/fury_http.rb +32 -0
- data/lib/faraday/request/multipart_with_file.rb +34 -0
- data/lib/gemfury.rb +82 -0
- data/lib/gemfury/client.rb +273 -0
- data/lib/gemfury/client/filters.rb +18 -0
- data/lib/gemfury/client/middleware.rb +23 -0
- data/lib/gemfury/command.rb +13 -0
- data/lib/gemfury/command/app.rb +425 -0
- data/lib/gemfury/command/authorization.rb +79 -0
- data/lib/gemfury/configuration.rb +48 -0
- data/lib/gemfury/const.rb +26 -0
- data/lib/gemfury/error.rb +28 -0
- data/lib/gemfury/platform.rb +19 -0
- data/lib/gemfury/tasks.rb +2 -0
- data/lib/gemfury/tasks/release.rake +36 -0
- data/lib/gemfury/version.rb +3 -0
- data/lib/rubygems/commands/fury_command.rb +29 -0
- data/lib/rubygems_plugin.rb +2 -0
- metadata +197 -0
@@ -0,0 +1,18 @@
|
|
1
|
+
module Gemfury
|
2
|
+
class Client
|
3
|
+
module Filters
|
4
|
+
|
5
|
+
private
|
6
|
+
def ensure_ready!(*args)
|
7
|
+
# Ensure authorization
|
8
|
+
if args.include?(:authorization)
|
9
|
+
raise Unauthorized unless authenticated?
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def authenticated?
|
14
|
+
self.user_api_key && !self.user_api_key.empty?
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Gemfury
|
2
|
+
class Client
|
3
|
+
class Handle503 < Faraday::Middleware
|
4
|
+
def call(env)
|
5
|
+
# This prevents errors in ParseJson
|
6
|
+
@app.call(env).on_complete do |out|
|
7
|
+
out[:body] = '' if out[:status] == 503
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class ParseJson < Faraday::Response::Middleware
|
13
|
+
def parse(body)
|
14
|
+
body =~ /\A\s*\z/ ? nil : MultiJson.decode(body)
|
15
|
+
end
|
16
|
+
|
17
|
+
def on_complete(response)
|
18
|
+
ok = response.request_headers['Accept'] =~ /json\z/
|
19
|
+
response.body = parse(response.body) if ok
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
gem "progressbar", ">= 1.10.1", "< 2.0.0.pre"
|
2
|
+
gem "highline", ">= 1.6.0", "< 2.1.0.pre"
|
3
|
+
gem "thor", ">= 0.14.0", "< 1.1.0.pre"
|
4
|
+
|
5
|
+
require 'thor'
|
6
|
+
require 'yaml'
|
7
|
+
require 'highline'
|
8
|
+
require 'fileutils'
|
9
|
+
|
10
|
+
module Gemfury::Command; end
|
11
|
+
|
12
|
+
require 'gemfury/command/authorization'
|
13
|
+
require 'gemfury/command/app'
|
@@ -0,0 +1,425 @@
|
|
1
|
+
require 'progressbar'
|
2
|
+
require 'delegate'
|
3
|
+
|
4
|
+
class Gemfury::Command::App < Thor
|
5
|
+
include Gemfury::Command::Authorization
|
6
|
+
UserAgent = "Gemfury CLI #{Gemfury::VERSION}".freeze
|
7
|
+
PackageExtensions = %w(gem egg tar.gz tgz nupkg)
|
8
|
+
|
9
|
+
# Impersonation
|
10
|
+
class_option :as, :desc => 'Access an account other than your own'
|
11
|
+
class_option :api_token, :desc => 'API token to use for commands'
|
12
|
+
|
13
|
+
map "-v" => :version
|
14
|
+
desc "version", "Show Gemfury version", :hide => true
|
15
|
+
def version
|
16
|
+
shell.say Gemfury::VERSION
|
17
|
+
end
|
18
|
+
|
19
|
+
### PACKAGE MANAGEMENT ###
|
20
|
+
option :public, :type => :boolean, :desc => "Create as public package"
|
21
|
+
option :quiet, :type => :boolean, :aliases => "-q", :desc => "Do not show progress bar", :default => false
|
22
|
+
desc "push FILE", "Upload a new version of a package"
|
23
|
+
def push(*gems)
|
24
|
+
with_checks_and_rescues do
|
25
|
+
push_files(:push, gems)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
desc "list", "List your packages"
|
30
|
+
def list
|
31
|
+
with_checks_and_rescues do
|
32
|
+
gems = client.list
|
33
|
+
shell.say "\n*** GEMFURY PACKAGES ***\n\n"
|
34
|
+
|
35
|
+
va = [ %w{ name kind version privacy } ]
|
36
|
+
gems.each do |g|
|
37
|
+
va << [ g['name'], g['language'],
|
38
|
+
g.dig('latest_version', 'version') || 'beta',
|
39
|
+
g['private'] ? 'private' : 'public ' ]
|
40
|
+
end
|
41
|
+
|
42
|
+
shell.print_table(va)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
desc "versions NAME", "List all the package versions"
|
47
|
+
def versions(gem_name)
|
48
|
+
with_checks_and_rescues do
|
49
|
+
versions = client.versions(gem_name)
|
50
|
+
shell.say "\n*** #{gem_name.capitalize} Versions ***\n\n"
|
51
|
+
|
52
|
+
va = []
|
53
|
+
va = [ %w{ version uploaded_by uploaded } ]
|
54
|
+
versions.each do |v|
|
55
|
+
uploaded = time_ago(Time.parse(v['created_at']).getlocal)
|
56
|
+
va << [ v['version'], v['created_by']['name'], uploaded ]
|
57
|
+
end
|
58
|
+
|
59
|
+
shell.print_table(va)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
desc "yank NAME", "Delete a package version"
|
64
|
+
method_options %w(version -v) => :required
|
65
|
+
def yank(gem_name)
|
66
|
+
with_checks_and_rescues do
|
67
|
+
version = options[:version]
|
68
|
+
client.yank_version(gem_name, version)
|
69
|
+
shell.say "\n*** Yanked #{gem_name}-#{version} ***\n\n"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
### AUTHENTICATION ###
|
74
|
+
desc "logout", "Remove Gemfury credentials"
|
75
|
+
def logout
|
76
|
+
if !has_credentials?
|
77
|
+
shell.say "You are logged out"
|
78
|
+
elsif shell.yes? "Are you sure you want to log out? [yN]"
|
79
|
+
with_checks_and_rescues { client.logout }
|
80
|
+
wipe_credentials!
|
81
|
+
shell.say "You have been logged out"
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
desc "login", "Save Gemfury credentials"
|
86
|
+
def login
|
87
|
+
with_checks_and_rescues do
|
88
|
+
me = client.account_info['name']
|
89
|
+
shell.say %Q(You are logged in as "#{me}"), :green
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
desc "whoami", "Show current user"
|
94
|
+
def whoami
|
95
|
+
has_credentials? ? self.login : begin
|
96
|
+
shell.say %Q(You are not logged in), :green
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
desc "accounts", "Show info about your Gemfury accounts"
|
101
|
+
def accounts
|
102
|
+
with_checks_and_rescues do
|
103
|
+
accounts = client.accounts
|
104
|
+
|
105
|
+
va = [ %w{ name kind permission } ]
|
106
|
+
accounts.each do |a|
|
107
|
+
va << [ a['name'], a['type'], a['viewer_permission'].downcase ]
|
108
|
+
end
|
109
|
+
|
110
|
+
shell.print_table(va)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
### COLLABORATION MANAGEMENT ###
|
115
|
+
map "sharing:add" => 'sharing_add'
|
116
|
+
map "sharing:remove" => 'sharing_remove'
|
117
|
+
|
118
|
+
desc "sharing", "List collaborators"
|
119
|
+
def sharing
|
120
|
+
with_checks_and_rescues do
|
121
|
+
account_info = client.account_info
|
122
|
+
me = account_info['username']
|
123
|
+
|
124
|
+
collaborators = client.list_collaborators
|
125
|
+
if collaborators.empty?
|
126
|
+
shell.say %Q(You (#{me}) are the only collaborator\n), :green
|
127
|
+
else
|
128
|
+
shell.say %Q(\n*** Collaborators for "#{me}" ***\n), :green
|
129
|
+
|
130
|
+
va = [ %w{ username permission } ]
|
131
|
+
|
132
|
+
if account_info['type'] == 'user'
|
133
|
+
va << [ me, 'owner' ]
|
134
|
+
end
|
135
|
+
|
136
|
+
collaborators.each { |c| va << [ c['username'], c['permission'] ] }
|
137
|
+
|
138
|
+
shell.print_table(va)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
desc "sharing:add EMAIL", "Add a collaborator"
|
144
|
+
def sharing_add(username)
|
145
|
+
with_checks_and_rescues do
|
146
|
+
client.add_collaborator(username)
|
147
|
+
shell.say "Invited #{username} as a collaborator"
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
desc "sharing:remove EMAIL", "Remove a collaborator"
|
152
|
+
def sharing_remove(username)
|
153
|
+
with_checks_and_rescues do
|
154
|
+
client.remove_collaborator(username)
|
155
|
+
shell.say "Removed #{username} as a collaborator"
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
### MIGRATION (Pushing directories) ###
|
160
|
+
desc "migrate DIR", "Upload all packages within a directory"
|
161
|
+
def migrate(*paths)
|
162
|
+
with_checks_and_rescues do
|
163
|
+
gem_paths = Dir.glob(paths.map do |p|
|
164
|
+
if File.directory?(p)
|
165
|
+
PackageExtensions.map { |ext| "#{p}/**/*.#{ext}" }
|
166
|
+
elsif File.file?(p)
|
167
|
+
p
|
168
|
+
else
|
169
|
+
nil
|
170
|
+
end
|
171
|
+
end.flatten.compact)
|
172
|
+
|
173
|
+
if gem_paths.empty?
|
174
|
+
die!("Problem: No valid packages found", nil, :migrate)
|
175
|
+
else
|
176
|
+
shell.say "Found the following packages:"
|
177
|
+
gem_paths.each { |p| shell.say " #{File.basename(p)}" }
|
178
|
+
if shell.yes? "Upload these files to Gemfury? [yN]", :green
|
179
|
+
push_files(:migrate, gem_paths)
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
### GIT REPOSITORY MANAGEMENT ###
|
186
|
+
map "git:list" => 'git_list'
|
187
|
+
map "git:reset" => 'git_reset'
|
188
|
+
map "git:rename" => 'git_rename'
|
189
|
+
map "git:rebuild" => 'git_rebuild'
|
190
|
+
|
191
|
+
desc "git:list", "List Git repositories"
|
192
|
+
def git_list
|
193
|
+
with_checks_and_rescues do
|
194
|
+
repos = client.git_repos['repos']
|
195
|
+
shell.say "\n*** GEMFURY GIT REPOS ***\n\n"
|
196
|
+
names = repos.map { |r| r['name'] }
|
197
|
+
names.sort.each { |n| shell.say(n) }
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
desc "git:rename", "Rename a Git repository"
|
202
|
+
def git_rename(repo, new_name)
|
203
|
+
with_checks_and_rescues do
|
204
|
+
client.git_update(repo, :repo => { :name => new_name })
|
205
|
+
shell.say "Renamed #{repo} repository to #{new_name}\n"
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
desc "git:reset", "Remove a Git repository"
|
210
|
+
def git_reset(repo)
|
211
|
+
with_checks_and_rescues do
|
212
|
+
client.git_reset(repo)
|
213
|
+
shell.say "\n*** Yanked #{repo} repository ***\n\n"
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
desc "git:rebuild", "Rebuild a Git repository"
|
218
|
+
method_options %w(revision -r) => :string
|
219
|
+
def git_rebuild(repo)
|
220
|
+
with_checks_and_rescues do
|
221
|
+
params = { :revision => options[:revision] }
|
222
|
+
shell.say "\n*** Rebuilding #{repo} repository ***\n\n"
|
223
|
+
shell.say client.git_rebuild(repo, :build => params)
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
### GIT REPOSITORY BUILD CONFIG ###
|
228
|
+
map 'git:config' => 'git_config'
|
229
|
+
map 'git:config:set' => 'git_config_set'
|
230
|
+
map 'git:config:unset' => 'git_config_unset'
|
231
|
+
|
232
|
+
desc "git:config", "List Git repository's build environment"
|
233
|
+
def git_config(repo)
|
234
|
+
with_checks_and_rescues do
|
235
|
+
vars = client.git_config(repo)['config_vars']
|
236
|
+
shell.say "*** #{repo} build config ***\n"
|
237
|
+
shell.print_table(vars.map { |kv|
|
238
|
+
["#{kv[0]}:", kv[1]]
|
239
|
+
})
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
desc "git:config:set", "Update Git repository's build environment"
|
244
|
+
def git_config_set(repo, *vars)
|
245
|
+
with_checks_and_rescues do
|
246
|
+
updates = Hash[vars.map { |v| v.split("=", 2) }]
|
247
|
+
client.git_config_update(repo, updates)
|
248
|
+
shell.say "Updated #{repo} build config"
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
desc "git:config:unset", "Remove variables from Git repository's build environment"
|
253
|
+
def git_config_unset(repo, *vars)
|
254
|
+
with_checks_and_rescues do
|
255
|
+
updates = Hash[vars.map { |v| [v, nil] }]
|
256
|
+
client.git_config_update(repo, updates)
|
257
|
+
shell.say "Updated #{repo} build config"
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
private
|
262
|
+
def client
|
263
|
+
opts = {}
|
264
|
+
opts[:user_api_key] = @user_api_key if @user_api_key
|
265
|
+
opts[:account] = options[:as] if options[:as]
|
266
|
+
client = Gemfury::Client.new(opts)
|
267
|
+
client.user_agent = UserAgent
|
268
|
+
return client
|
269
|
+
end
|
270
|
+
|
271
|
+
def with_checks_and_rescues(&block)
|
272
|
+
@user_api_key = options[:api_token] if options[:api_token]
|
273
|
+
with_authorization(&block)
|
274
|
+
rescue Gemfury::InvalidGemVersion => e
|
275
|
+
shell.say "You have a deprecated Gemfury client", :red
|
276
|
+
if shell.yes? "Would you like to update it now? [yN]"
|
277
|
+
exec("gem update gemfury")
|
278
|
+
else
|
279
|
+
shell.say %q(No problem. You can also run "gem update gemfury")
|
280
|
+
end
|
281
|
+
rescue Gemfury::Conflict => e
|
282
|
+
die!("Oops! Locked for another user. Try again later.", e)
|
283
|
+
rescue Gemfury::Forbidden => e
|
284
|
+
die!("Oops! You're not allowed to access this", e)
|
285
|
+
rescue Gemfury::NotFound => e
|
286
|
+
die!("Oops! Doesn't look like this exists", e)
|
287
|
+
rescue Gemfury::Error => e
|
288
|
+
die!("Oops! %s" % e.message, e)
|
289
|
+
rescue StandardError => e
|
290
|
+
die!("Oops! Something went wrong. Please contact support.", e)
|
291
|
+
end
|
292
|
+
|
293
|
+
def push_files(command, gem_paths)
|
294
|
+
files = gem_paths.map do |g|
|
295
|
+
g.is_a?(String) ? File.new(g) : g rescue nil
|
296
|
+
end.compact
|
297
|
+
|
298
|
+
if !options[:quiet] && !shell.mute? && $stdout.tty?
|
299
|
+
files = files.map { |g| ProgressIO.new(g) }
|
300
|
+
end
|
301
|
+
|
302
|
+
if files.empty?
|
303
|
+
die!("Problem: No valid packages found", nil, command)
|
304
|
+
end
|
305
|
+
|
306
|
+
push_options = { }
|
307
|
+
unless options[:public].nil?
|
308
|
+
push_options[:public] = options[:public]
|
309
|
+
end
|
310
|
+
|
311
|
+
error_ex = nil
|
312
|
+
|
313
|
+
files.each do |file|
|
314
|
+
show_bar = file.is_a?(ProgressIO) && file.show_bar?
|
315
|
+
title = "Uploading #{File.basename(file.path)} "
|
316
|
+
|
317
|
+
begin
|
318
|
+
if show_bar
|
319
|
+
begin
|
320
|
+
client.push_gem(file, push_options)
|
321
|
+
ensure
|
322
|
+
shell.say "\e[A\e[0K", nil, false
|
323
|
+
shell.say title
|
324
|
+
end
|
325
|
+
else
|
326
|
+
shell.say title
|
327
|
+
client.push_gem(file, push_options)
|
328
|
+
end
|
329
|
+
|
330
|
+
shell.say "- done"
|
331
|
+
rescue Gemfury::CorruptGemFile => e
|
332
|
+
shell.say "- problem processing this package", :red
|
333
|
+
error_ex = e
|
334
|
+
rescue Gemfury::DupeVersion => e
|
335
|
+
shell.say "- this version already exists", :red
|
336
|
+
error_ex = e
|
337
|
+
rescue Gemfury::TimeoutError, Errno::EPIPE => e
|
338
|
+
shell.say "- this file is too much to handle", :red
|
339
|
+
shell.say " Visit http://www.gemfury.com/large-package for more info"
|
340
|
+
error_ex = e
|
341
|
+
rescue => e
|
342
|
+
shell.say "- oops", :red
|
343
|
+
error_ex = e
|
344
|
+
end
|
345
|
+
end
|
346
|
+
|
347
|
+
unless error_ex.nil?
|
348
|
+
die!('There was a problem uploading at least 1 package', error_ex)
|
349
|
+
end
|
350
|
+
end
|
351
|
+
|
352
|
+
C50K = 50000
|
353
|
+
|
354
|
+
class ProgressIO < SimpleDelegator
|
355
|
+
attr_reader :content_type, :original_filename, :local_path
|
356
|
+
|
357
|
+
def initialize(filename_or_io, content_type = 'application/octet-stream', fname = nil)
|
358
|
+
io = filename_or_io
|
359
|
+
local_path = ''
|
360
|
+
|
361
|
+
if io.respond_to? :read
|
362
|
+
local_path = filename_or_io.respond_to?(:path) ? filename_or_io.path : 'local.path'
|
363
|
+
else
|
364
|
+
io = File.open(filename_or_io)
|
365
|
+
local_path = filename_or_io
|
366
|
+
end
|
367
|
+
|
368
|
+
fname ||= local_path
|
369
|
+
|
370
|
+
@content_type = content_type
|
371
|
+
@original_filename = File.basename(fname)
|
372
|
+
@local_path = local_path
|
373
|
+
|
374
|
+
if io.respond_to? :size
|
375
|
+
filesize = io.size
|
376
|
+
else
|
377
|
+
filesize = io.stat.size
|
378
|
+
end
|
379
|
+
|
380
|
+
if filesize > C50K
|
381
|
+
title = 'Uploading %s ' % File.basename(fname)
|
382
|
+
@bar = ProgressBar.create(:title => title, :total => filesize)
|
383
|
+
else
|
384
|
+
@bar = nil
|
385
|
+
end
|
386
|
+
|
387
|
+
super(io)
|
388
|
+
end
|
389
|
+
|
390
|
+
def show_bar?
|
391
|
+
@bar != nil
|
392
|
+
end
|
393
|
+
|
394
|
+
def read(length)
|
395
|
+
buf = __getobj__.read(length)
|
396
|
+
unless @bar.nil? || buf.nil?
|
397
|
+
@bar.progress += buf.bytesize
|
398
|
+
end
|
399
|
+
|
400
|
+
buf
|
401
|
+
end
|
402
|
+
end
|
403
|
+
|
404
|
+
def die!(msg, err = nil, command = nil)
|
405
|
+
shell.say msg, :red
|
406
|
+
help(command) if command
|
407
|
+
shell.say %Q(#{err.class.name}: #{err}\n#{err.backtrace.join("\n")}) if err && ENV['DEBUG']
|
408
|
+
exit(1)
|
409
|
+
end
|
410
|
+
|
411
|
+
def time_ago(tm)
|
412
|
+
ago = tm.strftime('%F %R')
|
413
|
+
|
414
|
+
in_secs = Time.now - tm
|
415
|
+
if in_secs < 60
|
416
|
+
ago += ' (~ %ds ago)' % in_secs
|
417
|
+
elsif in_secs < 3600
|
418
|
+
ago += ' (~ %sm ago)' % (in_secs / 60).floor
|
419
|
+
elsif in_secs < (3600 * 24)
|
420
|
+
ago += ' (~ %sh ago)' % (in_secs / 3600).floor
|
421
|
+
end
|
422
|
+
|
423
|
+
ago
|
424
|
+
end
|
425
|
+
end
|