[F] Fix release recovery workflow

This commit is contained in:
Azalea
2026-06-03 07:48:53 +00:00
parent 0fd343e6d5
commit 7c57f66678
6 changed files with 162 additions and 23 deletions

View File

@@ -11,6 +11,11 @@ on:
description: "Existing release tag to publish, such as 2.1.1 or v2.1.1"
required: true
type: string
source_ref:
description: "Ref to build/publish from. Leave empty to use the release tag; set to master/a branch for recovery fixes."
required: false
default: ""
type: string
create_github_release:
description: "Create or complete the GitHub Release"
required: true
@@ -40,26 +45,33 @@ jobs:
runs-on: ubuntu-22.04
outputs:
tag: ${{ steps.metadata.outputs.tag }}
source_ref: ${{ steps.release-refs.outputs.source_ref }}
version: ${{ steps.metadata.outputs.version }}
python_package: ${{ steps.metadata.outputs.python_package }}
npm_package: ${{ steps.metadata.outputs.npm_package }}
steps:
- name: Resolve release tag
id: release-tag
- name: Resolve release refs
id: release-refs
run: |
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
echo "tag=${{ inputs.tag }}" >> "$GITHUB_OUTPUT"
if [[ -n "${{ inputs.source_ref }}" ]]; then
echo "source_ref=${{ inputs.source_ref }}" >> "$GITHUB_OUTPUT"
else
echo "source_ref=${{ inputs.tag }}" >> "$GITHUB_OUTPUT"
fi
else
echo "tag=${{ github.ref_name }}" >> "$GITHUB_OUTPUT"
echo "source_ref=${{ github.ref_name }}" >> "$GITHUB_OUTPUT"
fi
- uses: actions/checkout@v4
with:
ref: ${{ steps.release-tag.outputs.tag }}
ref: ${{ steps.release-refs.outputs.source_ref }}
- name: Validate checked-in versions
id: metadata
run: python3 tools/release.py metadata --tag "${{ steps.release-tag.outputs.tag }}"
run: python3 tools/release.py metadata --tag "${{ steps.release-refs.outputs.tag }}"
release-state:
name: Check completed release stages
@@ -73,7 +85,7 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
ref: ${{ needs.metadata.outputs.tag }}
ref: ${{ needs.metadata.outputs.source_ref }}
- name: Query registries and GitHub Release
id: state
@@ -179,7 +191,7 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
ref: ${{ needs.metadata.outputs.tag }}
ref: ${{ needs.metadata.outputs.source_ref }}
- uses: actions/setup-python@v5
with:
@@ -221,29 +233,44 @@ jobs:
&& needs.release-state.outputs.github_release_complete != 'true'
}}
steps:
- uses: actions/checkout@v4
with:
ref: ${{ needs.metadata.outputs.source_ref }}
- uses: actions/download-artifact@v4
with:
name: python-dist-${{ needs.metadata.outputs.version }}
path: dist
- name: Create release if needed
- name: Write release notes
run: |
python3 tools/release.py changelog \
--version "${{ needs.metadata.outputs.version }}" \
--output RELEASE_NOTES.md
- name: Create or update release metadata
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
TAG: ${{ needs.metadata.outputs.tag }}
VERSION: ${{ needs.metadata.outputs.version }}
run: |
if gh release view "$TAG" >/dev/null 2>&1; then
echo "GitHub Release $TAG already exists."
gh release edit "$TAG" \
--title "$VERSION" \
--notes-file RELEASE_NOTES.md
else
gh release create "$TAG" \
--verify-tag \
--title "HyFetch $VERSION" \
--generate-notes
--title "$VERSION" \
--notes-file RELEASE_NOTES.md
fi
- name: Upload missing release assets
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
TAG: ${{ needs.metadata.outputs.tag }}
run: |
mapfile -t existing_assets < <(gh release view "$TAG" --json assets --jq '.assets[].name')
@@ -321,12 +348,11 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
ref: ${{ needs.metadata.outputs.tag }}
ref: ${{ needs.metadata.outputs.source_ref }}
- uses: actions/setup-node@v4
with:
node-version: "24"
registry-url: "https://registry.npmjs.org"
- name: Use npm with trusted publishing support
run: npm install -g npm@11

View File

@@ -4,11 +4,14 @@
"description": "Updated neofetch",
"repository": {
"type": "git",
"url": "git+https://github.com/hykilpikonna/neofetch.git"
"url": "https://github.com/hykilpikonna/hyfetch"
},
"bin": {
"neowofetch": "neofetch"
},
"files": [
"neofetch"
],
"keywords": [
"neofetch",
"screenfetch"
@@ -19,7 +22,7 @@
],
"license": "MIT",
"bugs": {
"url": "https://github.com/hykilpikonna/neofetch/issues"
"url": "https://github.com/hykilpikonna/hyfetch/issues"
},
"homepage": "https://github.com/hykilpikonna/neofetch#readme"
}
"homepage": "https://github.com/hykilpikonna/hyfetch#readme"
}

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env bash
FASTFETCH_VERSION="2.61.0"
FASTFETCH_VERSION="2.63.1"
FASTFETCH_DL="https://github.com/fastfetch-cli/fastfetch/releases/download/$FASTFETCH_VERSION/"
# Get script directory
@@ -85,7 +85,7 @@ function build_for_platform() {
rust_platform=$3
echo "Building for $ff_platform"
# Download the fastfetch binary
wget "$FASTFETCH_DL/fastfetch-$ff_platform.zip" -O "fastfetch-$ff_platform.zip"

View File

@@ -6,6 +6,9 @@ import re
import shlex
import stat
import subprocess
import urllib.error
import urllib.parse
import urllib.request
from pathlib import Path
from packaging import version as pv
@@ -14,6 +17,16 @@ from tools.list_distros import generate_help
from tools.reformat_readme import reformat_readme
NEOFETCH_NEW_VERSION = ""
FASTFETCH_RELEASE_API = 'https://api.github.com/repos/fastfetch-cli/fastfetch/releases'
FASTFETCH_ASSETS = [
'fastfetch-windows-amd64.zip',
'fastfetch-linux-amd64.zip',
'fastfetch-linux-aarch64.zip',
'fastfetch-linux-armv7l.zip',
'fastfetch-musl-amd64.zip',
'fastfetch-macos-amd64.zip',
'fastfetch-macos-aarch64.zip',
]
RELEASE_FILES = [
'Cargo.lock',
'Cargo.toml',
@@ -23,6 +36,7 @@ RELEASE_FILES = [
'hyfetch/__version__.py',
'neofetch',
'package.json',
'tools/build_pkg.sh',
]
@@ -97,6 +111,49 @@ def edit_versions(version: str):
NEOFETCH_NEW_VERSION = nf
def fetch_fastfetch_release(release: str) -> dict:
"""
Fetch fastfetch release metadata from GitHub.
"""
if release == 'latest':
url = f'{FASTFETCH_RELEASE_API}/latest'
else:
tag = urllib.parse.quote(release, safe='')
url = f'{FASTFETCH_RELEASE_API}/tags/{tag}'
request = urllib.request.Request(url, headers={
'Accept': 'application/vnd.github+json',
'User-Agent': 'hyfetch-release-script',
})
try:
with urllib.request.urlopen(request, timeout=30) as response:
return json.loads(response.read().decode())
except urllib.error.HTTPError as e:
if e.code == 404 and release.startswith('v'):
return fetch_fastfetch_release(release[1:])
raise
def update_fastfetch_version(release: str) -> str:
"""
Update the fastfetch binary version embedded in Python wheels.
"""
print(f'Checking fastfetch {release}...')
metadata = fetch_fastfetch_release(release)
tag = metadata['tag_name']
assets = {asset['name'] for asset in metadata['assets']}
missing = sorted(set(FASTFETCH_ASSETS) - assets)
assert not missing, f'Fastfetch {tag} is missing required release assets: {", ".join(missing)}'
print(f'Editing tools/build_pkg.sh fastfetch version to {tag}...')
path = Path('tools/build_pkg.sh')
content = path.read_text()
content = re.sub(r'(?<=^FASTFETCH_VERSION=")[^"]+(?="$)', tag, content, flags=re.MULTILINE)
path.write_text(content)
return tag
def finalize_neofetch():
"""
Finalize current version
@@ -181,6 +238,16 @@ def deploy():
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='HyFetch Release Utility')
parser.add_argument('version', help='Version to release')
parser.add_argument(
'--fastfetch-version',
default='latest',
help='Fastfetch release tag to embed in Python wheels, or "latest" (default).',
)
parser.add_argument(
'--skip-fastfetch-update',
action='store_true',
help='Keep the existing FASTFETCH_VERSION in tools/build_pkg.sh.',
)
parser.add_argument(
'--local-deploy',
action='store_true',
@@ -191,6 +258,8 @@ if __name__ == '__main__':
pre_check()
edit_versions(args.version)
if not args.skip_fastfetch_update:
update_fastfetch_version(args.fastfetch_version)
finalize_neofetch()
post_check()

View File

@@ -17,11 +17,18 @@ their trusted publishing flows.
* [ ] Run `python -m tools.deploy-release {version}`
* [ ] Watch the `Release` workflow
The local script prepares the release commit and pushes the version tag. GitHub
Actions creates/completes the GitHub Release, uploads Python artifacts, publishes
to PyPI, and publishes to npm.
The local script prepares the release commit and pushes the version tag. By
default it also updates the built-in fastfetch binaries to the latest upstream
release used by Python wheels. Use `--fastfetch-version {version}` to pin a
specific upstream fastfetch release, or `--skip-fastfetch-update` to keep the
current pin.
GitHub Actions creates/completes the GitHub Release, uploads Python artifacts,
publishes to PyPI, and publishes to npm.
If the workflow fails after one stage already published, fix the problem and
rerun it. If the fix changes the workflow itself, run the workflow manually with
the same tag. The workflow checks PyPI, npm, and the GitHub Release first, so it
will skip completed stages instead of publishing them again.
rerun it manually with the same release tag. If the fix changes files needed by
the failed stage, set `source_ref` to the branch or commit containing the fix,
and turn off any stages that already completed. The workflow checks PyPI, npm,
and the GitHub Release first, so completed stages are skipped instead of being
published again.

View File

@@ -84,6 +84,25 @@ def read_pyproject_name() -> str:
return match.group(1)
def read_changelog(version: str) -> str:
content = (ROOT / "README.md").read_text(encoding="utf-8")
heading = f"### {version}"
match = re.search(
rf"^{re.escape(heading)}\s*\n(?P<body>.*?)(?=^### \S|\Z)",
content,
flags=re.MULTILINE | re.DOTALL,
)
if not match:
raise RuntimeError(f"Could not find changelog section {heading!r} in README.md")
body = match.group("body").strip()
if not body:
raise RuntimeError(f"Changelog section {heading!r} is empty")
return body + "\n"
def normalize_tag(tag: str) -> Tuple[str, str]:
match = VERSION_RE.match(tag)
if not match:
@@ -199,6 +218,16 @@ def command_state(args: argparse.Namespace) -> None:
write_output("github_release_complete", github_release_complete)
def command_changelog(args: argparse.Namespace) -> None:
_, version = normalize_tag(args.version)
changelog = read_changelog(version)
if args.output:
Path(args.output).write_text(changelog, encoding="utf-8")
else:
print(changelog, end="")
def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(description="Release workflow helpers")
subparsers = parser.add_subparsers(dest="command", required=True)
@@ -215,6 +244,11 @@ def build_parser() -> argparse.ArgumentParser:
state.add_argument("--repo", required=True)
state.set_defaults(func=command_state)
changelog = subparsers.add_parser("changelog", help="Extract release notes from README.md")
changelog.add_argument("--version", required=True)
changelog.add_argument("--output")
changelog.set_defaults(func=command_changelog)
return parser