gemvault 0.2.5 → 0.2.6
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 +23 -0
- data/README.md +5 -1
- data/issues.rec +140 -4
- data/lib/gemvault/bundler_gemfile.rb +63 -0
- data/lib/gemvault/bundler_plugin_index.rb +58 -0
- data/lib/gemvault/bundler_plugin_root.rb +59 -0
- data/lib/gemvault/cli/commands/doctor.rb +90 -2
- data/lib/gemvault/version.rb +1 -1
- metadata +4 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6c8bf335258cabfbfbe016fe547e1302da87c37060d1523cae394e941bd493dd
|
|
4
|
+
data.tar.gz: f4a027c561cb8edf619f777989654d657bae70d741106ba8730b540b38de7f2f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9f3691b960faaa2d6ab493d18363091691ce3ac106a23d8859a052e946fd6aaa78178282da8269823e07c21cf52cb5ba5e9e262c51767aea19d805b620ee6287
|
|
7
|
+
data.tar.gz: 99feeb31a5af138c4da80b61b3fe3853cd545044954b895e202d57a5f7c2817b3cb59701f752140efbce75c839120f4b6bcf0a5871a806a94cc80cc6d12670ed
|
data/CHANGELOG.md
CHANGED
|
@@ -33,6 +33,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
33
33
|
plugin.)` — Bundler reinstalls the gem correctly but validates the ghost's
|
|
34
34
|
dead path — and nothing project-local can recover, because the wreck lives
|
|
35
35
|
in the ambient gem home (issue #23).
|
|
36
|
+
- `gemvault doctor` now works in a project whose Gemfile is inline
|
|
37
|
+
(`bundler/inline`). Such a script keeps its plugin index in
|
|
38
|
+
`<project>/.bundle/plugin`, a root bundler only consults mid-script, so
|
|
39
|
+
doctor's uninstall silently repaired nothing and the `bundle install` that
|
|
40
|
+
followed dumped bundler's entire usage screen. Doctor now reaches the
|
|
41
|
+
project's index the way `bundler/inline` does and, with no Gemfile to
|
|
42
|
+
reinstall from, says so and exits 0 — the inline gemfile reinstalls the
|
|
43
|
+
plugin the next time the script runs (issue #14). Run outside any project,
|
|
44
|
+
doctor no longer claims to have cleared a project index it never touched.
|
|
45
|
+
- `gemvault doctor` no longer strands a machine without the plugin. Its
|
|
46
|
+
closing `bundle install` does more than reinstall the plugin, and when that
|
|
47
|
+
extra work failed (a Ruby version pin, an unresolvable gem) the already-run
|
|
48
|
+
uninstall had cleared the plugin index for nothing — strictly worse than
|
|
49
|
+
the wreck doctor was asked to repair. The index is now snapshotted before
|
|
50
|
+
the uninstall and restored when the reinstall never happened, `bundle
|
|
51
|
+
install` runs as a child rather than replacing the process, and every
|
|
52
|
+
failure is one line on stderr and exit 1 instead of a backtrace
|
|
53
|
+
(issues #27, #24).
|
|
54
|
+
- The shim no longer spams `already initialized constant` warnings when
|
|
55
|
+
bundler evaluates two installed copies of it in one process — an upgrade
|
|
56
|
+
resolving a newer bundler-source-vault while the plugin index still names
|
|
57
|
+
the old one. The first copy wins, matching the guard plugins.rb already
|
|
58
|
+
applies to the vault source class (issue #26).
|
|
36
59
|
- `bundle plugin install bundler-source-vault` no longer dies with
|
|
37
60
|
`LoadError: cannot load such file -- bundler/plugin/vault_source`. The shim's
|
|
38
61
|
`plugins.rb` now derives the gem root from its own installed location (local
|
data/README.md
CHANGED
|
@@ -98,7 +98,11 @@ To recover, update the Gemfile to point at the new path and run:
|
|
|
98
98
|
gemvault doctor
|
|
99
99
|
```
|
|
100
100
|
|
|
101
|
-
`doctor` removes any ghost installation records, clears the broken entry from bundler's plugin index (`bundle plugin uninstall bundler-source-vault`) and then re-runs `bundle install`, which reinstalls the plugin against whatever the current Gemfile declares. Run it from your project directory. If a ghost record sits in a root-owned gem home, doctor says so on one line — re-run it with permissions for that gem home (e.g. `sudo gemvault doctor`).
|
|
101
|
+
`doctor` removes any ghost installation records, clears the broken entry from bundler's plugin index (`bundle plugin uninstall bundler-source-vault`) and then, when a Gemfile exists, re-runs `bundle install`, which reinstalls the plugin against whatever the current Gemfile declares. Run it from your project directory. If a ghost record sits in a root-owned gem home, doctor says so on one line — re-run it with permissions for that gem home (e.g. `sudo gemvault doctor`).
|
|
102
|
+
|
|
103
|
+
A project whose Gemfile is inline (`require "bundler/inline"`) works too. Such a script keeps its plugin index in `<project>/.bundle/plugin`, a root bundler only consults while the script runs; doctor reaches it the same way the script did. With no Gemfile on disk there is nothing to reinstall from, so doctor clears the entry, says so, and exits 0 — the inline gemfile reinstalls the plugin the next time the script runs. Run outside any project, doctor works against bundler's global index and points you back at the project directory for the reinstall.
|
|
104
|
+
|
|
105
|
+
The repair is transactional. `bundle install` does more than reinstall the plugin, and that extra work can fail on its own — a Ruby version pin, an unresolvable gem. When it does, doctor says so in one line and exits 1; and if the failure struck before the plugin was reinstalled, doctor puts the plugin index back the way it found it. A failed run never leaves the machine with less than it started with — fix the install error and re-run.
|
|
102
106
|
|
|
103
107
|
The published `bundler-source-vault` gem installed from rubygems.org is immune to this: it lives in a bundler-managed directory that does not move.
|
|
104
108
|
|
data/issues.rec
CHANGED
|
@@ -750,7 +750,42 @@ Id: 14
|
|
|
750
750
|
Updated: Wed, 29 Jul 2026 16:41:11 -0400
|
|
751
751
|
Name: gemvault doctor fails when bundler/inline is used
|
|
752
752
|
Description: Loudly fails with 2 errors, one of which is "can't find gemfile"
|
|
753
|
-
|
|
753
|
+
+
|
|
754
|
+
+ RESOLUTION: both errors reproduced in podman, and doctor turned out to be
|
|
755
|
+
+ broken twice over rather than once.
|
|
756
|
+
+
|
|
757
|
+
+ (1) "Plugin bundler-source-vault is not installed". bundler/inline sets
|
|
758
|
+
+ BUNDLE_GEMFILE to a bare "Gemfile" for the life of the script (bundler
|
|
759
|
+
+ inline.rb:53), so Bundler.root is the script's working directory and the plugin
|
|
760
|
+
+ index is written to <project>/.bundle/plugin. A later `gemvault doctor` sets no
|
|
761
|
+
+ such variable and finds no Gemfile on disk, so Bundler::Plugin.root --
|
|
762
|
+
+ local_root only when SharedHelpers.in_bundle?, global_root otherwise
|
|
763
|
+
+ (plugin.rb:162) -- falls back to ~/.bundle/plugin. The uninstall therefore
|
|
764
|
+
+ looked in the wrong root and repaired nothing, silently.
|
|
765
|
+
+
|
|
766
|
+
+ (2) "Could not locate Gemfile". doctor then exec'd `bundle install`, which with
|
|
767
|
+
+ no Gemfile prints its entire usage screen and exits 10 -- reading as gemvault
|
|
768
|
+
+ itself failing, after the repair it was asked for had already not happened.
|
|
769
|
+
+
|
|
770
|
+
+ doctor now runs the uninstall with BUNDLE_GEMFILE="Gemfile" when the project
|
|
771
|
+
+ owns a plugin root Bundler would ignore, which is bundler/inline's own trick and
|
|
772
|
+
+ needs no file to exist; verified against the real thing (plain uninstall says
|
|
773
|
+
+ "not installed", the same command with BUNDLE_GEMFILE set says "Uninstalled
|
|
774
|
+
+ plugin bundler-source-vault" and empties the index). When no Gemfile exists it
|
|
775
|
+
+ skips the reinstall and says so, exiting 0, because clearing the entry is the
|
|
776
|
+
+ repair and only the reinstall is unavailable -- an inline gemfile reinstalls the
|
|
777
|
+
+ plugin the next time the script runs.
|
|
778
|
+
+
|
|
779
|
+
+ Gemvault::BundlerGemfile mirrors SharedHelpers#find_gemfile and
|
|
780
|
+
+ Gemvault::BundlerPluginRoot mirrors Plugin.root, both reimplemented rather than
|
|
781
|
+
+ delegated because bundler is deliberately not a dependency and doctor runs
|
|
782
|
+
+ outside any Bundler process. Replicated first by
|
|
783
|
+
+ spec/integration/cli/commands/doctor_inline_spec.rb -- all 8 examples failed
|
|
784
|
+
+ against the old code -- with unit cover in spec/gemvault/bundler_gemfile_spec.rb,
|
|
785
|
+
+ spec/gemvault/bundler_plugin_root_spec.rb and the extended doctor_spec.rb. The
|
|
786
|
+
+ inline support now runs its script from the project directory, as a real user
|
|
787
|
+
+ does; that is what puts the plugin root where the report describes.
|
|
788
|
+
Status: closed
|
|
754
789
|
|
|
755
790
|
Id: 15
|
|
756
791
|
Updated: Wed, 29 Jul 2026 17:39:23 -0400
|
|
@@ -783,7 +818,11 @@ Id: 16
|
|
|
783
818
|
Updated: Wed, 29 Jul 2026 18:25:48 -0400
|
|
784
819
|
Name: Add jruby-ci workflow
|
|
785
820
|
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
|
-
|
|
821
|
+
+
|
|
822
|
+
+ RESOLUTION: shipped in commit 635ba41 ("ci: add JRuby test job") -- the jruby
|
|
823
|
+
+ job in .github/workflows/ci.yml runs the minitest suite on jruby-10.1.0.0 and
|
|
824
|
+
+ is green on CI.
|
|
825
|
+
Status: closed
|
|
787
826
|
|
|
788
827
|
Id: 17
|
|
789
828
|
Updated: Thu, 30 Jul 2026 00:07:42 +0000
|
|
@@ -972,7 +1011,15 @@ Description: [dev@rstore]/workspace% bundle install
|
|
|
972
1011
|
+ from /usr/local/bin/bundle:25:in '<main>'
|
|
973
1012
|
+
|
|
974
1013
|
+ Though I will add that gemvault doctor followed by bundle install fixed the issue
|
|
975
|
-
|
|
1014
|
+
+
|
|
1015
|
+
+ RESOLUTION: the broken-plugin-path wreck, recovered exactly as reported --
|
|
1016
|
+
+ gemvault doctor then bundle install. The plugin index entry pointed into a
|
|
1017
|
+
+ reinstalled rbenv ruby's gem home; bundler does not revalidate stored plugin
|
|
1018
|
+
+ paths, skips loading the plugin, and crashes in SourceList#add_plugin_source,
|
|
1019
|
+
+ upstream behavior README documents under "A broken plugin path". doctor is
|
|
1020
|
+
+ the repair. Hinting at doctor from the failure site was considered and
|
|
1021
|
+
+ declined: the crash happens in bundler frames before any gemvault code loads.
|
|
1022
|
+
Status: closed
|
|
976
1023
|
|
|
977
1024
|
Id: 21
|
|
978
1025
|
Updated: Fri, 31 Jul 2026 13:54:09 -0400
|
|
@@ -1011,12 +1058,27 @@ Description: bundler-cache: true on the ruby/setup-ruby action will not work cor
|
|
|
1011
1058
|
+ run: | # confine gems to vendor/bundle so the cache never shadows vendor/vendored.gemv
|
|
1012
1059
|
+ bundle config set --local path vendor/bundle
|
|
1013
1060
|
+ bundle install --jobs 4 --retry 3
|
|
1061
|
+
Status: open
|
|
1062
|
+
|
|
1063
|
+
Id: 26
|
|
1014
1064
|
Updated: Mon, 10 Aug 2026 10:14:51 -0400
|
|
1015
1065
|
Name: Gemvault spams warnings on bundle install
|
|
1016
1066
|
Description: /workspace/.bundle/plugin/gems/bundler-source-vault-0.2.2/gemvault_load_path.rb:31: warning: already initialized constant BundlerSourceVault::GemvaultLoadPath::GEM
|
|
1017
1067
|
+ /workspace/.bundle/plugin/gems/bundler-source-vault-0.2.2/gemvault_load_path.rb:32: warning: already initialized constant BundlerSourceVault::GemvaultLoadPath::SHIM
|
|
1018
1068
|
+ /workspace/.bundle/plugin/gems/bundler-source-vault-0.2.2/gemvault_load_path.rb:33: warning: already initialized constant BundlerSourceVault::GemvaultLoadPath::VAULT_SOURCE
|
|
1019
|
-
|
|
1069
|
+
+
|
|
1070
|
+
+ RESOLUTION: bundler can evaluate two installed copies of the shim in one
|
|
1071
|
+
+ process -- an upgrade resolving a newer bundler-source-vault while the index
|
|
1072
|
+
+ still names the old one, the dual-version plugin root the failure report
|
|
1073
|
+
+ also recorded as issue #29 -- and require_relative cannot deduplicate
|
|
1074
|
+
+ across paths, so the second copy reopened GemvaultLoadPath and warned on
|
|
1075
|
+
+ every constant. gemvault_load_path.rb now returns before redefining
|
|
1076
|
+
+ anything when the module is already loaded, first copy winning as
|
|
1077
|
+
+ plugins.rb already arranges for Bundler::Plugin::VaultSource. Replicated by
|
|
1078
|
+
+ spec/shim_reload_spec.rb (two copies loaded in one process, red against the
|
|
1079
|
+
+ old shim), with the plain-install path guarded in
|
|
1080
|
+
+ spec/integration/bundle_install_spec.rb.
|
|
1081
|
+
Status: closed
|
|
1020
1082
|
|
|
1021
1083
|
Id: 23
|
|
1022
1084
|
Updated: Mon, 10 Aug 2026 17:40:28 -0400
|
|
@@ -1086,4 +1148,78 @@ Id: 24
|
|
|
1086
1148
|
Updated: Mon, 10 Aug 2026 23:16:35 +0000
|
|
1087
1149
|
Name: doctor prints a backtrace when the plugin uninstall step fails
|
|
1088
1150
|
Description: gemvault doctor's `bundle plugin uninstall` step runs with exception: true; a nonzero exit raises RuntimeError and a missing bundle raises Errno::ENOENT, and both escape Command#run to command_kit's ExceptionHandler, which prints error.full_message -- a full backtrace -- violating the one-line-and-exit-1 convention issue #15 established. Adjacent to issue #14 (doctor failing under bundler/inline). The ghost-specification sweep added for issue #23 reports its own failures as one line; the uninstall step should match.
|
|
1151
|
+
+
|
|
1152
|
+
+ RESOLUTION: fixed alongside issue #27's transactional rework. The uninstall
|
|
1153
|
+
+ step no longer runs with exception: true; a failing or missing bundle is one
|
|
1154
|
+
+ line on stderr ("doctor: bundle plugin uninstall bundler-source-vault
|
|
1155
|
+
+ failed") and exit 1, matching the convention issues #15 and #23 established,
|
|
1156
|
+
+ and the snapshotted index is left untouched.
|
|
1157
|
+
Status: closed
|
|
1158
|
+
|
|
1159
|
+
Id: 25
|
|
1160
|
+
Updated: Tue, 11 Aug 2026 20:05:27 -0400
|
|
1161
|
+
Name: Project with Gemfile + locked json version + gemvault throws error
|
|
1162
|
+
Description: Project with Gemfile + locked json version + gemvault throws "bundler: failed to load command: rails (/private/tmp/local/flipmine/flipmine/vendor/ruby/4.0.0/bin/rails)
|
|
1163
|
+
+ /Users/davidgillis/.rbenv/versions/4.0.1/lib/ruby/gems/4.0.0/gems/bundler-4.0.18/lib/bundler/runtime.rb:328:in 'Bundler::Runtime#check_for_activated_spec!': You have already activated json 2.21.2, but your Gemfile requires json 2.19.7. Prepending `bundle exec` to your command may solve this. (Gem::LoadError)"
|
|
1164
|
+
+
|
|
1165
|
+
+ I believe this error to be coming from gemvault's dependency on JSON. It is clear that dependencies are cursed in this project, whether its the added load time for reinstalling them on every `bundle install`, or errors like this one. Rather than rely on JSON, I think it would be better to just marshal our own manifest, or use some other serialization tool if any that rubygems already provides.
|
|
1166
|
+
Status: open
|
|
1167
|
+
|
|
1168
|
+
Id: 27
|
|
1169
|
+
Updated: Wed, 12 Aug 2026 16:05:55 +0000
|
|
1170
|
+
Name: gemvault doctor can leave the machine with no plugin at all
|
|
1171
|
+
Description: Reported from a container where bundle install could not succeed
|
|
1172
|
+
+ (ruby version pin mismatch): doctor removed ghost records, uninstalled the
|
|
1173
|
+
+ plugin, emptied .bundle/plugin/index, then exec'd bundle install -- which
|
|
1174
|
+
+ failed, leaving no plugin installed. Strictly worse than the broken-path
|
|
1175
|
+
+ state doctor was invoked to repair, and recoverable only by knowing to run
|
|
1176
|
+
+ `bundle plugin install bundler-source-vault` by hand. doctor repairs by
|
|
1177
|
+
+ mutation with no dry-run, no report of findings before acting, and no
|
|
1178
|
+
+ rollback when the reinstall it hands off to fails. Suggested in the report:
|
|
1179
|
+
+ report-then-fix (--dry-run, or report by default and repair under --fix);
|
|
1180
|
+
+ reinstall the plugin directly with `bundle plugin install` and verify the
|
|
1181
|
+
+ index rather than exec'ing into `bundle install`, so a failed dependency
|
|
1182
|
+
+ install cannot take the plugin down with it; or restore the previous index
|
|
1183
|
+
+ when the step after the uninstall fails.
|
|
1184
|
+
+
|
|
1185
|
+
+ RESOLUTION: the uninstall-reinstall pair is now transactional. doctor
|
|
1186
|
+
+ snapshots the plugin index bundler will consult (Gemvault::BundlerPluginIndex
|
|
1187
|
+
+ over BundlerPluginRoot#consulted) before clearing it, runs `bundle install`
|
|
1188
|
+
+ as a child instead of exec'ing into it, and on failure checks whether the
|
|
1189
|
+
+ plugin came back: reinstalled means the repair stands and the one-line error
|
|
1190
|
+
+ says only the bundle is unfinished; not reinstalled means the snapshot is
|
|
1191
|
+
+ restored, so the machine is left as found and re-running doctor once the
|
|
1192
|
+
+ install error is fixed recovers fully. Replicated first by
|
|
1193
|
+
+ spec/integration/cli/commands/doctor_reinstall_failure_spec.rb -- doctor run
|
|
1194
|
+
+ against a Gemfile still naming a renamed plugin path, an un-installable
|
|
1195
|
+
+ bundle in the reporter's shape -- which fails against the old code and
|
|
1196
|
+
+ proves the restored index heals on the next run. Unit cover in
|
|
1197
|
+
+ spec/gemvault/bundler_plugin_index_spec.rb and the reworked doctor_spec.rb.
|
|
1198
|
+
Status: closed
|
|
1199
|
+
|
|
1200
|
+
Id: 28
|
|
1201
|
+
Updated: Wed, 12 Aug 2026 16:05:55 +0000
|
|
1202
|
+
Name: plugins.rb dies with a bare LoadError when gemvault is installed nowhere
|
|
1203
|
+
Description: A plugin root holding only the shim -- the normal state wherever
|
|
1204
|
+
+ `gem install gemvault` has run, since Bundler skips installing a plugin
|
|
1205
|
+
+ dependency already present -- stops loading when the tree lands on a machine
|
|
1206
|
+
+ with no gemvault in any gem root: GemvaultLoadPath.entries returns [] and
|
|
1207
|
+
+ `require "bundler/plugin/vault_source"` raises a bare LoadError with no hint.
|
|
1208
|
+
+ Observed after a macOS working tree was copied into a Linux container (the
|
|
1209
|
+
+ shim alone in .bundle/plugin, gemvault absent from every gem root on the
|
|
1210
|
+
+ box); also reachable by `gem uninstall gemvault` on a single machine. The
|
|
1211
|
+
+ ambient-root search cannot cover gemvault being truly absent. Suggested:
|
|
1212
|
+
+ fail with an explicit "gemvault not found in any gem root" message naming
|
|
1213
|
+
+ the remedy (gem install gemvault, or bundle plugin uninstall + install to
|
|
1214
|
+
+ get a self-contained root), or vendor gemvault into the shim.
|
|
1215
|
+
Status: open
|
|
1216
|
+
|
|
1217
|
+
Id: 29
|
|
1218
|
+
Updated: Wed, 12 Aug 2026 16:05:55 +0000
|
|
1219
|
+
Name: Stale plugin versions are never reaped from the plugin root
|
|
1220
|
+
Description: bundler-source-vault-0.2.4 survived in the plugin root's gems/,
|
|
1221
|
+
+ specifications/ and cache/ across the 0.2.5 upgrade and an explicit `bundle
|
|
1222
|
+
+ plugin uninstall`; only 0.2.5 was ever referenced by the index. Possibly
|
|
1223
|
+
+ Bundler's doing rather than the plugin's, but doctor is well placed to sweep
|
|
1224
|
+
+ plugin-root versions the index no longer references.
|
|
1089
1225
|
Status: open
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
require "pathname"
|
|
2
|
+
|
|
3
|
+
module Gemvault
|
|
4
|
+
##
|
|
5
|
+
# The Gemfile Bundler would load from a directory.
|
|
6
|
+
#
|
|
7
|
+
# Mirrors Bundler::SharedHelpers#find_gemfile: <tt>BUNDLE_GEMFILE</tt> wins
|
|
8
|
+
# when set and non-empty, otherwise the search walks up looking for
|
|
9
|
+
# <tt>gems.rb</tt> then <tt>Gemfile</tt> in each directory. Reimplemented
|
|
10
|
+
# rather than delegated because bundler is deliberately not a gemvault
|
|
11
|
+
# dependency (see gemvault.gemspec) and +gemvault doctor+ runs as a plain
|
|
12
|
+
# CLI, outside any Bundler process.
|
|
13
|
+
#
|
|
14
|
+
# One difference is deliberate. Bundler returns <tt>BUNDLE_GEMFILE</tt>
|
|
15
|
+
# whatever it points at, existing or not -- bundler/inline relies on that,
|
|
16
|
+
# setting it to a bare "Gemfile" purely to make Bundler treat the working
|
|
17
|
+
# directory as the app root. The question here is whether +bundle install+
|
|
18
|
+
# could work, so a value that does not name a file is ignored and the
|
|
19
|
+
# walk-up search decides instead.
|
|
20
|
+
class BundlerGemfile
|
|
21
|
+
# Checked in this order within each directory, as Bundler checks them.
|
|
22
|
+
NAMES = ["gems.rb", "Gemfile"].freeze
|
|
23
|
+
|
|
24
|
+
def initialize(dir: Dir.pwd, env: ENV)
|
|
25
|
+
@dir = Pathname(dir)
|
|
26
|
+
@env = env
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# :call-seq:
|
|
30
|
+
# path -> Pathname or nil
|
|
31
|
+
#
|
|
32
|
+
# The Gemfile Bundler would load, or +nil+ when there is none.
|
|
33
|
+
def path
|
|
34
|
+
return @path if defined?(@path)
|
|
35
|
+
|
|
36
|
+
@path = from_env || search_up
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Whether Bundler would find a Gemfile at all.
|
|
40
|
+
def exist?
|
|
41
|
+
!path.nil?
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
|
|
46
|
+
def from_env
|
|
47
|
+
given = @env["BUNDLE_GEMFILE"].to_s
|
|
48
|
+
return nil if given.empty?
|
|
49
|
+
|
|
50
|
+
candidate = Pathname(given).expand_path(@dir)
|
|
51
|
+
candidate.file? ? candidate : nil
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def search_up
|
|
55
|
+
@dir.expand_path.ascend do |dir|
|
|
56
|
+
found = NAMES.map { |name| dir / name }.find(&:file?)
|
|
57
|
+
return found if found
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
nil
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
require "pathname"
|
|
2
|
+
|
|
3
|
+
module Gemvault
|
|
4
|
+
##
|
|
5
|
+
# Bundler's plugin index file inside a plugin root.
|
|
6
|
+
#
|
|
7
|
+
# The uninstall step of +gemvault doctor+ clears entries from this file, and
|
|
8
|
+
# the reinstall that follows can fail for reasons unrelated to the plugin.
|
|
9
|
+
# Snapshot and restore make that pair transactional: a repair that cannot
|
|
10
|
+
# finish puts the index back instead of leaving the machine with no plugin
|
|
11
|
+
# registered at all (issue #27).
|
|
12
|
+
#
|
|
13
|
+
# The plugin_paths section is scanned textually rather than parsed as YAML:
|
|
14
|
+
# loading a YAML library into doctor's process invites the same
|
|
15
|
+
# stdlib-activation conflicts as issue #25, and bundler's own emitter writes
|
|
16
|
+
# one two-space-indented "name: path" line per plugin.
|
|
17
|
+
class BundlerPluginIndex
|
|
18
|
+
def initialize(root)
|
|
19
|
+
@root = Pathname(root)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def file
|
|
23
|
+
@root / "index"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Whether plugin_paths currently lists +plugin+.
|
|
27
|
+
def registered?(plugin)
|
|
28
|
+
plugin_paths.any? { |line| line.match?(/\A\s{2}#{Regexp.escape(plugin)}:/) }
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# :call-seq:
|
|
32
|
+
# snapshot -> String or nil
|
|
33
|
+
#
|
|
34
|
+
# The index content as it stands, +nil+ when no index exists.
|
|
35
|
+
def snapshot
|
|
36
|
+
file.file? ? file.read : nil
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Puts the index back the way +snapshot+ recorded it; restoring +nil+
|
|
40
|
+
# removes an index that did not exist at snapshot time.
|
|
41
|
+
def restore(snapshot)
|
|
42
|
+
return file.write(snapshot) if snapshot
|
|
43
|
+
|
|
44
|
+
file.delete if file.exist?
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
def plugin_paths
|
|
50
|
+
return [] unless file.file?
|
|
51
|
+
|
|
52
|
+
file.readlines
|
|
53
|
+
.drop_while { |line| !line.start_with?("plugin_paths:") }
|
|
54
|
+
.drop(1)
|
|
55
|
+
.take_while { |line| line.start_with?(" ") }
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
require "pathname"
|
|
2
|
+
require_relative "bundler_gemfile"
|
|
3
|
+
|
|
4
|
+
module Gemvault
|
|
5
|
+
##
|
|
6
|
+
# Where Bundler keeps plugin data for a directory.
|
|
7
|
+
#
|
|
8
|
+
# Bundler::Plugin.root answers the project-local <tt>.bundle/plugin</tt> when
|
|
9
|
+
# <tt>SharedHelpers.in_bundle?</tt> holds, and the global
|
|
10
|
+
# <tt>~/.bundle/plugin</tt> when it does not. Since +in_bundle?+ is just
|
|
11
|
+
# "was a Gemfile found", a project can own a plugin root that Bundler
|
|
12
|
+
# declines to look in.
|
|
13
|
+
class BundlerPluginRoot
|
|
14
|
+
LOCAL_DIR = ".bundle/plugin".freeze
|
|
15
|
+
|
|
16
|
+
def initialize(dir: Dir.pwd, gemfile: BundlerGemfile.new(dir: dir), env: ENV)
|
|
17
|
+
@dir = Pathname(dir)
|
|
18
|
+
@gemfile = gemfile
|
|
19
|
+
@env = env
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# The project's own plugin root, whether or not Bundler would consult it.
|
|
23
|
+
def local
|
|
24
|
+
@dir.expand_path / LOCAL_DIR
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Where Bundler keeps plugins for a user outside any project, mirroring
|
|
28
|
+
# Bundler's user_bundle_path lookup for plugins: <tt>BUNDLE_USER_PLUGIN</tt>
|
|
29
|
+
# names the root directly, <tt>BUNDLE_USER_HOME</tt> relocates
|
|
30
|
+
# <tt>.bundle</tt>, and the home directory is the default.
|
|
31
|
+
def global
|
|
32
|
+
named = @env["BUNDLE_USER_PLUGIN"]
|
|
33
|
+
return Pathname(named) if named
|
|
34
|
+
|
|
35
|
+
Pathname(@env["BUNDLE_USER_HOME"] || File.join(Dir.home, ".bundle")) / "plugin"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# The plugin root Bundler will consult here: beside the Gemfile when one
|
|
39
|
+
# was found, the project's own root when doctor points Bundler at it (see
|
|
40
|
+
# #unreachable?), the global root otherwise.
|
|
41
|
+
def consulted
|
|
42
|
+
return @gemfile.path.dirname / LOCAL_DIR if @gemfile.exist?
|
|
43
|
+
return local if unreachable?
|
|
44
|
+
|
|
45
|
+
global
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Whether this project has a plugin root that Bundler currently ignores.
|
|
49
|
+
#
|
|
50
|
+
# bundler/inline installs plugins into <tt><cwd>/.bundle/plugin</tt>, having
|
|
51
|
+
# set <tt>BUNDLE_GEMFILE</tt> to a bare "Gemfile" for the duration of the
|
|
52
|
+
# script. A later command in that same directory sets no such variable and
|
|
53
|
+
# finds no Gemfile on disk, so Bundler falls back to the global root and
|
|
54
|
+
# reports the plugin as not installed -- while the entry sits here.
|
|
55
|
+
def unreachable?
|
|
56
|
+
!@gemfile.exist? && local.directory?
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
require_relative "../command"
|
|
2
|
+
require_relative "../../bundler_gemfile"
|
|
3
|
+
require_relative "../../bundler_plugin_index"
|
|
4
|
+
require_relative "../../bundler_plugin_root"
|
|
2
5
|
require_relative "../../ghost_specification"
|
|
3
6
|
|
|
4
7
|
module Gemvault
|
|
@@ -25,9 +28,22 @@ module Gemvault
|
|
|
25
28
|
# Re-running bundle install afterwards triggers Bundler to reinstall
|
|
26
29
|
# the plugin against whatever the current Gemfile declares. Run this
|
|
27
30
|
# from the project directory.
|
|
31
|
+
#
|
|
32
|
+
# A project whose Gemfile is inline has no Gemfile to reinstall from, and
|
|
33
|
+
# keeps its plugins in a root Bundler will not consult unless told to.
|
|
34
|
+
# Both are handled below.
|
|
35
|
+
#
|
|
36
|
+
# The repair is transactional. `bundle install` does more than reinstall
|
|
37
|
+
# the plugin, and that extra work can fail on its own; the index the
|
|
38
|
+
# uninstall clears is snapshotted first and put back when the reinstall
|
|
39
|
+
# never happened, so a failed run leaves the machine as found rather
|
|
40
|
+
# than with no plugin at all (issue #27). Every failure is one line on
|
|
41
|
+
# stderr and exit 1 (issue #24).
|
|
28
42
|
class Doctor < Command
|
|
29
43
|
description "Repair broken bundler-source-vault plugin state and reinstall the plugin"
|
|
30
44
|
|
|
45
|
+
PLUGIN = "bundler-source-vault".freeze
|
|
46
|
+
|
|
31
47
|
OWNED_GEMS = %w[gemvault bundler-source-vault].freeze
|
|
32
48
|
|
|
33
49
|
PERMISSION_HINT = "(re-run with permissions for that gem home, e.g. sudo gemvault doctor)".freeze
|
|
@@ -39,8 +55,9 @@ module Gemvault
|
|
|
39
55
|
print_error("#{e.message} #{PERMISSION_HINT}")
|
|
40
56
|
exit(1)
|
|
41
57
|
end
|
|
42
|
-
|
|
43
|
-
|
|
58
|
+
saved = index.snapshot
|
|
59
|
+
uninstall_plugin
|
|
60
|
+
reinstall_or_explain(saved)
|
|
44
61
|
end
|
|
45
62
|
|
|
46
63
|
private
|
|
@@ -51,6 +68,77 @@ module Gemvault
|
|
|
51
68
|
puts "Removed ghost specification #{ghost}"
|
|
52
69
|
end
|
|
53
70
|
end
|
|
71
|
+
|
|
72
|
+
def gemfile
|
|
73
|
+
@gemfile ||= BundlerGemfile.new
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def plugin_root
|
|
77
|
+
@plugin_root ||= BundlerPluginRoot.new(gemfile: gemfile)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def index
|
|
81
|
+
@index ||= BundlerPluginIndex.new(plugin_root.consulted)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# bundler/inline's own trick: a non-empty BUNDLE_GEMFILE is the whole of
|
|
85
|
+
# what Bundler::Plugin.root consults to prefer a project's plugin
|
|
86
|
+
# directory over the global one, so setting it reaches the entry that
|
|
87
|
+
# needs clearing. The file it names never has to exist.
|
|
88
|
+
def uninstall_env
|
|
89
|
+
return {} unless plugin_root.unreachable?
|
|
90
|
+
|
|
91
|
+
{ "BUNDLE_GEMFILE" => "Gemfile" }
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def uninstall_plugin
|
|
95
|
+
return if system(uninstall_env, "bundle", "plugin", "uninstall", PLUGIN)
|
|
96
|
+
|
|
97
|
+
print_error("bundle plugin uninstall #{PLUGIN} failed")
|
|
98
|
+
exit(1)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# `bundle install` with no Gemfile prints its entire usage screen and
|
|
102
|
+
# exits 10, which reads as gemvault itself failing. By this point the
|
|
103
|
+
# repair has already happened; only the reinstall is unavailable.
|
|
104
|
+
def reinstall_or_explain(saved)
|
|
105
|
+
return reinstall(saved) if gemfile.exist?
|
|
106
|
+
return explain_inline_repair if plugin_root.unreachable?
|
|
107
|
+
|
|
108
|
+
puts "No Gemfile here to reinstall from -- re-run gemvault doctor from the"
|
|
109
|
+
puts "project directory to reinstall the plugin."
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def reinstall(saved)
|
|
113
|
+
return if system("bundle", "install")
|
|
114
|
+
return exit_partially_repaired if index.registered?(PLUGIN)
|
|
115
|
+
|
|
116
|
+
exit_restoring(saved)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# The plugin came back before the install died, so the repair stands;
|
|
120
|
+
# only the rest of the bundle is unfinished.
|
|
121
|
+
def exit_partially_repaired
|
|
122
|
+
print_error("bundle install failed; the #{PLUGIN} plugin itself was reinstalled " \
|
|
123
|
+
"-- fix the install error and re-run bundle install")
|
|
124
|
+
exit(1)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def exit_restoring(saved)
|
|
128
|
+
index.restore(saved)
|
|
129
|
+
print_error("bundle install failed before reinstalling #{PLUGIN}; restored the " \
|
|
130
|
+
"previous plugin index -- fix the install error and re-run gemvault doctor")
|
|
131
|
+
exit(1)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# The local path is named only when it was the uninstall's target --
|
|
135
|
+
# otherwise bundler worked against its global root, and naming this
|
|
136
|
+
# project's path would misreport what happened.
|
|
137
|
+
def explain_inline_repair
|
|
138
|
+
puts "Cleared the #{PLUGIN} plugin index at #{plugin_root.local}."
|
|
139
|
+
puts "No Gemfile here to reinstall from -- an inline gemfile installs the plugin"
|
|
140
|
+
puts "again the next time the script runs."
|
|
141
|
+
end
|
|
54
142
|
end
|
|
55
143
|
end
|
|
56
144
|
end
|
data/lib/gemvault/version.rb
CHANGED
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.
|
|
4
|
+
version: 0.2.6
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- David Gillis
|
|
@@ -56,6 +56,9 @@ files:
|
|
|
56
56
|
- lib/bundler/plugin/vaulted_gem.rb
|
|
57
57
|
- lib/gemvault.rb
|
|
58
58
|
- lib/gemvault/archive_entry.rb
|
|
59
|
+
- lib/gemvault/bundler_gemfile.rb
|
|
60
|
+
- lib/gemvault/bundler_plugin_index.rb
|
|
61
|
+
- lib/gemvault/bundler_plugin_root.rb
|
|
59
62
|
- lib/gemvault/cli.rb
|
|
60
63
|
- lib/gemvault/cli/command.rb
|
|
61
64
|
- lib/gemvault/cli/commands/add.rb
|