4848 if : github.ref == 'refs/heads/main' || github.event_name == 'workflow_dispatch'
4949 runs-on : ubuntu-latest
5050 permissions :
51+ contents : read
5152 pages : write
5253 id-token : write
5354 environment :
@@ -56,13 +57,13 @@ jobs:
5657
5758 steps :
5859 - name : Checkout
59- uses : actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
60+ uses : actions/checkout@v4.2.2
6061 with :
6162 # Needed to fetch the gh-pages storage branch for versioned snapshots.
6263 fetch-depth : 0
6364
6465 - name : Setup Bun
65- uses : oven-sh/setup-bun@3d267786b128fe76c2f16a390aa2448b815359f3 # v2.1.2
66+ uses : oven-sh/setup-bun@v2.1.2
6667 with :
6768 bun-version : latest
6869
@@ -75,28 +76,36 @@ jobs:
7576
7677 - name : Merge versioned snapshots from gh-pages storage
7778 run : |
79+ set -euo pipefail
7880 if git ls-remote --heads origin gh-pages | grep -q gh-pages; then
7981 git fetch origin gh-pages
80- # Copy each vX/ directory from the storage branch into the artifact root.
81- for entry in $(git ls-tree --name-only origin/gh-pages); do
82+ # List only top-level entries that match v<integer> (e.g. v1, v2).
83+ # Using a pathspec glob and a strict regex avoids iterating over
84+ # unrelated files (.nojekyll, README, etc.) at the branch root.
85+ for entry in $(git ls-tree --name-only origin/gh-pages -- 'v[0-9]*'); do
8286 if echo "$entry" | grep -qE '^v[0-9]+$'; then
8387 echo "Merging snapshot: $entry"
8488 mkdir -p "docs/.vitepress/dist/$entry"
8589 git archive origin/gh-pages "$entry" | tar -x -C docs/.vitepress/dist/
90+ # Validate: warn and remove if the extracted directory is empty.
91+ if [ -z "$(ls -A "docs/.vitepress/dist/$entry" 2>/dev/null)" ]; then
92+ echo "Warning: snapshot '$entry' is empty after extraction — removing."
93+ rmdir "docs/.vitepress/dist/$entry"
94+ fi
8695 fi
8796 done
8897 else
8998 echo "No gh-pages branch yet — skipping snapshot merge"
9099 fi
91100
92101 - name : Upload Pages artifact
93- uses : actions/upload-pages-artifact@56afc609e74202658d3ffba0e8f6dda462b719fa # v3.0.1
102+ uses : actions/upload-pages-artifact@v3.0.1
94103 with :
95104 path : docs/.vitepress/dist
96105
97106 - name : Deploy to GitHub Pages
98107 id : deploy
99- uses : actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4.0.5
108+ uses : actions/deploy-pages@v4.0.5
100109
101110 # ── Snapshot (versioned) ────────────────────────────────────────────────────
102111 # Triggered by a major release tag (e.g. v2.0.0).
@@ -118,19 +127,26 @@ jobs:
118127
119128 steps :
120129 - name : Checkout
121- uses : actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
130+ uses : actions/checkout@v4.2.2
122131 with :
123132 # Full history needed to push to gh-pages and commit versions.json to main.
124133 fetch-depth : 0
125134
126135 - name : Extract major version from tag
127136 id : ver
128137 run : |
129- MAJOR="$(echo "$GITHUB_REF_NAME" | cut -d. -f1)" # e.g. v2
138+ # Validate that the tag strictly matches vX.0.0 before proceeding.
139+ # The workflow trigger filter (v[0-9]*.0.0) is the primary guard, but
140+ # this ensures the script fails fast if triggered with an unexpected ref.
141+ if ! echo "$GITHUB_REF_NAME" | grep -Eq '^v[0-9]+\.0\.0$'; then
142+ echo "Error: '$GITHUB_REF_NAME' does not match expected pattern vX.0.0" >&2
143+ exit 1
144+ fi
145+ MAJOR="${GITHUB_REF_NAME%%.*}" # e.g. v2 from v2.0.0
130146 echo "major=$MAJOR" >> "$GITHUB_OUTPUT"
131147
132148 - name : Setup Bun
133- uses : oven-sh/setup-bun@3d267786b128fe76c2f16a390aa2448b815359f3 # v2.1.2
149+ uses : oven-sh/setup-bun@v2.1.2
134150 with :
135151 bun-version : latest
136152
@@ -145,6 +161,7 @@ jobs:
145161
146162 - name : Store snapshot in gh-pages branch
147163 run : |
164+ set -euo pipefail
148165 MAJOR="${{ steps.ver.outputs.major }}"
149166 git config user.name "github-actions[bot]"
150167 git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
@@ -164,6 +181,8 @@ jobs:
164181 git add .
165182 git diff --staged --quiet || git commit -m "docs: store snapshot $MAJOR [skip ci]"
166183 git push origin gh-pages
184+ # Explicit cleanup — belt-and-suspenders even on ephemeral runners.
185+ git worktree remove /tmp/gh-pages-storage
167186
168187 - name : Prepend new version entry to versions.json
169188 run : |
@@ -176,7 +195,7 @@ jobs:
176195 mv /tmp/versions_new.json docs/public/versions.json
177196
178197 - name : Commit versions.json to main
179- uses : stefanzweifel/git-auto-commit-action@8621497c8c39c72f3e2a999a26b4ca1b5590082e # v5.0.1
198+ uses : stefanzweifel/git-auto-commit-action@v5.0.1
180199 with :
181200 # No [skip ci] — the push to main matches paths: docs/** and re-triggers
182201 # the deploy job, which merges the new snapshot into the Pages artifact.
0 commit comments