235 lines
9.4 KiB
Bash
Executable file
235 lines
9.4 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
# TrainUs release script — local steps only.
|
|
# Prepares a release commit, tag, source archive, and a draft release notes file.
|
|
# Everything that touches Forgejo or a remote server is left to you (see checklist at the end).
|
|
# Usage: ./scripts/release.sh
|
|
set -euo pipefail
|
|
|
|
GREEN='\033[0;32m'; YELLOW='\033[1;33m'; CYAN='\033[0;36m'; BOLD='\033[1m'; NC='\033[0m'
|
|
RED='\033[0;31m'; DIM='\033[2m'
|
|
info() { echo -e " ${GREEN}✓${NC} $*"; }
|
|
doing() { echo -e "\n${CYAN}▶${NC} $*"; }
|
|
warn() { echo -e " ${YELLOW}⚠${NC} $*"; }
|
|
error() { echo -e "\n${RED}✗ Error:${NC} $*" >&2; exit 1; }
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
cd "$ROOT_DIR"
|
|
|
|
# ── Prerequisites ─────────────────────────────────────────────────────────────
|
|
echo ""
|
|
echo -e "${BOLD}Checking prerequisites...${NC}"
|
|
|
|
command -v npm >/dev/null 2>&1 || error "npm is not installed."
|
|
command -v git >/dev/null 2>&1 || error "git is not installed."
|
|
info "npm and git are available."
|
|
|
|
if [[ -n "$(git status --porcelain)" ]]; then
|
|
error "Working tree is not clean. Commit or stash your changes before releasing."
|
|
fi
|
|
info "Working tree is clean."
|
|
|
|
CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
|
|
if [[ "$CURRENT_BRANCH" != "main" ]]; then
|
|
warn "Current branch is '$CURRENT_BRANCH', not 'main'."
|
|
fi
|
|
info "On branch: $CURRENT_BRANCH"
|
|
|
|
# ── Commits since last release ────────────────────────────────────────────────
|
|
echo ""
|
|
echo -e "${BOLD}Changes since last release${NC}"
|
|
|
|
LAST_TAG=$(git describe --tags --abbrev=0 HEAD 2>/dev/null || echo "")
|
|
|
|
if [[ -n "$LAST_TAG" ]]; then
|
|
echo " Last release: $LAST_TAG"
|
|
echo " Commits since $LAST_TAG:"
|
|
echo ""
|
|
COMMIT_LOG=$(git log "${LAST_TAG}..HEAD" --oneline --no-merges)
|
|
else
|
|
warn "No previous tag found — listing all commits."
|
|
echo ""
|
|
COMMIT_LOG=$(git log --oneline --no-merges)
|
|
fi
|
|
|
|
if [[ -z "$COMMIT_LOG" ]]; then
|
|
warn "No commits since $LAST_TAG. Are you sure you want to release?"
|
|
else
|
|
while IFS= read -r line; do
|
|
echo -e " ${DIM}${line}${NC}"
|
|
done <<< "$COMMIT_LOG"
|
|
fi
|
|
|
|
echo ""
|
|
|
|
# ── Version ───────────────────────────────────────────────────────────────────
|
|
echo -e "${BOLD}Version${NC}"
|
|
CURRENT_VERSION=$(node -p "require('./package.json').version")
|
|
echo " Current version: $CURRENT_VERSION"
|
|
echo -n " New version (semver, e.g. 1.1.0): "
|
|
read -r VERSION
|
|
|
|
[[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] \
|
|
|| error "Invalid format. Use MAJOR.MINOR.PATCH without a 'v' prefix."
|
|
|
|
TAG="v$VERSION"
|
|
ARCHIVE="trainus-${TAG}-src.zip"
|
|
NOTES_FILE="trainus-${TAG}-notes.md"
|
|
|
|
git tag --list | grep -q "^${TAG}$" \
|
|
&& error "Tag $TAG already exists locally."
|
|
|
|
# ── Plan ──────────────────────────────────────────────────────────────────────
|
|
echo ""
|
|
echo -e "${BOLD}The script will perform the following steps in order:${NC}"
|
|
echo ""
|
|
echo " 1. Bump version in package.json and package-lock.json"
|
|
echo " $CURRENT_VERSION → $VERSION"
|
|
echo ""
|
|
echo " 2. Build the production app"
|
|
echo " npm run build → dist/"
|
|
echo ""
|
|
echo " 3. Create a release commit on branch '$CURRENT_BRANCH'"
|
|
echo " git commit -m \"chore: release $TAG\""
|
|
echo ""
|
|
echo " 4. Create an annotated git tag (local only, not pushed)"
|
|
echo " git tag -a $TAG -m \"Release $TAG\""
|
|
echo ""
|
|
echo " 5. Create a source archive of the current commit"
|
|
echo " $ARCHIVE (tracked files only, no node_modules, no dist)"
|
|
echo ""
|
|
echo " 6. Generate a draft release notes file for you to edit"
|
|
echo " $NOTES_FILE (pre-filled with the commit list above)"
|
|
echo ""
|
|
echo -e "${YELLOW}Nothing will be pushed or sent anywhere. All steps are local.${NC}"
|
|
echo ""
|
|
echo -n " Continue? [y/N] "
|
|
read -r CONFIRM
|
|
[[ "$CONFIRM" =~ ^[Yy]$ ]] || { echo " Aborted."; exit 0; }
|
|
|
|
# ── Step 1 — Bump version ─────────────────────────────────────────────────────
|
|
doing "Step 1/6 — Bumping version in package.json and package-lock.json"
|
|
echo " Running: npm version $VERSION --no-git-tag-version"
|
|
npm version "$VERSION" --no-git-tag-version --silent
|
|
info "package.json and package-lock.json updated to $VERSION."
|
|
|
|
# ── Step 2 — Build ────────────────────────────────────────────────────────────
|
|
doing "Step 2/6 — Building the production app"
|
|
echo " Running: npm run build"
|
|
npm run build
|
|
info "Build complete. Output is in dist/."
|
|
|
|
# ── Step 3 — Commit ───────────────────────────────────────────────────────────
|
|
doing "Step 3/6 — Creating release commit"
|
|
echo " Running: git add package.json package-lock.json"
|
|
echo " git commit -m \"chore: release $TAG\""
|
|
git add package.json package-lock.json
|
|
git commit -m "chore: release $TAG"
|
|
info "Commit created: chore: release $TAG"
|
|
|
|
# ── Step 4 — Tag ──────────────────────────────────────────────────────────────
|
|
doing "Step 4/6 — Creating annotated git tag"
|
|
echo " Running: git tag -a $TAG -m \"Release $TAG\""
|
|
git tag -a "$TAG" -m "Release $TAG"
|
|
info "Tag $TAG created locally."
|
|
|
|
# ── Step 5 — Archive ──────────────────────────────────────────────────────────
|
|
doing "Step 5/6 — Creating source archive"
|
|
echo " Running: git archive --format=zip HEAD --output=$ARCHIVE"
|
|
git archive --format=zip HEAD --output="$ARCHIVE"
|
|
info "Archive created: $ARCHIVE"
|
|
echo " Contains: all files tracked by git at commit $(git rev-parse --short HEAD)."
|
|
echo " Excludes: node_modules/, dist/, .git/, and any untracked files."
|
|
|
|
# ── Step 6 — Release notes file ───────────────────────────────────────────────
|
|
doing "Step 6/6 — Generating draft release notes"
|
|
echo " Writing: $NOTES_FILE"
|
|
|
|
if [[ -n "$LAST_TAG" ]]; then
|
|
RANGE_LABEL="since $LAST_TAG"
|
|
else
|
|
RANGE_LABEL="(no previous tag — all commits)"
|
|
fi
|
|
|
|
{
|
|
echo "# TrainUs $TAG — Release notes"
|
|
echo ""
|
|
echo "<!-- Edit this file before pasting into Forgejo. Delete comments. -->"
|
|
echo ""
|
|
echo "## What's new"
|
|
echo ""
|
|
echo "<!-- Describe new features. -->"
|
|
echo ""
|
|
echo "-"
|
|
echo ""
|
|
echo "## Bug fixes"
|
|
echo ""
|
|
echo "<!-- List bug fixes, or remove this section if none. -->"
|
|
echo ""
|
|
echo "-"
|
|
echo ""
|
|
echo "## Breaking changes"
|
|
echo ""
|
|
echo "None."
|
|
echo ""
|
|
echo "---"
|
|
echo ""
|
|
echo "## Commits $RANGE_LABEL"
|
|
echo ""
|
|
echo "<!-- For reference — trim or reword before publishing. -->"
|
|
echo ""
|
|
if [[ -n "$COMMIT_LOG" ]]; then
|
|
while IFS= read -r line; do
|
|
echo "- $line"
|
|
done <<< "$COMMIT_LOG"
|
|
else
|
|
echo "- (none)"
|
|
fi
|
|
} > "$NOTES_FILE"
|
|
|
|
info "Draft release notes written to $NOTES_FILE."
|
|
echo " Open it in your editor, rewrite the notes, then paste into Forgejo."
|
|
|
|
# ── Done — Manual checklist ───────────────────────────────────────────────────
|
|
echo ""
|
|
echo -e "${GREEN}${BOLD}Local release steps complete.${NC}"
|
|
echo ""
|
|
echo -e "${BOLD}Artifacts created:${NC}"
|
|
echo " $ARCHIVE ← attach to the Forgejo release"
|
|
echo " $NOTES_FILE ← edit and paste as the release body"
|
|
echo ""
|
|
echo -e "${BOLD}Manual steps remaining:${NC}"
|
|
echo ""
|
|
echo -e " ${BOLD}1. Edit the release notes${NC}"
|
|
echo " Open $NOTES_FILE and rewrite the notes into polished prose."
|
|
echo ""
|
|
echo -e " ${BOLD}2. Push the commit and tag to Forgejo${NC}"
|
|
echo " git push origin $CURRENT_BRANCH"
|
|
echo " git push origin $TAG"
|
|
echo ""
|
|
echo -e " ${BOLD}3. Create the release on Forgejo${NC}"
|
|
echo " Go to your repository → Releases → New release"
|
|
echo " Tag: $TAG"
|
|
echo " Title: TrainUs $TAG"
|
|
echo " Body: paste the contents of $NOTES_FILE"
|
|
echo " Attach: $ARCHIVE"
|
|
echo " Publish the release."
|
|
echo ""
|
|
echo -e " ${BOLD}4. Deploy the app${NC}"
|
|
echo " Upload the contents of dist/ to your static host."
|
|
echo ""
|
|
echo -e " ${BOLD}5. Deploy the website${NC}"
|
|
echo " npm run site:build"
|
|
echo " Upload the contents of website/public/ to your website host."
|
|
echo ""
|
|
echo -e " ${BOLD}6. Publish the release blog post${NC}"
|
|
echo " Duplicate website/content/blog/release-template.en.md"
|
|
echo " Rename it: $(date +%Y-%m-%d)-release-${TAG}.en.md"
|
|
echo " Fill in the placeholders — set draft: false when ready."
|
|
echo " Write the French version yourself."
|
|
echo " Rebuild and redeploy the website (step 5)."
|
|
echo ""
|
|
echo -e " ${BOLD}7. Clean up local artifacts${NC}"
|
|
echo " Once uploaded and published: rm $ARCHIVE $NOTES_FILE"
|
|
echo ""
|