gemvault 0.2.2 → 0.2.3

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: 6a1d5d0c990e232ac54514f5837db4224d92b127175f370a3443c8d15d44099f
4
- data.tar.gz: ab40029793c2dd05206b9812e27da8921eb7fc478164f71fa7485296d5455f15
3
+ metadata.gz: fc64d612838fd7c22293d18622272cf495d92374f0b23aeb55eb900678081ec8
4
+ data.tar.gz: 29dae30a7efc5c9d38b829859682e8c3d0db49e340b4e1a28f139940045938ad
5
5
  SHA512:
6
- metadata.gz: 916b9a114590c9eb5fa3a377d59e9639c33a28e948429b8bac18399473eba0dcacf6711a9808106e3f91348945d71ebd051c1f65fe05239f9804289f56fcf859
7
- data.tar.gz: 9ea36ee67195ca7f7a8bafd7ebc8658e000b72bcbf72356e3a2266c2f8d2679e0d8f5212f0b977a36a36c95441152f2dabae8ada78c5831770c0cd3498c4253d
6
+ metadata.gz: 305c089e3b58804bbf70f89e3c11f94300fc2e6204dec50fab4fb63346da163fe735f639ee89a7fceea83caa53b17f2a69c3f898e97e4b0585ace415f0ade178
7
+ data.tar.gz: 15bcb0df46245246f279fcb4a08bfa1c351d68f09e432ac547df92e56120800bc5decc1ae5f8351a49990b0f5001a23437a78161af66491f1dd71a2162915bb0
data/.rubycritic.yml ADDED
@@ -0,0 +1,15 @@
1
+ path: "tmp/rubycritic" # Set path where report will be saved (tmp/rubycritic by default)
2
+ coverage_path: "tmp/coverage" # Set path where SimpleCov coverage will be saved (./coverage by default)
3
+ threshold_score: 0 # default is 0
4
+ deduplicate_symlinks: false # default is false
5
+ suppress_ratings: false # default is false
6
+ no_browser: true # default is false
7
+ formats: # Available values are: html, json, console, lint. Default value is html.
8
+ - console
9
+ - json
10
+ minimum_score: 95 # default is 0
11
+ paths: # Files to analyse. Churn calculation is scoped to these files when using Git SCM.
12
+ - "lib/"
13
+ - "spec/"
14
+ - "test/"
15
+ - "shim/"
data/CLAUDE.md CHANGED
@@ -9,15 +9,16 @@ Do NOT modify `.rubocop.yml` or use inline `# rubocop:disable` tags without expl
9
9
  1. Specs always come first. Every plan should start with the skeleton of the BDD specs being added, changed or removed. Skeleton means the empty RSpec language, without implementation.
10
10
  2. Specs should never have comments. Any urge to put a comment in a spec means that comment should probably be its own spec
11
11
  3. DO NOT edit .rubocop.yml or add inline rubocop exemptions without explicit permission
12
- 4. DO NOT run any git command that will rewrite history without explicit permission
13
- 5. PREFER method & class extraction over comments
14
- 6. Making new files, classes, modules, and methods IS NOT overengineering
15
- 7. BEFORE writing code, identify which domain concept owns the behavior. Each class and module should have a single responsibility. If the new behavior doesn't fit an existing class's responsibility, create a new one — don't expand the scope of what's already there.
16
- 8. DO NOT name classes with suffixes like "-er" or "-or" unless using a canonical pattern name (e.g., Parser, Router, Controller)
17
- 9. ALWAYS write specs first. The workflow is: identify the domain concept (rule 5), write specs describing its behavior, then implement. No implementation without a failing spec.
18
- 10. Integration specs are the first line of defense for CLI-tool bugs. For any bug reported from using the CLI tool (not the gemvault lib / Ruby API), the FIRST spec you write is an integration spec that reproduces the user's exact invocation — real subprocess, real vault, real exit code. Stub-heavy unit specs are complementary, not sufficient: they prove internal logic produces the expected value assuming surrounding wiring works, but a user's bug report is evidence the wiring didn't work.
19
- 11. If an integration spec is not catching a reported CLI-tool bug, one of two things is true, and the fix starts by diagnosing which: (a) existing integration specs are not specific enough extend them to cover the exact scenario before touching production code; or (b) the scenario is not spec'd at all, which means the work is not a bug fix but a new feature write integration specs for the contract first (per rule 1), then implement.
20
- 12. NEVER write to /tmp. Use /workspace/tmp
12
+ 4. DO NOT edit .rubycritic.yml or add inline rubycritic exemptions without explicit permission
13
+ 5. DO NOT run any git command that will rewrite history without explicit permission
14
+ 6. PREFER method & class extraction over comments
15
+ 7. Making new files, classes, modules, and methods IS NOT overengineering
16
+ 8. BEFORE writing code, identify which domain concept owns the behavior. Each class and module should have a single responsibility. If the new behavior doesn't fit an existing class's responsibility, create a new one — don't expand the scope of what's already there.
17
+ 9. DO NOT name classes with suffixes like "-er" or "-or" unless using a canonical pattern name (e.g., Parser, Router, Controller)
18
+ 10. ALWAYS write specs first. The workflow is: identify the domain concept (rule 5), write specs describing its behavior, then implement. No implementation without a failing spec.
19
+ 11. Integration specs are the first line of defense for CLI-tool bugs. For any bug reported from using the CLI tool (not the gemvault lib / Ruby API), the FIRST spec you write is an integration spec that reproduces the user's exact invocation real subprocess, real vault, real exit code. Stub-heavy unit specs are complementary, not sufficient: they prove internal logic produces the expected value assuming surrounding wiring works, but a user's bug report is evidence the wiring didn't work.
20
+ 12. If an integration spec is not catching a reported CLI-tool bug, one of two things is true, and the fix starts by diagnosing which: (a) existing integration specs are not specific enough — extend them to cover the exact scenario before touching production code; or (b) the scenario is not spec'd at all, which means the work is not a bug fix but a new feature — write integration specs for the contract first (per rule 1), then implement.
21
+ 13. NEVER write to /tmp. Use /workspace/tmp
21
22
 
22
23
  ## Additional rules
23
24
 
@@ -94,7 +95,7 @@ bundle exec rake test
94
95
  ```
95
96
 
96
97
  Minitest covers the library; RSpec covers the CLI and the containerized
97
- integration suite. `rake` (the default task) runs `test`, `spec` and `rubocop`.
98
+ integration suite. `rake` (the default task) runs `test`, `spec`, `rubocop` and `rubycritic`.
98
99
 
99
100
  ```bash
100
101
  bundle exec rake test # minitest only
@@ -118,7 +119,7 @@ to avoid rubygems.org resolution during testing.
118
119
 
119
120
  The container has to look like a machine a user actually has. The `ruby` base
120
121
  image does not, and every way it differs has already hidden a real defect. These
121
- four are load-bearing; reverting any of them silently makes the suite green
122
+ five are load-bearing; reverting any of them silently makes the suite green
122
123
  against something other than the code under test:
123
124
 
124
125
  1. **`bundler-source-vault` is NOT installed system-wide in `Dockerfile.test`.**
@@ -141,6 +142,12 @@ against something other than the code under test:
141
142
  4. **Bundler is pinned to the version users run, not the image's default gem.**
142
143
  The plugin machinery under test is Bundler's own, so a stale default silently
143
144
  tests different code.
145
+ 5. **dnf runs with stock settings — weak dependencies included.** On a real
146
+ Fedora machine `dnf install ruby` pulls the unbundled default-gem RPMs
147
+ (`rubygem-json`, `rubygem-psych`, …) through the ruby package's Recommends.
148
+ `--setopt=install_weak_deps=False` builds a ruby no user has — one where
149
+ `require "json"` raises — and that faulty install once failed every CLI
150
+ scenario in the suite.
144
151
 
145
152
  Fidelity is a property of the image, not of a script fragment: integration specs
146
153
  run only commands a user would actually type.
@@ -153,6 +160,6 @@ container.
153
160
 
154
161
  - `bundler` — NOT a dependency; the plugin always runs inside an existing Bundler process, and declaring it breaks gem activation under `bundle exec`'s restricted GEM_PATH
155
162
  - `command_kit` (~> 0.6) — runtime (CLI)
156
- - `json` (~> 2.0) runtime; backs the tarball vault's manifest. A default gem upstream, but distros package it separately, so it is declared. Because the shim loads gemvault off `$LOAD_PATH` without activating it, it has to put json's require paths on `$LOAD_PATH` itself — see `dependency_specs` and `entries` in `shim/gemvault_load_path.rb`.
163
+ - `json` NOT a dependency, though it backs the tarball vault's manifest. A default gem upstream; distros that unbundle it (Fedora's `rubygem-json`) still install it alongside ruby itself, and it stays requireable even under `bundle exec`'s restricted view. Declaring it would compile a native extension on every `gem install gemvault` for no gain.
157
164
  - `sqlite3` (~> 2.0) — NOT a runtime dependency; loaded lazily only to read a legacy SQLite (Dbvault) vault. Declared in the Gemfile for development/test.
158
165
  - `minitest`, `rspec`, `rake` — development
data/Dockerfile.test CHANGED
@@ -9,13 +9,20 @@
9
9
  # also the shape that broke `bundle exec` in issue #12.
10
10
  FROM docker.io/library/fedora:44
11
11
 
12
- # redhat-rpm-config carries the hardening specs Fedora's ruby-devel compiles
13
- # extensions against; without it every native gem fails in extconf. Any Fedora
14
- # machine that can build a gem has it.
15
- RUN dnf -y install --setopt=install_weak_deps=False \
16
- ruby ruby-devel rubygem-bundler \
17
- gcc make redhat-rpm-config sqlite-devel tar \
18
- && dnf clean all
12
+ # Stock dnf settings, weak dependencies included: on a real Fedora machine
13
+ # `dnf install ruby` pulls the unbundled default-gem RPMs (rubygem-json,
14
+ # rubygem-psych, rubygem-rdoc, ...) through the ruby package's Recommends.
15
+ # Passing --setopt=install_weak_deps=False builds a ruby no user has -- one
16
+ # where `require "json"` raises -- and that faulty install once failed every
17
+ # CLI scenario in the suite.
18
+ RUN dnf \
19
+ install \
20
+ -y \
21
+ @standard \
22
+ @development-tools \
23
+ @ruby \
24
+ sqlite3 \
25
+ && dnf clean all
19
26
 
20
27
  # Keep this in step with the `BUNDLED WITH` line the project's own lockfile
21
28
  # carries. The plugin machinery these specs exercise is Bundler's own, so a
@@ -27,19 +34,6 @@ RUN gem install --no-document bundler -v 4.0.17
27
34
  # migrates.
28
35
  RUN gem install --no-document sqlite3
29
36
 
30
- # Gem files for integration specs that install the plugin from a local gem
31
- # index at spec runtime, kept for offline use: command_kit and json are
32
- # gemvault's runtime dependencies, and `bundle plugin install` resolves them
33
- # against the Gemfile's sources with GEM_PATH already narrowed to the plugin
34
- # root, so an ambient copy does not satisfy it -- every declared dependency has
35
- # to be servable from the index. The bundler gem lets specs reinstall bundler
36
- # in place.
37
- RUN mkdir -p /opt/gems \
38
- && cd /opt/gems \
39
- && gem fetch command_kit -v 0.6.0 \
40
- && gem fetch json -v "~> 2.0" \
41
- && gem fetch bundler -v "$(ruby -rbundler -e 'print Bundler::VERSION')"
42
-
43
37
  # The tree arrives as a bind mount at /src rather than a COPY: the build context
44
38
  # lives on a filesystem whose extended-attribute listing podman's copier cannot
45
39
  # read. `rake spec:build` supplies both the mount and SOURCE_DIGEST.
data/Rakefile CHANGED
@@ -24,14 +24,24 @@ RuboCop::RakeTask.new
24
24
  require "gempilot/version_task"
25
25
  Gempilot::VersionTask.new
26
26
 
27
+ require "rubycritic/rake_task"
28
+ require "yaml"
29
+
30
+ # The task's default paths are FileList["."], passed straight to the CLI where
31
+ # they override .rubycritic.yml's paths -- and "." drags in references/, whose
32
+ # vendored checkouts flog cannot even read. Feed the yml's own list back in so
33
+ # the config file stays the single source of truth.
34
+ RubyCritic::RakeTask.new do |task|
35
+ task.paths = YAML.load_file(".rubycritic.yml").fetch("paths")
36
+ end
37
+
27
38
  require "digest"
28
39
  require_relative "spec/support/container_helper"
40
+ require_relative "spec/support/container_image"
29
41
 
30
- CACHED_IMAGE = ContainerHelper::CACHED_IMAGE
31
-
32
- # Dockerfile.test reads the tree through a bind mount, whose contents podman
33
- # does not fold into its layer cache key. Feeding this digest in as a build arg
34
- # is what makes `rake spec:build` notice that the source changed.
42
+ # What the image is built from. ContainerImage feeds this to podman as a build
43
+ # arg, because the bind mount the Dockerfile reads is invisible to podman's
44
+ # layer cache key.
35
45
  def image_source_digest
36
46
  fingerprints = FileList["lib/**/*", "exe/*", "gemvault.gemspec"]
37
47
  .select { |path| File.file?(path) }.sort
@@ -39,31 +49,19 @@ def image_source_digest
39
49
  Digest::SHA256.hexdigest(fingerprints.join("\n"))
40
50
  end
41
51
 
42
- def cached_image_exists?
43
- system("podman", "image", "exists", CACHED_IMAGE, out: File::NULL, err: File::NULL)
44
- end
45
-
46
- def build_cached_image
47
- sh "podman", "build", "--network=host", "-v", "#{__dir__}:/src:ro,z",
48
- "--build-arg", "SOURCE_DIGEST=#{image_source_digest}",
49
- "-t", CACHED_IMAGE, "-f", "Dockerfile.test", "."
50
- end
51
-
52
- def destroy_cached_image
53
- strays = `podman ps -aq --filter ancestor=#{CACHED_IMAGE}`.split
54
- sh "podman", "rm", "-f", *strays unless strays.empty?
55
- sh "podman", "rmi", CACHED_IMAGE
52
+ def cached_image
53
+ ContainerImage.new(name: ContainerHelper::CACHED_IMAGE, root: __dir__, digest: image_source_digest)
56
54
  end
57
55
 
58
56
  namespace :spec do
59
57
  desc "Build cached container image with gemvault pre-installed"
60
- task(:build) { build_cached_image }
58
+ task(:build) { cached_image.build }
61
59
 
62
60
  desc "Build the cached image unless it already exists"
63
- task(:setup) { build_cached_image unless cached_image_exists? }
61
+ task(:setup) { cached_image.ensure_built }
64
62
 
65
63
  desc "Remove test containers and the cached image"
66
- task(:teardown) { destroy_cached_image if cached_image_exists? }
64
+ task(:teardown) { cached_image.destroy }
67
65
  end
68
66
 
69
67
  directory "pkg" do
@@ -87,4 +85,4 @@ Rake::Task[:build].enhance ["shim:build"]
87
85
  Rake::Task[:release].enhance ["shim:release"]
88
86
  Rake::Task[:clobber].enhance ["spec:teardown"]
89
87
 
90
- task default: [:test, :spec, :rubocop]
88
+ task default: [:test, :spec, :rubocop, :rubycritic]
data/issues.rec CHANGED
@@ -756,4 +756,264 @@ Id: 15
756
756
  Updated: Wed, 29 Jul 2026 17:39:23 -0400
757
757
  Name: gemvault new fails on path/to/gemvault.gemv
758
758
  Description: I did it in /tmp, maybe that's why?
759
+ +
760
+ + RESOLUTION: /tmp was a red herring -- the trigger is the missing parent
761
+ + directory. `gemvault new path/to/gemvault.gemv` reached Tempfile.create with a
762
+ + nonexistent dirname and died with a raw Errno::ENOENT and a 25-line Ruby
763
+ + backtrace; with path/to already present the same command always worked.
764
+ +
765
+ + A vault is one file, so `gemvault new vendor/vendored.gemv` in a tree that has
766
+ + no vendor yet reads as an ordinary request rather than a mistake: the missing
767
+ + directories are now created, and the shallowest one created is reported
768
+ + ("Created directory vendor") so nothing appears out of nowhere. What cannot be
769
+ + papered over is raised as VaultDestination::Error and printed as one line with
770
+ + exit 1 -- a parent that is a file, or one that cannot be created (EACCES).
771
+ + SystemCallError from the vault write itself is caught the same way, so no path
772
+ + through `gemvault new` ends in a backtrace.
773
+ +
774
+ + Gemvault::VaultDestination owns the .gemv suffix defaulting, the occupied-path
775
+ + check and the parent creation; the command stays thin. Replicated first by
776
+ + spec/integration/cli/commands/new_spec.rb (real subprocess, real vault, real
777
+ + exit code -- 5 of its 9 examples failed against the old code, including one
778
+ + asserting the absence of Errno::ENOENT), with unit cover in
779
+ + spec/gemvault/vault_destination_spec.rb and spec/gemvault/cli/commands/new_spec.rb.
780
+ Status: closed
781
+
782
+ Id: 16
783
+ Updated: Wed, 29 Jul 2026 18:25:48 -0400
784
+ Name: Add jruby-ci workflow
785
+ Description: It should be already set up on the jruby-support branch. It just needs to be picked from it. DO NOT use the other code in that - it is very outdated
786
+ Status: open
787
+
788
+ Id: 17
789
+ Updated: Thu, 30 Jul 2026 00:07:42 +0000
790
+ Name: Integration CI job reddens on transient podman/crun failures
791
+ Description: Both `integration` jobs failed on master (run 30496142063) and on PR #17 with:
792
+ +
793
+ + ```
794
+ + error running container: from /usr/bin/crun creating container for
795
+ + [/bin/sh -c dnf -y install ...]: unknown version specified
796
+ + ```
797
+ +
798
+ + Nothing had changed between the last green run and the first red one: same runner
799
+ + image 20260720.247.2, same pre-installed podman, same fedora:44 blob and config
800
+ + digests, same SOURCE_DIGEST build arg, and the only commit in between touched
801
+ + issues.rec text. Rerunning the identical commit went green.
802
+ +
803
+ + `rake spec:build` had no retry, so one momentary container-runtime failure reddens
804
+ + the whole run -- and because the matrix cancels the sibling leg, a flake reads as a
805
+ + real break in the suite CLAUDE.md calls the first line of defense for CLI bugs.
806
+ +
807
+ + RESOLUTION: the image lifecycle moved out of the Rakefile into ContainerImage
808
+ + (spec/support/container_image.rb), which retries a failed build ATTEMPTS (3) times
809
+ + and announces each retry. Nothing in the exit status separates a transient runtime
810
+ + failure from a real Dockerfile error, so every failure is retried; a genuinely broken
811
+ + Dockerfile still fails, just after 3 tries, and fails fast because it never reaches
812
+ + the network. Commands run through an injected Shell so the retry behaviour is
813
+ + specified without podman (spec/container_image_spec.rb, spec/shell_spec.rb), and the
814
+ + loop was also confirmed against real podman with a Dockerfile that exits nonzero.
815
+ +
816
+ + RESOLUTION: the `jruby` job is picked into .github/workflows/ci.yml; none of the
817
+ + branch's Sequel/jdbc-sqlite3 code came with it. Master needs none of it -- the
818
+ + tarball format uses only rubygems' own tar tooling, so gemvault already runs on
819
+ + JRuby. Three of the branch job's details did not survive the pick: JAVA_OPTS
820
+ + --enable-native-access existed to silence a jdbc-sqlite3 warning under Java 25
821
+ + (no JDBC driver here, and the job pins Java 21), BUNDLE_FROZEN existed for a
822
+ + committed MRI-resolved lock (Gemfile.lock is gitignored on master), and the
823
+ + branch's matrix/podman edits to the MRI jobs are the outdated part.
824
+ +
825
+ + Two things the branch job would have hit on master, both found by running it:
826
+ + (1) `bundle install` died on the sqlite3 C extension, which cannot build on the
827
+ + JVM. sqlite3 is a dev-only dependency for the legacy Dbvault read path, so the
828
+ + Gemfile now scopes it `platform: %i[ruby windows]`; nothing under test/ touches
829
+ + sqlite3, which is why the job runs `rake test` only and leaves the sqlite3-backed
830
+ + specs to the MRI jobs. (2) `bundle install` then exhausted JRuby's default 500MB
831
+ + heap during resolution -- inherent to the Gemfile, not to sqlite3 -- so the job
832
+ + sets JRUBY_OPTS=-J-Xmx2048m, the remedy JRuby's own error message prescribes.
833
+ +
834
+ + Verified locally against JRuby 10.1.0.0 on Java 25 (installed for this; the box
835
+ + had none, and no install-jruby script existed -- one is now at
836
+ + ~/.local/bin/install-jruby): bundle install clean with sqlite3 absent, rake test
837
+ + 113 tests / 231 assertions / 0 failures. MRI unaffected: sqlite3 still resolves,
838
+ + rake test 113/0, spec:core 130 examples/0 failures, rubocop 111 files clean.
839
+ Status: closed
840
+
841
+ Id: 18
842
+ Updated: Thu, 30 Jul 2026 01:06:09 +0000
843
+ Name: Integration CI broken by runner image podman 5 / crun 1.14 mismatch
844
+ Description: Every `integration` job fails at the Dockerfile's dnf layer with:
845
+ +
846
+ + ```
847
+ + error running container: from /usr/bin/crun creating container for
848
+ + [/bin/sh -c dnf -y install ...]: unknown version specified
849
+ + ```
850
+ +
851
+ + This looked like issue #17's flake and is not. Correlating the runner image
852
+ + against the outcome settles it:
853
+ +
854
+ + | runner image | podman | outcome |
855
+ + |---------------------|--------|-------------|
856
+ + | 20260720.247 (old) | 4.9.3 | pass |
857
+ + | 20260726.254 (new) | 5.8.4 | crun error |
858
+ +
859
+ + Every genuine failure is on 20260726.254; every job on 20260720.247 either passed
860
+ + or was cancelled by fail-fast (which is what made two of them look like failures).
861
+ + GitHub rolls the image out gradually, so jobs land on either one -- hence reruns
862
+ + sometimes helping and sometimes not, and hence integration CI going permanently
863
+ + red as the rollout completes.
864
+ +
865
+ + The new image bumped podman 4.9.3 -> 5.8.4 and left Ubuntu 24.04's crun 1.14 in
866
+ + place. podman 5 writes an OCI config declaring ociVersion 1.2.0; crun 1.14
867
+ + rejects any version not prefixed "1.0" with exactly "unknown version
868
+ + specified". A dev box running podman 5.8.4 with crun 1.28 builds and runs the
869
+ + same image fine, which is the control.
870
+ +
871
+ + Nothing about this is intermittent, so #17's retry cannot help: three attempts
872
+ + on the same runner fail three times.
873
+ +
874
+ + RESOLUTION: the integration job installs a pinned crun 1.28 (newer than any
875
+ + noble package) and names it in GEMVAULT_PODMAN_RUNTIME; Podman.command inserts
876
+ + `--runtime` ahead of the subcommand when that variable is set, so both
877
+ + ContainerImage's build and ContainerHelper's container runs go through it. Unset
878
+ + -- every dev machine, and any runner whose podman and crun agree -- podman
879
+ + chooses as before. The job also prints podman, crun and the chosen runtime, so
880
+ + the next mismatch names itself instead of being mistaken for a flake, and
881
+ + fail-fast is off because one leg cancelling the other hid which had broken.
882
+ +
883
+ + Verified locally both ways: with the variable set to a downloaded crun 1.28,
884
+ + spec:build plus 135 integration examples pass; with it unset, the same. A
885
+ + config-file override (`[engine.runtimes]`) was tried first and did not take --
886
+ + podman kept /usr/bin/crun -- which is why this uses the explicit flag.
887
+ Status: closed
888
+
889
+ Id: 19
890
+ Updated: Fri, 31 Jul 2026 09:18:45 -0400
891
+ Name: Deps in Dockerfile.test are not dynamically inferred
892
+ Description: Bundler version, command kit version, and all other deps that are being installed need to be inferred from this project via bundler info, and should be passed in as ENV variables. Hardcoding is not acceptable
893
+ Status: open
894
+
895
+ Id: 20
896
+ Updated: Fri, 31 Jul 2026 13:42:28 -0400
897
+ Name: Received the following error during bundle install:
898
+ Description: [dev@rstore]/workspace% bundle install
899
+ + Bundler 4.0.14 is running, but your lockfile was generated with 4.0.17. Installing Bundler 4.0.17 and restarting using that version.
900
+ + Source locally installed gems is ignoring #<Bundler::StubSpecification name=io-event version=1.19.4 platform=ruby> because it is missing extensions
901
+ + Fetching gem metadata from https://rubygems.org/.
902
+ + Source rubygems repository https://rubygems.org/ is ignoring #<Bundler::StubSpecification name=io-event version=1.19.4 platform=ruby> because it is missing extensions
903
+ + Installing bundler 4.0.17
904
+ + Ignoring io-event-1.19.4 because its extensions are not built. Try: gem pristine io-event --version 1.19.4
905
+ + Fetching gem metadata from https://rubygems.org/.......
906
+ + Source rubygems repository https://rubygems.org/, cached gems or installed locally is ignoring #<Bundler::StubSpecification name=io-event version=1.19.4 platform=ruby> because it is missing extensions
907
+ + Resolving dependencies...
908
+ + Fetching json 2.21.2
909
+ + Installing json 2.21.2 with native extensions
910
+ + Fetching gemvault 0.2.2
911
+ + Installing gemvault 0.2.2
912
+ + Fetching bundler-source-vault 0.2.2
913
+ + Installing bundler-source-vault 0.2.2
914
+ + The following plugin paths don't exist: /Users/davidgillis/.rbenv/versions/4.0.1/lib/ruby/gems/4.0.0/gems/bundler-source-vault-0.2.1/..
915
+ +
916
+ + This can happen if the plugin was installed with a different version of Ruby that has since been uninstalled.
917
+ +
918
+ + If you would like to reinstall the plugin, run:
919
+ +
920
+ + bundler plugin uninstall bundler-source-vault && bundler plugin install bundler-source-vault
921
+ +
922
+ + Continuing without installing plugin bundler-source-vault.
923
+ + /workspace/vendor/ruby/4.0.0/gems/bundler-4.0.17/lib/bundler/source_list.rb:59:in 'Bundler::SourceList#add_plugin_source': undefined method 'new' for nil (NoMethodError)
924
+ +
925
+ + add_source_to_list Plugin.source(source).new(options), @plugin_sources
926
+ + ^^^^
927
+ + from /workspace/vendor/ruby/4.0.0/gems/bundler-4.0.17/lib/bundler/dsl.rb:135:in 'Bundler::Dsl#source'
928
+ + from /workspace/Gemfile:20:in 'block in Bundler::Dsl#eval_gemfile'
929
+ + from /workspace/vendor/ruby/4.0.0/gems/bundler-4.0.17/lib/bundler/dsl.rb:49:in 'BasicObject#instance_eval'
930
+ + from /workspace/vendor/ruby/4.0.0/gems/bundler-4.0.17/lib/bundler/dsl.rb:49:in 'block in Bundler::Dsl#eval_gemfile'
931
+ + from /workspace/vendor/ruby/4.0.0/gems/bundler-4.0.17/lib/bundler/dsl.rb:330:in 'Bundler::Dsl#with_gemfile'
932
+ + from /workspace/vendor/ruby/4.0.0/gems/bundler-4.0.17/lib/bundler/dsl.rb:47:in 'Bundler::Dsl#eval_gemfile'
933
+ + from /workspace/vendor/ruby/4.0.0/gems/bundler-4.0.17/lib/bundler/dsl.rb:13:in 'Bundler::Dsl.evaluate'
934
+ + from /workspace/vendor/ruby/4.0.0/gems/bundler-4.0.17/lib/bundler/definition.rb:40:in 'Bundler::Definition.build'
935
+ + from /workspace/vendor/ruby/4.0.0/gems/bundler-4.0.17/lib/bundler.rb:235:in 'Bundler.definition'
936
+ + from /workspace/vendor/ruby/4.0.0/gems/bundler-4.0.17/lib/bundler/env.rb:36:in 'Bundler::Env.report'
937
+ + from /workspace/vendor/ruby/4.0.0/gems/bundler-4.0.17/lib/bundler/friendly_errors.rb:71:in 'Bundler::FriendlyErrors.request_issue_report_for'
938
+ + from /workspace/vendor/ruby/4.0.0/gems/bundler-4.0.17/lib/bundler/friendly_errors.rb:50:in 'Bundler::FriendlyErrors.log_error'
939
+ + from /workspace/vendor/ruby/4.0.0/gems/bundler-4.0.17/lib/bundler/friendly_errors.rb:124:in 'Bundler.with_friendly_errors'
940
+ + from /workspace/vendor/ruby/4.0.0/gems/bundler-4.0.17/exe/bundle:20:in '<top (required)>'
941
+ + from /usr/local/share/ruby/site_ruby/rubygems.rb:305:in 'Kernel#load'
942
+ + from /usr/local/share/ruby/site_ruby/rubygems.rb:305:in 'Gem.activate_and_load_bin_path'
943
+ + from /usr/local/bin/bundle:25:in '<main>'
944
+ + /workspace/vendor/ruby/4.0.0/gems/bundler-4.0.17/lib/bundler/source_list.rb:59:in 'Bundler::SourceList#add_plugin_source': undefined method 'new' for nil (NoMethodError)
945
+ +
946
+ + add_source_to_list Plugin.source(source).new(options), @plugin_sources
947
+ + ^^^^
948
+ + from /workspace/vendor/ruby/4.0.0/gems/bundler-4.0.17/lib/bundler/dsl.rb:135:in 'Bundler::Dsl#source'
949
+ + from /workspace/Gemfile:20:in 'block in Bundler::Dsl#eval_gemfile'
950
+ + from /workspace/vendor/ruby/4.0.0/gems/bundler-4.0.17/lib/bundler/dsl.rb:49:in 'BasicObject#instance_eval'
951
+ + from /workspace/vendor/ruby/4.0.0/gems/bundler-4.0.17/lib/bundler/dsl.rb:49:in 'block in Bundler::Dsl#eval_gemfile'
952
+ + from /workspace/vendor/ruby/4.0.0/gems/bundler-4.0.17/lib/bundler/dsl.rb:330:in 'Bundler::Dsl#with_gemfile'
953
+ + from /workspace/vendor/ruby/4.0.0/gems/bundler-4.0.17/lib/bundler/dsl.rb:47:in 'Bundler::Dsl#eval_gemfile'
954
+ + from /workspace/vendor/ruby/4.0.0/gems/bundler-4.0.17/lib/bundler/dsl.rb:13:in 'Bundler::Dsl.evaluate'
955
+ + from /workspace/vendor/ruby/4.0.0/gems/bundler-4.0.17/lib/bundler/definition.rb:40:in 'Bundler::Definition.build'
956
+ + from /workspace/vendor/ruby/4.0.0/gems/bundler-4.0.17/lib/bundler.rb:235:in 'Bundler.definition'
957
+ + from /workspace/vendor/ruby/4.0.0/gems/bundler-4.0.17/lib/bundler/cli/install.rb:45:in 'Bundler::CLI::Install#run'
958
+ + from /workspace/vendor/ruby/4.0.0/gems/bundler-4.0.17/lib/bundler/cli.rb:296:in 'block in Bundler::CLI#install'
959
+ + from /workspace/vendor/ruby/4.0.0/gems/bundler-4.0.17/lib/bundler/settings.rb:145:in 'Bundler::Settings#temporary'
960
+ + from /workspace/vendor/ruby/4.0.0/gems/bundler-4.0.17/lib/bundler/cli.rb:295:in 'Bundler::CLI#install'
961
+ + from /workspace/vendor/ruby/4.0.0/gems/bundler-4.0.17/lib/bundler/vendor/thor/lib/thor/command.rb:28:in 'Bundler::Thor::Command#run'
962
+ + from /workspace/vendor/ruby/4.0.0/gems/bundler-4.0.17/lib/bundler/vendor/thor/lib/thor/invocation.rb:127:in 'Bundler::Thor::Invocation#invoke_command'
963
+ + from /workspace/vendor/ruby/4.0.0/gems/bundler-4.0.17/lib/bundler/vendor/thor/lib/thor.rb:538:in 'Bundler::Thor.dispatch'
964
+ + from /workspace/vendor/ruby/4.0.0/gems/bundler-4.0.17/lib/bundler/cli.rb:35:in 'Bundler::CLI.dispatch'
965
+ + from /workspace/vendor/ruby/4.0.0/gems/bundler-4.0.17/lib/bundler/vendor/thor/lib/thor/base.rb:584:in 'Bundler::Thor::Base::ClassMethods#start'
966
+ + from /workspace/vendor/ruby/4.0.0/gems/bundler-4.0.17/lib/bundler/cli.rb:29:in 'Bundler::CLI.start'
967
+ + from /workspace/vendor/ruby/4.0.0/gems/bundler-4.0.17/exe/bundle:28:in 'block in <top (required)>'
968
+ + from /workspace/vendor/ruby/4.0.0/gems/bundler-4.0.17/lib/bundler/friendly_errors.rb:118:in 'Bundler.with_friendly_errors'
969
+ + from /workspace/vendor/ruby/4.0.0/gems/bundler-4.0.17/exe/bundle:20:in '<top (required)>'
970
+ + from /usr/local/share/ruby/site_ruby/rubygems.rb:305:in 'Kernel#load'
971
+ + from /usr/local/share/ruby/site_ruby/rubygems.rb:305:in 'Gem.activate_and_load_bin_path'
972
+ + from /usr/local/bin/bundle:25:in '<main>'
973
+ +
974
+ + Though I will add that gemvault doctor followed by bundle install fixed the issue
975
+ Status: open
976
+
977
+ Id: 21
978
+ Updated: Fri, 31 Jul 2026 13:54:09 -0400
979
+ Name: bundle info fails when gem is in vault
980
+ Description: bundle info jarpack
981
+ + Source locally installed gems is ignoring #<Bundler::StubSpecification name=json version=2.21.2 platform=ruby> because it is missing extensions
982
+ + Could not find gem 'jarpack'.
983
+ + Did you mean 'jarpack'?
984
+ +
985
+ +
986
+ + bundle info jarpack
987
+ Status: open
988
+
989
+ Id: 22
990
+ Updated: Mon, 10 Aug 2026 10:03:35 -0400
991
+ Name: Add github actions disclaimer/instructions
992
+ Description: bundler-cache: true on the ruby/setup-ruby action will not work correctly:
993
+ +
994
+ + - uses: ruby/setup-ruby@v1
995
+ + with:
996
+ + ruby-version: ${{ matrix.ruby }}
997
+ + # bundler-cache runs `bundle lock`, which cannot parse the vault source until
998
+ + # `bundle install` has fetched its plugin; install and cache manually instead.
999
+ + bundler-cache: false
1000
+ + - name: Cache gems
1001
+ + uses: actions/cache@v6
1002
+ + with:
1003
+ + path: |
1004
+ + vendor/bundle
1005
+ + .bundle/plugin
1006
+ + key: ${{ runner.os }}-gems-${{ matrix.ruby }}-${{ hashFiles('Gemfile', '*.gemspec', 'vendor/vendored.gemv') }}
1007
+ + restore-keys: |
1008
+ + ${{ runner.os }}-gems-${{ matrix.ruby }}-
1009
+ +
1010
+ + - name: Install gems
1011
+ + run: | # confine gems to vendor/bundle so the cache never shadows vendor/vendored.gemv
1012
+ + bundle config set --local path vendor/bundle
1013
+ + bundle install --jobs 4 --retry 3
1014
+ Updated: Mon, 10 Aug 2026 10:14:51 -0400
1015
+ Name: Gemvault spams warnings on bundle install
1016
+ Description: /workspace/.bundle/plugin/gems/bundler-source-vault-0.2.2/gemvault_load_path.rb:31: warning: already initialized constant BundlerSourceVault::GemvaultLoadPath::GEM
1017
+ + /workspace/.bundle/plugin/gems/bundler-source-vault-0.2.2/gemvault_load_path.rb:32: warning: already initialized constant BundlerSourceVault::GemvaultLoadPath::SHIM
1018
+ + /workspace/.bundle/plugin/gems/bundler-source-vault-0.2.2/gemvault_load_path.rb:33: warning: already initialized constant BundlerSourceVault::GemvaultLoadPath::VAULT_SOURCE
759
1019
  Status: open
@@ -1,5 +1,6 @@
1
- require "pathname"
2
1
  require_relative "../command"
2
+ require_relative "../destination"
3
+ require_relative "../../vault_destination"
3
4
 
4
5
  module Gemvault
5
6
  class CLI
@@ -13,16 +14,23 @@ module Gemvault
13
14
  desc: "Vault name (auto-appends .gemv)"
14
15
 
15
16
  def run(name)
16
- path = Pathname(name.end_with?(".gemv") ? name : "#{name}.gemv")
17
-
18
- if path.exist?
19
- print_error("#{path} already exists")
17
+ begin
18
+ destination = build_destination(name)
19
+ destination.refuse_existing
20
+ destination.create_parents
21
+ Vault.create(destination.path)
22
+ puts "Created #{destination.path}"
23
+ rescue VaultDestination::Error, Vault::Error, SystemCallError => e
24
+ print_error(e.message)
20
25
  exit(1)
21
26
  end
27
+ end
28
+
29
+ private
22
30
 
23
- vault = Vault.new(path, create: true)
24
- vault.close
25
- puts "Created #{path}"
31
+ def build_destination(name)
32
+ vault_destination = VaultDestination.new(name)
33
+ Destination.new(vault_destination, stdout:)
26
34
  end
27
35
  end
28
36
  end
@@ -0,0 +1,29 @@
1
+ require "forwardable"
2
+
3
+ module Gemvault
4
+ class CLI
5
+ ##
6
+ # A VaultDestination as the CLI presents it: the same operations, with the
7
+ # directories they grow narrated on +stdout+ so nothing appears on disk
8
+ # unannounced. What the destination refuses still surfaces as
9
+ # <tt>VaultDestination::Error</tt> for the command to report.
10
+ class Destination
11
+ extend Forwardable
12
+
13
+ def_delegators :@destination, :path, :refuse_existing
14
+
15
+ def initialize(destination, stdout:)
16
+ @destination = destination
17
+ @stdout = stdout
18
+ end
19
+
20
+ # Creates the destination's missing parent directories, reporting the
21
+ # shallowest one created so the user learns where the tree grew.
22
+ def create_parents
23
+ missing = @destination.missing_directory
24
+ @destination.create_parents
25
+ @stdout.puts "Created directory #{missing}" if missing
26
+ end
27
+ end
28
+ end
29
+ end
@@ -30,6 +30,11 @@ module Gemvault
30
30
  :spec_from_blob, :with_gem_file, :size, :close, :closed?,
31
31
  :path, :format_version
32
32
 
33
+ # Creates an empty vault file at +path+, leaving it closed.
34
+ def self.create(path)
35
+ new(path, create: true).close
36
+ end
37
+
33
38
  def self.assert_readable!(version:, path:)
34
39
  return if version.between?(MIN_READABLE_FORMAT, CURRENT_FORMAT)
35
40
 
@@ -0,0 +1,70 @@
1
+ require "pathname"
2
+
3
+ module Gemvault
4
+ ##
5
+ # Where a new vault is about to be written: the <tt>.gemv</tt> path itself,
6
+ # and the directories that have to exist before anything can be written to it.
7
+ #
8
+ # A vault is one file, so <tt>gemvault new vendor/vendored.gemv</tt> in a tree
9
+ # that has no <tt>vendor</tt> yet reads as an ordinary request rather than a
10
+ # mistake, and the missing directories are created. What cannot be papered
11
+ # over -- something already occupying the path, a parent that is a file, a
12
+ # parent that cannot be created -- is raised as Error, so the CLI reports one
13
+ # line instead of a Ruby backtrace.
14
+ class VaultDestination
15
+ SUFFIX = ".gemv".freeze
16
+
17
+ ##
18
+ # Raised when the path cannot hold a new vault.
19
+ class Error < StandardError; end
20
+
21
+ # The vault's path, with SUFFIX appended if the name lacked it.
22
+ attr_reader :path
23
+
24
+ def initialize(name)
25
+ locator = suffixed(name.to_s)
26
+ @path = Pathname(locator)
27
+ end
28
+
29
+ # Raises Error when something already occupies the vault's path.
30
+ def refuse_existing
31
+ raise Error, "#{@path} already exists" if @path.exist?
32
+ end
33
+
34
+ # :call-seq:
35
+ # missing_directory -> Pathname or nil
36
+ #
37
+ # The shallowest directory create_parents would have to make -- creating it
38
+ # brings every deeper one with it -- or +nil+ when the parent already
39
+ # exists.
40
+ def missing_directory
41
+ @path.dirname.ascend.reject(&:exist?).last
42
+ end
43
+
44
+ # Creates the vault's missing parent directories.
45
+ def create_parents
46
+ parent = @path.dirname
47
+ return if parent.directory?
48
+
49
+ raise Error, "#{parent} is not a directory" if parent.exist?
50
+
51
+ make(parent)
52
+ end
53
+
54
+ private
55
+
56
+ def suffixed(locator)
57
+ return locator if locator.end_with?(SUFFIX)
58
+
59
+ "#{locator}#{SUFFIX}"
60
+ end
61
+
62
+ def make(parent)
63
+ begin
64
+ parent.mkpath
65
+ rescue SystemCallError => e
66
+ raise Error, "cannot create directory #{parent}: #{e.message}"
67
+ end
68
+ end
69
+ end
70
+ end
@@ -1,3 +1,3 @@
1
1
  module Gemvault
2
- VERSION = "0.2.2".freeze
2
+ VERSION = "0.2.3".freeze
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gemvault
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.2
4
+ version: 0.2.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Gillis
@@ -23,20 +23,6 @@ dependencies:
23
23
  - - "~>"
24
24
  - !ruby/object:Gem::Version
25
25
  version: '0.6'
26
- - !ruby/object:Gem::Dependency
27
- name: json
28
- requirement: !ruby/object:Gem::Requirement
29
- requirements:
30
- - - "~>"
31
- - !ruby/object:Gem::Version
32
- version: '2.0'
33
- type: :runtime
34
- prerelease: false
35
- version_requirements: !ruby/object:Gem::Requirement
36
- requirements:
37
- - - "~>"
38
- - !ruby/object:Gem::Version
39
- version: '2.0'
40
26
  description: Portable .gemv archives for bundling and distributing multiple gems as
41
27
  a single file, read directly by Bundler and RubyGems
42
28
  email:
@@ -50,6 +36,7 @@ files:
50
36
  - ".rspec"
51
37
  - ".rubocop.yml"
52
38
  - ".ruby-version"
39
+ - ".rubycritic.yml"
53
40
  - CHANGELOG.md
54
41
  - CLAUDE.md
55
42
  - Dockerfile.test
@@ -78,6 +65,7 @@ files:
78
65
  - lib/gemvault/cli/commands/new.rb
79
66
  - lib/gemvault/cli/commands/remove.rb
80
67
  - lib/gemvault/cli/commands/upgrade.rb
68
+ - lib/gemvault/cli/destination.rb
81
69
  - lib/gemvault/dbvault.rb
82
70
  - lib/gemvault/deprecation.rb
83
71
  - lib/gemvault/gem_entry.rb
@@ -90,6 +78,7 @@ files:
90
78
  - lib/gemvault/tarball.rb
91
79
  - lib/gemvault/tarvault.rb
92
80
  - lib/gemvault/vault.rb
81
+ - lib/gemvault/vault_destination.rb
93
82
  - lib/gemvault/vault_path.rb
94
83
  - lib/gemvault/vault_session.rb
95
84
  - lib/gemvault/vault_upgrade.rb
@@ -119,7 +108,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
119
108
  - !ruby/object:Gem::Version
120
109
  version: '0'
121
110
  requirements: []
122
- rubygems_version: 4.0.17
111
+ rubygems_version: 4.0.18
123
112
  specification_version: 4
124
113
  summary: Multi-gem portable archives — a gem server in a file
125
114
  test_files: []