rake-gem-maintenance 0.1.6 → 0.2.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/CLAUDE.md +7 -7
- data/Gemfile.lock +2 -2
- data/README.md +47 -53
- data/TODO.md +4 -1
- data/lib/rake/gem/maintenance/api_key_renewer.rb +37 -35
- data/lib/rake/gem/maintenance/ci_environment.rb +7 -5
- data/lib/rake/gem/maintenance/credential_store.rb +46 -44
- data/lib/rake/gem/maintenance/gem_publisher.rb +86 -84
- data/lib/rake/gem/maintenance/gem_push.rb +44 -42
- data/lib/rake/gem/maintenance/install_tasks.rb +5 -5
- data/lib/rake/gem/maintenance/otp_provider.rb +33 -31
- data/lib/rake/gem/maintenance/renew_api_key_task.rb +111 -109
- data/lib/rake/gem/maintenance/repos.rb +81 -79
- data/lib/rake/gem/maintenance/ruby_gems_api_key_creator.rb +39 -37
- data/lib/rake/gem/maintenance/upgrade_task.rb +245 -242
- data/lib/rake/gem/maintenance/version.rb +4 -2
- data/lib/rake/gem/maintenance/version_bump_task.rb +83 -81
- data/lib/rake/gem/maintenance/woodpecker_secret_store.rb +69 -67
- data/scripts/ci_publish_rubygems.rb +3 -3
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1025716123e134ab6db2ffcfc5655a59208dd038691b73e270c92c6bd35ea977
|
|
4
|
+
data.tar.gz: 1445566f68985cdd2b915c9958ec9daddaf07caa5002c62277481b72a122d561
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ba35f363319b323fe927da2cc3a9e8934d7b4310f5995b31d1cc91a759b34c8462262c449ef59fa1debe3c35615c733ef8f0dc96e6f270b592a76f1f6cc76153
|
|
7
|
+
data.tar.gz: 892b319be15819eb07cf5f254e9e7144e8387393a586442e9831585602600ade156e6d6c7c0b1dc41b6e60ee9e58b411c10a1bcebbff5e08f8510ec56f32ffad
|
data/CLAUDE.md
CHANGED
|
@@ -22,7 +22,7 @@ rake rubocop
|
|
|
22
22
|
|
|
23
23
|
## Architecture
|
|
24
24
|
|
|
25
|
-
### Core Classes (lib/rake/
|
|
25
|
+
### Core Classes (lib/rake/gem/maintenance/)
|
|
26
26
|
|
|
27
27
|
- **UpgradeTask** — Rake::TaskLib subclass defining upgrade:* tasks (branch, gems, commit, push, auto)
|
|
28
28
|
- Default repositories: rubygems.org
|
|
@@ -36,21 +36,21 @@ rake rubocop
|
|
|
36
36
|
|
|
37
37
|
### Entry Points
|
|
38
38
|
|
|
39
|
-
- `rake/
|
|
40
|
-
- `rake/
|
|
39
|
+
- `rake/gem/maintenance.rb` — requires all task classes
|
|
40
|
+
- `rake/gem/maintenance/install_tasks.rb` — auto-instantiates UpgradeTask and VersionBumpTask with defaults
|
|
41
41
|
|
|
42
42
|
### Quick Usage
|
|
43
43
|
|
|
44
44
|
```ruby
|
|
45
45
|
# Internal gems only (cbp-org.internal)
|
|
46
|
-
Rake::
|
|
46
|
+
Rake::Gem::Maintenance::InternalUpgradeTask.new
|
|
47
47
|
|
|
48
48
|
# Both repositories
|
|
49
|
-
Rake::
|
|
49
|
+
Rake::Gem::Maintenance::DualUpgradeTask.new
|
|
50
50
|
|
|
51
51
|
# Default (rubygems.org) - can also use Repos module
|
|
52
|
-
Rake::
|
|
53
|
-
t.gem_repositories = Rake::
|
|
52
|
+
Rake::Gem::Maintenance::UpgradeTask.new do |t|
|
|
53
|
+
t.gem_repositories = Rake::Gem::Maintenance::Repos.internal # or Repos.all
|
|
54
54
|
end
|
|
55
55
|
```
|
|
56
56
|
|
data/Gemfile.lock
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
rake-gem-maintenance (0.
|
|
4
|
+
rake-gem-maintenance (0.2.0)
|
|
5
5
|
bundler-audit
|
|
6
6
|
gem-release
|
|
7
7
|
rake
|
|
@@ -254,7 +254,7 @@ CHECKSUMS
|
|
|
254
254
|
racc (1.8.1) sha256=4a7f6929691dbec8b5209a0b373bc2614882b55fc5d2e447a21aaa691303d62f
|
|
255
255
|
rainbow (3.1.1) sha256=039491aa3a89f42efa1d6dec2fc4e62ede96eb6acd95e52f1ad581182b79bc6a
|
|
256
256
|
rake (13.4.2) sha256=cb825b2bd5f1f8e91ca37bddb4b9aaf345551b4731da62949be002fa89283701
|
|
257
|
-
rake-gem-maintenance (0.
|
|
257
|
+
rake-gem-maintenance (0.2.0)
|
|
258
258
|
rb-fsevent (0.11.2) sha256=43900b972e7301d6570f64b850a5aa67833ee7d87b458ee92805d56b7318aefe
|
|
259
259
|
rb-inotify (0.11.1) sha256=a0a700441239b0ff18eb65e3866236cd78613d6b9f78fea1f9ac47a85e47be6e
|
|
260
260
|
rdoc (7.2.0) sha256=8650f76cd4009c3b54955eb5d7e3a075c60a57276766ebf36f9085e8c9f23192
|
data/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Rake::
|
|
1
|
+
# Rake::Gem::Maintenance
|
|
2
2
|
|
|
3
3
|
[](https://github.com/cbroult/rake-gem-maintenance/actions/workflows/main.yml)
|
|
4
4
|
[](https://github.com/cbroult/rake-gem-maintenance/network/updates)
|
|
@@ -18,7 +18,7 @@ gem "rake-gem-maintenance"
|
|
|
18
18
|
Add to your Rakefile for default behavior:
|
|
19
19
|
|
|
20
20
|
```ruby
|
|
21
|
-
require "rake/
|
|
21
|
+
require "rake/gem/maintenance/install_tasks"
|
|
22
22
|
```
|
|
23
23
|
|
|
24
24
|
This defines:
|
|
@@ -33,93 +33,87 @@ This defines:
|
|
|
33
33
|
## Customization
|
|
34
34
|
|
|
35
35
|
```ruby
|
|
36
|
-
require "rake/
|
|
36
|
+
require "rake/gem/maintenance"
|
|
37
37
|
|
|
38
|
-
Rake::
|
|
38
|
+
Rake::Gem::Maintenance::UpgradeTask.new do |t|
|
|
39
39
|
t.main_branch = "develop"
|
|
40
40
|
t.upgrade_branch = "chore/upgrade-deps"
|
|
41
41
|
t.commit_message = "chore: upgrade dependencies"
|
|
42
42
|
end
|
|
43
43
|
|
|
44
|
-
Rake::
|
|
44
|
+
Rake::Gem::Maintenance::VersionBumpTask.new do |t|
|
|
45
45
|
t.default_type = "minor"
|
|
46
46
|
end
|
|
47
47
|
```
|
|
48
48
|
|
|
49
49
|
## Automated Publishing to rubygems.org
|
|
50
50
|
|
|
51
|
-
|
|
52
|
-
generation if your rubygems.org account has MFA enabled.
|
|
53
|
-
|
|
54
|
-
| Env var | Purpose |
|
|
55
|
-
|---|---|
|
|
56
|
-
| `GEM_HOST_API_KEY` | rubygems.org API key (scoped to push) |
|
|
57
|
-
| `RUBYGEMS_OTP_SEED` | Base32 TOTP seed — auto-generates the 2FA code; omit if MFA is disabled |
|
|
58
|
-
|
|
59
|
-
### Quick setup
|
|
60
|
-
|
|
61
|
-
`require "rake/gem_maintenance/install_tasks"` pre-configures both env var names automatically —
|
|
62
|
-
no extra Ruby needed. See [features/install_tasks.feature](features/install_tasks.feature) for
|
|
63
|
-
the full workflow.
|
|
64
|
-
|
|
65
|
-
### Custom env var names
|
|
66
|
-
|
|
67
|
-
```ruby
|
|
68
|
-
require "rake/gem/maintenance"
|
|
51
|
+
### Step 1 — First-time setup (one-off, interactive)
|
|
69
52
|
|
|
70
|
-
|
|
71
|
-
Rake::GemMaintenance::Repos.rubygems_otp_seed_env_var = "MY_OTP_SEED"
|
|
53
|
+
Run the renewal task once on your local machine:
|
|
72
54
|
|
|
73
|
-
|
|
55
|
+
```bash
|
|
56
|
+
rake upgrade:renew_api_key
|
|
74
57
|
```
|
|
75
58
|
|
|
76
|
-
|
|
77
|
-
for all configuration options including geminabox and dual publishing.
|
|
59
|
+
It will prompt for three things:
|
|
78
60
|
|
|
79
|
-
|
|
61
|
+
| Prompt | What to enter |
|
|
62
|
+
|---|---|
|
|
63
|
+
| username | Your rubygems.org username or email |
|
|
64
|
+
| password | Your rubygems.org password (never stored) |
|
|
65
|
+
| OTP seed | The **base32 secret** from your authenticator app setup — the long code shown when you first enabled MFA, *not* the rotating 6-digit code. Omit (press Enter) if MFA is disabled. |
|
|
80
66
|
|
|
81
|
-
After the
|
|
67
|
+
After answering, the task generates a scoped API key and saves it locally alongside your
|
|
68
|
+
username and OTP seed:
|
|
82
69
|
|
|
83
70
|
```
|
|
84
71
|
~/.config/rake-gem-maintenance/credentials.yml # Linux / Mac (respects $XDG_CONFIG_HOME)
|
|
85
72
|
%APPDATA%\rake-gem-maintenance\credentials.yml # Windows
|
|
86
73
|
```
|
|
87
74
|
|
|
88
|
-
The file is
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
75
|
+
The file is `0600` (owner-read-only on Unix). The **password is never written to disk**.
|
|
76
|
+
|
|
77
|
+
### Step 2 — All future local runs are automatic
|
|
78
|
+
|
|
79
|
+
Any project using `require "rake/gem/maintenance/install_tasks"` automatically reads the
|
|
80
|
+
credential file at startup and sets `GEM_HOST_API_KEY` and `RUBYGEMS_OTP_SEED` in the process
|
|
81
|
+
environment. Running `rake upgrade` needs no manual credential setup from this point on.
|
|
92
82
|
|
|
93
83
|
See [features/upgrade_task/credential_store.feature](features/upgrade_task/credential_store.feature)
|
|
94
84
|
for the full behaviour specification.
|
|
95
85
|
|
|
96
|
-
###
|
|
86
|
+
### Step 3 — CI setup (Woodpecker / GitHub Actions)
|
|
97
87
|
|
|
98
|
-
|
|
88
|
+
Set the following as CI secrets:
|
|
99
89
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
90
|
+
| Secret / env var | Purpose |
|
|
91
|
+
|---|---|
|
|
92
|
+
| `RUBYGEMS_USERNAME` | rubygems.org username |
|
|
93
|
+
| `RUBYGEMS_PASSWORD` | rubygems.org password |
|
|
94
|
+
| `RUBYGEMS_OTP_SEED` | Same base32 seed as above |
|
|
95
|
+
| `GEM_HOST_API_KEY` | The API key generated in Step 1 (for the initial push) |
|
|
103
96
|
|
|
104
|
-
|
|
97
|
+
On subsequent runs the key is renewed automatically: when `gem push` returns 401/403, the
|
|
98
|
+
publisher transparently calls `upgrade:renew_api_key` and retries. The refreshed key is written
|
|
99
|
+
back to the `rubygems_api_key` CI secret (requires `WOODPECKER_TOKEN` + `WOODPECKER_SERVER`
|
|
100
|
+
when running under Woodpecker CI).
|
|
105
101
|
|
|
106
|
-
|
|
107
|
-
rake upgrade:renew_api_key
|
|
108
|
-
```
|
|
102
|
+
See [features/upgrade_task/renew_api_key.feature](features/upgrade_task/renew_api_key.feature).
|
|
109
103
|
|
|
110
|
-
|
|
111
|
-
unattended operation:
|
|
104
|
+
### Custom env var names
|
|
112
105
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
| `RUBYGEMS_USERNAME` | rubygems.org account username or email |
|
|
116
|
-
| `RUBYGEMS_PASSWORD` | rubygems.org account password |
|
|
117
|
-
| `RUBYGEMS_OTP_SEED` | Same TOTP seed as above — reused here to authenticate the key-creation request |
|
|
106
|
+
```ruby
|
|
107
|
+
require "rake/gem/maintenance"
|
|
118
108
|
|
|
119
|
-
|
|
120
|
-
|
|
109
|
+
Rake::Gem::Maintenance::Repos.rubygems_api_key_env_var = "MY_RUBYGEMS_KEY"
|
|
110
|
+
Rake::Gem::Maintenance::Repos.rubygems_otp_seed_env_var = "MY_OTP_SEED"
|
|
121
111
|
|
|
122
|
-
|
|
112
|
+
Rake::Gem::Maintenance::UpgradeTask.new
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
See [features/upgrade_task/repos_configuration.feature](features/upgrade_task/repos_configuration.feature)
|
|
116
|
+
for all configuration options including geminabox and dual publishing.
|
|
123
117
|
|
|
124
118
|
## License
|
|
125
119
|
|
data/TODO.md
CHANGED
|
@@ -1 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
# Packwerk
|
|
2
|
+
|
|
3
|
+
Packwerk is Shopify's package enforcement tool designed for large Rails monoliths.
|
|
4
|
+
With ~15 files in this library gem, the overhead is not warranted — close without implementing.
|
|
@@ -1,47 +1,49 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Rake
|
|
4
|
-
module
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
4
|
+
module Gem
|
|
5
|
+
module Maintenance
|
|
6
|
+
# Renews a rubygems.org API key using credentials from env vars and
|
|
7
|
+
# persists the new key to Woodpecker CI when server details are available.
|
|
8
|
+
class ApiKeyRenewer
|
|
9
|
+
def initialize(otp_provider:,
|
|
10
|
+
username_env_var: "RUBYGEMS_USERNAME",
|
|
11
|
+
password_env_var: "RUBYGEMS_PASSWORD")
|
|
12
|
+
@otp_provider = otp_provider
|
|
13
|
+
@username_env_var = username_env_var
|
|
14
|
+
@password_env_var = password_env_var
|
|
15
|
+
end
|
|
15
16
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
def renew(repository)
|
|
18
|
+
username = env_credential(@username_env_var)
|
|
19
|
+
password = env_credential(@password_env_var)
|
|
20
|
+
return nil if username.nil? || password.nil?
|
|
20
21
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
22
|
+
otp = @otp_provider.otp_for(repository[:name], otp_seed_env_var: repository[:otp_seed_env_var])
|
|
23
|
+
new_key = RubyGemsApiKeyCreator.new(host: repository.fetch(:url, "https://rubygems.org"))
|
|
24
|
+
.create(username, password, otp: otp)
|
|
25
|
+
persist_to_woodpecker(new_key)
|
|
26
|
+
new_key
|
|
27
|
+
rescue StandardError
|
|
28
|
+
nil
|
|
29
|
+
end
|
|
29
30
|
|
|
30
|
-
|
|
31
|
+
private
|
|
31
32
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
def env_credential(var)
|
|
34
|
+
value = ENV.fetch(var, nil)
|
|
35
|
+
value&.empty? ? nil : value
|
|
36
|
+
end
|
|
36
37
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
def persist_to_woodpecker(new_key)
|
|
39
|
+
server = ENV.fetch("WOODPECKER_SERVER", nil)
|
|
40
|
+
token = ENV.fetch("WOODPECKER_TOKEN", nil)
|
|
41
|
+
return unless server && token
|
|
41
42
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
43
|
+
org = ENV.fetch("WOODPECKER_ORG", "cbp-org")
|
|
44
|
+
WoodpeckerSecretStore.new(server: server, org: org, token: token)
|
|
45
|
+
.store("rubygems_api_key", new_key)
|
|
46
|
+
end
|
|
45
47
|
end
|
|
46
48
|
end
|
|
47
49
|
end
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Rake
|
|
4
|
-
module
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
module Gem
|
|
5
|
+
module Maintenance
|
|
6
|
+
# Detects whether the current process is running inside a CI environment.
|
|
7
|
+
module CIEnvironment
|
|
8
|
+
def self.ci?
|
|
9
|
+
ENV["CI"].to_s != ""
|
|
10
|
+
end
|
|
9
11
|
end
|
|
10
12
|
end
|
|
11
13
|
end
|
|
@@ -5,60 +5,62 @@ require "fileutils"
|
|
|
5
5
|
require "rubygems"
|
|
6
6
|
|
|
7
7
|
module Rake
|
|
8
|
-
module
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
8
|
+
module Gem
|
|
9
|
+
module Maintenance
|
|
10
|
+
# Persists rubygems.org credentials (username, API key, OTP seed) to a local config file.
|
|
11
|
+
class CredentialStore
|
|
12
|
+
def self.default_path
|
|
13
|
+
base = if ::Gem.win_platform?
|
|
14
|
+
ENV.fetch("APPDATA", File.expand_path("~"))
|
|
15
|
+
else
|
|
16
|
+
ENV.fetch("XDG_CONFIG_HOME", File.join(Dir.home, ".config"))
|
|
17
|
+
end
|
|
18
|
+
File.join(base, "rake-gem-maintenance", "credentials.yml")
|
|
19
|
+
end
|
|
19
20
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
def initialize(path: self.class.default_path)
|
|
22
|
+
@path = path
|
|
23
|
+
end
|
|
23
24
|
|
|
24
|
-
|
|
25
|
+
attr_reader :path
|
|
25
26
|
|
|
26
|
-
|
|
27
|
-
|
|
27
|
+
def read
|
|
28
|
+
return {} unless File.exist?(@path)
|
|
28
29
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
YAML.safe_load_file(@path, symbolize_names: true) || {}
|
|
31
|
+
rescue StandardError
|
|
32
|
+
{}
|
|
33
|
+
end
|
|
33
34
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
35
|
+
def write(credentials)
|
|
36
|
+
FileUtils.mkdir_p(File.dirname(@path))
|
|
37
|
+
File.write(@path, credentials.transform_keys(&:to_s).to_yaml)
|
|
38
|
+
File.chmod(0o600, @path) unless ::Gem.win_platform?
|
|
39
|
+
end
|
|
39
40
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
41
|
+
def apply_to_env(username_env_var:, api_key_env_var:)
|
|
42
|
+
creds = read
|
|
43
|
+
set_env_if_absent(username_env_var, creds[:username])
|
|
44
|
+
set_env_if_absent("RUBYGEMS_OTP_SEED", creds[:rubygems_otp_seed])
|
|
45
|
+
set_env_if_absent(api_key_env_var, creds[:gem_host_api_key])
|
|
46
|
+
end
|
|
46
47
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
48
|
+
def update(username:, api_key:, api_key_env_var:)
|
|
49
|
+
otp_seed = ENV.fetch("RUBYGEMS_OTP_SEED", nil)
|
|
50
|
+
updated = read.merge(username: username, gem_host_api_key: api_key)
|
|
51
|
+
updated[:rubygems_otp_seed] = otp_seed if otp_seed && !otp_seed.empty?
|
|
52
|
+
write(updated)
|
|
53
|
+
ENV[api_key_env_var] = api_key
|
|
54
|
+
end
|
|
54
55
|
|
|
55
|
-
|
|
56
|
+
private
|
|
56
57
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
58
|
+
def set_env_if_absent(env_var, value)
|
|
59
|
+
return unless value && !value.empty?
|
|
60
|
+
return if (existing = ENV.fetch(env_var, nil)) && !existing.empty?
|
|
60
61
|
|
|
61
|
-
|
|
62
|
+
ENV[env_var] = value
|
|
63
|
+
end
|
|
62
64
|
end
|
|
63
65
|
end
|
|
64
66
|
end
|
|
@@ -4,113 +4,115 @@ require "json"
|
|
|
4
4
|
require "open-uri"
|
|
5
5
|
|
|
6
6
|
module Rake
|
|
7
|
-
module
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
7
|
+
module Gem
|
|
8
|
+
module Maintenance
|
|
9
|
+
# Publishes gems to multiple gem repositories, with version checking and warning handling.
|
|
10
|
+
class GemPublisher
|
|
11
|
+
attr_reader :repositories, :warnings, :failed_repositories, :successful_repos
|
|
12
|
+
|
|
13
|
+
def initialize(repositories = default_repositories,
|
|
14
|
+
otp_provider: OtpProvider.new,
|
|
15
|
+
ci_environment: CIEnvironment)
|
|
16
|
+
@repositories = repositories
|
|
17
|
+
@otp_provider = otp_provider
|
|
18
|
+
@ci_environment = ci_environment
|
|
19
|
+
@warnings = []
|
|
20
|
+
@failed_pushes = []
|
|
21
|
+
@failed_repositories = []
|
|
22
|
+
@published_files = []
|
|
23
|
+
@successful_repos = []
|
|
24
|
+
end
|
|
24
25
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
def default_repositories
|
|
27
|
+
[{ name: "rubygems", url: "https://rubygems.org" }]
|
|
28
|
+
end
|
|
28
29
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
30
|
+
def publish(gem_file)
|
|
31
|
+
@failed_pushes = []
|
|
32
|
+
@published_files = []
|
|
33
|
+
build_and_push(gem_file)
|
|
34
|
+
print_warnings
|
|
35
|
+
end
|
|
35
36
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
def check_all_repositories(gem_name)
|
|
38
|
+
@failed_repositories = []
|
|
39
|
+
repositories.each do |repo|
|
|
40
|
+
versions_on_repository(gem_name, repo)
|
|
41
|
+
end
|
|
40
42
|
end
|
|
41
|
-
end
|
|
42
43
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
44
|
+
def versions_on_repository(gem_name, repository)
|
|
45
|
+
url = "#{repository[:url]}/api/v1/gems/#{gem_name}.json"
|
|
46
|
+
uri = URI.parse(url)
|
|
47
|
+
response = uri.read(accept: "application/json")
|
|
48
|
+
data = JSON.parse(response)
|
|
49
|
+
data["versions"].map { |v| v["number"] }
|
|
50
|
+
rescue StandardError => e
|
|
51
|
+
@failed_repositories << repository[:name]
|
|
52
|
+
@warnings << { repository: repository[:name], error: "Cannot fetch versions: #{e.message}" }
|
|
53
|
+
[]
|
|
54
|
+
end
|
|
54
55
|
|
|
55
|
-
|
|
56
|
-
|
|
56
|
+
def next_version(gem_name, current_version)
|
|
57
|
+
return current_version unless current_version
|
|
57
58
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
59
|
+
ver = ::Gem::Version.new(current_version)
|
|
60
|
+
loop do
|
|
61
|
+
return ver.to_s unless version_exists_on_all_repos?(gem_name, ver)
|
|
61
62
|
|
|
62
|
-
|
|
63
|
+
ver = ver.bump
|
|
64
|
+
end
|
|
63
65
|
end
|
|
64
|
-
end
|
|
65
66
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
67
|
+
def available_repositories
|
|
68
|
+
repositories.map { |r| r[:name] } - @failed_repositories
|
|
69
|
+
end
|
|
69
70
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
71
|
+
def any_available?
|
|
72
|
+
@failed_repositories.size < repositories.size
|
|
73
|
+
end
|
|
73
74
|
|
|
74
|
-
|
|
75
|
+
private
|
|
75
76
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
77
|
+
def build_and_push(gem_file)
|
|
78
|
+
repositories.each do |repo|
|
|
79
|
+
push(gem_file, repository: repo)
|
|
80
|
+
end
|
|
79
81
|
end
|
|
80
|
-
end
|
|
81
82
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
83
|
+
def push(gem_file, repository:)
|
|
84
|
+
result = GemPush.new(gem_file, repository, @otp_provider).attempt
|
|
85
|
+
return record_success(gem_file, repository) if result.success
|
|
85
86
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
87
|
+
record_push_failure(repository, result.error)
|
|
88
|
+
rescue StandardError => e
|
|
89
|
+
@failed_pushes << { repository: repository[:name], error: e.message }
|
|
90
|
+
end
|
|
90
91
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
92
|
+
def record_success(gem_file, repository)
|
|
93
|
+
@published_files << gem_file
|
|
94
|
+
@successful_repos << repository[:name]
|
|
95
|
+
end
|
|
95
96
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
97
|
+
def record_push_failure(repository, message)
|
|
98
|
+
@failed_pushes << { repository: repository[:name], error: message.strip }
|
|
99
|
+
end
|
|
99
100
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
101
|
+
def version_exists_on_all_repos?(gem_name, version)
|
|
102
|
+
repositories.all? do |repo|
|
|
103
|
+
versions_on_repository(gem_name, repo).include?(version.to_s)
|
|
104
|
+
end
|
|
103
105
|
end
|
|
104
|
-
end
|
|
105
106
|
|
|
106
|
-
|
|
107
|
-
|
|
107
|
+
def print_warnings
|
|
108
|
+
return if @failed_pushes.empty?
|
|
108
109
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
110
|
+
puts "[WARN] The following repositories were unavailable:"
|
|
111
|
+
@failed_pushes.each do |failure|
|
|
112
|
+
puts " - #{failure[:repository]}: #{failure[:error]}"
|
|
113
|
+
end
|
|
114
|
+
puts "[WARN] Warning: Published to #{@published_files.size} of #{repositories.size} repositories"
|
|
112
115
|
end
|
|
113
|
-
puts "[WARN] Warning: Published to #{@published_files.size} of #{repositories.size} repositories"
|
|
114
116
|
end
|
|
115
117
|
end
|
|
116
118
|
end
|