Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 16 additions & 18 deletions .github/workflows/chromatic.yml
Original file line number Diff line number Diff line change
Expand Up @@ -90,21 +90,19 @@ jobs:
# cannot merge with un-reviewed Chromatic changes (enforced
# via branch protection on this repo).
autoAcceptChanges: main
# TurboSnap (`onlyChanged: true`) is off for now. It chains
# snapshots across builds via the previous build's baseline, and
# can't detect content changes inside a consumed CSS package —
# `@code-sherpas/pharos-tokens/css` is side-effect-imported from
# `src/styles/index.css`, and its contents change with every
# pharos-tokens release without any file tracked by git being
# touched. Attempting to register it as `externals` doesn't help:
# TurboSnap diffs against the git tree, and `node_modules/` is
# not in it. The symptom: axe violations fixed upstream (e.g.
# darkening `error.fg` to pass WCAG AA 4.5:1) never cleared
# because old pixel snapshots were reused.
#
# At the current scale (~10 stories) the snapshot quota hit of a
# full rebuild on every push is negligible. Revisit once we have
# 50+ stories: at that point either cache the pharos-tokens
# `dist/styles.css` content-hash into a git-tracked file so
# TurboSnap can detect it, or use a dedicated TurboSnap-aware
# integration.
# TurboSnap: rebuild only the stories whose transitive dependency
# graph in the *git tree* has changed. The historical hazard
# (silent stale snapshots when `@code-sherpas/pharos-tokens`
# released a new CSS without any pharos-react source file being
# touched) is now covered by the `externals` glob below: the
# `postinstall` script writes `.pharos-tokens.fingerprint` from
# the resolved package contents; a pharos-tokens release that
# changes any byte of its publish directory mutates the hash;
# CI's `verify-fingerprint` step (ci.yml) blocks any PR whose
# committed fingerprint disagrees with `node_modules/`. With
# those guard rails TurboSnap can be trusted to invalidate the
# whole baseline whenever the fingerprint moves, so stale-token
# snapshots cannot ship.
onlyChanged: true
externals: |
.pharos-tokens.fingerprint
14 changes: 14 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,20 @@ jobs:
- name: Install dependencies
run: pnpm install --frozen-lockfile

# `pnpm install` triggers the `postinstall` script, which regenerates
# `.pharos-tokens.fingerprint` from the resolved `pharos-tokens`
# contents in `node_modules/`. If the committed fingerprint disagrees
# with reality (developer forgot to commit it after a dep bump), this
# step fails the build and prints a precise remediation message.
# Required for the `chromaui/action` step in chromatic.yml to trust
# the fingerprint as a TurboSnap external.
- name: Verify pharos-tokens fingerprint is current
run: |
if ! git diff --exit-code .pharos-tokens.fingerprint; then
echo "::error file=.pharos-tokens.fingerprint::pharos-tokens fingerprint is stale. Run \`pnpm install\` locally and commit the updated .pharos-tokens.fingerprint."
exit 1
fi

- name: Build
run: pnpm build

Expand Down
6 changes: 6 additions & 0 deletions .pharos-tokens.fingerprint
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# @code-sherpas/pharos-tokens content fingerprint.
# Generated by scripts/snapshot-pharos-tokens.sh — do not edit manually.
# Re-run `pnpm postinstall` (or `bash scripts/snapshot-pharos-tokens.sh`)
# after bumping the dependency.
version=0.4.0
content_sha256=a2b8ac3584ad91a6f7001962ff082011f47bc9ddc4db251d5df16ee51e1ac049
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@
"changeset": "changeset",
"release": "pnpm build && changeset publish",
"prepublishOnly": "pnpm build",
"prepare": "husky"
"prepare": "husky",
"postinstall": "bash scripts/snapshot-pharos-tokens.sh"
},
"lint-staged": {
"*.{ts,tsx,js,mjs,cjs,json,md,mdx,css,yml,yaml}": "prettier --write"
Expand Down
71 changes: 71 additions & 0 deletions scripts/snapshot-pharos-tokens.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
#!/usr/bin/env bash
#
# Captures the *content* fingerprint of the installed `@code-sherpas/pharos-tokens`
# into a git-tracked file (`.pharos-tokens.fingerprint`). The fingerprint becomes
# the canary that TurboSnap reads via `chromaui/action`'s `externals` glob: a
# pharos-tokens release that changes any byte of its published `dist/` mutates
# the fingerprint → TurboSnap detects the change in the git tree → invalidates
# every Chromatic story for that build → the next build with the same tokens
# falls back to TurboSnap's incremental path.
#
# Why hash the whole `dist/` instead of only `dist/styles.css`:
# pharos-tokens ships CSS variables today, but a future release could add a
# JS module (icon registry, semantic alias resolver, etc.) consumed at import
# time. Hashing the whole publish directory captures every artifact that
# actually lands in `node_modules/` after `pnpm install`, including
# `package.json` (version field) and any `.d.ts` shipped with the runtime.
#
# Determinism:
# `find` is run from inside the package dir so the hashed paths are
# package-relative (`./dist/...`). Output is sorted (`LC_ALL=C sort`) so two
# machines with different locales produce the same final hash. Each file is
# hashed individually with `sha256sum`; that line stream is then hashed once
# more to produce a single fingerprint.
#
# Exit codes:
# 0 — fingerprint file written (or already up-to-date).
# 1 — pharos-tokens not installed (run `pnpm install` first).
# 2 — pre-flight tool missing (sha256sum, jq, find).

set -euo pipefail

FINGERPRINT_FILE=".pharos-tokens.fingerprint"
PKG_DIR="node_modules/@code-sherpas/pharos-tokens"

# Pre-flight: every tool we use must be on PATH.
for cmd in sha256sum jq find; do
if ! command -v "$cmd" >/dev/null 2>&1; then
echo "error: \`$cmd\` is required by $0 but was not found on PATH" >&2
exit 2
fi
done

if [[ ! -d "$PKG_DIR" ]]; then
echo "error: $PKG_DIR not found. Run \`pnpm install\` first." >&2
exit 1
fi

VERSION=$(jq -r .version "$PKG_DIR/package.json")

# Hash every file under the package directory (not just dist/) — covers the
# whole installed artifact: dist/, package.json, README, etc. Paths are
# package-relative so the hash is machine-independent.
DIST_SHA=$(
cd "$PKG_DIR"
find . -type f -print0 \
| LC_ALL=C sort -z \
| xargs -0 sha256sum \
| sha256sum \
| cut -d' ' -f1
)

cat > "$FINGERPRINT_FILE" <<EOF
# @code-sherpas/pharos-tokens content fingerprint.
# Generated by scripts/snapshot-pharos-tokens.sh — do not edit manually.
# Re-run \`pnpm postinstall\` (or \`bash scripts/snapshot-pharos-tokens.sh\`)
# after bumping the dependency.
version=$VERSION
content_sha256=$DIST_SHA
EOF

echo "wrote $FINGERPRINT_FILE — version=$VERSION sha=${DIST_SHA:0:12}…"