Skip to content

Add banner and standardize README header #258

Add banner and standardize README header

Add banner and standardize README header #258

Workflow file for this run

name: Release
on:
push:
branches: [main]
workflow_dispatch:
inputs:
type:
description: 'Release type'
required: true
type: choice
options:
- 'auto'
- 'app-only'
- 'cli-only'
default: 'auto'
permissions:
contents: write
jobs:
# ===========================================
# Detect changes and prepare release
# ===========================================
prepare:
runs-on: ubuntu-latest
outputs:
app_changed: ${{ steps.changes.outputs.app }}
cli_changed: ${{ steps.changes.outputs.cli }}
app_version: ${{ steps.tags_exist.outputs.app_version }}
cli_version: ${{ steps.tags_exist.outputs.cli_version }}
app_bumped: ${{ steps.tags_exist.outputs.app_bumped }}
cli_bumped: ${{ steps.tags_exist.outputs.cli_bumped }}
should_release_app: ${{ steps.decide.outputs.release_app }}
should_release_cli: ${{ steps.decide.outputs.release_cli }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Check for changes since last release
id: changes
run: |
# Get last app release tag
LAST_APP_TAG=$(git tag -l "app-v*" --sort=-version:refname | head -n1)
LAST_CLI_TAG=$(git tag -l "cli-v*" --sort=-version:refname | head -n1)
# Default to comparing with origin/main~10 if no tags exist
APP_BASE=${LAST_APP_TAG:-$(git rev-parse HEAD~10)}
CLI_BASE=${LAST_CLI_TAG:-$(git rev-parse HEAD~10)}
# Check for app changes (everything except cli/)
if git diff --name-only $APP_BASE HEAD 2>/dev/null | grep -v "^cli/" | grep -q .; then
echo "app=true" >> $GITHUB_OUTPUT
echo "✅ App changes detected"
else
echo "app=false" >> $GITHUB_OUTPUT
echo "ℹ️ No app changes"
fi
# Check for CLI changes (cli/ and packages/shared/ both affect the CLI)
if git diff --name-only $CLI_BASE HEAD 2>/dev/null | grep -qE "^(cli/|packages/shared/)"; then
echo "cli=true" >> $GITHUB_OUTPUT
echo "✅ CLI changes detected"
else
echo "cli=false" >> $GITHUB_OUTPUT
echo "ℹ️ No CLI changes"
fi
- name: Get versions
id: versions
run: |
APP_VERSION=$(node -p "require('./package.json').version")
CLI_VERSION=$(node -p "require('./cli/package.json').version")
echo "app=$APP_VERSION" >> $GITHUB_OUTPUT
echo "cli=$CLI_VERSION" >> $GITHUB_OUTPUT
echo "📦 App version: $APP_VERSION"
echo "📦 CLI version: $CLI_VERSION"
- name: Check if tags already exist and auto-bump if needed
id: tags_exist
run: |
APP_VERSION="${{ steps.versions.outputs.app }}"
CLI_VERSION="${{ steps.versions.outputs.cli }}"
APP_TAG="app-v$APP_VERSION"
CLI_TAG="cli-v$CLI_VERSION"
# Check app tag
if git rev-parse "$APP_TAG" >/dev/null 2>&1; then
if [[ "${{ steps.changes.outputs.app }}" == "true" ]]; then
# Changes detected but tag exists - need to bump version
echo "⚠️ App tag $APP_TAG exists but changes detected - will auto-bump"
# Calculate next patch version
IFS='.' read -r major minor patch <<< "$APP_VERSION"
NEW_APP_VERSION="$major.$minor.$((patch + 1))"
echo "app_version=$NEW_APP_VERSION" >> $GITHUB_OUTPUT
echo "app_exists=false" >> $GITHUB_OUTPUT
echo "app_bumped=true" >> $GITHUB_OUTPUT
echo "📦 Auto-bumped app to v$NEW_APP_VERSION"
else
echo "app_exists=true" >> $GITHUB_OUTPUT
echo "app_bumped=false" >> $GITHUB_OUTPUT
echo "app_version=$APP_VERSION" >> $GITHUB_OUTPUT
fi
else
echo "app_exists=false" >> $GITHUB_OUTPUT
echo "app_bumped=false" >> $GITHUB_OUTPUT
echo "app_version=$APP_VERSION" >> $GITHUB_OUTPUT
fi
# Check CLI tag
if git rev-parse "$CLI_TAG" >/dev/null 2>&1; then
if [[ "${{ steps.changes.outputs.cli }}" == "true" ]]; then
# Changes detected but tag exists - need to bump version
echo "⚠️ CLI tag $CLI_TAG exists but changes detected - will auto-bump"
IFS='.' read -r major minor patch <<< "$CLI_VERSION"
NEW_CLI_VERSION="$major.$minor.$((patch + 1))"
echo "cli_version=$NEW_CLI_VERSION" >> $GITHUB_OUTPUT
echo "cli_exists=false" >> $GITHUB_OUTPUT
echo "cli_bumped=true" >> $GITHUB_OUTPUT
echo "📦 Auto-bumped CLI to v$NEW_CLI_VERSION"
else
echo "cli_exists=true" >> $GITHUB_OUTPUT
echo "cli_bumped=false" >> $GITHUB_OUTPUT
echo "cli_version=$CLI_VERSION" >> $GITHUB_OUTPUT
fi
else
echo "cli_exists=false" >> $GITHUB_OUTPUT
echo "cli_bumped=false" >> $GITHUB_OUTPUT
echo "cli_version=$CLI_VERSION" >> $GITHUB_OUTPUT
fi
- name: Decide what to release
id: decide
run: |
RELEASE_TYPE="${{ github.event.inputs.type || 'auto' }}"
# App release decision
if [[ "$RELEASE_TYPE" == "cli-only" ]]; then
echo "release_app=false" >> $GITHUB_OUTPUT
elif [[ "${{ steps.tags_exist.outputs.app_exists }}" == "true" ]]; then
echo "release_app=false" >> $GITHUB_OUTPUT
echo "ℹ️ App tag already exists, skipping"
elif [[ "$RELEASE_TYPE" == "app-only" || "$RELEASE_TYPE" == "auto" && "${{ steps.changes.outputs.app }}" == "true" ]]; then
echo "release_app=true" >> $GITHUB_OUTPUT
echo "✅ Will release app"
else
echo "release_app=false" >> $GITHUB_OUTPUT
fi
# CLI release decision
if [[ "$RELEASE_TYPE" == "app-only" ]]; then
echo "release_cli=false" >> $GITHUB_OUTPUT
elif [[ "${{ steps.tags_exist.outputs.cli_exists }}" == "true" ]]; then
echo "release_cli=false" >> $GITHUB_OUTPUT
echo "ℹ️ CLI tag already exists, skipping"
elif [[ "$RELEASE_TYPE" == "cli-only" || "$RELEASE_TYPE" == "auto" && "${{ steps.changes.outputs.cli }}" == "true" ]]; then
echo "release_cli=true" >> $GITHUB_OUTPUT
echo "✅ Will release CLI"
else
echo "release_cli=false" >> $GITHUB_OUTPUT
fi
# ===========================================
# Release App (Web UI)
# ===========================================
release-app:
needs: prepare
if: ${{ needs.prepare.outputs.should_release_app == 'true' }}
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
- name: Bump version in package.json if needed
if: ${{ needs.prepare.outputs.app_bumped == 'true' }}
run: |
npm version ${{ needs.prepare.outputs.app_version }} --no-git-tag-version
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add package.json
git commit -m "chore: bump app version to ${{ needs.prepare.outputs.app_version }} [skip ci]"
# Retry push with rebase on conflict
for i in 1 2 3; do
git push origin main && break
echo "Push failed, retrying with rebase..."
git stash
git pull origin main --rebase
git stash pop || true
sleep 2
done
- name: Create tag
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
TAG_NAME="app-v${{ needs.prepare.outputs.app_version }}"
# Check if tag exists locally or remotely
if git rev-parse "$TAG_NAME" >/dev/null 2>&1; then
echo "ℹ️ Tag $TAG_NAME already exists locally, skipping creation"
elif git ls-remote --tags origin | grep -q "refs/tags/$TAG_NAME"; then
echo "ℹ️ Tag $TAG_NAME already exists on remote, fetching it"
git fetch origin "refs/tags/$TAG_NAME:refs/tags/$TAG_NAME"
else
echo "📦 Creating tag $TAG_NAME"
git tag -a "$TAG_NAME" -m "LynxPrompt App v${{ needs.prepare.outputs.app_version }}"
git push origin "$TAG_NAME"
fi
- name: Generate changelog
id: changelog
run: |
# Get previous tag
PREV_TAG=$(git tag -l "app-v*" --sort=-version:refname | sed -n '2p')
CURRENT_TAG="app-v${{ needs.prepare.outputs.app_version }}"
if [ -z "$PREV_TAG" ]; then
PREV_TAG=$(git rev-list --max-parents=0 HEAD)
fi
echo "📝 Generating changelog from $PREV_TAG to $CURRENT_TAG"
# Generate changelog
{
echo "## What's Changed"
echo ""
# Features
FEATURES=$(git log $PREV_TAG..$CURRENT_TAG --pretty=format:"- %s" --grep="^feat" -- ':!cli/' 2>/dev/null || true)
if [ -n "$FEATURES" ]; then
echo "### ✨ Features"
echo "$FEATURES" | head -20
echo ""
fi
# Fixes
FIXES=$(git log $PREV_TAG..$CURRENT_TAG --pretty=format:"- %s" --grep="^fix" -- ':!cli/' 2>/dev/null || true)
if [ -n "$FIXES" ]; then
echo "### 🐛 Bug Fixes"
echo "$FIXES" | head -20
echo ""
fi
# Other changes
OTHERS=$(git log $PREV_TAG..$CURRENT_TAG --pretty=format:"- %s" --invert-grep --grep="^feat" --invert-grep --grep="^fix" -- ':!cli/' 2>/dev/null | head -10 || true)
if [ -n "$OTHERS" ]; then
echo "### 🔧 Other Changes"
echo "$OTHERS"
echo ""
fi
echo "---"
echo ""
echo "**Full Changelog**: https://github.com/${{ github.repository }}/compare/$PREV_TAG...$CURRENT_TAG"
} > changelog.md
cat changelog.md
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
tag_name: app-v${{ needs.prepare.outputs.app_version }}
name: "LynxPrompt v${{ needs.prepare.outputs.app_version }}"
body_path: changelog.md
draft: false
prerelease: false
generate_release_notes: false
# ===========================================
# Release CLI
# ===========================================
release-cli:
needs: prepare
if: ${{ needs.prepare.outputs.should_release_cli == 'true' }}
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
- name: Bump CLI version in package.json if needed
if: ${{ needs.prepare.outputs.cli_bumped == 'true' }}
run: |
cd cli
npm version ${{ needs.prepare.outputs.cli_version }} --no-git-tag-version
cd ..
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add cli/package.json
git commit -m "chore(cli): bump version to ${{ needs.prepare.outputs.cli_version }} [skip ci]"
# Retry push with rebase on conflict
for i in 1 2 3; do
git push origin main && break
echo "Push failed, retrying with rebase..."
git stash
git pull origin main --rebase
git stash pop || true
sleep 2
done
- name: Create tag
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
TAG_NAME="cli-v${{ needs.prepare.outputs.cli_version }}"
# Check if tag exists locally or remotely
if git rev-parse "$TAG_NAME" >/dev/null 2>&1; then
echo "ℹ️ Tag $TAG_NAME already exists locally, skipping creation"
elif git ls-remote --tags origin | grep -q "refs/tags/$TAG_NAME"; then
echo "ℹ️ Tag $TAG_NAME already exists on remote, fetching it"
git fetch origin "refs/tags/$TAG_NAME:refs/tags/$TAG_NAME"
else
echo "📦 Creating tag $TAG_NAME"
git tag -a "$TAG_NAME" -m "LynxPrompt CLI v${{ needs.prepare.outputs.cli_version }}"
git push origin "$TAG_NAME"
fi
- name: Generate changelog
id: changelog
run: |
# Get previous tag
PREV_TAG=$(git tag -l "cli-v*" --sort=-version:refname | sed -n '2p')
CURRENT_TAG="cli-v${{ needs.prepare.outputs.cli_version }}"
if [ -z "$PREV_TAG" ]; then
PREV_TAG=$(git rev-list --max-parents=0 HEAD)
fi
echo "📝 Generating changelog from $PREV_TAG to $CURRENT_TAG"
# Generate changelog
{
echo "## What's Changed in CLI"
echo ""
# All CLI changes
CHANGES=$(git log $PREV_TAG..$CURRENT_TAG --pretty=format:"- %s" -- 'cli/' 2>/dev/null || true)
if [ -n "$CHANGES" ]; then
echo "### Changes"
echo "$CHANGES" | head -30
echo ""
fi
echo "---"
echo ""
echo "## Installation"
echo ""
echo '```bash'
echo "# npm (requires Node.js)"
echo "npm install -g lynxprompt@${{ needs.prepare.outputs.cli_version }}"
echo ""
echo "# Homebrew (macOS/Linux)"
echo "brew install GeiserX/lynxprompt/lynxprompt"
echo ""
echo "# Chocolatey (Windows)"
echo "choco install lynxprompt"
echo ""
echo "# Snap (Linux)"
echo "snap install lynxprompt"
echo '```'
echo ""
echo "### Standalone Binaries (no dependencies)"
echo ""
echo "Download the appropriate binary for your platform from the assets below:"
echo "- **Linux x64**: \`lynxprompt-${{ needs.prepare.outputs.cli_version }}-linux-x64.tar.gz\`"
echo "- **Linux ARM64**: \`lynxprompt-${{ needs.prepare.outputs.cli_version }}-linux-arm64.tar.gz\`"
echo "- **macOS x64**: \`lynxprompt-${{ needs.prepare.outputs.cli_version }}-macos-x64.tar.gz\`"
echo "- **macOS ARM64** (Apple Silicon): \`lynxprompt-${{ needs.prepare.outputs.cli_version }}-macos-arm64.tar.gz\`"
echo "- **Windows x64**: \`lynxprompt-${{ needs.prepare.outputs.cli_version }}-win-x64.zip\`"
echo ""
echo "**Full Changelog**: https://github.com/${{ github.repository }}/compare/$PREV_TAG...$CURRENT_TAG"
} > changelog.md
cat changelog.md
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
tag_name: cli-v${{ needs.prepare.outputs.cli_version }}
name: "LynxPrompt CLI v${{ needs.prepare.outputs.cli_version }}"
body_path: changelog.md
draft: false
prerelease: false
generate_release_notes: false
- name: Trigger CLI publish workflow
uses: peter-evans/repository-dispatch@v3
with:
event-type: cli-release
client-payload: '{"version": "${{ needs.prepare.outputs.cli_version }}"}'
# ===========================================
# Summary
# ===========================================
summary:
needs: [prepare, release-app, release-cli]
if: always()
runs-on: ubuntu-latest
steps:
- name: Summary
run: |
echo "## Release Summary" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
if [[ "${{ needs.release-app.result }}" == "success" ]]; then
echo "✅ **App v${{ needs.prepare.outputs.app_version }}** released" >> $GITHUB_STEP_SUMMARY
elif [[ "${{ needs.prepare.outputs.should_release_app }}" == "true" ]]; then
echo "❌ App release failed" >> $GITHUB_STEP_SUMMARY
else
echo "⏭️ App release skipped (no changes or tag exists)" >> $GITHUB_STEP_SUMMARY
fi
if [[ "${{ needs.release-cli.result }}" == "success" ]]; then
echo "✅ **CLI v${{ needs.prepare.outputs.cli_version }}** released" >> $GITHUB_STEP_SUMMARY
elif [[ "${{ needs.prepare.outputs.should_release_cli }}" == "true" ]]; then
echo "❌ CLI release failed" >> $GITHUB_STEP_SUMMARY
else
echo "⏭️ CLI release skipped (no changes or tag exists)" >> $GITHUB_STEP_SUMMARY
fi