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.
@@ -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