1+ use std/assert
2+
13def 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