-
Notifications
You must be signed in to change notification settings - Fork 314
Expand file tree
/
Copy pathgenerate.nu
More file actions
190 lines (162 loc) · 6.61 KB
/
generate.nu
File metadata and controls
190 lines (162 loc) · 6.61 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
# The sections to be included in the release notes
const SECTIONS = [
[label, h2, h3];
["notes:breaking-changes", "Breaking changes", "Other breaking changes"]
["notes:additions", "Additions", "Other additions"]
["notes:deprecations", "Deprecations", "Other deprecations"]
["notes:removals", "Removals", "Other removals"]
["notes:other", "Other changes", "Additional changes"]
["notes:fixes", "Bug fixes", "Other fixes"]
["notes:mention", null, null]
["notes:hide", null, null]
]
use notice.nu *
use util.nu *
# Attempt to extract the "Release notes summary" section from a PR.
#
# Multiple checks are done to ensure that each PR has a valid release notes summary.
# If any issues are detected, a "notices" column with additional information is added.
export def get-release-notes []: record -> record {
mut pr = $in
let has_ready_label = "notes:ready" in $pr.labels.name
let sections = $SECTIONS | where label in $pr.labels.name
let hall_of_fame = $SECTIONS | where label == "notes:mention" | only
# Extract the notes section
mut notes = if "## Release notes summary" in $pr.body {
$pr.body | extract-notes
} else if $has_ready_label {
# If no release notes summary exists but ready label is set, treat as empty
$pr = $pr | add-notice warning "no release notes section but notes:ready label"
""
} else {
return ($pr | add-notice error "no release notes section")
}
# Check for empty notes section
if ($notes | is-empty-keyword) {
if ($sections | where label not-in (labels-without-heading) | is-not-empty) {
return ($pr | add-notice error "empty summary but a category other than Hall of Fame or hidden")
}
if ($notes | is-empty) and not $has_ready_label {
$pr = $pr | add-notice warning "empty release notes section and no explicit label"
}
$pr = $pr | insert section $hall_of_fame
$pr = $pr | insert notes ($pr.title | clean-title)
return $pr
}
# If the notes section isn't empty, make sure we have the ready label
if not $has_ready_label {
return ($pr | add-notice error $"no notes:ready label")
}
# Check that exactly one category is selected
let section = if ($sections | is-empty) {
$pr = $pr | add-notice info "no explicit release notes category selected (defaults to Hall of Fame)"
$hall_of_fame
} else if ($sections | length) > 1 {
return ($pr | add-notice error "multiple release notes categories selected")
} else {
$sections | only
}
# Add section to PR
$pr = $pr | insert section $section
let lines = $notes | lines | length
if $section.label == "notes:mention" and ($lines > 1) {
return ($pr | add-notice error "multi-line summaries in Hall of Fame section")
}
# Add PR title as default heading for multi-line summaries
if $lines > 1 and not ($notes starts-with "###") {
$pr = $pr | add-notice info "multi-line summaries with no explicit title (using PR title as heading title)"
$notes = "### " + ($pr.title | clean-title) ++ (char nl) ++ (char nl) ++ $notes
}
# Check for suspiciously short release notes section
if ($notes | split words | length) < 10 {
$pr = $pr | add-notice warning "release notes section that is less than 10 words"
}
$pr | insert notes $notes
}
# Extracts the "Release notes summary" section of the PR description
export def extract-notes []: string -> string {
lines
# skip until release notes heading
| skip until { $in starts-with "## Release notes summary" }
# this should already have been checked
| if ($in | is-empty) { assert false } else {}
| skip 1 # remove header
# extract until next heading
| take until {
$in starts-with "# " or $in starts-with "## " or $in starts-with "---"
}
| str join (char nl)
# remove HTML comments
| str replace -amr '<!--\O*?-->' ''
| str trim
}
# Generate the release notes from the list of PRs.
export def generate-notes [version: string]: table -> string {
let prs = $in
const template_path = path self "template.md"
let template = open $template_path
let arguments = {
# chop off the `v` in the version
version: ($version | str substring 1..),
changes: ($prs | generate-changes-section),
hall_of_fame: ($prs | generate-hall-of-fame)
changelog: (generate-full-changelog $version)
}
$arguments | format pattern $template
}
# Generate the "Changes" section of the release notes.
export def generate-changes-section []: table -> string {
group-by --to-table section.label
| rename section prs
# sort sections in order of appearance in table
| sort-by {|i| $SECTIONS | enumerate | where item.label == $i.section | only }
# Hall of Fame is handled separately
| where section not-in (labels-without-heading)
| each { generate-section }
| str join (char nl)
}
# Generate a subsection of the "Changes" section of the release notes.
export def generate-section []: record<section: string, prs: table> -> string {
let prs = $in.prs
let section = $prs.0.section
mut body = []
let multiline = $prs | where ($it.notes | lines | length) > 1
let bullet = $prs | where ($it.notes | lines | length) == 1
# Add header
$body ++= [$"## ($section.h2)\n"]
# Add multi-line summaries
for note in $multiline.notes {
if ($note | str ends-with "\n") {
$body ++= [$note]
} else {
$body ++= [($note ++ (char nl))]
}
}
# Add single-line summaries
if ($multiline | is-not-empty) {
$body ++= [$"### ($section.h3)\n"]
}
$body ++= $bullet | each {|pr| "* " ++ $pr.notes ++ $" \(($pr | pr-link)\)" }
($body | str join (char nl)) ++ (char nl)
}
# Generate the "Hall of Fame" section of the release notes.
export def generate-hall-of-fame []: table -> string {
where section.label == "notes:mention"
# If the PR has no notes, use the title
| update notes {|pr| default -e $pr.title }
| update author { md-link $'@($in.login)' $'https://github.com/($in.login)' }
| insert link { pr-link }
| select author notes link
| rename -c {notes: change}
| to md
| escape-tag
}
# Generate the "Full changelog" section of the release notes.
export def generate-full-changelog [version: string]: nothing -> string {
list-prs --milestone=$version
| pr-table
}
# Get section labels which don't have a corresponding heading (i.e., don't appear in Changes section)
def labels-without-heading [] {
$SECTIONS | where h2 == null | get label
}