Skip to content

Commit 172bcbc

Browse files
Merge pull request #74 from trysurface/chore/add-release-purge-remove-bundle-guard
ci: add Surface Tag release+purge (Step 2) + drop the stale bundle-guard
2 parents 0dfbb41 + 1a00e0e commit 172bcbc

2 files changed

Lines changed: 102 additions & 7 deletions

File tree

‎.github/workflows/ci.yml‎

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,7 @@ jobs:
2727

2828
- name: Unit tests
2929
run: pnpm test
30-
31-
# surface_tag.js / surface_embed_v1.js are committed CDN artifacts.
32-
# Fail if src/ was edited without rebuilding them.
33-
- name: Bundle is up to date
34-
run: |
35-
pnpm run build
36-
git diff --exit-code surface_tag.js surface_embed_v1.js
30+
# NOTE: the "Bundle is up to date" build-guard was removed — surface-forms
31+
# (packages/surface-scripts) now owns the tag source and pushes the built
32+
# surface_tag.js / surface_embed_v1.js here, so rebuilding from this repo's
33+
# (now non-authoritative) src/ would diff against the pushed bytes and fail main.
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
# STEP 2 of the two-step Surface Tag publish pipeline.
2+
#
3+
# Step 1 (surface-forms/.github/workflows/push-scripts-to-cdn.yml): builds the two bundles and pushes
4+
# them to this repo's main in one commit (via the "Scripts Repo Deploy" GitHub App).
5+
# Step 2 (this file): that push triggers this workflow, which cuts a release (advancing jsDelivr
6+
# @latest) and purges the CDN so customers get the new bytes immediately.
7+
#
8+
# No provisioned token needed — this Action acts on its OWN repo, so the built-in GITHUB_TOKEN (granted
9+
# contents:write below) is enough. surface-forms is now the source of truth; this repo only serves the
10+
# built artifacts it pushes (the ci.yml "Bundle is up to date" build-guard is removed in this same PR).
11+
12+
name: Release Surface Tag + purge CDN
13+
14+
on:
15+
push:
16+
branches: [main]
17+
# Only react to a real tag-bundle change, not docs/readme commits.
18+
paths:
19+
- surface_tag.js
20+
- surface_embed_v1.js
21+
22+
permissions:
23+
contents: write
24+
25+
# Never let two releases race (e.g. two quick pushes); run them one at a time.
26+
concurrency:
27+
group: release-surface-tag
28+
cancel-in-progress: false
29+
30+
jobs:
31+
release-and-purge:
32+
runs-on: ubuntu-latest
33+
steps:
34+
# jsDelivr @latest resolves to the highest SEMVER release, and this repo already uses v1.1.x — so
35+
# bump the PATCH of the latest release (v1.1.8 -> v1.1.9). Date-based tags would break @latest:
36+
# leading zeros (2026.09.02) are not valid semver, so jsDelivr would ignore them. target = the exact
37+
# pushed commit, so @latest serves precisely these bytes.
38+
- name: Cut a release for the pushed commit
39+
env:
40+
GH_TOKEN: ${{ github.token }}
41+
run: |
42+
set -euo pipefail
43+
# Read the latest release tag. Only a genuine "no releases yet" (HTTP 404) may fall back to
44+
# v1.1.0 — any OTHER failure (auth, rate limit, network, 5xx) must NOT fabricate a version:
45+
# a fabricated low tag either collides with an existing one (hard fail AFTER the bytes are
46+
# already on main, so @latest is stuck) or, when it's below the true latest, silently fails
47+
# to advance @latest while this job goes green.
48+
err=$(mktemp)
49+
if latest=$(gh api "repos/${GITHUB_REPOSITORY}/releases/latest" --jq '.tag_name' 2>"${err}"); then
50+
:
51+
elif grep -q "HTTP 404" "${err}"; then
52+
latest="v1.1.0"
53+
else
54+
cat "${err}" >&2
55+
echo "::error::Could not read the latest release (not a 404). Refusing to fabricate a version and risk a stale @latest — re-run once the GitHub API is reachable."
56+
exit 1
57+
fi
58+
rm -f "${err}"
59+
# Validate vMAJOR.MINOR.PATCH before the arithmetic — a malformed or pre-release latest tag
60+
# would otherwise yield an invalid/duplicate tag and silently stop @latest from advancing.
61+
# Each component must have NO leading zeros: a zero-padded value like 008 is not valid semver,
62+
# and Bash would parse it as octal ("008" -> value-too-great-for-base) and abort the release.
63+
if [[ ! "${latest}" =~ ^v?(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$ ]]; then
64+
echo "::error::Latest release tag '${latest}' is not vMAJOR.MINOR.PATCH with no leading zeros; refusing to derive the next release. Fix the latest release tag."
65+
exit 1
66+
fi
67+
MAJOR="${BASH_REMATCH[1]}"; MINOR="${BASH_REMATCH[2]}"; PATCH="${BASH_REMATCH[3]}"
68+
# Force base-10 so the increment can never be misread as octal, even if the guard above changes.
69+
RELEASE_TAG="v${MAJOR}.${MINOR}.$((10#${PATCH} + 1))"
70+
echo "Latest release ${latest}; cutting ${RELEASE_TAG} at ${GITHUB_SHA}"
71+
gh api -X POST "repos/${GITHUB_REPOSITORY}/releases" \
72+
-f tag_name="${RELEASE_TAG}" \
73+
-f target_commitish="${GITHUB_SHA}" \
74+
-f name="${RELEASE_TAG}" \
75+
-f body="Surface Tag synced from surface-forms (packages/surface-scripts)."
76+
echo "Released ${RELEASE_TAG}; jsDelivr @latest now resolves to this commit."
77+
78+
# jsDelivr caches aggressively; purge so @latest / @main serve the new bytes immediately instead of
79+
# up to 7 days later. A swallowed purge failure would show a green release while customers keep stale
80+
# bytes, so fail loudly.
81+
- name: Purge jsDelivr cache
82+
run: |
83+
set -euo pipefail
84+
purge_failed=0
85+
for f in surface_tag.js surface_tag.min.js surface_embed_v1.js surface_embed_v1.min.js; do
86+
for ref in latest main; do
87+
url="https://purge.jsdelivr.net/gh/${GITHUB_REPOSITORY}@${ref}/${f}"
88+
echo "Purging ${url}"
89+
if ! curl -sfS "${url}"; then
90+
echo "::warning::purge failed for ${url}"
91+
purge_failed=1
92+
fi
93+
done
94+
done
95+
if [ "${purge_failed}" -ne 0 ]; then
96+
echo "::error::One or more jsDelivr purges failed; @latest/@main may serve stale bytes. Re-run the purge."
97+
exit 1
98+
fi

0 commit comments

Comments
 (0)