Skip to content

Commit 990931c

Browse files
committed
Start release notes section extraction
1 parent 2976f50 commit 990931c

File tree

1 file changed

+146
-3
lines changed

1 file changed

+146
-3
lines changed

make_release/release-note/notes.nu

Lines changed: 146 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use std/assert
2+
13
def md-link [text: string, link: string] {
24
$"[($text)]\(($link)\)"
35
}
@@ -8,6 +10,19 @@ export def list-prs [
810
--since: datetime # list PRs on or after this date (defaults to 4 weeks ago if `--milestone` is not provided)
911
--milestone: string # only list PRs in a certain milestone
1012
--label: string # the PR label to filter by, e.g. 'good-first-issue'
13+
] {
14+
query-prs $repo --since=$since --milestone=$milestone --label=$label
15+
| select author title number mergedAt url
16+
| sort-by mergedAt --reverse
17+
| update author { get login }
18+
}
19+
20+
# Construct a GitHub query for merged PRs on a repo.
21+
def query-prs [
22+
repo: string = 'nushell/nushell' # the name of the repo, e.g. 'nushell/nushell'
23+
--since: datetime # list PRs on or after this date (defaults to 4 weeks ago if `--milestone` is not provided)
24+
--milestone: string # only list PRs in a certain milestone
25+
--label: string # the PR label to filter by, e.g. 'good-first-issue'
1126
] {
1227
mut query_parts = []
1328

@@ -28,11 +43,139 @@ export def list-prs [
2843

2944
(gh --repo $repo pr list --state merged
3045
--limit (inf | into int)
31-
--json author,title,number,mergedAt,url
46+
--json author,title,number,mergedAt,url,body,labels
3247
--search $query)
3348
| from json
34-
| sort-by mergedAt --reverse
35-
| update author { get login }
49+
}
50+
51+
# Get the release notes for all merged PRs on a repo.
52+
export def pr-notes [
53+
repo: string = 'nushell/nushell' # the name of the repo, e.g. 'nushell/nushell'
54+
--since: datetime # list PRs on or after this date (defaults to 4 weeks ago if `--milestone` is not provided)
55+
--milestone: string # only list PRs in a certain milestone
56+
--label: string # the PR label to filter by, e.g. 'good-first-issue'
57+
] {
58+
let processed = (
59+
list-prs $repo --since=$since --milestone=$milestone --label=$label
60+
| sort-by mergedAt
61+
| each { get-release-notes }
62+
)
63+
64+
$processed | display-notices
65+
66+
$processed
67+
| where notes? != null
68+
| select author title number mergedAt url notes
69+
}
70+
71+
def format-pr []: record -> string {
72+
let pr = $in
73+
let text = $"#($pr.number): ($pr.title)"
74+
$pr.url
75+
| ansi link -t $text
76+
| "- " ++ $in
77+
}
78+
79+
# Attempt to extract the "Release notes summary" section from a PR.
80+
#
81+
# If a valid release notes section is found, a "notes" column is added.
82+
# If any issues are detected, a "notices" column with additional information is added.
83+
def get-release-notes []: record -> record {
84+
mut pr = $in
85+
const READY = "pr:release-notes-mention"
86+
87+
if "## Release notes summary" not-in $pr.body {
88+
return ($pr | add-notice error "no release notes section")
89+
}
90+
91+
let notes = $pr.body | extract-notes
92+
let has_ready_label = $READY in $pr.labels.name
93+
94+
# If the notes are empty, it doesn't need any labels
95+
if ($notes | notes-are-empty) {
96+
if not $has_ready_label {
97+
$pr = $pr | add-notice warning "empty release notes section and no explicit label"
98+
}
99+
return $pr
100+
}
101+
102+
# If the notes section isn't empty, make sure we have the ready label
103+
if $READY not-in $pr.labels.name {
104+
return ($pr | add-notice error $"no ($READY) label")
105+
}
106+
107+
# Check that a category is selected
108+
if ($pr.labels.name | where $it starts-with "pr:" | reject $READY | is-empty) {
109+
$pr = $pr | add-notice warning "no explicit release notes category selected (defaults to mention)"
110+
}
111+
112+
# Check for suspiciously short release notes section
113+
if ($notes | split words | length) < 10 {
114+
$pr = $pr | add-notice warning "release notes section that is less than 10 words"
115+
}
116+
117+
$pr | insert notes $notes
118+
}
119+
120+
# Extracts the "Release notes summary" section of the PR description
121+
def extract-notes []: string -> string {
122+
lines
123+
# skip until release notes heading
124+
| skip until { $in starts-with "## Release notes summary" }
125+
# this should already have been checked
126+
| if ($in | is-empty) { assert false } else {}
127+
| skip 1 # remove header
128+
# extract until next heading
129+
| take until { $in starts-with "##" }
130+
| str join (char nl)
131+
# remove HTML comments
132+
| str replace -amr '<!--\O*?-->' ''
133+
| str trim
134+
}
135+
136+
def notes-are-empty []: string -> bool {
137+
$in in ["", "N/A"]
138+
}
139+
140+
# Adds an entry to the "notices" field of a PR
141+
def add-notice [type: string, message: string]: record -> record {
142+
upsert notices {
143+
append {type: $type, message: $message}
144+
}
145+
}
146+
147+
# Print all of the notices associated with a PR
148+
def display-notices [] {
149+
let prs = $in
150+
151+
$prs
152+
# Create row with PR info for each notice
153+
| each {|pr|
154+
get notices | each {|e|
155+
$pr | insert type $e.type | insert message $e.message
156+
}
157+
}
158+
| flatten
159+
| group-by --to-table type
160+
| sort-by -r type
161+
| each {|e| $e.items | display-notice-type $e.type }
162+
| ignore
163+
}
164+
165+
# Print notices of a certain type
166+
def display-notice-type [type: string] {
167+
let prs = $in
168+
let colors = {error: (ansi red), warning: (ansi yellow)}
169+
let color = $colors | get $type
170+
171+
$prs
172+
| group-by message --to-table
173+
| sort-by message
174+
| each {|e|
175+
print $"($color)PRs with ($e.message):"
176+
$e.items | each { format-pr | print }
177+
print ""
178+
}
36179
}
37180

38181
# Format the output of `list-prs` as a markdown table

0 commit comments

Comments
 (0)