fix(workflow): correct directory exclusion patterns in release workflow #150
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: Auto Bump Version, Build & Publish | |
| on: | |
| push: | |
| branches: | |
| - main | |
| jobs: | |
| bump: | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| packages: write | |
| env: | |
| MY_DASHBOARD_DATABASE_POSTGRES_URL: postgres://postgres:pgtoolspassword@localhost:5432/postgres?schema=public | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 24.11.1 | |
| # 🔍 Проверяем наличие [need ci] | |
| - name: Check if [need ci] present | |
| id: need_ci | |
| run: | | |
| if git log ${{ github.event.before }}..${{ github.sha }} --pretty=format:"%s%b" | grep -q "\[need ci\]"; then | |
| echo "force_run=true" >> $GITHUB_OUTPUT | |
| echo "✅ Found [need ci] — forcing full pipeline" | |
| else | |
| echo "force_run=false" >> $GITHUB_OUTPUT | |
| echo "ℹ️ No [need ci] found" | |
| fi | |
| # 🔍 Определяем изменённые проекты | |
| - name: Detect changed projects | |
| id: detect | |
| run: | | |
| PROJECTS=$(find . -maxdepth 1 -type d ! -path "." ! -path "./.github*" ! -path "./.vscode*" ! -path "./scripts*" ! -path "./demo*" | while read d; do | |
| if [ -f "$d/package.json" ]; then echo "$(basename "$d")"; fi | |
| done) | |
| if [ "${{ steps.need_ci.outputs.force_run }}" = "true" ]; then | |
| CHANGED_PROJECTS="$PROJECTS" | |
| else | |
| CHANGED=$(git diff --name-only ${{ github.event.before }} ${{ github.sha }} | cut -d/ -f1 | sort | uniq | grep -v -E '^(.github|.vscode|scripts|demo)$') | |
| CHANGED_PROJECTS=$(echo "$PROJECTS" | grep -Fxf <(echo "$CHANGED") || true) | |
| fi | |
| echo "Changed projects: $CHANGED_PROJECTS" | |
| CHANGED_PROJECTS_CLEAN=$(echo "$CHANGED_PROJECTS" | tr '\n' ' ' | sed 's/ $//') | |
| ENCODED=$(echo -n "$CHANGED_PROJECTS_CLEAN" | base64) | |
| echo "changed_projects_base64=$ENCODED" >> $GITHUB_OUTPUT | |
| # 🧩 Определяем тип bump'а | |
| - name: Determine bump type | |
| id: bump_type | |
| run: | | |
| MESSAGES=$(git log ${{ github.event.before }}..${{ github.sha }} --pretty=format:"%s%n%b") | |
| if echo "$MESSAGES" | grep -qi "BREAKING CHANGE"; then | |
| BUMP=major | |
| elif echo "$MESSAGES" | grep -Eqi "^feat"; then | |
| BUMP=minor | |
| elif echo "$MESSAGES" | grep -Eqi "^fix"; then | |
| BUMP=patch | |
| else | |
| BUMP=none | |
| fi | |
| echo "bump=$BUMP" >> $GITHUB_OUTPUT | |
| echo "Detected bump type: $BUMP" | |
| # 🚀 Bump, Build & Docker | |
| - name: Bump, Build & Docker | |
| if: steps.need_ci.outputs.force_run == 'true' || (steps.bump_type.outputs.bump != 'none' && steps.detect.outputs.changed_projects_base64 != '') | |
| id: bump_versions | |
| env: | |
| DOCKERHUB_USER: ${{ secrets.DOCKERHUB_USERNAME }} | |
| DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }} | |
| run: | | |
| sudo apt-get install -y jq | |
| npm install -g conventional-changelog-cli | |
| if [ -n "${DOCKERHUB_USER}" ] && [ -n "${DOCKERHUB_TOKEN}" ]; then | |
| echo "${DOCKERHUB_TOKEN}" | docker login -u "${DOCKERHUB_USER}" --password-stdin | |
| else | |
| echo "⚠️ DockerHub credentials missing — skipping Docker publish" | |
| fi | |
| BUMP=${{ steps.bump_type.outputs.bump }} | |
| CHANGED_PROJECTS=$(echo "${{ steps.detect.outputs.changed_projects_base64 }}" | base64 --decode) | |
| if [ -z "$CHANGED_PROJECTS" ]; then | |
| echo "ℹ️ No changed projects detected. Exiting." | |
| exit 0 | |
| fi | |
| TAGS="" | |
| cd web && npm ci && cd .. | |
| for project in $CHANGED_PROJECTS; do | |
| if [ -f "$project/package.json" ]; then | |
| cd $project | |
| NAME=$(jq -r '.name' package.json) | |
| DESC=$(jq -r '.description // "No description"' package.json) | |
| OLD_VERSION=$(jq -r '.version' package.json) | |
| if [ "$BUMP" != "none" ]; then | |
| npm version $BUMP --no-git-tag-version | |
| fi | |
| NEW_VERSION=$(jq -r '.version' package.json) | |
| conventional-changelog -p angular -i CHANGELOG.md -s -r 1 || true | |
| if jq -e '.scripts.build' package.json >/dev/null; then | |
| npm ci | |
| npm run build || echo "⚠️ Build failed for $project" | |
| fi | |
| if [ -f "Dockerfile" ] && [ -n "${DOCKERHUB_USER}" ]; then | |
| IMAGE_NAME="${DOCKERHUB_USER}/${NAME}" | |
| docker build -t "$IMAGE_NAME:$NEW_VERSION" -t "$IMAGE_NAME:latest" \ | |
| --label "org.opencontainers.image.title=$NAME" \ | |
| --label "org.opencontainers.image.description=$DESC" \ | |
| --label "org.opencontainers.image.version=$NEW_VERSION" \ | |
| --label "org.opencontainers.image.source=https://github.com/${{ github.repository }}" \ | |
| . || true | |
| docker push "$IMAGE_NAME:$NEW_VERSION" || true | |
| docker push "$IMAGE_NAME:latest" || true | |
| fi | |
| if [ -f "capacitor.config.ts" ]; then | |
| echo "${{ secrets.KEYSTORE }}" | base64 --decode > "${{ github.workspace }}/$project/my-dashboard.jks" | |
| docker run --rm \ | |
| -e KEYSTORE_PASSWORD="${{ secrets.KEYSTORE_PASSWORD }}" \ | |
| -e KEYSTORE_ALIAS="${{ secrets.KEYSTORE_ALIAS }}" \ | |
| -e KEYSTORE_ALIAS_PASSWORD="${{ secrets.KEYSTORE_ALIAS_PASSWORD }}" \ | |
| -e PRISMA_ENGINES_MIRROR="https://registry.npmmirror.com/-/binary/prisma" \ | |
| -v "${{ github.workspace }}/$project:/app" \ | |
| endykaufman/ionic-capacitor:latest | |
| mkdir -p ${{ github.workspace }}/$project/artifacts | |
| find ${{ github.workspace }}/$project/android/app/build/outputs/apk/release -type f -name "*.apk" -exec cp {} ${{ github.workspace }}/$project/artifacts/ \; | |
| fi | |
| cd - | |
| TAG="${project}@${NEW_VERSION}" | |
| git rev-parse "$TAG" >/dev/null 2>&1 || git tag "$TAG" | |
| TAGS="$TAGS $TAG" | |
| fi | |
| done | |
| echo "tags=$TAGS" >> $GITHUB_OUTPUT | |
| # 💾 Commit and push changes | |
| - name: Commit and push changes | |
| if: steps.need_ci.outputs.force_run == 'true' || (steps.bump_type.outputs.bump != 'none' && steps.detect.outputs.changed_projects_base64 != '') | |
| run: | | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| git add */package.json */CHANGELOG.md || true | |
| git commit -m "chore: auto bump version [skip ci]" || echo "No changes" | |
| git push origin main | |
| git push origin --tags | |
| # 📦 Upload artifacts | |
| - name: Upload artifacts | |
| if: steps.need_ci.outputs.force_run == 'true' || steps.bump_versions.outputs.tags != '' | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: project-builds | |
| path: "**/artifacts/**" | |
| if-no-files-found: ignore | |
| - name: Create releases & send Telegram message | |
| if: steps.need_ci.outputs.force_run == 'true' || steps.bump_versions.outputs.tags != '' | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| DOCKERHUB_USER: ${{ secrets.DOCKERHUB_USERNAME }} | |
| TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }} | |
| run: | | |
| # --- функция для экранирования Markdown-спецсимволов | |
| escape_markdown() { | |
| local text="$1" | |
| text="${text//_/\\_}" | |
| text="${text//\*/\\*}" | |
| text="${text//~/\\~}" | |
| echo "$text" | |
| } | |
| # --- проверка [hidden] | |
| if git log ${{ github.event.before }}..${{ github.sha }} --pretty=format:"%s%b" | grep -q "\[hidden\]"; then | |
| SKIP_TELEGRAM=1 | |
| else | |
| SKIP_TELEGRAM=0 | |
| fi | |
| TAGS="${{ steps.bump_versions.outputs.tags }}" | |
| if [ -z "$TAGS" ] && [ "${{ steps.need_ci.outputs.force_run }}" = "true" ]; then | |
| TAGS=$(find . -maxdepth 1 -type d ! -path "." ! -path "./.github*" ! -path "./.vscode*" ! -path "./scripts*" ! -path "./demo*" -exec bash -c 'cd "{}" && if [ -f package.json ]; then NAME=$(jq -r .name package.json); VER=$(jq -r .version package.json); echo "$(basename "$PWD")@$VER"; fi' \;) | |
| fi | |
| for TAG in $TAGS; do | |
| PROJECT=$(echo "$TAG" | cut -d@ -f1) | |
| PKG="$PROJECT/package.json" | |
| [ ! -f "$PKG" ] && continue | |
| NAME=$(jq -r '.name' "$PKG") | |
| VERSION=$(jq -r '.version' "$PKG") | |
| DESC=$(jq -r '.description // "No description"' "$PKG") | |
| CHANGELOG_PATH="$PROJECT/CHANGELOG.md" | |
| ARTIFACTS_PATH="$PROJECT/artifacts" | |
| RELEASE_URL="https://github.com/${{ github.repository }}/releases/tag/${TAG}" | |
| PROJECT_URL="https://github.com/${{ github.repository }}/tree/main/${PROJECT}" | |
| PIPELINE_URL="https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" | |
| # --- GitHub release через файл с переносами | |
| if [ -f "$CHANGELOG_PATH" ]; then | |
| BODY=$(git log --pretty=format:"• %s" ${{ github.event.before }}..${{ github.sha }} -- "$PROJECT") | |
| [ -z "$BODY" ] && BODY=$(git log -n 5 --pretty=format:"• %s") | |
| else | |
| BODY=$(git log --pretty=format:"• %s" ${{ github.event.before }}..${{ github.sha }} -- "$PROJECT") | |
| fi | |
| NOTES="🚀 ${NAME} v${VERSION} released! | |
| Changelog: | |
| ${BODY} | |
| " | |
| echo "$NOTES" > release_notes.txt | |
| echo "ARTIFACTS_PATH: $ARTIFACTS_PATH" | |
| if [ -d "$ARTIFACTS_PATH" ]; then | |
| FILES=$(find "$ARTIFACTS_PATH" -type f) | |
| echo "FILES: $FILES" | |
| gh release create "$TAG" --title "$TAG" --notes-file release_notes.txt $FILES || echo "Release exists" | |
| else | |
| gh release create "$TAG" --title "$TAG" --notes-file release_notes.txt || echo "Release exists" | |
| fi | |
| # --- Telegram | |
| if [ "$SKIP_TELEGRAM" -eq 0 ]; then | |
| TG_BODY=$(echo -e "🚀 ${NAME} v${VERSION} released!%0A%0A_${DESC}_%0A%0A") | |
| if [ -f "$CHANGELOG_PATH" ]; then | |
| BODY=$(git log --pretty=format:"• %s" ${{ github.event.before }}..${{ github.sha }} -- "$PROJECT") | |
| [ -z "$BODY" ] && BODY=$(git log -n 5 --pretty=format:"• %s") | |
| # Заменяем переносы на %0A | |
| NEW_TG_NOTES=$(echo "$BODY" | sed ':a;N;$!ba;s/\n/%0A/g') | |
| # Экранирование спецсимволов | |
| NEW_TG_NOTES=$(escape_markdown "$NEW_TG_NOTES") | |
| TG_BODY+="*Changelog:*%0A${NEW_TG_NOTES}%0A" | |
| else | |
| BODY=$(git log --pretty=format:"• %s" ${{ github.event.before }}..${{ github.sha }} -- "$PROJECT") | |
| # Заменяем переносы на %0A | |
| NEW_TG_NOTES=$(echo "$BODY" | sed ':a;N;$!ba;s/\n/%0A/g') | |
| # Экранирование спецсимволов | |
| NEW_TG_NOTES=$(escape_markdown "$NEW_TG_NOTES") | |
| TG_BODY+="*Recent commits:*%0A${NEW_TG_NOTES}%0A" | |
| fi | |
| BUTTONS="[" | |
| if [ -d "$ARTIFACTS_PATH" ]; then | |
| while IFS= read -r FILE; do | |
| EXT="${FILE##*.}" | |
| if [[ "$EXT" =~ ^(apk|exe|zip|html)$ ]]; then | |
| BASENAME=$(basename "$FILE") | |
| SAFE_TAG=$(echo "$TAG" | sed 's/@/%40/g') # <-- заменяем @ на %40 | |
| FILE_URL="https://github.com/${{ github.repository }}/releases/download/${SAFE_TAG}/${BASENAME}" | |
| BUTTONS="${BUTTONS}[{\"text\":\"💾 ${BASENAME}\",\"url\":\"${FILE_URL}\"}]," | |
| fi | |
| done < <(find "$ARTIFACTS_PATH" -type f) | |
| fi | |
| if [ -f "$PROJECT/Dockerfile" ]; then | |
| BUTTONS="${BUTTONS}[{\"text\":\"🐳 Docker image\",\"url\":\"https://hub.docker.com/r/${DOCKERHUB_USER}/${NAME}\"}]," | |
| fi | |
| HOMEPAGE=$(jq -r '.homepage // empty' "$PKG") | |
| if [ -n "$HOMEPAGE" ]; then | |
| BUTTONS="${BUTTONS}[{\"text\":\"🌐 Homepage\",\"url\":\"${HOMEPAGE}\"}]," | |
| fi | |
| BUTTONS="${BUTTONS}[{\"text\":\"📂 Project folder\",\"url\":\"${PROJECT_URL}\"}]," | |
| BUTTONS="${BUTTONS}[{\"text\":\"ℹ️ View release\",\"url\":\"${RELEASE_URL}\"}]" | |
| BUTTONS="${BUTTONS}]" | |
| BUTTONS=$(echo "$BUTTONS" | sed 's/,]/]/') | |
| echo "TG_BODY: $TG_BODY" | |
| echo "BUTTONS: $BUTTONS" | |
| # Отправка Telegram с обработкой ошибок | |
| if ! curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \ | |
| -d "chat_id=${{ vars.TELEGRAM_CHAT_ID }}" \ | |
| -d "message_thread_id=${{ vars.TELEGRAM_CHAT_THREAD_ID }}" \ | |
| -d "parse_mode=Markdown" \ | |
| -d "disable_web_page_preview=true" \ | |
| -d "text=${TG_BODY}" \ | |
| -d "reply_markup={\"inline_keyboard\":${BUTTONS}}"; then | |
| echo "⚠️ Telegram send failed!" | |
| echo "TG_BODY: $TG_BODY" | |
| echo "BUTTONS: $BUTTONS" | |
| fi | |
| fi | |
| done |