spm_version_updates
Core library for detecting available updates to Swift Package Manager
dependencies. It powers both the
danger-spm_version_updates
Danger plugin and the
Swift Package Version Updates GitHub Action,
and can be used directly from any Ruby program.
Installation
gem install spm_version_updates
Xcode-project mode additionally requires the xcodeproj gem; manifest mode
has no extra dependencies.
Usage
require "spm_version_updates"
checker = SpmChecker.new
# Manifest mode: check one or more Package.swift files (a Package.resolved
# next to each manifest is used automatically when present).
warnings = checker.check_manifests(["path/to/Package.swift"])
# Xcode mode: check the packages referenced by an Xcode project.
# warnings = checker.check_for_updates("path/to/App.xcodeproj")
warnings.each { |warning| puts warning }
# Structured details (repository URL, current/available version, severity,
# suggested update command, ...) for each warning:
checker.warning_details.each { |detail| p detail }
Behavior is configurable through accessors on SpmChecker — for example
check_when_exact, check_branches, report_above_maximum,
report_pre_releases, ignore_repos, and allow-host restrictions. See the
class documentation for the full list.
Errors
Everything the gem raises descends from one of two roots (defined in
lib/spm_version_updates/errors.rb), so callers can rescue by failure
category instead of enumerating concrete classes:
SpmVersionUpdates::Error < StandardErrorFileNotFoundError— a required file is missing:ManifestParser::CouldNotFindManifest— aPackage.swiftpath does not exist.ManifestParser::CouldNotFindResolvedFile— an expectedPackage.resolvedis missing in manifest mode; the message names the missing file(s). Raised rather than silently reporting incomplete results.XcodeParser::CouldNotFindResolvedFile— noPackage.resolvedwas found in the Xcode workspace locations.ParseError— a file exists but could not be read:PackageResolved::MalformedFileError— a corrupt or unrecognizedPackage.resolved.NetworkError— git lookup failures:GitOperations::LsRemoteError—git ls-remotefailed after bounded retries (unreachable host, authentication failure). Messages are credential-redacted.PolicyError— security-gate violations:SpmChecker::DisallowedRepositoryHost—allow_hostsis configured and a dependency's host is not on the list. Raised before git is contacted.
SpmVersionUpdates::ConfigurationError < ArgumentError— invalid caller-supplied configuration:ManifestParser::ManifestPathMustBeSet,XcodeParser::XcodeprojPathMustBeSet,allow_hostsentries that don't parse as hostnames, and every invalid repo-rules YAML shape. It inheritsArgumentError(notError) so existing callers that rescueArgumentErrorkeep working — rescue it alongsideSpmVersionUpdates::Errorwhen catching everything the gem raises:
begin
checker.check_manifests(["Modules/Package.swift"])
rescue SpmVersionUpdates::ConfigurationError, SpmVersionUpdates::Error => error
abort(error.)
end
Continuing past per-dependency failures
By default, the first failed git lookup or malformed Package.resolved
raises and aborts the run. Two optional handlers turn those into callbacks so
the remaining dependencies keep being checked:
# Called as (package, error) instead of raising GitOperations::LsRemoteError.
# A dependency shared by several manifests is reported only once per run.
checker.lookup_failure_handler = ->(package, error) {
puts("Skipping #{package.name}: #{error.}")
}
# Called as (resolved_path, error) instead of raising
# PackageResolved::MalformedFileError; the file's pins are skipped.
checker.malformed_resolved_handler = ->(path, error) {
puts("Ignoring #{path}: #{error.}")
}
Parse warnings
A .package(...) declaration whose version requirement isn't recognized (or
that has unbalanced parentheses) is skipped rather than guessed at. Each skip
is recorded on the checker — separate from update warnings, so update counts
and fail-on thresholds are unaffected:
checker.check_manifests(["Modules/Package.swift"])
checker.parse_warnings.each do |record|
# String-keyed hash: "type" ("parse_warning"), "reason", "source"
# (the manifest path), "snippet" (credential-redacted, truncated),
# and a human-readable "message".
puts(record["message"])
puts(ParseWarning.describe_reason(record)) # the reason as a readable phrase
puts(ParseWarning.issue_link(record)) # pre-filled GitHub new-issue URL
end
The snippet is redacted and shown in the report only — it is never embedded in the issue URL, where it could leak private repository URLs.
License
MIT — see LICENSE.txt.