Files
vikunja/.github/workflows/issue-closed-comment.yml
Copilot 30104fb749 fix: escape backticks and special chars in commit message for GitHub Action (#1928)
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>
2025-12-04 09:38:24 +00:00

149 lines
6.4 KiB
YAML
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;
}