danger-spm_version_updates 0.2.0 → 1.2.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: 9f0dbb2a2025a65d4be1bb5cdd4c33acaae489103521d4e852adfbafc64c23a8
4
- data.tar.gz: fa927bb32b7e55f60614122acf9dd13d6c5c487934049d566916bbc434d994ca
3
+ metadata.gz: 61b370bdf3ace482bdb37078ee355f35ee039c4ba8cf713d9d1d4f205f27fdcc
4
+ data.tar.gz: dd50d0ce76d7c4f3978c5c3a2a1a9b3fdb72174846a9da9e900f82a072f3fa6b
5
5
  SHA512:
6
- metadata.gz: 78248f949dc1bbcc7cbec1242763b34bd2abf43be89fdf506944b154f03c18c833e61913e504297a763fe75642e9d5dd3f3be7ece7240bd30bafb1b150348fc1
7
- data.tar.gz: 16f2aeebb6402dbbacbc06ee4d5bb3859fa2b4265318e66ce9a68e99a3d8d17a529d84eb3dae264d7498be90526318eb86e333d61fe6cd0c7c8acb333825a6bf
6
+ metadata.gz: 3363cb64593cbc9d94c154fc85383c3ec336cd776c97ed2e32175fd2861e60e586832d056eaaed4bc7be50aa396bf434362801b8fa7f9041ffbaa72a6f487e94
7
+ data.tar.gz: a57ff261b848a382f0677b963c5cd712c01729a6cfb54b8976f3cb0e765d49fb9cfb20a25bbdd7d45d456672ba754006f3c84a3e1ee193dc1f69398306d751c5
data/LICENSE.txt CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2023 Harold Martin <harold.martin@gmail.com>
1
+ Copyright (c) 2023-2024 Harold Martin <harold.martin@gmail.com>
2
2
 
3
3
  MIT License
4
4
 
data/README.md CHANGED
@@ -1,25 +1,20 @@
1
1
  # danger-spm_version_updates
2
2
 
3
- [![CI](https://github.com/hbmartin/danger-spm_version_updates/actions/workflows/lint_and_test.yml/badge.svg)](https://github.com/hbmartin/danger-spm_version_updates/actions/workflows/lint_and_test.yml)
4
- [![CodeFactor](https://www.codefactor.io/repository/github/hbmartin/danger-spm_version_updates/badge/main)](https://www.codefactor.io/repository/github/hbmartin/danger-spm_version_updates/overview/main)
5
- [![Gem Version](https://img.shields.io/gem/v/danger-spm_version_updates?color=D86149)](https://rubygems.org/gems/danger-spm_version_updates)
6
- [![codecov](https://codecov.io/gh/hbmartin/danger-spm_version_updates/graph/badge.svg?token=eXgUoWlvP7)](https://codecov.io/gh/hbmartin/danger-spm_version_updates)
7
- [![Documentation](https://img.shields.io/badge/Docs-3d3d41?logo=RubyGems)](https://hbmartin.github.io/danger-spm_version_updates/Danger/DangerSpmVersionUpdates.html)
8
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
9
-
10
- A [Danger](https://danger.systems/ruby/) plugin to detect if there are any updates to your Swift Package Manager dependencies.
11
-
12
- It's fast, lightweight, and does not require swift to be installed on the CI where it is run.
13
-
14
- Note that version 0.1.0 is the last version to support Ruby 2.7
3
+ A [Danger](https://danger.systems/ruby/) plugin to detect if there are any
4
+ updates to your Swift Package Manager dependencies. It supports both source
5
+ modes — Xcode projects and `Package.swift` manifests — and requires
6
+ Ruby >= 3.2.
7
+
8
+ The version-checking logic lives in the
9
+ [`spm_version_updates`](https://rubygems.org/gems/spm_version_updates) core
10
+ gem; this plugin is a thin Danger wrapper around it. The same checker also
11
+ powers the
12
+ [Swift Package Version Updates GitHub Action](https://github.com/hbmartin/github-action-spm_version_updates),
13
+ if you'd rather run dependency checks as a standalone action.
15
14
 
16
15
  ## Installation
17
16
 
18
- ```sh
19
- gem install danger-spm_version_updates
20
- ```
21
-
22
- or add the following to your Gemfile:
17
+ Add it to your Gemfile:
23
18
 
24
19
  ```ruby
25
20
  gem "danger-spm_version_updates"
@@ -27,63 +22,34 @@ gem "danger-spm_version_updates"
27
22
 
28
23
  ## Usage
29
24
 
30
- Just add this to your Dangerfile! Note that it is required to pass the path to your Xcode project.
25
+ Call it from your `Dangerfile`:
31
26
 
32
27
  ```ruby
33
- spm_version_updates.check_for_updates("Example.xcodeproj")
28
+ spm_version_updates.check_when_exact = false
29
+ spm_version_updates.report_above_maximum = false
30
+ spm_version_updates.report_pre_releases = false
31
+ spm_version_updates.ignore_repos = ["https://github.com/pointfreeco/swift-snapshot-testing"]
32
+ spm_version_updates.repo_rules_path = ".github/spm-version-rules.yml"
33
+ spm_version_updates.check_for_updates("MyApp.xcodeproj")
34
34
  ```
35
35
 
36
- You can also configure custom behaviors:
36
+ Or, for SwiftPM-first repos, check `Package.swift` manifests directly:
37
37
 
38
38
  ```ruby
39
- # Whether to check when dependencies are exact versions or commits, default false
40
- spm_version_updates.check_when_exact = true
41
-
42
- # Whether to report versions above the maximum version range, default false
43
- spm_version_updates.report_above_maximum = true
44
-
45
- # Whether to report pre-release versions, default false
46
- spm_version_updates.report_pre_releases = true
47
-
48
- # A list of repository URLs for packages to ignore entirely
49
- spm_version_updates.ignore_repos = ["https://github.com/pointfreeco/swift-snapshot-testing"]
39
+ spm_version_updates.check_manifests(["Modules/Package.swift", "BuildTools/Package.swift"])
50
40
  ```
51
41
 
52
- ## Development
53
-
54
- 1. Clone this repo
55
- 2. Run `bundle install` to setup dependencies.
56
- 3. Run `bundle exec rake spec` to run the tests.
57
- 4. Use `bundle exec guard` to automatically have tests run as you make changes.
58
- 5. Make your changes.
59
-
60
- ## Authors
61
-
62
- - [Harold Martin](https://www.linkedin.com/in/harold-martin-98526971/) - harold.martin at gmail
63
-
64
- ## Legal
65
-
66
- Swift and the Swift logo are trademarks of Apple Inc.
67
-
68
- Copyright (c) 2023 Harold Martin
69
-
70
- MIT License
42
+ `check_manifests` accepts a single path or a list, and an optional second
43
+ argument with explicit `Package.resolved` paths (by default a
44
+ `Package.resolved` next to each manifest is used). Each available update is
45
+ reported as a Danger `warn` that includes Compare/Releases links for supported
46
+ hosts, the originating manifest, and a ready-to-run `swift package update`
47
+ command in manifest mode.
71
48
 
72
- Permission is hereby granted, free of charge, to any person obtaining
73
- a copy of this software and associated documentation files (the
74
- "Software"), to deal in the Software without restriction, including
75
- without limitation the rights to use, copy, modify, merge, publish,
76
- distribute, sublicense, and/or sell copies of the Software, and to
77
- permit persons to whom the Software is furnished to do so, subject to
78
- the following conditions:
49
+ The configurable accessors are: `check_when_exact`, `check_branches`,
50
+ `check_revisions`, `report_above_maximum`, `report_pre_releases`,
51
+ `ignore_repos`, and `repo_rules_path`.
79
52
 
80
- The above copyright notice and this permission notice shall be
81
- included in all copies or substantial portions of the Software.
53
+ ## License
82
54
 
83
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
84
- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
85
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
86
- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
87
- LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
88
- OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
89
- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
55
+ MIT see [LICENSE.txt](LICENSE.txt).
@@ -1,8 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- lib = File.expand_path("lib", __dir__)
4
- $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
- require "spm_version_updates/gem_version"
3
+ # This sibling-directory require only resolves inside the repository checkout.
4
+ # `gem build` serializes the literal version into the packaged metadata, so
5
+ # the shipped .gemspec is not meant to be evaluated standalone.
6
+ require_relative "../spm_version_updates/lib/spm_version_updates/version"
6
7
 
7
8
  Gem::Specification.new do |spec|
8
9
  spec.name = "danger-spm_version_updates"
@@ -13,48 +14,33 @@ Gem::Specification.new do |spec|
13
14
  spec.summary = "A Danger plugin to detect if there are any updates to your Swift Package Manager dependencies."
14
15
  spec.homepage = "https://github.com/hbmartin/danger-spm_version_updates"
15
16
  spec.license = "MIT"
16
- spec.required_ruby_version = ">= 3.0"
17
-
18
- spec.files = Dir['lib/*'] + Dir['lib/**/*'] + Dir['*']
19
- spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
+ spec.required_ruby_version = ">= 3.2"
18
+
19
+ release_paths = [
20
+ "LICENSE.txt",
21
+ "README.md",
22
+ "danger-spm_version_updates.gemspec",
23
+ ]
24
+ spec.files = begin
25
+ git_files = begin
26
+ IO.popen(
27
+ ["git", "-C", __dir__, "ls-files", "-z", "lib", *release_paths],
28
+ err: File::NULL,
29
+ &:read
30
+ )
31
+ .split("\x0")
32
+ .reject(&:empty?)
33
+ rescue Errno::ENOENT
34
+ []
35
+ end
36
+ fallback_files = Dir.glob(["lib/**/*", *release_paths], base: __dir__)
37
+ .select { |path| File.file?(File.join(__dir__, path)) }
38
+ (git_files.empty? ? fallback_files : git_files).sort
39
+ end
20
40
  spec.require_paths = ["lib"]
21
41
  spec.metadata["rubygems_mfa_required"] = "true"
22
42
 
23
43
  spec.add_runtime_dependency("danger-plugin-api", "~> 1.0")
24
- spec.add_runtime_dependency("semantic", "~> 1.6")
44
+ spec.add_runtime_dependency("spm_version_updates", "~> #{SpmVersionUpdates::VERSION}")
25
45
  spec.add_runtime_dependency("xcodeproj", "~> 1.24")
26
-
27
- # General ruby development
28
- spec.add_development_dependency("bundler", "~> 2.0")
29
- spec.add_development_dependency("rake", "~> 13.0")
30
-
31
- # Testing support
32
- spec.add_development_dependency("rspec", "~> 3.9")
33
- spec.add_development_dependency("simplecov", "~> 0.22")
34
- spec.add_development_dependency("simplecov-cobertura", "~> 2.1")
35
-
36
- # Linting code and docs
37
- spec.add_development_dependency("reek")
38
- spec.add_development_dependency("rubocop", "~> 1.62")
39
- spec.add_development_dependency("rubocop-performance")
40
- spec.add_development_dependency("rubocop-rake")
41
- spec.add_development_dependency("rubocop-rspec")
42
- spec.add_development_dependency("yard", "~> 0.9.36")
43
-
44
- # Makes testing easy via `bundle exec guard`
45
- spec.add_development_dependency("guard", "~> 2.16")
46
- spec.add_development_dependency("guard-rspec", "~> 4.7")
47
- spec.add_development_dependency("guard-rubocop", "~> 1.2")
48
-
49
- # If you want to work on older builds of ruby
50
- spec.add_development_dependency("listen", "3.0.7")
51
-
52
- # This gives you the chance to run a REPL inside your tests
53
- # via:
54
- #
55
- # require 'pry'
56
- # binding.pry
57
- #
58
- # This will stop test execution and let you inspect the results
59
- spec.add_development_dependency("pry")
60
46
  end
@@ -1,3 +1,3 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "spm_version_updates/gem_version"
3
+ require "spm_version_updates/version"
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module SpmVersionUpdates
4
- VERSION = "0.2.0"
5
- end
3
+ # Compatibility shim: the version constant now lives in the spm_version_updates
4
+ # core gem. Existing `require "spm_version_updates/gem_version"` callers keep
5
+ # working through this file.
6
+ require "spm_version_updates/version"
@@ -1,44 +1,38 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "spm_version_updates/git_operations"
4
+ require "spm_version_updates/semver"
5
+
6
+ # Legacy git helper used by the Danger plugin API. Delegates to GitOperations,
7
+ # so lookups gain its retry behavior and raise GitOperations::LsRemoteError
8
+ # instead of masking failures as empty results.
9
+ # @deprecated Use {GitOperations} from the +spm_version_updates+ core gem
10
+ # instead. This shim exists only for backward compatibility with Dangerfiles
11
+ # written against +v0.2.0+.
3
12
  module Git
13
+ ALLOWED_PROTOCOLS = GitOperations::ALLOWED_PROTOCOLS
14
+
4
15
  # Removes protocol and trailing .git from a repo URL
5
16
  # @param [String] repo_url
6
17
  # The URL of the repository
7
18
  # @return [String]
8
19
  def self.trim_repo_url(repo_url)
9
- repo_url.split("://").last.gsub(/\.git$/, "")
20
+ GitOperations.trim_repo_url(repo_url)
10
21
  end
11
22
 
12
23
  # Extract a readable name for the repo given the url, generally org/repo
13
24
  # @return [String]
14
25
  def self.repo_name(repo_url)
15
- match = repo_url.match(%r{([\w-]+/[\w-]+)(.git)?$})
16
-
17
- if match
18
- match[1] || match[0]
19
- else
20
- repo_url
21
- end
26
+ GitOperations.repo_name(repo_url)
22
27
  end
23
28
 
24
29
  # Call git to list tags
25
30
  # @param [String] repo_url
26
31
  # The URL of the dependency's repository
27
- # @return [Array<Semantic::Version>]
32
+ # @raise [GitOperations::LsRemoteError] if the lookup fails after retries
33
+ # @return [Array<SpmVersionUpdates::Semver>]
28
34
  def self.version_tags(repo_url)
29
- versions = `git ls-remote -t #{repo_url}`
30
- .split("\n")
31
- .map { |line| line.split("/tags/").last }
32
- .filter_map { |line|
33
- begin
34
- Semantic::Version.new(line)
35
- rescue ArgumentError
36
- nil
37
- end
38
- }
39
- versions.sort!
40
- versions.reverse!
41
- versions
35
+ GitOperations.version_tags(repo_url)
42
36
  end
43
37
 
44
38
  # Call git to find the last commit on a branch
@@ -46,11 +40,9 @@ module Git
46
40
  # The URL of the dependency's repository
47
41
  # @param [String] branch_name
48
42
  # The name of the branch on which to find the last commit
49
- # @return [String]
43
+ # @raise [GitOperations::LsRemoteError] if the lookup fails after retries
44
+ # @return [String, nil]
50
45
  def self.branch_last_commit(repo_url, branch_name)
51
- `git ls-remote -h #{repo_url}`
52
- .split("\n")
53
- .find { |line| line.split("\trefs/heads/")[1] == branch_name }
54
- .split("\trefs/heads/")[0]
46
+ GitOperations.branch_last_commit(repo_url, branch_name)
55
47
  end
56
48
  end
@@ -1,15 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "semantic"
4
- require_relative "git"
5
- require_relative "xcode"
3
+ require "spm_version_updates"
6
4
 
5
+ # Danger's plugin namespace, reopened to register {DangerSpmVersionUpdates}.
7
6
  module Danger
8
7
  # A Danger plugin for checking if there are versions upgrades available for SPM dependencies
9
8
  #
10
9
  # @example Check if MyApp's SPM dependencies are up to date
11
10
  # spm_version_updates.check_for_updates("MyApp.xcodeproj")
12
11
  #
12
+ # @example Check dependencies declared in Package.swift manifests
13
+ # spm_version_updates.check_manifests(["Modules/Package.swift"])
14
+ #
13
15
  # @see hbmartin/danger-spm_version_updates
14
16
  # @tags swift, spm, swift package manager, xcode, xcodeproj, version, updates
15
17
  #
@@ -18,6 +20,14 @@ module Danger
18
20
  # @return [Boolean]
19
21
  attr_accessor :check_when_exact
20
22
 
23
+ # Whether to check dependencies pinned to a branch for newer commits, default true
24
+ # @return [Boolean]
25
+ attr_accessor :check_branches
26
+
27
+ # Whether to report the latest tagged version for dependencies pinned to a revision, default false
28
+ # @return [Boolean]
29
+ attr_accessor :check_revisions
30
+
21
31
  # Whether to report versions above the maximum version range, default false
22
32
  # @return [Boolean]
23
33
  attr_accessor :report_above_maximum
@@ -30,99 +40,116 @@ module Danger
30
40
  # @return [Array<String>]
31
41
  attr_accessor :ignore_repos
32
42
 
43
+ # Path to a YAML file with per-repository semantic update suppression rules
44
+ # @return [String]
45
+ attr_accessor :repo_rules_path
46
+
33
47
  # A method that you can call from your Dangerfile
34
48
  # @param [String] xcodeproj_path
35
49
  # The path to your Xcode project
36
- # @raise [XcodeprojPathMustBeSet] if the xcodeproj_path is blank
50
+ # @raise [XcodeParser::XcodeprojPathMustBeSet] if the xcodeproj_path is blank
51
+ # @raise [XcodeParser::CouldNotFindResolvedFile] if no Package.resolved files were found
37
52
  # @return [void]
38
53
  def check_for_updates(xcodeproj_path)
39
- remote_packages = Xcode.get_packages(xcodeproj_path)
40
- resolved_versions = Xcode.get_resolved_versions(xcodeproj_path)
41
- $stderr.puts("Found resolved versions for #{resolved_versions.size} packages")
42
-
43
- self.ignore_repos = self.ignore_repos&.map! { |repo| Git.trim_repo_url(repo) }
44
-
45
- remote_packages.each { |repository_url, requirement|
46
- next if self.ignore_repos&.include?(repository_url)
47
-
48
- name = Git.repo_name(repository_url)
49
- resolved_version = resolved_versions[repository_url]
50
- kind = requirement["kind"]
51
-
52
- if resolved_version.nil?
53
- $stderr.puts("Unable to locate the current version for #{name} (#{repository_url})")
54
- next
55
- end
56
-
57
- if kind == "branch"
58
- branch = requirement["branch"]
59
- last_commit = Git.branch_last_commit(repository_url, branch)
60
- warn("Newer commit available for #{name} (#{branch}): #{last_commit}") unless last_commit == resolved_version
61
- next
62
- end
63
-
64
- available_versions = Git.version_tags(repository_url)
65
- next if available_versions.first.to_s == resolved_version
66
-
67
- if kind == "exactVersion" && @check_when_exact
68
- warn_for_new_versions_exact(available_versions, name, resolved_version)
69
- elsif kind == "upToNextMajorVersion"
70
- warn_for_new_versions(:major, available_versions, name, resolved_version)
71
- elsif kind == "upToNextMinorVersion"
72
- warn_for_new_versions(:minor, available_versions, name, resolved_version)
73
- elsif kind == "versionRange"
74
- warn_for_new_versions_range(available_versions, name, requirement, resolved_version)
75
- end
76
- }
54
+ run_checker { |checker| checker.check_for_updates(xcodeproj_path) }
55
+ end
56
+
57
+ # Check for updates to dependencies declared in one or more Package.swift manifests
58
+ # @param [Array<String>, String] manifest_paths
59
+ # One or more paths to Package.swift manifests
60
+ # @param [Array<String>, String, nil] resolved_paths
61
+ # Optional explicit Package.resolved paths; defaults to a
62
+ # Package.resolved next to each manifest
63
+ # @raise [ManifestParser::ManifestPathMustBeSet] if manifest_paths is blank
64
+ # @raise [ManifestParser::CouldNotFindManifest] if a manifest does not exist
65
+ # @raise [ManifestParser::CouldNotFindResolvedFile] if an expected Package.resolved is missing
66
+ # @return [void]
67
+ def check_manifests(manifest_paths, resolved_paths = nil)
68
+ paths = Array(manifest_paths).map(&:to_s).reject(&:empty?)
69
+ raise(ManifestParser::ManifestPathMustBeSet) if paths.empty?
70
+
71
+ run_checker { |checker| checker.check_manifests(paths, Array(resolved_paths)) }
77
72
  end
78
73
 
79
74
  private
80
75
 
81
- def warn_for_new_versions_exact(available_versions, name, resolved_version)
82
- newest_version = available_versions.find { |version|
83
- report_pre_releases ? true : version.pre.nil?
76
+ def run_checker
77
+ checker = build_checker
78
+ yield(checker)
79
+ emit_checker_warnings(checker)
80
+ end
81
+
82
+ def build_checker
83
+ SpmChecker.new.tap { |checker|
84
+ copy_accessors(checker)
85
+ checker.repository_update_rules = repository_update_rules
86
+ checker.lookup_failure_handler = method(:warn_lookup_failure)
87
+ checker.malformed_resolved_handler = method(:warn_malformed_resolved)
84
88
  }
85
- warn(
86
- <<-TEXT
87
- Newer version of #{name}: #{newest_version} (but this package is set to exact version #{resolved_version})
88
- TEXT
89
- ) unless newest_version.to_s == resolved_version
90
89
  end
91
90
 
92
- def warn_for_new_versions_range(available_versions, name, requirement, resolved_version)
93
- max_version = Semantic::Version.new(requirement["maximumVersion"])
94
- if available_versions.first < max_version
95
- warn("Newer version of #{name}: #{available_versions.first}")
96
- else
97
- newest_meeting_reqs = available_versions.find { |version|
98
- version < max_version && (report_pre_releases ? true : version.pre.nil?)
99
- }
100
- warn("Newer version of #{name}: #{newest_meeting_reqs}") unless newest_meeting_reqs.to_s == resolved_version
101
- warn(
102
- <<-TEXT
103
- Newest version of #{name}: #{available_versions.first} (but this package is configured up to the next #{max_version} version)
104
- TEXT
105
- ) if report_above_maximum
106
- end
91
+ def copy_accessors(checker)
92
+ checker.check_when_exact = check_when_exact
93
+ checker.check_branches = check_branches != false
94
+ checker.check_revisions = check_revisions
95
+ checker.report_above_maximum = report_above_maximum
96
+ checker.report_pre_releases = report_pre_releases
97
+ checker.ignore_repos = Array(ignore_repos)
107
98
  end
108
99
 
109
- def warn_for_new_versions(major_or_minor, available_versions, name, resolved_version_string)
110
- resolved_version = Semantic::Version.new(resolved_version_string)
111
- newest_meeting_reqs = available_versions.find { |version|
112
- (version.send(major_or_minor) == resolved_version.send(major_or_minor)) && (report_pre_releases ? true : version.pre.nil?)
113
- }
100
+ def emit_checker_warnings(checker)
101
+ checker.warning_details.each { |detail| warn(render_warning(detail)) }
102
+ checker.parse_warnings.each { |record| warn(render_parse_warning(record)) }
103
+ end
114
104
 
115
- warn("Newer version of #{name}: #{newest_meeting_reqs}") unless newest_meeting_reqs == resolved_version
116
- return unless report_above_maximum
105
+ # Builds the Danger warning markdown for a `.package(...)` declaration the
106
+ # manifest parser had to skip.
107
+ def render_parse_warning(record)
108
+ "#{record['message']} ([open an issue](#{ParseWarning.issue_link(record)}))"
109
+ end
117
110
 
118
- newest_above_reqs = available_versions.find { |version|
119
- report_pre_releases ? true : version.pre.nil?
120
- }
121
- warn(
122
- <<-TEXT
123
- Newest version of #{name}: #{available_versions.first} (but this package is configured up to the next #{major_or_minor} version)
124
- TEXT
125
- ) unless newest_above_reqs == newest_meeting_reqs || newest_meeting_reqs.to_s == resolved_version
111
+ # Builds the Danger warning markdown for one structured warning detail:
112
+ # message, compare/release links when the host is supported, and the
113
+ # originating manifest. Uses <br> rather than newlines because Danger
114
+ # renders warnings inside a markdown table.
115
+ def render_warning(detail)
116
+ message = detail[:message]
117
+ links = warning_links(detail)
118
+ message = "#{message} (#{links})" if links
119
+
120
+ source = detail[:source]
121
+ message = "#{message}<br>Source: `#{source}`" if source
122
+
123
+ command = detail[:suggested_command]
124
+ command ? "#{message}<br>Update: `#{command}`" : message
125
+ end
126
+
127
+ def warning_links(detail)
128
+ link = RepositoryLink.from(detail[:repository_url])
129
+ return unless link
130
+
131
+ current, available = detail.values_at(:current_version, :available_version)
132
+ return unless current && available
133
+
134
+ link.markdown_links([{ current:, available: }], separator: " · ")
135
+ end
136
+
137
+ # Emits a Danger warning for a package whose remote lookup failed.
138
+ # @param package [SpmPackageContext] the package whose lookup failed
139
+ # @param error [GitOperations::LsRemoteError] the lookup failure
140
+ def warn_lookup_failure(package, error)
141
+ warn("Unable to check #{package.name} (#{package.normalized_url}) for updates: #{error.message}")
142
+ end
143
+
144
+ # Emits a Danger warning for a Package.resolved file that could not be parsed.
145
+ # @param resolved_path [String] the malformed file
146
+ # @param error [PackageResolved::MalformedFileError] the parse failure
147
+ def warn_malformed_resolved(resolved_path, error)
148
+ warn("Skipping malformed Package.resolved file #{resolved_path}: #{error.message}")
149
+ end
150
+
151
+ def repository_update_rules
152
+ repo_rules_path ? RepositoryUpdateRules.load_file(repo_rules_path) : RepositoryUpdateRules.empty
126
153
  end
127
154
  end
128
155
  end
@@ -1,7 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "spm_version_updates/package_resolved"
4
+ require "spm_version_updates/xcode_parser"
5
+ require "spm_version_updates/xcode_project_package_reader"
3
6
  require "xcodeproj"
4
7
 
8
+ # Legacy Xcode project parser used by the Danger plugin API.
9
+ # @deprecated Use {XcodeProjectPackageReader} and {XcodeParser} from the
10
+ # +spm_version_updates+ core gem instead. This shim exists only for backward
11
+ # compatibility with Dangerfiles written against +v0.2.0+.
5
12
  module Xcode
6
13
  # Find the configured SPM dependencies in the xcodeproj
7
14
  # @param [String] xcodeproj_path
@@ -10,62 +17,53 @@ module Xcode
10
17
  def self.get_packages(xcodeproj_path)
11
18
  raise(XcodeprojPathMustBeSet) if xcodeproj_path.nil? || xcodeproj_path.empty?
12
19
 
13
- project = Xcodeproj::Project.open(xcodeproj_path)
14
- project.objects.select { |obj|
15
- obj.kind_of?(Xcodeproj::Project::Object::XCRemoteSwiftPackageReference) &&
16
- obj.requirement["kind"] != "commit"
20
+ XcodeProjectPackageReader.package_references(xcodeproj_path).to_h { |package|
21
+ repository_url = package.repository_url
22
+ [Git.trim_repo_url(repository_url), package.requirement]
17
23
  }
18
- .to_h { |package|
19
- [Git.trim_repo_url(package.repositoryURL), package.requirement]
20
- }
21
24
  end
22
25
 
23
- # Extracts resolved versions from Package.resolved relative to an Xcode project
26
+ # Extracts resolved versions from Package.resolved relative to an Xcode project.
27
+ # When a block is given, malformed resolved files are reported to it as
28
+ # `(resolved_path, error)` and skipped; without a block the error is raised.
24
29
  # @param [String] xcodeproj_path
25
30
  # The path to your Xcode project
26
31
  # @raise [CouldNotFindResolvedFile] if no Package.resolved files were found
32
+ # @raise [PackageResolved::MalformedFileError] if a resolved file is invalid JSON and no block is given
27
33
  # @return [Hash<String, String>]
28
34
  def self.get_resolved_versions(xcodeproj_path)
29
35
  resolved_paths = find_packages_resolved_file(xcodeproj_path)
30
36
  raise(CouldNotFindResolvedFile) if resolved_paths.empty?
31
37
 
32
- resolved_versions = resolved_paths.map { |resolved_path|
33
- contents = JSON.load_file!(resolved_path)
34
- pins = contents["pins"] || contents["object"]["pins"]
35
- pins.to_h { |pin|
36
- [
37
- Git.trim_repo_url(pin["location"] || pin["repositoryURL"]),
38
- pin["state"]["version"] || pin["state"]["revision"],
39
- ]
40
- }
38
+ resolved_paths.each_with_object({}) { |resolved_path, pins|
39
+ begin
40
+ pins.merge!(PackageResolved.versions_from(resolved_path))
41
+ rescue PackageResolved::MalformedFileError => error
42
+ raise unless block_given?
43
+
44
+ yield(resolved_path, error)
45
+ end
41
46
  }
42
- resolved_versions.reduce(:merge!)
43
47
  end
44
48
 
45
49
  # Find the Packages.resolved file
46
50
  # @return [Array<String>]
47
51
  def self.find_packages_resolved_file(xcodeproj_path)
48
- locations = []
49
- # First check the workspace for a resolved file
50
- workspace = xcodeproj_path.sub("xcodeproj", "xcworkspace")
51
- if Dir.exist?(workspace)
52
- path = File.join(workspace, "xcshareddata", "swiftpm", "Package.resolved")
53
- locations << path if File.exist?(path)
54
- end
55
-
56
- # Then check the project for a resolved file
57
- path = File.join(xcodeproj_path, "project.xcworkspace", "xcshareddata", "swiftpm", "Package.resolved")
58
- locations << path if File.exist?(path)
52
+ checked = XcodeProjectPackageReader.package_resolved_candidate_paths(xcodeproj_path)
53
+ locations = checked.select { |path| File.exist?(path) }
59
54
 
60
- $stderr.puts("Searching for resolved packages in: #{locations}")
55
+ Kernel.warn("Checked Package.resolved paths: #{checked}")
56
+ Kernel.warn("Found Package.resolved paths: #{locations}")
61
57
  locations
62
58
  end
63
59
 
64
60
  private_class_method :find_packages_resolved_file
65
61
 
66
- class XcodeprojPathMustBeSet < StandardError
67
- end
62
+ # Raised when Danger plugin Xcode mode is invoked without a project path.
63
+ # Aliased to the core parser's class so plugin callers rescuing the legacy
64
+ # name keep working now that the plugin delegates to SpmChecker/XcodeParser.
65
+ XcodeprojPathMustBeSet = XcodeParser::XcodeprojPathMustBeSet
68
66
 
69
- class CouldNotFindResolvedFile < StandardError
70
- end
67
+ # Raised when a Danger plugin Xcode project has no Package.resolved file.
68
+ CouldNotFindResolvedFile = XcodeParser::CouldNotFindResolvedFile
71
69
  end