gemkeeper 0.6.7 → 0.7.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/CHANGELOG.md +17 -1
- data/README.md +15 -12
- data/lib/gemkeeper/cli/commands/setup.rb +8 -38
- data/lib/gemkeeper/cli/commands/sync.rb +1 -1
- data/lib/gemkeeper/config_generator.rb +3 -3
- data/lib/gemkeeper/configuration.rb +6 -2
- data/lib/gemkeeper/gem_syncer.rb +32 -3
- data/lib/gemkeeper/manifest_reader.rb +1 -1
- data/lib/gemkeeper/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: bed56339bf29ed0dd82b9aec9c94a4a466b0d38364b3b3e5280d4d51b1101cf0
|
|
4
|
+
data.tar.gz: '09132dd1f425d64e0c423386583a29c9010b0f73fe016088428e5a3fe5dbe57c'
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4e1178a59bda6fd8f299f1e16e68d828ae23b8176785dba7cff5c6c7f7d8776563e564fcadbb0746ea11bc8ce808a00be89de06836cf36dd49ecfbb52f401de4
|
|
7
|
+
data.tar.gz: 81e4e60ce2f0ced3d809448f4bbbada9a959bbc33215f27808c5eaf4a7987e56f6905259e26c93b1e09b177c503b5422702d7fb940f08855ae2e314c84146670
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.7.0] - 2026-05-28
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- `gemkeeper setup` and `gemkeeper manifest generate` no longer write `repo:` into generated `gemkeeper.yml` entries.
|
|
8
|
+
The repo URL is resolved from the manifest by gem name at sync time, making the manifest the single source of truth for name→repo mappings.
|
|
9
|
+
Re-running `setup` strips `repo:` from matched entries, promoting them to manifest-only.
|
|
10
|
+
- `gemkeeper setup` now accepts only a `Gemfile.lock`, `Gemfile`, or directory as its source.
|
|
11
|
+
The path that imported an existing `gemkeeper.yml` into the manifest has been removed — it only made sense when configs carried `repo:`; populate the manifest with `gemkeeper manifest generate` or `setup` from a lockfile instead.
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- `repo:` in `gemkeeper.yml` is now optional. When absent, `gemkeeper sync` resolves it from the manifest (`~/.config/gemkeeper/manifest.yml`) by gem name.
|
|
16
|
+
It remains a supported per-project override (escape hatch) for gems not in the manifest; `sync` warns when an explicit `repo:` diverges from the manifest and uses the `gemkeeper.yml` value.
|
|
17
|
+
|
|
3
18
|
## [0.6.7] - 2026-05-28
|
|
4
19
|
|
|
5
20
|
### Fixed
|
|
@@ -139,7 +154,8 @@
|
|
|
139
154
|
|
|
140
155
|
- Initial release
|
|
141
156
|
|
|
142
|
-
[Unreleased]: https://github.com/danhorst/gemkeeper/compare/v0.
|
|
157
|
+
[Unreleased]: https://github.com/danhorst/gemkeeper/compare/v0.7.0...HEAD
|
|
158
|
+
[0.7.0]: https://github.com/danhorst/gemkeeper/compare/0.6.7...0.7.0
|
|
143
159
|
[0.6.7]: https://github.com/danhorst/gemkeeper/compare/0.6.6...0.6.7
|
|
144
160
|
[0.6.6]: https://github.com/danhorst/gemkeeper/compare/0.6.5...0.6.6
|
|
145
161
|
[0.6.5]: https://github.com/danhorst/gemkeeper/compare/0.6.4...0.6.5
|
data/README.md
CHANGED
|
@@ -70,15 +70,17 @@ Public gems are proxied from RubyGems.org automatically.
|
|
|
70
70
|
|
|
71
71
|
## Quick Start
|
|
72
72
|
|
|
73
|
-
1.
|
|
73
|
+
1. Ensure your org manifest is present at `~/.config/gemkeeper/manifest.yml` (see [Workstation Setup](#workstation-setup)), then create a project `gemkeeper.yml`:
|
|
74
74
|
|
|
75
75
|
```yaml
|
|
76
76
|
port: 9292
|
|
77
77
|
gems:
|
|
78
|
-
-
|
|
78
|
+
- name: internal-gem
|
|
79
79
|
version: latest
|
|
80
80
|
```
|
|
81
81
|
|
|
82
|
+
The repo URL for `internal-gem` is resolved from the manifest at sync time.
|
|
83
|
+
|
|
82
84
|
2. Start the server:
|
|
83
85
|
|
|
84
86
|
```bash
|
|
@@ -127,18 +129,21 @@ pid_file: ./cache/gemkeeper.pid
|
|
|
127
129
|
|
|
128
130
|
# List of gems to manage
|
|
129
131
|
gems:
|
|
130
|
-
#
|
|
131
|
-
-
|
|
132
|
+
# Preferred form: name only. The repo URL is resolved from the manifest at sync time.
|
|
133
|
+
- name: gem-one
|
|
132
134
|
version: latest # Use the latest commit on main/master; cached by resolved gemspec version
|
|
133
135
|
|
|
134
|
-
-
|
|
136
|
+
- name: gem-two
|
|
135
137
|
version: v1.2.3 # Use a specific tag; both v-prefixed and bare semver accepted
|
|
136
138
|
|
|
137
|
-
-
|
|
139
|
+
- name: gem-two
|
|
138
140
|
version: from_lockfile # Read version from the nearest Gemfile.lock
|
|
139
141
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
+
# repo: is optional — a per-project override for gems not in the manifest (HTTPS recommended,
|
|
143
|
+
# works without SSH key setup; alternative: git@github.com:company/gem-three.git). When both are
|
|
144
|
+
# present, repo: wins and sync warns if it diverges from the manifest.
|
|
145
|
+
- name: gem-three
|
|
146
|
+
repo: https://github.com/company/ruby-gem-three
|
|
142
147
|
```
|
|
143
148
|
|
|
144
149
|
## CLI Commands
|
|
@@ -169,9 +174,6 @@ gemkeeper server status
|
|
|
169
174
|
# Generate gemkeeper.yml from a Gemfile.lock and org manifest
|
|
170
175
|
gemkeeper setup path/to/Gemfile.lock
|
|
171
176
|
|
|
172
|
-
# Use an existing gemkeeper.yml as input (updates manifest, optionally installs as global config)
|
|
173
|
-
gemkeeper setup path/to/gemkeeper.yml
|
|
174
|
-
|
|
175
177
|
# Use a custom manifest path
|
|
176
178
|
gemkeeper setup path/to/Gemfile.lock --manifest ~/.config/myorg/manifest.yml
|
|
177
179
|
|
|
@@ -195,7 +197,8 @@ It sets `repos_path` and `gems_path` as absolute paths under the corresponding `
|
|
|
195
197
|
### Manifest Management
|
|
196
198
|
|
|
197
199
|
The manifest (`~/.config/gemkeeper/manifest.yml`) is the global name→repo lookup table shared across projects.
|
|
198
|
-
`manifest generate` builds or updates it; `setup` reads it.
|
|
200
|
+
`manifest generate` builds or updates it; `setup` reads it, and `sync` resolves each gem's repo URL from it.
|
|
201
|
+
Because `gemkeeper.yml` entries omit `repo:`, the manifest must be present before `sync`; run `gemkeeper manifest validate --resolve` first to confirm every gem maps to a reachable repo.
|
|
199
202
|
|
|
200
203
|
```bash
|
|
201
204
|
# Build or update the manifest from a Gemfile.lock
|
|
@@ -6,11 +6,10 @@ module Gemkeeper
|
|
|
6
6
|
class Setup < Dry::CLI::Command
|
|
7
7
|
include CLI::LockfileResolution
|
|
8
8
|
|
|
9
|
-
desc "Generate gemkeeper.yml from a Gemfile.lock
|
|
9
|
+
desc "Generate gemkeeper.yml from a Gemfile.lock"
|
|
10
10
|
|
|
11
11
|
argument :source_path, type: :string, required: false,
|
|
12
|
-
desc: "Gemfile.lock, Gemfile, directory
|
|
13
|
-
"(default: nearest Gemfile.lock)"
|
|
12
|
+
desc: "Gemfile.lock, Gemfile, or directory (default: nearest Gemfile.lock)"
|
|
14
13
|
option :manifest, type: :string,
|
|
15
14
|
desc: "Path to gem manifest (default: ~/.config/gemkeeper/manifest.yml)"
|
|
16
15
|
option :config, type: :string, desc: "Path to write gemkeeper.yml (default: ./gemkeeper.yml)"
|
|
@@ -25,12 +24,9 @@ module Gemkeeper
|
|
|
25
24
|
validate_options!(options)
|
|
26
25
|
output_path = resolve_output_path(options)
|
|
27
26
|
resolved = resolve_source_path(source_path)
|
|
27
|
+
not_a_lockfile!(resolved) unless lockfile?(resolved)
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
setup_from_lockfile(resolved, output_path, options)
|
|
31
|
-
else
|
|
32
|
-
setup_from_config(resolved, output_path, options)
|
|
33
|
-
end
|
|
29
|
+
setup_from_lockfile(resolved, output_path, options)
|
|
34
30
|
rescue UnresolvableGemError, ManifestConflictError => error
|
|
35
31
|
warn "Error: #{error.message}"
|
|
36
32
|
exit 1
|
|
@@ -61,36 +57,10 @@ module Gemkeeper
|
|
|
61
57
|
BundlerMirrorConfigurator.new(resolved, port:, global: options[:global]).configure
|
|
62
58
|
end
|
|
63
59
|
|
|
64
|
-
def
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
install_global_config(source, output_path) if options[:global]
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
def update_manifest_from_config(source, manifest, options)
|
|
72
|
-
(source["gems"] || []).each do |entry|
|
|
73
|
-
repo = entry["repo"].to_s
|
|
74
|
-
next if repo.empty?
|
|
75
|
-
|
|
76
|
-
name = File.basename(repo, ".git").sub(/^ruby-/, "")
|
|
77
|
-
manifest.add_mapping(name:, repo:)
|
|
78
|
-
end
|
|
79
|
-
manifest.save(manifest_path(options))
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
def install_global_config(source, output_path)
|
|
83
|
-
existing = File.exist?(output_path) ? (YAML.safe_load_file(output_path) || {}) : {}
|
|
84
|
-
merged = existing.merge(source.except("gems")).merge("gems" => merge_gem_lists(existing, source))
|
|
85
|
-
File.write(output_path, merged.to_yaml)
|
|
86
|
-
puts "Wrote #{output_path}"
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
def merge_gem_lists(existing, source)
|
|
90
|
-
existing_gems = existing["gems"] || []
|
|
91
|
-
source_gems = source["gems"] || []
|
|
92
|
-
existing_repos = existing_gems.to_set { |g| g["repo"] }
|
|
93
|
-
existing_gems + source_gems.reject { |g| existing_repos.include?(g["repo"]) }
|
|
60
|
+
def not_a_lockfile!(path)
|
|
61
|
+
warn "Error: setup builds from a Gemfile.lock, Gemfile, or directory — got #{path}. " \
|
|
62
|
+
"To populate the manifest, run 'gemkeeper manifest generate'."
|
|
63
|
+
exit 1
|
|
94
64
|
end
|
|
95
65
|
|
|
96
66
|
def load_manifest(options)
|
|
@@ -12,7 +12,7 @@ module Gemkeeper
|
|
|
12
12
|
def call(gem_name: nil, **options)
|
|
13
13
|
config = Configuration.load(options[:config])
|
|
14
14
|
gems_to_sync = select_gems(config, gem_name)
|
|
15
|
-
syncer = GemSyncer.new(config, GemUploader.new(config.geminabox_url))
|
|
15
|
+
syncer = GemSyncer.new(config, GemUploader.new(config.geminabox_url), manifest: ManifestReader.load)
|
|
16
16
|
counts, failures = run_sync(gems_to_sync, syncer)
|
|
17
17
|
report_results(counts, failures, gems_to_sync.size)
|
|
18
18
|
end
|
|
@@ -22,7 +22,7 @@ module Gemkeeper
|
|
|
22
22
|
def matched_gems
|
|
23
23
|
matched = @manifest.gems.filter_map do |gem_entry|
|
|
24
24
|
name = gem_entry[:name]
|
|
25
|
-
{ name
|
|
25
|
+
{ name: } if @lockfile_versions.key?(name)
|
|
26
26
|
end
|
|
27
27
|
warn_unmatched
|
|
28
28
|
matched
|
|
@@ -47,7 +47,7 @@ module Gemkeeper
|
|
|
47
47
|
|
|
48
48
|
def build_fresh(matched, global_output_path: nil)
|
|
49
49
|
repos_path, gems_path = data_paths_for(global_output_path)
|
|
50
|
-
gem_entries = matched.map { |g| { "name" => g[:name], "
|
|
50
|
+
gem_entries = matched.map { |g| { "name" => g[:name], "version" => "from_lockfile" } }
|
|
51
51
|
{ "port" => Configuration::DEFAULT_PORT, "repos_path" => repos_path,
|
|
52
52
|
"gems_path" => gems_path, "gems" => gem_entries }
|
|
53
53
|
end
|
|
@@ -62,7 +62,7 @@ module Gemkeeper
|
|
|
62
62
|
def merge(existing, matched)
|
|
63
63
|
existing_gems = existing["gems"] || []
|
|
64
64
|
new_by_name = matched.to_h do |g|
|
|
65
|
-
[g[:name], { "name" => g[:name], "
|
|
65
|
+
[g[:name], { "name" => g[:name], "version" => "from_lockfile" }]
|
|
66
66
|
end
|
|
67
67
|
|
|
68
68
|
updated = existing_gems.map do |entry|
|
|
@@ -127,9 +127,11 @@ module Gemkeeper
|
|
|
127
127
|
attr_reader :repo, :version, :name
|
|
128
128
|
|
|
129
129
|
def initialize(config)
|
|
130
|
-
@repo = config[:repo]
|
|
131
|
-
@version = config[:version] || "latest"
|
|
130
|
+
@repo = config[:repo]
|
|
132
131
|
@name = config[:name] || extract_name_from_repo
|
|
132
|
+
raise InvalidConfigError, "Gem definition needs a 'name' or 'repo'" unless @name
|
|
133
|
+
|
|
134
|
+
@version = config[:version] || "latest"
|
|
133
135
|
validate_version!
|
|
134
136
|
end
|
|
135
137
|
|
|
@@ -153,6 +155,8 @@ module Gemkeeper
|
|
|
153
155
|
end
|
|
154
156
|
|
|
155
157
|
def extract_name_from_repo
|
|
158
|
+
return nil unless @repo
|
|
159
|
+
|
|
156
160
|
File.basename(@repo, ".git").sub(/^ruby-/, "")
|
|
157
161
|
end
|
|
158
162
|
end
|
data/lib/gemkeeper/gem_syncer.rb
CHANGED
|
@@ -11,12 +11,14 @@ module Gemkeeper
|
|
|
11
11
|
/fatal: credential/i
|
|
12
12
|
].freeze
|
|
13
13
|
|
|
14
|
-
def initialize(config, uploader)
|
|
14
|
+
def initialize(config, uploader, manifest:)
|
|
15
15
|
@config = config
|
|
16
16
|
@uploader = uploader
|
|
17
|
+
@manifest = manifest
|
|
17
18
|
end
|
|
18
19
|
|
|
19
20
|
def sync(gem_def)
|
|
21
|
+
repo_url = resolve_repo(gem_def)
|
|
20
22
|
version = resolve_version(gem_def)
|
|
21
23
|
name = gem_def.name
|
|
22
24
|
gems_path = @config.gems_path
|
|
@@ -25,13 +27,13 @@ module Gemkeeper
|
|
|
25
27
|
|
|
26
28
|
puts "Syncing #{name} @ #{version}..."
|
|
27
29
|
local_path = File.join(@config.repos_path, name)
|
|
28
|
-
repo = fetch_repo(
|
|
30
|
+
repo = fetch_repo(repo_url, local_path)
|
|
29
31
|
|
|
30
32
|
Output.step("Checking out #{version}...")
|
|
31
33
|
repo.checkout_version(version)
|
|
32
34
|
|
|
33
35
|
if gem_def.latest?
|
|
34
|
-
version = latest_version!(repo, name, gems_path,
|
|
36
|
+
version = latest_version!(repo, name, gems_path, repo_url)
|
|
35
37
|
return :skipped unless version
|
|
36
38
|
end
|
|
37
39
|
|
|
@@ -41,6 +43,33 @@ module Gemkeeper
|
|
|
41
43
|
|
|
42
44
|
private
|
|
43
45
|
|
|
46
|
+
# Explicit repo: in gemkeeper.yml wins, but warns on divergence from the manifest.
|
|
47
|
+
# Otherwise the repo is resolved from the manifest by gem name.
|
|
48
|
+
def resolve_repo(gem_def)
|
|
49
|
+
manifest_repo = @manifest.repo_for(gem_def.name)
|
|
50
|
+
return manifest_repo || missing_repo!(gem_def.name) unless gem_def.repo
|
|
51
|
+
|
|
52
|
+
warn_if_divergent(gem_def.name, gem_def.repo, manifest_repo)
|
|
53
|
+
gem_def.repo
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def missing_repo!(name)
|
|
57
|
+
unless File.exist?(@manifest.path)
|
|
58
|
+
raise InvalidConfigError,
|
|
59
|
+
"No manifest found at #{@manifest.path} — run 'gemkeeper manifest generate' to create one"
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
raise InvalidConfigError,
|
|
63
|
+
"No repo configured for #{name.inspect} — add it to the manifest with 'gemkeeper manifest generate'"
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def warn_if_divergent(name, config_repo, manifest_repo)
|
|
67
|
+
return unless manifest_repo && manifest_repo != config_repo
|
|
68
|
+
|
|
69
|
+
warn "Warning: repo for #{name} in gemkeeper.yml (#{config_repo}) " \
|
|
70
|
+
"differs from manifest (#{manifest_repo}) — using gemkeeper.yml"
|
|
71
|
+
end
|
|
72
|
+
|
|
44
73
|
def resolve_version(gem_def)
|
|
45
74
|
return gem_def.version unless gem_def.from_lockfile?
|
|
46
75
|
|
data/lib/gemkeeper/version.rb
CHANGED