|
| 1 | +#!/usr/bin/env bash |
| 2 | +# |
| 3 | +# Vercel "Ignored Build Step" for @objectstack/docs. |
| 4 | +# |
| 5 | +# Vercel's contract is inverted from the usual one: |
| 6 | +# exit 0 -> IGNORE the build (skip) |
| 7 | +# exit 1 -> RUN the build |
| 8 | +# |
| 9 | +# Why this exists: every push to `main` used to rebuild the docs site. Measured |
| 10 | +# over one week, that was 228 production builds consuming 2835 build-minutes — |
| 11 | +# 98.6% of the whole team's build time — while `objectui` (18s/build) and |
| 12 | +# `hotcrm` (6s/build) queued behind them on a `concurrentBuilds: 1` team. The |
| 13 | +# queue reached 92 deployments, the oldest 34 hours old (#12743). |
| 14 | +# |
| 15 | +# ⚠️ The single most important property of this script is its FAILURE DIRECTION. |
| 16 | +# A wrong "build" costs a few build-minutes. A wrong "skip" silently stops |
| 17 | +# publishing documentation, with no error anywhere — the site just quietly goes |
| 18 | +# stale. Every indeterminate case below therefore exits 1. |
| 19 | +set -uo pipefail |
| 20 | + |
| 21 | +# All paths below are repo-relative, but Vercel runs this from the project's |
| 22 | +# Root Directory (apps/docs), so anchor to the repo root first. |
| 23 | +cd "$(git rev-parse --show-toplevel)" || exit 1 |
| 24 | + |
| 25 | +# `VERCEL_GIT_COMMIT_SHA` is what Vercel is deploying; falling back to HEAD lets |
| 26 | +# this script be exercised locally and in tests. |
| 27 | +HEAD_SHA="${VERCEL_GIT_COMMIT_SHA:-HEAD}" |
| 28 | +PREV_SHA="${VERCEL_GIT_PREVIOUS_SHA:-}" |
| 29 | + |
| 30 | +# 1. Preview deployments never build the docs site. This preserves exactly the |
| 31 | +# behaviour of the dashboard rule this replaces, whose preview half was |
| 32 | +# already correct; only its production half ("always build") was wasteful. |
| 33 | +if [ "${VERCEL_ENV:-}" != "production" ]; then |
| 34 | + echo "skip: VERCEL_ENV=${VERCEL_ENV:-unset} is not production" |
| 35 | + exit 0 |
| 36 | +fi |
| 37 | + |
| 38 | +# 2. No baseline to compare against -> build. Happens on the first deployment |
| 39 | +# after this lands, and any time Vercel cannot name a previous success. |
| 40 | +if [ -z "$PREV_SHA" ]; then |
| 41 | + echo "build: no VERCEL_GIT_PREVIOUS_SHA to compare against" |
| 42 | + exit 1 |
| 43 | +fi |
| 44 | + |
| 45 | +# 3. Vercel builds from a shallow clone, so the previous commit is frequently |
| 46 | +# absent. Try to fetch it; if it stays unreachable, build. |
| 47 | +if ! git cat-file -e "${PREV_SHA}^{commit}" 2>/dev/null; then |
| 48 | + git fetch --depth=100 origin "$PREV_SHA" >/dev/null 2>&1 || true |
| 49 | +fi |
| 50 | +if ! git cat-file -e "${PREV_SHA}^{commit}" 2>/dev/null; then |
| 51 | + echo "build: previous SHA ${PREV_SHA} is not reachable in this clone" |
| 52 | + exit 1 |
| 53 | +fi |
| 54 | + |
| 55 | +# 4. The site's own sources. |
| 56 | +# |
| 57 | +# This check is NOT redundant with turbo-ignore below, and removing it breaks |
| 58 | +# documentation publishing. `turbo --filter=<pkg>...[range]` computes affected |
| 59 | +# packages BY PACKAGE DIRECTORY. This repo's MDX lives at the repo root in |
| 60 | +# `content/`, outside the `apps/docs` package boundary, so turbo does not see |
| 61 | +# it. `turbo.json` does list `"$TURBO_ROOT$/content/**"` under |
| 62 | +# `@objectstack/docs#build`'s `inputs`, but `inputs` only feeds the cache |
| 63 | +# hash — it does not widen the affected-package calculation. |
| 64 | +# |
| 65 | +# Measured on `main`: commit 1265f12b touches only |
| 66 | +# `content/docs/api/client-sdk.mdx`, and `turbo-ignore` alone reports SKIP. |
| 67 | +if ! git diff --quiet "$PREV_SHA" "$HEAD_SHA" -- content apps/docs; then |
| 68 | + echo "build: content/ or apps/docs/ changed since ${PREV_SHA}" |
| 69 | + exit 1 |
| 70 | +fi |
| 71 | + |
| 72 | +# 5. Nothing in the site's own sources changed. Ask turbo whether the docs app |
| 73 | +# is affected through its dependency graph — `@objectstack/spec` is the only |
| 74 | +# workspace dependency, but it changes often. |
| 75 | +# |
| 76 | +# Deliberately NOT `turbo-ignore`: that wrapper is deprecated upstream ("Use |
| 77 | +# `turbo query affected` instead") and it derives its own comparison range, |
| 78 | +# falling back to `[HEAD^]` when it cannot read Vercel's git environment — |
| 79 | +# a range that silently answers a different question than the one asked here. |
| 80 | +# Naming the range explicitly keeps this decision reviewable and testable. |
| 81 | +echo "no direct docs changes; asking turbo about the dependency graph" |
| 82 | +DRY=$(npx --yes "turbo@${TURBO_VERSION:-^2}" run build \ |
| 83 | + --filter="@objectstack/docs...[${PREV_SHA}...${HEAD_SHA}]" \ |
| 84 | + --dry=json 2>/dev/null) |
| 85 | +if [ -z "$DRY" ]; then |
| 86 | + echo "build: could not get a verdict from turbo" |
| 87 | + exit 1 |
| 88 | +fi |
| 89 | + |
| 90 | +AFFECTED=$(printf '%s' "$DRY" | node -e ' |
| 91 | + let s=""; |
| 92 | + process.stdin.on("data", d => s += d); |
| 93 | + process.stdin.on("end", () => { |
| 94 | + try { process.stdout.write(String((JSON.parse(s).tasks || []).length)); } |
| 95 | + catch { process.stdout.write("error"); } |
| 96 | + }); |
| 97 | +' 2>/dev/null) |
| 98 | + |
| 99 | +case "$AFFECTED" in |
| 100 | + 0) echo "skip: nothing in @objectstack/docs dependency graph changed since ${PREV_SHA}"; exit 0 ;; |
| 101 | + ''|*[!0-9]*) echo "build: could not parse turbo's verdict (${AFFECTED:-empty})"; exit 1 ;; |
| 102 | + *) echo "build: ${AFFECTED} task(s) in the docs dependency graph affected"; exit 1 ;; |
| 103 | +esac |
0 commit comments