Skip to content

Commit a845daf

Browse files
vaindclaude
andcommitted
feat: enhance Danger with inline changelog suggestions
- Implement inline changelog suggestions instead of generic instructions - Add unified flavor configuration with grouped labels - Extract testable functions into dangerfile-utils.js module - Add comprehensive test suite with 21 test cases - Integrate JavaScript testing into CI workflow Resolves #45 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 06ba389 commit a845daf

File tree

4 files changed

+519
-28
lines changed

4 files changed

+519
-28
lines changed

.github/workflows/script-tests.yml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,21 @@ jobs:
2323
- run: Invoke-Pester
2424
working-directory: updater
2525
shell: pwsh
26+
27+
danger:
28+
name: Danger JS Tests
29+
runs-on: ubuntu-latest
30+
defaults:
31+
run:
32+
working-directory: danger
33+
steps:
34+
- uses: actions/checkout@v4
35+
36+
- uses: actions/setup-node@v4
37+
with:
38+
node-version: '18'
39+
40+
- run: node --test
41+
42+
- name: Check syntax
43+
run: node -c dangerfile.js

danger/dangerfile-utils.js

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
/// Unified configuration for PR flavors
2+
const FLAVOR_CONFIG = [
3+
{
4+
labels: ["feat", "feature"],
5+
changelog: "Features",
6+
isFeature: true
7+
},
8+
{
9+
labels: ["fix", "bug", "bugfix"],
10+
changelog: "Fixes"
11+
},
12+
{
13+
labels: ["sec", "security"],
14+
changelog: "Security"
15+
},
16+
{
17+
labels: ["perf", "performance"],
18+
changelog: "Performance"
19+
},
20+
{
21+
labels: ["docs", "doc"],
22+
changelog: undefined // Internal documentation changes
23+
},
24+
{
25+
labels: ["style", "refactor"],
26+
changelog: undefined // Internal code improvements - no changelog needed
27+
},
28+
{
29+
labels: ["test"],
30+
changelog: undefined // Test updates don't need changelog
31+
},
32+
{
33+
labels: ["build"],
34+
changelog: undefined // Build system changes
35+
},
36+
{
37+
labels: ["ci"],
38+
changelog: undefined // CI changes don't need changelog
39+
},
40+
{
41+
labels: ["chore"],
42+
changelog: undefined // General maintenance
43+
},
44+
{
45+
labels: ["deps", "dep", "chore(deps)", "build(deps)"],
46+
changelog: undefined // Dependency updates
47+
}
48+
];
49+
50+
/// Get flavor configuration for a given PR flavor
51+
function getFlavorConfig(prFlavor) {
52+
const normalizedFlavor = prFlavor.toLowerCase().trim();
53+
54+
const config = FLAVOR_CONFIG.find(config =>
55+
config.labels.includes(normalizedFlavor)
56+
);
57+
58+
return config || {
59+
changelog: "Features" // Default to Features
60+
};
61+
}
62+
63+
/// Find the appropriate line to insert a changelog entry
64+
function findChangelogInsertionPoint(lines, sectionName) {
65+
let unreleasedIndex = -1;
66+
let sectionIndex = -1;
67+
let insertionLine = -1;
68+
let isNewSection = false;
69+
70+
// Find the "## Unreleased" section
71+
for (let i = 0; i < lines.length; i++) {
72+
if (lines[i].match(/^##\s+Unreleased/i)) {
73+
unreleasedIndex = i;
74+
break;
75+
}
76+
}
77+
78+
if (unreleasedIndex === -1) {
79+
// No "Unreleased" section found
80+
return { success: false };
81+
}
82+
83+
// Look for the target section under "Unreleased"
84+
for (let i = unreleasedIndex + 1; i < lines.length; i++) {
85+
// Stop if we hit another ## section (next release)
86+
if (lines[i].match(/^##\s+/)) {
87+
break;
88+
}
89+
90+
// Check if this is our target section
91+
if (lines[i].match(new RegExp(`^###\\s+${sectionName}`, 'i'))) {
92+
sectionIndex = i;
93+
break;
94+
}
95+
}
96+
97+
if (sectionIndex !== -1) {
98+
// Found the section, find where to insert within it
99+
for (let i = sectionIndex + 1; i < lines.length; i++) {
100+
// Stop if we hit another section or end
101+
if (lines[i].match(/^##[#]?\s+/)) {
102+
break;
103+
}
104+
105+
// Find the first non-empty line after the section header
106+
if (lines[i].trim() !== '') {
107+
// Insert before this line if it's not already a bullet point
108+
insertionLine = lines[i].match(/^-\s+/) ? i + 1 : i;
109+
break;
110+
}
111+
}
112+
113+
// If we didn't find a good spot, insert right after the section header
114+
if (insertionLine === -1) {
115+
insertionLine = sectionIndex + 2; // +1 for the section, +1 for empty line
116+
}
117+
} else {
118+
// Section doesn't exist, we need to create it
119+
isNewSection = true;
120+
121+
// Find where to insert the new section
122+
// Look for the next section after Unreleased or find a good spot
123+
for (let i = unreleasedIndex + 1; i < lines.length; i++) {
124+
if (lines[i].match(/^##\s+/)) {
125+
// Insert before the next release section
126+
insertionLine = i - 1;
127+
break;
128+
} else if (lines[i].match(/^###\s+/)) {
129+
// Insert before the first existing section
130+
insertionLine = i;
131+
break;
132+
}
133+
}
134+
135+
// If no good spot found, insert after a reasonable gap from Unreleased
136+
if (insertionLine === -1) {
137+
insertionLine = unreleasedIndex + 2;
138+
}
139+
}
140+
141+
return {
142+
success: true,
143+
lineNumber: insertionLine + 1, // Convert to 1-based line numbering
144+
isNewSection: isNewSection,
145+
sectionHeader: isNewSection ? `### ${sectionName}` : null
146+
};
147+
}
148+
149+
/// Extract PR flavor from title or branch name
150+
function extractPRFlavor(prTitle, prBranchRef) {
151+
if (prTitle) {
152+
const parts = prTitle.split(":");
153+
if (parts.length > 1) {
154+
return parts[0].toLowerCase().trim();
155+
}
156+
}
157+
if (prBranchRef) {
158+
const parts = prBranchRef.split("/");
159+
if (parts.length > 1) {
160+
return parts[0].toLowerCase();
161+
}
162+
}
163+
return "";
164+
}
165+
166+
module.exports = {
167+
FLAVOR_CONFIG,
168+
getFlavorConfig,
169+
findChangelogInsertionPoint,
170+
extractPRFlavor
171+
};

0 commit comments

Comments
 (0)