Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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 [email protected]: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"
142 changes: 142 additions & 0 deletions scripts/release-publish.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
#!/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"

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

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