mnenv 0.1.2 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9bb839d75925723bae3de1d40012914fc768b8f981d4be837247ea0b8de8bf2d
4
- data.tar.gz: 1335ac68fba15671370c1c42f4d6999707915fd3ec884d33ee47759f3ba95863
3
+ metadata.gz: ebbe93f7754277cc8cb84ed21cf9d8c588c8dc9edd2164a51a4e175509e0afc8
4
+ data.tar.gz: d6c02ff55dbf8dbc816102e603c07a0a5e947b0570cfda4b6d08c0736f5e78da
5
5
  SHA512:
6
- metadata.gz: b09af8fc57ec321e46c1b4976ff69e73f00b9443168659b7e343cb8a1f527ef64eaa0ea9dc0265fda6e4d8b678d351bca6fe1adca4b35ded707ddc1e4b895b18
7
- data.tar.gz: 68a851a68c5a2feafe61cb47b0e68f98dc328d78c7e5459b31440250048c03fd19724cf2fcdf2462e46c6b00c6fdf6e33514d1c24569929d539fdaa8fe7efb69
6
+ metadata.gz: 1a6b0db4fb7021a8b82af5532632c39273afe217984487f61e21b1110738a3f2cc04221de9056d18f77b5656264bbaf7224bea5a417e2455ce26a0e8f00d0695
7
+ data.tar.gz: bd4196df600ebd1fcfcafc5db7baa6dc35dcf5ceafb762a6615e3fde71f3ca4d8de315d00e372b3cf836772c40d6e6c9a784bd27faf5b0aabf325044e8aac2bb
@@ -28,17 +28,9 @@ module Mnenv
28
28
  desc 'refresh', 'Fetch and add new Snap versions (incremental)'
29
29
  def refresh
30
30
  fetcher = Snap::Fetcher.new
31
- repo = fetcher.repository
32
-
33
- # Build map of existing composite keys
34
- existing_keys = repo.all.map { |v| "#{v.version}-#{v.revision}-#{v.arch}-#{v.channel}" }
31
+ existing = fetcher.repository.all.map(&:version)
35
32
  remote_versions = fetcher.fetch_all
36
-
37
- # Find truly new versions (composite key doesn't exist)
38
- new_versions = remote_versions.reject do |v|
39
- key = "#{v.version}-#{v.revision}-#{v.arch}-#{v.channel}"
40
- existing_keys.include?(key)
41
- end
33
+ new_versions = remote_versions.reject { |v| existing.include?(v.version) }
42
34
 
43
35
  if new_versions.empty?
44
36
  puts 'No new Snap versions found'
@@ -55,22 +47,21 @@ module Mnenv
55
47
  puts "Revamped #{versions.size} Snap versions"
56
48
  end
57
49
 
58
- desc 'update VERSION', 'Update a specific Snap version (all arch/channel combinations)'
50
+ desc 'update VERSION', 'Update a specific Snap version'
59
51
  def update(version)
60
52
  fetcher = Snap::Fetcher.new
61
53
  versions = fetcher.fetch_all
62
- targets = versions.select { |v| v.version == version }
54
+ target = versions.find { |v| v.version == version }
63
55
 
64
- if targets.empty?
65
- puts "Snap version #{version} not found in current channel-map"
66
- puts 'Note: Historical versions no longer in Snap API cannot be updated'
56
+ if target.nil?
57
+ puts "Snap version #{version} not found"
67
58
  exit 1
68
59
  end
69
60
 
70
- fetcher.repository.save_all(targets)
71
- puts "Updated #{targets.size} Snap entries for version #{version}:"
72
- targets.each do |v|
73
- puts " - #{v.arch}/#{v.channel}: revision #{v.revision}"
61
+ fetcher.repository.save_all([target])
62
+ puts "Updated 1 Snap entry for version #{version}:"
63
+ target.channels.each do |v|
64
+ puts " - channel: #{v.name} arch: #{v.arch} revision: #{v.revision}"
74
65
  end
75
66
  end
76
67
 
@@ -80,6 +80,8 @@ module Mnenv
80
80
  # Store relative paths (relative to data/gemfile directory)
81
81
  version.gemfile_path = "v#{version.version}/Gemfile"
82
82
  version.gemfile_lock_path = "v#{version.version}/Gemfile.lock.archived"
83
+ # Set parsed_at to now if not already set
84
+ version.parsed_at = DateTime.now if version.parsed_at.nil?
83
85
 
84
86
  repository.save(version)
85
87
  end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mnenv
4
+ class SnapChannel < Lutaml::Model::Serializable
5
+ attribute :revision, :integer
6
+ attribute :arch, :string, default: 'amd64'
7
+ attribute :name, :string, default: 'stable'
8
+
9
+ key_value do
10
+ map 'name', to: :name
11
+ map 'revision', to: :revision
12
+ map 'arch', to: :arch
13
+ end
14
+
15
+ def to_hash
16
+ super.merge(
17
+ 'revision' => revision,
18
+ 'arch' => arch,
19
+ 'name' => name
20
+ )
21
+ end
22
+ end
23
+ end
@@ -1,29 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'version'
4
+ require_relative 'snap_channel'
4
5
 
5
6
  module Mnenv
6
7
  class SnapVersion < ArtifactVersion
7
- attribute :revision, :integer
8
- attribute :arch, :string, default: 'amd64'
9
- attribute :channel, :string, default: 'stable'
8
+ attribute :channels, ::Mnenv::SnapChannel,
9
+ collection: true, default: -> { [] }
10
10
 
11
11
  key_value do
12
- map 'version', to: :version
13
- map 'published_at', to: :published_at
14
- map 'parsed_at', to: :parsed_at
15
- map 'revision', to: :revision
16
- map 'arch', to: :arch
17
- map 'channel', to: :channel
12
+ map 'channels', to: :channels
18
13
  end
19
14
 
20
- def display_name = revision ? "#{version}-#{revision}" : "v#{version}"
21
-
22
15
  def to_hash
23
16
  super.merge(
24
- 'revision' => revision,
25
- 'arch' => arch,
26
- 'channel' => channel
17
+ 'channels' => channels.map(&:to_hash)
27
18
  )
28
19
  end
29
20
  end
@@ -62,7 +62,7 @@ module Mnenv
62
62
  end
63
63
  end
64
64
 
65
- # Override in subclasses for custom caching (e.g., SnapRepository uses composite keys)
65
+ # Override in subclasses for custom caching
66
66
  def cache_version(version)
67
67
  @versions_cache[version.version] = version
68
68
  end
@@ -8,6 +8,9 @@ require 'uri'
8
8
  require 'json'
9
9
  require 'fileutils'
10
10
  require 'net/http'
11
+ require_relative 'missing_credentials_error'
12
+ require_relative 'snapcraft_not_available_error'
13
+ require_relative 'login_failed_error'
11
14
 
12
15
  module Mnenv
13
16
  module Snap
@@ -24,45 +27,112 @@ module Mnenv
24
27
  ARCHITECTURES = %w[amd64 arm64].freeze
25
28
 
26
29
  def fetch_all
27
- # Load existing versions from YAML (single source of truth)
28
- existing_map = repository.all.to_h { |v| [snap_key(v), v] }
30
+ fetch_all_from_snapcraft
31
+ end
32
+
33
+ def fetch_all_from_cache
34
+ # Load existing versions from YAML
35
+ version_map = repository.all
29
36
 
30
37
  # Fetch current heads from snap_metadata API
31
38
  current_versions = fetch_current_heads
32
39
 
33
- # Merge: keep existing, add/update current from API
34
- version_map = {}
35
-
36
- # Add all existing versions
37
- existing_map.each_value { |v| version_map[snap_key(v)] = v }
38
-
39
- # Add/update current from API (overrides existing if same key)
40
- current_versions.each do |cv|
41
- key = snap_key_hash(cv)
42
- version_map[key] = SnapVersion.new(
43
- version: cv.fetch('version'),
44
- revision: cv.fetch('revision'),
45
- arch: cv.fetch('arch'),
46
- channel: cv.fetch('channel')
40
+ current_versions.each_key do |k|
41
+ next if repository.exists?(k)
42
+
43
+ # Add new version
44
+ version_map << SnapVersion.new(
45
+ version: k,
46
+ parsed_at: DateTime.now,
47
+ channels: current_versions[k].map do |cv|
48
+ SnapChannel.new(
49
+ name: cv.fetch('channel'),
50
+ revision: cv.fetch('revision'),
51
+ arch: cv.fetch('arch')
52
+ )
53
+ end
47
54
  )
48
55
  end
49
56
 
50
- version_map.values.sort
57
+ version_map
58
+ end
59
+
60
+ def fetch_all_from_snapcraft
61
+ raise MissingCredentialsError unless ENV['SNAPCRAFT_STORE_CREDENTIALS']
62
+ raise SnapcraftNotAvailableError unless snapcraft_available?
63
+
64
+ login_result = system('echo "$SNAPCRAFT_STORE_CREDENTIALS" | snapcraft login --with -')
65
+
66
+ raise LoginFailedError unless login_result
67
+
68
+ # delete the environment variable immediately to prevent
69
+ # duplicate login error
70
+ ENV.delete('SNAPCRAFT_STORE_CREDENTIALS')
71
+
72
+ result = `snapcraft revisions metanorma`
73
+ snap_revisions = parse_snap_revisions(result)
74
+ build_snap_version_map(snap_revisions)
51
75
  end
52
76
 
53
77
  private
54
78
 
55
- def snap_key(version)
56
- "#{version.version}-#{version.revision}-#{version.arch}-#{version.channel}"
79
+ def snapcraft_available?
80
+ system('command -v snapcraft > /dev/null 2>&1')
57
81
  end
58
82
 
59
- def snap_key_hash(hash)
60
- "#{hash.fetch('version')}-#{hash.fetch('revision')}-#{hash.fetch('arch')}-#{hash.fetch('channel')}"
83
+ def parse_snap_revisions(data)
84
+ lines = data.lines.map(&:strip).reject { |l| l.empty? || l.start_with?('Rev.') }
85
+ revisions = []
86
+ lines.each do |line|
87
+ fields = line.split(/\s{2,}/)
88
+ rev, uploaded, arches, version, channels = fields
89
+ revisions << {
90
+ version: version,
91
+ published_at: uploaded,
92
+ revision: rev,
93
+ arch: arches,
94
+ channels: channels
95
+ }
96
+ end
97
+ revisions
98
+ end
99
+
100
+ def build_snap_version_map(snap_revisions)
101
+ versions = []
102
+ latest_version = snap_revisions.first[:version]
103
+
104
+ snap_revisions.each do |sr|
105
+ snap_version = versions.find { |v| v&.version == sr[:version] }
106
+ if snap_version.nil?
107
+ snap_version = SnapVersion.new
108
+ snap_version.version = sr[:version]
109
+ snap_version.published_at = sr[:published_at]
110
+ snap_version.parsed_at = DateTime.now
111
+ end
112
+
113
+ channels = sr[:channels].split(',').map(&:strip)
114
+ channels.each do |channel|
115
+ next unless channel.start_with? 'latest/'
116
+
117
+ channel = channel.sub('latest/', '')
118
+ next if (sr[:version] == latest_version) && !channel.end_with?('*')
119
+
120
+ snap_version.channels << SnapChannel.new(
121
+ name: channel.gsub('*', ''),
122
+ revision: sr[:revision],
123
+ arch: sr[:arch]
124
+ )
125
+ end
126
+
127
+ versions << snap_version
128
+ end
129
+
130
+ versions.reverse
61
131
  end
62
132
 
63
133
  # Fetch current heads from snap_metadata API for all channel/arch combinations
64
134
  def fetch_current_heads
65
- versions = []
135
+ versions = {}
66
136
 
67
137
  CHANNELS.each do |channel|
68
138
  ARCHITECTURES.each do |arch|
@@ -88,8 +158,9 @@ module Mnenv
88
158
 
89
159
  if data['_embedded'] && data['_embedded']['clickindex:package']
90
160
  pkg = data['_embedded']['clickindex:package'][0]
91
- versions << {
92
- 'version' => pkg['version'],
161
+
162
+ versions[pkg['version']] ||= []
163
+ versions[pkg['version']] << {
93
164
  'revision' => pkg['revision'],
94
165
  'arch' => arch,
95
166
  'channel' => channel
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mnenv
4
+ module Snap
5
+ class LoginFailedError < StandardError
6
+ def initialize
7
+ super('Failed to login to snapcraft with provided credentials.')
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mnenv
4
+ module Snap
5
+ class MissingCredentialsError < StandardError
6
+ def initialize
7
+ super('Missing SNAPCRAFT_STORE_CREDENTIALS environment variable. Please set it to fetch Snap versions.')
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mnenv
4
+ module Snap
5
+ class SnapcraftNotAvailableError < StandardError
6
+ def initialize
7
+ super('Snapcraft is not available. Please install Snapcraft to fetch Snap versions.')
8
+ end
9
+ end
10
+ end
11
+ end
@@ -8,45 +8,30 @@ module Mnenv
8
8
  def version_class = SnapVersion
9
9
  def source_name = :snap
10
10
 
11
- # Override cache_version to use composite key for Snap
12
- # Since same version can have multiple revisions/arch/channels
13
- def cache_version(version)
14
- @versions_cache[snap_key(version)] = version
15
- end
16
-
17
- # Override find to work with composite keys
18
- def find(version_number)
19
- @versions_cache.values.find { |v| v.version == version_number }
20
- end
21
-
22
- # Find by specific version, revision, arch, channel
23
- def find_exact(version_number, revision, arch, channel)
24
- @versions_cache[snap_key(version_number, revision, arch, channel)]
25
- end
26
-
27
- # Get all entries for a specific version
28
- def find_all_by_version(version_number)
29
- @versions_cache.values.select { |v| v.version == version_number }.sort
30
- end
31
-
32
- def exists?(version_number)
33
- @versions_cache.values.any? { |v| v.version == version_number }
34
- end
35
-
36
- private
37
-
38
- def snap_key(*args)
39
- case args.size
40
- when 1
41
- # Single SnapVersion object
42
- v = args.first
43
- "#{v.version}-#{v.revision}-#{v.arch}-#{v.channel}"
44
- when 4
45
- # version, revision, arch, channel
46
- version_number, revision, arch, channel = args
47
- "#{version_number}-#{revision}-#{arch}-#{channel}"
48
- else
49
- raise ArgumentError, 'snap_key requires 1 or 4 arguments'
11
+ protected
12
+
13
+ def load
14
+ data = fetch_versions_data
15
+
16
+ return if data.nil? || data['versions'].nil?
17
+
18
+ data['versions'].each do |version_hash|
19
+ channels = version_hash['channels'].map do |c|
20
+ SnapChannel.new(
21
+ name: c['name'],
22
+ revision: c['revision'],
23
+ arch: c['arch']
24
+ )
25
+ end
26
+
27
+ version = SnapVersion.new.tap do |v|
28
+ v.version = version_hash['version']
29
+ v.published_at = version_hash['published_at']
30
+ v.parsed_at = version_hash['parsed_at']
31
+ v.channels = channels
32
+ end
33
+
34
+ cache_version(version)
50
35
  end
51
36
  end
52
37
  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.2'
4
+ VERSION = '0.1.3'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mnenv
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-02-14 00:00:00.000000000 Z
11
+ date: 2026-03-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -206,6 +206,7 @@ files:
206
206
  - lib/mnenv/models/chocolatey_version.rb
207
207
  - lib/mnenv/models/gemfile_version.rb
208
208
  - lib/mnenv/models/homebrew_version.rb
209
+ - lib/mnenv/models/snap_channel.rb
209
210
  - lib/mnenv/models/snap_version.rb
210
211
  - lib/mnenv/models/version.rb
211
212
  - lib/mnenv/paths.rb
@@ -220,6 +221,9 @@ files:
220
221
  - lib/mnenv/shim_manager.rb
221
222
  - lib/mnenv/snap.rb
222
223
  - lib/mnenv/snap/fetcher.rb
224
+ - lib/mnenv/snap/login_failed_error.rb
225
+ - lib/mnenv/snap/missing_credentials_error.rb
226
+ - lib/mnenv/snap/snapcraft_not_available_error.rb
223
227
  - lib/mnenv/snap_repository.rb
224
228
  - lib/mnenv/source_registry.rb
225
229
  - lib/mnenv/sources.rb