qmd/scripts/pre-push
Tobi Lutke 09803a75b7
feat: compile to JS for npm, release system, full changelog
- Add tsc build step (tsconfig.build.json) so npm package ships
  compiled JS instead of raw TypeScript requiring tsx at runtime
- Update qmd wrapper and daemon spawn to use dist/qmd.js in
  production while keeping tsx for development
- Add self-installing pre-push hook validating v* tag pushes:
  package.json version match, changelog entry, CI status
- Add release.sh script that renames [Unreleased] to versioned
  entry, bumps package.json, commits, and tags
- Add extract-changelog.sh for cumulative GitHub release notes
- Update publish workflow with build step and GitHub release creation
- Flesh out CHANGELOG.md with full history from 0.1.0 through 1.0.0
  in Keep-a-Changelog format with PR/contributor attributions
- Add release standards and changelog guidelines to CLAUDE.md
2026-02-16 08:42:32 -04:00

113 lines
3.5 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
# Pre-push hook: validates v* tag pushes before they reach the remote.
#
# Checks:
# 1. package.json version matches the tag
# 2. CHANGELOG.md has a "## [{version}] - {date}" entry
# 3. CI passed upstream on GitHub for the tagged commit
#
# Installed automatically by: bun install (via prepare script)
while read -r local_ref local_sha remote_ref remote_sha; do
# Only validate v* tag pushes
if [[ "$local_ref" != refs/tags/v* ]]; then
continue
fi
# Skip tag deletions
if [[ "$local_sha" == "0000000000000000000000000000000000000000" ]]; then
continue
fi
TAG="${local_ref#refs/tags/}"
VERSION="${TAG#v}"
echo "Validating release $TAG..."
# --- 1. package.json version must match the tag ---
PKG_VERSION=$(jq -r .version package.json)
if [[ "$PKG_VERSION" != "$VERSION" ]]; then
echo ""
echo "ABORT: package.json version is $PKG_VERSION but tag is $TAG"
echo "Run: jq --arg v '$VERSION' '.version = \$v' package.json > tmp && mv tmp package.json"
exit 1
fi
echo " package.json version: $PKG_VERSION"
# --- 2. CHANGELOG.md must have an entry for this version ---
if [[ ! -f CHANGELOG.md ]]; then
echo ""
echo "ABORT: CHANGELOG.md not found"
exit 1
fi
if ! grep -q "^## \[$VERSION\] - " CHANGELOG.md; then
echo ""
echo "ABORT: CHANGELOG.md has no entry for this release"
echo "Expected heading: ## [$VERSION] - $(date +%Y-%m-%d)"
echo ""
echo "Write the changelog entry first. See CLAUDE.md for guidelines."
exit 1
fi
echo " CHANGELOG.md: entry found"
# --- 3. CI must have passed on GitHub for this commit ---
COMMIT=$(git rev-parse "$TAG" 2>/dev/null || git rev-parse HEAD)
if ! command -v gh &>/dev/null; then
echo " CI check: skipped (gh CLI not installed)"
continue
fi
# Check GitHub Actions check runs
CHECK_JSON=$(gh api "repos/{owner}/{repo}/commits/$COMMIT/check-runs" 2>/dev/null || echo "")
if [[ -z "$CHECK_JSON" ]]; then
echo " CI check: skipped (could not reach GitHub API)"
continue
fi
TOTAL=$(echo "$CHECK_JSON" | jq -r '.total_count // 0' 2>/dev/null || echo "0")
if [[ "$TOTAL" -eq 0 ]] 2>/dev/null; then
# No checks found — commit may not be pushed yet
MAIN_SHA=$(git rev-parse origin/main 2>/dev/null || echo "")
if [[ "$COMMIT" != "$MAIN_SHA" ]]; then
echo ""
echo "WARNING: No CI runs found for $COMMIT and it's not the tip of origin/main."
echo "Push the commit to main first and wait for CI to pass."
read -p "Push tag anyway? [y/N] " -n 1 -r </dev/tty
echo ""
[[ $REPLY =~ ^[Yy]$ ]] || exit 1
else
echo " CI check: no runs found (commit is on main)"
fi
else
FAILED=$(echo "$CHECK_JSON" | jq '[.check_runs // [] | .[] | select(.conclusion == "failure")] | length' 2>/dev/null || echo "0")
PENDING=$(echo "$CHECK_JSON" | jq '[.check_runs // [] | .[] | select(.status != "completed")] | length' 2>/dev/null || echo "0")
if [[ "$FAILED" -gt 0 ]] 2>/dev/null; then
echo ""
echo "ABORT: CI failed for commit $COMMIT"
echo "Check: https://github.com/tobi/qmd/commit/$COMMIT"
exit 1
fi
if [[ "$PENDING" -gt 0 ]] 2>/dev/null; then
echo ""
echo "WARNING: CI still running for commit $COMMIT ($PENDING pending)"
read -p "Push tag anyway? [y/N] " -n 1 -r </dev/tty
echo ""
[[ $REPLY =~ ^[Yy]$ ]] || exit 1
else
echo " CI check: all passed"
fi
fi
echo "All checks passed for $TAG"
done
exit 0