fix(prime-checker): correct Mersenne prime facts across all 17 locales #207
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: Deploy to VPS | |
| on: | |
| push: | |
| branches: [main] | |
| workflow_dispatch: {} | |
| concurrency: | |
| group: deploy-vps | |
| cancel-in-progress: false | |
| jobs: | |
| deploy: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 40 | |
| needs: [] | |
| defaults: | |
| run: | |
| working-directory: site | |
| env: | |
| DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }} | |
| DEPLOY_USER: ${{ secrets.DEPLOY_USER }} | |
| DEPLOY_PATH: ${{ secrets.DEPLOY_PATH }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| # 218ツール×17言語=約7,800ページのSSGでランナーのディスクが枯渇する(ENOSPC)。 | |
| # ビルド前に未使用のプリインストールSDKを削除して約30GB確保する。 | |
| - name: Free disk space | |
| working-directory: / | |
| run: | | |
| echo "Before:"; df -h / | tail -1 | |
| sudo rm -rf /usr/share/dotnet /opt/ghc /usr/local/.ghcup \ | |
| /usr/local/lib/android /usr/local/share/boost \ | |
| /usr/local/share/powershell /usr/share/swift "$AGENT_TOOLSDIRECTORY" || true | |
| sudo docker image prune -af || true | |
| echo "After:"; df -h / | tail -1 | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: "24" | |
| cache: "npm" | |
| cache-dependency-path: site/package-lock.json | |
| - name: Install dependencies | |
| run: npm ci --no-fund --no-audit | |
| - name: Type check | |
| run: npm run typecheck | |
| - name: i18n / SEO audits | |
| run: | | |
| npm run audit:i18n | |
| npm run audit:seo | |
| - name: Unit tests | |
| run: npm test | |
| - name: Production build (standalone) | |
| run: npm run build | |
| env: | |
| NEXT_PUBLIC_SITE_URL: https://toolify365.com | |
| NEXT_PUBLIC_ORG_NAME: Toolify | |
| NEXT_PUBLIC_CONTACT_EMAIL: app.develop.sk@gmail.com | |
| NEXT_PUBLIC_ADSENSE_CLIENT: ca-pub-4927026308242118 | |
| NEXT_PUBLIC_FC_ID: "" | |
| NEXT_PUBLIC_BING_SITE_VERIFICATION: ${{ secrets.BING_SITE_VERIFICATION }} | |
| NEXT_PUBLIC_YANDEX_SITE_VERIFICATION: ${{ secrets.YANDEX_SITE_VERIFICATION }} | |
| # AdSense ad unit slot IDs (set after AdSense approval; empty = placeholder shown) | |
| NEXT_PUBLIC_ADSENSE_SLOT_BANNER: ${{ secrets.ADSENSE_SLOT_BANNER }} | |
| NEXT_PUBLIC_ADSENSE_SLOT_INARTICLE: ${{ secrets.ADSENSE_SLOT_INARTICLE }} | |
| NEXT_PUBLIC_ADSENSE_SLOT_STICKY: ${{ secrets.ADSENSE_SLOT_STICKY }} | |
| NEXT_PUBLIC_ADSENSE_SLOT_BELOWRESULT: ${{ secrets.ADSENSE_SLOT_BELOWRESULT }} | |
| NEXT_PUBLIC_ADSENSE_SLOT_INFEED: ${{ secrets.ADSENSE_SLOT_INFEED }} | |
| NEXT_PUBLIC_ADSENSE_SLOT_ANCHOR: ${{ secrets.ADSENSE_SLOT_ANCHOR }} | |
| NEXT_PUBLIC_GA_ID: ${{ secrets.GA_ID }} | |
| NEXT_PUBLIC_CLARITY_PROJECT_ID: ${{ secrets.CLARITY_PROJECT_ID }} | |
| # 楽天市場 価格比較ツール (/api/price-search) — サーバーサイドのみ | |
| RAKUTEN_APP_ID: ${{ secrets.RAKUTEN_APP_ID }} | |
| RAKUTEN_ACCESS_KEY: ${{ secrets.RAKUTEN_ACCESS_KEY }} | |
| RAKUTEN_AFFILIATE_ID: ${{ secrets.RAKUTEN_AFFILIATE_ID }} | |
| # 楽天アプリ登録のApplication URLと一致させる | |
| RAKUTEN_ORIGIN_URL: ${{ secrets.RAKUTEN_ORIGIN_URL }} | |
| # Amazon アソシエイト (承認後に設定) | |
| NEXT_PUBLIC_AMAZON_PARTNER_TAG: ${{ secrets.AMAZON_PARTNER_TAG }} | |
| - name: Set up SSH | |
| run: | | |
| mkdir -p ~/.ssh | |
| echo "${{ secrets.DEPLOY_SSH_KEY }}" > ~/.ssh/deploy_key | |
| chmod 600 ~/.ssh/deploy_key | |
| ssh-keyscan -H "$DEPLOY_HOST" >> ~/.ssh/known_hosts 2>/dev/null | |
| - name: Write runtime env file for VPS | |
| env: | |
| RAKUTEN_APP_ID: ${{ secrets.RAKUTEN_APP_ID }} | |
| RAKUTEN_ACCESS_KEY: ${{ secrets.RAKUTEN_ACCESS_KEY }} | |
| RAKUTEN_AFFILIATE_ID: ${{ secrets.RAKUTEN_AFFILIATE_ID }} | |
| RAKUTEN_ORIGIN_URL: ${{ secrets.RAKUTEN_ORIGIN_URL }} | |
| run: | | |
| printf 'RAKUTEN_APP_ID=%s\nRAKUTEN_ACCESS_KEY=%s\nRAKUTEN_AFFILIATE_ID=%s\nRAKUTEN_ORIGIN_URL=%s\n' \ | |
| "$RAKUTEN_APP_ID" "$RAKUTEN_ACCESS_KEY" "$RAKUTEN_AFFILIATE_ID" "$RAKUTEN_ORIGIN_URL" \ | |
| > .next/standalone/.env.local | |
| - name: rsync standalone artifacts to VPS | |
| run: | | |
| rsync -az --delete \ | |
| -e "ssh -i ~/.ssh/deploy_key -o StrictHostKeyChecking=accept-new" \ | |
| .next/standalone/ "$DEPLOY_USER@$DEPLOY_HOST:$DEPLOY_PATH/" | |
| rsync -az --delete \ | |
| -e "ssh -i ~/.ssh/deploy_key -o StrictHostKeyChecking=accept-new" \ | |
| .next/static/ "$DEPLOY_USER@$DEPLOY_HOST:$DEPLOY_PATH/.next/static/" | |
| rsync -az --delete \ | |
| -e "ssh -i ~/.ssh/deploy_key -o StrictHostKeyChecking=accept-new" \ | |
| public/ "$DEPLOY_USER@$DEPLOY_HOST:$DEPLOY_PATH/public/" | |
| - name: Clear ISR / full-route cache on VPS | |
| run: | | |
| ssh -i ~/.ssh/deploy_key -o StrictHostKeyChecking=accept-new \ | |
| "$DEPLOY_USER@$DEPLOY_HOST" \ | |
| "rm -rf \"$DEPLOY_PATH/.next/cache/\" && echo 'cache cleared'" | |
| - name: Reload toolify on VPS via PM2 (zero-downtime) | |
| run: | | |
| ssh -i ~/.ssh/deploy_key -o StrictHostKeyChecking=accept-new \ | |
| "$DEPLOY_USER@$DEPLOY_HOST" \ | |
| "pm2 reload toolify --update-env && pm2 save" | |
| - name: Smoke test live URL | |
| run: | | |
| set -e | |
| for i in 1 2 3 4 5; do | |
| if curl -sSfL -o /dev/null -w "%{http_code}" --max-time 15 https://toolify365.com/en/tools/bmi-calculator | grep -q 200 && curl -sSf --max-time 10 https://toolify365.com/feed-ja.xml | grep -q "<rss"; then | |
| echo "✓ Live URL returning 200" | |
| exit 0 | |
| fi | |
| echo "Attempt $i failed, retrying in 5s..." | |
| sleep 5 | |
| done | |
| echo "✗ Live URL not returning 200 after 5 attempts" | |
| exit 1 | |
| # Pinterest RSS自動投稿の回帰ガード(2026-07-21の実障害): | |
| # feedのenclosure画像が全item同一になると、Pinterestの画像ハッシュ重複排除で | |
| # ピン化が2枚で無言停止する(/api/og がクエリを読まなくなる等の回帰で再発しうる)。 | |
| # 先頭3itemの画像を実取得しMD5が2種以上あることを検証。失敗=workflow赤で | |
| # 朝のCI監視ルーチンが検知する。 | |
| - name: Guard feed pin images are unique (Pinterest dedupe) | |
| run: | | |
| set -e | |
| urls=$(curl -sSf --max-time 15 https://toolify365.com/feed.xml \ | |
| | grep -o 'enclosure url="[^"]*"' | sed 's/enclosure url="//;s/"$//;s/\&/\&/g' | head -3) | |
| [ -n "$urls" ] || { echo "✗ feedにenclosureが無い"; exit 1; } | |
| md5s="" | |
| while IFS= read -r u; do | |
| curl -sSf --max-time 30 "$u" -o /tmp/pin.png | |
| bytes=$(wc -c < /tmp/pin.png | tr -d ' ') | |
| h=$(md5sum /tmp/pin.png | cut -d' ' -f1) | |
| echo " $h ${bytes}B $u" | |
| # 2026-08-14の実障害: /api/og が edge ランタイムでフォントを読めず、 | |
| # 全リクエストが 70B の 1×1 透明PNG フォールバックに落ちていた。 | |
| # 「200が返る」だけでは検知できないのでバイト数で門番する。 | |
| if [ "$bytes" -lt 20000 ]; then | |
| echo "✗ ピン画像が ${bytes}B しかない → /api/og が空白PNGフォールバックに落ちている" | |
| exit 1 | |
| fi | |
| md5s="$md5s$h\n" | |
| done <<< "$urls" | |
| uniq_count=$(printf "%b" "$md5s" | sort -u | grep -c .) | |
| if [ "$uniq_count" -lt 2 ]; then | |
| echo "✗ feedのピン画像が全て同一(MD5 1種) → Pinterest重複排除でRSS投稿が止まる回帰" | |
| exit 1 | |
| fi | |
| echo "✓ ピン画像 unique ($uniq_count 種/3件)" | |
| - name: Cleanup SSH key | |
| if: always() | |
| run: rm -f ~/.ssh/deploy_key |