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,110 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+
5
+ module Mnenv
6
+ module Shells
7
+ # PowerShell shell implementation (Windows)
8
+ class PowerShellShell < BaseShell
9
+ def name
10
+ 'powershell'
11
+ end
12
+
13
+ def shim_extension
14
+ '.ps1'
15
+ end
16
+
17
+ def shim_content(executable_name)
18
+ <<~SHIM
19
+ # mnenv shim for PowerShell
20
+ $ErrorActionPreference = "Stop"
21
+
22
+ $MNENV_ROOT = if ($env:MNENV_ROOT) { $env:MNENV_ROOT } else { "$env:USERPROFILE\\.mnenv" }
23
+
24
+ # Resolve version and source using mnenv resolver
25
+ $versionFile = "$MNENV_ROOT\\version"
26
+ $sourceFile = "$MNENV_ROOT\\source"
27
+
28
+ # Check environment variables first
29
+ $VERSION = $env:METANORMA_VERSION
30
+ $SOURCE = $env:METANORMA_SOURCE
31
+
32
+ # Check local .metanorma-version file
33
+ if (-not $VERSION) {
34
+ $dir = (Get-Location).Path
35
+ $root = [System.IO.Path]::GetPathRoot($dir)
36
+ while ($dir -and $dir -ne $root) {
37
+ $localVersionFile = Join-Path $dir ".metanorma-version"
38
+ if (Test-Path $localVersionFile) {
39
+ $VERSION = Get-Content $localVersionFile -Raw
40
+ $VERSION = $VERSION.Trim()
41
+ break
42
+ }
43
+ $localSourceFile = Join-Path $dir ".metanorma-source"
44
+ if (-not $SOURCE -and (Test-Path $localSourceFile)) {
45
+ $SOURCE = Get-Content $localSourceFile -Raw
46
+ $SOURCE = $SOURCE.Trim()
47
+ }
48
+ $dir = Split-Path $dir -Parent
49
+ }
50
+ }
51
+
52
+ # Check global version file
53
+ if (-not $VERSION -and (Test-Path $versionFile)) {
54
+ $VERSION = Get-Content $versionFile -Raw
55
+ $VERSION = $VERSION.Trim()
56
+ }
57
+
58
+ if (-not $SOURCE -and (Test-Path $sourceFile)) {
59
+ $SOURCE = Get-Content $sourceFile -Raw
60
+ $SOURCE = $SOURCE.Trim()
61
+ }
62
+
63
+ # Default source
64
+ if (-not $SOURCE) {
65
+ $SOURCE = "gemfile"
66
+ }
67
+
68
+ if (-not $VERSION) {
69
+ Write-Error "mnenv: version not set or invalid"
70
+ Write-Error "Set a version with: mnenv global <version> or mnenv local <version>"
71
+ exit 1
72
+ }
73
+
74
+ # Determine executable path based on source
75
+ # Installed versions are in $MNENV_ROOT\\installed\\<version>-<source>\\
76
+ if ($SOURCE -eq "binary") {
77
+ $EXECUTABLE = "$MNENV_ROOT\\installed\\$VERSION-$SOURCE\\metanorma.exe"
78
+ } else {
79
+ # Bundler creates .cmd files on Windows, not .bat
80
+ $EXECUTABLE = "$MNENV_ROOT\\installed\\$VERSION-$SOURCE\\bin\\#{executable_name}.cmd"
81
+ }
82
+
83
+ if (-not (Test-Path $EXECUTABLE)) {
84
+ Write-Error "mnenv: #{executable_name} not installed for version $VERSION (source: $SOURCE)"
85
+ Write-Error "Install it with: mnenv install $VERSION --source $SOURCE"
86
+ exit 1
87
+ }
88
+
89
+ & $EXECUTABLE $args
90
+ SHIM
91
+ end
92
+
93
+ def use_output(version, source)
94
+ lines = []
95
+ lines << "$env:METANORMA_VERSION = '#{version}'"
96
+ lines << "$env:METANORMA_SOURCE = '#{source}'" if source
97
+ lines << '# Run this in your PowerShell session'
98
+ lines.join("\n")
99
+ end
100
+
101
+ def config_file
102
+ File.expand_path('~\Documents\PowerShell\Microsoft.PowerShell_profile.ps1')
103
+ end
104
+
105
+ def windows?
106
+ true
107
+ end
108
+ end
109
+ end
110
+ end
@@ -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
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'source_registry'
4
+ require_relative 'gemfile_repository'
5
+ require_relative 'binary_repository'
6
+ require_relative 'models/gemfile_version'
7
+ require_relative 'models/binary_version'
8
+ require_relative 'installers/gemfile_installer'
9
+ require_relative 'installers/binary_installer'
10
+
11
+ module Mnenv
12
+ # Auto-registration of built-in sources
13
+ # This enables the plugin architecture - new sources just need to register themselves
14
+ module Sources
15
+ class << self
16
+ # Register all built-in sources
17
+ def setup
18
+ register_gemfile
19
+ register_binary
20
+ end
21
+
22
+ private
23
+
24
+ def register_gemfile
25
+ SourceRegistry.register(
26
+ name: 'gemfile',
27
+ repository: GemfileRepository,
28
+ model: GemfileVersion,
29
+ installer: Installers::GemfileInstaller
30
+ )
31
+ end
32
+
33
+ def register_binary
34
+ SourceRegistry.register(
35
+ name: 'binary',
36
+ repository: BinaryRepository,
37
+ model: BinaryVersion,
38
+ installer: Installers::BinaryInstaller
39
+ )
40
+ end
41
+ end
42
+ end
43
+ end
44
+
45
+ # Auto-setup on load
46
+ Mnenv::Sources.setup
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.2'
5
5
  end
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mnenv
4
+ # Centralized version and source resolution with clear precedence:
5
+ # 1. Environment variables (METANORMA_VERSION, METANORMA_SOURCE)
6
+ # 2. Local files (.metanorma-version, .metanorma-source) - walk up tree
7
+ # 3. Global files (~/.mnenv/version, ~/.mnenv/source)
8
+ # 4. Defaults (gemfile for source)
9
+ #
10
+ # This class is the single source of truth for version resolution.
11
+ # The Bash resolver (lib/mnenv/resolver) is kept for shims only.
12
+ class VersionResolver
13
+ # Resolve the current Metanorma version
14
+ # @return [String, nil] The resolved version or nil if not set
15
+ def resolve_version
16
+ from_env('METANORMA_VERSION') ||
17
+ from_local_file('.metanorma-version') ||
18
+ from_global_file(Paths::VERSION_FILE)
19
+ end
20
+
21
+ # Resolve the current Metanorma source
22
+ # @return [String] The resolved source (defaults to 'gemfile')
23
+ def resolve_source
24
+ from_env('METANORMA_SOURCE') ||
25
+ from_local_file('.metanorma-source') ||
26
+ from_global_file(Paths::SOURCE_FILE) ||
27
+ 'gemfile'
28
+ end
29
+
30
+ # Resolve both version and source
31
+ # @return [Array<String, String>] Tuple of [version, source]
32
+ def resolve
33
+ [resolve_version, resolve_source]
34
+ end
35
+
36
+ # Check if a version is set anywhere
37
+ # @return [Boolean]
38
+ def version_set?
39
+ !resolve_version.nil?
40
+ end
41
+
42
+ # Check if a source is explicitly set (not default)
43
+ # @return [Boolean]
44
+ def source_set?
45
+ from_env('METANORMA_SOURCE') ||
46
+ from_local_file('.metanorma-source') ||
47
+ from_global_file(Paths::SOURCE_FILE)
48
+ end
49
+
50
+ # Get the source of version resolution (for debugging)
51
+ # @return [Symbol] :environment, :local, :global, or :none
52
+ def version_source
53
+ return :environment if from_env('METANORMA_VERSION')
54
+ return :local if from_local_file('.metanorma-version')
55
+ return :global if from_global_file(Paths::VERSION_FILE)
56
+
57
+ :none
58
+ end
59
+
60
+ # Get the source of source resolution (for debugging)
61
+ # @return [Symbol] :environment, :local, :global, or :default
62
+ def source_source
63
+ return :environment if from_env('METANORMA_SOURCE')
64
+ return :local if from_local_file('.metanorma-source')
65
+ return :global if from_global_file(Paths::SOURCE_FILE)
66
+
67
+ :default
68
+ end
69
+
70
+ private
71
+
72
+ # Read from environment variable
73
+ # @param var [String] Environment variable name
74
+ # @return [String, nil] Value or nil if not set/empty
75
+ def from_env(var)
76
+ value = ENV[var]
77
+ value&.strip&.then { |v| v unless v.empty? }
78
+ end
79
+
80
+ # Read from local file, walking up directory tree
81
+ # @param filename [String] Filename to look for
82
+ # @return [String, nil] File contents or nil if not found
83
+ def from_local_file(filename)
84
+ dir = Dir.pwd
85
+
86
+ loop do
87
+ path = File.join(dir, filename)
88
+ return File.read(path).strip if File.exist?(path)
89
+
90
+ parent = File.dirname(dir)
91
+ break if parent == dir # Reached root
92
+
93
+ dir = parent
94
+ end
95
+
96
+ nil
97
+ end
98
+
99
+ # Read from global file
100
+ # @param path [String] Full path to file
101
+ # @return [String, nil] File contents or nil if not found
102
+ def from_global_file(path)
103
+ File.read(path).strip if File.exist?(path)
104
+ rescue StandardError
105
+ nil
106
+ end
107
+ end
108
+ 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,12 @@
3
3
  require 'lutaml/model'
4
4
  require 'thor'
5
5
 
6
+ require_relative 'mnenv/paths'
7
+ require_relative 'mnenv/platform_detector'
8
+ require_relative 'mnenv/version_resolver'
6
9
  require_relative 'mnenv/models'
10
+ require_relative 'mnenv/source_registry'
11
+ require_relative 'mnenv/sources'
7
12
  require_relative 'mnenv/commands'
13
+ require_relative 'mnenv/installer'
8
14
  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