mirror of
https://github.com/tiennm99/ccs.git
synced 2026-10-11 12:09:03 +00:00
fix(ci): harden release automation workflows
This commit is contained in:
1 parent
2ee87a4532
commit
5616c68471
6 files changed
+223
-119
No files matched your search
@@ -11,8 +11,7 @@ concurrency:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release:
|
release:
|
||||||
# Skip if commit message contains [skip ci]
|
if: ${{ github.ref == 'refs/heads/dev' && (github.event_name != 'push' || !contains(github.event.head_commit.message, '[skip ci]')) }}
|
||||||
if: "!contains(github.event.head_commit.message, '[skip ci]')"
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
@@ -65,12 +64,7 @@ jobs:
|
|||||||
# Use custom dev release script instead of semantic-release
|
# Use custom dev release script instead of semantic-release
|
||||||
# This ensures dev versions follow {stable}-dev.{N} pattern
|
# This ensures dev versions follow {stable}-dev.{N} pattern
|
||||||
chmod +x scripts/dev-release.sh
|
chmod +x scripts/dev-release.sh
|
||||||
if ./scripts/dev-release.sh; then
|
./scripts/dev-release.sh
|
||||||
echo "released=true" >> $GITHUB_OUTPUT
|
|
||||||
else
|
|
||||||
echo "Dev release failed or skipped"
|
|
||||||
echo "released=false" >> $GITHUB_OUTPUT
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Notify Discord
|
- name: Notify Discord
|
||||||
if: success() && steps.release.outputs.released == 'true'
|
if: success() && steps.release.outputs.released == 'true'
|
||||||
@@ -87,28 +81,46 @@ jobs:
|
|||||||
if: success() && steps.release.outputs.released == 'true'
|
if: success() && steps.release.outputs.released == 'true'
|
||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
PREVIOUS_DEV_TAG: ${{ steps.release.outputs.previous_dev_tag }}
|
||||||
|
STABLE_TAG: ${{ steps.release.outputs.stable_tag }}
|
||||||
run: |
|
run: |
|
||||||
# Get version from package.json
|
# Get version from package.json
|
||||||
VERSION=$(jq -r '.version' package.json)
|
VERSION=$(jq -r '.version' package.json)
|
||||||
|
|
||||||
# Get commits ONLY since last dev tag (not all release commits)
|
# Get commits ONLY since last dev tag (not all release commits)
|
||||||
# This prevents re-tagging issues from older releases
|
# This prevents re-tagging issues from older releases
|
||||||
LAST_DEV_TAG=$(git tag -l "v*-dev.*" --sort=-v:refname | head -1 || echo "")
|
if [[ -n "$PREVIOUS_DEV_TAG" ]]; then
|
||||||
|
|
||||||
if [ -n "$LAST_DEV_TAG" ]; then
|
|
||||||
# Commits between last dev tag and current (excluding release commit)
|
# Commits between last dev tag and current (excluding release commit)
|
||||||
RANGE="${LAST_DEV_TAG}..HEAD~1"
|
RANGE="${PREVIOUS_DEV_TAG}..HEAD~1"
|
||||||
else
|
else
|
||||||
# First dev release - check commits since last stable tag
|
# First dev release - check commits since last stable tag
|
||||||
STABLE_TAG=$(git tag -l "v[0-9]*.[0-9]*.[0-9]" --merged origin/main --sort=-v:refname | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | head -1 || echo "")
|
|
||||||
RANGE="${STABLE_TAG:-HEAD~10}..HEAD~1"
|
RANGE="${STABLE_TAG:-HEAD~10}..HEAD~1"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Checking commits in range: $RANGE"
|
echo "Checking commits in range: $RANGE"
|
||||||
|
|
||||||
# Extract issue numbers from commit messages
|
COMMIT_TEXT=$(git log $RANGE --pretty=format:"%s%n%b" 2>/dev/null || true)
|
||||||
ISSUES=$(git log $RANGE --pretty=format:"%s %b" 2>/dev/null | \
|
ISSUES_FROM_COMMITS=$(printf '%s\n' "$COMMIT_TEXT" | \
|
||||||
grep -oE "(Fixes|Closes|Resolves|Refs?) #[0-9]+" | \
|
grep -oE "(Fixes|Closes|Resolves|Refs?) #[0-9]+" | \
|
||||||
|
grep -oE "#[0-9]+" || true)
|
||||||
|
|
||||||
|
PR_CANDIDATES=$(printf '%s\n' "$COMMIT_TEXT" | \
|
||||||
|
grep -oE "Merge pull request #[0-9]+|\\(#[0-9]+\\)" | \
|
||||||
|
grep -oE "[0-9]+" | sort -u || true)
|
||||||
|
|
||||||
|
PR_TEXT=""
|
||||||
|
for PR_NUM in $PR_CANDIDATES; do
|
||||||
|
DETAILS=$(gh pr view "$PR_NUM" --repo "${{ github.repository }}" --json title,body --jq '.title + "\n" + (.body // "")' 2>/dev/null || true)
|
||||||
|
if [[ -n "$DETAILS" ]]; then
|
||||||
|
PR_TEXT="${PR_TEXT}"$'\n'"${DETAILS}"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
ISSUES_FROM_PRS=$(printf '%s\n' "$PR_TEXT" | \
|
||||||
|
grep -oE "(Fixes|Closes|Resolves|Refs?) #[0-9]+" | \
|
||||||
|
grep -oE "#[0-9]+" || true)
|
||||||
|
|
||||||
|
ISSUES=$(printf '%s\n%s\n' "$ISSUES_FROM_COMMITS" "$ISSUES_FROM_PRS" | \
|
||||||
grep -oE "#[0-9]+" | sort -u || true)
|
grep -oE "#[0-9]+" | sort -u || true)
|
||||||
|
|
||||||
if [[ -z "$ISSUES" ]]; then
|
if [[ -z "$ISSUES" ]]; then
|
||||||
|
|||||||
@@ -4,39 +4,57 @@ on:
|
|||||||
release:
|
release:
|
||||||
types:
|
types:
|
||||||
- published
|
- published
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
tag:
|
||||||
|
description: Stable tag to publish manually, for example v7.55.0
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: docker-release-${{ github.ref }}
|
group: docker-release-${{ github.event_name == 'release' && github.event.release.tag_name || inputs.tag || github.ref }}
|
||||||
cancel-in-progress: false
|
cancel-in-progress: false
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
publish:
|
publish:
|
||||||
if: ${{ !github.event.release.prerelease && startsWith(github.event.release.tag_name, 'v') && !contains(github.event.release.tag_name, '-') }}
|
if: ${{ github.event_name != 'release' || !github.event.release.prerelease }}
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
packages: write
|
packages: write
|
||||||
|
|
||||||
env:
|
|
||||||
RELEASE_TAG: ${{ github.event.release.tag_name }}
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout release tag
|
- name: Resolve target tag
|
||||||
uses: actions/checkout@v4
|
id: target
|
||||||
with:
|
env:
|
||||||
ref: ${{ github.event.release.tag_name }}
|
MANUAL_TAG: ${{ inputs.tag }}
|
||||||
|
RELEASE_EVENT_TAG: ${{ github.event.release.tag_name }}
|
||||||
|
run: |
|
||||||
|
if [[ "${GITHUB_EVENT_NAME}" == "release" ]]; then
|
||||||
|
TARGET_TAG="${RELEASE_EVENT_TAG}"
|
||||||
|
else
|
||||||
|
TARGET_TAG="${MANUAL_TAG}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "tag=${TARGET_TAG}" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
- name: Validate stable semver tag
|
- name: Validate stable semver tag
|
||||||
id: tag
|
id: tag
|
||||||
run: |
|
run: |
|
||||||
if [[ "${RELEASE_TAG}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
if [[ "${{ steps.target.outputs.tag }}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||||
echo "publish=true" >> "$GITHUB_OUTPUT"
|
echo "publish=true" >> "$GITHUB_OUTPUT"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "publish=false" >> "$GITHUB_OUTPUT"
|
echo "publish=false" >> "$GITHUB_OUTPUT"
|
||||||
echo "Skipping non-stable semver tag ${RELEASE_TAG}"
|
echo "Skipping non-stable semver tag ${{ steps.target.outputs.tag }}"
|
||||||
|
|
||||||
|
- name: Checkout release tag
|
||||||
|
if: steps.tag.outputs.publish == 'true'
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
ref: ${{ steps.target.outputs.tag }}
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
if: steps.tag.outputs.publish == 'true'
|
if: steps.tag.outputs.publish == 'true'
|
||||||
@@ -49,18 +67,34 @@ jobs:
|
|||||||
- name: Derive image metadata
|
- name: Derive image metadata
|
||||||
if: steps.tag.outputs.publish == 'true'
|
if: steps.tag.outputs.publish == 'true'
|
||||||
id: meta
|
id: meta
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
run: |
|
run: |
|
||||||
VERSION="${RELEASE_TAG#v}"
|
VERSION="${{ steps.target.outputs.tag }}"
|
||||||
|
VERSION="${VERSION#v}"
|
||||||
MINOR="${VERSION%.*}"
|
MINOR="${VERSION%.*}"
|
||||||
MAJOR="${VERSION%%.*}"
|
MAJOR="${VERSION%%.*}"
|
||||||
OWNER_LOWER=$(echo "${GITHUB_REPOSITORY_OWNER}" | tr '[:upper:]' '[:lower:]')
|
OWNER_LOWER=$(echo "${GITHUB_REPOSITORY_OWNER}" | tr '[:upper:]' '[:lower:]')
|
||||||
IMAGE="ghcr.io/${OWNER_LOWER}/ccs-dashboard"
|
IMAGE="ghcr.io/${OWNER_LOWER}/ccs-dashboard"
|
||||||
|
REVISION=$(git rev-parse HEAD)
|
||||||
|
TAGS="${IMAGE}:${VERSION}
|
||||||
|
${IMAGE}:${MINOR}
|
||||||
|
${IMAGE}:${MAJOR}"
|
||||||
|
|
||||||
|
if [[ "${GITHUB_EVENT_NAME}" == "release" ]]; then
|
||||||
|
TAGS="${TAGS}
|
||||||
|
${IMAGE}:latest"
|
||||||
|
fi
|
||||||
|
|
||||||
{
|
{
|
||||||
echo "version=${VERSION}"
|
echo "version=${VERSION}"
|
||||||
echo "minor=${MINOR}"
|
echo "minor=${MINOR}"
|
||||||
echo "major=${MAJOR}"
|
echo "major=${MAJOR}"
|
||||||
echo "image=${IMAGE}"
|
echo "image=${IMAGE}"
|
||||||
|
echo "revision=${REVISION}"
|
||||||
|
echo "tags<<EOF"
|
||||||
|
echo "${TAGS}"
|
||||||
|
echo "EOF"
|
||||||
} >> "$GITHUB_OUTPUT"
|
} >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
- name: Log in to GitHub Container Registry
|
- name: Log in to GitHub Container Registry
|
||||||
@@ -79,17 +113,13 @@ jobs:
|
|||||||
file: docker/Dockerfile
|
file: docker/Dockerfile
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
push: true
|
push: true
|
||||||
tags: |
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
${{ steps.meta.outputs.image }}:${{ steps.meta.outputs.version }}
|
|
||||||
${{ steps.meta.outputs.image }}:${{ steps.meta.outputs.minor }}
|
|
||||||
${{ steps.meta.outputs.image }}:${{ steps.meta.outputs.major }}
|
|
||||||
${{ steps.meta.outputs.image }}:latest
|
|
||||||
labels: |
|
labels: |
|
||||||
org.opencontainers.image.title=ccs-dashboard
|
org.opencontainers.image.title=ccs-dashboard
|
||||||
org.opencontainers.image.description=CCS Dashboard container image
|
org.opencontainers.image.description=CCS Dashboard container image
|
||||||
org.opencontainers.image.url=https://github.com/${{ github.repository }}
|
org.opencontainers.image.url=https://github.com/${{ github.repository }}
|
||||||
org.opencontainers.image.source=https://github.com/${{ github.repository }}
|
org.opencontainers.image.source=https://github.com/${{ github.repository }}
|
||||||
org.opencontainers.image.version=${{ steps.meta.outputs.version }}
|
org.opencontainers.image.version=${{ steps.meta.outputs.version }}
|
||||||
org.opencontainers.image.revision=${{ github.sha }}
|
org.opencontainers.image.revision=${{ steps.meta.outputs.revision }}
|
||||||
cache-from: type=gha
|
cache-from: type=gha
|
||||||
cache-to: type=gha,mode=max
|
cache-to: type=gha,mode=max
|
||||||
@@ -2,7 +2,7 @@ name: Label Pending Release
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
types: [opened, reopened]
|
types: [opened, reopened, edited, synchronize]
|
||||||
branches: [dev]
|
branches: [dev]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
@@ -10,17 +10,22 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
issues: write
|
issues: write
|
||||||
|
pull-requests: read
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Label linked issues as pending-release
|
- name: Label linked issues as pending-release
|
||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||||
PR_TITLE: ${{ github.event.pull_request.title }}
|
PR_TITLE: ${{ github.event.pull_request.title }}
|
||||||
PR_BODY: ${{ github.event.pull_request.body }}
|
PR_BODY: ${{ github.event.pull_request.body }}
|
||||||
run: |
|
run: |
|
||||||
# Extract issue numbers from PR title and body (passed via env vars for safety)
|
# Extract issue numbers from PR title/body plus current PR commits.
|
||||||
# Using env vars prevents shell injection from backticks in markdown
|
# Using env vars prevents shell injection from backticks in markdown.
|
||||||
PR_TEXT="$PR_TITLE $PR_BODY"
|
COMMIT_TEXT=$(gh api "repos/${{ github.repository }}/pulls/${PR_NUMBER}/commits" --paginate --jq '.[].commit.message' 2>/dev/null || true)
|
||||||
|
PR_TEXT="$PR_TITLE
|
||||||
|
$PR_BODY
|
||||||
|
$COMMIT_TEXT"
|
||||||
ISSUES=$(echo "$PR_TEXT" | grep -oE "(Fixes|Closes|Resolves|Refs?) #[0-9]+" | grep -oE "#[0-9]+" | sort -u || true)
|
ISSUES=$(echo "$PR_TEXT" | grep -oE "(Fixes|Closes|Resolves|Refs?) #[0-9]+" | grep -oE "#[0-9]+" | sort -u || true)
|
||||||
|
|
||||||
if [[ -z "$ISSUES" ]]; then
|
if [[ -z "$ISSUES" ]]; then
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release:
|
release:
|
||||||
|
if: ${{ github.ref == 'refs/heads/main' }}
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
@@ -57,10 +58,17 @@ jobs:
|
|||||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||||
run: |
|
run: |
|
||||||
OUTPUT=$(bunx semantic-release 2>&1) || true
|
set +e
|
||||||
|
OUTPUT=$(bunx semantic-release 2>&1)
|
||||||
|
STATUS=$?
|
||||||
|
set -e
|
||||||
echo "$OUTPUT"
|
echo "$OUTPUT"
|
||||||
# Strip ANSI color codes before matching (semantic-release outputs colored text)
|
# Strip ANSI color codes before matching (semantic-release outputs colored text)
|
||||||
CLEAN=$(echo "$OUTPUT" | sed 's/\x1b\[[0-9;]*m//g')
|
CLEAN=$(echo "$OUTPUT" | sed 's/\x1b\[[0-9;]*m//g')
|
||||||
|
if [[ "$STATUS" -ne 0 ]]; then
|
||||||
|
echo "released=false" >> $GITHUB_OUTPUT
|
||||||
|
exit "$STATUS"
|
||||||
|
fi
|
||||||
if echo "$CLEAN" | grep -q "Published GitHub release"; then
|
if echo "$CLEAN" | grep -q "Published GitHub release"; then
|
||||||
echo "released=true" >> $GITHUB_OUTPUT
|
echo "released=true" >> $GITHUB_OUTPUT
|
||||||
else
|
else
|
||||||
@@ -83,31 +91,31 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
run: |
|
run: |
|
||||||
# semantic-release already adds "released" label via .releaserc.cjs
|
VERSION=$(jq -r '.version' package.json)
|
||||||
# This step removes transitional labels and closes issues now in stable.
|
RELEASE_BODY=$(gh release view "v${VERSION}" --repo "${{ github.repository }}" --json body --jq '.body' 2>/dev/null || echo "")
|
||||||
|
RELEASE_ISSUES=$(printf '%s\n' "$RELEASE_BODY" | \
|
||||||
|
perl -ne 'if (/(fixes|closes|resolves|refs?)(.*)/i) { print "$2\n"; }' | \
|
||||||
|
grep -oE '#[0-9]+' | tr -d '#' | sort -u || true)
|
||||||
|
RESOLVED_ISSUES=$(printf '%s\n' "$RELEASE_BODY" | \
|
||||||
|
perl -ne 'if (/(fixes|closes|resolves)(.*)/i) { print "$2\n"; }' | \
|
||||||
|
grep -oE '#[0-9]+' | tr -d '#' | sort -u || true)
|
||||||
|
|
||||||
# Find issues with both "released" and "released-dev" labels
|
if [[ -z "$RELEASE_ISSUES" ]]; then
|
||||||
ISSUES=$(gh issue list --label "released" --label "released-dev" --state all --json number --jq '.[].number' 2>/dev/null || echo "")
|
echo "No release-scoped issues found for v${VERSION}"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
for NUM in $ISSUES; do
|
for NUM in $RELEASE_ISSUES; do
|
||||||
echo "Cleaning up labels on issue #$NUM"
|
echo "Cleaning release state on issue #$NUM"
|
||||||
gh issue edit "$NUM" --remove-label "released-dev" --remove-label "pending-release" --repo "${{ github.repository }}" 2>/dev/null || true
|
gh issue edit "$NUM" \
|
||||||
done
|
--remove-label "released-dev" \
|
||||||
|
--remove-label "pending-release" \
|
||||||
# Also clean pending-release from any issues with released label
|
--repo "${{ github.repository }}" 2>/dev/null || true
|
||||||
PENDING=$(gh issue list --label "released" --label "pending-release" --state all --json number --jq '.[].number' 2>/dev/null || echo "")
|
|
||||||
|
HAS_RELEASED=$(gh issue view "$NUM" --repo "${{ github.repository }}" --json labels --jq '[.labels[].name | select(. == "released")] | length' 2>/dev/null || echo "0")
|
||||||
for NUM in $PENDING; do
|
if [[ "$HAS_RELEASED" -gt 0 ]] && printf '%s\n' "$RESOLVED_ISSUES" | grep -qx "$NUM"; then
|
||||||
echo "Removing pending-release from issue #$NUM"
|
gh issue close "$NUM" \
|
||||||
gh issue edit "$NUM" --remove-label "pending-release" --repo "${{ github.repository }}" 2>/dev/null || true
|
--comment "[bot] Closing issue because this fix/feature is now in stable release (@latest)." \
|
||||||
done
|
--repo "${{ github.repository }}" || true
|
||||||
|
fi
|
||||||
# Close open issues that are marked as released
|
|
||||||
OPEN_RELEASED=$(gh issue list --label "released" --state open --json number --jq '.[].number' 2>/dev/null || echo "")
|
|
||||||
|
|
||||||
for NUM in $OPEN_RELEASED; do
|
|
||||||
echo "Closing released issue #$NUM"
|
|
||||||
gh issue close "$NUM" \
|
|
||||||
--comment "[bot] Closing issue because this fix/feature is now in stable release (@latest)." \
|
|
||||||
--repo "${{ github.repository }}" || true
|
|
||||||
done
|
done
|
||||||
@@ -1,15 +1,12 @@
|
|||||||
name: Sync Dev After Main Release
|
name: Sync Dev After Main Release
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_run:
|
release:
|
||||||
workflows: ["Release"]
|
types: [published]
|
||||||
types: [completed]
|
|
||||||
branches: [main]
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
sync-dev:
|
sync-dev:
|
||||||
# Only run if Release workflow succeeded on main branch
|
if: ${{ !github.event.release.prerelease && github.event.release.target_commitish == 'main' && startsWith(github.event.release.tag_name, 'v') && !contains(github.event.release.tag_name, '-') }}
|
||||||
if: ${{ github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.head_branch == 'main' }}
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
|
|||||||
+102
-50
@@ -26,8 +26,8 @@ log_info() { echo -e "${GREEN}[i]${NC} $1"; }
|
|||||||
log_warn() { echo -e "${YELLOW}[!]${NC} $1"; }
|
log_warn() { echo -e "${YELLOW}[!]${NC} $1"; }
|
||||||
log_error() { echo -e "${RED}[X]${NC} $1"; }
|
log_error() { echo -e "${RED}[X]${NC} $1"; }
|
||||||
|
|
||||||
# Ensure we have the latest tags
|
# Ensure we have the latest tags and branch refs
|
||||||
git fetch --tags origin main
|
git fetch --tags origin main dev
|
||||||
|
|
||||||
# Get latest stable tag from main (exclude prereleases like -dev, -beta, -rc)
|
# Get latest stable tag from main (exclude prereleases like -dev, -beta, -rc)
|
||||||
# Match only clean semver tags: vX.Y.Z
|
# Match only clean semver tags: vX.Y.Z
|
||||||
@@ -39,58 +39,100 @@ if [ -z "$STABLE_TAG" ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
STABLE=${STABLE_TAG#v}
|
STABLE=${STABLE_TAG#v}
|
||||||
|
CURRENT_VERSION=$(jq -r '.version' package.json)
|
||||||
|
HEAD_SUBJECT=$(git log -1 --pretty=%s 2>/dev/null || echo "")
|
||||||
|
DEV_VERSION_REGEX="^${STABLE//./\\.}-dev\\.[0-9]+$"
|
||||||
|
PREVIOUS_DEV_TAG=""
|
||||||
|
RECOVERY_MODE=false
|
||||||
|
|
||||||
log_info "Current stable version: ${STABLE}"
|
log_info "Current stable version: ${STABLE}"
|
||||||
|
|
||||||
# Find latest dev tag for this stable version
|
if [[ "$CURRENT_VERSION" =~ $DEV_VERSION_REGEX ]] && [[ "$HEAD_SUBJECT" == "chore(release): ${CURRENT_VERSION} [skip ci]" ]]; then
|
||||||
LATEST_DEV=$(git tag -l "v${STABLE}-dev.*" --sort=-v:refname | head -1 || echo "")
|
VERSION="$CURRENT_VERSION"
|
||||||
|
CURRENT_TAG="v${VERSION}"
|
||||||
|
PREVIOUS_DEV_TAG=$(git tag -l "v${STABLE}-dev.*" --sort=-v:refname | grep -vx "$CURRENT_TAG" | head -1 || echo "")
|
||||||
|
RECOVERY_MODE=true
|
||||||
|
log_warn "Recovery mode for ${VERSION}"
|
||||||
|
|
||||||
# Calculate next dev number
|
if git rev-parse "${CURRENT_TAG}" >/dev/null 2>&1; then
|
||||||
if [ -z "$LATEST_DEV" ]; then
|
TAG_COMMIT=$(git rev-parse "${CURRENT_TAG}^{commit}")
|
||||||
DEV_NUM=1
|
HEAD_COMMIT=$(git rev-parse HEAD)
|
||||||
log_info "No existing dev tags for ${STABLE}, starting at dev.1"
|
if [[ "$TAG_COMMIT" != "$HEAD_COMMIT" ]]; then
|
||||||
|
log_error "Tag ${CURRENT_TAG} exists but does not point to HEAD"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
log_info "Reusing existing tag ${CURRENT_TAG}"
|
||||||
|
else
|
||||||
|
git tag "${CURRENT_TAG}"
|
||||||
|
log_warn "Recreated missing tag ${CURRENT_TAG} on release commit"
|
||||||
|
fi
|
||||||
else
|
else
|
||||||
DEV_NUM=$(echo "$LATEST_DEV" | sed 's/.*dev\.\([0-9]*\)/\1/')
|
# Find latest dev tag for this stable version
|
||||||
DEV_NUM=$((DEV_NUM + 1))
|
LATEST_DEV=$(git tag -l "v${STABLE}-dev.*" --sort=-v:refname | head -1 || echo "")
|
||||||
log_info "Latest dev tag: ${LATEST_DEV}, incrementing to dev.${DEV_NUM}"
|
PREVIOUS_DEV_TAG="$LATEST_DEV"
|
||||||
|
|
||||||
|
# Calculate next dev number
|
||||||
|
if [ -z "$LATEST_DEV" ]; then
|
||||||
|
DEV_NUM=1
|
||||||
|
log_info "No existing dev tags for ${STABLE}, starting at dev.1"
|
||||||
|
else
|
||||||
|
DEV_NUM=$(echo "$LATEST_DEV" | sed 's/.*dev\.\([0-9]*\)/\1/')
|
||||||
|
DEV_NUM=$((DEV_NUM + 1))
|
||||||
|
log_info "Latest dev tag: ${LATEST_DEV}, incrementing to dev.${DEV_NUM}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
VERSION="${STABLE}-dev.${DEV_NUM}"
|
||||||
|
CURRENT_TAG="v${VERSION}"
|
||||||
|
log_info "New version: ${VERSION}"
|
||||||
|
|
||||||
|
# Check if tag already exists (safety check)
|
||||||
|
if git rev-parse "${CURRENT_TAG}" >/dev/null 2>&1; then
|
||||||
|
log_error "Tag ${CURRENT_TAG} already exists!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Update package.json
|
||||||
|
npm version "$VERSION" --no-git-tag-version
|
||||||
|
log_info "Updated package.json to ${VERSION}"
|
||||||
|
|
||||||
|
# Configure git for GitHub Actions
|
||||||
|
git config user.name "github-actions[bot]"
|
||||||
|
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||||
|
|
||||||
|
# Commit version change
|
||||||
|
git add package.json
|
||||||
|
git commit -m "chore(release): ${VERSION} [skip ci]"
|
||||||
|
log_info "Created release commit"
|
||||||
|
|
||||||
|
# Create tag
|
||||||
|
git tag "${CURRENT_TAG}"
|
||||||
|
log_info "Created tag ${CURRENT_TAG}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
VERSION="${STABLE}-dev.${DEV_NUM}"
|
PACKAGE_NAME=$(jq -r '.name' package.json)
|
||||||
log_info "New version: ${VERSION}"
|
if npm view "${PACKAGE_NAME}@${VERSION}" version >/dev/null 2>&1; then
|
||||||
|
log_info "npm already has ${PACKAGE_NAME}@${VERSION}, skipping publish"
|
||||||
# Check if tag already exists (safety check)
|
else
|
||||||
if git rev-parse "v${VERSION}" >/dev/null 2>&1; then
|
npm publish --tag dev
|
||||||
log_error "Tag v${VERSION} already exists!"
|
log_info "Published to npm with @dev tag"
|
||||||
exit 1
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Update package.json
|
if git merge-base --is-ancestor HEAD origin/dev >/dev/null 2>&1; then
|
||||||
npm version "$VERSION" --no-git-tag-version
|
log_info "origin/dev already contains release commit"
|
||||||
log_info "Updated package.json to ${VERSION}"
|
else
|
||||||
|
git push origin HEAD:dev
|
||||||
|
log_info "Pushed release commit to origin/dev"
|
||||||
|
fi
|
||||||
|
|
||||||
# Configure git for GitHub Actions
|
if git ls-remote --exit-code --tags origin "refs/tags/${CURRENT_TAG}" >/dev/null 2>&1; then
|
||||||
git config user.name "github-actions[bot]"
|
log_info "Remote tag ${CURRENT_TAG} already exists"
|
||||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
else
|
||||||
|
git push origin "${CURRENT_TAG}"
|
||||||
# Commit version change
|
log_info "Pushed tag ${CURRENT_TAG}"
|
||||||
git add package.json
|
fi
|
||||||
git commit -m "chore(release): ${VERSION} [skip ci]"
|
|
||||||
log_info "Created release commit"
|
|
||||||
|
|
||||||
# Create tag
|
|
||||||
git tag "v${VERSION}"
|
|
||||||
log_info "Created tag v${VERSION}"
|
|
||||||
|
|
||||||
# Push commit and tag
|
|
||||||
git push origin dev
|
|
||||||
git push origin "v${VERSION}"
|
|
||||||
log_info "Pushed to origin"
|
|
||||||
|
|
||||||
# Publish to npm with dev tag
|
|
||||||
npm publish --tag dev
|
|
||||||
log_info "Published to npm with @dev tag"
|
|
||||||
|
|
||||||
# Generate release notes from commits since last tag
|
# Generate release notes from commits since last tag
|
||||||
PREV_TAG=$(git tag -l "v*" --sort=-v:refname | sed -n '2p' || echo "")
|
PREV_TAG="${PREVIOUS_DEV_TAG:-$STABLE_TAG}"
|
||||||
if [ -n "$PREV_TAG" ]; then
|
if [ -n "$PREV_TAG" ]; then
|
||||||
# Get commits between previous tag and the one before our release commit
|
# Get commits between previous tag and the one before our release commit
|
||||||
NOTES=$(git log --pretty=format:"- %s" "${PREV_TAG}..HEAD~1" 2>/dev/null | grep -v "chore(release):" | head -15 || echo "- Dev release")
|
NOTES=$(git log --pretty=format:"- %s" "${PREV_TAG}..HEAD~1" 2>/dev/null | grep -v "chore(release):" | head -15 || echo "- Dev release")
|
||||||
@@ -99,12 +141,16 @@ else
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Create GitHub prerelease
|
# Create GitHub prerelease
|
||||||
gh release create "v${VERSION}" \
|
if gh release view "${CURRENT_TAG}" >/dev/null 2>&1; then
|
||||||
--title "v${VERSION}" \
|
log_info "GitHub prerelease ${CURRENT_TAG} already exists"
|
||||||
--notes "${NOTES}" \
|
else
|
||||||
--prerelease
|
gh release create "${CURRENT_TAG}" \
|
||||||
|
--title "${CURRENT_TAG}" \
|
||||||
|
--notes "${NOTES}" \
|
||||||
|
--prerelease
|
||||||
|
|
||||||
log_info "Created GitHub prerelease"
|
log_info "Created GitHub prerelease"
|
||||||
|
fi
|
||||||
|
|
||||||
# Save release info for Discord notification
|
# Save release info for Discord notification
|
||||||
# This file is read by send-discord-release.cjs for dev releases
|
# This file is read by send-discord-release.cjs for dev releases
|
||||||
@@ -117,7 +163,13 @@ EOF
|
|||||||
log_info "Saved release info for Discord notification"
|
log_info "Saved release info for Discord notification"
|
||||||
|
|
||||||
# Output for GitHub Actions
|
# Output for GitHub Actions
|
||||||
echo "version=${VERSION}" >> "${GITHUB_OUTPUT:-/dev/null}"
|
{
|
||||||
echo "released=true" >> "${GITHUB_OUTPUT:-/dev/null}"
|
echo "current_tag=${CURRENT_TAG}"
|
||||||
|
echo "previous_dev_tag=${PREVIOUS_DEV_TAG}"
|
||||||
|
echo "recovery_mode=${RECOVERY_MODE}"
|
||||||
|
echo "released=true"
|
||||||
|
echo "stable_tag=${STABLE_TAG}"
|
||||||
|
echo "version=${VERSION}"
|
||||||
|
} >> "${GITHUB_OUTPUT:-/dev/null}"
|
||||||
|
|
||||||
log_info "Dev release ${VERSION} complete!"
|
log_info "Dev release ${VERSION} complete!"
|
||||||
Reference in new issue
Block a user