mnenv 0.1.0 → 0.1.1

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.
Files changed (303) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +3 -0
  3. data/PROPOSAL.md +197 -0
  4. data/README.adoc +168 -461
  5. data/Rakefile +7 -4
  6. data/bin/Install-Mnenv.ps1 +145 -0
  7. data/bin/mnenv-installer +72 -0
  8. data/completions/bash +47 -0
  9. data/completions/fish +29 -0
  10. data/completions/powershell.ps1 +94 -0
  11. data/completions/zsh +43 -0
  12. data/lib/mnenv/binary_repository.rb +189 -0
  13. data/lib/mnenv/chocolatey.rb +7 -0
  14. data/lib/mnenv/cli.rb +110 -10
  15. data/lib/mnenv/commands/available_command.rb +169 -0
  16. data/lib/mnenv/commands/chocolatey_command.rb +4 -5
  17. data/lib/mnenv/commands/gemfile_command.rb +4 -5
  18. data/lib/mnenv/commands/homebrew_command.rb +4 -5
  19. data/lib/mnenv/commands/install_command.rb +234 -0
  20. data/lib/mnenv/commands/snap_command.rb +5 -7
  21. data/lib/mnenv/commands/uninstall_command.rb +42 -0
  22. data/lib/mnenv/commands/version_command.rb +213 -0
  23. data/lib/mnenv/commands.rb +9 -4
  24. data/lib/mnenv/gemfile/extractor.rb +10 -3
  25. data/lib/mnenv/gemfile.rb +8 -0
  26. data/lib/mnenv/gemfile_repository.rb +0 -2
  27. data/lib/mnenv/homebrew.rb +7 -0
  28. data/lib/mnenv/installer/base.rb +62 -0
  29. data/lib/mnenv/installer/factory.rb +17 -0
  30. data/lib/mnenv/installer.rb +12 -0
  31. data/lib/mnenv/installers/binary_installer.rb +242 -0
  32. data/lib/mnenv/installers/gemfile_installer.rb +76 -0
  33. data/lib/mnenv/json_formatter.rb +3 -13
  34. data/lib/mnenv/logger.rb +9 -1
  35. data/lib/mnenv/models/binary_version.rb +78 -0
  36. data/lib/mnenv/models/chocolatey_version.rb +7 -0
  37. data/lib/mnenv/models/gemfile_version.rb +19 -5
  38. data/lib/mnenv/models/homebrew_version.rb +7 -0
  39. data/lib/mnenv/models/snap_version.rb +8 -0
  40. data/lib/mnenv/models/version.rb +16 -0
  41. data/lib/mnenv/models.rb +7 -5
  42. data/lib/mnenv/paths.rb +69 -0
  43. data/lib/mnenv/repository.rb +50 -35
  44. data/lib/mnenv/resolver +72 -0
  45. data/lib/mnenv/shells/base.rb +32 -0
  46. data/lib/mnenv/shells/bash.rb +72 -0
  47. data/lib/mnenv/shells/cmd.rb +108 -0
  48. data/lib/mnenv/shells/factory.rb +82 -0
  49. data/lib/mnenv/shells/power_shell.rb +110 -0
  50. data/lib/mnenv/shim_manager.rb +121 -0
  51. data/lib/mnenv/snap.rb +7 -0
  52. data/lib/mnenv/snap_repository.rb +2 -19
  53. data/lib/mnenv/source_registry.rb +69 -0
  54. data/lib/mnenv/version.rb +1 -1
  55. data/lib/mnenv/versions_manager.rb +92 -0
  56. data/lib/mnenv.rb +3 -0
  57. data/mnenv.gemspec +4 -1
  58. data/scripts/cross-source-switching-test.sh +214 -0
  59. data/scripts/integration-test.sh +89 -0
  60. data/scripts/version-switching-test.sh +151 -0
  61. metadata +82 -247
  62. data/data/chocolatey/versions.yaml +0 -812
  63. data/data/gemfile/v1.1.6/Gemfile +0 -4
  64. data/data/gemfile/v1.1.6/Gemfile.lock.archived +0 -232
  65. data/data/gemfile/v1.1.7/Gemfile +0 -4
  66. data/data/gemfile/v1.1.7/Gemfile.lock.archived +0 -235
  67. data/data/gemfile/v1.1.8/Gemfile +0 -4
  68. data/data/gemfile/v1.1.8/Gemfile.lock.archived +0 -238
  69. data/data/gemfile/v1.10.0/Gemfile +0 -5
  70. data/data/gemfile/v1.10.0/Gemfile.lock.archived +0 -930
  71. data/data/gemfile/v1.10.1/Gemfile +0 -5
  72. data/data/gemfile/v1.10.1/Gemfile.lock.archived +0 -929
  73. data/data/gemfile/v1.10.10/Gemfile +0 -5
  74. data/data/gemfile/v1.10.10/Gemfile.lock.archived +0 -973
  75. data/data/gemfile/v1.10.11/Gemfile +0 -5
  76. data/data/gemfile/v1.10.11/Gemfile.lock.archived +0 -975
  77. data/data/gemfile/v1.10.2/Gemfile +0 -5
  78. data/data/gemfile/v1.10.2/Gemfile.lock.archived +0 -939
  79. data/data/gemfile/v1.10.3/Gemfile +0 -5
  80. data/data/gemfile/v1.10.3/Gemfile.lock.archived +0 -946
  81. data/data/gemfile/v1.10.5/Gemfile +0 -5
  82. data/data/gemfile/v1.10.5/Gemfile.lock.archived +0 -958
  83. data/data/gemfile/v1.10.6/Gemfile +0 -5
  84. data/data/gemfile/v1.10.6/Gemfile.lock.archived +0 -969
  85. data/data/gemfile/v1.10.7/Gemfile +0 -5
  86. data/data/gemfile/v1.10.7/Gemfile.lock.archived +0 -969
  87. data/data/gemfile/v1.10.8/Gemfile +0 -5
  88. data/data/gemfile/v1.10.8/Gemfile.lock.archived +0 -968
  89. data/data/gemfile/v1.10.9/Gemfile +0 -5
  90. data/data/gemfile/v1.10.9/Gemfile.lock.archived +0 -972
  91. data/data/gemfile/v1.11.0/Gemfile +0 -5
  92. data/data/gemfile/v1.11.0/Gemfile.lock.archived +0 -971
  93. data/data/gemfile/v1.11.1/Gemfile +0 -5
  94. data/data/gemfile/v1.11.1/Gemfile.lock.archived +0 -975
  95. data/data/gemfile/v1.11.4/Gemfile +0 -5
  96. data/data/gemfile/v1.11.4/Gemfile.lock.archived +0 -1046
  97. data/data/gemfile/v1.11.5/Gemfile +0 -5
  98. data/data/gemfile/v1.11.5/Gemfile.lock.archived +0 -1047
  99. data/data/gemfile/v1.12.10/Gemfile +0 -3
  100. data/data/gemfile/v1.12.10/Gemfile.lock.archived +0 -1073
  101. data/data/gemfile/v1.12.3/Gemfile +0 -3
  102. data/data/gemfile/v1.12.3/Gemfile.lock.archived +0 -1050
  103. data/data/gemfile/v1.12.4/Gemfile +0 -3
  104. data/data/gemfile/v1.12.4/Gemfile.lock.archived +0 -1056
  105. data/data/gemfile/v1.12.5/Gemfile +0 -3
  106. data/data/gemfile/v1.12.5/Gemfile.lock.archived +0 -1054
  107. data/data/gemfile/v1.12.6/Gemfile +0 -3
  108. data/data/gemfile/v1.12.6/Gemfile.lock.archived +0 -1056
  109. data/data/gemfile/v1.12.8/Gemfile +0 -3
  110. data/data/gemfile/v1.12.8/Gemfile.lock.archived +0 -1063
  111. data/data/gemfile/v1.13.0/Gemfile +0 -3
  112. data/data/gemfile/v1.13.0/Gemfile.lock.archived +0 -1074
  113. data/data/gemfile/v1.13.2/Gemfile +0 -3
  114. data/data/gemfile/v1.13.2/Gemfile.lock.archived +0 -899
  115. data/data/gemfile/v1.13.3/Gemfile +0 -3
  116. data/data/gemfile/v1.13.3/Gemfile.lock.archived +0 -938
  117. data/data/gemfile/v1.13.4/Gemfile +0 -3
  118. data/data/gemfile/v1.13.4/Gemfile.lock.archived +0 -938
  119. data/data/gemfile/v1.13.5/Gemfile +0 -3
  120. data/data/gemfile/v1.13.5/Gemfile.lock.archived +0 -944
  121. data/data/gemfile/v1.13.7/Gemfile +0 -3
  122. data/data/gemfile/v1.13.7/Gemfile.lock.archived +0 -944
  123. data/data/gemfile/v1.13.8/Gemfile +0 -3
  124. data/data/gemfile/v1.13.8/Gemfile.lock.archived +0 -944
  125. data/data/gemfile/v1.13.9/Gemfile +0 -3
  126. data/data/gemfile/v1.13.9/Gemfile.lock.archived +0 -956
  127. data/data/gemfile/v1.14.3/Gemfile +0 -3
  128. data/data/gemfile/v1.14.3/Gemfile.lock.archived +0 -950
  129. data/data/gemfile/v1.2.12/Gemfile +0 -3
  130. data/data/gemfile/v1.2.12/Gemfile.lock.archived +0 -283
  131. data/data/gemfile/v1.2.2/Gemfile +0 -4
  132. data/data/gemfile/v1.2.2/Gemfile.lock.archived +0 -224
  133. data/data/gemfile/v1.2.3/Gemfile +0 -4
  134. data/data/gemfile/v1.2.3/Gemfile.lock.archived +0 -231
  135. data/data/gemfile/v1.2.6/Gemfile +0 -4
  136. data/data/gemfile/v1.2.6/Gemfile.lock.archived +0 -239
  137. data/data/gemfile/v1.2.8/Gemfile +0 -4
  138. data/data/gemfile/v1.2.8/Gemfile.lock.archived +0 -233
  139. data/data/gemfile/v1.2.9/Gemfile +0 -4
  140. data/data/gemfile/v1.2.9/Gemfile.lock.archived +0 -245
  141. data/data/gemfile/v1.3.1/Gemfile +0 -3
  142. data/data/gemfile/v1.3.1/Gemfile.lock.archived +0 -296
  143. data/data/gemfile/v1.3.2/Gemfile +0 -3
  144. data/data/gemfile/v1.3.2/Gemfile.lock.archived +0 -296
  145. data/data/gemfile/v1.3.4/Gemfile +0 -3
  146. data/data/gemfile/v1.3.4/Gemfile.lock.archived +0 -284
  147. data/data/gemfile/v1.3.5/Gemfile +0 -3
  148. data/data/gemfile/v1.3.5/Gemfile.lock.archived +0 -284
  149. data/data/gemfile/v1.3.6/Gemfile +0 -3
  150. data/data/gemfile/v1.3.6/Gemfile.lock.archived +0 -286
  151. data/data/gemfile/v1.3.9/Gemfile +0 -3
  152. data/data/gemfile/v1.3.9/Gemfile.lock.archived +0 -334
  153. data/data/gemfile/v1.4.0/Gemfile +0 -3
  154. data/data/gemfile/v1.4.0/Gemfile.lock.archived +0 -330
  155. data/data/gemfile/v1.4.10/Gemfile +0 -4
  156. data/data/gemfile/v1.4.10/Gemfile.lock.archived +0 -461
  157. data/data/gemfile/v1.4.11/Gemfile +0 -4
  158. data/data/gemfile/v1.4.11/Gemfile.lock.archived +0 -452
  159. data/data/gemfile/v1.4.12/Gemfile +0 -4
  160. data/data/gemfile/v1.4.12/Gemfile.lock.archived +0 -452
  161. data/data/gemfile/v1.4.13/Gemfile +0 -4
  162. data/data/gemfile/v1.4.13/Gemfile.lock.archived +0 -455
  163. data/data/gemfile/v1.4.14/Gemfile +0 -4
  164. data/data/gemfile/v1.4.14/Gemfile.lock.archived +0 -456
  165. data/data/gemfile/v1.4.18/Gemfile +0 -3
  166. data/data/gemfile/v1.4.18/Gemfile.lock.archived +0 -486
  167. data/data/gemfile/v1.4.3/Gemfile +0 -3
  168. data/data/gemfile/v1.4.3/Gemfile.lock.archived +0 -339
  169. data/data/gemfile/v1.4.4/Gemfile +0 -3
  170. data/data/gemfile/v1.4.4/Gemfile.lock.archived +0 -339
  171. data/data/gemfile/v1.4.5/Gemfile +0 -3
  172. data/data/gemfile/v1.4.5/Gemfile.lock.archived +0 -348
  173. data/data/gemfile/v1.4.6/Gemfile +0 -3
  174. data/data/gemfile/v1.4.6/Gemfile.lock.archived +0 -357
  175. data/data/gemfile/v1.4.7/Gemfile +0 -3
  176. data/data/gemfile/v1.4.7/Gemfile.lock.archived +0 -391
  177. data/data/gemfile/v1.4.8/Gemfile +0 -3
  178. data/data/gemfile/v1.4.8/Gemfile.lock.archived +0 -445
  179. data/data/gemfile/v1.4.9/Gemfile +0 -3
  180. data/data/gemfile/v1.4.9/Gemfile.lock.archived +0 -448
  181. data/data/gemfile/v1.5.0/Gemfile +0 -3
  182. data/data/gemfile/v1.5.0/Gemfile.lock.archived +0 -478
  183. data/data/gemfile/v1.5.10/Gemfile +0 -3
  184. data/data/gemfile/v1.5.10/Gemfile.lock.archived +0 -668
  185. data/data/gemfile/v1.5.11/Gemfile +0 -3
  186. data/data/gemfile/v1.5.11/Gemfile.lock.archived +0 -668
  187. data/data/gemfile/v1.5.15/Gemfile +0 -3
  188. data/data/gemfile/v1.5.15/Gemfile.lock.archived +0 -686
  189. data/data/gemfile/v1.5.16/Gemfile +0 -3
  190. data/data/gemfile/v1.5.16/Gemfile.lock.archived +0 -684
  191. data/data/gemfile/v1.5.17/Gemfile +0 -3
  192. data/data/gemfile/v1.5.17/Gemfile.lock.archived +0 -684
  193. data/data/gemfile/v1.5.18/Gemfile +0 -5
  194. data/data/gemfile/v1.5.18/Gemfile.lock.archived +0 -691
  195. data/data/gemfile/v1.5.19/Gemfile +0 -5
  196. data/data/gemfile/v1.5.19/Gemfile.lock.archived +0 -703
  197. data/data/gemfile/v1.5.20/Gemfile +0 -5
  198. data/data/gemfile/v1.5.20/Gemfile.lock.archived +0 -703
  199. data/data/gemfile/v1.5.21/Gemfile +0 -5
  200. data/data/gemfile/v1.5.21/Gemfile.lock.archived +0 -707
  201. data/data/gemfile/v1.5.22/Gemfile +0 -5
  202. data/data/gemfile/v1.5.22/Gemfile.lock.archived +0 -707
  203. data/data/gemfile/v1.5.23/Gemfile +0 -5
  204. data/data/gemfile/v1.5.23/Gemfile.lock.archived +0 -711
  205. data/data/gemfile/v1.5.24/Gemfile +0 -5
  206. data/data/gemfile/v1.5.24/Gemfile.lock.archived +0 -711
  207. data/data/gemfile/v1.5.3/Gemfile +0 -3
  208. data/data/gemfile/v1.5.3/Gemfile.lock.archived +0 -651
  209. data/data/gemfile/v1.5.4/Gemfile +0 -3
  210. data/data/gemfile/v1.5.4/Gemfile.lock.archived +0 -657
  211. data/data/gemfile/v1.5.5/Gemfile +0 -3
  212. data/data/gemfile/v1.5.5/Gemfile.lock.archived +0 -657
  213. data/data/gemfile/v1.5.6/Gemfile +0 -3
  214. data/data/gemfile/v1.5.6/Gemfile.lock.archived +0 -657
  215. data/data/gemfile/v1.5.7/Gemfile +0 -3
  216. data/data/gemfile/v1.5.7/Gemfile.lock.archived +0 -657
  217. data/data/gemfile/v1.5.8/Gemfile +0 -3
  218. data/data/gemfile/v1.5.8/Gemfile.lock.archived +0 -655
  219. data/data/gemfile/v1.5.9/Gemfile +0 -3
  220. data/data/gemfile/v1.5.9/Gemfile.lock.archived +0 -656
  221. data/data/gemfile/v1.6.1/Gemfile +0 -5
  222. data/data/gemfile/v1.6.1/Gemfile.lock.archived +0 -721
  223. data/data/gemfile/v1.6.10/Gemfile +0 -5
  224. data/data/gemfile/v1.6.10/Gemfile.lock.archived +0 -744
  225. data/data/gemfile/v1.6.11/Gemfile +0 -5
  226. data/data/gemfile/v1.6.11/Gemfile.lock.archived +0 -744
  227. data/data/gemfile/v1.6.12/Gemfile +0 -5
  228. data/data/gemfile/v1.6.12/Gemfile.lock.archived +0 -745
  229. data/data/gemfile/v1.6.13/Gemfile +0 -5
  230. data/data/gemfile/v1.6.13/Gemfile.lock.archived +0 -745
  231. data/data/gemfile/v1.6.14/Gemfile +0 -5
  232. data/data/gemfile/v1.6.14/Gemfile.lock.archived +0 -754
  233. data/data/gemfile/v1.6.15/Gemfile +0 -5
  234. data/data/gemfile/v1.6.15/Gemfile.lock.archived +0 -757
  235. data/data/gemfile/v1.6.2/Gemfile +0 -5
  236. data/data/gemfile/v1.6.2/Gemfile.lock.archived +0 -718
  237. data/data/gemfile/v1.6.3/Gemfile +0 -5
  238. data/data/gemfile/v1.6.3/Gemfile.lock.archived +0 -728
  239. data/data/gemfile/v1.6.4/Gemfile +0 -5
  240. data/data/gemfile/v1.6.4/Gemfile.lock.archived +0 -730
  241. data/data/gemfile/v1.6.5/Gemfile +0 -5
  242. data/data/gemfile/v1.6.5/Gemfile.lock.archived +0 -733
  243. data/data/gemfile/v1.6.6/Gemfile +0 -5
  244. data/data/gemfile/v1.6.6/Gemfile.lock.archived +0 -733
  245. data/data/gemfile/v1.6.7/Gemfile +0 -5
  246. data/data/gemfile/v1.6.7/Gemfile.lock.archived +0 -733
  247. data/data/gemfile/v1.6.9/Gemfile +0 -5
  248. data/data/gemfile/v1.6.9/Gemfile.lock.archived +0 -744
  249. data/data/gemfile/v1.7.0/Gemfile +0 -5
  250. data/data/gemfile/v1.7.0/Gemfile.lock.archived +0 -750
  251. data/data/gemfile/v1.7.1/Gemfile +0 -5
  252. data/data/gemfile/v1.7.1/Gemfile.lock.archived +0 -750
  253. data/data/gemfile/v1.7.2/Gemfile +0 -5
  254. data/data/gemfile/v1.7.2/Gemfile.lock.archived +0 -747
  255. data/data/gemfile/v1.7.3/Gemfile +0 -5
  256. data/data/gemfile/v1.7.3/Gemfile.lock.archived +0 -755
  257. data/data/gemfile/v1.7.4/Gemfile +0 -5
  258. data/data/gemfile/v1.7.4/Gemfile.lock.archived +0 -756
  259. data/data/gemfile/v1.7.5/Gemfile +0 -5
  260. data/data/gemfile/v1.7.5/Gemfile.lock.archived +0 -759
  261. data/data/gemfile/v1.7.6/Gemfile +0 -5
  262. data/data/gemfile/v1.7.6/Gemfile.lock.archived +0 -768
  263. data/data/gemfile/v1.8.10/Gemfile +0 -5
  264. data/data/gemfile/v1.8.10/Gemfile.lock.archived +0 -792
  265. data/data/gemfile/v1.8.11/Gemfile +0 -5
  266. data/data/gemfile/v1.8.11/Gemfile.lock.archived +0 -862
  267. data/data/gemfile/v1.8.3/Gemfile +0 -5
  268. data/data/gemfile/v1.8.3/Gemfile.lock.archived +0 -773
  269. data/data/gemfile/v1.8.4/Gemfile +0 -5
  270. data/data/gemfile/v1.8.4/Gemfile.lock.archived +0 -768
  271. data/data/gemfile/v1.8.5/Gemfile +0 -5
  272. data/data/gemfile/v1.8.5/Gemfile.lock.archived +0 -768
  273. data/data/gemfile/v1.8.6/Gemfile +0 -5
  274. data/data/gemfile/v1.8.6/Gemfile.lock.archived +0 -777
  275. data/data/gemfile/v1.8.7/Gemfile +0 -5
  276. data/data/gemfile/v1.8.7/Gemfile.lock.archived +0 -777
  277. data/data/gemfile/v1.8.8/Gemfile +0 -5
  278. data/data/gemfile/v1.8.8/Gemfile.lock.archived +0 -778
  279. data/data/gemfile/v1.8.9/Gemfile +0 -5
  280. data/data/gemfile/v1.8.9/Gemfile.lock.archived +0 -775
  281. data/data/gemfile/v1.9.0/Gemfile +0 -5
  282. data/data/gemfile/v1.9.0/Gemfile.lock.archived +0 -871
  283. data/data/gemfile/v1.9.1/Gemfile +0 -5
  284. data/data/gemfile/v1.9.1/Gemfile.lock.archived +0 -906
  285. data/data/gemfile/v1.9.2/Gemfile +0 -5
  286. data/data/gemfile/v1.9.2/Gemfile.lock.archived +0 -898
  287. data/data/gemfile/v1.9.3/Gemfile +0 -5
  288. data/data/gemfile/v1.9.3/Gemfile.lock.archived +0 -898
  289. data/data/gemfile/v1.9.4/Gemfile +0 -5
  290. data/data/gemfile/v1.9.4/Gemfile.lock.archived +0 -901
  291. data/data/gemfile/v1.9.5/Gemfile +0 -5
  292. data/data/gemfile/v1.9.5/Gemfile.lock.archived +0 -903
  293. data/data/gemfile/v1.9.6/Gemfile +0 -5
  294. data/data/gemfile/v1.9.6/Gemfile.lock.archived +0 -900
  295. data/data/gemfile/v1.9.7/Gemfile +0 -5
  296. data/data/gemfile/v1.9.7/Gemfile.lock.archived +0 -922
  297. data/data/gemfile/v1.9.8/Gemfile +0 -5
  298. data/data/gemfile/v1.9.8/Gemfile.lock.archived +0 -933
  299. data/data/gemfile/versions.yaml +0 -751
  300. data/data/homebrew/versions.yaml +0 -567
  301. data/data/snap/github_tags.json +0 -42
  302. data/data/snap/versions.yaml +0 -589
  303. data/snapcraft-list-copied-from-site.md +0 -101
@@ -0,0 +1,121 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'set'
4
+ require_relative 'shells/factory'
5
+
6
+ module Mnenv
7
+ class ShimManager
8
+ RESOLVER_SCRIPT = File.expand_path('resolver', __dir__).freeze
9
+
10
+ attr_reader :shims_dir, :installed_dir
11
+
12
+ def initialize(shims_dir: nil, installed_dir: nil)
13
+ @shims_dir = shims_dir || Paths::SHIMS_DIR
14
+ @installed_dir = installed_dir || Paths::INSTALLED_DIR
15
+ end
16
+
17
+ def regenerate_all
18
+ FileUtils.mkdir_p(@shims_dir)
19
+ FileUtils.mkdir_p(Paths::LIB_DIR)
20
+
21
+ # Copy resolver script to mnenv lib directory (for Unix shells)
22
+ if File.exist?(RESOLVER_SCRIPT)
23
+ FileUtils.cp(RESOLVER_SCRIPT, File.join(Paths::LIB_DIR, 'resolver'))
24
+ File.chmod(0o755, File.join(Paths::LIB_DIR, 'resolver'))
25
+ end
26
+
27
+ executables = discover_executables
28
+
29
+ executables.each do |exe|
30
+ create_shims(exe)
31
+ end
32
+
33
+ remove_obsolete_shims(executables)
34
+ end
35
+
36
+ def create_shims(executable_name)
37
+ # Create shims for all platform-appropriate shells
38
+ Shells::ShellFactory.platform_shells.each do |shell|
39
+ create_shim_for_shell(executable_name, shell)
40
+ end
41
+ end
42
+
43
+ private
44
+
45
+ def create_shim_for_shell(executable_name, shell)
46
+ # For bash, no extension; for PowerShell, .ps1; for CMD, .bat
47
+ extension = shell.shim_extension
48
+ shim_name = executable_name + extension
49
+ shim_path = File.join(@shims_dir, shim_name)
50
+
51
+ File.write(shim_path, shell.shim_content(executable_name))
52
+ File.chmod(0o755, shim_path) unless shell.windows?
53
+ end
54
+
55
+ def discover_executables
56
+ executables = Set.new
57
+
58
+ # Scan installed directories with new naming convention: <version>-<source>
59
+ Dir.glob(File.join(@installed_dir, '*')).each do |version_dir|
60
+ next unless File.directory?(version_dir)
61
+
62
+ dir_name = File.basename(version_dir)
63
+ version, source = Paths.parse_version_dir(dir_name)
64
+ next unless version && source
65
+
66
+ case source
67
+ when 'gemfile'
68
+ # Discover binstubs from bundle install
69
+ bin_dir = File.join(version_dir, 'bin')
70
+ next unless Dir.exist?(bin_dir)
71
+
72
+ Dir.glob(File.join(bin_dir, '*')).each do |bin_path|
73
+ next if File.directory?(bin_path)
74
+
75
+ basename = File.basename(bin_path)
76
+
77
+ # On Windows, bundler creates both 'command' and 'command.cmd'
78
+ # Skip .cmd and .bat files - we'll create shims from the base name
79
+ next if windows? && (basename.end_with?('.cmd') || basename.end_with?('.bat'))
80
+
81
+ executables << basename if windows? || File.executable?(bin_path)
82
+ end
83
+
84
+ when 'binary'
85
+ # Binary installations have a single metanorma binary
86
+ binary_path = File.join(version_dir, 'metanorma')
87
+ exe_path = File.join(version_dir, 'metanorma.exe')
88
+
89
+ if File.exist?(binary_path) && (File.executable?(binary_path) || windows?)
90
+ executables << 'metanorma'
91
+ elsif File.exist?(exe_path)
92
+ executables << 'metanorma'
93
+ end
94
+ end
95
+ end
96
+
97
+ executables.to_a.sort
98
+ end
99
+
100
+ def remove_obsolete_shims(valid_executables)
101
+ # Build list of valid shim names (with extensions)
102
+ valid_shim_names = Set.new
103
+ Shells::ShellFactory.platform_shells.each do |shell|
104
+ valid_executables.each do |exe|
105
+ valid_shim_names << exe + shell.shim_extension
106
+ end
107
+ end
108
+
109
+ Dir.glob(File.join(@shims_dir, '*')).each do |shim_path|
110
+ next if File.directory?(shim_path)
111
+
112
+ shim_name = File.basename(shim_path)
113
+ File.delete(shim_path) unless valid_shim_names.include?(shim_name)
114
+ end
115
+ end
116
+
117
+ def windows?
118
+ RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/
119
+ end
120
+ end
121
+ end
data/lib/mnenv/snap.rb ADDED
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mnenv
4
+ module Snap
5
+ autoload :Fetcher, 'mnenv/snap/fetcher'
6
+ end
7
+ end
@@ -8,16 +8,10 @@ module Mnenv
8
8
  def version_class = SnapVersion
9
9
  def source_name = :snap
10
10
 
11
- # Override save and save_all to use composite key for Snap
11
+ # Override cache_version to use composite key for Snap
12
12
  # Since same version can have multiple revisions/arch/channels
13
- def save(version)
13
+ def cache_version(version)
14
14
  @versions_cache[snap_key(version)] = version
15
- persist
16
- end
17
-
18
- def save_all(versions)
19
- versions.each { |v| @versions_cache[snap_key(v)] = v }
20
- persist
21
15
  end
22
16
 
23
17
  # Override find to work with composite keys
@@ -55,16 +49,5 @@ module Mnenv
55
49
  raise ArgumentError, 'snap_key requires 1 or 4 arguments'
56
50
  end
57
51
  end
58
-
59
- # Override load to handle snap composite keys
60
- def load
61
- data = YAML.load_file(versions_file_path)
62
- return if data.nil? || data['versions'].nil?
63
-
64
- data['versions'].each do |version_hash|
65
- version = version_class.new(version_hash)
66
- @versions_cache[snap_key(version)] = version
67
- end
68
- end
69
52
  end
70
53
  end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mnenv
4
+ # Registry for version sources (gemfile, binary, snap, homebrew, chocolatey)
5
+ # Enables plugin architecture - new sources just need to register themselves
6
+ class SourceRegistry
7
+ class DuplicateSourceError < StandardError; end
8
+ class UnknownSourceError < StandardError; end
9
+
10
+ # Register a new source
11
+ # @param name [String] the source identifier (e.g., 'gemfile', 'binary')
12
+ # @param repository [Class] the repository class for this source
13
+ # @param model [Class] the version model class for this source
14
+ # @param installer [Class, nil] the installer class (optional)
15
+ def self.register(name:, repository:, model:, installer: nil)
16
+ name = name.to_s
17
+ raise DuplicateSourceError, "Source '#{name}' is already registered" if sources.key?(name)
18
+
19
+ sources[name] = {
20
+ repository: repository,
21
+ model: model,
22
+ installer: installer
23
+ }
24
+ end
25
+
26
+ # Get repository class for a source
27
+ def self.repository(name)
28
+ source = sources[name.to_s]
29
+ raise UnknownSourceError, "Unknown source: #{name}" unless source
30
+
31
+ source[:repository]
32
+ end
33
+
34
+ # Get model class for a source
35
+ def self.model(name)
36
+ source = sources[name.to_s]
37
+ raise UnknownSourceError, "Unknown source: #{name}" unless source
38
+
39
+ source[:model]
40
+ end
41
+
42
+ # Get installer class for a source
43
+ def self.installer(name)
44
+ source = sources[name.to_s]
45
+ raise UnknownSourceError, "Unknown source: #{name}" unless source
46
+
47
+ source[:installer]
48
+ end
49
+
50
+ # Get all registered source names
51
+ def self.all_names
52
+ sources.keys.sort
53
+ end
54
+
55
+ # Check if a source is registered
56
+ def self.registered?(name)
57
+ sources.key?(name.to_s)
58
+ end
59
+
60
+ # Clear all registrations (useful for testing)
61
+ def self.clear
62
+ @sources = nil
63
+ end
64
+
65
+ def self.sources
66
+ @sources ||= {}
67
+ end
68
+ end
69
+ end
data/lib/mnenv/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Mnenv
4
- VERSION = '0.1.0'
4
+ VERSION = '0.1.1'
5
5
  end
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fileutils'
4
+ require 'git'
5
+
6
+ module Mnenv
7
+ # Manages the local clone of the metanorma/versions repository
8
+ # Similar to how Homebrew manages its core tap
9
+ class VersionsManager
10
+ VERSIONS_REPO = 'https://github.com/metanorma/versions.git'
11
+ DEFAULT_MNENV_DIR = File.expand_path('~/.mnenv')
12
+ VERSIONS_DIR = 'versions'
13
+
14
+ attr_reader :mnenv_dir, :versions_path
15
+
16
+ def initialize(mnenv_dir: nil)
17
+ @mnenv_dir = mnenv_dir || DEFAULT_MNENV_DIR
18
+ @versions_path = File.join(@mnenv_dir, VERSIONS_DIR)
19
+ end
20
+
21
+ # Ensure versions repository is cloned and up to date
22
+ # @param update [Boolean] Force update (git fetch) even if already cloned
23
+ # @return [String] Path to the versions data directory
24
+ def ensure_versions_data(update: false)
25
+ if cloned?
26
+ update_clone if update || stale?
27
+ else
28
+ clone_repo
29
+ end
30
+
31
+ data_path
32
+ end
33
+
34
+ # Check if versions repository is cloned
35
+ def cloned?
36
+ File.directory?(File.join(versions_path, '.git'))
37
+ end
38
+
39
+ # Check if the clone is stale (older than 24 hours)
40
+ def stale?
41
+ return true unless cloned?
42
+
43
+ last_update_file = File.join(versions_path, '.mnenv_last_update')
44
+ return true unless File.exist?(last_update_file)
45
+
46
+ last_update = File.mtime(last_update_file)
47
+ Time.now - last_update > 86_400 # 24 hours
48
+ end
49
+
50
+ # Path to the data directory within the versions repository
51
+ def data_path
52
+ File.join(versions_path, 'data')
53
+ end
54
+
55
+ # Clone the versions repository using the git gem
56
+ def clone_repo
57
+ FileUtils.mkdir_p(mnenv_dir)
58
+ Git.clone(VERSIONS_REPO, VERSIONS_DIR, path: mnenv_dir, depth: 1)
59
+ touch_update_marker
60
+ rescue Git::GitExecuteError => e
61
+ raise "Failed to clone versions repository: #{e.message}"
62
+ end
63
+
64
+ # Update the versions repository using the git gem
65
+ def update_clone
66
+ return unless cloned?
67
+
68
+ g = Git.open(versions_path)
69
+ g.fetch
70
+ g.reset_hard('origin/main')
71
+ touch_update_marker
72
+ rescue Git::GitExecuteError => e
73
+ warn "Warning: Failed to update versions repository: #{e.message}"
74
+ end
75
+
76
+ # Force update the versions repository
77
+ def update
78
+ if cloned?
79
+ update_clone
80
+ else
81
+ clone_repo
82
+ end
83
+ data_path
84
+ end
85
+
86
+ private
87
+
88
+ def touch_update_marker
89
+ FileUtils.touch(File.join(versions_path, '.mnenv_last_update'))
90
+ end
91
+ end
92
+ end
data/lib/mnenv.rb CHANGED
@@ -3,6 +3,9 @@
3
3
  require 'lutaml/model'
4
4
  require 'thor'
5
5
 
6
+ require_relative 'mnenv/paths'
6
7
  require_relative 'mnenv/models'
7
8
  require_relative 'mnenv/commands'
9
+ require_relative 'mnenv/installer'
10
+ require_relative 'mnenv/source_registry'
8
11
  require_relative 'mnenv/cli'
data/mnenv.gemspec CHANGED
@@ -12,7 +12,7 @@ Gem::Specification.new do |spec|
12
12
  spec.description = 'Unified interface for discovering and managing Metanorma ' \
13
13
  'versions from Gemfile (Docker), Snap, Homebrew, and Chocolatey sources. ' \
14
14
  'Provides mnenv CLI command for querying version information.'
15
- spec.homepage = 'https://github.com/metanorma/versions'
15
+ spec.homepage = 'https://github.com/metanorma/mnenv'
16
16
  spec.license = 'MIT'
17
17
 
18
18
  spec.required_ruby_version = '>= 3.1'
@@ -34,10 +34,13 @@ Gem::Specification.new do |spec|
34
34
 
35
35
  # Runtime dependencies
36
36
  spec.add_dependency 'activesupport'
37
+ spec.add_dependency 'git'
37
38
  spec.add_dependency 'lutaml-model', '~> 0.7'
38
39
  spec.add_dependency 'lutaml-xsd', '~> 1.0'
39
40
  spec.add_dependency 'moxml'
40
41
  spec.add_dependency 'nokogiri'
42
+ spec.add_dependency 'octokit'
41
43
  spec.add_dependency 'paint'
42
44
  spec.add_dependency 'thor', '~> 1.0'
45
+ spec.add_dependency 'tty-prompt'
43
46
  end
@@ -0,0 +1,214 @@
1
+ #!/bin/bash
2
+ # Local cross-source switching test script for mnenv
3
+ # Tests switching between gemfile and binary for the same version
4
+ # Usage: ./scripts/cross-source-switching-test.sh [VERSION]
5
+
6
+ set -e
7
+
8
+ # Colors
9
+ RED='\033[0;31m'
10
+ GREEN='\033[0;32m'
11
+ YELLOW='\033[1;33m'
12
+ BLUE='\033[0;34m'
13
+ NC='\033[0m' # No Color
14
+
15
+ log_info() {
16
+ echo -e "${GREEN}[INFO]${NC} $1"
17
+ }
18
+
19
+ log_warn() {
20
+ echo -e "${YELLOW}[WARN]${NC} $1"
21
+ }
22
+
23
+ log_error() {
24
+ echo -e "${RED}[ERROR]${NC} $1"
25
+ }
26
+
27
+ log_step() {
28
+ echo -e "${BLUE}=== $1 ===${NC}"
29
+ }
30
+
31
+ # Version to test
32
+ VERSION="${1:-1.14.3}"
33
+
34
+ # Check if we're in the mnenv directory
35
+ if [ ! -f "mnenv.gemspec" ]; then
36
+ log_error "Please run this script from the mnenv root directory"
37
+ exit 1
38
+ fi
39
+
40
+ # Install mnenv
41
+ log_step "Installing mnenv"
42
+ bundle install > /dev/null 2>&1
43
+ rake install > /dev/null 2>&1
44
+ log_info "mnenv $(mnenv version) installed"
45
+
46
+ # Add mnenv to PATH
47
+ export PATH="$HOME/.mnenv/shims:$PATH"
48
+
49
+ # Clean up any existing test state
50
+ log_step "Cleaning up previous test state"
51
+ rm -f ~/.mnenv/version ~/.mnenv/source
52
+ rm -rf "/tmp/mnenv-cross-test-$VERSION"
53
+ mkdir -p "/tmp/mnenv-cross-test-$VERSION"
54
+
55
+ # Check binary availability
56
+ log_step "Checking binary availability for version $VERSION"
57
+ BINARY_URL="https://github.com/metanorma/packed-mn/releases/download/v${VERSION}/metanorma-$(uname -s | tr '[:upper:]' '[:lower:]')"
58
+ BINARY_AVAILABLE=false
59
+
60
+ if curl -s -f -L -o /dev/null "$BINARY_URL" 2>/dev/null; then
61
+ BINARY_AVAILABLE=true
62
+ log_info "Binary is available for $VERSION"
63
+ else
64
+ log_warn "Binary NOT available for $VERSION"
65
+ log_info "Will test gemfile installation only"
66
+ fi
67
+
68
+ # Install with gemfile
69
+ log_step "Installing Metanorma $VERSION (gemfile)"
70
+ if mnenv install "$VERSION" --source gemfile > /dev/null 2>&1; then
71
+ log_info "✓ Gemfile installation successful"
72
+ else
73
+ log_warn "⚠ Gemfile installation may have failed (may already be installed)"
74
+ fi
75
+
76
+ # Install with binary
77
+ if [ "$BINARY_AVAILABLE" = true ]; then
78
+ log_step "Installing Metanorma $VERSION (binary)"
79
+ if mnenv install "$VERSION" --source binary > /dev/null 2>&1; then
80
+ log_info "✓ Binary installation successful"
81
+ else
82
+ log_warn "⚠ Binary installation may have failed (may already be installed)"
83
+ fi
84
+
85
+ # Show both installations
86
+ log_step "Installation structure"
87
+ echo "Gemfile installation:"
88
+ ls -lah ~/.mnenv/versions/"$VERSION"/gemfile/ 2>/dev/null || echo " Not found"
89
+
90
+ echo ""
91
+ echo "Binary installation:"
92
+ ls -lah ~/.mnenv/versions/"$VERSION"/binary/ 2>/dev/null || echo " Not found"
93
+ fi
94
+
95
+ # Show installed versions
96
+ log_step "Installed versions"
97
+ mnenv versions
98
+
99
+ if [ "$BINARY_AVAILABLE" = false ]; then
100
+ echo ""
101
+ log_warn "Binary not available - skipping cross-source tests"
102
+ log_info "Binary tests require packed-mn releases"
103
+ exit 0
104
+ fi
105
+
106
+ # Test 1: Switch from gemfile to binary
107
+ log_step "Test 1: Switch from gemfile to binary"
108
+ mnenv global "$VERSION" --source gemfile
109
+ v_gemfile=$(metanorma --version 2>&1 | grep -oP '\d+\.\d+\.\d+' || echo "failed")
110
+ log_info "Gemfile source: metanorma --version = $v_gemfile"
111
+
112
+ mnenv global "$VERSION" --source binary
113
+ v_binary=$(metanorma --version 2>&1 | grep -oP '\d+\.\d+\.\d+' || echo "failed")
114
+ log_info "Binary source: metanorma --version = $v_binary"
115
+
116
+ if [ "$v_gemfile" = "$VERSION" ] && [ "$v_binary" = "$VERSION" ]; then
117
+ log_info "✓ Both sources produce correct version $VERSION"
118
+ else
119
+ log_error "✗ Version mismatch: gemfile=$v_gemfile, binary=$v_binary"
120
+ exit 1
121
+ fi
122
+
123
+ # Test 2: Switch from binary to gemfile
124
+ log_step "Test 2: Switch from binary to gemfile"
125
+ mnenv global "$VERSION" --source binary
126
+ v1=$(metanorma --version 2>&1 | grep -oP '\d+\.\d+\.\d+' || echo "failed")
127
+
128
+ mnenv global "$VERSION" --source gemfile
129
+ v2=$(metanorma --version 2>&1 | grep -oP '\d+\.\d+\.\d+' || echo "failed")
130
+
131
+ if [ "$v1" = "$VERSION" ] && [ "$v2" = "$VERSION" ]; then
132
+ log_info "✓ Bidirectional switching works"
133
+ else
134
+ log_error "✗ Version mismatch: binary=$v1, gemfile=$v2"
135
+ exit 1
136
+ fi
137
+
138
+ # Test 3: Rapid switching
139
+ log_step "Test 3: Rapid source switching (5 rounds)"
140
+ for i in {1..5}; do
141
+ mnenv global "$VERSION" --source gemfile > /dev/null
142
+ v_g=$(metanorma --version 2>&1 | grep -oP '\d+\.\d+\.\d+' || echo "failed")
143
+
144
+ mnenv global "$VERSION" --source binary > /dev/null
145
+ v_b=$(metanorma --version 2>&1 | grep -oP '\d+\.\d+\.\d+' || echo "failed")
146
+
147
+ if [ "$v_g" != "$VERSION" ] || [ "$v_b" != "$VERSION" ]; then
148
+ log_error "✗ Round $i failed: gemfile=$v_g, binary=$v_b"
149
+ exit 1
150
+ fi
151
+ done
152
+ log_info "✓ Rapid switching test passed"
153
+
154
+ # Test 4: Local override with different source
155
+ log_step "Test 4: Local override with source change"
156
+ cd "/tmp/mnenv-cross-test-$VERSION"
157
+
158
+ # Set global to gemfile
159
+ mnenv global "$VERSION" --source gemfile > /dev/null
160
+ v_global=$(metanorma --version 2>&1 | grep -oP '\d+\.\d+\.\d+' || echo "failed")
161
+
162
+ # Set local to binary
163
+ mnenv local "$VERSION" --source binary > /dev/null
164
+ v_local=$(metanorma --version 2>&1 | grep -oP '\d+\.\d+\.\d+' || echo "failed")
165
+
166
+ if [ "$v_global" = "$VERSION" ] && [ "$v_local" = "$VERSION" ]; then
167
+ log_info "✓ Local override works with different source"
168
+ else
169
+ log_error "✗ Local override failed"
170
+ exit 1
171
+ fi
172
+
173
+ # Test 5: Shell override takes precedence
174
+ log_step "Test 5: Shell session override"
175
+ # Local is binary
176
+ export METANORMA_VERSION="$VERSION"
177
+ export METANORMA_SOURCE=gemfile
178
+ v_env=$(metanorma --version 2>&1 | grep -oP '\d+\.\d+\.\d+' || echo "failed")
179
+
180
+ if [ "$v_env" = "$VERSION" ]; then
181
+ log_info "✓ Environment variable overrides local file"
182
+ else
183
+ log_error "✗ Environment override failed"
184
+ exit 1
185
+ fi
186
+ unset METANORMA_VERSION METANORMA_SOURCE
187
+
188
+ # Test 6: Version list shows both sources
189
+ log_step "Test 6: Version list shows both sources"
190
+ if mnenv versions | grep -q "$VERSION.*gemfile"; then
191
+ log_info "✓ Gemfile source shown in version list"
192
+ else
193
+ log_error "✗ Gemfile source not in version list"
194
+ exit 1
195
+ fi
196
+
197
+ if mnenv versions | grep -q "$VERSION.*binary"; then
198
+ log_info "✓ Binary source shown in version list"
199
+ else
200
+ log_error "✗ Binary source not in version list"
201
+ exit 1
202
+ fi
203
+
204
+ # Final state
205
+ log_step "All tests passed!"
206
+ echo ""
207
+ echo "Final state:"
208
+ echo " Global version: $(cat ~/.mnenv/version 2>/dev/null || echo 'not set')"
209
+ echo " Global source: $(cat ~/.mnenv/source 2>/dev/null || echo 'not set')"
210
+ echo " Current metanorma: $(metanorma --version 2>&1 | head -1)"
211
+
212
+ # Clean up
213
+ cd /
214
+ rm -rf "/tmp/mnenv-cross-test-$VERSION"
@@ -0,0 +1,89 @@
1
+ #!/bin/bash
2
+ # Local integration test script for mnenv
3
+ # Usage: ./scripts/integration-test.sh [gemfile|binary]
4
+
5
+ set -e
6
+
7
+ # Configuration
8
+ METANORMA_VERSION="${METANORMA_VERSION:-1.14.4}"
9
+ METHOD="${1:-gemfile}"
10
+ SAMPLES_REPO="${SAMPLES_REPO:-metanorma/mn-samples-cc}"
11
+ SAMPLES_DIR="/tmp/mn-samples-cc"
12
+
13
+ # Colors
14
+ RED='\033[0;31m'
15
+ GREEN='\033[0;32m'
16
+ YELLOW='\033[1;33m'
17
+ NC='\033[0m' # No Color
18
+
19
+ log_info() {
20
+ echo -e "${GREEN}[INFO]${NC} $1"
21
+ }
22
+
23
+ log_warn() {
24
+ echo -e "${YELLOW}[WARN]${NC} $1"
25
+ }
26
+
27
+ log_error() {
28
+ echo -e "${RED}[ERROR]${NC} $1"
29
+ }
30
+
31
+ # Check if we're in the mnenv directory
32
+ if [ ! -f "mnenv.gemspec" ]; then
33
+ log_error "Please run this script from the mnenv root directory"
34
+ exit 1
35
+ fi
36
+
37
+ # Check if mnenv is installed
38
+ if ! command -v mnenv &> /dev/null; then
39
+ log_info "Installing mnenv..."
40
+ bundle install
41
+ rake install
42
+ else
43
+ log_info "mnenv is already installed"
44
+ fi
45
+
46
+ # Install Metanorma
47
+ log_info "Installing Metanorma ${METANORMA_VERSION} (method: ${METHOD})..."
48
+ mnenv install "${METANORMA_VERSION}" --source "${METHOD}"
49
+ mnenv global "${METANORMA_VERSION}" --source "${METHOD}"
50
+
51
+ # Show installed versions
52
+ log_info "Installed versions:"
53
+ mnenv versions
54
+
55
+ # Clone or update samples repository
56
+ if [ -d "$SAMPLES_DIR" ]; then
57
+ log_info "Updating samples repository..."
58
+ cd "$SAMPLES_DIR"
59
+ git pull
60
+ else
61
+ log_info "Cloning samples repository..."
62
+ git clone "https://github.com/${SAMPLES_REPO}.git" "$SAMPLES_DIR"
63
+ cd "$SAMPLES_DIR"
64
+ fi
65
+
66
+ # Test single document compilation
67
+ log_info "Testing single document compilation..."
68
+ if metanorma compile sources/cc-18011.adoc; then
69
+ log_info "Document compilation successful"
70
+ ls -lah sources/cc-18011.* 2>/dev/null || true
71
+ else
72
+ log_error "Document compilation failed"
73
+ exit 1
74
+ fi
75
+
76
+ # Test site generation
77
+ log_info "Testing site generation..."
78
+ if metanorma site generate --agree-to-terms; then
79
+ log_info "Site generation successful"
80
+ if [ -d "site" ]; then
81
+ log_info "Site contents:"
82
+ ls -lah site/ | head -20
83
+ fi
84
+ else
85
+ log_error "Site generation failed"
86
+ exit 1
87
+ fi
88
+
89
+ log_info "All tests passed!"