11# Managed by repo-content-updater
2- # Dependabot Cursor Review workflow
2+ # Dependency Cursor Review workflow
33#
4- # Runs Cursor CLI analysis for Dependabot PRs by using:
4+ # Runs Cursor CLI analysis for Dependabot/Renovate PRs by using:
55# - Dependabot PR body release notes + commit list
66# - An upstream dependency checkout
77# - Local usage hints in the target repo
88#
99# Source documentation: https://cursor.com/docs/cli/github-actions
10- name : Dependabot Cursor Review
10+ name : Dependency Cursor Review
1111
1212on :
1313 pull_request :
1414 types : [opened, synchronize, reopened]
1515 workflow_dispatch :
1616 inputs :
1717 pr_number :
18- description : " Dependabot PR number to analyze"
18+ description : " Dependabot/Renovate PR number to analyze"
1919 required : true
2020 type : number
2121
@@ -28,7 +28,7 @@ permissions:
2828 pull-requests : write
2929
3030jobs :
31- dependabot -review :
31+ dependency -review :
3232 if : github.event_name == 'workflow_dispatch' || (github.event_name == 'pull_request' && (github.event.pull_request.user.login == 'dependabot[bot]' || github.event.pull_request.user.login == 'renovate[bot]'))
3333 runs-on : ubuntu-latest
3434 timeout-minutes : 15
6161 return;
6262 }
6363 const allowedBots = ['dependabot[bot]', 'renovate[bot]'];
64- if (context.eventName !== 'workflow_dispatch' && !allowedBots.includes(pr.user?.login)) {
64+ if (!allowedBots.includes(pr.user?.login)) {
6565 core.setFailed(`Target PR #${pr.number} is not opened by an allowed bot. Author: ${pr.user?.login}`);
6666 return;
6767 }
@@ -112,8 +112,9 @@ jobs:
112112 }
113113
114114 function extractDetailsSection(text, summaryLabel) {
115+ // Allow optional trailing text after the label (e.g. Renovate appends " (pkgalias)").
115116 const summaryRe = new RegExp(
116- `<details[^>]*>\\s*<summary>\\s*${escapeRegex(summaryLabel)}\\s *</summary>`,
117+ `<details[^>]*>\\s*<summary>\\s*${escapeRegex(summaryLabel)}[^<] *</summary>`,
117118 'i'
118119 );
119120 const summaryMatch = summaryRe.exec(text);
@@ -146,34 +147,79 @@ jobs:
146147 .trim();
147148 }
148149
149- const releaseNotes = extractDetailsSection(body, 'Release notes');
150- const commits = extractDetailsSection(body, 'Commits');
151150 // Prefer the dependency link from Dependabot's lead sentence.
152151 // Fallback to any GitHub repo URL if that sentence format changes.
152+ // Both github.com and redirect.github.com (used by Renovate) are matched.
153153 const dependencyRepoMatch = body.match(
154- /^\s*(?:Bumps|Update(?:s|d)?)\s+\[[^\]]+\]\(\s*https?:\/\/github\.com\/([A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+)(?:[\/#?][^)\s]*)?\s*\)/im
154+ /^\s*(?:Bumps|Update(?:s|d)?)\s+\[[^\]]+\]\(\s*https?:\/\/(?:redirect\.)? github\.com\/([A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+)(?:[\/#?][^)\s]*)?\s*\)/im
155155 );
156156 const repoMatch = dependencyRepoMatch || body.match(
157- /https?:\/\/github\.com\/([A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+)(?=\/|$|[)\]>\s"'?#])/i
157+ /https?:\/\/(?:redirect\.)? github\.com\/([A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+)(?=\/|$|[)\]>\s"'?#])/i
158158 );
159159 const upstreamRepo = repoMatch ? repoMatch[1].replace(/\.git$/i, '') : '';
160+
161+ // Release notes: Dependabot uses <details><summary>Release notes</summary>,
162+ // Renovate uses <details><summary>org/repo (pkg)</summary> under a ### Release Notes heading.
163+ let releaseNotes = extractDetailsSection(body, 'Release notes');
164+ if (!releaseNotes && upstreamRepo) {
165+ releaseNotes = extractDetailsSection(body, upstreamRepo);
166+ }
167+ if (!releaseNotes) {
168+ const rnHeading = body.match(/###\s*Release\s+Notes?\s*\n([\s\S]*?)(?=\n---|\n###\s|$)/i);
169+ if (rnHeading) releaseNotes = rnHeading[1].trim();
170+ }
171+
172+ // Commits: Dependabot uses <details><summary>Commits</summary>.
173+ // Renovate omits this section, so fall back to extracting any SHA-like hex strings from the body.
174+ let commits = extractDetailsSection(body, 'Commits');
175+ if (!commits) {
176+ const shaMatches = body.match(/\b[0-9a-f]{7,40}\b/gi);
177+ if (shaMatches && shaMatches.length > 0) commits = shaMatches.join('\n');
178+ }
179+
160180 const title = ${{ toJSON(steps.target_pr.outputs.title) }} || '';
181+ // Dependabot-style titles (3 groups: pkg, fromVersion, toVersion).
161182 const titleMatch =
162183 title.match(/[Uu]pdate\s+(.+?)\s+requirement\s+from\s+([^\s]+)\s+to\s+([^\s]+)/) ||
163184 title.match(/[Bb]ump\s+(.+?)\s+from\s+([^\s]+)\s+to\s+([^\s]+)/) ||
164185 title.match(/[Uu]pdate\s+(.+?)\s+from\s+([^\s]+)\s+to\s+([^\s]+)/);
165- const packageName = titleMatch ? titleMatch[1].trim() : '';
166- const fromVersion = titleMatch ? titleMatch[2].trim() : '';
167- const toVersion = titleMatch ? titleMatch[3].trim() : '';
186+ // Renovate-style titles (2 groups: pkg, toVersion — no "from" clause).
187+ const renovateTitleMatch = !titleMatch && (
188+ title.match(/(?:update|pin)\s+dependency\s+(.+?)\s+to\s+v?([^\s]+)/i) ||
189+ title.match(/(?:update|pin)\s+(.+?)\s+(?:action|digest|docker\s+tag)\s+to\s+v?([^\s]+)/i) ||
190+ title.match(/(?:update|pin)\s+(.+?)\s+to\s+v?([^\s]+)/i)
191+ );
192+ let packageName, fromVersion, toVersion;
193+ if (titleMatch) {
194+ packageName = titleMatch[1].trim();
195+ fromVersion = titleMatch[2].trim();
196+ toVersion = titleMatch[3].trim();
197+ } else if (renovateTitleMatch) {
198+ packageName = renovateTitleMatch[1].trim();
199+ fromVersion = '';
200+ toVersion = renovateTitleMatch[2].trim();
201+ } else {
202+ packageName = '';
203+ fromVersion = '';
204+ toVersion = '';
205+ }
206+ // Body-based version fallback: recover missing from/to from Renovate's table (e.g. `1.2.3` -> `1.2.4`).
207+ if (!fromVersion || !toVersion) {
208+ const bodyVersionMatch = body.match(/`([^`\s]+)`\s*(?:->|→)\s*`([^`\s]+)`/);
209+ if (bodyVersionMatch) {
210+ if (!fromVersion) fromVersion = bodyVersionMatch[1].replace(/^v/i, '');
211+ if (!toVersion) toVersion = bodyVersionMatch[2].replace(/^v/i, '');
212+ }
213+ }
168214
169215 if (!upstreamRepo) {
170- core.setFailed('Dependabot PR body is missing an upstream GitHub repository link.');
216+ core.setFailed('PR body is missing an upstream GitHub repository link.');
171217 return;
172218 }
173219 const normalizedReleaseNotes =
174- releaseNotes || 'Dependabot did not include a Release notes details section.';
220+ releaseNotes || 'PR body did not include a Release notes details section.';
175221 const normalizedCommits =
176- commits || 'Dependabot did not include a Commits details section.';
222+ commits || 'PR body did not include a Commits details section.';
177223
178224 const out = {
179225 prNumber: Number('${{ steps.target_pr.outputs.number }}'),
@@ -688,12 +734,13 @@ jobs:
688734 fi
689735 } > "$output_summary"
690736
737+ _gheof="GHEOF_$(uuidgen)"
691738 {
692739 echo "status=$status"
693740 echo "changed_files_count=$changed_count"
694- echo "summary<<EOF "
741+ echo "summary<<${_gheof} "
695742 cat "$output_summary"
696- echo "EOF "
743+ echo "${_gheof} "
697744 } >> "$GITHUB_OUTPUT"
698745
699746 if [ "$total_count" -gt 0 ] && [ "$warn_only" = true ]; then
@@ -709,7 +756,7 @@ jobs:
709756 PACKAGE_NAME : ${{ steps.dependabot_context.outputs.package_name }}
710757 run : |
711758 if [ -z "$PACKAGE_NAME" ]; then
712- echo "No package detected from Dependabot metadata." > package_usage.txt
759+ echo "No package detected from PR metadata." > package_usage.txt
713760 else
714761 {
715762 echo "Search pattern: $PACKAGE_NAME"
@@ -745,7 +792,7 @@ jobs:
745792 return ""
746793
747794 base_context = f"""
748- This is a Dependabot PR review request.
795+ This is a dependency bot PR review request.
749796
750797 PR title:
751798 {os.getenv("PR_TITLE", "")}
@@ -758,13 +805,13 @@ jobs:
758805 - from: {os.getenv("FROM_VERSION", "")}
759806 - to: {os.getenv("TO_VERSION", "")}
760807
761- Dependabot comment context JSON:
808+ PR context JSON:
762809 {read_file("dependabot_comment_context.json")}
763810
764- Dependabot Release notes:
811+ Release notes:
765812 {read_file("dependabot_release_notes.md")}
766813
767- Dependabot Commits:
814+ Commits:
768815 {read_file("dependabot_commits.md")}
769816
770817 Local usage hints (non-authoritative rg hits):
0 commit comments