Unverified Commit 581cdfc6 authored by Austin Horstman's avatar Austin Horstman
Browse files

yaziPlugins: rewrite update script in python

parent 2de04232
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -6,7 +6,7 @@
}:
let
  root = ./.;
  updateScript = ./update.sh;
  updateScript = ./update.py;

  mkYaziPlugin =
    args@{
+256 −0
Original line number Diff line number Diff line
#!/usr/bin/env nix-shell
#!nix-shell -i python3 -p python3 python3Packages.requests python3Packages.packaging nix curl git

import os
import re
import subprocess
import sys
from pathlib import Path
from typing import Dict, Tuple

import requests
from packaging import version


def run_command(cmd: str, capture_output: bool = True) -> str:
    """Run a shell command and return its output"""
    result = subprocess.run(cmd, shell=True, text=True, capture_output=capture_output)
    if result.returncode != 0:
        if capture_output:
            print(f"Error running command: {cmd}")
            print(f"stderr: {result.stderr}")
        sys.exit(1)
    return result.stdout.strip() if capture_output else ""


def get_plugin_info(nixpkgs_dir: str, plugin_name: str) -> Dict[str, str]:
    """Get plugin repository information from Nix"""
    owner = run_command(f"nix eval --raw -f {nixpkgs_dir} yaziPlugins.\"{plugin_name}\".src.owner")
    repo = run_command(f"nix eval --raw -f {nixpkgs_dir} yaziPlugins.\"{plugin_name}\".src.repo")

    return {
        "owner": owner,
        "repo": repo
    }


def get_yazi_version(nixpkgs_dir: str) -> str:
    """Get the current Yazi version from Nix"""
    return run_command(f"nix eval --raw -f {nixpkgs_dir} yazi-unwrapped.version")



def get_github_headers() -> Dict[str, str]:
    """Create headers for GitHub API requests"""
    headers = {"Accept": "application/vnd.github.v3+json"}
    github_token = os.environ.get("GITHUB_TOKEN")
    if github_token:
        headers["Authorization"] = f"token {github_token}"
    return headers


def fetch_plugin_content(owner: str, repo: str, plugin_pname: str, headers: Dict[str, str]) -> str:
    """Fetch the plugin's main.lua content from GitHub"""
    plugin_path = f"{plugin_pname}/" if owner == "yazi-rs" else ""
    main_lua_url = f"https://raw.githubusercontent.com/{owner}/{repo}/main/{plugin_path}main.lua"

    try:
        response = requests.get(main_lua_url, headers=headers)
        response.raise_for_status()
        return response.text
    except requests.RequestException as e:
        print(f"Error fetching plugin content: {e}")
        sys.exit(1)


def check_version_compatibility(plugin_content: str, plugin_name: str, yazi_version: str) -> str:
    """Check if the plugin is compatible with the current Yazi version"""
    required_version_match = re.search(r"since ([0-9.]+)", plugin_content.split("\n")[0])
    required_version = required_version_match.group(1) if required_version_match else "0"

    if required_version == "0":
        print(f"No version requirement found for {plugin_name}, assuming compatible with any Yazi version")
    else:
        # Check if the plugin is compatible with current Yazi version
        if version.parse(required_version) > version.parse(yazi_version):
            print(f"{plugin_name} plugin requires Yazi {required_version}, but we have {yazi_version}")
            sys.exit(0)

    return required_version


def get_latest_commit(owner: str, repo: str, plugin_pname: str, headers: Dict[str, str]) -> Tuple[str, str]:
    """Get the latest commit hash and date for the plugin"""
    if owner == "yazi-rs":
        # For official plugins, get commit info for the specific plugin file
        api_url = f"https://api.github.com/repos/{owner}/{repo}/commits?path={plugin_pname}/main.lua&per_page=1"
    else:
        # For third-party plugins, get latest commit on main branch
        api_url = f"https://api.github.com/repos/{owner}/{repo}/commits/main"

    try:
        response = requests.get(api_url, headers=headers)
        response.raise_for_status()
        commit_data = response.json()
    except requests.RequestException as e:
        print(f"Error fetching commit data: {e}")
        sys.exit(1)

    if owner == "yazi-rs":
        latest_commit = commit_data[0]["sha"]
        commit_date = commit_data[0]["commit"]["committer"]["date"].split("T")[0]
    else:
        latest_commit = commit_data["sha"]
        commit_date = commit_data["commit"]["committer"]["date"].split("T")[0]

    if not latest_commit:
        print("Error: Could not get latest commit hash")
        sys.exit(1)

    return latest_commit, commit_date


def calculate_sri_hash(owner: str, repo: str, latest_commit: str) -> str:
    """Calculate the SRI hash for the plugin source"""
    prefetch_url = f"https://github.com/{owner}/{repo}/archive/{latest_commit}.tar.gz"

    try:
        new_hash = run_command(f"nix-prefetch-url --unpack --type sha256 {prefetch_url} 2>/dev/null")

        # If the hash is not in SRI format, convert it
        if not new_hash.startswith("sha256-"):
            # Try to convert the hash to SRI format
            new_hash = run_command(f"nix hash to-sri --type sha256 {new_hash} 2>/dev/null")

            # If that fails, try another approach
            if not new_hash.startswith("sha256-"):
                print("Warning: Failed to get SRI hash directly, trying alternative method...")
                raw_hash = run_command(f"nix-prefetch-url --type sha256 {prefetch_url} 2>/dev/null")
                new_hash = run_command(f"nix hash to-sri --type sha256 {raw_hash} 2>/dev/null")
    except Exception as e:
        print(f"Error calculating hash: {e}")
        sys.exit(1)

    # Verify we got a valid SRI hash
    if not new_hash.startswith("sha256-"):
        print(f"Error: Failed to generate valid SRI hash. Output was: {new_hash}")
        sys.exit(1)

    return new_hash


def read_nix_file(file_path: str) -> str:
    """Read the content of a Nix file"""
    try:
        with open(file_path, 'r') as f:
            return f.read()
    except IOError as e:
        print(f"Error reading file {file_path}: {e}")
        sys.exit(1)


def write_nix_file(file_path: str, content: str) -> None:
    """Write content to a Nix file"""
    try:
        with open(file_path, 'w') as f:
            f.write(content)
    except IOError as e:
        print(f"Error writing to file {file_path}: {e}")
        sys.exit(1)


def update_nix_file(default_nix_path: str, latest_commit: str, new_version: str, new_hash: str) -> None:
    """Update the default.nix file with new version, revision and hash"""
    default_nix_content = read_nix_file(default_nix_path)

    # Update the revision in default.nix
    default_nix_content = re.sub(r'rev = "[^"]*"', f'rev = "{latest_commit}"', default_nix_content)

    # Update the version in default.nix
    if 'version = "' in default_nix_content:
        default_nix_content = re.sub(r'version = "[^"]*"', f'version = "{new_version}"', default_nix_content)
    else:
        # Add version attribute after pname if it doesn't exist
        default_nix_content = re.sub(r'(pname = "[^"]*";)', f'\\1\n  version = "{new_version}";', default_nix_content)

    # Update hash in default.nix
    if 'hash = "' in default_nix_content:
        default_nix_content = re.sub(r'hash = "[^"]*"', f'hash = "{new_hash}"', default_nix_content)
    elif 'fetchFromGitHub' in default_nix_content:
        default_nix_content = re.sub(r'sha256 = "[^"]*"', f'sha256 = "{new_hash}"', default_nix_content)
    else:
        print(f"Error: Could not find hash attribute in {default_nix_path}")
        sys.exit(1)

    # Write the updated content back to the file
    write_nix_file(default_nix_path, default_nix_content)

    # Verify the hash was updated
    updated_content = read_nix_file(default_nix_path)
    if f'hash = "{new_hash}"' in updated_content or f'sha256 = "{new_hash}"' in updated_content:
        print(f"Successfully updated hash to: {new_hash}")
    else:
        print(f"Error: Failed to update hash in {default_nix_path}")
        sys.exit(1)


def validate_environment() -> Tuple[str, str, str]:
    """Validate environment variables and paths"""
    nixpkgs_dir = os.getcwd()

    plugin_name = os.environ.get("PLUGIN_NAME")
    plugin_pname = os.environ.get("PLUGIN_PNAME")

    if not plugin_name or not plugin_pname:
        print("Error: PLUGIN_NAME and PLUGIN_PNAME environment variables must be set")
        sys.exit(1)

    plugin_dir = f"{nixpkgs_dir}/pkgs/by-name/ya/yazi/plugins/{plugin_name}"
    if not Path(f"{plugin_dir}/default.nix").exists():
        print(f"Error: Could not find default.nix for plugin {plugin_name} at {plugin_dir}")
        sys.exit(1)

    return nixpkgs_dir, plugin_name, plugin_pname


def main():
    """Main function to update a Yazi plugin"""
    # Basic setup and validation
    nixpkgs_dir, plugin_name, plugin_pname = validate_environment()
    plugin_dir = f"{nixpkgs_dir}/pkgs/by-name/ya/yazi/plugins/{plugin_name}"
    default_nix_path = f"{plugin_dir}/default.nix"

    # Get repository info
    plugin_info = get_plugin_info(nixpkgs_dir, plugin_name)
    owner = plugin_info["owner"]
    repo = plugin_info["repo"]

    # Get Yazi version separately
    yazi_version = get_yazi_version(nixpkgs_dir)

    # Setup GitHub API headers
    headers = get_github_headers()

    # Check plugin compatibility with current Yazi version
    plugin_content = fetch_plugin_content(owner, repo, plugin_pname, headers)
    required_version = check_version_compatibility(plugin_content, plugin_name, yazi_version)

    # Get latest commit info
    latest_commit, commit_date = get_latest_commit(owner, repo, plugin_pname, headers)
    print(f"Updating {plugin_name} to commit {latest_commit} ({commit_date})")

    # Generate new version string
    new_version = f"{required_version}-unstable-{commit_date}"

    # Calculate hash for the plugin
    new_hash = calculate_sri_hash(owner, repo, latest_commit)
    print(f"Generated SRI hash: {new_hash}")

    # Update the default.nix file
    update_nix_file(default_nix_path, latest_commit, new_version, new_hash)

    print(f"Successfully updated {plugin_name} to version {new_version} (commit {latest_commit})")


if __name__ == "__main__":
    main()
+0 −141
Original line number Diff line number Diff line
#!/usr/bin/env nix-shell
#!nix-shell -i bash -p bash curl coreutils jq git

set -e

# Basic setup and validation
NIXPKGS_DIR="$PWD"

if [ "${PLUGIN_NAME:-}" = "" ] || [ "${PLUGIN_PNAME:-}" = "" ]; then
    echo "Error: PLUGIN_NAME and PLUGIN_PNAME environment variables must be set"
    exit 1
fi

PLUGIN_DIR="$NIXPKGS_DIR/pkgs/by-name/ya/yazi/plugins/$PLUGIN_NAME"
if [ ! -f "$PLUGIN_DIR/default.nix" ]; then
    echo "Error: Could not find default.nix for plugin $PLUGIN_NAME at $PLUGIN_DIR"
    exit 1
fi

GITHUB_HEADER=(-H "Accept: application/vnd.github.v3+json")
if [ "${GITHUB_TOKEN:-}" != "" ]; then
    GITHUB_HEADER+=(-H "Authorization: token $GITHUB_TOKEN")
fi

# Get repository info and Yazi version
OWNER=$(nix eval --raw -f "$NIXPKGS_DIR" yaziPlugins."$PLUGIN_NAME".src.owner)
REPO=$(nix eval --raw -f "$NIXPKGS_DIR" yaziPlugins."$PLUGIN_NAME".src.repo)
YAZI_VERSION=$(nix eval --raw -f "$NIXPKGS_DIR" yazi-unwrapped.version)

# Check plugin compatibility with current Yazi version
PLUGIN_PATH="$([[ $OWNER == "yazi-rs" ]] && echo "$PLUGIN_PNAME/" || echo "")"
MAIN_LUA_URL="https://raw.githubusercontent.com/$OWNER/$REPO/main/${PLUGIN_PATH}main.lua"
PLUGIN_CONTENT=$(curl --silent "${GITHUB_HEADER[@]}" "$MAIN_LUA_URL")
REQUIRED_VERSION=$(echo "$PLUGIN_CONTENT" | head -n 1 | grep -o "since [0-9.]*" | sed 's/since \([0-9.]*\)/\1/')

# If no version requirement is found, default to "0"
if [ "$REQUIRED_VERSION" = "" ]; then
    echo "No version requirement found for $PLUGIN_NAME, assuming compatible with any Yazi version"
    REQUIRED_VERSION="0"
else
    # Check if the plugin is compatible with current Yazi version
    if ! printf "%s\n%s\n" "$REQUIRED_VERSION" "$YAZI_VERSION" | sort -C -V; then
        echo "$PLUGIN_NAME plugin requires Yazi $REQUIRED_VERSION, but we have $YAZI_VERSION"
        exit 0
    fi
fi

# Get latest commit info
if [ "$OWNER" == "yazi-rs" ]; then
    # For official plugins, get commit info for the specific plugin file
    API_URL="https://api.github.com/repos/$OWNER/$REPO/commits?path=$PLUGIN_PNAME/main.lua&per_page=1"
else
    # For third-party plugins, get latest commit on main branch
    API_URL="https://api.github.com/repos/$OWNER/$REPO/commits/main"
fi

if [ "$OWNER" == "yazi-rs" ]; then
    COMMIT_DATA=$(curl --silent "${GITHUB_HEADER[@]}" "$API_URL")
    LATEST_COMMIT=$(echo "$COMMIT_DATA" | jq -r '.[0].sha')
    COMMIT_DATE=$(echo "$COMMIT_DATA" | jq -r '.[0].commit.committer.date' | cut -d'T' -f1)
else
    COMMIT_DATA=$(curl --silent "${GITHUB_HEADER[@]}" "$API_URL")
    LATEST_COMMIT=$(echo "$COMMIT_DATA" | jq -r '.sha')
    COMMIT_DATE=$(echo "$COMMIT_DATA" | jq -r '.commit.committer.date' | cut -d'T' -f1)
fi

if [ "$LATEST_COMMIT" = "" ] || [ "$LATEST_COMMIT" == "null" ]; then
    echo "Error: Could not get latest commit hash for $PLUGIN_NAME"
    exit 1
fi

echo "Updating $PLUGIN_NAME to commit $LATEST_COMMIT ($COMMIT_DATE)"

# Update version and revision
NEW_VERSION="${REQUIRED_VERSION}-unstable-${COMMIT_DATE}"

# Update the revision in default.nix
sed -i "s/rev = \"[^\"]*\"/rev = \"$LATEST_COMMIT\"/" "$PLUGIN_DIR/default.nix"

# Update the version in default.nix
if grep -q 'version = "' "$PLUGIN_DIR/default.nix"; then
    sed -i "s/version = \"[^\"]*\"/version = \"$NEW_VERSION\"/" "$PLUGIN_DIR/default.nix"
else
    # Add version attribute after pname if it doesn't exist
    sed -i "/pname = \"[^\"]*\"/a \ \ version = \"$NEW_VERSION\";" "$PLUGIN_DIR/default.nix"
fi

# Update the hash
# Calculate hash directly for all plugins
PREFETCH_URL="https://github.com/$OWNER/$REPO/archive/$LATEST_COMMIT.tar.gz"

# Get the hash directly in SRI format
NEW_HASH=$(nix-prefetch-url --unpack --type sha256 "$PREFETCH_URL" 2>/dev/null)

# If the hash is not in SRI format, convert it
if [[ ! "$NEW_HASH" =~ ^sha256- ]]; then
    # Try to convert the hash to SRI format
    NEW_HASH=$(nix hash to-sri --type sha256 "$NEW_HASH" 2>/dev/null)

    # If that fails, try another approach
    if [[ ! "$NEW_HASH" =~ ^sha256- ]]; then
        echo "Warning: Failed to get SRI hash directly, trying alternative method..."
        RAW_HASH=$(nix-prefetch-url --type sha256 "$PREFETCH_URL" 2>/dev/null)
        NEW_HASH=$(nix hash to-sri --type sha256 "$RAW_HASH" 2>/dev/null)
    fi
fi

# Verify we got a valid SRI hash (starts with sha256-)
if [[ ! "$NEW_HASH" =~ ^sha256- ]]; then
    echo "Error: Failed to generate valid SRI hash. Output was:"
    echo "$NEW_HASH"
    exit 1
fi

echo "Generated SRI hash: $NEW_HASH"

# Update hash in default.nix
if grep -q 'hash = "' "$PLUGIN_DIR/default.nix"; then
    sed -i "s|hash = \"[^\"]*\"|hash = \"$NEW_HASH\"|" "$PLUGIN_DIR/default.nix"
else
    # For files that use fetchFromGitHub, update the hash there
    if grep -q 'fetchFromGitHub' "$PLUGIN_DIR/default.nix"; then
        sed -i "s|sha256 = \"[^\"]*\"|sha256 = \"$NEW_HASH\"|" "$PLUGIN_DIR/default.nix"
    else
        echo "Error: Could not find hash attribute in $PLUGIN_DIR/default.nix"
        exit 1
    fi
fi

# Verify the hash was updated
if grep -q "hash = \"$NEW_HASH\"" "$PLUGIN_DIR/default.nix" || grep -q "sha256 = \"$NEW_HASH\"" "$PLUGIN_DIR/default.nix"; then
    echo "Successfully updated hash to: $NEW_HASH"
else
    echo "Error: Failed to update hash in $PLUGIN_DIR/default.nix"
    exit 1
fi

# Update the revision in default.nix
sed -i "s/rev = \"[^\"]*\"/rev = \"$LATEST_COMMIT\"/" "$PLUGIN_DIR/default.nix"

echo "Successfully updated $PLUGIN_NAME to version $NEW_VERSION (commit $LATEST_COMMIT)"