forked from xiaozhi/xiaozhi-esp32
Update component version (#1382)
* Add download_github_runs.py * Update esp-ml307 version * Update esp-sr to 2.2.0
This commit is contained in:
@@ -20,11 +20,11 @@ dependencies:
|
||||
78/esp_lcd_nv3023: ~1.0.0
|
||||
78/esp-wifi-connect: ~2.6.1
|
||||
78/esp-opus-encoder: ~2.4.1
|
||||
78/esp-ml307: ~3.3.6
|
||||
78/esp-ml307: ~3.3.7
|
||||
78/xiaozhi-fonts: ~1.5.4
|
||||
espressif/led_strip: ~3.0.1
|
||||
espressif/esp_codec_dev: ~1.5
|
||||
espressif/esp-sr: ~2.1.5
|
||||
espressif/esp-sr: ~2.2.0
|
||||
espressif/button: ~4.1.3
|
||||
espressif/knob: ^1.0.0
|
||||
espressif/esp_video:
|
||||
|
||||
264
scripts/download_github_runs.py
Normal file
264
scripts/download_github_runs.py
Normal file
@@ -0,0 +1,264 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Download GitHub Actions artifacts and rename them with version numbers.
|
||||
|
||||
Usage:
|
||||
python download_github_runs.py 2.0.4 https://github.com/78/xiaozhi-esp32/actions/runs/18866246016
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
from urllib.parse import urlparse
|
||||
|
||||
import requests
|
||||
from dotenv import load_dotenv
|
||||
|
||||
|
||||
def parse_github_run_url(url: str) -> tuple[str, str, str]:
|
||||
"""
|
||||
Parse GitHub Actions run URL to extract owner, repo, and run_id.
|
||||
|
||||
Args:
|
||||
url: GitHub Actions run URL
|
||||
|
||||
Returns:
|
||||
Tuple of (owner, repo, run_id)
|
||||
"""
|
||||
# Example: https://github.com/78/xiaozhi-esp32/actions/runs/18866246016
|
||||
pattern = r'github\.com/([^/]+)/([^/]+)/actions/runs/(\d+)'
|
||||
match = re.search(pattern, url)
|
||||
|
||||
if not match:
|
||||
raise ValueError(f"Invalid GitHub Actions URL: {url}")
|
||||
|
||||
owner, repo, run_id = match.groups()
|
||||
return owner, repo, run_id
|
||||
|
||||
|
||||
def get_artifacts(owner: str, repo: str, run_id: str, token: str) -> list[dict]:
|
||||
"""
|
||||
Get all artifacts for a specific workflow run (with pagination support).
|
||||
|
||||
Args:
|
||||
owner: Repository owner
|
||||
repo: Repository name
|
||||
run_id: Workflow run ID
|
||||
token: GitHub personal access token
|
||||
|
||||
Returns:
|
||||
List of artifact dictionaries
|
||||
"""
|
||||
headers = {
|
||||
"Authorization": f"Bearer {token}",
|
||||
"Accept": "application/vnd.github+json",
|
||||
"X-GitHub-Api-Version": "2022-11-28"
|
||||
}
|
||||
|
||||
all_artifacts = []
|
||||
page = 1
|
||||
per_page = 100 # Maximum allowed by GitHub API
|
||||
|
||||
while True:
|
||||
url = f"https://api.github.com/repos/{owner}/{repo}/actions/runs/{run_id}/artifacts"
|
||||
params = {
|
||||
"page": page,
|
||||
"per_page": per_page
|
||||
}
|
||||
|
||||
response = requests.get(url, headers=headers, params=params)
|
||||
response.raise_for_status()
|
||||
|
||||
data = response.json()
|
||||
artifacts = data.get("artifacts", [])
|
||||
|
||||
if not artifacts:
|
||||
break
|
||||
|
||||
all_artifacts.extend(artifacts)
|
||||
|
||||
# Check if there are more pages
|
||||
total_count = data.get("total_count", 0)
|
||||
if len(all_artifacts) >= total_count:
|
||||
break
|
||||
|
||||
page += 1
|
||||
|
||||
return all_artifacts
|
||||
|
||||
|
||||
def download_artifact(artifact_url: str, token: str, output_path: Path) -> None:
|
||||
"""
|
||||
Download an artifact from GitHub.
|
||||
|
||||
Args:
|
||||
artifact_url: Artifact download URL
|
||||
token: GitHub personal access token
|
||||
output_path: Path to save the downloaded artifact
|
||||
"""
|
||||
headers = {
|
||||
"Authorization": f"Bearer {token}",
|
||||
"Accept": "application/vnd.github+json",
|
||||
"X-GitHub-Api-Version": "2022-11-28"
|
||||
}
|
||||
|
||||
response = requests.get(artifact_url, headers=headers, stream=True)
|
||||
response.raise_for_status()
|
||||
|
||||
# Create parent directory if it doesn't exist
|
||||
output_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Download the file
|
||||
with open(output_path, 'wb') as f:
|
||||
for chunk in response.iter_content(chunk_size=8192):
|
||||
if chunk:
|
||||
f.write(chunk)
|
||||
|
||||
|
||||
def rename_artifact(original_name: str, version: str) -> str:
|
||||
"""
|
||||
Rename artifact according to the specified rules.
|
||||
|
||||
Rules:
|
||||
- Remove "xiaozhi_" prefix
|
||||
- Remove hash suffix (underscore followed by hex string)
|
||||
- Add version prefix (e.g., "v2.0.4_")
|
||||
- Change extension to .zip
|
||||
|
||||
Example:
|
||||
xiaozhi_atk-dnesp32s3-box0_43ef2f4e7f0957dc62ec7d628ac2819d226127b8.bin
|
||||
-> v2.0.4_atk-dnesp32s3-box0.zip
|
||||
|
||||
Args:
|
||||
original_name: Original artifact name
|
||||
version: Version string (e.g., "2.0.4")
|
||||
|
||||
Returns:
|
||||
New filename
|
||||
"""
|
||||
# Remove "xiaozhi_" prefix
|
||||
name = original_name
|
||||
if name.startswith("xiaozhi_"):
|
||||
name = name[len("xiaozhi_"):]
|
||||
|
||||
# Remove extension
|
||||
name_without_ext = os.path.splitext(name)[0]
|
||||
|
||||
# Remove hash suffix (pattern: underscore followed by 40+ hex characters)
|
||||
# This matches Git commit hashes and similar identifiers
|
||||
name_without_hash = re.sub(r'_[a-f0-9]{40,}$', '', name_without_ext)
|
||||
|
||||
# Add version prefix and .zip extension
|
||||
new_name = f"v{version}_{name_without_hash}.zip"
|
||||
|
||||
return new_name
|
||||
|
||||
|
||||
def main():
|
||||
"""Main function to download and rename GitHub Actions artifacts."""
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Download GitHub Actions artifacts and rename them with version numbers."
|
||||
)
|
||||
parser.add_argument(
|
||||
"version",
|
||||
help="Version number (e.g., 2.0.4)"
|
||||
)
|
||||
parser.add_argument(
|
||||
"url",
|
||||
help="GitHub Actions run URL (e.g., https://github.com/owner/repo/actions/runs/12345)"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--output-dir",
|
||||
default="../releases",
|
||||
help="Output directory for downloaded artifacts (default: ../releases)"
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Load GitHub token from .env file
|
||||
load_dotenv()
|
||||
github_token = os.getenv("GITHUB_TOKEN")
|
||||
|
||||
if not github_token:
|
||||
print("Error: GITHUB_TOKEN not found in environment variables.", file=sys.stderr)
|
||||
print("Please create a .env file with GITHUB_TOKEN=your_token_here", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
try:
|
||||
# Parse the GitHub URL
|
||||
owner, repo, run_id = parse_github_run_url(args.url)
|
||||
print(f"Repository: {owner}/{repo}")
|
||||
print(f"Run ID: {run_id}")
|
||||
print(f"Version: {args.version}")
|
||||
print()
|
||||
|
||||
# Get artifacts
|
||||
print("Fetching artifacts...")
|
||||
artifacts = get_artifacts(owner, repo, run_id, github_token)
|
||||
|
||||
if not artifacts:
|
||||
print("No artifacts found for this run.")
|
||||
return
|
||||
|
||||
print(f"Found {len(artifacts)} artifact(s):")
|
||||
for artifact in artifacts:
|
||||
print(f" - {artifact['name']}")
|
||||
print()
|
||||
|
||||
# Create output directory
|
||||
output_dir = Path(args.output_dir)
|
||||
output_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Download and rename each artifact
|
||||
downloaded_count = 0
|
||||
skipped_count = 0
|
||||
|
||||
for artifact in artifacts:
|
||||
original_name = artifact['name']
|
||||
new_name = rename_artifact(original_name, args.version)
|
||||
final_path = output_dir / new_name
|
||||
|
||||
# Check if file already exists
|
||||
if final_path.exists():
|
||||
print(f"Skipping (already exists): {original_name}")
|
||||
print(f" -> {new_name}")
|
||||
print(f" File: {final_path}")
|
||||
print()
|
||||
skipped_count += 1
|
||||
continue
|
||||
|
||||
print(f"Downloading: {original_name}")
|
||||
print(f" -> {new_name}")
|
||||
|
||||
# Download to temporary path first
|
||||
temp_path = output_dir / f"{original_name}.zip"
|
||||
download_artifact(
|
||||
artifact['archive_download_url'],
|
||||
github_token,
|
||||
temp_path
|
||||
)
|
||||
|
||||
# Rename to final name
|
||||
temp_path.rename(final_path)
|
||||
|
||||
print(f" Saved to: {final_path}")
|
||||
print()
|
||||
downloaded_count += 1
|
||||
|
||||
print(f"Summary:")
|
||||
print(f" Downloaded: {downloaded_count} artifact(s)")
|
||||
print(f" Skipped: {skipped_count} artifact(s)")
|
||||
print(f" Total: {len(artifacts)} artifact(s)")
|
||||
print(f" Output directory: {output_dir.absolute()}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error: {e}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
Reference in New Issue
Block a user