| 178 | } |
| 179 | |
| 180 | func generateReleaseNotes(changelogPath string) (string, error) { |
| 181 | lines, err := readFileLines(changelogPath) |
| 182 | if err != nil { |
| 183 | return "", fmt.Errorf("failed to read changelog: %w", err) |
| 184 | } |
| 185 | |
| 186 | // Find the first two ## headers |
| 187 | firstHeaderIndex := -1 |
| 188 | secondHeaderIndex := -1 |
| 189 | |
| 190 | for i, line := range lines { |
| 191 | if strings.HasPrefix(line, "## ") { |
| 192 | if firstHeaderIndex == -1 { |
| 193 | firstHeaderIndex = i |
| 194 | } else if secondHeaderIndex == -1 { |
| 195 | secondHeaderIndex = i |
| 196 | break |
| 197 | } |
| 198 | } |
| 199 | } |
| 200 | |
| 201 | if secondHeaderIndex == -1 { |
| 202 | return "", fmt.Errorf("could not find two ## headers in the changelog") |
| 203 | } |
| 204 | |
| 205 | // Extract the content between the two headers |
| 206 | content := []string{} |
| 207 | for i := firstHeaderIndex + 1; i < secondHeaderIndex; i++ { |
| 208 | content = append(content, lines[i]) |
| 209 | } |
| 210 | |
| 211 | // Strip leading and trailing whitespace-only lines |
| 212 | start := 0 |
| 213 | end := len(content) - 1 |
| 214 | |
| 215 | // Find first non-whitespace line |
| 216 | for start < len(content) && strings.TrimSpace(content[start]) == "" { |
| 217 | start++ |
| 218 | } |
| 219 | |
| 220 | // Find last non-whitespace line |
| 221 | for end >= 0 && strings.TrimSpace(content[end]) == "" { |
| 222 | end-- |
| 223 | } |
| 224 | |
| 225 | // Build the result string |
| 226 | var result strings.Builder |
| 227 | for i := start; i <= end; i++ { |
| 228 | result.WriteString(content[i]) |
| 229 | if i < end { |
| 230 | result.WriteString("\n") |
| 231 | } |
| 232 | } |
| 233 | |
| 234 | return result.String(), nil |
| 235 | } |
| 236 | |
| 237 | func readFileLines(filename string) ([]string, error) { |