release.sh 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. #!/usr/bin/env bash
  2. set -euo pipefail
  3. # QMD Release Script
  4. # Usage: ./scripts/release.sh [patch|minor|major|<version>]
  5. # Examples:
  6. # ./scripts/release.sh patch # 0.9.0 -> 0.9.1
  7. # ./scripts/release.sh minor # 0.9.0 -> 0.10.0
  8. # ./scripts/release.sh major # 0.9.0 -> 1.0.0
  9. # ./scripts/release.sh 1.0.0 # explicit version
  10. BUMP="${1:?Usage: release.sh [patch|minor|major|<version>]}"
  11. # Ensure we're on main and clean
  12. BRANCH=$(git branch --show-current)
  13. if [[ "$BRANCH" != "main" ]]; then
  14. echo "Error: must be on main branch (currently on $BRANCH)" >&2
  15. exit 1
  16. fi
  17. if [[ -n "$(git status --porcelain)" ]]; then
  18. echo "Error: working directory not clean" >&2
  19. git status --short
  20. exit 1
  21. fi
  22. # Read current version
  23. CURRENT=$(jq -r .version package.json)
  24. echo "Current version: $CURRENT"
  25. # Calculate new version
  26. bump_version() {
  27. local current="$1" type="$2"
  28. IFS='.' read -r major minor patch <<< "$current"
  29. case "$type" in
  30. major) echo "$((major + 1)).0.0" ;;
  31. minor) echo "$major.$((minor + 1)).0" ;;
  32. patch) echo "$major.$minor.$((patch + 1))" ;;
  33. *) echo "$type" ;; # explicit version
  34. esac
  35. }
  36. NEW=$(bump_version "$CURRENT" "$BUMP")
  37. echo "New version: $NEW"
  38. echo ""
  39. # Confirm
  40. read -p "Release v$NEW? [y/N] " -n 1 -r
  41. echo ""
  42. [[ $REPLY =~ ^[Yy]$ ]] || { echo "Aborted."; exit 1; }
  43. # Gather commits since last tag (or all if no tags)
  44. LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
  45. if [[ -n "$LAST_TAG" ]]; then
  46. RANGE="$LAST_TAG..HEAD"
  47. else
  48. RANGE="HEAD"
  49. fi
  50. echo ""
  51. echo "Commits since ${LAST_TAG:-beginning}:"
  52. git log "$RANGE" --oneline --no-decorate
  53. echo ""
  54. # Generate changelog entry
  55. DATE=$(date +%Y-%m-%d)
  56. ENTRY="## [$NEW] - $DATE"$'\n'$'\n'
  57. # Collect conventional commits
  58. FEATS=$(git log "$RANGE" --oneline --no-decorate --grep="^feat" | sed 's/^[a-f0-9]* feat[:(]/- /' | sed 's/)$//' || true)
  59. FIXES=$(git log "$RANGE" --oneline --no-decorate --grep="^fix" | sed 's/^[a-f0-9]* fix[:(]/- /' | sed 's/)$//' || true)
  60. OTHER=$(git log "$RANGE" --oneline --no-decorate --grep="^feat" --grep="^fix" --grep="^docs" --grep="^chore" --grep="^refactor" --invert-grep | sed 's/^[a-f0-9]* /- /' || true)
  61. if [[ -n "$FEATS" ]]; then
  62. ENTRY+="### Features"$'\n'$'\n'"$FEATS"$'\n'$'\n'
  63. fi
  64. if [[ -n "$FIXES" ]]; then
  65. ENTRY+="### Fixes"$'\n'$'\n'"$FIXES"$'\n'$'\n'
  66. fi
  67. if [[ -n "$OTHER" ]]; then
  68. ENTRY+="### Other"$'\n'$'\n'"$OTHER"$'\n'$'\n'
  69. fi
  70. # Add link reference
  71. LINK="[$NEW]: https://github.com/tobi/qmd/compare/v$CURRENT...v$NEW"
  72. # Show what will be added
  73. echo "--- Changelog entry ---"
  74. echo "$ENTRY"
  75. echo "$LINK"
  76. echo "--- End ---"
  77. echo ""
  78. read -p "Looks good? [y/N] " -n 1 -r
  79. echo ""
  80. [[ $REPLY =~ ^[Yy]$ ]] || { echo "Aborted."; exit 1; }
  81. # Update package.json version
  82. jq --arg v "$NEW" '.version = $v' package.json > package.json.tmp && mv package.json.tmp package.json
  83. # Prepend changelog entry (after the header line)
  84. if [[ -f CHANGELOG.md ]]; then
  85. # Insert after "# Changelog" header and any blank lines
  86. awk -v entry="$ENTRY$LINK" '
  87. /^# Changelog/ { print; getline; print; print ""; print entry; print ""; next }
  88. { print }
  89. ' CHANGELOG.md > CHANGELOG.md.tmp && mv CHANGELOG.md.tmp CHANGELOG.md
  90. else
  91. echo "# Changelog"$'\n'$'\n'"$ENTRY$LINK" > CHANGELOG.md
  92. fi
  93. # Commit and tag
  94. git add package.json CHANGELOG.md
  95. git commit -m "release: v$NEW"
  96. git tag -a "v$NEW" -m "v$NEW"
  97. echo ""
  98. echo "Created commit and tag v$NEW"
  99. echo ""
  100. echo "Next steps:"
  101. echo " git push origin main --tags # push to GitHub"
  102. echo " npm publish # publish to npm"
  103. echo ""
  104. echo "Or both at once:"
  105. echo " git push origin main --tags && npm publish"