mirror of
https://github.com/darkzoul5/YoutubePlaylistSync.git
synced 2026-07-03 04:23:59 +03:00
refactor(build): fully rewrite build workflow + release; remove unused workflows
This commit is contained in:
@@ -0,0 +1,247 @@
|
||||
name: Build & Release
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: "Tag to create (e.g., v0.1.0)"
|
||||
required: true
|
||||
default: "v0.1.0"
|
||||
type: string
|
||||
force_tag:
|
||||
description: "Recreate tag if it already exists"
|
||||
required: false
|
||||
default: false
|
||||
type: boolean
|
||||
draft:
|
||||
description: "Create release as draft"
|
||||
required: false
|
||||
default: true
|
||||
type: boolean
|
||||
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
tag:
|
||||
name: Create tag
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Validate input tag
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
TAG="${{ inputs.tag }}"
|
||||
if [[ -z "$TAG" ]]; then
|
||||
echo "tag is required" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ "$TAG" != v* ]]; then
|
||||
echo "tag must start with 'v' (got: $TAG)" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Create/push tag
|
||||
shell: bash
|
||||
env:
|
||||
TAG: ${{ inputs.tag }}
|
||||
FORCE: ${{ inputs.force_tag }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
if git rev-parse -q --verify "refs/tags/${TAG}" >/dev/null; then
|
||||
if [[ "${FORCE}" != "true" ]]; then
|
||||
echo "Tag ${TAG} already exists. Re-run with force_tag=true to recreate." >&2
|
||||
exit 1
|
||||
fi
|
||||
git tag -d "${TAG}" || true
|
||||
git push origin ":refs/tags/${TAG}" || true
|
||||
fi
|
||||
|
||||
git tag "${TAG}" "${GITHUB_SHA}"
|
||||
git push origin "${TAG}"
|
||||
|
||||
test:
|
||||
name: Unit tests
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: "3.12"
|
||||
cache: "pip"
|
||||
- name: Install
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -e ".[dev]" || pip install -e .
|
||||
pip install pytest
|
||||
- name: Run tests
|
||||
env:
|
||||
PYTHONPATH: ${{ github.workspace }}/src
|
||||
run: pytest
|
||||
|
||||
build:
|
||||
name: Build packages
|
||||
needs: [tag, test]
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [windows-latest, ubuntu-latest]
|
||||
ffmpeg: [bundled, system]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Derive version
|
||||
id: version
|
||||
shell: bash
|
||||
run: |
|
||||
TAG="${{ inputs.tag }}"
|
||||
echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
|
||||
echo "version=${TAG#v}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: "3.12"
|
||||
cache: "pip"
|
||||
|
||||
- name: Install build deps
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -e .
|
||||
pip install pyinstaller
|
||||
|
||||
- name: Build binary (Windows)
|
||||
if: runner.os == 'Windows'
|
||||
shell: pwsh
|
||||
run: |
|
||||
$ErrorActionPreference = "Stop"
|
||||
pyinstaller --noconfirm --onefile --name "ytpl-sync" --distpath "dist/pyinstaller" --workpath "build/pyinstaller" --specpath "build/pyinstaller" --paths "src" "ytpl-sync-entry.py"
|
||||
|
||||
- name: Build binary (Linux)
|
||||
if: runner.os == 'Linux'
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
pyinstaller --noconfirm --onefile --name "ytpl-sync" --distpath "dist/pyinstaller" --workpath "build/pyinstaller" --specpath "build/pyinstaller" --paths "src" "ytpl-sync-entry.py"
|
||||
|
||||
- name: Stage package
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
OS="${{ runner.os }}"
|
||||
FFMPEG="${{ matrix.ffmpeg }}"
|
||||
VERSION="${{ steps.version.outputs.version }}"
|
||||
|
||||
PKG_ROOT="$GITHUB_WORKSPACE/package"
|
||||
rm -rf "$PKG_ROOT"
|
||||
mkdir -p "$PKG_ROOT/config" "$PKG_ROOT/bin"
|
||||
|
||||
# Binary
|
||||
if [[ "$OS" == "Windows" ]]; then
|
||||
cp "$GITHUB_WORKSPACE/dist/pyinstaller/ytpl-sync.exe" "$PKG_ROOT/ytpl-sync.exe"
|
||||
else
|
||||
cp "$GITHUB_WORKSPACE/dist/pyinstaller/ytpl-sync" "$PKG_ROOT/ytpl-sync.exe"
|
||||
chmod +x "$PKG_ROOT/ytpl-sync.exe"
|
||||
fi
|
||||
|
||||
# Config: ship example as the default config
|
||||
python - <<'PY'
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
workspace = Path(os.environ["GITHUB_WORKSPACE"])
|
||||
pkg_root = workspace / "package"
|
||||
src = workspace / "config" / "yt-playlist-config.example.json"
|
||||
dst = pkg_root / "config" / "yt-playlist-config.json"
|
||||
data = json.loads(src.read_text(encoding="utf-8"))
|
||||
|
||||
os_name = os.environ["RUNNER_OS"]
|
||||
ffmpeg_mode = os.environ["FFMPEG_MODE"]
|
||||
if ffmpeg_mode == "bundled":
|
||||
data["ffmpeg_path"] = "./bin/ffmpeg.exe" if os_name == "Windows" else "./bin/ffmpeg"
|
||||
else:
|
||||
data["ffmpeg_path"] = "ffmpeg"
|
||||
|
||||
dst.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8")
|
||||
PY
|
||||
env:
|
||||
FFMPEG_MODE: ${{ matrix.ffmpeg }}
|
||||
|
||||
- name: Bundle FFmpeg (Windows)
|
||||
if: runner.os == 'Windows' && matrix.ffmpeg == 'bundled'
|
||||
shell: pwsh
|
||||
run: |
|
||||
$ErrorActionPreference = "Stop"
|
||||
New-Item -ItemType Directory -Force -Path "package/bin" | Out-Null
|
||||
Invoke-WebRequest -Uri "https://www.gyan.dev/ffmpeg/builds/ffmpeg-release-essentials.zip" -OutFile "ffmpeg.zip"
|
||||
Expand-Archive "ffmpeg.zip" -DestinationPath "ffmpeg_tmp"
|
||||
$ffmpegExe = Get-ChildItem -Path "ffmpeg_tmp" -Filter "ffmpeg.exe" -Recurse | Select-Object -First 1
|
||||
if (-not $ffmpegExe) { throw "ffmpeg.exe not found in archive" }
|
||||
Copy-Item $ffmpegExe.FullName "package/bin/ffmpeg.exe"
|
||||
|
||||
- name: Bundle FFmpeg (Linux)
|
||||
if: runner.os == 'Linux' && matrix.ffmpeg == 'bundled'
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
mkdir -p package/bin ffmpeg_tmp
|
||||
curl -L "https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-amd64-static.tar.xz" -o ffmpeg.tar.xz
|
||||
tar -xf ffmpeg.tar.xz -C ffmpeg_tmp --strip-components=1
|
||||
mv ffmpeg_tmp/ffmpeg package/bin/ffmpeg
|
||||
chmod +x package/bin/ffmpeg
|
||||
|
||||
- name: Archive (Windows)
|
||||
if: runner.os == 'Windows'
|
||||
shell: pwsh
|
||||
run: |
|
||||
$ErrorActionPreference = "Stop"
|
||||
$version = "${{ steps.version.outputs.version }}"
|
||||
$variant = "${{ matrix.ffmpeg }}"
|
||||
$name = "ytpl-sync-windows-$version-$variant.zip"
|
||||
Compress-Archive -Path "package/*" -DestinationPath $name
|
||||
|
||||
- name: Archive (Linux)
|
||||
if: runner.os == 'Linux'
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
version="${{ steps.version.outputs.version }}"
|
||||
variant="${{ matrix.ffmpeg }}"
|
||||
name="ytpl-sync-linux-${version}-${variant}.tar.gz"
|
||||
tar -C package -czf "$name" .
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: packages-${{ runner.os }}-${{ matrix.ffmpeg }}
|
||||
path: |
|
||||
*.zip
|
||||
*.tar.gz
|
||||
|
||||
release:
|
||||
name: Create Release
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: artifacts
|
||||
|
||||
- name: Create release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
tag_name: ${{ inputs.tag }}
|
||||
draft: ${{ inputs.draft }}
|
||||
files: |
|
||||
artifacts/**/*.zip
|
||||
artifacts/**/*.tar.gz
|
||||
@@ -1,155 +0,0 @@
|
||||
name: Build Release V2
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: "Release tag (e.g., v0.1.0)"
|
||||
required: true
|
||||
default: "v0.1.0"
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
packages: write
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: '3.11'
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install pytest
|
||||
- name: Run tests
|
||||
env:
|
||||
PYTHONPATH: ${{ github.workspace }}
|
||||
run: pytest
|
||||
|
||||
build:
|
||||
needs: test
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- platform: windows
|
||||
os: windows-latest
|
||||
artifact_name: windows-release
|
||||
- platform: linux
|
||||
os: ubuntu-latest
|
||||
artifact_name: linux-release
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Get version
|
||||
id: version
|
||||
shell: bash
|
||||
run: |
|
||||
TAG="${{ github.event.inputs.tag || inputs.tag }}"
|
||||
VERSION="${TAG#v}"
|
||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||
|
||||
# --- WINDOWS BUILD ---
|
||||
- name: Build Windows Package
|
||||
if: matrix.platform == 'windows'
|
||||
shell: pwsh
|
||||
run: |
|
||||
$VERSION = "${{ steps.version.outputs.version }}"
|
||||
$WORKSPACE = "${{ github.workspace }}"
|
||||
New-Item -ItemType Directory -Force -Path "$WORKSPACE/dist/windows/bin"
|
||||
|
||||
# FFmpeg
|
||||
Invoke-WebRequest -Uri "https://www.gyan.dev/ffmpeg/builds/ffmpeg-release-essentials.zip" -OutFile "$WORKSPACE/dist/windows/ffmpeg.zip"
|
||||
Expand-Archive "$WORKSPACE/dist/windows/ffmpeg.zip" -DestinationPath "$WORKSPACE/dist/windows/ffmpeg_temp"
|
||||
$ffmpegExe = Get-ChildItem -Path "$WORKSPACE/dist/windows/ffmpeg_temp" -Filter "ffmpeg.exe" -Recurse | Select-Object -First 1
|
||||
Move-Item $ffmpegExe.FullName "$WORKSPACE/dist/windows/bin/ffmpeg.exe"
|
||||
|
||||
# Build .exe using PyInstaller
|
||||
# Use --add-data to include the src folder so internal imports (like 'import cli') work
|
||||
pip install pyinstaller
|
||||
pyinstaller --onefile --name "yt-playlist-downloader" --workpath "$WORKSPACE/build" --specpath "$WORKSPACE" --distpath "$WORKSPACE/dist" --add-data "$WORKSPACE/src;src" "$WORKSPACE/yt-playlist-main.py"
|
||||
Move-Item "$WORKSPACE/dist/yt-playlist-downloader.exe" "$WORKSPACE/dist/windows/yt-playlist-downloader.exe"
|
||||
|
||||
# Cleanup & Archive
|
||||
Remove-Item -Recurse -Force "$WORKSPACE/dist/windows/ffmpeg_temp", "$WORKSPACE/dist/windows/aria2_temp", "$WORKSPACE/dist/windows/*.zip"
|
||||
Compress-Archive -Path "$WORKSPACE/dist/windows/*" -DestinationPath "$WORKSPACE/yt-playlist-windows-$VERSION.zip"
|
||||
|
||||
# --- LINUX BUILD ---
|
||||
- name: Build Linux Package
|
||||
if: matrix.platform == 'linux'
|
||||
run: |
|
||||
VERSION="${{ steps.version.outputs.version }}"
|
||||
mkdir -p "$GITHUB_WORKSPACE/dist/linux/bin"
|
||||
cp "$GITHUB_WORKSPACE/yt-playlist-main.py" "$GITHUB_WORKSPACE/dist/linux/"
|
||||
cp -r "$GITHUB_WORKSPACE/src" "$GITHUB_WORKSPACE/dist/linux/"
|
||||
|
||||
# FFmpeg (static)
|
||||
curl -L https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-amd64-static.tar.xz -o "$GITHUB_WORKSPACE/ffmpeg.tar.xz"
|
||||
mkdir -p "$GITHUB_WORKSPACE/ffmpeg_temp"
|
||||
tar -xf "$GITHUB_WORKSPACE/ffmpeg.tar.xz" -C "$GITHUB_WORKSPACE/ffmpeg_temp" --strip-components=1
|
||||
mv "$GITHUB_WORKSPACE/ffmpeg_temp/ffmpeg" "$GITHUB_WORKSPACE/dist/linux/bin/"
|
||||
chmod +x "$GITHUB_WORKSPACE/dist/linux/bin/ffmpeg"
|
||||
|
||||
- name: Archive Linux Package
|
||||
if: matrix.platform == 'linux'
|
||||
run: |
|
||||
VERSION="${{ steps.version.outputs.version }}"
|
||||
# Archive
|
||||
cd "$GITHUB_WORKSPACE/dist/linux" && tar -czf "$GITHUB_WORKSPACE/yt-playlist-linux-$VERSION.tar.gz" *
|
||||
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: ${{ matrix.artifact_name }}
|
||||
path: |
|
||||
${{ github.workspace }}/*.zip
|
||||
${{ github.workspace }}/*.tar.gz
|
||||
|
||||
|
||||
release:
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
if: startsWith(github.event.inputs.tag, 'v')
|
||||
steps:
|
||||
- name: Download all artifacts
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
path: ${{ github.workspace }}/artifacts
|
||||
|
||||
- name: Get version
|
||||
id: version
|
||||
shell: bash
|
||||
run: |
|
||||
TAG="${{ github.event.inputs.tag || inputs.tag }}"
|
||||
VERSION="${TAG#v}"
|
||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v4
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Push Docker images
|
||||
run: |
|
||||
docker load -i "${{ github.workspace }}/artifacts/docker-images/docker-image.tar"
|
||||
docker push ghcr.io/${GITHUB_ACTOR}/ytpl-Sync:${{ steps.version.outputs.version }}
|
||||
docker load -i "${{ github.workspace }}/artifacts/docker-images/docker-image-latest.tar"
|
||||
docker push ghcr.io/${GITHUB_ACTOR}/ytpl-Sync:latest
|
||||
|
||||
- name: Create Release
|
||||
uses: softprops/action-gh-release@v3
|
||||
with:
|
||||
tag_name: ${{ github.event.inputs.tag }}
|
||||
draft: true
|
||||
files: |
|
||||
${{ github.workspace }}/artifacts/**/*.zip
|
||||
${{ github.workspace }}/artifacts/**/*.tar.gz
|
||||
@@ -1,64 +0,0 @@
|
||||
name: dependency-updates
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: "0 12 * * 1"
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
update-bundled-binaries:
|
||||
name: Refresh pinned build dependencies
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Discover latest aria2 release
|
||||
id: aria2
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
latest_version="$(
|
||||
git ls-remote --tags --refs https://github.com/aria2/aria2.git 'refs/tags/release-*' \
|
||||
| awk -F/ '{print $NF}' \
|
||||
| sed 's/^release-//' \
|
||||
| sort -V \
|
||||
| tail -n 1
|
||||
)"
|
||||
|
||||
if [ -z "$latest_version" ]; then
|
||||
echo "Unable to determine the latest aria2 release." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "version=$latest_version" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Update release workflow pins
|
||||
shell: bash
|
||||
env:
|
||||
ARIA2_VERSION: ${{ steps.aria2.outputs.version }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
python3 -c 'from pathlib import Path; import os; path = Path(".github/workflows/build_v2.yml"); text = path.read_text(encoding="utf-8"); version = os.environ["ARIA2_VERSION"]; updated = text.replace("release-1.37.0", f"release-{version}").replace("aria2-1.37.0", f"aria2-{version}").replace("aria2c-linux-1.37.0", f"aria2c-linux-{version}"); path.write_text(updated, encoding="utf-8") if updated != text else None; print(f"Updated aria2 references to {version}." if updated != text else "No aria2 references changed.")'
|
||||
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v8
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
commit-message: "chore: refresh aria2 build pins"
|
||||
title: "Refresh aria2 build pins"
|
||||
body: |
|
||||
Automated maintenance update for the bundled aria2 release references used by the release workflow.
|
||||
branch: dependency-updates/aria2
|
||||
delete-branch: true
|
||||
labels: dependencies, maintenance
|
||||
@@ -0,0 +1,8 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from app.cli import main
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
|
||||
Reference in New Issue
Block a user