sparkle-validator
v1.2.0
Published
Validate Sparkle appcast.xml feeds — CLI tool, library, and web app
Downloads
264
Maintainers
Readme
Note: This is an independent community project. It is not affiliated with, endorsed by, or sponsored by the official Sparkle project or its maintainers.
Methodology
This validator was developed by analyzing 500+ real-world appcasts from production macOS applications including iTerm2, VLC, Cyberduck, Brave Browser, Dash, Transmission, and many others. Our validation rules are grounded in:
- Sparkle Source Code — Direct analysis of version comparison logic, signature verification, and parsing behavior in the Sparkle 2.x codebase
- Maintainer Feedback — Rule refinements based on feedback from Sparkle maintainer @zorgiepoo
- Real-World Patterns — Identifying common issues that cause silent failures (missing versions, malformed signatures, date/version ordering mismatches)
The test corpus includes apps that are:
- Perfect (zero warnings): LowProfile, Scroll Reverser, SourceTree, Skim
- Real-world valid (minor warnings): iTerm2, Dash, Cyberduck, Tunnelblick
- Edge cases: Large feeds (200+ items), delta-heavy feeds, multi-channel feeds
This empirical approach ensures the validator catches issues that actually matter in production while minimizing false positives.
Features
- Validates Sparkle appcast.xml feeds against all known requirements
- Reports errors, warnings, and informational messages with line numbers
- Provides fix suggestions for common issues
- Works as CLI, library, web app, or GitHub Action
- Checks:
- XML structure (RSS 2.0 + Sparkle namespace)
- Version declarations
- Enclosure attributes (url, length, type)
- URL validity
- Date formats (RFC 2822)
- Signatures (EdDSA/DSA)
- System requirements
- Delta updates
- Phased rollouts
- Channel names
- And more...
Web App
Try it online at SparkleValidator.com
CLI Installation
npm install -g sparkle-validatorOr run directly without installing:
npx sparkle-validator https://example.com/appcast.xmlOr with Homebrew:
brew tap dweekly/sparkle-validator
brew install sparkle-validatorCLI Usage
# Validate a local file
sparkle-validator appcast.xml
# Validate from URL
sparkle-validator https://example.com/appcast.xml
# Validate from stdin
cat appcast.xml | sparkle-validator -
# JSON output
sparkle-validator --format json appcast.xml
# Strict mode (warnings as errors)
sparkle-validator --strict appcast.xml
# Only show errors
sparkle-validator --quiet appcast.xml
# Check that URLs exist and sizes match
sparkle-validator --check-urls appcast.xml
# Check URLs with custom timeout (ms)
sparkle-validator --check-urls --timeout 30000 appcast.xmlCLI Options
| Option | Description |
|--------|-------------|
| -f, --format <type> | Output format: text (default) or json |
| -s, --strict | Treat warnings as errors |
| -c, --check-urls | Check that URLs exist and sizes match |
| --timeout <ms> | Timeout for URL checks (default: 10000ms) |
| --no-info | Suppress informational messages |
| --no-color | Disable colored output |
| -q, --quiet | Only show errors |
| -v, --version | Show version number |
| -h, --help | Show help |
Exit Codes
| Code | Meaning |
|------|---------|
| 0 | Valid (no errors) |
| 1 | Invalid (has errors, or warnings with --strict) |
| 2 | Input error (file not found, network error, etc.) |
CI/CD Integration
GitHub Action
Use the official GitHub Action for the simplest integration:
name: Validate Appcast
on:
push:
paths: ['appcast.xml']
pull_request:
paths: ['appcast.xml']
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Validate appcast.xml
uses: dweekly/Sparkle-Validator@v1
with:
file: appcast.xml
# With options
- name: Validate with URL checking
uses: dweekly/Sparkle-Validator@v1
with:
file: appcast.xml
strict: true
check-urls: trueAction Inputs
| Input | Description | Default |
|-------|-------------|---------|
| file | Path or URL to appcast.xml | (required) |
| strict | Treat warnings as errors | false |
| check-urls | Verify enclosure URLs exist | false |
| timeout | URL check timeout (ms) | 10000 |
| quiet | Only show errors | false |
| format | Output format: text or json | text |
Action Outputs
| Output | Description |
|--------|-------------|
| valid | true if no errors |
| error-count | Number of errors |
| warning-count | Number of warnings |
| info-count | Number of info messages |
| json | Full result as JSON |
npx Alternative
- name: Validate appcast.xml
run: npx sparkle-validator appcast.xml
# Strict mode (warnings fail the build)
- name: Validate appcast.xml (strict)
run: npx sparkle-validator --strict appcast.xml
# JSON output for further processing
- name: Validate and capture results
run: |
npx sparkle-validator --format json appcast.xml > validation.json
cat validation.jsonValidate Remote Appcast
- name: Validate published appcast
uses: dweekly/Sparkle-Validator@v1
with:
file: https://example.com/appcast.xmlPre-commit Hook
# .git/hooks/pre-commit
#!/bin/sh
npx sparkle-validator appcast.xml || exit 1Library Usage
import { validate } from 'sparkle-validator';
const xml = `<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:sparkle="http://www.andymatuschak.org/xml-namespaces/sparkle">
<channel>
<title>My App</title>
<link>https://example.com</link>
<item>
<title>Version 2.0</title>
<pubDate>Thu, 13 Jul 2023 14:30:00 -0700</pubDate>
<sparkle:version>200</sparkle:version>
<description><![CDATA[<p>New features!</p>]]></description>
<enclosure url="https://example.com/app.zip"
length="12345678"
type="application/octet-stream"
sparkle:edSignature="eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eA==" />
</item>
</channel>
</rss>`;
const result = validate(xml);
console.log(result.valid); // true
console.log(result.errorCount); // 0
console.log(result.diagnostics); // Array of diagnosticsValidationResult
interface ValidationResult {
valid: boolean; // true if no errors
diagnostics: Diagnostic[];
errorCount: number;
warningCount: number;
infoCount: number;
}
interface Diagnostic {
id: string; // e.g. "E008", "W003"
severity: "error" | "warning" | "info";
message: string;
line?: number; // 1-based
column?: number; // 1-based
path?: string; // e.g. "rss > channel > item[2] > enclosure"
fix?: string; // Suggestion for fixing the issue
}Validation Rules
Errors (E001-E031, excluding E026)
| ID | Description |
|----|-------------|
| E001 | Not well-formed XML |
| E002 | Root element is not <rss> |
| E003 | Missing version="2.0" on <rss> |
| E004 | Missing Sparkle namespace declaration |
| E005 | Missing <channel> inside <rss> |
| E006 | More than one <channel> element |
| E007 | No <item> elements in <channel> |
| E008 | Item missing sparkle:version |
| E009 | Item has neither <enclosure> with url nor <link> |
| E010-E013 | Enclosure missing/invalid attributes |
| E014-E018 | Invalid URLs |
| E019 | Invalid channel name characters |
| E020-E021 | Phased rollout errors |
| E022 | Invalid installationType |
| E023-E025 | Delta update structure errors |
| E027 | URL returns non-2xx status (--check-urls) |
| E028 | Content-Length doesn't match declared length (--check-urls) |
| E029 | Version string is empty or whitespace-only |
| E030 | Invalid sparkle:os value (must be "macos" or "windows") |
| E031 | Invalid Ed25519/DSA signature (malformed base64 or wrong length) |
Warnings (W001-W043)
| ID | Description |
|----|-------------|
| W001-W002 | Missing title on channel/item |
| W003-W004 | Missing or invalid pubDate |
| W006 | DSA-only signature (deprecated, use EdDSA) |
| W007-W008 | Redundant version declarations |
| W009 | No release notes |
| W010 | Non-standard MIME type |
| W011-W013 | System version format issues |
| W014 | (Moved to I011) |
| W016 | Unencoded URL characters |
| W017 | informationalUpdate with enclosure |
| W018 | Items not sorted by version (newest first) |
| W019 | Enclosure length is 0 |
| W020 | Duplicate version |
| W021 | URL redirects to different location (--check-urls) |
| W022 | Content-Length header missing (--check-urls) |
| W023 | Local/private URL skipped (--check-urls) |
| W024 | URL uses insecure HTTP instead of HTTPS (--check-urls) |
| W025 | pubDate is in the future |
| W026 | pubDate is implausibly old (before 2001/Mac OS X era) |
| W027 | Version string is non-numeric (may cause comparison failures) |
| W028 | Version decreases while pubDate increases |
| W030 | URL file extension doesn't match expected type |
| W031 | (Moved to I012) |
| W032 | Multiple delta enclosures for same deltaFrom |
| W033 | shortVersionString format unusual (not x.y.z) |
| W034 | criticalUpdate version attribute not valid format |
| W035 | Feed mixes HTTP and HTTPS URLs |
| W036 | hardwareRequirements contains unknown architecture |
| W037 | releaseNotesLink missing xml:lang for localization |
| W038 | CDATA section used in version/signature elements |
| W039 | XML declaration missing encoding attribute |
| W040 | Channel has language but items have different lang |
| W041 | Version missing but deducible from filename (Sparkle fallback) |
| W042 | Version only as enclosure attribute (prefer <sparkle:version> element) |
| W043 | sparkle:os deprecated (prefer separate feeds per platform) |
Info (I001-I012)
| ID | Description | |----|-------------| | I001 | Summary: N items across M channels | | I002 | Item contains N delta updates | | I003 | Item uses phased rollout | | I004 | Item marked as critical update | | I005 | Item targets non-macOS platform | | I006 | Item requires specific hardware (Sparkle 2.9+) | | I007 | Item requires minimum app version to update (Sparkle 2.9+) | | I008 | Feed contains >50 items (performance consideration) | | I009 | Summary of OS support range across all items | | I010 | Enclosure has no signature (signatures are optional) | | I011 | Missing channel link (informational) | | I012 | Delta references version not in feed (old versions may be pruned) |
Development
# Install dependencies
npm install
# Run tests
npm test
# Build
npm run build
# Type check
npm run lintSupply Chain Security
This package is published with:
- npm provenance — cryptographically attests that the package was built from this repository via GitHub Actions
- SBOM — Software Bill of Materials (CycloneDX format) attached to each GitHub release
You can verify provenance on npm: npm audit signatures
License
MIT License - see LICENSE for details.
Contributing
See CONTRIBUTING.md for guidelines.
