refactor: reorganize proxy management structure and add tests #45
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: Build and Release | |
| on: | |
| push: | |
| branches: | |
| - master | |
| tags: | |
| - 'v*' | |
| paths-ignore: | |
| - 'README.md' | |
| - '.github/dependabot.yml' | |
| - '.gitignore' | |
| - 'docs/**' | |
| permissions: | |
| contents: write | |
| jobs: | |
| build: | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - os: ubuntu-latest | |
| target: linux-x64 | |
| artifact: kemono-scraper-linux-x64 | |
| - os: ubuntu-latest | |
| target: linux-arm64 | |
| artifact: kemono-scraper-linux-arm64 | |
| - os: ubuntu-latest | |
| target: windows-x64 | |
| artifact: kemono-scraper-windows-x64.exe | |
| - os: macos-latest | |
| target: darwin-x64 | |
| artifact: kemono-scraper-darwin-x64 | |
| - os: macos-latest | |
| target: darwin-arm64 | |
| artifact: kemono-scraper-darwin-arm64 | |
| runs-on: ${{ matrix.os }} | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 # Fetch all history for git describe | |
| - name: Setup Bun | |
| uses: oven-sh/setup-bun@v2 | |
| with: | |
| bun-version: latest | |
| - name: Install dependencies | |
| run: bun install --frozen-lockfile | |
| - name: Build executable | |
| run: bun run scripts/build.ts ${{ matrix.target }} | |
| env: | |
| # Pass version explicitly for tagged releases | |
| BUILD_VERSION: ${{ startsWith(github.ref, 'refs/tags/') && github.ref_name || '' }} | |
| - name: Upload artifact | |
| uses: actions/upload-artifact@v5 | |
| with: | |
| name: ${{ matrix.artifact }} | |
| path: dist/${{ matrix.artifact }} | |
| if-no-files-found: error | |
| release: | |
| needs: build | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 | |
| - name: Download all artifacts | |
| uses: actions/download-artifact@v6 | |
| with: | |
| path: artifacts | |
| - name: Prepare release files | |
| run: | | |
| mkdir -p release | |
| # Move all artifacts to release directory, flattening structure | |
| find artifacts -type f -exec mv {} release/ \; | |
| ls -la release/ | |
| - name: Generate release name and tag | |
| id: release_info | |
| run: | | |
| # Derive current tag/name and changelog window | |
| LATEST_STABLE_TAG=$(git tag --list 'v*' --sort=-v:refname | sed -n '1p') | |
| PREVIOUS_STABLE_TAG=$(git tag --list 'v*' --sort=-v:refname | sed -n '2p') | |
| if [[ "${{ github.ref }}" == refs/tags/* ]]; then | |
| # Tagged release | |
| TAG_NAME="${{ github.ref_name }}" | |
| RELEASE_NAME="${TAG_NAME}" | |
| IS_PRERELEASE=false | |
| # Use the previous stable tag (if any) to bound the changelog range | |
| SINCE_TAG="${PREVIOUS_STABLE_TAG}" | |
| else | |
| # Nightly release - use fixed tag name (only one nightly release exists at a time) | |
| SHORT_SHA=$(git rev-parse --short HEAD) | |
| TAG_NAME="nightly" | |
| RELEASE_NAME="Nightly Build (${SHORT_SHA})" | |
| IS_PRERELEASE=true | |
| # For nightly, diff from latest stable tag (if one exists) | |
| SINCE_TAG="${LATEST_STABLE_TAG}" | |
| fi | |
| echo "tag_name=${TAG_NAME}" >> $GITHUB_OUTPUT | |
| echo "release_name=${RELEASE_NAME}" >> $GITHUB_OUTPUT | |
| echo "is_prerelease=${IS_PRERELEASE}" >> $GITHUB_OUTPUT | |
| echo "commit_sha=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT | |
| echo "short_sha=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT | |
| echo "since_tag=${SINCE_TAG}" >> $GITHUB_OUTPUT | |
| - name: Delete existing nightly release and tag | |
| if: ${{ !startsWith(github.ref, 'refs/tags/') }} | |
| run: | | |
| # Delete the existing nightly release and tag (only one nightly release at a time) | |
| gh release delete nightly --yes --cleanup-tag || true | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Generate changelog from semantic commit messages | |
| id: changelog_fallback | |
| run: | | |
| LOG_START="${{ steps.release_info.outputs.since_tag }}" | |
| if [[ -z "${LOG_START}" ]]; then | |
| LOG_START=$(git rev-list --max-parents=0 HEAD | tail -n 1) | |
| fi | |
| LOG_END="${{ steps.release_info.outputs.commit_sha }}" | |
| # Try semantic commits without merges first | |
| git log --no-merges --pretty=format:'- %s (%h)' --reverse "${LOG_START}..${LOG_END}" > semantic_changelog.md || true | |
| # If empty (e.g., only merge commits), fall back to including merges | |
| if [[ ! -s semantic_changelog.md ]]; then | |
| git log --pretty=format:'- %s (%h)' --reverse "${LOG_START}..${LOG_END}" > semantic_changelog.md || true | |
| fi | |
| # If still empty, emit a placeholder with context | |
| if [[ ! -s semantic_changelog.md ]]; then | |
| echo "- No commits found for range ${LOG_START}..${LOG_END}." > semantic_changelog.md | |
| fi | |
| echo "changelog_file=semantic_changelog.md" >> $GITHUB_OUTPUT | |
| - name: Create Release | |
| run: | | |
| # Create release notes file | |
| cat > release_body.md << 'RELEASE_EOF' | |
| ## Kemono Scraper | |
| Standalone executables for scraping kemono/coomer content. | |
| ### Downloads | |
| | Platform | Architecture | File | | |
| |----------|--------------|------| | |
| | Linux | x64 | [kemono-scraper-linux-x64](https://github.com/3dnsfw/kemono-scraper/releases/download/${{ steps.release_info.outputs.tag_name }}/kemono-scraper-linux-x64) | | |
| | Linux | ARM64 | [kemono-scraper-linux-arm64](https://github.com/3dnsfw/kemono-scraper/releases/download/${{ steps.release_info.outputs.tag_name }}/kemono-scraper-linux-arm64) | | |
| | Windows | x64 | [kemono-scraper-windows-x64.exe](https://github.com/3dnsfw/kemono-scraper/releases/download/${{ steps.release_info.outputs.tag_name }}/kemono-scraper-windows-x64.exe) | | |
| | macOS | x64 (Intel) | [kemono-scraper-darwin-x64](https://github.com/3dnsfw/kemono-scraper/releases/download/${{ steps.release_info.outputs.tag_name }}/kemono-scraper-darwin-x64) | | |
| | macOS | ARM64 (Apple Silicon) | [kemono-scraper-darwin-arm64](https://github.com/3dnsfw/kemono-scraper/releases/download/${{ steps.release_info.outputs.tag_name }}/kemono-scraper-darwin-arm64) | | |
| ### Usage | |
| ```bash | |
| # Linux/macOS - make executable first | |
| chmod +x kemono-scraper-* | |
| # Run with required arguments | |
| ./kemono-scraper-linux-x64 --service onlyfans --userId USERNAME | |
| # Windows | |
| kemono-scraper-windows-x64.exe --service onlyfans --userId USERNAME | |
| ``` | |
| ### Build Info | |
| - Commit: [${{ steps.release_info.outputs.commit_sha }}](https://github.com/3dnsfw/kemono-scraper/commit/${{ steps.release_info.outputs.commit_sha }}) | |
| - Built with Bun | |
| ### Changelog | |
| (Semantic commits) | |
| RELEASE_EOF | |
| CHANGELOG_FILE="${{ steps.changelog_fallback.outputs.changelog_file }}" | |
| if [[ -n "${CHANGELOG_FILE}" && -s "${CHANGELOG_FILE}" ]]; then | |
| # Drop the top-level heading if present to keep the release notes compact | |
| if head -n 1 "${CHANGELOG_FILE}" | grep -q '^# '; then | |
| tail -n +2 "${CHANGELOG_FILE}" >> release_body.md | |
| else | |
| cat "${CHANGELOG_FILE}" >> release_body.md | |
| fi | |
| else | |
| echo "- No changelog entries found." >> release_body.md | |
| fi | |
| # Build gh release create command | |
| PRERELEASE_FLAG="" | |
| if [[ "${{ steps.release_info.outputs.is_prerelease }}" == "true" ]]; then | |
| PRERELEASE_FLAG="--prerelease" | |
| fi | |
| # Create the release (gh release create does NOT create drafts by default) | |
| gh release create "${{ steps.release_info.outputs.tag_name }}" \ | |
| --title "${{ steps.release_info.outputs.release_name }}" \ | |
| --notes-file release_body.md \ | |
| $PRERELEASE_FLAG \ | |
| release/* | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Verify release assets exist | |
| if: steps.release_info.outputs.is_prerelease == 'false' | |
| id: verify_assets | |
| run: | | |
| TAG_NAME="${{ steps.release_info.outputs.tag_name }}" | |
| echo "Verifying assets for release ${TAG_NAME}..." | |
| EXPECTED_ASSETS=( | |
| "kemono-scraper-linux-x64" | |
| "kemono-scraper-linux-arm64" | |
| "kemono-scraper-windows-x64.exe" | |
| "kemono-scraper-darwin-x64" | |
| "kemono-scraper-darwin-arm64" | |
| ) | |
| ACTUAL_ASSETS=$(gh release view "${TAG_NAME}" --json assets --jq '.assets[].name') | |
| MISSING=() | |
| for asset in "${EXPECTED_ASSETS[@]}"; do | |
| if ! echo "${ACTUAL_ASSETS}" | grep -q "^${asset}$"; then | |
| MISSING+=("${asset}") | |
| fi | |
| done | |
| if [[ ${#MISSING[@]} -gt 0 ]]; then | |
| echo "::warning::Missing assets: ${MISSING[*]}" | |
| echo "assets_verified=false" >> $GITHUB_OUTPUT | |
| else | |
| echo "All expected assets found." | |
| echo "assets_verified=true" >> $GITHUB_OUTPUT | |
| fi | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Update docs with latest stable release tag | |
| if: steps.release_info.outputs.is_prerelease == 'false' && steps.verify_assets.outputs.assets_verified == 'true' | |
| run: | | |
| TAG_NAME="${{ steps.release_info.outputs.tag_name }}" | |
| DOCS_FILE="docs/src/content/docs/getting-started/installation.mdx" | |
| echo "Updating docs with stable release tag: ${TAG_NAME}" | |
| # Replace __LATEST_TAG__ placeholder with actual tag | |
| sed -i "s/__LATEST_TAG__/${TAG_NAME}/g" "${DOCS_FILE}" | |
| # Check if any changes were made | |
| if git diff --quiet "${DOCS_FILE}"; then | |
| echo "No changes to docs (placeholder may already be updated or not present)." | |
| exit 0 | |
| fi | |
| # Configure git | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| # Commit and push | |
| git add "${DOCS_FILE}" | |
| git commit -m "docs: update download links to ${TAG_NAME}" | |
| git push origin HEAD:master | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |