Add banner and standardize README header #258
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 | |