Merge pull request #107 from fulll/release/1.8.3 #4
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: Docs | |
| # Triggers: | |
| # - push to main touching docs/** or this workflow → deploy latest to GitHub Pages | |
| # - push of a major release tag (v2.0.0, v3.0.0 …) → versioned snapshot | |
| # - workflow_dispatch → manual deploy of latest | |
| on: | |
| push: | |
| branches: [main] | |
| paths: | |
| - "docs/**" | |
| - ".github/workflows/docs.yaml" | |
| tags: | |
| # Glob pattern (not regex): matches v1.0.0, v2.0.0, v10.0.0 … | |
| - "v[0-9]*.0.0" | |
| workflow_dispatch: | |
| # Deployment strategy: GitHub Actions Pages source (actions/deploy-pages — official, no third party). | |
| # • gh-pages branch = versioned snapshot STORAGE only (not served directly by Pages) | |
| # • Every deploy job assembles a combined artifact: | |
| # - latest docs built from main at the artifact root | |
| # - each vX/ snapshot from the gh-pages branch merged in | |
| # → single artifact deployed via actions/deploy-pages | |
| # • Snapshot job stores the built snapshot in gh-pages branch via plain git, | |
| # then pushes the updated versions.json to main (no [skip ci]) which | |
| # re-triggers the deploy job to include the new snapshot immediately. | |
| # Requires: Settings → Pages → Source: GitHub Actions. | |
| permissions: | |
| contents: write | |
| pages: write | |
| id-token: write | |
| # Only one concurrent deployment for the same ref; cancel outdated runs. | |
| concurrency: | |
| group: docs-${{ github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| # ── Deploy (latest) ───────────────────────────────────────────────────────── | |
| # Assembles a combined Pages artifact: | |
| # 1. Builds the latest docs (base: /github-code-search/) | |
| # 2. Merges existing versioned snapshots from the gh-pages storage branch | |
| # into the artifact (docs/.vitepress/dist/vX/ for each stored version) | |
| # 3. Uploads the artifact and deploys via actions/deploy-pages | |
| # Requires: Settings → Pages → Source: GitHub Actions. | |
| deploy: | |
| name: Build and deploy docs | |
| if: github.ref == 'refs/heads/main' || github.event_name == 'workflow_dispatch' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| pages: write | |
| id-token: write | |
| environment: | |
| name: github-pages | |
| url: ${{ steps.deploy.outputs.page_url }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| # Needed to fetch the gh-pages storage branch for versioned snapshots. | |
| fetch-depth: 0 | |
| - name: Setup Bun | |
| uses: oven-sh/setup-bun@ecf28ddc73e819eb6fa29df6b34ef8921c743461 # v2.1.3 | |
| with: | |
| bun-version: latest | |
| - name: Install dependencies | |
| run: bun install --frozen-lockfile | |
| - name: Build latest docs | |
| run: bun run docs:build | |
| # Base URL: /github-code-search/ (default in config.mts) | |
| - name: Merge versioned snapshots from gh-pages storage | |
| run: | | |
| set -euo pipefail | |
| if git ls-remote --heads origin gh-pages | grep -q gh-pages; then | |
| git fetch origin gh-pages | |
| # List only top-level entries that match v<integer> (e.g. v1, v2). | |
| # Using a pathspec glob and a strict regex avoids iterating over | |
| # unrelated files (.nojekyll, README, etc.) at the branch root. | |
| for entry in $(git ls-tree --name-only origin/gh-pages -- 'v[0-9]*'); do | |
| if echo "$entry" | grep -qE '^v[0-9]+$'; then | |
| echo "Merging snapshot: $entry" | |
| mkdir -p "docs/.vitepress/dist/$entry" | |
| git archive origin/gh-pages "$entry" | tar -x -C docs/.vitepress/dist/ | |
| # Validate: warn and remove if the extracted directory is empty. | |
| if [ -z "$(ls -A "docs/.vitepress/dist/$entry" 2>/dev/null)" ]; then | |
| echo "Warning: snapshot '$entry' is empty after extraction — removing." | |
| rmdir "docs/.vitepress/dist/$entry" | |
| fi | |
| fi | |
| done | |
| else | |
| echo "No gh-pages branch yet — skipping snapshot merge" | |
| fi | |
| - name: Upload Pages artifact | |
| uses: actions/upload-pages-artifact@56afc609e74202658d3ffba0e8f6dda462b719fa # v3.0.1 | |
| with: | |
| path: docs/.vitepress/dist | |
| - name: Deploy to GitHub Pages | |
| id: deploy | |
| uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4.0.5 | |
| # ── Snapshot (versioned) ──────────────────────────────────────────────────── | |
| # Triggered by a major release tag (e.g. v2.0.0). | |
| # 1. Builds docs with a versioned base URL (/github-code-search/v2/). | |
| # 2. Stores the output in the gh-pages branch under /v2/ using plain git | |
| # (no third-party action). The gh-pages branch is storage only — Pages | |
| # still points to GitHub Actions; the deploy job merges snapshots in. | |
| # 3. Prepends the entry to versions.json on main WITHOUT [skip ci], which | |
| # re-triggers the deploy job so the new snapshot is live immediately. | |
| # | |
| # Convention: only tags matching vX.0.0 (major bumps) trigger a snapshot. | |
| # Patch and minor releases update the main docs in-place via the deploy job. | |
| snapshot: | |
| name: Snapshot versioned docs | |
| if: startsWith(github.ref, 'refs/tags/') | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| # Full history needed to push to gh-pages and commit versions.json to main. | |
| fetch-depth: 0 | |
| - name: Extract major version from tag | |
| id: ver | |
| run: | | |
| # Validate that the tag strictly matches vX.0.0 before proceeding. | |
| # The workflow trigger filter (v[0-9]*.0.0) is the primary guard, but | |
| # this ensures the script fails fast if triggered with an unexpected ref. | |
| if ! echo "$GITHUB_REF_NAME" | grep -Eq '^v[0-9]+\.0\.0$'; then | |
| echo "Error: '$GITHUB_REF_NAME' does not match expected pattern vX.0.0" >&2 | |
| exit 1 | |
| fi | |
| MAJOR="${GITHUB_REF_NAME%%.*}" # e.g. v2 from v2.0.0 | |
| echo "major=$MAJOR" >> "$GITHUB_OUTPUT" | |
| - name: Setup Bun | |
| uses: oven-sh/setup-bun@ecf28ddc73e819eb6fa29df6b34ef8921c743461 # v2.1.3 | |
| with: | |
| bun-version: latest | |
| - name: Install dependencies | |
| run: bun install --frozen-lockfile | |
| - name: Build versioned snapshot | |
| env: | |
| # config.mts reads VITEPRESS_BASE when set; falls back to /github-code-search/ | |
| VITEPRESS_BASE: /github-code-search/${{ steps.ver.outputs.major }}/ | |
| run: bun run docs:build | |
| - name: Store snapshot in gh-pages branch | |
| run: | | |
| set -euo pipefail | |
| MAJOR="${{ steps.ver.outputs.major }}" | |
| git config user.name "github-actions[bot]" | |
| git config user.email "41898282+github-actions[bot]@users.noreply.github.com" | |
| # Set up the gh-pages worktree (create orphan branch if it doesn't exist yet). | |
| if git ls-remote --heads origin gh-pages | grep -q gh-pages; then | |
| git fetch origin gh-pages | |
| git worktree add /tmp/gh-pages-storage origin/gh-pages | |
| else | |
| git worktree add --orphan -b gh-pages /tmp/gh-pages-storage | |
| touch /tmp/gh-pages-storage/.nojekyll # prevent Jekyll processing | |
| fi | |
| # Copy the built snapshot into its versioned directory. | |
| rm -rf "/tmp/gh-pages-storage/$MAJOR" | |
| cp -r docs/.vitepress/dist "/tmp/gh-pages-storage/$MAJOR" | |
| # Commit and push. | |
| cd /tmp/gh-pages-storage | |
| git add . | |
| git diff --staged --quiet || git commit -m "docs: store snapshot $MAJOR [skip ci]" | |
| git push origin gh-pages | |
| # Explicit cleanup — belt-and-suspenders even on ephemeral runners. | |
| git worktree remove /tmp/gh-pages-storage | |
| - name: Prepend new version entry to versions.json | |
| run: | | |
| MAJOR="${{ steps.ver.outputs.major }}" | |
| LINK="/${MAJOR}/" | |
| # Idempotent — skip if the entry already exists. | |
| jq --arg text "$MAJOR" --arg link "$LINK" \ | |
| 'if any(.[]; .link == $link) then . else [{"text": $text, "link": $link}] + . end' \ | |
| docs/public/versions.json > /tmp/versions_new.json | |
| mv /tmp/versions_new.json docs/public/versions.json | |
| - name: Generate blog post stub for new major version | |
| run: | | |
| set -euo pipefail | |
| # Convert vX.0.0 → v<maj>-0-0 for the file name (e.g. v3.0.0 → v3-0-0) | |
| TAG="$GITHUB_REF_NAME" | |
| SLUG="${TAG//./-}" # v3.0.0 → v3-0-0 | |
| BLOG_FILE="docs/blog/release-${SLUG}.md" | |
| RELEASE_DATE="$(date -u +%Y-%m-%d)" | |
| # Idempotent — skip creation if the file already exists (manually authored). | |
| if [ -f "$BLOG_FILE" ]; then | |
| echo "Blog post $BLOG_FILE already exists — skipping stub generation." | |
| else | |
| # Fix: use Python to write the file so heredoc indentation never | |
| # leaks into the generated Markdown (which would break frontmatter). | |
| python3 - <<PY | |
| import pathlib | |
| tag = "$TAG" | |
| release_date = "$RELEASE_DATE" | |
| blog_file = "$BLOG_FILE" | |
| content = ( | |
| f'---\ntitle: "What\'s new in {tag}"\n' | |
| f'description: "Highlights of github-code-search {tag}"\n' | |
| f'date: {release_date}\n---\n\n' | |
| f'# What\'s new in github-code-search {tag}\n\n' | |
| f'> Full release notes: <https://github.com/fulll/github-code-search/releases/tag/{tag}>\n\n' | |
| '<!-- TODO: fill in feature highlights, usage examples and screenshots. -->\n' | |
| ) | |
| pathlib.Path(blog_file).write_text(content, encoding="utf-8") | |
| print(f"Created blog stub: {blog_file}") | |
| PY | |
| fi | |
| - name: Update blog/index.md table with new major version | |
| run: | | |
| set -euo pipefail | |
| TAG="$GITHUB_REF_NAME" | |
| SLUG="${TAG//./-}" | |
| # Add a row to the blog index table only if the version isn't already listed. | |
| if grep -qF "release-${SLUG}" docs/blog/index.md; then | |
| echo "Blog index already contains ${TAG} — skipping." | |
| else | |
| python3 - <<PY | |
| import re, pathlib | |
| path = pathlib.Path("docs/blog/index.md") | |
| content = path.read_text() | |
| tag = "$TAG" | |
| slug = "$SLUG" | |
| new_row = f"| [{tag}](./release-{slug}) | <!-- TODO: add summary --> |" | |
| # Insert the new row after the last markdown table row (line ending with |). | |
| updated = re.sub( | |
| r"(\|[^\n]+\|)(\s*\Z)", | |
| lambda m: m.group(1) + "\n" + new_row + m.group(2), | |
| content, | |
| count=1, | |
| flags=re.DOTALL, | |
| ) | |
| path.write_text(updated) | |
| print(f"Inserted row for {tag} into blog/index.md") | |
| PY | |
| fi | |
| - name: Commit blog stub and versions.json to main | |
| # Pin to exact commit SHA to prevent supply-chain attacks. | |
| uses: stefanzweifel/git-auto-commit-action@8621497c8c39c72f3e2a999a26b4ca1b5058a842 # v5.0.1 | |
| with: | |
| # No [skip ci] — the push to main matches paths: docs/** and re-triggers | |
| # the deploy job, which merges the new snapshot into the Pages artifact. | |
| commit_message: "docs: add ${{ steps.ver.outputs.major }} to versions.json and blog" | |
| file_pattern: docs/public/versions.json docs/blog/ | |
| branch: main |