fhir_packages_manager 0.1.0 → 0.3.0

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: c3db88fe718417bdd2a0bd82604b48928db13d036ee912094cb8891e5218d967
4
- data.tar.gz: d84a0f807bcd2f479cd511fb100cfd9c723c6b59fca65c5dc41aa2e6dd829418
3
+ metadata.gz: 7d65db7b1f5b29ac8f0064d0cfd224529dc336b2b88c7a4de952206fe81477f7
4
+ data.tar.gz: c16f3255dce19c1058b3781b5695e0c059e6d76b45dc63ff1c35a94891eacaa3
5
5
  SHA512:
6
- metadata.gz: 17d1da3f230507fbeb91cd949a90370561bcdeece97bdf433b169f16b7baadf960cc542bd9931f56b2b6afe93936a1d53865efe8f45e9be969e2f1883dcb5acf
7
- data.tar.gz: 3b4e62d57b1b034c137869355e2d8454fc92292d3b0b59f4825abc89af0d86013afd76f563de14f95a23a03e28f1fd7783ef8b54994e7cf5137d44068bda562e
6
+ metadata.gz: 016d41a5b7f0aed1ce89fa445e149ea85b4e991c9c61d9ba111fab699b113e88b8e62c650f8b08314e5246ca875927dbe0abb9bbdd92b9d68dbf8306c502f68c
7
+ data.tar.gz: bed4d3fb7c4f7abb708837a2ebbd7ecac247c18284b16e51967b216240f98878090df35765dcfdfdf3c8700c41ab785176d05d58747dff6a076ee636ef1ff29a
data/CHANGELOG.md CHANGED
@@ -5,6 +5,27 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [Unreleased]
9
+
10
+ ## [0.3.0] - 2026-07-10
11
+
12
+ ### Added
13
+
14
+ - `sync` CLI command and `Manager#sync`: downloads every non-ignored version of a package not
15
+ already present in the destination folder (checked as `name-version.tgz`, the same naming
16
+ `fetch` uses), reusing `#fetch` for the actual download of each missing version. Output reuses
17
+ `fetch`'s `OK`/`SKIP`/`MISS`/`ERR` lines, with `SKIP` covering both an ignored version and one
18
+ already on disk. (#3)
19
+
20
+ ## [0.2.0] - 2026-07-10
21
+
22
+ ### Added
23
+
24
+ - `list` CLI command and `Manager#list_versions`/`Registry#versions`: lists every version of a
25
+ package published across all configured registries, without needing a version suffix on the
26
+ spec. Respects the ignore list the same way `fetch` does (whole-package or single-version
27
+ entries). (#1)
28
+
8
29
  ## [0.1.0] - 2026-07-10
9
30
 
10
31
  ### Added
@@ -39,5 +60,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
39
60
  - **Documentation**: YARD doc comments across the public API, and a Codecov/docs badge pair
40
61
  in the README.
41
62
 
42
- [Unreleased]: https://github.com/projkov/fhir_packages_manager/compare/v0.1.0...HEAD
63
+ [Unreleased]: https://github.com/projkov/fhir_packages_manager/compare/v0.3.0...HEAD
64
+ [0.3.0]: https://github.com/projkov/fhir_packages_manager/compare/v0.2.0...v0.3.0
65
+ [0.2.0]: https://github.com/projkov/fhir_packages_manager/compare/v0.1.0...v0.2.0
43
66
  [0.1.0]: https://github.com/projkov/fhir_packages_manager/releases/tag/v0.1.0
data/README.md CHANGED
@@ -1,5 +1,7 @@
1
- # FhirPackagesManager
1
+ # FHIR Packages Manager
2
2
 
3
+ [![Gem Version](https://badge.fury.io/rb/fhir_packages_manager.svg?icon=si%3Arubygems)](https://badge.fury.io/rb/fhir_packages_manager)
4
+ [![Ruby](https://img.shields.io/badge/ruby-%3E%3D%203.2-CC342D?logo=ruby&logoColor=white)](fhir_packages_manager.gemspec)
3
5
  [![codecov](https://codecov.io/gh/projkov/fhir_packages_manager/graph/badge.svg)](https://codecov.io/gh/projkov/fhir_packages_manager)
4
6
  [![docs](https://img.shields.io/badge/docs-yard-blue.svg)](https://projkov.github.io/fhir_packages_manager/)
5
7
 
@@ -42,6 +44,10 @@ result.status # => :downloaded, :ignored, :not_found, or :error
42
44
  result.path # => "./fhir_packages/hl7.fhir.us.core-6.1.0.tgz" when downloaded
43
45
 
44
46
  manager.fetch_all(["hl7.fhir.us.core@6.1.0", "hl7.fhir.r4.core"]) # bare name = latest
47
+
48
+ manager.list_versions("hl7.fhir.us.core") # => {"https://packages.fhir.org" => ["1.0.0", "1.0.1", ...], ...}
49
+
50
+ manager.sync("hl7.fhir.us.core") # fetches every non-ignored version not already in destination
45
51
  ```
46
52
 
47
53
  The ignore list file (YAML or JSON) is a flat array where a bare string
@@ -60,8 +66,18 @@ exe/fhir_packages_manager check hl7.fhir.us.core@6.1.0 -r https://packages.fhir.
60
66
  exe/fhir_packages_manager fetch hl7.fhir.us.core@6.1.0 hl7.fhir.r4.core \
61
67
  -r https://packages.fhir.org -r https://packages.simplifier.net \
62
68
  -d ./fhir_packages -i fhir_packages_ignore.yml
69
+ exe/fhir_packages_manager list hl7.fhir.us.core \
70
+ -r https://packages.fhir.org -r https://packages.simplifier.net
71
+ exe/fhir_packages_manager sync hl7.fhir.us.core \
72
+ -r https://packages.fhir.org -d ./fhir_packages -i fhir_packages_ignore.yml
63
73
  ```
64
74
 
75
+ `sync` downloads every non-ignored version of a package that isn't already in the destination
76
+ folder (checked as `name-version.tgz`, the same naming `fetch` uses — keep that naming if you
77
+ move the files afterward, since a bare `version.tgz` can collide between two IGs that happen to
78
+ publish the same version number). Its output reuses `fetch`'s `OK`/`SKIP`/`MISS`/`ERR` lines,
79
+ with `SKIP` covering both an ignored version and one already on disk.
80
+
65
81
  ### Docker
66
82
 
67
83
  The CLI is also published as a container image, so it can be used without installing Ruby
@@ -74,7 +90,13 @@ docker run --rm -v "$(pwd)/fhir_packages:/fhir_packages" ghcr.io/projkov/fhir_pa
74
90
  fetch hl7.fhir.us.core@6.1.0 -r https://packages.fhir.org -d /fhir_packages
75
91
  ```
76
92
 
77
- Mount a volume (as above) to get downloaded `.tgz` files back out onto the host.
93
+ Mount a volume (as above) to get downloaded `.tgz` files back out onto the host. `latest` and
94
+ version tags are built for both `linux/amd64` and `linux/arm64` (e.g. Apple Silicon).
95
+
96
+ To get a fresh image built from an unreleased commit (without cutting a real gem release),
97
+ run the **Docker Snapshot** workflow manually from the Actions tab. It re-runs the test/quality
98
+ gate, then pushes `ghcr.io/projkov/fhir_packages_manager:<gem-version>-<commit-sha>` (e.g.
99
+ `0.1.0-9649122`) — never `latest`, so it can't be mistaken for a real release.
78
100
 
79
101
  #### Docker Compose
80
102
 
@@ -7,7 +7,7 @@ module FhirPackagesManager
7
7
  # See the "CLI" section of the README for usage examples.
8
8
  class CLI
9
9
  # @return [Array<String>] the supported subcommands
10
- COMMANDS = %w[fetch check].freeze
10
+ COMMANDS = %w[fetch check list sync].freeze
11
11
 
12
12
  # @return [String] usage text shown on --help and on invalid invocations
13
13
  BANNER = <<~USAGE
@@ -16,6 +16,8 @@ module FhirPackagesManager
16
16
  Commands:
17
17
  fetch Download packages into the destination folder
18
18
  check Report which registry (if any) has each package/version
19
+ list List every version of a package available across registries
20
+ sync Download every non-ignored version not already in the destination folder
19
21
 
20
22
  USAGE
21
23
 
@@ -53,6 +55,10 @@ module FhirPackagesManager
53
55
  fetch(package_specs)
54
56
  when 'check'
55
57
  check(package_specs)
58
+ when 'list'
59
+ list(package_specs)
60
+ when 'sync'
61
+ sync(package_specs)
56
62
  end
57
63
  end
58
64
 
@@ -110,9 +116,11 @@ module FhirPackagesManager
110
116
 
111
117
  def fetch_line(result)
112
118
  package = result.package
119
+ path = result.path
113
120
  case result.status
114
- when :downloaded then "OK #{package} -> #{result.path} (#{result.registry})"
121
+ when :downloaded then "OK #{package} -> #{path} (#{result.registry})"
115
122
  when :ignored then "SKIP #{package} (ignored)"
123
+ when :skipped then "SKIP #{package} (already exists at #{path})"
116
124
  when :not_found then "MISS #{package} (not found in any registry)"
117
125
  when :error then "ERR #{package}: #{result.error}"
118
126
  end
@@ -132,5 +140,26 @@ module FhirPackagesManager
132
140
  registry, version = found
133
141
  "AVAILABLE #{package.name}@#{version} (#{registry.base_url})"
134
142
  end
143
+
144
+ def list(package_specs)
145
+ package_specs.each do |spec|
146
+ name = Package.parse(spec).name
147
+ list_lines(name, manager.list_versions(name)).each { |line| puts line }
148
+ end
149
+ end
150
+
151
+ def list_lines(name, versions_by_registry)
152
+ return ["NONE #{name} (not found in any registry)"] if versions_by_registry.empty?
153
+
154
+ versions_by_registry.map do |base_url, versions|
155
+ "FOUND #{name} @ #{base_url}: #{versions.sort.join(', ')}"
156
+ end
157
+ end
158
+
159
+ def sync(package_specs)
160
+ results = package_specs.flat_map { |spec| manager.sync(Package.parse(spec).name) }
161
+ results.each { |result| puts fetch_line(result) }
162
+ exit 1 if results.any? { |result| result.not_found? || result.error? }
163
+ end
135
164
  end
136
165
  end
@@ -1,16 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module FhirPackagesManager
4
- # Outcome of {Manager#fetch} for a single package.
4
+ # Outcome of {Manager#fetch} (or {Manager#sync}) for a single package/version.
5
5
  #
6
6
  # @!attribute package
7
7
  # @return [Package] the package that was requested (version resolved, when downloaded)
8
8
  # @!attribute status
9
- # @return [Symbol] one of :downloaded, :ignored, :not_found, :error
9
+ # @return [Symbol] one of :downloaded, :ignored, :skipped, :not_found, :error
10
10
  # @!attribute registry
11
11
  # @return [String, nil] the base URL of the registry that served the package, if downloaded
12
12
  # @!attribute path
13
- # @return [String, nil] where the tarball was written, if downloaded
13
+ # @return [String, nil] where the tarball was (or already is) on disk, if downloaded/skipped
14
14
  # @!attribute error
15
15
  # @return [String, nil] the error message, when status is :error
16
16
  class FetchResult < Struct.new(:package, :status, :registry, :path, :error, keyword_init: true)
@@ -24,6 +24,11 @@ module FhirPackagesManager
24
24
  status == :ignored
25
25
  end
26
26
 
27
+ # @return [Boolean] true if {Manager#sync} skipped it because it was already downloaded
28
+ def skipped?
29
+ status == :skipped
30
+ end
31
+
27
32
  # @return [Boolean] true if no registry had the package/version
28
33
  def not_found?
29
34
  status == :not_found
@@ -60,6 +60,40 @@ module FhirPackagesManager
60
60
  packages.map { |package| fetch(package) }
61
61
  end
62
62
 
63
+ # Lists every version of a package published across all configured registries, skipping
64
+ # registries that don't have it at all and filtering out any ignored versions.
65
+ #
66
+ # @param name [String] the package name
67
+ # @return [Hash{String => Array<String>}] versions, keyed by registry base_url, for
68
+ # registries that have at least one non-ignored version; empty if the whole package is
69
+ # ignored or no registry has it
70
+ # @raise [HttpError] if a registry that does have the package fails for another reason
71
+ # (mirrors {#find_registry}/{Registry#version?}, which likewise only treat "not found" as
72
+ # an expected, non-raising outcome)
73
+ def list_versions(name)
74
+ result = {} # : Hash[String, Array[String]]
75
+ return result if ignored?(Package.new(name, nil))
76
+
77
+ registries.each_with_object(result) do |registry, found|
78
+ versions = available_versions(registry, name)
79
+ found[registry.base_url] = versions unless versions.empty?
80
+ end
81
+ end
82
+
83
+ # Downloads every non-ignored version of a package that isn't already present in
84
+ # {#destination} (as `name-version.tgz`, the same convention {#fetch} downloads use).
85
+ # A whole-package ignore skips it entirely, without querying any registry; an individual
86
+ # ignored version instead surfaces as a normal `:ignored` {FetchResult}, same as {#fetch}.
87
+ #
88
+ # @param name [String] the package name
89
+ # @return [Array<FetchResult>] one result per version considered, across all registries
90
+ def sync(name)
91
+ whole_package = Package.new(name, nil)
92
+ return [FetchResult.new(package: whole_package, status: :ignored)] if ignored?(whole_package)
93
+
94
+ candidate_versions(name).map { |version| sync_version(Package.new(name, version)) }
95
+ end
96
+
63
97
  private
64
98
 
65
99
  def fetch_package(package)
@@ -77,13 +111,38 @@ module FhirPackagesManager
77
111
  !!ignore_list&.ignored?(package.name, package.version)
78
112
  end
79
113
 
114
+ def available_versions(registry, name)
115
+ raw_versions(registry, name).reject { |version| ignored?(Package.new(name, version)) }
116
+ end
117
+
118
+ def raw_versions(registry, name)
119
+ registry.versions(name)
120
+ rescue PackageNotFoundError
121
+ []
122
+ end
123
+
124
+ def candidate_versions(name)
125
+ registries.flat_map { |registry| raw_versions(registry, name) }.uniq
126
+ end
127
+
128
+ def sync_version(package)
129
+ existing_path = download_path(package)
130
+ return FetchResult.new(package: package, status: :skipped, path: existing_path) if File.exist?(existing_path)
131
+
132
+ fetch(package)
133
+ end
134
+
135
+ def download_path(package)
136
+ File.join(destination, "#{package.name}-#{package.version}.tgz")
137
+ end
138
+
80
139
  def download_result(package, found)
81
140
  registry, resolved_version = found
82
141
  name = package.name
83
- target = File.join(destination, "#{name}-#{resolved_version}.tgz")
142
+ resolved_package = Package.new(name, resolved_version)
143
+ target = download_path(resolved_package)
84
144
  registry.download(name, resolved_version, target)
85
- FetchResult.new(package: Package.new(name, resolved_version), status: :downloaded,
86
- registry: registry.base_url, path: target)
145
+ FetchResult.new(package: resolved_package, status: :downloaded, registry: registry.base_url, path: target)
87
146
  end
88
147
  end
89
148
  end
@@ -49,6 +49,14 @@ module FhirPackagesManager
49
49
  nil
50
50
  end
51
51
 
52
+ # @param name [String] the package name
53
+ # @return [Array<String>] every version published for this package on this registry
54
+ # @raise [PackageNotFoundError] if the package doesn't exist on this registry
55
+ # @raise [HttpError] for any other non-2xx/3xx response
56
+ def versions(name)
57
+ metadata(name)['versions']&.keys || []
58
+ end
59
+
52
60
  # @param name [String] the package name
53
61
  # @param version [String] an exact version, as returned by {#version?}
54
62
  # @return [String] the tarball's download URL
@@ -2,5 +2,5 @@
2
2
 
3
3
  module FhirPackagesManager
4
4
  # @return [String] the gem's version, following semver
5
- VERSION = '0.1.0'
5
+ VERSION = '0.3.0'
6
6
  end
@@ -30,5 +30,11 @@ module FhirPackagesManager
30
30
  def check: (Array[String] package_specs) -> void
31
31
 
32
32
  def check_line: (Package package, [Registry, String]? found) -> String
33
+
34
+ def list: (Array[String] package_specs) -> void
35
+
36
+ def list_lines: (String name, Hash[String, Array[String]] versions_by_registry) -> Array[String]
37
+
38
+ def sync: (Array[String] package_specs) -> void
33
39
  end
34
40
  end
@@ -12,6 +12,8 @@ module FhirPackagesManager
12
12
 
13
13
  def ignored?: () -> bool
14
14
 
15
+ def skipped?: () -> bool
16
+
15
17
  def not_found?: () -> bool
16
18
 
17
19
  def error?: () -> bool
@@ -14,12 +14,26 @@ module FhirPackagesManager
14
14
 
15
15
  def fetch_all: (Array[String | Package] packages) -> Array[FetchResult]
16
16
 
17
+ def list_versions: (String name) -> Hash[String, Array[String]]
18
+
19
+ def sync: (String name) -> Array[FetchResult]
20
+
17
21
  private
18
22
 
19
23
  def fetch_package: (Package package) -> FetchResult
20
24
 
21
25
  def ignored?: (Package package) -> bool
22
26
 
27
+ def available_versions: (Registry registry, String name) -> Array[String]
28
+
29
+ def raw_versions: (Registry registry, String name) -> Array[String]
30
+
31
+ def candidate_versions: (String name) -> Array[String]
32
+
33
+ def sync_version: (Package package) -> FetchResult
34
+
35
+ def download_path: (Package package) -> String
36
+
23
37
  def download_result: (Package package, [Registry, String] found) -> FetchResult
24
38
  end
25
39
  end
@@ -10,6 +10,8 @@ module FhirPackagesManager
10
10
 
11
11
  def version?: (String name, ?String? version) -> String?
12
12
 
13
+ def versions: (String name) -> Array[String]
14
+
13
15
  def tarball_url: (String name, String version) -> String
14
16
 
15
17
  def download: (String name, String version, String destination_path) -> String
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fhir_packages_manager
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Pavel Rozhkov