npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

sparkle-validator

v1.2.0

Published

Validate Sparkle appcast.xml feeds — CLI tool, library, and web app

Downloads

264

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:

  1. Sparkle Source Code — Direct analysis of version comparison logic, signature verification, and parsing behavior in the Sparkle 2.x codebase
  2. Maintainer Feedback — Rule refinements based on feedback from Sparkle maintainer @zorgiepoo
  3. 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-validator

Or run directly without installing:

npx sparkle-validator https://example.com/appcast.xml

Or with Homebrew:

brew tap dweekly/sparkle-validator
brew install sparkle-validator

CLI 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.xml

CLI 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: true

Action 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.json

Validate Remote Appcast

      - name: Validate published appcast
        uses: dweekly/Sparkle-Validator@v1
        with:
          file: https://example.com/appcast.xml

Pre-commit Hook

# .git/hooks/pre-commit
#!/bin/sh
npx sparkle-validator appcast.xml || exit 1

Library 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 diagnostics

ValidationResult

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 lint

Supply 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.