diff --git a/.github/workflows/build-and-upload.yml b/.github/workflows/build-and-upload.yml index 392db41d..36d3e022 100644 --- a/.github/workflows/build-and-upload.yml +++ b/.github/workflows/build-and-upload.yml @@ -28,6 +28,21 @@ on: required: false default: true type: boolean + upload_actions_artifacts: + description: "Upload built artifacts to GitHub Actions run artifacts" + required: false + default: false + type: boolean + actions_artifacts_retention_days: + description: "Retention (days) for GitHub Actions artifacts" + required: false + default: 7 + type: number + actions_artifacts_name_prefix: + description: "Optional prefix for Actions artifact names" + required: false + default: "" + type: string set_versions: description: "Run npm version to set workspace versions" required: false @@ -203,6 +218,15 @@ jobs: gh release upload "$TAG" "$file" --clobber done + - name: Upload Actions artifacts (Electron macOS) + if: ${{ inputs.upload_actions_artifacts }} + uses: actions/upload-artifact@v4 + with: + name: ${{ inputs.actions_artifacts_name_prefix }}electron-macos + path: packages/electron-app/release/*.zip + retention-days: ${{ inputs.actions_artifacts_retention_days }} + if-no-files-found: error + build-windows: runs-on: windows-2025 env: @@ -244,6 +268,15 @@ jobs: gh release upload $env:TAG $_.FullName --clobber } + - name: Upload Actions artifacts (Electron Windows) + if: ${{ inputs.upload_actions_artifacts }} + uses: actions/upload-artifact@v4 + with: + name: ${{ inputs.actions_artifacts_name_prefix }}electron-windows + path: packages/electron-app/release/*.zip + retention-days: ${{ inputs.actions_artifacts_retention_days }} + if-no-files-found: error + build-linux: runs-on: ubuntu-24.04 env: @@ -286,6 +319,15 @@ jobs: gh release upload "$TAG" "$file" --clobber done + - name: Upload Actions artifacts (Electron Linux) + if: ${{ inputs.upload_actions_artifacts }} + uses: actions/upload-artifact@v4 + with: + name: ${{ inputs.actions_artifacts_name_prefix }}electron-linux + path: packages/electron-app/release/*.zip + retention-days: ${{ inputs.actions_artifacts_retention_days }} + if-no-files-found: error + build-tauri-macos: runs-on: macos-15-intel env: @@ -339,7 +381,7 @@ jobs: run: npm exec -- tauri build - name: Package Tauri artifacts (macOS) - if: ${{ inputs.upload }} + if: ${{ inputs.upload || inputs.upload_actions_artifacts }} run: | set -euo pipefail BUNDLE_ROOT="packages/tauri-app/target/release/bundle" @@ -350,6 +392,15 @@ jobs: ditto -ck --sequesterRsrc --keepParent "$BUNDLE_ROOT/macos/CodeNomad.app" "$ARTIFACT_DIR/CodeNomad-Tauri-${VERSION}-macos-x64.zip" fi + - name: Upload Actions artifacts (Tauri macOS) + if: ${{ inputs.upload_actions_artifacts }} + uses: actions/upload-artifact@v4 + with: + name: ${{ inputs.actions_artifacts_name_prefix }}tauri-macos + path: packages/tauri-app/release-tauri/*.zip + retention-days: ${{ inputs.actions_artifacts_retention_days }} + if-no-files-found: warn + - name: Upload Tauri release assets (macOS) if: ${{ inputs.upload && inputs.tag != '' }} run: | @@ -414,7 +465,7 @@ jobs: run: npm exec -- tauri build - name: Package Tauri artifacts (macOS arm64) - if: ${{ inputs.upload }} + if: ${{ inputs.upload || inputs.upload_actions_artifacts }} run: | set -euo pipefail BUNDLE_ROOT="packages/tauri-app/target/release/bundle" @@ -425,6 +476,15 @@ jobs: ditto -ck --sequesterRsrc --keepParent "$BUNDLE_ROOT/macos/CodeNomad.app" "$ARTIFACT_DIR/CodeNomad-Tauri-${VERSION}-macos-arm64.zip" fi + - name: Upload Actions artifacts (Tauri macOS arm64) + if: ${{ inputs.upload_actions_artifacts }} + uses: actions/upload-artifact@v4 + with: + name: ${{ inputs.actions_artifacts_name_prefix }}tauri-macos-arm64 + path: packages/tauri-app/release-tauri/*.zip + retention-days: ${{ inputs.actions_artifacts_retention_days }} + if-no-files-found: warn + - name: Upload Tauri release assets (macOS arm64) if: ${{ inputs.upload && inputs.tag != '' }} run: | @@ -492,7 +552,7 @@ jobs: run: npm exec -- tauri build - name: Package Tauri artifacts (Windows) - if: ${{ inputs.upload }} + if: ${{ inputs.upload || inputs.upload_actions_artifacts }} shell: pwsh run: | $bundleRoot = "packages/tauri-app/target/release/bundle" @@ -505,6 +565,15 @@ jobs: Compress-Archive -Path $exe.Directory.FullName -DestinationPath $dest -Force } + - name: Upload Actions artifacts (Tauri Windows) + if: ${{ inputs.upload_actions_artifacts }} + uses: actions/upload-artifact@v4 + with: + name: ${{ inputs.actions_artifacts_name_prefix }}tauri-windows + path: packages/tauri-app/release-tauri/*.zip + retention-days: ${{ inputs.actions_artifacts_retention_days }} + if-no-files-found: warn + - name: Upload Tauri release assets (Windows) if: ${{ inputs.upload && inputs.tag != '' }} shell: pwsh @@ -582,7 +651,7 @@ jobs: run: npm exec -- tauri build - name: Package Tauri artifacts (Linux) - if: ${{ inputs.upload }} + if: ${{ inputs.upload || inputs.upload_actions_artifacts }} run: | set -euo pipefail SEARCH_ROOT="packages/tauri-app/target" @@ -608,6 +677,15 @@ jobs: cp "$deb" "$ARTIFACT_DIR/CodeNomad-Tauri-${VERSION}-linux-x64.deb" cp "$rpm" "$ARTIFACT_DIR/CodeNomad-Tauri-${VERSION}-linux-x64.rpm" + - name: Upload Actions artifacts (Tauri Linux) + if: ${{ inputs.upload_actions_artifacts }} + uses: actions/upload-artifact@v4 + with: + name: ${{ inputs.actions_artifacts_name_prefix }}tauri-linux + path: packages/tauri-app/release-tauri/* + retention-days: ${{ inputs.actions_artifacts_retention_days }} + if-no-files-found: warn + - name: Upload Tauri release assets (Linux) if: ${{ inputs.upload && inputs.tag != '' }} run: | @@ -766,3 +844,12 @@ jobs: echo "Uploading $file" gh release upload "$TAG" "$file" --clobber done + + - name: Upload Actions artifacts (Electron Linux RPM) + if: ${{ inputs.upload_actions_artifacts }} + uses: actions/upload-artifact@v4 + with: + name: ${{ inputs.actions_artifacts_name_prefix }}electron-linux-rpm + path: packages/electron-app/release/*.rpm + retention-days: ${{ inputs.actions_artifacts_retention_days }} + if-no-files-found: error diff --git a/.github/workflows/comment-pr-artifacts.yml b/.github/workflows/comment-pr-artifacts.yml new file mode 100644 index 00000000..b50a4146 --- /dev/null +++ b/.github/workflows/comment-pr-artifacts.yml @@ -0,0 +1,101 @@ +name: Comment PR Artifacts + +on: + workflow_run: + workflows: + - PR Build Validation + types: + - completed + +permissions: + actions: read + pull-requests: write + issues: write + +jobs: + comment: + # Only runs for PR Build Validation runs triggered by PRs. + if: ${{ github.event.workflow_run.event == 'pull_request' }} + runs-on: ubuntu-latest + steps: + - name: Comment with artifact download link + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const run = context.payload.workflow_run; + const owner = context.repo.owner; + const repo = context.repo.repo; + + const prs = run.pull_requests || []; + const prNumber = prs[0]?.number; + if (!prNumber) { + core.info('No PR number found for this workflow_run; skipping.'); + return; + } + + // Only comment when the PR build job actually ran (i.e. authorization passed). + // Unauthorized PRs targeting non-dev will skip the `build` job. + const jobs = await github.paginate( + github.rest.actions.listJobsForWorkflowRun, + { owner, repo, run_id: run.id, per_page: 100 } + ); + const buildJob = jobs.find((j) => j.name === 'build'); + if (!buildJob) { + core.info('No `build` job found on this run; skipping.'); + return; + } + if (buildJob.conclusion === 'skipped') { + core.info('`build` job was skipped; skipping comment.'); + return; + } + + // List artifacts from the run. If none exist (e.g. build failed before packaging), + // still comment with the run link so testers can see logs. + const artifacts = await github.paginate( + github.rest.actions.listWorkflowRunArtifacts, + { owner, repo, run_id: run.id, per_page: 100 } + ); + const active = artifacts.filter((a) => !a.expired); + + const marker = ''; + const runUrl = `https://github.com/${owner}/${repo}/actions/runs/${run.id}`; + const retentionDays = 7; + + const artifactsBlock = active.length + ? ['Artifacts:', ...active.map((a) => `- ${a.name}`)].join('\n') + : 'Artifacts: (none found on this run)'; + + const body = [ + marker, + 'PR builds are available as GitHub Actions artifacts:', + '', + runUrl, + '', + `Artifacts expire in ${retentionDays} days.`, + artifactsBlock, + ].join('\n'); + + const comments = await github.paginate( + github.rest.issues.listComments, + { owner, repo, issue_number: prNumber, per_page: 100 } + ); + const existing = comments.find((c) => (c.body || '').includes(marker)); + + if (existing) { + await github.rest.issues.updateComment({ + owner, + repo, + comment_id: existing.id, + body, + }); + core.info(`Updated existing artifacts comment: ${existing.html_url}`); + } else { + const created = await github.rest.issues.createComment({ + owner, + repo, + issue_number: prNumber, + body, + }); + core.info(`Created artifacts comment: ${created.data.html_url}`); + } diff --git a/.github/workflows/pr-build.yml b/.github/workflows/pr-build.yml index c0795e9b..1f780ffc 100644 --- a/.github/workflows/pr-build.yml +++ b/.github/workflows/pr-build.yml @@ -9,6 +9,7 @@ on: permissions: contents: read + actions: write concurrency: group: pr-build-${{ github.event.pull_request.number }} @@ -49,4 +50,7 @@ jobs: with: ref: ${{ github.event.pull_request.head.sha }} upload: false + upload_actions_artifacts: true + actions_artifacts_retention_days: 7 + actions_artifacts_name_prefix: pr-${{ github.event.pull_request.number }}-${{ github.event.pull_request.head.sha }}- set_versions: false