relnotes.go 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  1. // Copyright (C) 2025 The Syncthing Authors.
  2. //
  3. // This Source Code Form is subject to the terms of the Mozilla Public
  4. // License, v. 2.0. If a copy of the MPL was not distributed with this file,
  5. // You can obtain one at https://mozilla.org/MPL/2.0/.
  6. //go:build ignore
  7. // +build ignore
  8. package main
  9. import (
  10. "bytes"
  11. "cmp"
  12. "encoding/json"
  13. "errors"
  14. "flag"
  15. "fmt"
  16. "io"
  17. "log"
  18. "net/http"
  19. "os"
  20. "regexp"
  21. "strings"
  22. )
  23. var (
  24. githubToken = os.Getenv("GITHUB_TOKEN")
  25. githubRepo = cmp.Or(os.Getenv("GITHUB_REPOSITORY"), "syncthing/syncthing")
  26. )
  27. func main() {
  28. ver := flag.String("new-ver", "", "New version tag")
  29. prevVer := flag.String("prev-ver", "", "Previous version tag")
  30. branch := flag.String("branch", "HEAD", "Branch to release from")
  31. flag.Parse()
  32. log.SetOutput(os.Stderr)
  33. if *ver == "" {
  34. log.Fatalln("Must set --new-ver")
  35. }
  36. if githubToken == "" {
  37. log.Fatalln("Must set $GITHUB_TOKEN")
  38. }
  39. notes, err := additionalNotes(*ver)
  40. if err != nil {
  41. log.Fatalln("Gathering additional notes:", err)
  42. }
  43. gh, err := generatedNotes(*ver, *branch, *prevVer)
  44. if err != nil {
  45. log.Fatalln("Gathering github notes:", err)
  46. }
  47. notes = append(notes, gh)
  48. fmt.Println(strings.Join(notes, "\n\n"))
  49. }
  50. // Load potential additional release notes from within the repo
  51. func additionalNotes(newVer string) ([]string, error) {
  52. var notes []string
  53. ver, _, _ := strings.Cut(newVer, "-")
  54. for {
  55. file := fmt.Sprintf("relnotes/%s.md", ver)
  56. if bs, err := os.ReadFile(file); err == nil {
  57. notes = append(notes, strings.TrimSpace(string(bs)))
  58. } else if !os.IsNotExist(err) {
  59. return nil, err
  60. }
  61. if idx := strings.LastIndex(ver, "."); idx > 0 {
  62. ver = ver[:idx]
  63. } else {
  64. break
  65. }
  66. }
  67. return notes, nil
  68. }
  69. // Load generated release notes (list of pull requests and contributors)
  70. // from GitHub.
  71. func generatedNotes(newVer, targetCommit, prevVer string) (string, error) {
  72. fields := map[string]string{
  73. "tag_name": newVer,
  74. "target_commitish": targetCommit,
  75. "previous_tag_name": prevVer,
  76. }
  77. bs, err := json.Marshal(fields)
  78. if err != nil {
  79. return "", err
  80. }
  81. req, err := http.NewRequest(http.MethodPost, "https://api.github.com/repos/"+githubRepo+"/releases/generate-notes", bytes.NewReader(bs)) //nolint:noctx
  82. if err != nil {
  83. return "", err
  84. }
  85. req.Header.Set("Accept", "application/vnd.github+json")
  86. req.Header.Set("Authorization", "Bearer "+githubToken)
  87. req.Header.Set("X-Github-Api-Version", "2022-11-28")
  88. res, err := http.DefaultClient.Do(req)
  89. if err != nil {
  90. return "", err
  91. }
  92. if res.StatusCode != http.StatusOK {
  93. bs, _ := io.ReadAll(res.Body)
  94. log.Print(string(bs))
  95. return "", errors.New(res.Status) //nolint:err113
  96. }
  97. defer res.Body.Close()
  98. var resJSON struct {
  99. Body string
  100. }
  101. if err := json.NewDecoder(res.Body).Decode(&resJSON); err != nil {
  102. return "", err
  103. }
  104. return strings.TrimSpace(removeHTMLComments(resJSON.Body)), nil
  105. }
  106. func removeHTMLComments(s string) string {
  107. return regexp.MustCompile(`<!--.*?-->`).ReplaceAllString(s, "")
  108. }