← Files OneSignalARCHIVED FILE

skills/setup/scripts/resolve_sdk_version.py

8.3 KB · Sep 30, 2026 · 22:50 UTC

↓ Download file

#!/usr/bin/env python3
"""Resolve the exact, pinned OneSignal SDK version for a platform.

This exists to remove a specific failure: models were emitting Gradle/CocoaPods
version *ranges* (e.g. `[5.6.1, 5.9.99]`), which cause real mobile build
failures. There is no code path here that can produce a range — the only
output is an exact pin read from the official release feed.

Source of truth: https://onesignal.github.io/sdk-releases/releases.json
Never guess a version. If the feed is unreachable, this exits non-zero and the
caller must ask the user to confirm — it must not invent a number.

Usage:
    resolve_sdk_version.py <platform> [--track stable|current] [--format FMT]

Platforms:
    android ios web react-native expo flutter cordova capacitor unity

Formats:
    json (default)  machine + human summary: version, coordinate, dep line
    raw             just the exact version string (e.g. 5.9.1)
    line            just the ready-to-paste dependency line for the platform

Exit codes:
    0 resolved   2 unknown platform / no version on any track   3 feed unreachable
"""
import argparse
import json
import sys
import urllib.request

FEED_URL = "https://onesignal.github.io/sdk-releases/releases.json"

# platform key -> (feed "name", how to render the dependency line)
# The line renderers take the bare version and return the exact, pinned snippet.
PLATFORMS = {
    "android": {
        "feed": "Android",
        "coordinate": "com.onesignal:OneSignal",
        "lines": {
            "gradle-kts": 'implementation("com.onesignal:OneSignal:{v}") // onesignal:managed v1',
            "gradle-groovy": "implementation 'com.onesignal:OneSignal:{v}' // onesignal:managed v1",
        },
        "default_line": "gradle-kts",
    },
    "ios": {
        "feed": "iOS",
        "coordinate": "OneSignal-iOS-SDK / OneSignalXCFramework",
        "lines": {
            "spm": 'https://github.com/OneSignal/OneSignal-iOS-SDK — "Exact Version" {v} (product: OneSignalFramework)',
            "podfile": "pod 'OneSignal/OneSignal', '{v}' # onesignal:managed v1",
        },
        "default_line": "spm",
    },
    "react-native": {
        "feed": "ReactNative",
        "coordinate": "react-native-onesignal",
        "lines": {"npm": '"react-native-onesignal": "{v}"'},
        "default_line": "npm",
    },
    "expo": {
        "feed": "ReactNative",  # the runtime dependency; plugin resolved separately below
        "coordinate": "react-native-onesignal + onesignal-expo-plugin",
        "lines": {"npm": '"react-native-onesignal": "{v}"'},
        "default_line": "npm",
        "companion": {"feed": "Expo", "coordinate": "onesignal-expo-plugin"},
    },
    "flutter": {
        "feed": "Flutter",
        "coordinate": "onesignal_flutter",
        "lines": {"pubspec": "onesignal_flutter: {v}"},
        "default_line": "pubspec",
    },
    "cordova": {
        "feed": "Cordova",
        "coordinate": "onesignal-cordova-plugin",
        "lines": {"npm": '"onesignal-cordova-plugin": "{v}"'},
        "default_line": "npm",
    },
    "capacitor": {
        "feed": "Capacitor",
        "coordinate": "@onesignal/capacitor-plugin",
        "lines": {"npm": '"@onesignal/capacitor-plugin": "{v}"'},
        "default_line": "npm",
    },
    "unity": {
        "feed": "Unity",
        "coordinate": "com.onesignal.unity.push",
        "lines": {"unity": "OneSignal Unity SDK {v} (install via .unitypackage / UPM — GUI step)"},
        "default_line": "unity",
    },
    # Web is special: it ships from a fixed CDN major (v16), not a pinned
    # semver. The build number in the feed is metadata; the correct "pin" is
    # the CDN v16 script, which is stable and self-updating within v16.
    "web": {
        "feed": "Web",
        "coordinate": "cdn.onesignal.com/sdks/web/v16",
        "lines": {
            "cdn": '<script src="https://cdn.onesignal.com/sdks/web/v16/OneSignalSDK.page.js" defer></script>',
        },
        "default_line": "cdn",
        "web_fixed_major": "v16",
    },
}


def fetch_feed():
    try:
        with urllib.request.urlopen(FEED_URL, timeout=15) as r:
            return json.load(r)
    except Exception as e:  # noqa: BLE001 - any failure means "ask the user"
        sys.stderr.write(
            f"ERROR: could not fetch {FEED_URL}: {e}\n"
            "Do NOT guess a version. Ask the user to confirm the SDK version from\n"
            "the OneSignal dashboard or release notes, then pin it exactly.\n"
        )
        sys.exit(3)


def find_entry(feed, feed_name):
    for e in feed:
        if e.get("name") == feed_name:
            return e
    return None


def resolve_version(feed, feed_name, track):
    """Return (version, track_used). Falls back stable->current with a note."""
    entry = find_entry(feed, feed_name)
    if entry is None:
        return None, None
    channels = entry.get("channels") or {}
    order = [track] + [t for t in ("current", "stable") if t != track]
    for t in order:
        ch = channels.get(t)
        if ch and ch.get("version"):
            v = ch["version"].lstrip("v")  # feed prefixes some API SDKs with 'v'
            return v, t
    return None, None


def main():
    ap = argparse.ArgumentParser(description="Resolve exact OneSignal SDK version (never a range).")
    ap.add_argument("platform", choices=sorted(PLATFORMS.keys()))
    ap.add_argument("--track", default="stable", choices=["stable", "current"])
    ap.add_argument("--format", default="json", choices=["json", "raw", "line"])
    ap.add_argument("--line-format", default=None,
                    help="override the dependency-line renderer (e.g. gradle-groovy)")
    args = ap.parse_args()

    spec = PLATFORMS[args.platform]
    feed = fetch_feed()

    version, track_used = resolve_version(feed, spec["feed"], args.track)
    if version is None:
        sys.stderr.write(
            f"ERROR: no version found for '{args.platform}' on any track. "
            "The feed shape may have changed — ask the user to confirm the version.\n"
        )
        sys.exit(2)
    # Emit the track substitution on stderr too, so it is visible in raw/line
    # formats (which print only the version/dep line to stdout) — not just in JSON.
    if track_used != args.track:
        sys.stderr.write(
            f"NOTE: requested track '{args.track}' had no release for '{args.platform}'; "
            f"used '{track_used}' ({version}).\n"
        )

    line_key = args.line_format or spec["default_line"]
    if line_key not in spec["lines"]:
        line_key = spec["default_line"]
    dep_line = spec["lines"][line_key].format(v=version)

    companion = None
    if "companion" in spec:
        cv, ct = resolve_version(feed, spec["companion"]["feed"], args.track)
        if cv:
            companion = {
                "coordinate": spec["companion"]["coordinate"],
                "version": cv,
                "track_used": ct,
                "line": f'"onesignal-expo-plugin": "{cv}"',
            }
        else:
            # Don't fall silent: an unresolved companion is exactly the case where
            # the agent goes back to memory and fabricates a plugin version.
            sys.stderr.write(
                f"WARNING: could not resolve companion package "
                f"'{spec['companion']['coordinate']}' from the feed. Do NOT guess its "
                "version — confirm it with the user before pinning.\n"
            )

    if args.format == "raw":
        print(version)
        return
    if args.format == "line":
        print(dep_line)
        if companion:
            print(companion["line"])
        return

    out = {
        "platform": args.platform,
        "coordinate": spec["coordinate"],
        "version": version,
        "track_requested": args.track,
        "track_used": track_used,
        "line_format": line_key,
        "dependency_line": dep_line,
        "source": FEED_URL,
        "is_range": False,
    }
    if spec.get("web_fixed_major"):
        out["note"] = (
            f"Web pins to the fixed CDN major {spec['web_fixed_major']}; "
            f"feed build number is {version} (metadata only)."
        )
    if track_used != args.track:
        fallback = f"Requested track '{args.track}' had no release; used '{track_used}'."
        out["note"] = (out.get("note", "") + " " + fallback).strip()
    if companion:
        out["companion"] = companion
    print(json.dumps(out, indent=2))


if __name__ == "__main__":
    main()

SHA-256: ace92e553b189a17a56fac0bc0de6c9f232516f6f12ce0b054b6396bf5ab9b1b