From ef57cb00425c60009eac3714fbc1acafe884948a Mon Sep 17 00:00:00 2001 From: Yevhen <38507392+JenyaL@users.noreply.github.com> Date: Sat, 27 Sep 2025 19:46:58 -0600 Subject: [PATCH 01/20] docs(fetch-vercel-logs.yml) --- .github/workflows/fetch-vercel-logs.yml | 321 ++++++++++++++++-------- 1 file changed, 220 insertions(+), 101 deletions(-) diff --git a/.github/workflows/fetch-vercel-logs.yml b/.github/workflows/fetch-vercel-logs.yml index aee8977..4cb7d31 100644 --- a/.github/workflows/fetch-vercel-logs.yml +++ b/.github/workflows/fetch-vercel-logs.yml @@ -1,144 +1,263 @@ -name: Fetch Vercel Logs +name: Fetch Vercel Logs (PR + auto on Vercel checks) on: - # This enables the "Run workflow" button in GitHub Actions UI + pull_request: + types: [opened, synchronize, reopened] + check_run: + types: [completed] workflow_dispatch: inputs: pr: - description: 'Pull Request number (optional)' - required: false - sha: - description: 'Commit SHA (optional, overrides PR if set)' - required: false + description: "PR number (e.g. 9)" + required: true permissions: contents: read - pull-requests: write # required to add a comment in PRs + pull-requests: write + checks: write jobs: fetch: + if: | + github.event_name == 'pull_request' || + github.event_name == 'workflow_dispatch' || + (github.event_name == 'check_run' && + (contains(github.event.check_run.name, 'Vercel') || + contains(github.event.check_run.app.slug, 'vercel'))) runs-on: ubuntu-latest env: - # Secrets stored in GitHub → Settings → Secrets and variables → Actions - VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }} - VERCEL_PROJECT: ${{ secrets.VERCEL_PROJECT_ID }} - VERCEL_ORG: ${{ secrets.VERCEL_ORG_ID }} + VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }} # personal token + VERCEL_PROJECT: ${{ secrets.VERCEL_PROJECT_ID }} # prj_... + VERCEL_ORG: ${{ secrets.VERCEL_ORG_ID }} # team_... + # optional: Settings → Secrets → Actions + VERCEL_DEPLOY_HOOK_URL: ${{ secrets.VERCEL_DEPLOY_HOOK_URL }} steps: - uses: actions/checkout@v4 - # Step 1: Resolve PR number and Commit SHA - - name: Resolve PR number and SHA - id: ctx - shell: bash - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Ensure jq run: | - PR_INPUT="${{ github.event.inputs.pr }}" - SHA_INPUT="${{ github.event.inputs.sha }}" - - # If SHA provided manually, use it - if [ -n "$SHA_INPUT" ]; then - echo "sha=$SHA_INPUT" >> $GITHUB_OUTPUT - else - # If PR number provided, fetch its HEAD SHA - if [ -n "$PR_INPUT" ]; then - SHA=$(gh pr view "$PR_INPUT" --json headRefOid --jq .headRefOid) - echo "sha=$SHA" >> $GITHUB_OUTPUT - echo "pr=$PR_INPUT" >> $GITHUB_OUTPUT - else - # Try to find PR automatically by current commit - PR=$(gh pr list --search "${{ github.sha }}" --json number --jq '.[0].number' || true) - if [ -n "$PR" ] && [ "$PR" != "null" ]; then - SHA=$(gh pr view "$PR" --json headRefOid --jq .headRefOid) - echo "pr=$PR" >> $GITHUB_OUTPUT - echo "sha=$SHA" >> $GITHUB_OUTPUT - else - # Fallback: just use current commit SHA - echo "sha=${{ github.sha }}" >> $GITHUB_OUTPUT - fi - fi + if ! command -v jq >/dev/null 2>&1; then + sudo apt-get update && sudo apt-get install -y jq fi - # Step 2: Find latest Vercel deployment for this PR/commit - - name: Find Vercel deployment + # (optional) redeploy from GitHub before fetching logs + - name: Optional redeploy via Deploy Hook + if: ${{ github.event_name == 'workflow_dispatch' && inputs.redeploy == 'yes' && env.VERCEL_DEPLOY_HOOK_URL != '' }} + run: | + echo "Triggering Vercel Deploy Hook…" + curl -sS -X POST "$VERCEL_DEPLOY_HOOK_URL" -o /dev/null + # give Vercel time to kick the new build & post a check + sleep 25 + + # resolve PR/SHA: inputs → event → latest open PR + - name: Resolve PR & SHA + id: ctx + uses: actions/github-script@v7 + with: + script: | + const {owner, repo} = context.repo; + const inputPr = core.getInput('pr'); + let pr = inputPr ? Number(inputPr) : null; + let sha = null; + + if (context.eventName === 'pull_request') { + pr = pr ?? context.payload.pull_request.number; + sha = context.payload.pull_request.head.sha; + } else if (context.eventName === 'check_run') { + const cr = context.payload.check_run; + sha = cr.head_sha; + if (!pr && sha) { + const prs = await github.rest.repos.listPullRequestsAssociatedWithCommit({ owner, repo, commit_sha: sha }); + pr = prs.data[0]?.number || null; + } + } + + if (!pr) { + const prs = await github.rest.pulls.list({ owner, repo, state: 'open', per_page: 1, sort: 'updated', direction: 'desc' }); + pr = prs.data[0]?.number || null; + } + if (!sha && pr) { + const prInfo = await github.rest.pulls.get({ owner, repo, pull_number: pr }); + sha = prInfo.data.head.sha; + } + + core.setOutput('pr', pr ? String(pr) : ''); + core.setOutput('sha', sha ? String(sha) : ''); + + - name: Seed deployment from check_run + id: seed + if: ${{ github.event_name == 'check_run' }} + uses: actions/github-script@v7 + with: + script: | + const url = context.payload.check_run?.details_url || ''; + let dep = ''; + let host = ''; + if (url) { + const m1 = url.match(/dpl_[A-Za-z0-9]+/); + if (m1) dep = m1[0]; + const m2 = url.match(/https?:\/\/([^\/\s]+\.vercel\.app)/i); + if (m2) host = m2[1]; + } + core.setOutput('id', dep); + core.setOutput('host', host); + + # find deployment by PR (meta.githubPrId), or by preview host, or latest preview + - name: Find deployment id: dep - shell: bash run: | - set -e - test -n "$VERCEL_TOKEN" || { echo "VERCEL_TOKEN is empty"; exit 1; } - test -n "$VERCEL_PROJECT" || { echo "VERCEL_PROJECT_ID is empty"; exit 1; } - test -n "$VERCEL_ORG" || { echo "VERCEL_ORG_ID is empty"; exit 1; } + if [ -n "${{ steps.seed.outputs.id }}" ]; then + echo "id=${{ steps.seed.outputs.id }}" >> $GITHUB_OUTPUT + exit 0 + fi - # Get list of latest preview deployments for the project curl -sS -H "Authorization: Bearer $VERCEL_TOKEN" \ - "https://api.vercel.com/v6/deployments?projectId=$VERCEL_PROJECT&target=preview&limit=50" \ - > deployments.json + "https://api.vercel.com/v6/deployments?projectId=$VERCEL_PROJECT&target=preview&limit=50" > deployments.json - # Prefer to match by PR number - DEP_ID=$(jq -r --arg PR "${{ steps.ctx.outputs.pr }}" '.deployments[] - | select(.meta.githubPrId == $PR) | .uid' deployments.json | head -n1) + PR="${{ steps.ctx.outputs.pr }}" + DEP_ID="" + if [ -n "$PR" ]; then + DEP_ID=$(jq -r --arg PR "$PR" '.deployments[] | select(.meta.githubPrId == $PR) | .uid' deployments.json | head -n1) + fi - # If not found, try to match by commit SHA if [ -z "$DEP_ID" ] || [ "$DEP_ID" = "null" ]; then - DEP_ID=$(jq -r --arg SHA "${{ steps.ctx.outputs.sha }}" '.deployments[] - | select(.meta.githubCommitSha == $SHA) | .uid' deployments.json | head -n1) + HOST="${{ steps.seed.outputs.host }}" + if [ -n "$HOST" ]; then + DEP_ID=$(jq -r --arg HN "$HOST" '.deployments[] | select(.url == $HN) | .uid' deployments.json | head -n1) + fi fi - # If still not found, fail if [ -z "$DEP_ID" ] || [ "$DEP_ID" = "null" ]; then - echo "No deployment found for this PR/SHA"; exit 1; + DEP_ID=$(jq -r '.deployments[0].uid' deployments.json) fi + test -n "$DEP_ID" && [ "$DEP_ID" != "null" ] || { echo "No deployment id found"; exit 1; } echo "id=$DEP_ID" >> $GITHUB_OUTPUT - # Step 3: Fetch deployment logs (events) and convert to plain text - - name: Fetch logs (events → text) - shell: bash + - name: Fetch events JSON + id: ev run: | - curl -sS -H "Authorization: Bearer $VERCEL_TOKEN" \ - "https://api.vercel.com/v2/deployments/${{ steps.dep.outputs.id }}/events?orgId=$VERCEL_ORG" \ - > vercel-events.json + DEP="${{ steps.dep.outputs.id || steps.seed.outputs.id }}" + URL="https://api.vercel.com/v2/deployments/${DEP}/events?teamId=${{ env.VERCEL_ORG }}&limit=1000" + curl -sS -H "Authorization: Bearer $VERCEL_TOKEN" "$URL" > vercel-events.json - # Extract only text log lines from events - jq -r '.events[] | .payload.text // empty' vercel-events.json > vercel-build.log || true + - name: Parse to plaintext + id: logs + run: | + jq -r ' + def evs: + if type=="array" then . + elif has("events") then .events + else [] end; + evs + | .[] + | ( .payload? // .entry? // . ) + | ( .text?, .message?, .error?, .error?.message?, (.entries? // [])[]?, (.logs? // [])[]? ) + | strings + ' vercel-events.json > vercel-build.log || true - # Prepare a short tail for PR comment (last 120 lines) - tail -n 120 vercel-build.log > vercel-build-tail.txt || true + if [ -s vercel-build.log ]; then + tail -n 200 vercel-build.log > tail.txt + head -c 60000 vercel-build.log > full.txt + else + echo "[no events text returned by Vercel API]" > tail.txt + cp tail.txt full.txt + fi - # Step 4: Upload full log file as artifact - - name: Upload full log - uses: actions/upload-artifact@v4 - with: - name: vercel-build.log - path: vercel-build.log - if-no-files-found: ignore + - name: Deployment info + id: info + run: | + DEP="${{ steps.dep.outputs.id || steps.seed.outputs.id }}" + curl -sS -H "Authorization: Bearer $VERCEL_TOKEN" \ + "https://api.vercel.com/v13/deployments/${DEP}?teamId=${{ env.VERCEL_ORG }}" > vercel-deployment.json + echo "url=$(jq -r '.url // empty' vercel-deployment.json)" >> $GITHUB_OUTPUT + echo "state=$(jq -r '.readyState // empty' vercel-deployment.json)" >> $GITHUB_OUTPUT - # Step 5: Post log tail as a PR comment (only if PR number is known) - - name: Comment tail to PR - if: ${{ steps.ctx.outputs.pr != '' }} + # Job summary — полезно в Checks → job + - name: Job summary (tail 200) + run: | + ICON="✅"; [ "${{ steps.info.outputs.state }}" = "ERROR" ] && ICON="❌" + echo "### ${ICON} Vercel Logs (tail 200)" >> "$GITHUB_STEP_SUMMARY" + if [ -n "${{ steps.info.outputs.url }}" ]; then + echo "Preview: https://${{ steps.info.outputs.url }}" >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + fi + echo '```' >> "$GITHUB_STEP_SUMMARY" + cat tail.txt >> "$GITHUB_STEP_SUMMARY" + echo '```' >> "$GITHUB_STEP_SUMMARY" + + - name: Publish check run (Vercel Logs) + if: ${{ steps.ctx.outputs.sha != '' }} uses: actions/github-script@v7 with: script: | const fs = require('fs'); - const pr = Number('${{ steps.ctx.outputs.pr }}'); - const dep = '${{ steps.dep.outputs.id }}'; - let tail = '(no log lines)'; - try { tail = fs.readFileSync('vercel-build-tail.txt','utf8'); } catch {} - - const body = [ - `❌ Vercel deployment **${dep}** failed (PR #${pr})`, - '', - 'Last 120 lines:', - '```', - tail || '(empty)', - '```', - '📎 Full log attached as artifact (vercel-build.log).' - ].join('\n'); - - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: pr, - body - }) + const {owner, repo} = context.repo; + const sha = '${{ steps.ctx.outputs.sha }}'; + const state= '${{ steps.info.outputs.state }}'; + const url = '${{ steps.info.outputs.url }}'; + const tail = fs.readFileSync('tail.txt','utf8'); + const isFail = state === 'ERROR'; + await github.rest.checks.create({ + owner, repo, + name: 'Vercel Logs', + head_sha: sha, + status: 'completed', + conclusion: isFail ? 'failure' : 'success', + output: { + title: `Vercel Logs (${state || 'unknown'})`, + summary: `${url ? `Preview: https://${url}\n\n` : ''}Last 200 lines:\n\n\`\`\`\n${tail}\n\`\`\`` + } + }); + + - name: Build PR comment markdown (failure only) + if: ${{ steps.ctx.outputs.pr != '' && steps.info.outputs.state == 'ERROR' }} + run: | + ICON="❌" + PREVIEW="" + if [ -n "${{ steps.info.outputs.url }}" ]; then + PREVIEW="https://${{ steps.info.outputs.url }}" + fi + { + echo "${ICON} Deployment: \`${{ steps.dep.outputs.id || steps.seed.outputs.id }}\` — state: **${{ steps.info.outputs.state }}**" + if [ -n "$PREVIEW" ]; then + echo + echo "Preview: $PREVIEW" + fi + echo + echo "
Show full log (truncated)" + echo + echo '```' + cat full.txt + echo '```' + echo + echo "
" + } > comment.md + + - name: Comment to PR on failure + if: ${{ steps.ctx.outputs.pr != '' && steps.info.outputs.state == 'ERROR' }} + uses: peter-evans/create-or-update-comment@v4 + with: + issue-number: ${{ steps.ctx.outputs.pr }} + body-file: comment.md + edit-mode: replace + search: 'Deployment:' + + - name: Fail job if Vercel ERROR + if: ${{ steps.info.outputs.state == 'ERROR' }} + run: | + echo "Vercel readyState = ERROR → failing job." + exit 1 + + - name: Upload raw (debug) + if: always() + uses: actions/upload-artifact@v4 + with: + name: vercel-raw + path: | + vercel-events.json + vercel-deployment.json + vercel-build.log + tail.txt + full.txt \ No newline at end of file From 90bdee7e7daa0860ed2068982099908bb2db4efd Mon Sep 17 00:00:00 2001 From: Yevhen <38507392+JenyaL@users.noreply.github.com> Date: Sat, 27 Sep 2025 19:50:30 -0600 Subject: [PATCH 02/20] docs(fetch-vercel-logs.yml) test 1.0 --- .github/workflows/fetch-vercel-logs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/fetch-vercel-logs.yml b/.github/workflows/fetch-vercel-logs.yml index 4cb7d31..89fcdd6 100644 --- a/.github/workflows/fetch-vercel-logs.yml +++ b/.github/workflows/fetch-vercel-logs.yml @@ -174,7 +174,7 @@ jobs: echo "url=$(jq -r '.url // empty' vercel-deployment.json)" >> $GITHUB_OUTPUT echo "state=$(jq -r '.readyState // empty' vercel-deployment.json)" >> $GITHUB_OUTPUT - # Job summary — полезно в Checks → job + # Job summary — полезно в Checks → job. - name: Job summary (tail 200) run: | ICON="✅"; [ "${{ steps.info.outputs.state }}" = "ERROR" ] && ICON="❌" From e00aba5762b06cd16234ef494b5c1e67724124a3 Mon Sep 17 00:00:00 2001 From: Yevhen <38507392+JenyaL@users.noreply.github.com> Date: Sat, 27 Sep 2025 19:55:13 -0600 Subject: [PATCH 03/20] docs(index.js) test 1.0 --- index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.js b/index.js index 12104bd..db7d83c 100644 --- a/index.js +++ b/index.js @@ -1,6 +1,6 @@ require("dotenv").config(); - if (process.env.BUILD_RESULT === "success") { + if (process.env.BUILD_RESULT !== "success") { console.log("Build success"); process.exit(0); } From 0d3785e624feb50464a1c43dd37f9b90600a6a39 Mon Sep 17 00:00:00 2001 From: Yevhen <38507392+JenyaL@users.noreply.github.com> Date: Sat, 27 Sep 2025 19:56:16 -0600 Subject: [PATCH 04/20] docs(index.js) test 1.1 --- index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.js b/index.js index db7d83c..12104bd 100644 --- a/index.js +++ b/index.js @@ -1,6 +1,6 @@ require("dotenv").config(); - if (process.env.BUILD_RESULT !== "success") { + if (process.env.BUILD_RESULT === "success") { console.log("Build success"); process.exit(0); } From 4cb5f882daed84b7681d39117afc03e1b7f0be93 Mon Sep 17 00:00:00 2001 From: Yevhen <38507392+JenyaL@users.noreply.github.com> Date: Wed, 8 Oct 2025 20:00:47 -0600 Subject: [PATCH 05/20] chore(ci): add GitHub Actions workflow for build and Vercel deploy --- .github/workflows/ci-deploy.yml | 116 ++++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 .github/workflows/ci-deploy.yml diff --git a/.github/workflows/ci-deploy.yml b/.github/workflows/ci-deploy.yml new file mode 100644 index 0000000..8e36992 --- /dev/null +++ b/.github/workflows/ci-deploy.yml @@ -0,0 +1,116 @@ +name: CI + Deploy (POC) + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +permissions: + contents: read + +env: + VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }} + VERCEL_PROJECT: ${{ secrets.VERCEL_PROJECT_ID }} + VERCEL_ORG: ${{ secrets.VERCEL_ORG_ID }} + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: 18 + cache: npm + + - name: Install + run: | + if [ -f package-lock.json ]; then npm ci; else npm install; fi + + - name: Build + run: npm run build + + - name: Upload build summary + run: | + echo "Build ✅" >> "$GITHUB_STEP_SUMMARY" + + deploy: + needs: build + if: ${{ success() }} + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: 18 + + - name: Install Vercel CLI + run: npm i -g vercel@latest + + - name: Decide target + id: tgt + run: | + if [ "${{ github.event_name }}" = "pull_request" ]; then + echo "target=preview" >> $GITHUB_OUTPUT + else + echo "target=production" >> $GITHUB_OUTPUT + fi + + - name: Deploy to Vercel (from GitHub) + id: deploy + env: + VC_TOKEN: ${{ env.VERCEL_TOKEN }} + VC_PROJ: ${{ env.VERCEL_PROJECT }} + VC_TEAM: ${{ env.VERCEL_ORG }} + TARGET: ${{ steps.tgt.outputs.target }} + run: | + set -e + + + DEPLOY_OUTPUT=$(vercel --yes \ + --token="$VC_TOKEN" \ + --scope="$VC_TEAM" \ + --project="$VC_PROJ" \ + --target="$TARGET" \ + --confirm 2>&1) + + echo "$DEPLOY_OUTPUT" + + URL=$(printf "%s\n" "$DEPLOY_OUTPUT" | grep -Eo 'https?://[a-zA-Z0-9.-]+\.vercel\.app' | tail -n1 || true) + echo "url=$URL" >> $GITHUB_OUTPUT + + HOST=$(echo "$URL" | sed -E 's#https?://##') + echo "host=$HOST" >> $GITHUB_OUTPUT + + - name: Resolve Deployment ID + id: dep + if: ${{ steps.deploy.outputs.host != '' }} + env: + VC_TOKEN: ${{ env.VERCEL_TOKEN }} + VC_TEAM: ${{ env.VERCEL_ORG }} + run: | + # /v13/deployments?teamId=...&url= + HOST="${{ steps.deploy.outputs.host }}" + UID=$(curl -sS -H "Authorization: Bearer $VC_TOKEN" \ + "https://api.vercel.com/v13/deployments?teamId=$VC_TEAM&url=$HOST" \ + | jq -r '.deployments[0].uid // empty') + echo "id=$UID" >> $GITHUB_OUTPUT + + - name: Summary (URL + ID) + run: | + echo "### Vercel Deployment" >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + echo "- Target: **${{ steps.tgt.outputs.target }}**" >> "$GITHUB_STEP_SUMMARY" + if [ -n "${{ steps.deploy.outputs.url }}" ]; then + echo "- URL: ${{ steps.deploy.outputs.url }}" >> "$GITHUB_STEP_SUMMARY" + else + echo "- URL: (not detected)" >> "$GITHUB_STEP_SUMMARY" + fi + if [ -n "${{ steps.dep.outputs.id }}" ]; then + echo "- Deployment ID: \`${{ steps.dep.outputs.id }}\`" >> "$GITHUB_STEP_SUMMARY" + else + echo "- Deployment ID: (not detected)" >> "$GITHUB_STEP_SUMMARY" + fi \ No newline at end of file From 9581a3254c65f83c47cd85c3c40ba6bbc054553c Mon Sep 17 00:00:00 2001 From: Yevhen <38507392+JenyaL@users.noreply.github.com> Date: Wed, 8 Oct 2025 20:08:42 -0600 Subject: [PATCH 06/20] ci: add initial build + deploy workflow (POC) --- index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.js b/index.js index 12104bd..4d4aaf1 100644 --- a/index.js +++ b/index.js @@ -5,4 +5,4 @@ require("dotenv").config(); process.exit(0); } -throw new Error("Build failed"); +// throw new Error("Build failed"); From 856d629233a67ee3a3bbedb2c1710f03acf00ddb Mon Sep 17 00:00:00 2001 From: Yevhen <38507392+JenyaL@users.noreply.github.com> Date: Wed, 8 Oct 2025 20:13:49 -0600 Subject: [PATCH 07/20] ci: add build and deploy workflow to Vercel (POC) --- .github/workflows/ci-deploy.yml | 56 +++++++++++++++------------------ 1 file changed, 26 insertions(+), 30 deletions(-) diff --git a/.github/workflows/ci-deploy.yml b/.github/workflows/ci-deploy.yml index 8e36992..9eccf15 100644 --- a/.github/workflows/ci-deploy.yml +++ b/.github/workflows/ci-deploy.yml @@ -33,8 +33,7 @@ jobs: run: npm run build - name: Upload build summary - run: | - echo "Build ✅" >> "$GITHUB_STEP_SUMMARY" + run: echo "Build ✅" >> "$GITHUB_STEP_SUMMARY" deploy: needs: build @@ -47,6 +46,12 @@ jobs: with: node-version: 18 + - name: Ensure jq + run: | + if ! command -v jq >/dev/null 2>&1; then + sudo apt-get update && sudo apt-get install -y jq + fi + - name: Install Vercel CLI run: npm i -g vercel@latest @@ -67,37 +72,28 @@ jobs: VC_TEAM: ${{ env.VERCEL_ORG }} TARGET: ${{ steps.tgt.outputs.target }} run: | - set -e + set -euo pipefail + # Сформируем базовую команду деплоя + CMD=(vercel deploy --yes --token="$VC_TOKEN" --scope="$VC_TEAM" --project="$VC_PROJ" --confirm --output=json) + # Прод деплой – только для push в main + if [ "$TARGET" = "production" ]; then + CMD+=(--prod) + fi - DEPLOY_OUTPUT=$(vercel --yes \ - --token="$VC_TOKEN" \ - --scope="$VC_TEAM" \ - --project="$VC_PROJ" \ - --target="$TARGET" \ - --confirm 2>&1) + # Запускаем и получаем JSON + DEPLOY_JSON="$("${CMD[@]}")" + echo "$DEPLOY_JSON" > deploy.json - echo "$DEPLOY_OUTPUT" + # Вытаскиваем основные поля (CLI обычно отдаёт url и/или aliases[]) + URL=$(jq -r '.url // empty' deploy.json) + if [ -z "$URL" ] || [ "$URL" = "null" ]; then + URL=$(jq -r '.aliases[0] // empty' deploy.json) + fi + ID=$(jq -r '.id // .uid // empty' deploy.json) - URL=$(printf "%s\n" "$DEPLOY_OUTPUT" | grep -Eo 'https?://[a-zA-Z0-9.-]+\.vercel\.app' | tail -n1 || true) echo "url=$URL" >> $GITHUB_OUTPUT - - HOST=$(echo "$URL" | sed -E 's#https?://##') - echo "host=$HOST" >> $GITHUB_OUTPUT - - - name: Resolve Deployment ID - id: dep - if: ${{ steps.deploy.outputs.host != '' }} - env: - VC_TOKEN: ${{ env.VERCEL_TOKEN }} - VC_TEAM: ${{ env.VERCEL_ORG }} - run: | - # /v13/deployments?teamId=...&url= - HOST="${{ steps.deploy.outputs.host }}" - UID=$(curl -sS -H "Authorization: Bearer $VC_TOKEN" \ - "https://api.vercel.com/v13/deployments?teamId=$VC_TEAM&url=$HOST" \ - | jq -r '.deployments[0].uid // empty') - echo "id=$UID" >> $GITHUB_OUTPUT + echo "id=$ID" >> $GITHUB_OUTPUT - name: Summary (URL + ID) run: | @@ -109,8 +105,8 @@ jobs: else echo "- URL: (not detected)" >> "$GITHUB_STEP_SUMMARY" fi - if [ -n "${{ steps.dep.outputs.id }}" ]; then - echo "- Deployment ID: \`${{ steps.dep.outputs.id }}\`" >> "$GITHUB_STEP_SUMMARY" + if [ -n "${{ steps.deploy.outputs.id }}" ]; then + echo "- Deployment ID: \`${{ steps.deploy.outputs.id }}\`" >> "$GITHUB_STEP_SUMMARY" else echo "- Deployment ID: (not detected)" >> "$GITHUB_STEP_SUMMARY" fi \ No newline at end of file From 2d965ff684168c23ec6d751ce49b012db8307904 Mon Sep 17 00:00:00 2001 From: Yevhen <38507392+JenyaL@users.noreply.github.com> Date: Wed, 8 Oct 2025 20:19:38 -0600 Subject: [PATCH 08/20] fix: update Vercel deploy step and remove deprecated flags --- .github/workflows/ci-deploy.yml | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/.github/workflows/ci-deploy.yml b/.github/workflows/ci-deploy.yml index 9eccf15..b64e81c 100644 --- a/.github/workflows/ci-deploy.yml +++ b/.github/workflows/ci-deploy.yml @@ -68,24 +68,19 @@ jobs: id: deploy env: VC_TOKEN: ${{ env.VERCEL_TOKEN }} - VC_PROJ: ${{ env.VERCEL_PROJECT }} VC_TEAM: ${{ env.VERCEL_ORG }} TARGET: ${{ steps.tgt.outputs.target }} run: | set -euo pipefail - # Сформируем базовую команду деплоя - CMD=(vercel deploy --yes --token="$VC_TOKEN" --scope="$VC_TEAM" --project="$VC_PROJ" --confirm --output=json) - # Прод деплой – только для push в main + CMD=(vercel deploy --yes --token="$VC_TOKEN" --scope="$VC_TEAM" --prebuilt --confirm --output=json) if [ "$TARGET" = "production" ]; then CMD+=(--prod) fi - # Запускаем и получаем JSON DEPLOY_JSON="$("${CMD[@]}")" echo "$DEPLOY_JSON" > deploy.json - # Вытаскиваем основные поля (CLI обычно отдаёт url и/или aliases[]) URL=$(jq -r '.url // empty' deploy.json) if [ -z "$URL" ] || [ "$URL" = "null" ]; then URL=$(jq -r '.aliases[0] // empty' deploy.json) From dc55384ae684c3ae8a23333e7dd5ff01af66ff87 Mon Sep 17 00:00:00 2001 From: Yevhen <38507392+JenyaL@users.noreply.github.com> Date: Wed, 8 Oct 2025 20:23:40 -0600 Subject: [PATCH 09/20] chore(ci): fix Vercel deploy step for latest CLI and remove deprecated options --- .github/workflows/ci-deploy.yml | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/.github/workflows/ci-deploy.yml b/.github/workflows/ci-deploy.yml index b64e81c..90d6221 100644 --- a/.github/workflows/ci-deploy.yml +++ b/.github/workflows/ci-deploy.yml @@ -10,9 +10,8 @@ permissions: contents: read env: - VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }} - VERCEL_PROJECT: ${{ secrets.VERCEL_PROJECT_ID }} - VERCEL_ORG: ${{ secrets.VERCEL_ORG_ID }} + VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }} + VERCEL_ORG: ${{ secrets.VERCEL_ORG_ID }} jobs: build: @@ -73,22 +72,18 @@ jobs: run: | set -euo pipefail - CMD=(vercel deploy --yes --token="$VC_TOKEN" --scope="$VC_TEAM" --prebuilt --confirm --output=json) + ARGS=(deploy --yes --token "$VC_TOKEN" --scope "$VC_TEAM" --prebuilt) if [ "$TARGET" = "production" ]; then - CMD+=(--prod) + ARGS+=(--prod) fi - DEPLOY_JSON="$("${CMD[@]}")" - echo "$DEPLOY_JSON" > deploy.json + URL="$(vercel "${ARGS[@]}")" + echo "url=$URL" >> "$GITHUB_OUTPUT" - URL=$(jq -r '.url // empty' deploy.json) - if [ -z "$URL" ] || [ "$URL" = "null" ]; then - URL=$(jq -r '.aliases[0] // empty' deploy.json) - fi - ID=$(jq -r '.id // .uid // empty' deploy.json) - - echo "url=$URL" >> $GITHUB_OUTPUT - echo "id=$ID" >> $GITHUB_OUTPUT + HOST="${URL#https://}" + ID="$(curl -fsSL -H "Authorization: Bearer $VC_TOKEN" \ + "https://api.vercel.com/v13/deployments/$HOST" | jq -r '.id // empty')" + echo "id=$ID" >> "$GITHUB_OUTPUT" - name: Summary (URL + ID) run: | From e6c2979be4eb91a1a7449dd2bd9d64243fde94a4 Mon Sep 17 00:00:00 2001 From: Yevhen <38507392+JenyaL@users.noreply.github.com> Date: Wed, 8 Oct 2025 20:28:00 -0600 Subject: [PATCH 10/20] ci: add vercel prebuild step for true prebuilt deployment flow --- .github/workflows/ci-deploy.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/ci-deploy.yml b/.github/workflows/ci-deploy.yml index 90d6221..cc1983b 100644 --- a/.github/workflows/ci-deploy.yml +++ b/.github/workflows/ci-deploy.yml @@ -63,6 +63,10 @@ jobs: echo "target=production" >> $GITHUB_OUTPUT fi + - name: Prebuild for Vercel + run: | + vercel build --token "${{ env.VERCEL_TOKEN }}" --scope "${{ env.VERCEL_ORG }}" + - name: Deploy to Vercel (from GitHub) id: deploy env: From b3a7478a61bab224974d76bfed5ad5bdd49cbdec Mon Sep 17 00:00:00 2001 From: Yevhen <38507392+JenyaL@users.noreply.github.com> Date: Wed, 8 Oct 2025 20:31:35 -0600 Subject: [PATCH 11/20] ci: add vercel pull step before build to fetch project settings --- .github/workflows/ci-deploy.yml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci-deploy.yml b/.github/workflows/ci-deploy.yml index cc1983b..f808a0d 100644 --- a/.github/workflows/ci-deploy.yml +++ b/.github/workflows/ci-deploy.yml @@ -63,9 +63,18 @@ jobs: echo "target=production" >> $GITHUB_OUTPUT fi + - name: Pull Vercel Project Settings + run: | + vercel pull --yes \ + --environment "${{ steps.tgt.outputs.target }}" \ + --token "${{ env.VERCEL_TOKEN }}" \ + --scope "${{ env.VERCEL_ORG }}" + - name: Prebuild for Vercel run: | - vercel build --token "${{ env.VERCEL_TOKEN }}" --scope "${{ env.VERCEL_ORG }}" + vercel build \ + --token "${{ env.VERCEL_TOKEN }}" \ + --scope "${{ env.VERCEL_ORG }}" - name: Deploy to Vercel (from GitHub) id: deploy From d19a146b2f9b89b3dc69c21c18c0089d5c563c84 Mon Sep 17 00:00:00 2001 From: Yevhen <38507392+JenyaL@users.noreply.github.com> Date: Wed, 8 Oct 2025 20:42:54 -0600 Subject: [PATCH 12/20] ci: split build and deploy stages with tests before prebuilt Vercel deploy --- .github/workflows/ci-deploy.yml | 77 +++++++++++++++++---------------- 1 file changed, 39 insertions(+), 38 deletions(-) diff --git a/.github/workflows/ci-deploy.yml b/.github/workflows/ci-deploy.yml index f808a0d..4037cda 100644 --- a/.github/workflows/ci-deploy.yml +++ b/.github/workflows/ci-deploy.yml @@ -1,4 +1,4 @@ -name: CI + Deploy (POC) +name: CI + Deploy (prebuilt) on: push: @@ -14,28 +14,46 @@ env: VERCEL_ORG: ${{ secrets.VERCEL_ORG_ID }} jobs: - build: + build_and_test: runs-on: ubuntu-latest + outputs: + is_pr: ${{ github.event_name == 'pull_request' }} steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 18 - cache: npm + cache: 'npm' - name: Install run: | - if [ -f package-lock.json ]; then npm ci; else npm install; fi + npm ci || npm install - - name: Build + # Линтер (если есть "lint" в package.json — запустится, иначе шаг пропустится) + - name: Lint + run: | + if npm run | grep -q " lint"; then npm run lint; else echo "No lint script"; fi + + # Юнит-тесты (если есть "test" — запустится, иначе пропуск) + - name: Unit tests + run: | + if npm run | grep -q " test"; then npm test --ci --passWithNoTests=false; else echo "No test script"; fi + + # E2E (опционально; с флагом пропуска, если нет сценария) + - name: E2E tests (optional) + run: | + if npm run | grep -q " e2e"; then npm run e2e; else echo "No e2e script"; fi + + # Локальный билд проекта (ваш обычный билдер: Next/.vite/webpack и т.д.) + - name: App build run: npm run build - - name: Upload build summary - run: echo "Build ✅" >> "$GITHUB_STEP_SUMMARY" + - name: Build summary + run: echo "Build & tests passed ✅" >> "$GITHUB_STEP_SUMMARY" deploy: - needs: build + needs: build_and_test if: ${{ success() }} runs-on: ubuntu-latest steps: @@ -44,16 +62,12 @@ jobs: - uses: actions/setup-node@v4 with: node-version: 18 - - - name: Ensure jq - run: | - if ! command -v jq >/dev/null 2>&1; then - sudo apt-get update && sudo apt-get install -y jq - fi + cache: 'npm' - name: Install Vercel CLI run: npm i -g vercel@latest + # Определяем окружение деплоя: preview для PR, production для пушей в main - name: Decide target id: tgt run: | @@ -63,53 +77,40 @@ jobs: echo "target=production" >> $GITHUB_OUTPUT fi - - name: Pull Vercel Project Settings + # Тянем настройки проекта (создаёт .vercel/project.json) + - name: Pull Vercel project settings run: | vercel pull --yes \ --environment "${{ steps.tgt.outputs.target }}" \ --token "${{ env.VERCEL_TOKEN }}" \ --scope "${{ env.VERCEL_ORG }}" - - name: Prebuild for Vercel + # Собираем prebuilt артефакт в .vercel/output + - name: Vercel prebuild run: | vercel build \ --token "${{ env.VERCEL_TOKEN }}" \ --scope "${{ env.VERCEL_ORG }}" - - name: Deploy to Vercel (from GitHub) + # Деплой строго из prebuilt (Vercel не билдит) + - name: Deploy (prebuilt) id: deploy env: VC_TOKEN: ${{ env.VERCEL_TOKEN }} VC_TEAM: ${{ env.VERCEL_ORG }} TARGET: ${{ steps.tgt.outputs.target }} run: | - set -euo pipefail - + set -e ARGS=(deploy --yes --token "$VC_TOKEN" --scope "$VC_TEAM" --prebuilt) if [ "$TARGET" = "production" ]; then ARGS+=(--prod) fi - URL="$(vercel "${ARGS[@]}")" echo "url=$URL" >> "$GITHUB_OUTPUT" + echo "Deployed: $URL" - HOST="${URL#https://}" - ID="$(curl -fsSL -H "Authorization: Bearer $VC_TOKEN" \ - "https://api.vercel.com/v13/deployments/$HOST" | jq -r '.id // empty')" - echo "id=$ID" >> "$GITHUB_OUTPUT" - - - name: Summary (URL + ID) + - name: Summary run: | - echo "### Vercel Deployment" >> "$GITHUB_STEP_SUMMARY" - echo "" >> "$GITHUB_STEP_SUMMARY" + echo "### Deployment" >> "$GITHUB_STEP_SUMMARY" echo "- Target: **${{ steps.tgt.outputs.target }}**" >> "$GITHUB_STEP_SUMMARY" - if [ -n "${{ steps.deploy.outputs.url }}" ]; then - echo "- URL: ${{ steps.deploy.outputs.url }}" >> "$GITHUB_STEP_SUMMARY" - else - echo "- URL: (not detected)" >> "$GITHUB_STEP_SUMMARY" - fi - if [ -n "${{ steps.deploy.outputs.id }}" ]; then - echo "- Deployment ID: \`${{ steps.deploy.outputs.id }}\`" >> "$GITHUB_STEP_SUMMARY" - else - echo "- Deployment ID: (not detected)" >> "$GITHUB_STEP_SUMMARY" - fi \ No newline at end of file + echo "- URL: ${{ steps.deploy.outputs.url }}" >> "$GITHUB_STEP_SUMMARY" \ No newline at end of file From 9003c1ef4f70e4a74b5909842ef52131a30585bf Mon Sep 17 00:00:00 2001 From: Yevhen <38507392+JenyaL@users.noreply.github.com> Date: Thu, 9 Oct 2025 18:06:18 -0600 Subject: [PATCH 13/20] ci: deleted comments --- .github/workflows/ci-deploy.yml | 8 -------- 1 file changed, 8 deletions(-) diff --git a/.github/workflows/ci-deploy.yml b/.github/workflows/ci-deploy.yml index 4037cda..ccd034a 100644 --- a/.github/workflows/ci-deploy.yml +++ b/.github/workflows/ci-deploy.yml @@ -30,22 +30,18 @@ jobs: run: | npm ci || npm install - # Линтер (если есть "lint" в package.json — запустится, иначе шаг пропустится) - name: Lint run: | if npm run | grep -q " lint"; then npm run lint; else echo "No lint script"; fi - # Юнит-тесты (если есть "test" — запустится, иначе пропуск) - name: Unit tests run: | if npm run | grep -q " test"; then npm test --ci --passWithNoTests=false; else echo "No test script"; fi - # E2E (опционально; с флагом пропуска, если нет сценария) - name: E2E tests (optional) run: | if npm run | grep -q " e2e"; then npm run e2e; else echo "No e2e script"; fi - # Локальный билд проекта (ваш обычный билдер: Next/.vite/webpack и т.д.) - name: App build run: npm run build @@ -67,7 +63,6 @@ jobs: - name: Install Vercel CLI run: npm i -g vercel@latest - # Определяем окружение деплоя: preview для PR, production для пушей в main - name: Decide target id: tgt run: | @@ -77,7 +72,6 @@ jobs: echo "target=production" >> $GITHUB_OUTPUT fi - # Тянем настройки проекта (создаёт .vercel/project.json) - name: Pull Vercel project settings run: | vercel pull --yes \ @@ -85,14 +79,12 @@ jobs: --token "${{ env.VERCEL_TOKEN }}" \ --scope "${{ env.VERCEL_ORG }}" - # Собираем prebuilt артефакт в .vercel/output - name: Vercel prebuild run: | vercel build \ --token "${{ env.VERCEL_TOKEN }}" \ --scope "${{ env.VERCEL_ORG }}" - # Деплой строго из prebuilt (Vercel не билдит) - name: Deploy (prebuilt) id: deploy env: From bcd14faca29501b43fc99b52c8cac3e8122d3d34 Mon Sep 17 00:00:00 2001 From: Yevhen <38507392+JenyaL@users.noreply.github.com> Date: Thu, 9 Oct 2025 18:09:28 -0600 Subject: [PATCH 14/20] ci: Added version number into index.html --- public/index.html | 1 + 1 file changed, 1 insertion(+) diff --git a/public/index.html b/public/index.html index 9c9eb81..92d7b49 100644 --- a/public/index.html +++ b/public/index.html @@ -6,5 +6,6 @@

Hello from build-test

+

1.0 v

From 7b73c9a2a44fd424677924219b3b86c48d3b61d1 Mon Sep 17 00:00:00 2001 From: Yevhen <38507392+JenyaL@users.noreply.github.com> Date: Thu, 9 Oct 2025 18:18:28 -0600 Subject: [PATCH 15/20] ci: add automatic PR comment with Vercel preview URL for each commit --- .github/workflows/ci-deploy.yml | 39 +++- .github/workflows/ci.yml | 45 ---- .github/workflows/fetch-vercel-logs.yml | 263 ------------------------ 3 files changed, 38 insertions(+), 309 deletions(-) delete mode 100644 .github/workflows/ci.yml delete mode 100644 .github/workflows/fetch-vercel-logs.yml diff --git a/.github/workflows/ci-deploy.yml b/.github/workflows/ci-deploy.yml index ccd034a..100b9ed 100644 --- a/.github/workflows/ci-deploy.yml +++ b/.github/workflows/ci-deploy.yml @@ -105,4 +105,41 @@ jobs: run: | echo "### Deployment" >> "$GITHUB_STEP_SUMMARY" echo "- Target: **${{ steps.tgt.outputs.target }}**" >> "$GITHUB_STEP_SUMMARY" - echo "- URL: ${{ steps.deploy.outputs.url }}" >> "$GITHUB_STEP_SUMMARY" \ No newline at end of file + echo "- URL: ${{ steps.deploy.outputs.url }}" >> "$GITHUB_STEP_SUMMARY" + + + - name: Post Preview URL to PR + if: ${{ github.event_name == 'pull_request' && steps.deploy.outputs.url != '' }} + uses: actions/github-script@v7 + with: + script: | + const url = process.env.URL || '${{ steps.deploy.outputs.url }}'; + const pr = context.payload.pull_request.number; + const commit = context.payload.pull_request.head.sha.slice(0, 7); + const message = `✅ **Preview ready for commit \`${commit}\`**\n🔗 ${url}`; + await github.rest.issues.createComment({ + issue_number: pr, + owner: context.repo.owner, + repo: context.repo.repo, + body: message + }); + env: + URL: ${{ steps.deploy.outputs.url }} + + - name: Post Preview URL to PR + if: ${{ github.event_name == 'pull_request' && steps.deploy.outputs.url != '' }} + uses: actions/github-script@v7 + with: + script: | + const url = process.env.URL || '${{ steps.deploy.outputs.url }}'; + const pr = context.payload.pull_request.number; + const commit = context.payload.pull_request.head.sha.slice(0, 7); + const message = `✅ **Preview ready for commit \`${commit}\`**\n🔗 ${url}`; + await github.rest.issues.createComment({ + issue_number: pr, + owner: context.repo.owner, + repo: context.repo.repo, + body: message + }); + env: + URL: ${{ steps.deploy.outputs.url }} \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index 90b67d9..0000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,45 +0,0 @@ -name: CI - -on: - push: - branches: [ main ] - pull_request: - branches: [ main ] - -jobs: - build: - runs-on: ubuntu-latest - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: 20 - cache: npm - - - name: Install dependencies - run: | - if [ -f package-lock.json ]; then - npm ci - else - npm install - fi - - - name: Validate BUILD_RESULT - env: - BUILD_RESULT: ${{ secrets.BUILD_RESULT }} - run: | - if [ -z "${BUILD_RESULT}" ]; then - echo "BUILD_RESULT is empty." - exit 1 - else - echo "BUILD_RESULT is set." - fi - - - name: Run build - env: - BUILD_RESULT: ${{ secrets.BUILD_RESULT }} - run: npm run build diff --git a/.github/workflows/fetch-vercel-logs.yml b/.github/workflows/fetch-vercel-logs.yml deleted file mode 100644 index 89fcdd6..0000000 --- a/.github/workflows/fetch-vercel-logs.yml +++ /dev/null @@ -1,263 +0,0 @@ -name: Fetch Vercel Logs (PR + auto on Vercel checks) - -on: - pull_request: - types: [opened, synchronize, reopened] - check_run: - types: [completed] - workflow_dispatch: - inputs: - pr: - description: "PR number (e.g. 9)" - required: true - -permissions: - contents: read - pull-requests: write - checks: write - -jobs: - fetch: - if: | - github.event_name == 'pull_request' || - github.event_name == 'workflow_dispatch' || - (github.event_name == 'check_run' && - (contains(github.event.check_run.name, 'Vercel') || - contains(github.event.check_run.app.slug, 'vercel'))) - runs-on: ubuntu-latest - env: - VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }} # personal token - VERCEL_PROJECT: ${{ secrets.VERCEL_PROJECT_ID }} # prj_... - VERCEL_ORG: ${{ secrets.VERCEL_ORG_ID }} # team_... - # optional: Settings → Secrets → Actions - VERCEL_DEPLOY_HOOK_URL: ${{ secrets.VERCEL_DEPLOY_HOOK_URL }} - - steps: - - uses: actions/checkout@v4 - - - name: Ensure jq - run: | - if ! command -v jq >/dev/null 2>&1; then - sudo apt-get update && sudo apt-get install -y jq - fi - - # (optional) redeploy from GitHub before fetching logs - - name: Optional redeploy via Deploy Hook - if: ${{ github.event_name == 'workflow_dispatch' && inputs.redeploy == 'yes' && env.VERCEL_DEPLOY_HOOK_URL != '' }} - run: | - echo "Triggering Vercel Deploy Hook…" - curl -sS -X POST "$VERCEL_DEPLOY_HOOK_URL" -o /dev/null - # give Vercel time to kick the new build & post a check - sleep 25 - - # resolve PR/SHA: inputs → event → latest open PR - - name: Resolve PR & SHA - id: ctx - uses: actions/github-script@v7 - with: - script: | - const {owner, repo} = context.repo; - const inputPr = core.getInput('pr'); - let pr = inputPr ? Number(inputPr) : null; - let sha = null; - - if (context.eventName === 'pull_request') { - pr = pr ?? context.payload.pull_request.number; - sha = context.payload.pull_request.head.sha; - } else if (context.eventName === 'check_run') { - const cr = context.payload.check_run; - sha = cr.head_sha; - if (!pr && sha) { - const prs = await github.rest.repos.listPullRequestsAssociatedWithCommit({ owner, repo, commit_sha: sha }); - pr = prs.data[0]?.number || null; - } - } - - if (!pr) { - const prs = await github.rest.pulls.list({ owner, repo, state: 'open', per_page: 1, sort: 'updated', direction: 'desc' }); - pr = prs.data[0]?.number || null; - } - if (!sha && pr) { - const prInfo = await github.rest.pulls.get({ owner, repo, pull_number: pr }); - sha = prInfo.data.head.sha; - } - - core.setOutput('pr', pr ? String(pr) : ''); - core.setOutput('sha', sha ? String(sha) : ''); - - - name: Seed deployment from check_run - id: seed - if: ${{ github.event_name == 'check_run' }} - uses: actions/github-script@v7 - with: - script: | - const url = context.payload.check_run?.details_url || ''; - let dep = ''; - let host = ''; - if (url) { - const m1 = url.match(/dpl_[A-Za-z0-9]+/); - if (m1) dep = m1[0]; - const m2 = url.match(/https?:\/\/([^\/\s]+\.vercel\.app)/i); - if (m2) host = m2[1]; - } - core.setOutput('id', dep); - core.setOutput('host', host); - - # find deployment by PR (meta.githubPrId), or by preview host, or latest preview - - name: Find deployment - id: dep - run: | - if [ -n "${{ steps.seed.outputs.id }}" ]; then - echo "id=${{ steps.seed.outputs.id }}" >> $GITHUB_OUTPUT - exit 0 - fi - - curl -sS -H "Authorization: Bearer $VERCEL_TOKEN" \ - "https://api.vercel.com/v6/deployments?projectId=$VERCEL_PROJECT&target=preview&limit=50" > deployments.json - - PR="${{ steps.ctx.outputs.pr }}" - DEP_ID="" - if [ -n "$PR" ]; then - DEP_ID=$(jq -r --arg PR "$PR" '.deployments[] | select(.meta.githubPrId == $PR) | .uid' deployments.json | head -n1) - fi - - if [ -z "$DEP_ID" ] || [ "$DEP_ID" = "null" ]; then - HOST="${{ steps.seed.outputs.host }}" - if [ -n "$HOST" ]; then - DEP_ID=$(jq -r --arg HN "$HOST" '.deployments[] | select(.url == $HN) | .uid' deployments.json | head -n1) - fi - fi - - if [ -z "$DEP_ID" ] || [ "$DEP_ID" = "null" ]; then - DEP_ID=$(jq -r '.deployments[0].uid' deployments.json) - fi - - test -n "$DEP_ID" && [ "$DEP_ID" != "null" ] || { echo "No deployment id found"; exit 1; } - echo "id=$DEP_ID" >> $GITHUB_OUTPUT - - - name: Fetch events JSON - id: ev - run: | - DEP="${{ steps.dep.outputs.id || steps.seed.outputs.id }}" - URL="https://api.vercel.com/v2/deployments/${DEP}/events?teamId=${{ env.VERCEL_ORG }}&limit=1000" - curl -sS -H "Authorization: Bearer $VERCEL_TOKEN" "$URL" > vercel-events.json - - - name: Parse to plaintext - id: logs - run: | - jq -r ' - def evs: - if type=="array" then . - elif has("events") then .events - else [] end; - evs - | .[] - | ( .payload? // .entry? // . ) - | ( .text?, .message?, .error?, .error?.message?, (.entries? // [])[]?, (.logs? // [])[]? ) - | strings - ' vercel-events.json > vercel-build.log || true - - if [ -s vercel-build.log ]; then - tail -n 200 vercel-build.log > tail.txt - head -c 60000 vercel-build.log > full.txt - else - echo "[no events text returned by Vercel API]" > tail.txt - cp tail.txt full.txt - fi - - - name: Deployment info - id: info - run: | - DEP="${{ steps.dep.outputs.id || steps.seed.outputs.id }}" - curl -sS -H "Authorization: Bearer $VERCEL_TOKEN" \ - "https://api.vercel.com/v13/deployments/${DEP}?teamId=${{ env.VERCEL_ORG }}" > vercel-deployment.json - echo "url=$(jq -r '.url // empty' vercel-deployment.json)" >> $GITHUB_OUTPUT - echo "state=$(jq -r '.readyState // empty' vercel-deployment.json)" >> $GITHUB_OUTPUT - - # Job summary — полезно в Checks → job. - - name: Job summary (tail 200) - run: | - ICON="✅"; [ "${{ steps.info.outputs.state }}" = "ERROR" ] && ICON="❌" - echo "### ${ICON} Vercel Logs (tail 200)" >> "$GITHUB_STEP_SUMMARY" - if [ -n "${{ steps.info.outputs.url }}" ]; then - echo "Preview: https://${{ steps.info.outputs.url }}" >> "$GITHUB_STEP_SUMMARY" - echo "" >> "$GITHUB_STEP_SUMMARY" - fi - echo '```' >> "$GITHUB_STEP_SUMMARY" - cat tail.txt >> "$GITHUB_STEP_SUMMARY" - echo '```' >> "$GITHUB_STEP_SUMMARY" - - - name: Publish check run (Vercel Logs) - if: ${{ steps.ctx.outputs.sha != '' }} - uses: actions/github-script@v7 - with: - script: | - const fs = require('fs'); - const {owner, repo} = context.repo; - const sha = '${{ steps.ctx.outputs.sha }}'; - const state= '${{ steps.info.outputs.state }}'; - const url = '${{ steps.info.outputs.url }}'; - const tail = fs.readFileSync('tail.txt','utf8'); - const isFail = state === 'ERROR'; - await github.rest.checks.create({ - owner, repo, - name: 'Vercel Logs', - head_sha: sha, - status: 'completed', - conclusion: isFail ? 'failure' : 'success', - output: { - title: `Vercel Logs (${state || 'unknown'})`, - summary: `${url ? `Preview: https://${url}\n\n` : ''}Last 200 lines:\n\n\`\`\`\n${tail}\n\`\`\`` - } - }); - - - name: Build PR comment markdown (failure only) - if: ${{ steps.ctx.outputs.pr != '' && steps.info.outputs.state == 'ERROR' }} - run: | - ICON="❌" - PREVIEW="" - if [ -n "${{ steps.info.outputs.url }}" ]; then - PREVIEW="https://${{ steps.info.outputs.url }}" - fi - { - echo "${ICON} Deployment: \`${{ steps.dep.outputs.id || steps.seed.outputs.id }}\` — state: **${{ steps.info.outputs.state }}**" - if [ -n "$PREVIEW" ]; then - echo - echo "Preview: $PREVIEW" - fi - echo - echo "
Show full log (truncated)" - echo - echo '```' - cat full.txt - echo '```' - echo - echo "
" - } > comment.md - - - name: Comment to PR on failure - if: ${{ steps.ctx.outputs.pr != '' && steps.info.outputs.state == 'ERROR' }} - uses: peter-evans/create-or-update-comment@v4 - with: - issue-number: ${{ steps.ctx.outputs.pr }} - body-file: comment.md - edit-mode: replace - search: 'Deployment:' - - - name: Fail job if Vercel ERROR - if: ${{ steps.info.outputs.state == 'ERROR' }} - run: | - echo "Vercel readyState = ERROR → failing job." - exit 1 - - - name: Upload raw (debug) - if: always() - uses: actions/upload-artifact@v4 - with: - name: vercel-raw - path: | - vercel-events.json - vercel-deployment.json - vercel-build.log - tail.txt - full.txt \ No newline at end of file From 8b5b71b9ac94a09bcf48d21fc11276adafc9ddfc Mon Sep 17 00:00:00 2001 From: Yevhen <38507392+JenyaL@users.noreply.github.com> Date: Thu, 9 Oct 2025 18:30:06 -0600 Subject: [PATCH 16/20] ci: fix PR preview comment block and cleanup workflow structure --- .github/workflows/ci-deploy.yml | 34 ++++----------------------------- 1 file changed, 4 insertions(+), 30 deletions(-) diff --git a/.github/workflows/ci-deploy.yml b/.github/workflows/ci-deploy.yml index 100b9ed..477ee92 100644 --- a/.github/workflows/ci-deploy.yml +++ b/.github/workflows/ci-deploy.yml @@ -106,40 +106,14 @@ jobs: echo "### Deployment" >> "$GITHUB_STEP_SUMMARY" echo "- Target: **${{ steps.tgt.outputs.target }}**" >> "$GITHUB_STEP_SUMMARY" echo "- URL: ${{ steps.deploy.outputs.url }}" >> "$GITHUB_STEP_SUMMARY" - - - - name: Post Preview URL to PR - if: ${{ github.event_name == 'pull_request' && steps.deploy.outputs.url != '' }} - uses: actions/github-script@v7 - with: - script: | - const url = process.env.URL || '${{ steps.deploy.outputs.url }}'; - const pr = context.payload.pull_request.number; - const commit = context.payload.pull_request.head.sha.slice(0, 7); - const message = `✅ **Preview ready for commit \`${commit}\`**\n🔗 ${url}`; - await github.rest.issues.createComment({ - issue_number: pr, - owner: context.repo.owner, - repo: context.repo.repo, - body: message - }); - env: - URL: ${{ steps.deploy.outputs.url }} - name: Post Preview URL to PR if: ${{ github.event_name == 'pull_request' && steps.deploy.outputs.url != '' }} uses: actions/github-script@v7 with: script: | - const url = process.env.URL || '${{ steps.deploy.outputs.url }}'; - const pr = context.payload.pull_request.number; - const commit = context.payload.pull_request.head.sha.slice(0, 7); - const message = `✅ **Preview ready for commit \`${commit}\`**\n🔗 ${url}`; await github.rest.issues.createComment({ - issue_number: pr, - owner: context.repo.owner, - repo: context.repo.repo, - body: message - }); - env: - URL: ${{ steps.deploy.outputs.url }} \ No newline at end of file + ...context.repo, + issue_number: context.payload.pull_request.number, + body: `✅ Preview ready: ${{ steps.deploy.outputs.url }}` + }); \ No newline at end of file From 9276b8ac1ac3ea67d2b049025fb642f2fa6feb85 Mon Sep 17 00:00:00 2001 From: Yevhen <38507392+JenyaL@users.noreply.github.com> Date: Thu, 9 Oct 2025 18:35:12 -0600 Subject: [PATCH 17/20] fix(ci): grant permissions for PR comments and deployment --- .github/workflows/ci-deploy.yml | 32 ++++++++------------------------ 1 file changed, 8 insertions(+), 24 deletions(-) diff --git a/.github/workflows/ci-deploy.yml b/.github/workflows/ci-deploy.yml index 477ee92..93d37a0 100644 --- a/.github/workflows/ci-deploy.yml +++ b/.github/workflows/ci-deploy.yml @@ -8,6 +8,9 @@ on: permissions: contents: read + issues: write + pull-requests: write + deployments: write env: VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }} @@ -20,31 +23,20 @@ jobs: is_pr: ${{ github.event_name == 'pull_request' }} steps: - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 with: node-version: 18 cache: 'npm' - - name: Install - run: | - npm ci || npm install - + run: npm ci || npm install - name: Lint - run: | - if npm run | grep -q " lint"; then npm run lint; else echo "No lint script"; fi - + run: if npm run | grep -q " lint"; then npm run lint; else echo "No lint script"; fi - name: Unit tests - run: | - if npm run | grep -q " test"; then npm test --ci --passWithNoTests=false; else echo "No test script"; fi - + run: if npm run | grep -q " test"; then npm test --ci --passWithNoTests=false; else echo "No test script"; fi - name: E2E tests (optional) - run: | - if npm run | grep -q " e2e"; then npm run e2e; else echo "No e2e script"; fi - + run: if npm run | grep -q " e2e"; then npm run e2e; else echo "No e2e script"; fi - name: App build run: npm run build - - name: Build summary run: echo "Build & tests passed ✅" >> "$GITHUB_STEP_SUMMARY" @@ -54,15 +46,12 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 with: node-version: 18 cache: 'npm' - - name: Install Vercel CLI run: npm i -g vercel@latest - - name: Decide target id: tgt run: | @@ -71,20 +60,17 @@ jobs: else echo "target=production" >> $GITHUB_OUTPUT fi - - name: Pull Vercel project settings run: | vercel pull --yes \ --environment "${{ steps.tgt.outputs.target }}" \ --token "${{ env.VERCEL_TOKEN }}" \ --scope "${{ env.VERCEL_ORG }}" - - name: Vercel prebuild run: | vercel build \ --token "${{ env.VERCEL_TOKEN }}" \ --scope "${{ env.VERCEL_ORG }}" - - name: Deploy (prebuilt) id: deploy env: @@ -100,13 +86,11 @@ jobs: URL="$(vercel "${ARGS[@]}")" echo "url=$URL" >> "$GITHUB_OUTPUT" echo "Deployed: $URL" - - name: Summary run: | echo "### Deployment" >> "$GITHUB_STEP_SUMMARY" echo "- Target: **${{ steps.tgt.outputs.target }}**" >> "$GITHUB_STEP_SUMMARY" echo "- URL: ${{ steps.deploy.outputs.url }}" >> "$GITHUB_STEP_SUMMARY" - - name: Post Preview URL to PR if: ${{ github.event_name == 'pull_request' && steps.deploy.outputs.url != '' }} uses: actions/github-script@v7 @@ -116,4 +100,4 @@ jobs: ...context.repo, issue_number: context.payload.pull_request.number, body: `✅ Preview ready: ${{ steps.deploy.outputs.url }}` - }); \ No newline at end of file + }) \ No newline at end of file From 4fe7c95ddc3547af338c3961cedf532ec543e67c Mon Sep 17 00:00:00 2001 From: Yevhen <38507392+JenyaL@users.noreply.github.com> Date: Thu, 9 Oct 2025 18:44:02 -0600 Subject: [PATCH 18/20] ci: simplify prebuilt flow and add clear comments for each step --- .github/workflows/ci-deploy.yml | 45 +++++++++++++++++++++++++++------ public/index.html | 2 +- 2 files changed, 38 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci-deploy.yml b/.github/workflows/ci-deploy.yml index 93d37a0..5999af7 100644 --- a/.github/workflows/ci-deploy.yml +++ b/.github/workflows/ci-deploy.yml @@ -6,6 +6,7 @@ on: pull_request: branches: [ main ] +# Minimal permissions for posting PR comments and (future) deployments API permissions: contents: read issues: write @@ -19,24 +20,37 @@ env: jobs: build_and_test: runs-on: ubuntu-latest - outputs: - is_pr: ${{ github.event_name == 'pull_request' }} steps: + # Check out repository - uses: actions/checkout@v4 + + # Install Node and enable npm cache - uses: actions/setup-node@v4 with: node-version: 18 cache: 'npm' + + # Install deps (prefer CI flow if lockfile exists) - name: Install run: npm ci || npm install + + # Lint if a "lint" script exists - name: Lint - run: if npm run | grep -q " lint"; then npm run lint; else echo "No lint script"; fi + run: npm run -s | grep -qE '(^| )lint( |:)' && npm run lint || echo "No lint script" + + # Unit tests if a "test" script exists - name: Unit tests - run: if npm run | grep -q " test"; then npm test --ci --passWithNoTests=false; else echo "No test script"; fi + run: npm run -s | grep -qE '(^| )test( |:)' && npm test --ci --passWithNoTests=false || echo "No test script" + + # E2E tests if an "e2e" script exists - name: E2E tests (optional) - run: if npm run | grep -q " e2e"; then npm run e2e; else echo "No e2e script"; fi + run: npm run -s | grep -qE '(^| )e2e( |:)' && npm run e2e || echo "No e2e script" + + # Project build (your app’s own build step) - name: App build run: npm run build + + # Short success note in the PR checks summary - name: Build summary run: echo "Build & tests passed ✅" >> "$GITHUB_STEP_SUMMARY" @@ -45,13 +59,20 @@ jobs: if: ${{ success() }} runs-on: ubuntu-latest steps: + # Check out repository for deploy context - uses: actions/checkout@v4 + + # Install Node for vercel CLI - uses: actions/setup-node@v4 with: node-version: 18 cache: 'npm' + + # Install Vercel CLI (latest) - name: Install Vercel CLI run: npm i -g vercel@latest + + # Decide Vercel environment: preview for PRs, production for main - name: Decide target id: tgt run: | @@ -60,17 +81,23 @@ jobs: else echo "target=production" >> $GITHUB_OUTPUT fi + + # Pull project settings and envs for the chosen environment - name: Pull Vercel project settings run: | vercel pull --yes \ --environment "${{ steps.tgt.outputs.target }}" \ --token "${{ env.VERCEL_TOKEN }}" \ --scope "${{ env.VERCEL_ORG }}" + + # Build a prebuilt artifact into .vercel/output - name: Vercel prebuild run: | vercel build \ --token "${{ env.VERCEL_TOKEN }}" \ --scope "${{ env.VERCEL_ORG }}" + + # Deploy prebuilt artifact (no build on Vercel) - name: Deploy (prebuilt) id: deploy env: @@ -80,17 +107,19 @@ jobs: run: | set -e ARGS=(deploy --yes --token "$VC_TOKEN" --scope "$VC_TEAM" --prebuilt) - if [ "$TARGET" = "production" ]; then - ARGS+=(--prod) - fi + [ "$TARGET" = "production" ] && ARGS+=(--prod) || true URL="$(vercel "${ARGS[@]}")" echo "url=$URL" >> "$GITHUB_OUTPUT" echo "Deployed: $URL" + + # Put target and URL into the PR checks summary for quick access - name: Summary run: | echo "### Deployment" >> "$GITHUB_STEP_SUMMARY" echo "- Target: **${{ steps.tgt.outputs.target }}**" >> "$GITHUB_STEP_SUMMARY" echo "- URL: ${{ steps.deploy.outputs.url }}" >> "$GITHUB_STEP_SUMMARY" + + # Comment the preview link on PRs so reviewers always see the latest link - name: Post Preview URL to PR if: ${{ github.event_name == 'pull_request' && steps.deploy.outputs.url != '' }} uses: actions/github-script@v7 diff --git a/public/index.html b/public/index.html index 92d7b49..7db3af1 100644 --- a/public/index.html +++ b/public/index.html @@ -6,6 +6,6 @@

Hello from build-test

-

1.0 v

+

2.0 v

From 3272a5f58f572e356cade582ca272ffe3f20a716 Mon Sep 17 00:00:00 2001 From: Yevhen <38507392+JenyaL@users.noreply.github.com> Date: Thu, 9 Oct 2025 18:45:56 -0600 Subject: [PATCH 19/20] ci: test --- index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.js b/index.js index 4d4aaf1..12104bd 100644 --- a/index.js +++ b/index.js @@ -5,4 +5,4 @@ require("dotenv").config(); process.exit(0); } -// throw new Error("Build failed"); +throw new Error("Build failed"); From 5eedeb855f14399b3f5aa7c7df85693fabb1aae6 Mon Sep 17 00:00:00 2001 From: Yevhen <38507392+JenyaL@users.noreply.github.com> Date: Thu, 9 Oct 2025 19:12:17 -0600 Subject: [PATCH 20/20] ci: test 2 --- index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.js b/index.js index 12104bd..dfea677 100644 --- a/index.js +++ b/index.js @@ -5,4 +5,4 @@ require("dotenv").config(); process.exit(0); } -throw new Error("Build failed"); +//throw new Error("Build failed");