122 lines
5.2 KiB
YAML
122 lines
5.2 KiB
YAML
name: Post Web Flasher Link Comment
|
|
|
|
on:
|
|
workflow_run:
|
|
workflows: [CI]
|
|
types: [completed]
|
|
|
|
permissions:
|
|
pull-requests: write
|
|
actions: read
|
|
|
|
jobs:
|
|
post-flasher-link:
|
|
if: >
|
|
github.event.workflow_run.event == 'pull_request' &&
|
|
github.event.workflow_run.conclusion == 'success' &&
|
|
github.repository == 'meshtastic/firmware'
|
|
continue-on-error: true
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Post or update web flasher link comment
|
|
uses: actions/github-script@v8
|
|
with:
|
|
script: |
|
|
const marker = '<!-- web-flasher-link -->';
|
|
const run = context.payload.workflow_run;
|
|
const { owner, repo } = context.repo;
|
|
|
|
// Resolve the PR number (run.pull_requests is empty for fork PRs)
|
|
let prNumber = run.pull_requests?.[0]?.number;
|
|
if (!prNumber) {
|
|
const { data: prs } = await github.rest.repos.listPullRequestsAssociatedWithCommit({
|
|
owner, repo, commit_sha: run.head_sha,
|
|
});
|
|
prNumber = (prs.find((pr) => pr.head.sha === run.head_sha) ?? prs[0])?.number;
|
|
}
|
|
if (!prNumber) {
|
|
core.info('No pull request associated with this run; skipping.');
|
|
return;
|
|
}
|
|
|
|
const { data: pr } = await github.rest.pulls.get({ owner, repo, pull_number: prNumber });
|
|
|
|
// Only comment on PRs authored by members of the organization.
|
|
// author_association MEMBER is computed by GitHub and reflects org
|
|
// membership (including concealed members); OWNER covers a repo owner.
|
|
const allowedAssociations = ['OWNER', 'MEMBER'];
|
|
if (!allowedAssociations.includes(pr.author_association)) {
|
|
core.info(`Author association ${pr.author_association} is not an org member; skipping.`);
|
|
return;
|
|
}
|
|
if (pr.state !== 'open') {
|
|
core.info('Pull request is not open; skipping.');
|
|
return;
|
|
}
|
|
if (pr.head.sha !== run.head_sha) {
|
|
core.info('Run is for an outdated commit; skipping.');
|
|
return;
|
|
}
|
|
|
|
// Require at least one per-arch firmware artifact from gather-artifacts
|
|
const artifacts = await github.paginate(github.rest.actions.listWorkflowRunArtifacts, {
|
|
owner, repo, run_id: run.id, per_page: 100,
|
|
});
|
|
const archRe = /^firmware-(esp32|esp32s3|esp32c3|esp32c6|nrf52840|rp2040|rp2350|stm32)-(\d+\.\d+\.\d+\.[0-9a-f]+)$/;
|
|
const archArtifacts = artifacts.filter((a) => archRe.test(a.name) && !a.expired);
|
|
if (archArtifacts.length === 0) {
|
|
core.info('No per-arch firmware artifacts found; skipping.');
|
|
return;
|
|
}
|
|
|
|
const version = archRe.exec(archArtifacts[0].name)[2];
|
|
const expiresAt = archArtifacts[0].expires_at
|
|
? new Date(archArtifacts[0].expires_at).toISOString().slice(0, 10)
|
|
: null;
|
|
|
|
// Per-board deep links from manifest-{platform}-{board}-{version} artifacts
|
|
const escapedVersion = version.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
const boardRe = new RegExp(`^manifest-([a-z0-9]+)-(.+)-${escapedVersion}$`);
|
|
const boards = artifacts
|
|
.map((a) => boardRe.exec(a.name))
|
|
.filter(Boolean)
|
|
.map((m) => ({ platform: m[1], board: m[2] }))
|
|
.sort((a, b) => a.board.localeCompare(b.board));
|
|
|
|
const flasherUrl = `https://flasher.meshtastic.org/?pr=${prNumber}`;
|
|
const boardLines = boards
|
|
.map((b) => `| [\`${b.board}\`](${flasherUrl}&device=${encodeURIComponent(b.board)}) | ${b.platform} |`)
|
|
.join('\n');
|
|
|
|
const body = [
|
|
marker,
|
|
'## 🔦 Try this PR in the Web Flasher',
|
|
'',
|
|
`[**Flash firmware \`${version}\`** (commit \`${run.head_sha.slice(0, 7)}\`)](${flasherUrl})`,
|
|
'',
|
|
'> [!WARNING]',
|
|
'> This is an automated, unreviewed CI test build. Back up your device configuration',
|
|
'> before flashing, and only flash devices you are able to recover.',
|
|
'',
|
|
`<details><summary>Boards built by this PR (${boards.length})</summary>`,
|
|
'',
|
|
'| Board | Platform |',
|
|
'| --- | --- |',
|
|
boardLines,
|
|
'',
|
|
'</details>',
|
|
'',
|
|
`*Build artifacts expire${expiresAt ? ` on ${expiresAt}` : ' after 30 days'}. Updated for ${run.head_sha}.*`,
|
|
].join('\n');
|
|
|
|
// Sticky comment: update in place when the marker is found
|
|
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 });
|
|
} else {
|
|
await github.rest.issues.createComment({ owner, repo, issue_number: prNumber, body });
|
|
}
|