feat: track bundle (#90) #9
Workflow file for this run
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: Draft Release | |
| on: | |
| push: | |
| tags: [ 'v*.*.*' ] | |
| permissions: | |
| actions: read | |
| contents: write | |
| concurrency: | |
| group: draft-release-${{ github.ref_name }} | |
| cancel-in-progress: false | |
| jobs: | |
| draft-release: | |
| name: Draft GitHub release | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Collect build artifacts | |
| uses: actions/github-script@v8 | |
| env: | |
| TAG_NAME: ${{ github.ref_name }} | |
| WORKFLOWS: | | |
| Build Rust Application | |
| Build Probe | |
| with: | |
| script: | | |
| const fs = require('fs'); | |
| const path = require('path'); | |
| const owner = context.repo.owner; | |
| const repo = context.repo.repo; | |
| const tagName = process.env.TAG_NAME; | |
| const workflows = process.env.WORKFLOWS | |
| .split(/\r?\n/) | |
| .map((name) => name.trim()) | |
| .filter(Boolean); | |
| const targetSha = context.sha; | |
| const outDir = path.join(process.cwd(), 'artifact-archives'); | |
| const pollDelayMs = 30_000; | |
| const timeoutMs = 30 * 60 * 1000; | |
| fs.mkdirSync(outDir, { recursive: true }); | |
| async function sleep(ms) { | |
| return new Promise((resolve) => setTimeout(resolve, ms)); | |
| } | |
| async function findWorkflowRun(workflowName) { | |
| const startedAt = Date.now(); | |
| while (Date.now() - startedAt < timeoutMs) { | |
| const runs = await github.paginate( | |
| github.rest.actions.listWorkflowRunsForRepo, | |
| { | |
| owner, | |
| repo, | |
| head_sha: targetSha, | |
| event: 'push', | |
| per_page: 100, | |
| }, | |
| ); | |
| const run = runs.find((candidate) => | |
| candidate.name === workflowName && | |
| candidate.head_sha === targetSha && | |
| candidate.head_branch === tagName | |
| ); | |
| if (run?.status === 'completed') { | |
| if (run.conclusion === 'success') { | |
| return run; | |
| } | |
| throw new Error( | |
| `${workflowName} completed with conclusion ${run.conclusion}: ${run.html_url}`, | |
| ); | |
| } | |
| core.info( | |
| run | |
| ? `${workflowName} is ${run.status}; waiting for completion.` | |
| : `Waiting for ${workflowName} to start for ${tagName}.`, | |
| ); | |
| await sleep(pollDelayMs); | |
| } | |
| throw new Error(`Timed out waiting for ${workflowName} on ${tagName}.`); | |
| } | |
| for (const workflowName of workflows) { | |
| const run = await findWorkflowRun(workflowName); | |
| core.info(`Downloading artifacts from ${workflowName}: ${run.html_url}`); | |
| const artifacts = await github.paginate( | |
| github.rest.actions.listWorkflowRunArtifacts, | |
| { | |
| owner, | |
| repo, | |
| run_id: run.id, | |
| per_page: 100, | |
| }, | |
| ); | |
| for (const artifact of artifacts.filter((item) => !item.expired)) { | |
| if (artifact.name.endsWith('.dockerbuild')) { | |
| core.info(`Skipping Docker build record artifact ${artifact.name}`); | |
| continue; | |
| } | |
| const archive = await github.rest.actions.downloadArtifact({ | |
| owner, | |
| repo, | |
| artifact_id: artifact.id, | |
| archive_format: 'zip', | |
| }); | |
| const artifactPath = path.join(outDir, `${artifact.name}.zip`); | |
| fs.writeFileSync(artifactPath, Buffer.from(archive.data)); | |
| core.info(`Saved ${artifactPath}`); | |
| } | |
| } | |
| - name: Extract release assets | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| mkdir -p release-artifacts | |
| for archive in artifact-archives/*.zip; do | |
| artifact_name="$(basename "$archive" .zip)" | |
| extract_dir="$(mktemp -d)" | |
| unzip -q "$archive" -d "$extract_dir" | |
| while IFS= read -r -d '' file; do | |
| filename="$(basename "$file")" | |
| target="release-artifacts/$filename" | |
| if [ -e "$target" ]; then | |
| target="release-artifacts/${artifact_name}-${filename}" | |
| fi | |
| mv "$file" "$target" | |
| done < <(find "$extract_dir" -type f -print0) | |
| rm -rf "$extract_dir" | |
| done | |
| - name: Generate release body | |
| uses: actions/github-script@v8 | |
| env: | |
| TAG_NAME: ${{ github.ref_name }} | |
| with: | |
| script: | | |
| const fs = require('fs'); | |
| const path = require('path'); | |
| const tag = process.env.TAG_NAME; | |
| const files = fs.readdirSync('release-artifacts') | |
| .filter((name) => fs.statSync(path.join('release-artifacts', name)).isFile()) | |
| .sort(); | |
| function describe(name) { | |
| if (/^cheburchecker_.+_amd64\.deb$/.test(name)) { | |
| return 'Пакет Cheburchecker для Debian Linux x86-64.'; | |
| } | |
| if (/^cheburchecker_.+_arm64\.deb$/.test(name)) { | |
| return 'Пакет Cheburchecker для Debian Linux ARM64.'; | |
| } | |
| if (/^cheburprobe-.+-linux-amd64$/.test(name)) { | |
| return 'Cheburprobe для Linux x86-64.'; | |
| } | |
| if (/^cheburprobe-.+-linux-arm64$/.test(name)) { | |
| return 'Cheburprobe для Linux ARM64.'; | |
| } | |
| if (/^cheburprobe-.+-windows-x86_64\.exe$/.test(name)) { | |
| return 'Cheburprobe для Windows x86-64.'; | |
| } | |
| if (/^cheburprobe_.+_amd64\.deb$/.test(name)) { | |
| return 'Пакет Cheburprobe для Debian Linux x86-64 с сервисом и автообновлением.'; | |
| } | |
| if (/^cheburprobe_.+_arm64\.deb$/.test(name)) { | |
| return 'Пакет Cheburprobe для Debian Linux ARM64 с сервисом и автообновлением.'; | |
| } | |
| if (/^cheburprobe-.+_.+\.apk$/.test(name)) { | |
| return 'Пакет Cheburprobe для версий OpenWrt с пакетным менеджером APK (25.12+).'; | |
| } | |
| if (/^luci-app-cheburprobe-.+\.apk$/.test(name)) { | |
| return 'Интерфейс LuCI для APK-пакета Cheburprobe.'; | |
| } | |
| if (/^cheburprobe_.+_.+\.ipk$/.test(name)) { | |
| return 'Пакет Cheburprobe для версий OpenWrt с пакетным менеджером opkg.'; | |
| } | |
| if (/^luci-app-cheburprobe_.+_all\.ipk$/.test(name)) { | |
| return 'Интерфейс LuCI для IPK-пакета Cheburprobe.'; | |
| } | |
| return 'Файл релиза.'; | |
| } | |
| const baseUrl = [ | |
| 'https://github.com', | |
| encodeURIComponent(context.repo.owner), | |
| encodeURIComponent(context.repo.repo), | |
| 'releases', | |
| 'download', | |
| encodeURIComponent(tag), | |
| ].join('/'); | |
| const sourceBaseUrl = [ | |
| 'https://github.com', | |
| encodeURIComponent(context.repo.owner), | |
| encodeURIComponent(context.repo.repo), | |
| 'blob', | |
| encodeURIComponent(tag), | |
| ].join('/'); | |
| const rows = files.map((name) => { | |
| const url = baseUrl + '/' + encodeURIComponent(name); | |
| return '| [`' + name + '`](' + url + ') | ' + describe(name) + ' |'; | |
| }); | |
| const body = [ | |
| '## Документация', | |
| '', | |
| '- [Cheburprobe](' + sourceBaseUrl + '/probe/README.md)', | |
| '- [Cheburchecker](' + sourceBaseUrl + '/reporter/README.md)', | |
| '', | |
| '## Файлы релиза', | |
| '', | |
| '| Файл | Описание |', | |
| '| --- | --- |', | |
| ...rows, | |
| '', | |
| ].join('\n'); | |
| fs.writeFileSync('release-body.md', body); | |
| - name: Publish draft release | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| tag_name: ${{ github.ref_name }} | |
| name: ${{ github.ref_name }} | |
| draft: true | |
| body_path: release-body.md | |
| fail_on_unmatched_files: true | |
| files: release-artifacts/* |