gemkeeper 0.5.0 → 0.6.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f619f51cc389c49dbd52a00f22a6fc3766822a9d689eaea44d5fcfbb94e5bfe2
4
- data.tar.gz: 79e3718a784f510d72156f071551af20bba3c2d9177ed4b9a2e9791917337947
3
+ metadata.gz: 001a1d05846be989be3ef4c6f4c614f0fc36d8da80f0adaa2cf26998c8664e07
4
+ data.tar.gz: 3816ebfed535178ac1a26c21d9605feb00860182b6558641436ac060cd3a51dd
5
5
  SHA512:
6
- metadata.gz: 03ce5eeb1e672cc09815d80a505a0cf2974bbd378c0f3c38245168100a82b59783a45aacd2afb149b0e9814df8ecbd7114fb1eda4738dc8bfa772c5cfa104809
7
- data.tar.gz: f26bf3d36e183c2aa7585a36349599092f84347fb45f6325b21c196e37b8893a6102afc7a169efa50a880c408c406ca09a0b80af7e5bd87dbac0b5538960e509
6
+ metadata.gz: e19335cfccc7d720acc122fed595b9d9115f0fe3bff5d1e29369413df03a4d799a91ce585a211f3b916af6f12aa38fef3b21f59318827a7008106cb0b523876b
7
+ data.tar.gz: 89832f20c7349469c53b7918fd58c85a43d8988374c321016d520b0e3b93ae567c16ca5d63e0e0bbf3b0a94dd2d5b023cadc359ec38a63e4ed6b89d39420a87e
data/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.6.0] - 2026-05-28
4
+
5
+ ### Added
6
+
7
+ - `gemkeeper setup` now accepts a directory path and finds `Gemfile.lock` inside it.
8
+ - `gemkeeper setup` now accepts a `Gemfile` path and uses the sibling `Gemfile.lock`.
9
+ - `gemkeeper setup` now works with no arguments by finding the nearest `Gemfile.lock` (walks up from the current directory).
10
+ - `gemkeeper manifest generate` now accepts a directory, `Gemfile` path, or no arguments (same resolution as `setup`).
11
+
12
+ ### Fixed
13
+
14
+ - `gemkeeper setup` no longer configures a Bundler mirror for a private gem registry when all gems from that source were skipped during manifest resolution.
15
+ - `gemkeeper setup` and `gemkeeper manifest generate` now report a clear error when the resolved source path does not exist.
16
+ - All commands that accept `--config` now report a clear error when the specified config file does not exist, instead of silently using defaults.
17
+ - `gemkeeper server stop` now exits 0 when the server is already stopped; stopping an already-stopped server is not an error.
18
+
3
19
  ## [0.5.0] - 2026-05-27
4
20
 
5
21
  ### Added
@@ -67,7 +83,8 @@
67
83
 
68
84
  - Initial release
69
85
 
70
- [Unreleased]: https://github.com/danhorst/gemkeeper/compare/v0.5.0...HEAD
86
+ [Unreleased]: https://github.com/danhorst/gemkeeper/compare/v0.6.0...HEAD
87
+ [0.6.0]: https://github.com/danhorst/gemkeeper/compare/0.5.0...0.6.0
71
88
  [0.5.0]: https://github.com/danhorst/gemkeeper/compare/0.4.0...0.5.0
72
89
  [0.4.0]: https://github.com/danhorst/gemkeeper/compare/0.3.0...0.4.0
73
90
  [0.3.0]: https://github.com/danhorst/gemkeeper/compare/0.2.1...0.3.0
data/README.md CHANGED
@@ -169,12 +169,21 @@ gemkeeper server status
169
169
  # Generate gemkeeper.yml from a Gemfile.lock and org manifest
170
170
  gemkeeper setup path/to/Gemfile.lock
171
171
 
172
+ # Use an existing gemkeeper.yml as input (updates manifest, optionally installs as global config)
173
+ gemkeeper setup path/to/gemkeeper.yml
174
+
172
175
  # Use a custom manifest path
173
176
  gemkeeper setup path/to/Gemfile.lock --manifest ~/.config/myorg/manifest.yml
174
177
 
178
+ # Write gemkeeper.yml to a specific path
179
+ gemkeeper setup path/to/Gemfile.lock --config path/to/output.yml
180
+
175
181
  # Overwrite existing gemkeeper.yml entirely
176
182
  gemkeeper setup path/to/Gemfile.lock --force
177
183
 
184
+ # Skip auto-configuring Bundler mirrors
185
+ gemkeeper setup path/to/Gemfile.lock --skip-bundler-config
186
+
178
187
  # Write to the global Homebrew service config instead of the current directory
179
188
  gemkeeper setup path/to/Gemfile.lock --global
180
189
  ```
@@ -183,6 +192,31 @@ gemkeeper setup path/to/Gemfile.lock --global
183
192
  It sets `repos_path` and `gems_path` as absolute paths under the corresponding `var` directory so the daemon finds them regardless of which directory you run commands from.
184
193
  `--global` and `--config` are mutually exclusive.
185
194
 
195
+ ### Manifest Management
196
+
197
+ 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.
199
+
200
+ ```bash
201
+ # Build or update the manifest from a Gemfile.lock
202
+ gemkeeper manifest generate path/to/Gemfile.lock
203
+
204
+ # Use a custom manifest path
205
+ gemkeeper manifest generate path/to/Gemfile.lock --manifest ~/.config/myorg/manifest.yml
206
+
207
+ # Overwrite the manifest entirely (discard existing entries)
208
+ gemkeeper manifest generate path/to/Gemfile.lock --force
209
+
210
+ # Validate the default manifest
211
+ gemkeeper manifest validate
212
+
213
+ # Validate a specific manifest file
214
+ gemkeeper manifest validate path/to/manifest.yml
215
+
216
+ # Validate and probe each repo via git ls-remote
217
+ gemkeeper manifest validate --resolve
218
+ ```
219
+
186
220
  ### Gem Synchronization
187
221
 
188
222
  ```bash
data/exe/gemkeeper CHANGED
@@ -10,4 +10,7 @@ begin
10
10
  Dry::CLI.new(Gemkeeper::CLI).call
11
11
  rescue SystemExit => e
12
12
  exit(ARGV.include?("--help") || ARGV.include?("-h") ? 0 : e.status)
13
+ rescue Gemkeeper::ConfigFileNotFoundError => e
14
+ warn "Error: #{e.message}"
15
+ exit 1
13
16
  end
@@ -5,16 +5,19 @@ module Gemkeeper
5
5
  module Commands
6
6
  module Manifest
7
7
  class Generate < Dry::CLI::Command
8
+ include CLI::LockfileResolution
9
+
8
10
  desc "Build or update the gem manifest from a Gemfile.lock"
9
11
 
10
- argument :lockfile_path, type: :string, required: true,
11
- desc: "Path to the project's Gemfile.lock"
12
+ argument :lockfile_path, type: :string, required: false,
13
+ desc: "Gemfile.lock, Gemfile, or directory (default: nearest Gemfile.lock)"
12
14
  option :manifest, type: :string,
13
15
  desc: "Path to write manifest (default: ~/.config/gemkeeper/manifest.yml)"
14
16
  option :force, type: :boolean, default: false,
15
17
  desc: "Overwrite existing manifest entirely"
16
18
 
17
- def call(lockfile_path:, **options)
19
+ def call(lockfile_path: nil, **options)
20
+ lockfile_path = resolve_source_path(lockfile_path)
18
21
  path = manifest_path(options)
19
22
  manifest = ManifestReader.load(path)
20
23
  manifest.clear! if options[:force]
@@ -15,9 +15,8 @@ module Gemkeeper
15
15
  manager.stop
16
16
 
17
17
  puts "Geminabox server stopped"
18
- rescue ServerNotRunningError => error
19
- warn "Error: #{error.message}"
20
- exit 1
18
+ rescue ServerNotRunningError
19
+ puts "Geminabox server is not running"
21
20
  rescue ServerError => error
22
21
  warn "Error stopping server: #{error.message}"
23
22
  exit 1
@@ -4,10 +4,13 @@ module Gemkeeper
4
4
  module CLI
5
5
  module Commands
6
6
  class Setup < Dry::CLI::Command
7
+ include CLI::LockfileResolution
8
+
7
9
  desc "Generate gemkeeper.yml from a Gemfile.lock or existing gemkeeper.yml"
8
10
 
9
- argument :source_path, type: :string, required: true,
10
- desc: "Path to a Gemfile.lock or gemkeeper.yml"
11
+ argument :source_path, type: :string, required: false,
12
+ desc: "Gemfile.lock, Gemfile, directory, or gemkeeper.yml " \
13
+ "(default: nearest Gemfile.lock)"
11
14
  option :manifest, type: :string,
12
15
  desc: "Path to gem manifest (default: ~/.config/gemkeeper/manifest.yml)"
13
16
  option :config, type: :string, desc: "Path to write gemkeeper.yml (default: ./gemkeeper.yml)"
@@ -18,14 +21,15 @@ module Gemkeeper
18
21
  option :skip_bundler_config, type: :boolean, default: false,
19
22
  desc: "Skip configuring Bundler mirrors for private gem sources"
20
23
 
21
- def call(source_path:, **options)
24
+ def call(source_path: nil, **options)
22
25
  validate_options!(options)
23
26
  output_path = resolve_output_path(options)
27
+ resolved = resolve_source_path(source_path)
24
28
 
25
- if lockfile?(source_path)
26
- setup_from_lockfile(source_path, output_path, options)
29
+ if lockfile?(resolved)
30
+ setup_from_lockfile(resolved, output_path, options)
27
31
  else
28
- setup_from_config(source_path, output_path, options)
32
+ setup_from_config(resolved, output_path, options)
29
33
  end
30
34
  rescue UnresolvableGemError, ManifestConflictError => error
31
35
  warn "Error: #{error.message}"
@@ -53,7 +57,8 @@ module Gemkeeper
53
57
  return if options[:skip_bundler_config]
54
58
 
55
59
  port = config.fetch("port", Configuration::DEFAULT_PORT)
56
- BundlerMirrorConfigurator.new(result.candidates, port:, global: options[:global]).configure
60
+ resolved = result.candidates.select { |c| result.manifest.repo_for(c[:name]) }
61
+ BundlerMirrorConfigurator.new(resolved, port:, global: options[:global]).configure
57
62
  end
58
63
 
59
64
  def setup_from_config(source_path, output_path, options)
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gemkeeper
4
+ module CLI
5
+ # Shared path coercion for commands that accept a Gemfile.lock argument.
6
+ # Accepts nil (find nearest), a directory, a Gemfile path, or an explicit path.
7
+ module LockfileResolution
8
+ private
9
+
10
+ def resolve_source_path(path)
11
+ resolved = coerce_source_path(path)
12
+ File.exist?(resolved) ? resolved : missing_source!(resolved)
13
+ end
14
+
15
+ def coerce_source_path(path)
16
+ return LockfileParser.find || no_lockfile! if path.nil?
17
+ return File.join(path, "Gemfile.lock") if File.directory?(path)
18
+ return File.join(File.dirname(path), "Gemfile.lock") if File.basename(path) == "Gemfile"
19
+
20
+ path
21
+ end
22
+
23
+ def no_lockfile!
24
+ warn "Error: no Gemfile.lock found in #{Dir.pwd} or any parent directory"
25
+ exit 1
26
+ end
27
+
28
+ def missing_source!(path)
29
+ warn "Error: file not found — #{path}"
30
+ exit 1
31
+ end
32
+ end
33
+ end
34
+ end
data/lib/gemkeeper/cli.rb CHANGED
@@ -12,6 +12,7 @@ module Gemkeeper
12
12
  end
13
13
  end
14
14
 
15
+ require_relative "cli/lockfile_resolution"
15
16
  require_relative "cli/commands/version"
16
17
  require_relative "cli/commands/setup"
17
18
  require_relative "cli/commands/sync"
@@ -56,6 +56,7 @@ module Gemkeeper
56
56
  end
57
57
 
58
58
  def initialize(config_path = nil)
59
+ @explicit_config = !config_path.nil?
59
60
  @config_path = config_path || find_config_file
60
61
  @config = load_config
61
62
  apply_config
@@ -91,7 +92,13 @@ module Gemkeeper
91
92
  end
92
93
 
93
94
  def load_config
94
- return {} unless @config_path && File.exist?(@config_path)
95
+ return {} unless @config_path
96
+
97
+ unless File.exist?(@config_path)
98
+ raise ConfigFileNotFoundError, "config file not found — #{@config_path}" if @explicit_config
99
+
100
+ return {}
101
+ end
95
102
 
96
103
  begin
97
104
  YAML.safe_load_file(@config_path, permitted_classes: [], symbolize_names: true) || {}
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Gemkeeper
4
- VERSION = "0.5.0"
4
+ VERSION = "0.6.0"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gemkeeper
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dan Brubaker Horst
@@ -106,6 +106,7 @@ files:
106
106
  - lib/gemkeeper/cli/commands/setup.rb
107
107
  - lib/gemkeeper/cli/commands/sync.rb
108
108
  - lib/gemkeeper/cli/commands/version.rb
109
+ - lib/gemkeeper/cli/lockfile_resolution.rb
109
110
  - lib/gemkeeper/config_generator.rb
110
111
  - lib/gemkeeper/configuration.rb
111
112
  - lib/gemkeeper/errors.rb