mnenv 0.1.0 → 0.1.2

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 (306) 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 +111 -0
  22. data/lib/mnenv/commands/version_command.rb +167 -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 +46 -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/platform_detector.rb +109 -0
  44. data/lib/mnenv/repository.rb +50 -35
  45. data/lib/mnenv/resolver +72 -0
  46. data/lib/mnenv/shells/base.rb +32 -0
  47. data/lib/mnenv/shells/bash.rb +72 -0
  48. data/lib/mnenv/shells/cmd.rb +108 -0
  49. data/lib/mnenv/shells/factory.rb +82 -0
  50. data/lib/mnenv/shells/power_shell.rb +110 -0
  51. data/lib/mnenv/shim_manager.rb +121 -0
  52. data/lib/mnenv/snap.rb +7 -0
  53. data/lib/mnenv/snap_repository.rb +2 -19
  54. data/lib/mnenv/source_registry.rb +69 -0
  55. data/lib/mnenv/sources.rb +46 -0
  56. data/lib/mnenv/version.rb +1 -1
  57. data/lib/mnenv/version_resolver.rb +108 -0
  58. data/lib/mnenv/versions_manager.rb +92 -0
  59. data/lib/mnenv.rb +6 -0
  60. data/mnenv.gemspec +4 -1
  61. data/scripts/cross-source-switching-test.sh +214 -0
  62. data/scripts/integration-test.sh +89 -0
  63. data/scripts/version-switching-test.sh +151 -0
  64. metadata +85 -247
  65. data/data/chocolatey/versions.yaml +0 -812
  66. data/data/gemfile/v1.1.6/Gemfile +0 -4
  67. data/data/gemfile/v1.1.6/Gemfile.lock.archived +0 -232
  68. data/data/gemfile/v1.1.7/Gemfile +0 -4
  69. data/data/gemfile/v1.1.7/Gemfile.lock.archived +0 -235
  70. data/data/gemfile/v1.1.8/Gemfile +0 -4
  71. data/data/gemfile/v1.1.8/Gemfile.lock.archived +0 -238
  72. data/data/gemfile/v1.10.0/Gemfile +0 -5
  73. data/data/gemfile/v1.10.0/Gemfile.lock.archived +0 -930
  74. data/data/gemfile/v1.10.1/Gemfile +0 -5
  75. data/data/gemfile/v1.10.1/Gemfile.lock.archived +0 -929
  76. data/data/gemfile/v1.10.10/Gemfile +0 -5
  77. data/data/gemfile/v1.10.10/Gemfile.lock.archived +0 -973
  78. data/data/gemfile/v1.10.11/Gemfile +0 -5
  79. data/data/gemfile/v1.10.11/Gemfile.lock.archived +0 -975
  80. data/data/gemfile/v1.10.2/Gemfile +0 -5
  81. data/data/gemfile/v1.10.2/Gemfile.lock.archived +0 -939
  82. data/data/gemfile/v1.10.3/Gemfile +0 -5
  83. data/data/gemfile/v1.10.3/Gemfile.lock.archived +0 -946
  84. data/data/gemfile/v1.10.5/Gemfile +0 -5
  85. data/data/gemfile/v1.10.5/Gemfile.lock.archived +0 -958
  86. data/data/gemfile/v1.10.6/Gemfile +0 -5
  87. data/data/gemfile/v1.10.6/Gemfile.lock.archived +0 -969
  88. data/data/gemfile/v1.10.7/Gemfile +0 -5
  89. data/data/gemfile/v1.10.7/Gemfile.lock.archived +0 -969
  90. data/data/gemfile/v1.10.8/Gemfile +0 -5
  91. data/data/gemfile/v1.10.8/Gemfile.lock.archived +0 -968
  92. data/data/gemfile/v1.10.9/Gemfile +0 -5
  93. data/data/gemfile/v1.10.9/Gemfile.lock.archived +0 -972
  94. data/data/gemfile/v1.11.0/Gemfile +0 -5
  95. data/data/gemfile/v1.11.0/Gemfile.lock.archived +0 -971
  96. data/data/gemfile/v1.11.1/Gemfile +0 -5
  97. data/data/gemfile/v1.11.1/Gemfile.lock.archived +0 -975
  98. data/data/gemfile/v1.11.4/Gemfile +0 -5
  99. data/data/gemfile/v1.11.4/Gemfile.lock.archived +0 -1046
  100. data/data/gemfile/v1.11.5/Gemfile +0 -5
  101. data/data/gemfile/v1.11.5/Gemfile.lock.archived +0 -1047
  102. data/data/gemfile/v1.12.10/Gemfile +0 -3
  103. data/data/gemfile/v1.12.10/Gemfile.lock.archived +0 -1073
  104. data/data/gemfile/v1.12.3/Gemfile +0 -3
  105. data/data/gemfile/v1.12.3/Gemfile.lock.archived +0 -1050
  106. data/data/gemfile/v1.12.4/Gemfile +0 -3
  107. data/data/gemfile/v1.12.4/Gemfile.lock.archived +0 -1056
  108. data/data/gemfile/v1.12.5/Gemfile +0 -3
  109. data/data/gemfile/v1.12.5/Gemfile.lock.archived +0 -1054
  110. data/data/gemfile/v1.12.6/Gemfile +0 -3
  111. data/data/gemfile/v1.12.6/Gemfile.lock.archived +0 -1056
  112. data/data/gemfile/v1.12.8/Gemfile +0 -3
  113. data/data/gemfile/v1.12.8/Gemfile.lock.archived +0 -1063
  114. data/data/gemfile/v1.13.0/Gemfile +0 -3
  115. data/data/gemfile/v1.13.0/Gemfile.lock.archived +0 -1074
  116. data/data/gemfile/v1.13.2/Gemfile +0 -3
  117. data/data/gemfile/v1.13.2/Gemfile.lock.archived +0 -899
  118. data/data/gemfile/v1.13.3/Gemfile +0 -3
  119. data/data/gemfile/v1.13.3/Gemfile.lock.archived +0 -938
  120. data/data/gemfile/v1.13.4/Gemfile +0 -3
  121. data/data/gemfile/v1.13.4/Gemfile.lock.archived +0 -938
  122. data/data/gemfile/v1.13.5/Gemfile +0 -3
  123. data/data/gemfile/v1.13.5/Gemfile.lock.archived +0 -944
  124. data/data/gemfile/v1.13.7/Gemfile +0 -3
  125. data/data/gemfile/v1.13.7/Gemfile.lock.archived +0 -944
  126. data/data/gemfile/v1.13.8/Gemfile +0 -3
  127. data/data/gemfile/v1.13.8/Gemfile.lock.archived +0 -944
  128. data/data/gemfile/v1.13.9/Gemfile +0 -3
  129. data/data/gemfile/v1.13.9/Gemfile.lock.archived +0 -956
  130. data/data/gemfile/v1.14.3/Gemfile +0 -3
  131. data/data/gemfile/v1.14.3/Gemfile.lock.archived +0 -950
  132. data/data/gemfile/v1.2.12/Gemfile +0 -3
  133. data/data/gemfile/v1.2.12/Gemfile.lock.archived +0 -283
  134. data/data/gemfile/v1.2.2/Gemfile +0 -4
  135. data/data/gemfile/v1.2.2/Gemfile.lock.archived +0 -224
  136. data/data/gemfile/v1.2.3/Gemfile +0 -4
  137. data/data/gemfile/v1.2.3/Gemfile.lock.archived +0 -231
  138. data/data/gemfile/v1.2.6/Gemfile +0 -4
  139. data/data/gemfile/v1.2.6/Gemfile.lock.archived +0 -239
  140. data/data/gemfile/v1.2.8/Gemfile +0 -4
  141. data/data/gemfile/v1.2.8/Gemfile.lock.archived +0 -233
  142. data/data/gemfile/v1.2.9/Gemfile +0 -4
  143. data/data/gemfile/v1.2.9/Gemfile.lock.archived +0 -245
  144. data/data/gemfile/v1.3.1/Gemfile +0 -3
  145. data/data/gemfile/v1.3.1/Gemfile.lock.archived +0 -296
  146. data/data/gemfile/v1.3.2/Gemfile +0 -3
  147. data/data/gemfile/v1.3.2/Gemfile.lock.archived +0 -296
  148. data/data/gemfile/v1.3.4/Gemfile +0 -3
  149. data/data/gemfile/v1.3.4/Gemfile.lock.archived +0 -284
  150. data/data/gemfile/v1.3.5/Gemfile +0 -3
  151. data/data/gemfile/v1.3.5/Gemfile.lock.archived +0 -284
  152. data/data/gemfile/v1.3.6/Gemfile +0 -3
  153. data/data/gemfile/v1.3.6/Gemfile.lock.archived +0 -286
  154. data/data/gemfile/v1.3.9/Gemfile +0 -3
  155. data/data/gemfile/v1.3.9/Gemfile.lock.archived +0 -334
  156. data/data/gemfile/v1.4.0/Gemfile +0 -3
  157. data/data/gemfile/v1.4.0/Gemfile.lock.archived +0 -330
  158. data/data/gemfile/v1.4.10/Gemfile +0 -4
  159. data/data/gemfile/v1.4.10/Gemfile.lock.archived +0 -461
  160. data/data/gemfile/v1.4.11/Gemfile +0 -4
  161. data/data/gemfile/v1.4.11/Gemfile.lock.archived +0 -452
  162. data/data/gemfile/v1.4.12/Gemfile +0 -4
  163. data/data/gemfile/v1.4.12/Gemfile.lock.archived +0 -452
  164. data/data/gemfile/v1.4.13/Gemfile +0 -4
  165. data/data/gemfile/v1.4.13/Gemfile.lock.archived +0 -455
  166. data/data/gemfile/v1.4.14/Gemfile +0 -4
  167. data/data/gemfile/v1.4.14/Gemfile.lock.archived +0 -456
  168. data/data/gemfile/v1.4.18/Gemfile +0 -3
  169. data/data/gemfile/v1.4.18/Gemfile.lock.archived +0 -486
  170. data/data/gemfile/v1.4.3/Gemfile +0 -3
  171. data/data/gemfile/v1.4.3/Gemfile.lock.archived +0 -339
  172. data/data/gemfile/v1.4.4/Gemfile +0 -3
  173. data/data/gemfile/v1.4.4/Gemfile.lock.archived +0 -339
  174. data/data/gemfile/v1.4.5/Gemfile +0 -3
  175. data/data/gemfile/v1.4.5/Gemfile.lock.archived +0 -348
  176. data/data/gemfile/v1.4.6/Gemfile +0 -3
  177. data/data/gemfile/v1.4.6/Gemfile.lock.archived +0 -357
  178. data/data/gemfile/v1.4.7/Gemfile +0 -3
  179. data/data/gemfile/v1.4.7/Gemfile.lock.archived +0 -391
  180. data/data/gemfile/v1.4.8/Gemfile +0 -3
  181. data/data/gemfile/v1.4.8/Gemfile.lock.archived +0 -445
  182. data/data/gemfile/v1.4.9/Gemfile +0 -3
  183. data/data/gemfile/v1.4.9/Gemfile.lock.archived +0 -448
  184. data/data/gemfile/v1.5.0/Gemfile +0 -3
  185. data/data/gemfile/v1.5.0/Gemfile.lock.archived +0 -478
  186. data/data/gemfile/v1.5.10/Gemfile +0 -3
  187. data/data/gemfile/v1.5.10/Gemfile.lock.archived +0 -668
  188. data/data/gemfile/v1.5.11/Gemfile +0 -3
  189. data/data/gemfile/v1.5.11/Gemfile.lock.archived +0 -668
  190. data/data/gemfile/v1.5.15/Gemfile +0 -3
  191. data/data/gemfile/v1.5.15/Gemfile.lock.archived +0 -686
  192. data/data/gemfile/v1.5.16/Gemfile +0 -3
  193. data/data/gemfile/v1.5.16/Gemfile.lock.archived +0 -684
  194. data/data/gemfile/v1.5.17/Gemfile +0 -3
  195. data/data/gemfile/v1.5.17/Gemfile.lock.archived +0 -684
  196. data/data/gemfile/v1.5.18/Gemfile +0 -5
  197. data/data/gemfile/v1.5.18/Gemfile.lock.archived +0 -691
  198. data/data/gemfile/v1.5.19/Gemfile +0 -5
  199. data/data/gemfile/v1.5.19/Gemfile.lock.archived +0 -703
  200. data/data/gemfile/v1.5.20/Gemfile +0 -5
  201. data/data/gemfile/v1.5.20/Gemfile.lock.archived +0 -703
  202. data/data/gemfile/v1.5.21/Gemfile +0 -5
  203. data/data/gemfile/v1.5.21/Gemfile.lock.archived +0 -707
  204. data/data/gemfile/v1.5.22/Gemfile +0 -5
  205. data/data/gemfile/v1.5.22/Gemfile.lock.archived +0 -707
  206. data/data/gemfile/v1.5.23/Gemfile +0 -5
  207. data/data/gemfile/v1.5.23/Gemfile.lock.archived +0 -711
  208. data/data/gemfile/v1.5.24/Gemfile +0 -5
  209. data/data/gemfile/v1.5.24/Gemfile.lock.archived +0 -711
  210. data/data/gemfile/v1.5.3/Gemfile +0 -3
  211. data/data/gemfile/v1.5.3/Gemfile.lock.archived +0 -651
  212. data/data/gemfile/v1.5.4/Gemfile +0 -3
  213. data/data/gemfile/v1.5.4/Gemfile.lock.archived +0 -657
  214. data/data/gemfile/v1.5.5/Gemfile +0 -3
  215. data/data/gemfile/v1.5.5/Gemfile.lock.archived +0 -657
  216. data/data/gemfile/v1.5.6/Gemfile +0 -3
  217. data/data/gemfile/v1.5.6/Gemfile.lock.archived +0 -657
  218. data/data/gemfile/v1.5.7/Gemfile +0 -3
  219. data/data/gemfile/v1.5.7/Gemfile.lock.archived +0 -657
  220. data/data/gemfile/v1.5.8/Gemfile +0 -3
  221. data/data/gemfile/v1.5.8/Gemfile.lock.archived +0 -655
  222. data/data/gemfile/v1.5.9/Gemfile +0 -3
  223. data/data/gemfile/v1.5.9/Gemfile.lock.archived +0 -656
  224. data/data/gemfile/v1.6.1/Gemfile +0 -5
  225. data/data/gemfile/v1.6.1/Gemfile.lock.archived +0 -721
  226. data/data/gemfile/v1.6.10/Gemfile +0 -5
  227. data/data/gemfile/v1.6.10/Gemfile.lock.archived +0 -744
  228. data/data/gemfile/v1.6.11/Gemfile +0 -5
  229. data/data/gemfile/v1.6.11/Gemfile.lock.archived +0 -744
  230. data/data/gemfile/v1.6.12/Gemfile +0 -5
  231. data/data/gemfile/v1.6.12/Gemfile.lock.archived +0 -745
  232. data/data/gemfile/v1.6.13/Gemfile +0 -5
  233. data/data/gemfile/v1.6.13/Gemfile.lock.archived +0 -745
  234. data/data/gemfile/v1.6.14/Gemfile +0 -5
  235. data/data/gemfile/v1.6.14/Gemfile.lock.archived +0 -754
  236. data/data/gemfile/v1.6.15/Gemfile +0 -5
  237. data/data/gemfile/v1.6.15/Gemfile.lock.archived +0 -757
  238. data/data/gemfile/v1.6.2/Gemfile +0 -5
  239. data/data/gemfile/v1.6.2/Gemfile.lock.archived +0 -718
  240. data/data/gemfile/v1.6.3/Gemfile +0 -5
  241. data/data/gemfile/v1.6.3/Gemfile.lock.archived +0 -728
  242. data/data/gemfile/v1.6.4/Gemfile +0 -5
  243. data/data/gemfile/v1.6.4/Gemfile.lock.archived +0 -730
  244. data/data/gemfile/v1.6.5/Gemfile +0 -5
  245. data/data/gemfile/v1.6.5/Gemfile.lock.archived +0 -733
  246. data/data/gemfile/v1.6.6/Gemfile +0 -5
  247. data/data/gemfile/v1.6.6/Gemfile.lock.archived +0 -733
  248. data/data/gemfile/v1.6.7/Gemfile +0 -5
  249. data/data/gemfile/v1.6.7/Gemfile.lock.archived +0 -733
  250. data/data/gemfile/v1.6.9/Gemfile +0 -5
  251. data/data/gemfile/v1.6.9/Gemfile.lock.archived +0 -744
  252. data/data/gemfile/v1.7.0/Gemfile +0 -5
  253. data/data/gemfile/v1.7.0/Gemfile.lock.archived +0 -750
  254. data/data/gemfile/v1.7.1/Gemfile +0 -5
  255. data/data/gemfile/v1.7.1/Gemfile.lock.archived +0 -750
  256. data/data/gemfile/v1.7.2/Gemfile +0 -5
  257. data/data/gemfile/v1.7.2/Gemfile.lock.archived +0 -747
  258. data/data/gemfile/v1.7.3/Gemfile +0 -5
  259. data/data/gemfile/v1.7.3/Gemfile.lock.archived +0 -755
  260. data/data/gemfile/v1.7.4/Gemfile +0 -5
  261. data/data/gemfile/v1.7.4/Gemfile.lock.archived +0 -756
  262. data/data/gemfile/v1.7.5/Gemfile +0 -5
  263. data/data/gemfile/v1.7.5/Gemfile.lock.archived +0 -759
  264. data/data/gemfile/v1.7.6/Gemfile +0 -5
  265. data/data/gemfile/v1.7.6/Gemfile.lock.archived +0 -768
  266. data/data/gemfile/v1.8.10/Gemfile +0 -5
  267. data/data/gemfile/v1.8.10/Gemfile.lock.archived +0 -792
  268. data/data/gemfile/v1.8.11/Gemfile +0 -5
  269. data/data/gemfile/v1.8.11/Gemfile.lock.archived +0 -862
  270. data/data/gemfile/v1.8.3/Gemfile +0 -5
  271. data/data/gemfile/v1.8.3/Gemfile.lock.archived +0 -773
  272. data/data/gemfile/v1.8.4/Gemfile +0 -5
  273. data/data/gemfile/v1.8.4/Gemfile.lock.archived +0 -768
  274. data/data/gemfile/v1.8.5/Gemfile +0 -5
  275. data/data/gemfile/v1.8.5/Gemfile.lock.archived +0 -768
  276. data/data/gemfile/v1.8.6/Gemfile +0 -5
  277. data/data/gemfile/v1.8.6/Gemfile.lock.archived +0 -777
  278. data/data/gemfile/v1.8.7/Gemfile +0 -5
  279. data/data/gemfile/v1.8.7/Gemfile.lock.archived +0 -777
  280. data/data/gemfile/v1.8.8/Gemfile +0 -5
  281. data/data/gemfile/v1.8.8/Gemfile.lock.archived +0 -778
  282. data/data/gemfile/v1.8.9/Gemfile +0 -5
  283. data/data/gemfile/v1.8.9/Gemfile.lock.archived +0 -775
  284. data/data/gemfile/v1.9.0/Gemfile +0 -5
  285. data/data/gemfile/v1.9.0/Gemfile.lock.archived +0 -871
  286. data/data/gemfile/v1.9.1/Gemfile +0 -5
  287. data/data/gemfile/v1.9.1/Gemfile.lock.archived +0 -906
  288. data/data/gemfile/v1.9.2/Gemfile +0 -5
  289. data/data/gemfile/v1.9.2/Gemfile.lock.archived +0 -898
  290. data/data/gemfile/v1.9.3/Gemfile +0 -5
  291. data/data/gemfile/v1.9.3/Gemfile.lock.archived +0 -898
  292. data/data/gemfile/v1.9.4/Gemfile +0 -5
  293. data/data/gemfile/v1.9.4/Gemfile.lock.archived +0 -901
  294. data/data/gemfile/v1.9.5/Gemfile +0 -5
  295. data/data/gemfile/v1.9.5/Gemfile.lock.archived +0 -903
  296. data/data/gemfile/v1.9.6/Gemfile +0 -5
  297. data/data/gemfile/v1.9.6/Gemfile.lock.archived +0 -900
  298. data/data/gemfile/v1.9.7/Gemfile +0 -5
  299. data/data/gemfile/v1.9.7/Gemfile.lock.archived +0 -922
  300. data/data/gemfile/v1.9.8/Gemfile +0 -5
  301. data/data/gemfile/v1.9.8/Gemfile.lock.archived +0 -933
  302. data/data/gemfile/versions.yaml +0 -751
  303. data/data/homebrew/versions.yaml +0 -567
  304. data/data/snap/github_tags.json +0 -42
  305. data/data/snap/versions.yaml +0 -589
  306. data/snapcraft-list-copied-from-site.md +0 -101
@@ -0,0 +1,109 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mnenv
4
+ # Centralized platform detection for consistent OS/architecture/variant detection
5
+ # across the codebase. Used by binary installer, repository, and CLI.
6
+ class PlatformDetector
7
+ class UnsupportedPlatform < StandardError; end
8
+
9
+ class << self
10
+ # Detect the current operating system
11
+ # @return [String] 'linux', 'darwin', or 'windows'
12
+ # @raise [UnsupportedPlatform] if platform is not supported
13
+ def os
14
+ case RbConfig::CONFIG['host_os']
15
+ when /linux/
16
+ 'linux'
17
+ when /darwin/
18
+ 'darwin'
19
+ when /mswin|mingw|cygwin/
20
+ 'windows'
21
+ else
22
+ raise UnsupportedPlatform, "Unsupported platform: #{RbConfig::CONFIG['host_os']}"
23
+ end
24
+ end
25
+
26
+ # Detect the current CPU architecture
27
+ # @return [String] 'arm64' or 'x86_64'
28
+ def arch
29
+ case RbConfig::CONFIG['host_cpu']
30
+ when /arm64|aarch64/
31
+ 'arm64'
32
+ when /x86_64|x64/
33
+ 'x86_64'
34
+ else
35
+ 'x86_64' # Default fallback
36
+ end
37
+ end
38
+
39
+ # Detect libc variant (for Linux)
40
+ # @return [String, nil] 'musl' for Alpine/musl systems, nil for glibc
41
+ def variant
42
+ return nil unless os == 'linux'
43
+
44
+ 'musl' if musl?
45
+ end
46
+
47
+ # Check if running on a musl-based system (Alpine Linux)
48
+ # @return [Boolean]
49
+ def musl?
50
+ File.exist?('/etc/alpine-release') ||
51
+ File.symlink?('/lib/libc.musl-x86_64.so.1')
52
+ rescue StandardError
53
+ false
54
+ end
55
+
56
+ # Check if running on Windows
57
+ # @return [Boolean]
58
+ def windows?
59
+ os == 'windows'
60
+ end
61
+
62
+ # Check if running on macOS
63
+ # @return [Boolean]
64
+ def macos?
65
+ os == 'darwin'
66
+ end
67
+
68
+ # Check if running on Linux
69
+ # @return [Boolean]
70
+ def linux?
71
+ os == 'linux'
72
+ end
73
+
74
+ # Get platform info as a hash
75
+ # @return [Hash] With keys :os, :arch, :variant
76
+ def to_h
77
+ {
78
+ os: os,
79
+ arch: arch,
80
+ variant: variant
81
+ }
82
+ end
83
+
84
+ # Get a human-readable platform string
85
+ # @return [String] e.g., "linux-x86_64", "darwin-arm64", "linux-musl-x86_64"
86
+ def platform_string
87
+ parts = [os]
88
+ parts << variant if variant
89
+ parts << arch
90
+ parts.join('-')
91
+ end
92
+
93
+ # Get all possible platform strings for binary matching
94
+ # (includes variant-specific and generic versions)
95
+ # @return [Array<String>] List of platform strings in order of preference
96
+ def platform_candidates
97
+ candidates = []
98
+
99
+ # With variant (if applicable)
100
+ candidates << "#{os}-#{variant}-#{arch}" if variant
101
+
102
+ # Without variant
103
+ candidates << "#{os}-#{arch}"
104
+
105
+ candidates
106
+ end
107
+ end
108
+ end
109
+ end
@@ -2,16 +2,32 @@
2
2
 
3
3
  require 'yaml'
4
4
  require 'fileutils'
5
+ require_relative 'versions_manager'
5
6
 
6
7
  module Mnenv
7
8
  class Repository
9
+ # Default versions manager (shared across all repositories)
10
+ class << self
11
+ def versions_manager
12
+ @versions_manager ||= VersionsManager.new
13
+ end
14
+
15
+ attr_writer :versions_manager
16
+
17
+ # Force update the versions data
18
+ def update_versions
19
+ versions_manager.update
20
+ end
21
+ end
22
+
8
23
  attr_reader :data_dir, :versions_file_path
9
24
 
10
- def initialize(data_dir: nil)
11
- @data_dir = data_dir || default_data_dir
25
+ def initialize(data_dir: nil, update: false)
26
+ @data_dir = data_dir || cli_data_dir || default_data_dir
12
27
  @versions_file_path = File.join(@data_dir, 'versions.yaml')
13
28
  @versions_cache = {}
14
- load if File.file?(versions_file_path)
29
+ @update = update
30
+ load
15
31
  end
16
32
 
17
33
  def find(version_number) = @versions_cache[version_number]
@@ -37,22 +53,29 @@ module Mnenv
37
53
  protected
38
54
 
39
55
  def load
40
- data = YAML.load_file(versions_file_path)
56
+ data = fetch_versions_data
41
57
  return if data.nil? || data['versions'].nil?
42
58
 
43
59
  data['versions'].each do |version_hash|
44
60
  version = version_class.new(version_hash)
45
- @versions_cache[version.version] = version
61
+ cache_version(version)
46
62
  end
47
63
  end
48
64
 
65
+ # Override in subclasses for custom caching (e.g., SnapRepository uses composite keys)
66
+ def cache_version(version)
67
+ @versions_cache[version.version] = version
68
+ end
69
+
49
70
  def persist
50
- FileUtils.mkdir_p(data_dir)
71
+ return unless @data_dir
72
+
73
+ FileUtils.mkdir_p(@data_dir)
51
74
  output = {
52
75
  'metadata' => metadata,
53
- 'versions' => @versions_cache.values.sort.map { |v| version_to_hash(v) }
76
+ 'versions' => @versions_cache.values.sort.map(&:to_hash)
54
77
  }
55
- File.write(versions_file_path, output.to_yaml)
78
+ File.write(@versions_file_path, output.to_yaml)
56
79
  end
57
80
 
58
81
  def metadata
@@ -68,38 +91,30 @@ module Mnenv
68
91
 
69
92
  def source_name = raise NotImplementedError
70
93
 
71
- def default_data_dir = File.join(__dir__, '..', '..', 'data', source_name.to_s)
72
-
73
94
  private
74
95
 
75
- def version_to_hash(version)
76
- hash = {
77
- 'version' => version.version,
78
- 'published_at' => format_timestamp(version.published_at),
79
- 'parsed_at' => format_timestamp(version.parsed_at)
80
- }
96
+ def cli_data_dir
97
+ # Check if a data directory was specified via CLI option
98
+ return nil unless defined?(Mnenv::Cli) && Mnenv::Cli.data_dir
81
99
 
82
- # Add subclass-specific fields
83
- case version
84
- when GemfileVersion
85
- hash['gemfile_exists'] = version.gemfile_exists
86
- hash['gemfile_path'] = version.gemfile_path
87
- hash['gemfile_lock_path'] = version.gemfile_lock_path
88
- when SnapVersion
89
- hash['revision'] = version.revision
90
- hash['arch'] = version.arch
91
- hash['channel'] = version.channel
92
- when HomebrewVersion
93
- hash['tag_name'] = version.tag_name
94
- hash['commit_sha'] = version.commit_sha
95
- when ChocolateyVersion
96
- hash['package_name'] = version.package_name
97
- hash['is_pre_release'] = version.is_pre_release
98
- end
100
+ source_dir = Mnenv::Cli.data_dir
101
+ # If the CLI data-dir is specified, use it directly with the source name
102
+ File.join(source_dir, source_name.to_s)
103
+ end
99
104
 
100
- hash
105
+ def default_data_dir
106
+ # Use the versions manager to ensure data is available
107
+ data_path = self.class.versions_manager.ensure_versions_data(update: @update)
108
+ File.join(data_path, source_name.to_s)
101
109
  end
102
110
 
103
- def format_timestamp(t) = t&.strftime('%Y-%m-%dT%H:%M:%SZ')
111
+ def fetch_versions_data
112
+ return unless File.file?(@versions_file_path)
113
+
114
+ YAML.safe_load(File.read(@versions_file_path), permitted_classes: [Time, Symbol, Date])
115
+ rescue StandardError => e
116
+ warn "Warning: Failed to load versions from #{@versions_file_path}: #{e.message}"
117
+ nil
118
+ end
104
119
  end
105
120
  end
@@ -0,0 +1,72 @@
1
+ #!/bin/bash
2
+ # Version and source resolver for mnenv shims
3
+ # Usage: ./resolver "version" | ./resolver "source"
4
+
5
+ set -e
6
+
7
+ export MNENV_ROOT="${MNENV_ROOT:-$HOME/.mnenv}"
8
+
9
+ resolve_version() {
10
+ # 1. Check METANORMA_VERSION environment variable
11
+ if [ -n "$METANORMA_VERSION" ]; then
12
+ echo "$METANORMA_VERSION"
13
+ return 0
14
+ fi
15
+
16
+ # 2. Check .metanorma-version file (walk up directory tree)
17
+ local dir="$PWD"
18
+ while [ -n "$dir" ] && [ "$dir" != "/" ]; do
19
+ if [ -f "$dir/.metanorma-version" ]; then
20
+ cat "$dir/.metanorma-version"
21
+ return 0
22
+ fi
23
+ dir="${dir%/*}"
24
+ done
25
+
26
+ # 3. Check ~/.mnenv/version (global default)
27
+ if [ -f "$MNENV_ROOT/version" ]; then
28
+ cat "$MNENV_ROOT/version"
29
+ return 0
30
+ fi
31
+
32
+ # 4. No version found
33
+ return 1
34
+ }
35
+
36
+ resolve_source() {
37
+ # 1. Check METANORMA_SOURCE environment variable
38
+ if [ -n "$METANORMA_SOURCE" ]; then
39
+ echo "$METANORMA_SOURCE"
40
+ return 0
41
+ fi
42
+
43
+ # 2. Check .metanorma-source file (walk up directory tree)
44
+ local dir="$PWD"
45
+ while [ -n "$dir" ] && [ "$dir" != "/" ]; do
46
+ if [ -f "$dir/.metanorma-source" ]; then
47
+ cat "$dir/.metanorma-source"
48
+ return 0
49
+ fi
50
+ dir="${dir%/*}"
51
+ done
52
+
53
+ # 3. Check ~/.mnenv/source (global default)
54
+ if [ -f "$MNENV_ROOT/source" ]; then
55
+ cat "$MNENV_ROOT/source"
56
+ return 0
57
+ fi
58
+
59
+ # 4. Default to gemfile (faster for devs)
60
+ echo "gemfile"
61
+ return 0
62
+ }
63
+
64
+ # Main dispatch
65
+ case "$1" in
66
+ version) resolve_version ;;
67
+ source) resolve_source ;;
68
+ *)
69
+ echo "Usage: $0 {version|source}" >&2
70
+ exit 1
71
+ ;;
72
+ esac
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mnenv
4
+ module Shells
5
+ # Base class for shell-specific behavior
6
+ class BaseShell
7
+ def name
8
+ raise NotImplementedError
9
+ end
10
+
11
+ def shim_extension
12
+ raise NotImplementedError
13
+ end
14
+
15
+ def shim_content(executable_name)
16
+ raise NotImplementedError
17
+ end
18
+
19
+ def use_output(version, source)
20
+ raise NotImplementedError
21
+ end
22
+
23
+ def config_file
24
+ raise NotImplementedError
25
+ end
26
+
27
+ def windows?
28
+ false
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+
5
+ module Mnenv
6
+ module Shells
7
+ # Bash shell implementation (also works for sh, dash, zsh)
8
+ class BashShell < BaseShell
9
+ def name
10
+ 'bash'
11
+ end
12
+
13
+ def shim_extension
14
+ '' # No extension for bash scripts
15
+ end
16
+
17
+ def shim_content(executable_name)
18
+ <<~SHIM
19
+ #!/bin/bash
20
+ set -e
21
+
22
+ export MNENV_ROOT="${MNENV_ROOT:-$HOME/.mnenv}"
23
+
24
+ # Resolve version and source using mnenv resolver
25
+ VERSION="$("$MNENV_ROOT/lib/mnenv/resolver" "version")"
26
+ SOURCE="$("$MNENV_ROOT/lib/mnenv/resolver" "source")"
27
+
28
+ if [ -z "$VERSION" ]; then
29
+ echo "mnenv: version not set or invalid" >&2
30
+ echo "Set a version with: mnenv global <version> or mnenv local <version>" >&2
31
+ exit 1
32
+ fi
33
+
34
+ if [ -z "$SOURCE" ]; then
35
+ echo "mnenv: source not set or invalid" >&2
36
+ exit 1
37
+ fi
38
+
39
+ # Determine executable path based on source
40
+ # Installed versions are in $MNENV_ROOT/installed/<version>-<source>/
41
+ if [ "$SOURCE" = "binary" ]; then
42
+ # Binary: single self-contained binary
43
+ EXECUTABLE="$MNENV_ROOT/installed/$VERSION-$SOURCE/metanorma"
44
+ else
45
+ # Gemfile: binstub from bundle install
46
+ EXECUTABLE="$MNENV_ROOT/installed/$VERSION-$SOURCE/bin/#{executable_name}"
47
+ fi
48
+
49
+ if [ ! -f "$EXECUTABLE" ]; then
50
+ echo "mnenv: #{executable_name} not installed for version $VERSION (source: $SOURCE)" >&2
51
+ echo "Install it with: mnenv install $VERSION --source $SOURCE" >&2
52
+ exit 1
53
+ fi
54
+
55
+ exec "$EXECUTABLE" "$@"
56
+ SHIM
57
+ end
58
+
59
+ def use_output(version, source)
60
+ lines = []
61
+ lines << "export METANORMA_VERSION=#{version}"
62
+ lines << "export METANORMA_SOURCE=#{source}" if source
63
+ lines << "# Run this in your shell, or use: eval \"$(mnenv use #{version}#{" --source #{source}" if source})\""
64
+ lines.join("\n")
65
+ end
66
+
67
+ def config_file
68
+ File.expand_path('~/.bashrc')
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+
5
+ module Mnenv
6
+ module Shells
7
+ # CMD/Batch shell implementation (Windows)
8
+ class CmdShell < BaseShell
9
+ def name
10
+ 'cmd'
11
+ end
12
+
13
+ def shim_extension
14
+ '.bat'
15
+ end
16
+
17
+ def shim_content(executable_name)
18
+ <<~SHIM
19
+ @echo off
20
+ rem mnenv shim for CMD
21
+
22
+ if defined MNENV_ROOT (
23
+ set "MNENV_ROOT=%MNENV_ROOT%"
24
+ ) else (
25
+ set "MNENV_ROOT=%USERPROFILE%\\.mnenv"
26
+ )
27
+
28
+ rem Resolve version and source
29
+ set "VERSION="
30
+ set "SOURCE="
31
+
32
+ rem Check environment variables first
33
+ if defined METANORMA_VERSION set "VERSION=%METANORMA_VERSION%"
34
+ if defined METANORMA_SOURCE set "SOURCE=%METANORMA_SOURCE%"
35
+
36
+ rem Check local .metanorma-version file
37
+ if not defined VERSION (
38
+ set "DIR=%CD%"
39
+ :loop
40
+ if exist "%DIR%\\.metanorma-version" (
41
+ set /p VERSION=<"%DIR%\\.metanorma-version"
42
+ goto :found_version
43
+ )
44
+ for %%I in ("%DIR%\\..") do set "PARENT=%%~fI"
45
+ if "%PARENT%"=="%DIR%" goto :check_global
46
+ set "DIR=%PARENT%"
47
+ goto :loop
48
+ )
49
+
50
+ :check_global
51
+ if not defined VERSION (
52
+ if exist "%MNENV_ROOT%\\version" (
53
+ set /p VERSION=<"%MNENV_ROOT%\\version"
54
+ )
55
+ )
56
+
57
+ :found_version
58
+ if not defined SOURCE (
59
+ if exist "%MNENV_ROOT%\\source" (
60
+ set /p SOURCE=<"%MNENV_ROOT%\\source"
61
+ )
62
+ )
63
+
64
+ rem Default source
65
+ if not defined SOURCE set "SOURCE=gemfile"
66
+
67
+ if not defined VERSION (
68
+ echo mnenv: version not set or invalid 1>&2
69
+ echo Set a version with: mnenv global ^<version^> or mnenv local ^<version^> 1>&2
70
+ exit /b 1
71
+ )
72
+
73
+ rem Determine executable path based on source
74
+ rem Installed versions are in %MNENV_ROOT%\\installed\\<version>-<source>\\
75
+ if "%SOURCE%"=="binary" (
76
+ set "EXECUTABLE=%MNENV_ROOT%\\installed\\%VERSION%-%SOURCE%\\metanorma.exe"
77
+ ) else (
78
+ set "EXECUTABLE=%MNENV_ROOT%\\installed\\%VERSION%-%SOURCE%\\bin\\#{executable_name}.cmd"
79
+ )
80
+
81
+ if not exist "%EXECUTABLE%" (
82
+ echo mnenv: #{executable_name} not installed for version %VERSION% ^(source: %SOURCE%^) 1>&2
83
+ echo Install it with: mnenv install %VERSION% --source %SOURCE% 1>&2
84
+ exit /b 1
85
+ )
86
+
87
+ call "%EXECUTABLE%" %*
88
+ SHIM
89
+ end
90
+
91
+ def use_output(version, source)
92
+ lines = []
93
+ lines << "set METANORMA_VERSION=#{version}"
94
+ lines << "set METANORMA_SOURCE=#{source}" if source
95
+ lines << 'rem Run this in your CMD session'
96
+ lines.join("\n")
97
+ end
98
+
99
+ def config_file
100
+ nil # CMD doesn't have a standard config file
101
+ end
102
+
103
+ def windows?
104
+ true
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+ require_relative 'bash'
5
+ require_relative 'power_shell'
6
+ require_relative 'cmd'
7
+
8
+ module Mnenv
9
+ module Shells
10
+ # Factory for detecting and creating shell instances
11
+ class ShellFactory
12
+ SHELLS = {
13
+ 'bash' => BashShell,
14
+ 'sh' => BashShell,
15
+ 'dash' => BashShell,
16
+ 'zsh' => BashShell,
17
+ 'fish' => BashShell, # Fish can run bash scripts in compatibility mode
18
+ 'powershell' => PowerShellShell,
19
+ 'pwsh' => PowerShellShell,
20
+ 'cmd' => CmdShell
21
+ }.freeze
22
+
23
+ class << self
24
+ # Detect the current shell from environment
25
+ def detect
26
+ shell_env = ENV['SHELL'] || ENV['COMSPEC'] || ''
27
+
28
+ if windows?
29
+ detect_windows_shell(shell_env)
30
+ else
31
+ detect_unix_shell(shell_env)
32
+ end
33
+ end
34
+
35
+ # Get shell by name
36
+ def get(shell_name)
37
+ shell_class = SHELLS[shell_name.downcase]
38
+ raise ArgumentError, "Unknown shell: #{shell_name}" unless shell_class
39
+
40
+ shell_class.new
41
+ end
42
+
43
+ # Get all shells for the current platform
44
+ def platform_shells
45
+ if windows?
46
+ [PowerShellShell.new, CmdShell.new]
47
+ else
48
+ [BashShell.new]
49
+ end
50
+ end
51
+
52
+ private
53
+
54
+ def windows?
55
+ RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/
56
+ end
57
+
58
+ def detect_unix_shell(shell_env)
59
+ # Extract shell name from path
60
+ shell_name = File.basename(shell_env) if shell_env
61
+
62
+ # Default to bash if we can't determine
63
+ shell_name ||= 'bash'
64
+
65
+ get(shell_name)
66
+ rescue ArgumentError
67
+ # Default to bash for unknown shells
68
+ BashShell.new
69
+ end
70
+
71
+ def detect_windows_shell(comspec)
72
+ if comspec =~ /powershell|pwsh/i
73
+ PowerShellShell.new
74
+ else
75
+ # Default to CMD on Windows, but also create PowerShell shim
76
+ CmdShell.new
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end