chore(seo): redirects 单跳 + root metadata hreflang,i18n 段化收尾 #766
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: Build Check | |
| permissions: | |
| contents: read | |
| actions: write | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| workflow_dispatch: | |
| jobs: | |
| build: | |
| runs-on: ubuntu-latest | |
| env: | |
| NEXT_TELEMETRY_DISABLED: "1" | |
| CI: "true" | |
| steps: | |
| - uses: actions/checkout@v4 | |
| # Enable corepack to ensure the exact pnpm version from package.json is used | |
| - name: Enable Corepack | |
| run: corepack enable | |
| - uses: pnpm/action-setup@v4 | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: 22 | |
| cache: pnpm | |
| # Verify pnpm version matches package.json packageManager field | |
| - name: Check pnpm version | |
| run: node scripts/check-pnpm-version.mjs | |
| - run: pnpm install --frozen-lockfile | |
| # Verify lockfile wasn't modified by install | |
| - name: Check lockfile consistency | |
| run: | | |
| if ! git diff --exit-code pnpm-lock.yaml; then | |
| echo "❌ Error: pnpm-lock.yaml was modified after install" | |
| echo "This indicates a pnpm version mismatch or corrupted lockfile" | |
| echo "" | |
| echo "Expected pnpm version from package.json:" | |
| # Use multiple fallback methods to extract version | |
| grep '"packageManager"' package.json | grep -o 'pnpm@[^"]*' || \ | |
| node -e "try { console.log(require('./package.json').packageManager || 'not specified') } catch(e) { console.log('Could not read') }" || \ | |
| echo "Could not extract version" | |
| echo "" | |
| echo "Actual pnpm version:" | |
| pnpm --version || echo "pnpm not found" | |
| exit 1 | |
| fi | |
| echo "✅ Lockfile is consistent" | |
| - run: pnpm run lint | |
| - run: pnpm run lint:images | |
| - run: pnpm run typecheck | |
| # === IndexNow 提交(仅在 main 分支执行) === | |
| - name: Install jq (for JSON build) | |
| if: github.ref == 'refs/heads/main' | |
| run: sudo apt-get update && sudo apt-get install -y jq | |
| - name: Submit IndexNow (changed URLs) | |
| if: github.ref == 'refs/heads/main' | |
| env: | |
| SITE_ORIGIN: https://involutionhell.com | |
| INDEXNOW_API: https://involutionhell.com/api/indexnow | |
| INDEXNOW_API_TOKEN: ${{ secrets.INDEXNOW_API_TOKEN }} | |
| run: | | |
| set -euo pipefail | |
| git fetch --depth=2 origin ${{ github.ref }} || true | |
| CHANGED="$(git diff --name-only HEAD~1 HEAD || true)" | |
| CHANGED_DOCS="$(echo "$CHANGED" | grep -E '^content/docs/.*\.(md|mdx)$' || true)" | |
| # i18n URL 段化(2026-05)后所有 docs URL 都带 /<locale>/ 前缀。 | |
| # 每篇文档对外有 /zh/docs/<slug> 和 /en/docs/<slug> 两个独立 URL, | |
| # 任意文件变更都推送两条让 IndexNow 同时刷新两种语言版本。 | |
| # 文件命名约定(fumadocs dot parser): | |
| # xxx.mdx → 默认 (zh) 原文,slug = xxx | |
| # xxx.en.mdx → en 翻译,slug = xxx(去 .en 后缀提 base slug) | |
| # | |
| # 例外:career/interview-prep/leetcode/ 下含中文的文件名会被 | |
| # lib/source.ts 的 transformer 拼音化(convertSlugToPinyin), | |
| # 实际路由的最后一段是拼音 slug 而不是中文 stem。这里复用 | |
| # generated/leetcode-slug-map.json (prebuild 时由 | |
| # scripts/generate-leetcode-slug-map.mts 与 source.ts 同算法生成) | |
| # 把中文 stem 映射到拼音 slug,否则推送的 URL 会 404。 | |
| LEETCODE_PREFIX="career/interview-prep/leetcode/" | |
| SLUG_MAP_FILE="generated/leetcode-slug-map.json" | |
| URLS=() | |
| while IFS= read -r f; do | |
| [ -z "$f" ] && continue | |
| if [[ "$f" =~ ^content/docs/(.*)\.(md|mdx)$ ]]; then | |
| slug="${BASH_REMATCH[1]}" | |
| # 剥离 locale 后缀(.en / .zh),拿到 canonical base slug | |
| slug="${slug%.en}" | |
| slug="${slug%.zh}" | |
| # index.mdx 对应目录本身的 URL(fumadocs 约定) | |
| slug="${slug%/index}" | |
| # leetcode 中文 stem → 拼音 slug 映射(与 source.ts transformer 一致) | |
| if [[ "$slug" == "$LEETCODE_PREFIX"* && -f "$SLUG_MAP_FILE" ]]; then | |
| stem="${slug##*/}" | |
| dir="${slug%/*}" | |
| mapped="$(jq -r --arg k "$stem" '.[$k] // empty' "$SLUG_MAP_FILE")" | |
| if [ -n "$mapped" ]; then | |
| slug="$dir/$mapped" | |
| fi | |
| fi | |
| URLS+=("$SITE_ORIGIN/zh/docs/$slug") | |
| URLS+=("$SITE_ORIGIN/en/docs/$slug") | |
| fi | |
| done <<< "$CHANGED_DOCS" | |
| mapfile -t URLS < <(printf "%s\n" "${URLS[@]}" | awk 'NF' | sort -u) | |
| if [ "${#URLS[@]}" -eq 0 ]; then | |
| # 没有 docs 改动时(例如改 README / 配置等)仍提交首页让 Bing/Yandex | |
| # 知道站点活跃。i18n 段化后首页有两个 URL,分别推送。 | |
| URLS=("$SITE_ORIGIN/zh" "$SITE_ORIGIN/en") | |
| fi | |
| echo "✅ Submitting URLs to IndexNow:" | |
| printf ' - %s\n' "${URLS[@]}" | |
| JSON="$(jq -n --argjson arr "$(printf '%s\n' "${URLS[@]}" | jq -R . | jq -s .)" '{urlList: $arr}')" | |
| AUTH_HEADER=() | |
| if [ -n "${INDEXNOW_API_TOKEN:-}" ]; then | |
| AUTH_HEADER=(-H "Authorization: Bearer ${INDEXNOW_API_TOKEN}") | |
| fi | |
| echo "📡 Sending request to $INDEXNOW_API ..." | |
| RESPONSE=$(curl -sS -w "\nHTTP_STATUS=%{http_code}" -X POST "$INDEXNOW_API" \ | |
| -H "Content-Type: application/json" \ | |
| "${AUTH_HEADER[@]}" \ | |
| -d "$JSON") | |
| BODY=$(echo "$RESPONSE" | sed -e 's/HTTP_STATUS=.*//g') | |
| STATUS=$(echo "$RESPONSE" | tr -d '\n' | sed -e 's/.*HTTP_STATUS=//') | |
| echo "📥 API Response Status: $STATUS" | |
| echo "📦 Response Body:" | |
| echo "$BODY" | |
| if [ "$STATUS" -ge 400 ]; then | |
| echo "❌ IndexNow submission failed!" | |
| exit 1 | |
| fi | |
| echo "✅ IndexNow submission completed." |