Repository navigation
internal(ci): Build the docs site only when its inputs change #4141
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
+300
−3
Merged
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
126c824
internal(ci): Build the docs site only when its inputs change
cursoragent acf34c6
internal(ci): Simplify Vercel ignore script
claude 79deecd
Merge remote-tracking branch 'origin/master' into claude/project-thre…
claude d42025a
internal(ci): Build previews when no merge-base is available
claude c6afab0
internal(ci): Count merged feature branches and fail open on production
claude 1948ec1
Merge remote-tracking branch 'origin/master' into claude/project-thre…
claude f195e8a
internal(ci): Count merge resolutions and deepen for long PRs
claude 52f32e3
internal(ci): Tidy vercel-ignore helpers and rule
claude 56fec30
internal(ci): Trim ci-config rule for the Vercel ignore step
claude File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
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
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,100 @@ | ||
| #!/usr/bin/env bash | ||
| # Decide whether Vercel should build the docs site. | ||
| # | ||
| # Exit 0 skips the build. Exit 1 builds. Vercel treats every non-zero status | ||
| # as "build", so this script exits 0 only when it can see that the published | ||
| # site is unchanged. If git history is missing, it builds (fail open). | ||
| # | ||
| # Preview branches must not use `git diff HEAD^ HEAD`. Merging master into a | ||
| # pull request makes that diff the incoming master tree, so a site commit | ||
| # already on master starts a full preview build of an unrelated PR. | ||
|
|
||
| set -u | ||
|
|
||
| # The published site: the Docusaurus app plus the doc trees it compiles. | ||
| # Keep in sync with the `paths` of site-preview.yml and site-release.yml. | ||
| SITE_PATHS=( | ||
| website docs/core docs/rest docs/graphql | ||
| ':(exclude)website/CHANGELOG.md' | ||
| ':(exclude,glob)website/**/__tests__/**' | ||
| ':(exclude,glob)website/**/*.test.*' | ||
| ) | ||
|
|
||
| build() { | ||
| echo "vercel-ignore: build — $*" | ||
| exit 1 | ||
| } | ||
|
|
||
| skip() { | ||
| echo "vercel-ignore: skip — $*" | ||
| exit 0 | ||
| } | ||
|
|
||
| [[ "${VERCEL_GIT_COMMIT_REF:-}" == gh-pages* ]] && skip "gh-pages branch" | ||
|
|
||
| cd "$(git rev-parse --show-toplevel)" || build "cannot find repo root" | ||
|
|
||
| # Builds if site paths changed between $1 and $2 (or the diff fails); else skips. | ||
| decide() { | ||
| local files | ||
| files="$(git diff --name-only --no-renames "$1" "$2" -- "${SITE_PATHS[@]}" 2>/dev/null)" || | ||
| build "could not diff $1..$2" | ||
| [ -n "$files" ] || skip "$3" | ||
| build "$3: ${files//$'\n'/, }" | ||
| } | ||
|
|
||
| has_rev() { | ||
| git rev-parse --verify -q "$1" >/dev/null | ||
| } | ||
|
|
||
| is_ancestor() { | ||
| [ -n "$1" ] && git merge-base --is-ancestor "$1" "$2" 2>/dev/null | ||
| } | ||
|
|
||
| # Prints master's sha. Vercel clones shallow, so fetch it if missing; the | ||
| # timeout keeps a hung network call from holding the build machine. | ||
| upstream() { | ||
| has_rev origin/master || | ||
| timeout 15 git fetch -q --no-tags --depth=80 origin master:refs/remotes/origin/master 2>/dev/null | ||
| git rev-parse --verify -q origin/master || git rev-parse --verify -q master | ||
| } | ||
|
|
||
| # Vercel clones about 10 commits deep. Fetch more of this branch and master | ||
| # when a comparison base is out of reach. | ||
| deepen() { | ||
| [ -n "${VERCEL_GIT_COMMIT_REF:-}" ] && | ||
| timeout 30 git fetch -q --no-tags --deepen=300 origin "$VERCEL_GIT_COMMIT_REF" \ | ||
| '+refs/heads/master:refs/remotes/origin/master' 2>/dev/null | ||
| } | ||
|
|
||
| merge_base() { | ||
| master="$(upstream)" && git merge-base HEAD "$master" 2>/dev/null | ||
| } | ||
|
|
||
| prev="${VERCEL_GIT_PREVIOUS_SHA:-}" | ||
|
|
||
| # A push can carry several commits (rebase merges), so compare against the | ||
| # last deploy. | ||
| if [[ "${VERCEL_GIT_COMMIT_REF:-}" =~ ^(master|rest-hooks-site)$ || "${VERCEL_ENV:-}" == production ]]; then | ||
| [ -n "$prev" ] && ! has_rev "$prev^{commit}" && deepen | ||
| is_ancestor "$prev" HEAD && decide "$prev" HEAD "production changes since ${prev:0:12}" | ||
| build "no previous production deploy to compare" | ||
| fi | ||
|
|
||
| # Previews compare the branch's changes, not commits merged in from upstream. | ||
| # When the tip merges master, diff against the merged master commit: that | ||
| # counts the branch's site files and any conflict resolutions, not master's. | ||
| # Merges of other branches fall through and count in full. | ||
| if has_rev 'HEAD^2' && master="$(upstream)" && is_ancestor 'HEAD^2' "$master"; then | ||
| decide 'HEAD^2' HEAD "preview changes vs master (merge)" | ||
| fi | ||
|
|
||
| is_ancestor "$prev" HEAD && decide "$prev" HEAD "preview changes since ${prev:0:12}" | ||
|
|
||
| if base="$(merge_base)" || { deepen && base="$(merge_base)"; }; then | ||
| decide "$base" HEAD "preview changes vs master" | ||
| fi | ||
|
|
||
| # Without a base, the tip commit alone can't prove earlier commits left the | ||
| # site unchanged. | ||
| build "no base to compare" | ||
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,183 @@ | ||
| #!/usr/bin/env bash | ||
| # Exercises website/scripts/vercel-ignore.sh against a throwaway repo. | ||
| set -euo pipefail | ||
|
|
||
| root="$(cd "$(dirname "$0")/../.." && pwd)" | ||
| script="$root/website/scripts/vercel-ignore.sh" | ||
| repo="$(mktemp -d)" | ||
| trap 'rm -rf "$repo"' EXIT | ||
|
|
||
| git -C "$repo" init -b master >/dev/null | ||
| git -C "$repo" config user.email "vercel-ignore-test@example.com" | ||
| git -C "$repo" config user.name "vercel-ignore-test" | ||
| git -C "$repo" config commit.gpgsign false | ||
| git -C "$repo" config core.fsmonitor false | ||
| git -C "$repo" config core.untrackedcache false | ||
|
|
||
| commit() { | ||
| local msg="$1" | ||
| shift | ||
| local path | ||
| for path in "$@"; do | ||
| mkdir -p "$repo/$(dirname "$path")" | ||
| printf '%s\n' "$msg" >>"$repo/$path" | ||
| git -C "$repo" add -- "$path" | ||
| done | ||
| git -C "$repo" commit -m "$msg" >/dev/null | ||
| } | ||
|
|
||
| # expect <skip|build> <name> <ref> [previous-sha] [vercel-env] | ||
| expect() { | ||
| local want="$1" name="$2" ref="$3" out rc code=1 | ||
| [ "$want" = skip ] && code=0 | ||
| set +e | ||
| out="$( | ||
| cd "$repo" && | ||
| VERCEL_GIT_COMMIT_REF="$ref" VERCEL_GIT_PREVIOUS_SHA="${4-}" VERCEL_ENV="${5-}" \ | ||
| bash "$script" 2>&1 | ||
| )" | ||
| rc=$? | ||
| set -e | ||
| if [ "$rc" -ne "$code" ]; then | ||
| printf 'FAIL %s: expected %s (%s), got %s\n%s\n' "$name" "$want" "$code" "$rc" "$out" >&2 | ||
| exit 1 | ||
| fi | ||
| printf 'ok %s\n' "$name" | ||
| } | ||
|
|
||
| # Previous deploy for a single-commit push. | ||
| parent() { git -C "$repo" rev-parse HEAD^; } | ||
|
|
||
| commit "init" README.md | ||
|
|
||
| # --- production (master): one squash commit --- | ||
| commit "pkg" packages/core/src/index.ts | ||
| expect skip "master package-only" master "$(parent)" | ||
|
|
||
| commit "docs page" docs/core/api/Controller.md | ||
| expect build "master docs/core" master "$(parent)" | ||
|
|
||
| commit "roadmap" docs/ROADMAP.md | ||
| expect skip "master docs/ROADMAP.md" master "$(parent)" | ||
|
|
||
| commit "prettier" docs/.prettierrc | ||
| expect skip "master docs/.prettierrc" master "$(parent)" | ||
|
|
||
| commit "blog" website/blog/2026-10-04-note.md | ||
| expect build "master website blog" master "$(parent)" | ||
|
|
||
| commit "changelog" website/CHANGELOG.md | ||
| expect skip "master website changelog" master "$(parent)" | ||
|
|
||
| commit "unit test" website/src/components/Playground/__tests__/transformCode.test.ts website/src/components/Playground/__tests__/fixture.json | ||
| expect skip "master website unit test" master "$(parent)" | ||
|
|
||
| commit "colocated test" website/src/components/Playground/transformCode.test.ts website/scripts/vercel-ignore.test.sh | ||
| expect skip "master colocated website tests" master "$(parent)" | ||
|
|
||
| commit "ci" .circleci/config.yml .github/workflows/benchmark.yml | ||
| expect skip "master CI-only" master "$(parent)" | ||
|
|
||
| # Last successful production deploy was before a package commit. Still skip. | ||
| pkg_sha="$(git -C "$repo" rev-parse HEAD)" | ||
| commit "another pkg" packages/rest/src/index.ts | ||
| expect skip "master package since previous deploy" master "$pkg_sha" | ||
|
|
||
| # --- preview branch, linear --- | ||
| git -C "$repo" checkout -b feature >/dev/null 2>&1 | ||
| commit "feature pkg" packages/vue/src/index.ts .changeset/preview.md | ||
| expect skip "preview package and changeset" feature | ||
|
|
||
| commit "feature roadmap" docs/ROADMAP.md | ||
| expect skip "preview docs that are not published" feature | ||
|
|
||
| commit "feature page" docs/rest/api/Entity.md | ||
| expect build "preview docs/rest" feature | ||
|
|
||
| commit "graphql page" docs/graphql/api/GQLEndpoint.md | ||
| expect build "preview docs/graphql since last deploy" feature "$(git -C "$repo" rev-parse HEAD^)" | ||
|
|
||
| commit "feature ci follow-up" .github/workflows/site-preview.yml | ||
| # Whole branch still contains docs/rest, and there is no previous deploy, | ||
| # so the site change must still build. | ||
| expect build "preview follow-up after unpublished site change" feature | ||
|
|
||
| page_sha="$(git -C "$repo" rev-parse HEAD)" | ||
| commit "feature pkg follow-up" packages/core/src/other.ts | ||
| expect skip "preview package follow-up after site deploy" feature "$page_sha" | ||
|
|
||
| commit "playground source" website/src/components/Playground/transformCode.ts | ||
| expect build "preview playground source since last deploy" feature "$page_sha" | ||
|
|
||
| # Multi-commit branch with no prior deploy: the site edit is not the tip. | ||
| git -C "$repo" checkout -b stacked master >/dev/null 2>&1 | ||
| commit "stacked site" website/src/pages/index.js | ||
| commit "stacked pkg" packages/endpoint/src/index.ts | ||
| expect build "preview multi-commit site change not at tip" stacked | ||
|
|
||
| # --- merge master into a package PR (the credit-burn case) --- | ||
| git -C "$repo" checkout master >/dev/null 2>&1 | ||
| commit "master site moves" website/src/pages/index.js docs/core/concepts/overview.md | ||
| master_sha="$(git -C "$repo" rev-parse HEAD)" | ||
|
|
||
| # Branch from before that commit, so master is ahead when the PR merges it. | ||
| git -C "$repo" checkout -b pkg-pr "$master_sha^" >/dev/null 2>&1 | ||
| commit "pr packages" packages/core/src/set.ts .circleci/config.yml | ||
| git -C "$repo" merge --no-edit "$master_sha" >/dev/null | ||
| expect skip "preview merge of master into package PR" pkg-pr | ||
|
|
||
| # Site PR already deployed, then merges master: rebuild so the preview | ||
| # reflects the merged result. | ||
| git -C "$repo" checkout -b site-pr "$master_sha^" >/dev/null 2>&1 | ||
| commit "pr website" website/docusaurus.config.ts | ||
| deployed="$(git -C "$repo" rev-parse HEAD)" | ||
| git -C "$repo" merge --no-edit "$master_sha" >/dev/null | ||
| expect build "preview merge of master into a deployed site PR" site-pr "$deployed" | ||
|
|
||
| # A conflict resolution in the merge commit changes the site itself. | ||
| git -C "$repo" checkout -b conflict-pr "$master_sha^" >/dev/null 2>&1 | ||
| commit "pr index" website/src/pages/index.js | ||
| conflict_deployed="$(git -C "$repo" rev-parse HEAD)" | ||
| git -C "$repo" merge --no-edit "$master_sha" >/dev/null 2>&1 || true | ||
| printf 'resolved\n' >"$repo/website/src/pages/index.js" | ||
| git -C "$repo" commit -qam "merge master" >/dev/null | ||
| expect build "preview merge with a site conflict resolution" conflict-pr "$conflict_deployed" | ||
|
|
||
| # Site PR that has never deployed, merged with master: still build. | ||
| git -C "$repo" checkout -B site-pr-fresh "$deployed" >/dev/null 2>&1 | ||
| git -C "$repo" merge --no-edit "$master_sha" >/dev/null | ||
| expect build "preview merge of a site PR with no prior deploy" site-pr-fresh | ||
|
|
||
| # Merging another feature branch (not upstream) brings its site changes in. | ||
| git -C "$repo" checkout -b feat-docs "$deployed" >/dev/null 2>&1 | ||
| commit "stacked docs" docs/core/api/Stacked.md | ||
| git -C "$repo" checkout -b feat "$deployed" >/dev/null 2>&1 | ||
| commit "feat pkg" packages/core/src/feat.ts | ||
| git -C "$repo" merge --no-edit feat-docs >/dev/null | ||
| expect build "preview merge of a non-upstream branch with site changes" feat "$deployed" | ||
|
|
||
| # No merge-base with master (e.g. shallow history): the tip alone can't prove | ||
| # the site is unchanged, so build. | ||
| git -C "$repo" checkout --orphan unrelated >/dev/null 2>&1 | ||
| commit "unrelated site" website/src/pages/index.js | ||
| commit "unrelated pkg" packages/core/src/index.ts | ||
| expect build "preview without merge-base builds" unrelated | ||
|
|
||
| # gh-pages branches never build, even if website files differ. | ||
| expect skip "gh-pages branch" gh-pages-bench | ||
|
|
||
| # Last production deploy is outside the clone (shallow history): the tip | ||
| # alone can't prove earlier commits in the push left the site unchanged. | ||
| git -C "$repo" checkout master >/dev/null 2>&1 | ||
| commit "rebase-merged docs" docs/rest/api/Rebased.md | ||
| commit "rebase-merged pkg" packages/rest/src/rebased.ts | ||
| expect build "master with unreachable previous deploy" master 0123456789abcdef0123456789abcdef01234567 | ||
|
|
||
| # VERCEL_ENV=production uses this branch's history, not the diff against master. | ||
| # A site commit still builds; a later package-only commit does not. | ||
| commit "prod site" website/src/pages/index.js | ||
| expect build "production env site tip" other-branch "$(parent)" production | ||
| commit "prod pkg" packages/normalizr/src/index.ts | ||
| expect skip "production env package tip" other-branch "$(parent)" production | ||
|
|
||
| echo "all vercel-ignore cases passed" |
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,4 @@ | ||
| { | ||
| "cleanUrls": true, | ||
| "ignoreCommand": "case \"$VERCEL_GIT_COMMIT_REF\" in gh-pages*) exit 0;; esac; if [ \"$VERCEL_GIT_COMMIT_REF\" = \"master\" ]; then git diff HEAD^ HEAD --quiet -- . ../docs/; else git diff HEAD^ HEAD --quiet -- .; fi" | ||
| "ignoreCommand": "bash scripts/vercel-ignore.sh" | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.