docs(deployment): add migration instructions for Vercel deployment #157
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: | | |
| set +e # Disable immediate exit on error for better control | |
| echo "Starting project detection..." || true | |
| 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) || true | |
| echo "Found projects: '$PROJECTS'" || true | |
| # Handle case where no projects are found | |
| if [ -z "$PROJECTS" ]; then | |
| echo "No projects found with package.json files" || true | |
| PROJECTS="" | |
| fi | |
| if [ "${{ steps.need_ci.outputs.force_run }}" = "true" ]; then | |
| CHANGED_PROJECTS="$PROJECTS" | |
| echo "Force run enabled, using all projects" || true | |
| else | |
| echo "Checking for changed files..." || true | |
| CHANGED=$(git diff --name-only ${{ github.event.before }} ${{ github.sha }} | cut -d/ -f1 | sort | uniq | grep -v -E '^(.github|.vscode|scripts|demo)$') || true | |
| echo "Changed directories: '$CHANGED'" || true | |
| if [ -n "$CHANGED" ] && [ -n "$PROJECTS" ]; then | |
| echo "Filtering projects based on changes..." || true | |
| echo "Projects list: $PROJECTS" || true | |
| echo "Changed directories: $CHANGED" || true | |
| # Use a more robust approach to avoid grep failures | |
| CHANGED_PROJECTS="" | |
| for project in $PROJECTS; do | |
| for change in $CHANGED; do | |
| if [ "$project" = "$change" ]; then | |
| if [ -z "$CHANGED_PROJECTS" ]; then | |
| CHANGED_PROJECTS="$project" | |
| else | |
| CHANGED_PROJECTS="$CHANGED_PROJECTS $project" | |
| fi | |
| echo "Matched project: $project" || true | |
| break | |
| fi | |
| done | |
| done | |
| echo "Matched projects: '$CHANGED_PROJECTS'" || true | |
| else | |
| echo "No changes or no projects found, setting empty" || true | |
| CHANGED_PROJECTS="" | |
| fi | |
| fi | |
| echo "Changed projects: $CHANGED_PROJECTS" || true | |
| CHANGED_PROJECTS_CLEAN=$(echo "$CHANGED_PROJECTS" | tr '\n' ' ' | sed 's/ $//' | sed 's/^[[:space:]]*//') || true | |
| echo "Changed projects clean: '$CHANGED_PROJECTS_CLEAN'" || true | |
| # Handle empty case | |
| if [ -z "$CHANGED_PROJECTS_CLEAN" ]; then | |
| echo "No changed projects to process" || true | |
| CHANGED_PROJECTS_CLEAN="" | |
| fi | |
| # Use printf instead of echo -n for better compatibility | |
| if command -v base64 >/dev/null 2>&1; then | |
| ENCODED=$(printf "%s" "$CHANGED_PROJECTS_CLEAN" | base64 | tr -d '\n') || true | |
| echo "Used base64 command for encoding" || true | |
| else | |
| # Fallback: just use the raw value | |
| ENCODED=$(printf "%s" "$CHANGED_PROJECTS_CLEAN") || true | |
| echo "Warning: base64 command not found, using raw value" || true | |
| fi | |
| # Make sure the GitHub output file exists and is writable | |
| if [ -n "$GITHUB_OUTPUT" ] && [ -w "$GITHUB_OUTPUT" ]; then | |
| echo "changed_projects_base64=$ENCODED" >> $GITHUB_OUTPUT || true | |
| echo "Successfully wrote to GitHub output file" || true | |
| else | |
| echo "Warning: Cannot write to GitHub output file or GITHUB_OUTPUT not set" || true | |
| echo "GITHUB_OUTPUT value: '$GITHUB_OUTPUT'" || true | |
| echo "changed_projects_base64=$ENCODED" || true | |
| fi | |
| echo "Encoded value: $ENCODED" || true | |
| # Debug the decoded value to ensure it works | |
| if [ -n "$ENCODED" ] && command -v base64 >/dev/null 2>&1; then | |
| DECODED=$(printf "%s" "$ENCODED" | base64 --decode 2>/dev/null || echo "") || true | |
| echo "Decoded value: '$DECODED'" || true | |
| else | |
| echo "Empty encoded value or base64 not available" || true | |
| fi | |
| echo "Project detection completed successfully" || true | |
| echo "Final output written to GitHub output" || true | |
| exit 0 # Explicitly exit with success | |
| # 🧩 Определяем тип 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 |