From 65f07ecd209e9f5474f9c206e053b865c83813cb Mon Sep 17 00:00:00 2001 From: Arturo Castillo Delgado Date: Tue, 24 Feb 2026 11:57:57 +0100 Subject: [PATCH] [Release] Automate some manual steps (WIP) --- package.json | 2 + scripts/release-prep.sh | 132 +++++++++++++++++++++++++++++++++++++ scripts/release-publish.sh | 129 ++++++++++++++++++++++++++++++++++++ 3 files changed, 263 insertions(+) create mode 100755 scripts/release-prep.sh create mode 100755 scripts/release-publish.sh diff --git a/package.json b/package.json index c5bc5d81aa9..b97095f8998 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,8 @@ "build": "printf $'\\x1b[K\\x1b[37;41mPlease run this script from the \\x1b[1;4mpackages/eui\\x1b[0m\\x1b[37;41m directory instead\\x1b[0m\\n'; exit 1", "watch": "node scripts/watch-eui.js", "release": "node scripts/release", + "release:prep": "bash scripts/release-prep.sh", + "release:publish": "bash scripts/release-publish.sh", "clean": "node scripts/clean.mjs" }, "repository": { diff --git a/scripts/release-prep.sh b/scripts/release-prep.sh new file mode 100755 index 00000000000..8e3907fce16 --- /dev/null +++ b/scripts/release-prep.sh @@ -0,0 +1,132 @@ +#!/usr/bin/env bash +# +# Official release preparation script (pre-PR) +# +# Automates steps 1-8 of the official release process: +# 1. Log out of npm +# 2. Checkout main +# 3. Pull latest from upstream +# 4. Create a timestamped release branch +# 5. Build the release CLI +# 6. Run the release dry-run (interactive) +# 7. (User confirms in the interactive CLI) +# 8. Push the branch to origin and open a PR +# +# Usage: yarn release:prep +# + +set -euo pipefail + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[0;33m' +BOLD='\033[1m' +RESET='\033[0m' + +step() { + echo "" + echo -e "${GREEN}${BOLD}[$1]${RESET} $2" +} + +warn() { + echo -e "${YELLOW}Warning:${RESET} $1" +} + +error() { + echo -e "${RED}Error:${RESET} $1" >&2 + exit 1 +} + +REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null) || error "Not inside a git repository" +cd "$REPO_ROOT" + +# Verify the upstream remote exists +git remote get-url upstream &>/dev/null || error "'upstream' remote not found. Please add it: git remote add upstream git@github.com:elastic/eui.git" + +# ── Step 1: Log out of npm ────────────────────────────────────────────────── + +step "1/8" "Ensuring npm is not authenticated..." +npm logout 2>/dev/null || true +yarn npm logout 2>/dev/null || true + +# ── Step 2: Checkout main ─────────────────────────────────────────────────── + +step "2/8" "Checking out main branch..." +git checkout main + +# ── Step 3: Pull latest ──────────────────────────────────────────────────── + +step "3/8" "Pulling latest changes from upstream..." +git pull upstream main + +# ── Step 4: Create release branch ─────────────────────────────────────────── + +BRANCH_NAME="release/$(date +%s)" +step "4/8" "Creating release branch: ${BOLD}${BRANCH_NAME}${RESET}" +git checkout -b "$BRANCH_NAME" + +# ── Step 5: Build release CLI ─────────────────────────────────────────────── + +step "5/8" "Installing dependencies and building release CLI..." +yarn +yarn workspace @elastic/eui-release-cli run build + +# ── Step 6: Run release (dry-run) ─────────────────────────────────────────── + +step "6/8" "Starting release process (dry-run)..." +echo "" +yarn release run official --dry-run --allow-custom --skip-auth-check --use-auth-token + +# ── Step 7: Push branch ──────────────────────────────────────────────────── + +step "7/8" "Pushing branch to origin..." +git push -u origin "$BRANCH_NAME" + +# ── Step 8: Open PR ──────────────────────────────────────────────────────── + +step "8/8" "Opening release PR..." + +# Detect changed packages by comparing versions on this branch vs main +PR_TITLE_PARTS="" +PR_BODY_LINES="" + +for pkg_dir in packages/eui packages/eui-theme-common packages/eui-theme-borealis packages/docusaurus-preset packages/docusaurus-theme packages/eslint-plugin; do + pkg_json="${pkg_dir}/package.json" + [[ -f "$pkg_json" ]] || continue + + new_version=$(node -p "require('./${pkg_json}').version") + old_version=$(git show "main:${pkg_json}" 2>/dev/null | node -p "JSON.parse(require('fs').readFileSync('/dev/stdin','utf8')).version" 2>/dev/null || echo "") + + if [[ -n "$old_version" && "$new_version" != "$old_version" ]]; then + pkg_name=$(node -p "require('./${pkg_json}').name") + + if [[ -n "$PR_TITLE_PARTS" ]]; then + PR_TITLE_PARTS="${PR_TITLE_PARTS}, ${pkg_name} v${new_version}" + else + PR_TITLE_PARTS="${pkg_name} v${new_version}" + fi + PR_BODY_LINES="${PR_BODY_LINES}\n- \`${pkg_name}\` - v${old_version} → v${new_version}" + fi +done + +if [[ -z "$PR_TITLE_PARTS" ]]; then + error "No changed package versions detected. Did the release dry-run update any versions?" +fi + +PR_TITLE="Release: ${PR_TITLE_PARTS}" +PR_BODY="$(printf "Packages to release:\n${PR_BODY_LINES}")" + +PR_URL=$(gh pr create \ + --title "$PR_TITLE" \ + --body "$PR_BODY" \ + --label "skip-changelog" \ + --label "release" \ + --base main) + +echo "" +echo -e "${GREEN}${BOLD}Prep complete!${RESET}" +echo "" +echo -e " PR: ${BOLD}${PR_URL}${RESET}" +echo "" +echo -e " ${BOLD}After the PR is merged:${RESET}" +echo -e " yarn release:publish" diff --git a/scripts/release-publish.sh b/scripts/release-publish.sh new file mode 100755 index 00000000000..dbd42ba873c --- /dev/null +++ b/scripts/release-publish.sh @@ -0,0 +1,129 @@ +#!/usr/bin/env bash +# +# Official release publish script (post-merge) +# +# Automates steps 10-11 of the official release process: +# - Detects the EUI version and changed workspaces +# - Tags the merge commit +# - Pushes the tag to upstream +# - Triggers the GitHub Actions release workflow +# +# Usage: yarn release:publish [merge-commit-sha] +# +# If no SHA is provided, defaults to HEAD on main. +# + +set -euo pipefail + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[0;33m' +BOLD='\033[1m' +RESET='\033[0m' + +step() { + echo "" + echo -e "${GREEN}${BOLD}[$1]${RESET} $2" +} + +warn() { + echo -e "${YELLOW}Warning:${RESET} $1" +} + +error() { + echo -e "${RED}Error:${RESET} $1" >&2 + exit 1 +} + +REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null) || error "Not inside a git repository" +cd "$REPO_ROOT" + +git remote get-url upstream &>/dev/null || error "'upstream' remote not found" + +# ── Ensure we're on main and up to date ────────────────────────────────────── + +step "1/5" "Updating main branch..." +git checkout main +git pull upstream main + +# ── Determine the merge commit SHA ─────────────────────────────────────────── + +MERGE_SHA="${1:-$(git rev-parse HEAD)}" + +step "2/5" "Detecting release details from ${BOLD}${MERGE_SHA:0:12}${RESET}..." + +# Read the EUI version at the target commit +EUI_VERSION=$(git show "${MERGE_SHA}:packages/eui/package.json" | node -p "JSON.parse(require('fs').readFileSync('/dev/stdin','utf8')).version") +TAG_NAME="v${EUI_VERSION}" + +# Check if this tag already exists +if git rev-parse "$TAG_NAME" &>/dev/null; then + error "Tag ${TAG_NAME} already exists. Has this release already been published?" +fi + +# ── Detect changed workspaces ──────────────────────────────────────────────── + +# Find the most recent existing release tag to compare against +PREV_TAG=$(git describe --tags --abbrev=0 "${MERGE_SHA}^" 2>/dev/null) || error "Could not find a previous release tag" + +# Compare package.json versions between previous tag and release commit +# to determine which public packages changed +CHANGED_WORKSPACES="" +for pkg_dir in packages/eui packages/eui-theme-common packages/eui-theme-borealis packages/docusaurus-preset packages/docusaurus-theme packages/eslint-plugin; do + pkg_json="${pkg_dir}/package.json" + + # Skip if package.json doesn't exist at the merge commit + git show "${MERGE_SHA}:${pkg_json}" &>/dev/null || continue + + new_version=$(git show "${MERGE_SHA}:${pkg_json}" | node -p "JSON.parse(require('fs').readFileSync('/dev/stdin','utf8')).version") + old_version=$(git show "${PREV_TAG}:${pkg_json}" 2>/dev/null | node -p "JSON.parse(require('fs').readFileSync('/dev/stdin','utf8')).version" 2>/dev/null || echo "") + + if [[ "$new_version" != "$old_version" ]]; then + pkg_name=$(git show "${MERGE_SHA}:${pkg_json}" | node -p "JSON.parse(require('fs').readFileSync('/dev/stdin','utf8')).name") + if [[ -n "$CHANGED_WORKSPACES" ]]; then + CHANGED_WORKSPACES="${CHANGED_WORKSPACES},${pkg_name}" + else + CHANGED_WORKSPACES="${pkg_name}" + fi + fi +done + +if [[ -z "$CHANGED_WORKSPACES" ]]; then + error "No changed workspaces detected. Are you sure the release PR was merged?" +fi + +# ── Summary & confirmation ────────────────────────────────────────────────── + +step "3/5" "Release summary" +echo "" +echo -e " Tag: ${BOLD}${TAG_NAME}${RESET}" +echo -e " Commit: ${BOLD}${MERGE_SHA:0:12}${RESET}" +echo -e " Previous tag: ${BOLD}${PREV_TAG}${RESET}" +echo -e " Workspaces: ${BOLD}${CHANGED_WORKSPACES}${RESET}" +echo "" +read -r -p "Proceed with tagging and triggering the release? (y/N) " confirm +if [[ "$confirm" != "y" && "$confirm" != "Y" ]]; then + echo "Aborted." + exit 0 +fi + +# ── Tag and push ──────────────────────────────────────────────────────────── + +step "4/5" "Creating and pushing tag ${BOLD}${TAG_NAME}${RESET}..." +git tag -a "$TAG_NAME" "$MERGE_SHA" -m "@elastic/eui ${TAG_NAME}" +git push upstream "$TAG_NAME" --no-verify + +# ── Trigger release workflow ──────────────────────────────────────────────── + +step "5/5" "Triggering release workflow..." +gh workflow run release.yml \ + --repo elastic/eui \ + -f release_ref="$MERGE_SHA" \ + -f type=official \ + -f workspaces="$CHANGED_WORKSPACES" \ + -f dry_run=false + +echo "" +echo -e "${GREEN}${BOLD}Release triggered!${RESET}" +echo "" +echo -e " Monitor: ${BOLD}https://github.com/elastic/eui/actions/workflows/release.yml${RESET}"