mirror of
https://github.com/hykilpikonna/hyfetch.git
synced 2026-08-27 03:07:26 -05:00
[F] Fix release recovery workflow
This commit is contained in:
48
.github/workflows/release.yml
vendored
48
.github/workflows/release.yml
vendored
@@ -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
|
||||
|
||||
11
package.json
11
package.json
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user