Skip to content
Latchkey

CI workflow (jaywcjlove/free-font)

The CI workflow from jaywcjlove/free-font, explained and optimized by Latchkey.

F

CI health: F - at risk

Point runs-on at Latchkey and get caching, run de-duplication, job timeouts, SHA-pinned actions, self-healing for flaky steps, and up to 58% lower cost, applied automatically.

Grade your own workflow free or run it on Latchkey →
Source: jaywcjlove/free-font.github/workflows/ci.ymlLicense MITView source

What it does

This is the CI workflow from the jaywcjlove/free-font repository, a real project running GitHub Actions. It is shown here with attribution under its MIT license.

Below, Latchkey shows a faster, safer version produced by its optimization engine.

The workflow

workflow (.yml)
name: CI
on:
  push:
    branches:
      - main

jobs:
  ghpage:
    if: github.repository == 'jaywcjlove/free-font'
    runs-on: ubuntu-latest
    permissions:
      contents: write
      id-token: write
    outputs:
      release_successful: ${{ steps.create_release.outputs.successful }}
      changelog_version: ${{ steps.changelog.outputs.version }}
    steps:
      - uses: actions/checkout@v4
      - name: Free Disk Space (Ubuntu)
        uses: jlumbroso/free-disk-space@main
        with:
          tool-cache: true
          android: true
          dotnet: true
          haskell: true
          large-packages: true
          docker-images: true
          swap-storage: true
      - run: mkdir -p website
      - name: Create info.html page.
        working-directory: website
        run: |
          cat > info.html << EOF
          <!DOCTYPE html>
          <html lang="zh-CN">
          <head>
            <meta charset="UTF-8">
            <meta name="viewport" content="width=device-width, initial-scale=1.0">
            <meta name="description" content="Free Font - 收录商用免费字体,包括汉字和英文字体,提供免费下载和使用。">
            <meta name="keywords" content="免费字体, 商用字体, 汉字字体, 英文字体, 字体下载, 免费商用字体, free fonts, commercial free fonts, Chinese fonts, English fonts, font download, free commercial fonts">
            <meta name="author" content="jaywcjlove">
            <title>Free Font - 收录商用免费字体(汉字英文字体)</title>
            <style>
              body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; min-height: 100vh; display: flex; align-items: center; justify-content: center; text-align: center; }
              h1 { font-size: 3em; }
              h2 { font-size: 24px; font-weight: 400; }
              a { background: #2196F3; color: #fff; display: inline-block; text-decoration: none; border-radius: 4px; padding: 5px 8px; transition: all 0.3s; }
              a:hover { background: #001eff; }
            </style>
          </head>
          <body onload="startCountdown(10)">
            <div>
              <h1>Free Font 免费可商用字体</h1>
              <p style="font-size: 2em;">倒计时: <span id="countdown">10</span></p>
              <h2>页面将在 10 秒钟后自动跳转到<b>镜像网站</b></h2>
              <p style="color: red;">⚠️ GitHub Page 因体积太大已无法更新,最新更新,访问下面镜像网站</p>
              <div>
                <a href="https://raw.githack.com/jaywcjlove/free-font/main/docs/index.html" target="_blank">githack.com</a> &nbsp;
                <a href="https://free-font.vercel.app" target="_blank">vercel.app</a>
                <a href="https://freefont.netlify.app" target="_blank">netlify.app</a>
              </div>
              <p style="font-weight: 300;">欢迎部署镜像网站,镜像网站将放到这里提供给大家选择</p>
            </div>
            <script>
              function startCountdown(seconds) {
                const countdownElement = document.getElementById('countdown');
                let remainingTime = seconds;
                const interval = setInterval(() => {
                    if (remainingTime <= 0) {
                        clearInterval(interval);
                        countdownElement.parentElement = "正在跳转到 Free Font 页面...";
                        setTimeout(function() {
                            window.location.href = "https://free-font.vercel.app/";
                        }, 1000);
                    } else {
                        countdownElement.textContent = remainingTime;
                        remainingTime--;
                    }
                }, 1000);
              }
            </script>
          </body>
          </html>
          EOF
      - name: Move static resource files
        run: |
          cp docs/*.html ./website
          cp -rp docs/appicon ./website
          cp -rp docs/css ./website
          cp -rp docs/js ./website
          cp -rp docs/images ./website
          cp -rp docs/details ./website
          cp -rp docs/icons ./website
          cp -rp docs/sitemap.txt ./website
      - name: Create README.md
        working-directory: website
        run: |
          cat > README.md << EOF
          GitHub [Page](https://jaywcjlove.github.io/free-font/) ,镜像网站访问:[`Githack`](https://raw.githack.com/jaywcjlove/free-font/main/docs/index.html)、[`Vercel`](https://free-font.vercel.app)、[`Netlify`](https://freefont.netlify.app)
          EOF
      - name: Create Tag
        id: create_tag
        uses: jaywcjlove/create-tag-action@main
        with:
          package-path: ./package.json
      - name: Generate Changelog
        id: changelog
        uses: jaywcjlove/changelog-generator@main
        with:
          filter-author: (小弟调调™|\@github-actions-bot\@renovate-bot|dependabot|renovate\\[bot\\]|dependabot\\[bot\\]|Renovate Bot)
          filter: (^[\s]+?[R|r]elease)|(^[R|r]elease)
          gh-pages: main
      - name: Create Release
        id: create_release
        uses: jaywcjlove/create-tag-action@main
        if: steps.create_tag.outputs.successful == 'true'
        with:
          package-path: ./package.json
          version: ${{steps.create_tag.outputs.version}}
          release: true
          prerelease: false
          draft: false
          body: |
            [![Buy me a coffee](https://img.shields.io/badge/Buy%20me%20a%20coffee-048754?logo=buymeacoffee)](https://jaywcjlove.github.io/#/sponsor)  
            备用历史网站: ${{ steps.changelog.outputs.tag }}: https://raw.githack.com/jaywcjlove/free-font/${{ steps.changelog.outputs.gh-pages-short-hash }}/docs/index.html  
            比较变化: ${{ steps.changelog.outputs.compareurl }}  
            > [!NOTE]
            > 
            > GitHub [Page](https://jaywcjlove.github.io/free-font/) ,镜像网站访问:[`Githack`](https://raw.githack.com/jaywcjlove/free-font/main/docs/index.html)、[`Vercel`](https://free-font.vercel.app)、[`Netlify`](https://freefont.netlify.app)

            ${{ steps.changelog.outputs.changelog }}

            ## Docker

            ```shell
            docker pull wcjiang/free-font:latest
            ```

            ```shell
            docker run --name reference --rm -d -p 9677:3000 wcjiang/free-font:latest
            # Or
            docker run --name reference -itd -p 9677:3000 wcjiang/free-font:latest
            ```
      - name: Deploy
        uses: peaceiris/actions-gh-pages@v4
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          publish_dir: ./website
          exclude_assets: |
            fonts/
            docs/fonts/
            **/*.ttf
            **/*.otf
            **/*.woff
            **/*.woff2

  build:
    if: github.repository == 'jaywcjlove/free-font' && needs.ghpage.outputs.release_successful == 'true'
    needs: ghpage
    runs-on: ubuntu-latest
    timeout-minutes: 480  # 8 hours timeout
    permissions:
      contents: write
      id-token: write
    steps:
      - name: Free Disk Space (Ubuntu)
        uses: jlumbroso/free-disk-space@main
        with:
          tool-cache: true
          android: true
          dotnet: true
          haskell: true
          large-packages: true
          docker-images: true
          swap-storage: true
      - name: Check Disk Space After Cleanup
        run: |
          echo "=== After Initial Cleanup ==="
          df -h
          echo "=== Memory Usage ==="
          free -h
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
      - name: Check Disk Space Before npm install
        run: |
          echo "=== Before npm install ==="
          df -h
      - run: npm install
      - name: Check Disk Space After npm install
        run: |
          echo "=== After npm install ==="
          df -h
      - run: npm run build:docker
      - name: Check Disk Space After build
        run: |
          echo "=== After build ==="
          df -h
      - name: Additional Cleanup Before Docker
        run: |
          echo "=== Before Additional Cleanup ==="
          df -h
          
          # Clean npm cache and node modules
          npm cache clean --force || true
          sudo rm -rf ~/.npm ~/.node-gyp ~/.cache/node-gyp ~/.nvm
          sudo rm -rf /usr/local/lib/node_modules
          sudo rm -rf node_modules/.cache
          
          # Clean system packages and cache
          sudo apt-get clean
          sudo apt-get autoremove -y
          sudo rm -rf /var/lib/apt/lists/*
          sudo rm -rf /tmp/*
          sudo rm -rf /var/tmp/*
          
          # Clean additional directories
          sudo rm -rf /usr/local/lib/android /opt/ghc /usr/share/dotnet /usr/share/swift /usr/local/.ghcup
          sudo rm -rf /usr/share/miniconda /usr/local/share/boost /usr/local/graalvm
          sudo rm -rf /usr/local/share/powershell /usr/share/swift
          
          # Clean logs
          sudo rm -rf /var/log/* || true
          
          echo "=== After Additional Cleanup ==="
          df -h
      - uses: docker/setup-buildx-action@v3
        with:
          driver-opts: |
            network=host
            image=moby/buildkit:master
          buildkitd-config-inline: |
            [worker.oci]
              max-parallelism = 1
              gc = true
              gckeepstorage = "2GB"
              gcpolicy = [{keep-duration = "24h"}, {filters = ["type==source.local"], all = true, keepBytes = 1073741824}]
            [registry."docker.io"]
              mirrors = ["mirror.gcr.io"]
      - name: Login to Docker Hub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKER_USER }}
          password: ${{ secrets.DOCKER_PASSWORD }}
      - name: Clean Docker system
        run: |
          echo "=== Before Docker cleanup ==="
          df -h
          docker system prune -af --volumes
          docker buildx prune -af
          echo "=== After Docker cleanup ==="
          df -h
      - name: Build and push image:latest
        uses: docker/build-push-action@v5
        with:
          push: true
          context: .
          platforms: linux/amd64,linux/arm64
          tags: wcjiang/free-font:latest
          cache-from: type=gha
          cache-to: type=gha,mode=max
          outputs: type=registry
          provenance: false
          sbom: false
      - name: Clean Between Docker Builds
        run: |
          echo "=== Cleaning between Docker builds ==="
          docker system prune -f
          echo "=== Disk space after cleanup ==="
          df -h
      - name: Build and push image:tags
        uses: docker/build-push-action@v5
        if: needs.ghpage.outputs.release_successful == 'true'
        with:
          push: true
          context: .
          platforms: linux/amd64,linux/arm64
          tags: wcjiang/free-font:${{needs.ghpage.outputs.changelog_version}}
          cache-from: type=gha
          cache-to: type=gha,mode=max
          outputs: type=registry
          provenance: false
          sbom: false

The same workflow, on Latchkey

Estimated ~20% faster on cache hits, plus fewer wasted runs and a safer supply chain. Added and changed lines are highlighted.

name: CI
on:
  push:
    branches:
      - main
 
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true
 
jobs:
  ghpage:
    timeout-minutes: 30
    if: github.repository == 'jaywcjlove/free-font'
    runs-on: latchkey-small
    permissions:
      contents: write
      id-token: write
    outputs:
      release_successful: ${{ steps.create_release.outputs.successful }}
      changelog_version: ${{ steps.changelog.outputs.version }}
    steps:
      - uses: actions/checkout@v4
      - name: Free Disk Space (Ubuntu)
        uses: jlumbroso/free-disk-space@main
        with:
          tool-cache: true
          android: true
          dotnet: true
          haskell: true
          large-packages: true
          docker-images: true
          swap-storage: true
      - run: mkdir -p website
      - name: Create info.html page.
        working-directory: website
        run: |
          cat > info.html << EOF
          <!DOCTYPE html>
          <html lang="zh-CN">
          <head>
            <meta charset="UTF-8">
            <meta name="viewport" content="width=device-width, initial-scale=1.0">
            <meta name="description" content="Free Font - 收录商用免费字体,包括汉字和英文字体,提供免费下载和使用。">
            <meta name="keywords" content="免费字体, 商用字体, 汉字字体, 英文字体, 字体下载, 免费商用字体, free fonts, commercial free fonts, Chinese fonts, English fonts, font download, free commercial fonts">
            <meta name="author" content="jaywcjlove">
            <title>Free Font - 收录商用免费字体(汉字英文字体)</title>
            <style>
              body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; min-height: 100vh; display: flex; align-items: center; justify-content: center; text-align: center; }
              h1 { font-size: 3em; }
              h2 { font-size: 24px; font-weight: 400; }
              a { background: #2196F3; color: #fff; display: inline-block; text-decoration: none; border-radius: 4px; padding: 5px 8px; transition: all 0.3s; }
              a:hover { background: #001eff; }
            </style>
          </head>
          <body onload="startCountdown(10)">
            <div>
              <h1>Free Font 免费可商用字体</h1>
              <p style="font-size: 2em;">倒计时: <span id="countdown">10</span></p>
              <h2>页面将在 10 秒钟后自动跳转到<b>镜像网站</b></h2>
              <p style="color: red;">⚠️ GitHub Page 因体积太大已无法更新,最新更新,访问下面镜像网站</p>
              <div>
                <a href="https://raw.githack.com/jaywcjlove/free-font/main/docs/index.html" target="_blank">githack.com</a> &nbsp;
                <a href="https://free-font.vercel.app" target="_blank">vercel.app</a>
                <a href="https://freefont.netlify.app" target="_blank">netlify.app</a>
              </div>
              <p style="font-weight: 300;">欢迎部署镜像网站,镜像网站将放到这里提供给大家选择</p>
            </div>
            <script>
              function startCountdown(seconds) {
                const countdownElement = document.getElementById('countdown');
                let remainingTime = seconds;
                const interval = setInterval(() => {
                    if (remainingTime <= 0) {
                        clearInterval(interval);
                        countdownElement.parentElement = "正在跳转到 Free Font 页面...";
                        setTimeout(function() {
                            window.location.href = "https://free-font.vercel.app/";
                        }, 1000);
                    } else {
                        countdownElement.textContent = remainingTime;
                        remainingTime--;
                    }
                }, 1000);
              }
            </script>
          </body>
          </html>
          EOF
      - name: Move static resource files
        run: |
          cp docs/*.html ./website
          cp -rp docs/appicon ./website
          cp -rp docs/css ./website
          cp -rp docs/js ./website
          cp -rp docs/images ./website
          cp -rp docs/details ./website
          cp -rp docs/icons ./website
          cp -rp docs/sitemap.txt ./website
      - name: Create README.md
        working-directory: website
        run: |
          cat > README.md << EOF
          GitHub [Page](https://jaywcjlove.github.io/free-font/) ,镜像网站访问:[`Githack`](https://raw.githack.com/jaywcjlove/free-font/main/docs/index.html)、[`Vercel`](https://free-font.vercel.app)、[`Netlify`](https://freefont.netlify.app)
          EOF
      - name: Create Tag
        id: create_tag
        uses: jaywcjlove/create-tag-action@main
        with:
          package-path: ./package.json
      - name: Generate Changelog
        id: changelog
        uses: jaywcjlove/changelog-generator@main
        with:
          filter-author: (小弟调调™|\@github-actions-bot\@renovate-bot|dependabot|renovate\\[bot\\]|dependabot\\[bot\\]|Renovate Bot)
          filter: (^[\s]+?[R|r]elease)|(^[R|r]elease)
          gh-pages: main
      - name: Create Release
        id: create_release
        uses: jaywcjlove/create-tag-action@main
        if: steps.create_tag.outputs.successful == 'true'
        with:
          package-path: ./package.json
          version: ${{steps.create_tag.outputs.version}}
          release: true
          prerelease: false
          draft: false
          body: |
            [![Buy me a coffee](https://img.shields.io/badge/Buy%20me%20a%20coffee-048754?logo=buymeacoffee)](https://jaywcjlove.github.io/#/sponsor)  
            备用历史网站: ${{ steps.changelog.outputs.tag }}: https://raw.githack.com/jaywcjlove/free-font/${{ steps.changelog.outputs.gh-pages-short-hash }}/docs/index.html  
            比较变化: ${{ steps.changelog.outputs.compareurl }}  
            > [!NOTE]
            > 
            > GitHub [Page](https://jaywcjlove.github.io/free-font/) ,镜像网站访问:[`Githack`](https://raw.githack.com/jaywcjlove/free-font/main/docs/index.html)、[`Vercel`](https://free-font.vercel.app)、[`Netlify`](https://freefont.netlify.app)
 
            ${{ steps.changelog.outputs.changelog }}
 
            ## Docker
 
            ```shell
            docker pull wcjiang/free-font:latest
            ```
 
            ```shell
            docker run --name reference --rm -d -p 9677:3000 wcjiang/free-font:latest
            # Or
            docker run --name reference -itd -p 9677:3000 wcjiang/free-font:latest
            ```
      - name: Deploy
        uses: peaceiris/actions-gh-pages@v4
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          publish_dir: ./website
          exclude_assets: |
            fonts/
            docs/fonts/
            **/*.ttf
            **/*.otf
            **/*.woff
            **/*.woff2
 
  build:
    if: github.repository == 'jaywcjlove/free-font' && needs.ghpage.outputs.release_successful == 'true'
    needs: ghpage
    runs-on: latchkey-small
    timeout-minutes: 480  # 8 hours timeout
    permissions:
      contents: write
      id-token: write
    steps:
      - name: Free Disk Space (Ubuntu)
        uses: jlumbroso/free-disk-space@main
        with:
          tool-cache: true
          android: true
          dotnet: true
          haskell: true
          large-packages: true
          docker-images: true
          swap-storage: true
      - name: Check Disk Space After Cleanup
        run: |
          echo "=== After Initial Cleanup ==="
          df -h
          echo "=== Memory Usage ==="
          free -h
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          cache: 'npm'
          node-version: 20
      - name: Check Disk Space Before npm install
        run: |
          echo "=== Before npm install ==="
          df -h
      - run: npm install
      - name: Check Disk Space After npm install
        run: |
          echo "=== After npm install ==="
          df -h
      - run: npm run build:docker
      - name: Check Disk Space After build
        run: |
          echo "=== After build ==="
          df -h
      - name: Additional Cleanup Before Docker
        run: |
          echo "=== Before Additional Cleanup ==="
          df -h
          
          # Clean npm cache and node modules
          npm cache clean --force || true
          sudo rm -rf ~/.npm ~/.node-gyp ~/.cache/node-gyp ~/.nvm
          sudo rm -rf /usr/local/lib/node_modules
          sudo rm -rf node_modules/.cache
          
          # Clean system packages and cache
          sudo apt-get clean
          sudo apt-get autoremove -y
          sudo rm -rf /var/lib/apt/lists/*
          sudo rm -rf /tmp/*
          sudo rm -rf /var/tmp/*
          
          # Clean additional directories
          sudo rm -rf /usr/local/lib/android /opt/ghc /usr/share/dotnet /usr/share/swift /usr/local/.ghcup
          sudo rm -rf /usr/share/miniconda /usr/local/share/boost /usr/local/graalvm
          sudo rm -rf /usr/local/share/powershell /usr/share/swift
          
          # Clean logs
          sudo rm -rf /var/log/* || true
          
          echo "=== After Additional Cleanup ==="
          df -h
      - uses: docker/setup-buildx-action@v3
        with:
          driver-opts: |
            network=host
            image=moby/buildkit:master
          buildkitd-config-inline: |
            [worker.oci]
              max-parallelism = 1
              gc = true
              gckeepstorage = "2GB"
              gcpolicy = [{keep-duration = "24h"}, {filters = ["type==source.local"], all = true, keepBytes = 1073741824}]
            [registry."docker.io"]
              mirrors = ["mirror.gcr.io"]
      - name: Login to Docker Hub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKER_USER }}
          password: ${{ secrets.DOCKER_PASSWORD }}
      - name: Clean Docker system
        run: |
          echo "=== Before Docker cleanup ==="
          df -h
          docker system prune -af --volumes
          docker buildx prune -af
          echo "=== After Docker cleanup ==="
          df -h
      - name: Build and push image:latest
        uses: docker/build-push-action@v5
        with:
          push: true
          context: .
          platforms: linux/amd64,linux/arm64
          tags: wcjiang/free-font:latest
          cache-from: type=gha
          cache-to: type=gha,mode=max
          outputs: type=registry
          provenance: false
          sbom: false
      - name: Clean Between Docker Builds
        run: |
          echo "=== Cleaning between Docker builds ==="
          docker system prune -f
          echo "=== Disk space after cleanup ==="
          df -h
      - name: Build and push image:tags
        uses: docker/build-push-action@v5
        if: needs.ghpage.outputs.release_successful == 'true'
        with:
          push: true
          context: .
          platforms: linux/amd64,linux/arm64
          tags: wcjiang/free-font:${{needs.ghpage.outputs.changelog_version}}
          cache-from: type=gha
          cache-to: type=gha,mode=max
          outputs: type=registry
          provenance: false
          sbom: false
 

What changed

7 third-party actions are referenced by a movable tag. Pin them to the commit SHA (Latchkey resolves and applies this automatically) so a repointed tag cannot change what runs.

What Latchkey heals here

This workflow has steps that commonly fail on transient issues (network, registries, flaky browsers). On Latchkey managed runners they are detected, retried, and self-healed instead of failing your build:

This workflow runs 2 jobs per trigger. On Latchkey the same minutes cost up to 58% less than GitHub-hosted, with zero queue time.

Actions used in this workflow