[+] Release workflow #1
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Release | |
| on: | |
| push: | |
| tags: | |
| - "[0-9]*.[0-9]*.[0-9]*" | |
| - "v[0-9]*.[0-9]*.[0-9]*" | |
| workflow_dispatch: | |
| inputs: | |
| tag: | |
| description: "Existing release tag to publish, such as 2.1.1 or v2.1.1" | |
| required: true | |
| type: string | |
| create_github_release: | |
| description: "Create or complete the GitHub Release" | |
| required: true | |
| default: true | |
| type: boolean | |
| publish_pypi: | |
| description: "Publish the Python distributions to PyPI" | |
| required: true | |
| default: true | |
| type: boolean | |
| publish_npm: | |
| description: "Publish package.json to npm" | |
| required: true | |
| default: true | |
| type: boolean | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: release-${{ github.event_name == 'workflow_dispatch' && inputs.tag || github.ref_name }} | |
| cancel-in-progress: false | |
| jobs: | |
| metadata: | |
| name: Validate release metadata | |
| runs-on: ubuntu-22.04 | |
| outputs: | |
| tag: ${{ steps.metadata.outputs.tag }} | |
| 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 | |
| run: | | |
| if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then | |
| echo "tag=${{ inputs.tag }}" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "tag=${{ github.ref_name }}" >> "$GITHUB_OUTPUT" | |
| fi | |
| - uses: actions/checkout@v4 | |
| with: | |
| ref: ${{ steps.release-tag.outputs.tag }} | |
| - name: Validate checked-in versions | |
| id: metadata | |
| run: python3 tools/release.py metadata --tag "${{ steps.release-tag.outputs.tag }}" | |
| release-state: | |
| name: Check completed release stages | |
| runs-on: ubuntu-22.04 | |
| needs: metadata | |
| outputs: | |
| pypi_exists: ${{ steps.state.outputs.pypi_exists }} | |
| npm_exists: ${{ steps.state.outputs.npm_exists }} | |
| github_release_exists: ${{ steps.state.outputs.github_release_exists }} | |
| github_release_complete: ${{ steps.state.outputs.github_release_complete }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| ref: ${{ needs.metadata.outputs.tag }} | |
| - name: Query registries and GitHub Release | |
| id: state | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| python3 tools/release.py state \ | |
| --tag "${{ needs.metadata.outputs.tag }}" \ | |
| --version "${{ needs.metadata.outputs.version }}" \ | |
| --python-package "${{ needs.metadata.outputs.python_package }}" \ | |
| --npm-package "${{ needs.metadata.outputs.npm_package }}" \ | |
| --repo "${{ github.repository }}" | |
| - name: Summarize release state | |
| run: | | |
| { | |
| echo "## Release state" | |
| echo | |
| echo "| Stage | Already complete |" | |
| echo "| --- | --- |" | |
| echo "| PyPI | ${{ steps.state.outputs.pypi_exists }} |" | |
| echo "| npm | ${{ steps.state.outputs.npm_exists }} |" | |
| echo "| GitHub Release assets | ${{ steps.state.outputs.github_release_complete }} |" | |
| } >> "$GITHUB_STEP_SUMMARY" | |
| preflight: | |
| name: Preflight release stages | |
| runs-on: ubuntu-22.04 | |
| needs: | |
| - metadata | |
| - release-state | |
| steps: | |
| - name: Verify pending publish stages | |
| env: | |
| CREATE_GITHUB_RELEASE: ${{ github.event_name != 'workflow_dispatch' || inputs.create_github_release }} | |
| PUBLISH_PYPI: ${{ github.event_name != 'workflow_dispatch' || inputs.publish_pypi }} | |
| PUBLISH_NPM: ${{ github.event_name != 'workflow_dispatch' || inputs.publish_npm }} | |
| run: | | |
| pending=() | |
| skipped=() | |
| if [[ "$CREATE_GITHUB_RELEASE" == "true" ]]; then | |
| if [[ "${{ needs.release-state.outputs.github_release_complete }}" == "true" ]]; then | |
| skipped+=("GitHub Release") | |
| else | |
| pending+=("GitHub Release") | |
| fi | |
| fi | |
| if [[ "$PUBLISH_PYPI" == "true" ]]; then | |
| if [[ "${{ needs.release-state.outputs.pypi_exists }}" == "true" ]]; then | |
| skipped+=("PyPI") | |
| else | |
| pending+=("PyPI") | |
| fi | |
| fi | |
| if [[ "$PUBLISH_NPM" == "true" ]]; then | |
| if [[ "${{ needs.release-state.outputs.npm_exists }}" == "true" ]]; then | |
| skipped+=("npm") | |
| else | |
| pending+=("npm") | |
| fi | |
| fi | |
| { | |
| echo "## Preflight" | |
| echo | |
| if ((${#pending[@]})); then | |
| printf 'Pending stages: %s\n\n' "$(IFS=', '; echo "${pending[*]}")" | |
| else | |
| echo "No pending stages." | |
| echo | |
| fi | |
| if ((${#skipped[@]})); then | |
| printf 'Already complete: %s\n\n' "$(IFS=', '; echo "${skipped[*]}")" | |
| fi | |
| echo "PyPI and npm publishing use trusted publishing/OIDC; no registry secrets are required." | |
| } >> "$GITHUB_STEP_SUMMARY" | |
| build-python-dist: | |
| name: Build Python distributions | |
| runs-on: ubuntu-22.04 | |
| needs: | |
| - metadata | |
| - release-state | |
| - preflight | |
| if: >- | |
| ${{ | |
| always() | |
| && needs.preflight.result == 'success' | |
| && ( | |
| ( | |
| (github.event_name != 'workflow_dispatch' || inputs.publish_pypi) | |
| && needs.release-state.outputs.pypi_exists != 'true' | |
| ) | |
| || ( | |
| (github.event_name != 'workflow_dispatch' || inputs.create_github_release) | |
| && needs.release-state.outputs.github_release_complete != 'true' | |
| ) | |
| ) | |
| }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| ref: ${{ needs.metadata.outputs.tag }} | |
| - uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.13" | |
| - name: Install build dependencies | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y help2man libarchive-tools shellcheck unzip wget zip | |
| python -m pip install --upgrade pip | |
| python -m pip install build twine | |
| - name: Build and check distributions | |
| run: tools/build_pkg.sh | |
| - name: Upload distribution artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: python-dist-${{ needs.metadata.outputs.version }} | |
| path: dist/* | |
| if-no-files-found: error | |
| github-release: | |
| name: Create GitHub Release | |
| runs-on: ubuntu-22.04 | |
| needs: | |
| - metadata | |
| - release-state | |
| - preflight | |
| - build-python-dist | |
| permissions: | |
| contents: write | |
| if: >- | |
| ${{ | |
| always() | |
| && needs.preflight.result == 'success' | |
| && needs.build-python-dist.result == 'success' | |
| && (github.event_name != 'workflow_dispatch' || inputs.create_github_release) | |
| && needs.release-state.outputs.github_release_complete != 'true' | |
| }} | |
| steps: | |
| - uses: actions/download-artifact@v4 | |
| with: | |
| name: python-dist-${{ needs.metadata.outputs.version }} | |
| path: dist | |
| - name: Create release if needed | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| 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." | |
| else | |
| gh release create "$TAG" \ | |
| --verify-tag \ | |
| --title "HyFetch $VERSION" \ | |
| --generate-notes | |
| fi | |
| - name: Upload missing release assets | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| TAG: ${{ needs.metadata.outputs.tag }} | |
| run: | | |
| mapfile -t existing_assets < <(gh release view "$TAG" --json assets --jq '.assets[].name') | |
| missing_assets=() | |
| for file in dist/*; do | |
| name="$(basename "$file")" | |
| found=false | |
| for existing in "${existing_assets[@]}"; do | |
| if [[ "$existing" == "$name" ]]; then | |
| found=true | |
| break | |
| fi | |
| done | |
| if [[ "$found" == "false" ]]; then | |
| missing_assets+=("$file") | |
| fi | |
| done | |
| if ((${#missing_assets[@]} == 0)); then | |
| echo "All release assets are already uploaded." | |
| else | |
| gh release upload "$TAG" "${missing_assets[@]}" | |
| fi | |
| publish-pypi: | |
| name: Publish to PyPI | |
| runs-on: ubuntu-22.04 | |
| permissions: | |
| contents: read | |
| id-token: write | |
| needs: | |
| - metadata | |
| - release-state | |
| - preflight | |
| - build-python-dist | |
| if: >- | |
| ${{ | |
| always() | |
| && needs.preflight.result == 'success' | |
| && needs.build-python-dist.result == 'success' | |
| && (github.event_name != 'workflow_dispatch' || inputs.publish_pypi) | |
| && needs.release-state.outputs.pypi_exists != 'true' | |
| }} | |
| steps: | |
| - uses: actions/download-artifact@v4 | |
| with: | |
| name: python-dist-${{ needs.metadata.outputs.version }} | |
| path: dist | |
| - name: Publish distributions | |
| uses: pypa/gh-action-pypi-publish@release/v1 | |
| with: | |
| packages-dir: dist | |
| skip-existing: true | |
| publish-npm: | |
| name: Publish to npm | |
| runs-on: ubuntu-22.04 | |
| permissions: | |
| contents: read | |
| id-token: write | |
| needs: | |
| - metadata | |
| - release-state | |
| - preflight | |
| if: >- | |
| ${{ | |
| always() | |
| && needs.preflight.result == 'success' | |
| && (github.event_name != 'workflow_dispatch' || inputs.publish_npm) | |
| && needs.release-state.outputs.npm_exists != 'true' | |
| }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| ref: ${{ needs.metadata.outputs.tag }} | |
| - 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 | |
| - name: Confirm npm version is still unpublished | |
| id: npm-state | |
| env: | |
| NPM_PACKAGE: ${{ needs.metadata.outputs.npm_package }} | |
| VERSION: ${{ needs.metadata.outputs.version }} | |
| run: | | |
| if npm view "$NPM_PACKAGE@$VERSION" version --registry https://registry.npmjs.org >/dev/null 2>&1; then | |
| echo "$NPM_PACKAGE@$VERSION is already published; skipping." | |
| echo "exists=true" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| echo "exists=false" >> "$GITHUB_OUTPUT" | |
| - name: Publish package | |
| if: steps.npm-state.outputs.exists != 'true' | |
| run: npm publish --provenance |