Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
132 changes: 132 additions & 0 deletions scripts/release-prep.sh
Original file line number Diff line number Diff line change
@@ -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
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

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

The repo’s CI/release workflows use yarn install --immutable (Yarn 4) to ensure installs don’t modify the lockfile or create unexpected changes. Using plain yarn here can introduce uncommitted changes (e.g. lockfile/install state) and cause the later dirty-worktree prompt to trigger for unrelated reasons. Consider switching this to yarn install --immutable (and any other flags the repo standardizes on) to keep the release branch clean and reproducible.

Suggested change
yarn
yarn install --immutable

Copilot uses AI. Check for mistakes.
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"
129 changes: 129 additions & 0 deletions scripts/release-publish.sh
Original file line number Diff line number Diff line change
@@ -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

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.

we should probably also do git fetch upstream --tags here

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.

addressed in efb7da0

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