Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
135 changes: 135 additions & 0 deletions scripts/release-prep.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
#!/usr/bin/env bash
#
# Official release preparation script (pre-PR)
#
# Automates initial steps of the process:
# - Log out of npm
# - Checkout main, pull latest from upstream
# - Create a timestamped release branch
# - Build the release CLI
# - Run the release dry-run (interactive)
# - (User confirms in the interactive CLI)
# - 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"

PACKAGE_DIRS=(packages/eui packages/eui-theme-common packages/eui-theme-borealis packages/docusaurus-preset packages/docusaurus-theme packages/eslint-plugin)

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"

# Bail early if there are uncommitted changes
if [[ -n "$(git status --porcelain)" ]]; then
error "Working tree is dirty. Please commit or stash your changes before running this script."
fi

step "1/8" "Ensuring npm is not authenticated..."
npm logout 2>/dev/null || true
yarn npm logout 2>/dev/null || true

step "2/8" "Checking out main branch..."
git checkout main

step "3/8" "Pulling latest changes from upstream..."
git pull upstream main

BRANCH_NAME="release/$(date +%s)"
step "4/8" "Creating release branch: ${BOLD}${BRANCH_NAME}${RESET}"
git checkout -b "$BRANCH_NAME"

step "5/8" "Installing dependencies and building release CLI..."
yarn
Comment thread
acstll marked this conversation as resolved.
yarn workspace @elastic/eui-release-cli run build

step "6/8" "Starting release process (dry-run)..."
echo ""
yarn release run official --dry-run --allow-custom --skip-auth-check --use-auth-token

# Check for uncommitted changes after the release CLI run
if [[ -n "$(git status --porcelain)" ]]; then
echo ""
warn "There are uncommitted changes in the working tree:"
git status --short
echo ""
read -r -p "Continue pushing without these changes? They won't be included in the PR. (y/N) " confirm
if [[ "$confirm" != "y" && "$confirm" != "Y" ]]; then
echo ""
echo "Stopped. Commit your changes and re-run from step 7 manually:"
echo " git push -u origin $BRANCH_NAME"
exit 0
fi
fi

step "7/8" "Pushing branch to origin..."
git push -u origin "$BRANCH_NAME"

step "8/8" "Opening release PR..."

PR_TITLE_PARTS=""
PR_BODY_LINES=""

for pkg_dir in "${PACKAGE_DIRS[@]}"; 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"
138 changes: 138 additions & 0 deletions scripts/release-publish.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
#!/usr/bin/env bash
#
# Official release publish script (post-merge)
#
# Automates final steps of the process:
# - Detects the EUI version and changed workspaces
# - Tags the merge commit
# - Pushes the tag to upstream
# - Triggers the GitHub Actions release workflow
# - Creates a GitHub release
#
# 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"

PACKAGE_DIRS=(packages/eui packages/eui-theme-common packages/eui-theme-borealis packages/docusaurus-preset packages/docusaurus-theme packages/eslint-plugin)

git remote get-url upstream &>/dev/null || error "'upstream' remote not found"

step "1/6" "Updating main branch..."
git checkout main
git pull upstream main
git fetch upstream --tags
Comment thread
acstll marked this conversation as resolved.

MERGE_SHA="${1:-$(git rev-parse HEAD)}"

step "2/6" "Detecting release details from ${BOLD}${MERGE_SHA:0:12}${RESET}..."

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}"

if git rev-parse "$TAG_NAME" &>/dev/null; then
error "Tag ${TAG_NAME} already exists. Has this release already been published?"
fi

PREV_TAG=$(git describe --tags --abbrev=0 "${MERGE_SHA}^" 2>/dev/null) || error "Could not find a previous release tag"

# Detect changed workspaces and collect changelog entries for the GitHub release
CHANGED_WORKSPACES=""
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: You can now get it directly from .release/published_packages.json, but I'm happy to leave it as-is for the time being

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's do so, and of course use the new .release/published_packages.json when porting to CI ✨

RELEASE_BODY=""

for pkg_dir in "${PACKAGE_DIRS[@]}"; do
pkg_json="${pkg_dir}/package.json"

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

# Extract changelog section for this version
changelog_file=$(ls -t "${pkg_dir}"/changelogs/CHANGELOG_*.md 2>/dev/null | head -1)
if [[ -n "$changelog_file" ]]; then
escaped_version="${new_version//./\\.}"
changelog_section=$(awk "/^## \[\`v${escaped_version}\`\]/{found=1; next} /^## \[/{if(found) exit} found" "$changelog_file")
if [[ -n "$changelog_section" ]]; then
RELEASE_BODY="${RELEASE_BODY}### \`${pkg_name}\` [v${new_version}](https://github.com/elastic/eui/blob/main/${changelog_file})
${changelog_section}
"
Comment thread
acstll marked this conversation as resolved.
fi
fi
fi
done

if [[ -z "$CHANGED_WORKSPACES" ]]; then
error "No changed workspaces detected. Are you sure the release PR was merged?"
fi

step "3/6" "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

step "4/6" "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

step "5/6" "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

step "6/6" "Creating GitHub release..."
gh release create "$TAG_NAME" \
--repo elastic/eui \
--title "$TAG_NAME" \
--notes "$RELEASE_BODY"

echo ""
echo -e "${GREEN}${BOLD}Release triggered!${RESET}"
echo ""
echo -e " Release: ${BOLD}https://github.com/elastic/eui/releases/tag/${TAG_NAME}${RESET}"
echo -e " Workflow: ${BOLD}https://github.com/elastic/eui/actions/workflows/release.yml${RESET}"