mirror of
https://github.com/go-vikunja/vikunja.git
synced 2025-12-05 19:16:51 -06:00
The `issue-closed-comment` workflow fails when commit messages contain
backticks because they're interpolated directly into JS template
strings, breaking syntax.
### Changes
- Escape backslashes, backticks, and `${` sequences before setting the
commit message output
- Order matters: backslashes first to avoid interfering with subsequent
escaping
```javascript
// Before: raw message breaks template string if it contains backticks
core.setOutput('commit_message', commit.message);
// After: properly escaped for safe interpolation
const escapedMessage = commit.message.replace(/\\/g, '\\\\').replace(/`/g, '\\`').replace(/\$\{/g, '\\${');
core.setOutput('commit_message', escapedMessage);
```
<!-- START COPILOT CODING AGENT SUFFIX -->
<details>
<summary>Original prompt</summary>
> the github action which comments on issue closure fails when the
commit message contains ` since these are js strings. Make sure to
escape them.
</details>
<!-- START COPILOT CODING AGENT TIPS -->
---
✨ Let Copilot coding agent [set things up for
you](https://github.com/go-vikunja/vikunja/issues/new?title=✨+Set+up+Copilot+instructions&body=Configure%20instructions%20for%20this%20repository%20as%20documented%20in%20%5BBest%20practices%20for%20Copilot%20coding%20agent%20in%20your%20repository%5D%28https://gh.io/copilot-coding-agent-tips%29%2E%0A%0A%3COnboard%20this%20repo%3E&assignees=copilot)
— coding agent works faster and does higher quality work when set up for
your repo.
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: kolaente <13721712+kolaente@users.noreply.github.com>
149 lines
6.4 KiB
YAML
149 lines
6.4 KiB
YAML
name: Comment on issue when it is closed automatically
|
||
|
||
on:
|
||
issues:
|
||
types: [closed]
|
||
|
||
jobs:
|
||
comment-on-issue-closure:
|
||
runs-on: ubuntu-latest
|
||
steps:
|
||
- name: Generate GitHub App token
|
||
id: generate-token
|
||
uses: actions/create-github-app-token@7e473efe3cb98aa54f8d4bac15400b15fad77d94 # v2
|
||
with:
|
||
app-id: ${{ secrets.BOT_APP_ID }}
|
||
private-key: ${{ secrets.BOT_APP_PRIVATE_KEY }}
|
||
|
||
- name: Check if issue was closed by commit
|
||
id: check-commit
|
||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||
with:
|
||
github-token: ${{ steps.generate-token.outputs.token }}
|
||
script: |
|
||
const issueNumber = context.payload.issue.number;
|
||
|
||
// Get the issue events to find the "closed" event with commit_id
|
||
const { data: events } = await github.rest.issues.listEvents({
|
||
owner: context.repo.owner,
|
||
repo: context.repo.repo,
|
||
issue_number: issueNumber
|
||
});
|
||
|
||
// Find the most recent "closed" event
|
||
const closedEvent = events
|
||
.filter(event => event.event === 'closed')
|
||
.pop(); // Get the last (most recent) closed event
|
||
// Find the most recent "referenced" event
|
||
const referencedEvent = events
|
||
.filter(event => event.event === 'referenced')
|
||
.pop(); // Get the last (most recent) referenced event
|
||
|
||
console.log({closedEvent, referencedEvent});
|
||
if (closedEvent && (closedEvent.commit_id || referencedEvent)) {
|
||
const commitId = closedEvent.commit_id ?? referencedEvent.commit_id
|
||
console.log(`✅ Issue #${issueNumber} was closed by commit: ${commitId}`);
|
||
|
||
// Get commit details
|
||
const { data: commit } = await github.rest.git.getCommit({
|
||
owner: context.repo.owner,
|
||
repo: context.repo.repo,
|
||
commit_sha: commitId
|
||
});
|
||
|
||
core.setOutput('closed_by_commit', 'true');
|
||
core.setOutput('commit_sha', commitId);
|
||
// Escape backslashes, backticks and ${ to prevent breaking JS template strings
|
||
const escapedMessage = commit.message.replace(/\\/g, '\\\\').replace(/`/g, '\\`').replace(/\$\{/g, '\\${');
|
||
core.setOutput('commit_message', escapedMessage);
|
||
core.setOutput('commit_url', closedEvent.commit_url);
|
||
} else {
|
||
console.log(`ℹ️ Issue #${issueNumber} was closed manually (not by commit)`);
|
||
core.setOutput('closed_by_commit', 'false');
|
||
}
|
||
|
||
- name: Determine closure method and comment on issue
|
||
if: steps.check-commit.outputs.closed_by_commit == 'true'
|
||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||
with:
|
||
github-token: ${{ steps.generate-token.outputs.token }}
|
||
script: |
|
||
const issueNumber = context.payload.issue.number;
|
||
const commitSha = '${{ steps.check-commit.outputs.commit_sha }}';
|
||
const commitMessage = `${{ steps.check-commit.outputs.commit_message }}`;
|
||
const commitUrl = '${{ steps.check-commit.outputs.commit_url }}';
|
||
|
||
try {
|
||
// Find PRs that include this commit
|
||
const { data: prs } = await github.rest.pulls.list({
|
||
owner: context.repo.owner,
|
||
repo: context.repo.repo,
|
||
state: 'all',
|
||
sort: 'updated',
|
||
direction: 'desc',
|
||
per_page: 100
|
||
});
|
||
|
||
let closingPR = null;
|
||
|
||
// Check each PR to see if it contains our commit
|
||
for (const pr of prs) {
|
||
try {
|
||
const { data: commits } = await github.rest.pulls.listCommits({
|
||
owner: context.repo.owner,
|
||
repo: context.repo.repo,
|
||
pull_number: pr.number
|
||
});
|
||
|
||
if (commits.some(commit => commit.sha === commitSha)) {
|
||
closingPR = pr;
|
||
console.log(`✅ Found PR #${pr.number} containing commit ${commitSha.substring(0, 7)}`);
|
||
break;
|
||
}
|
||
} catch (error) {
|
||
console.log(`Error checking commits for PR #${pr.number}: ${error.message}`);
|
||
}
|
||
}
|
||
|
||
// If no PR found with the exact commit, try alternative approaches
|
||
if (!closingPR) {
|
||
console.log(`🔍 No PR found with exact commit ${commitSha.substring(0, 7)}, trying alternative search...`);
|
||
|
||
// Try to find a merged PR that mentions this issue
|
||
const relatedPRs = prs.filter(pr =>
|
||
pr.state === 'closed' &&
|
||
pr.merged_at &&
|
||
(pr.title.includes(`#${issueNumber}`) ||
|
||
pr.body?.includes(`#${issueNumber}`))
|
||
);
|
||
|
||
if (relatedPRs.length > 0) {
|
||
closingPR = relatedPRs[0];
|
||
console.log(`✅ Found related PR #${closingPR.number} that mentions issue #${issueNumber}`);
|
||
}
|
||
}
|
||
|
||
const closedRef = closingPR
|
||
? `#${closingPR.number}`
|
||
: `[\`${commitSha.substring(0, 7)}\`](${commitUrl})`
|
||
|
||
const comment = `This issue has been fixed in ${closedRef}, please check with the next unstable build (should be ready for deployment in ~30min, also on [the demo](https://try.vikunja.io)).`
|
||
|
||
await github.rest.issues.createComment({
|
||
owner: context.repo.owner,
|
||
repo: context.repo.repo,
|
||
issue_number: issueNumber,
|
||
body: comment,
|
||
});
|
||
|
||
if (closingPR) {
|
||
console.log(`✅ Added comment to issue #${issueNumber} (closed by PR #${closingPR.number})`);
|
||
} else {
|
||
console.log(`✅ Added comment to issue #${issueNumber} (closed by direct commit ${commitSha.substring(0, 7)})`);
|
||
}
|
||
|
||
} catch (error) {
|
||
console.error(`❌ Error processing issue #${issueNumber}: ${error.message}`);
|
||
throw error;
|
||
}
|