forked from r-lib/usethis
-
Notifications
You must be signed in to change notification settings - Fork 0
/
release.R
204 lines (182 loc) · 6.35 KB
/
release.R
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
191
192
193
194
195
196
197
198
199
200
201
202
203
204
#' Create a release checklist in a GitHub issue
#'
#' When preparing to release a package there are quite a few steps that need to
#' be performed, and some of the steps can take multiple hours. This function
#' creates an issue checklist so that you can keep track of where you are in the
#' process, and feel a sense of satisfaction as you progress. It also helps
#' watchers of your package stay informed about where you are in the process.
#'
#' @param version Optional version number for release. If unspecified, you can
#' make an interactive choice.
#' @export
#' @examples
#' \dontrun{
#' use_release_issue("2.0.0")
#' }
use_release_issue <- function(version = NULL) {
check_is_package("use_release_issue()")
tr <- target_repo(github_get = TRUE)
if (!isTRUE(tr$can_push)) {
ui_line("
It is very unusual to open a release issue on a repo you can't push to:
{ui_value(tr$repo_spec)}")
if (ui_nope("Do you really want to do this?")) {
ui_stop("Aborting.")
}
}
version <- version %||% choose_version()
if (is.null(version)) {
return(invisible(FALSE))
}
on_cran <- !is.null(cran_version())
checklist <- release_checklist(version, on_cran)
gh <- gh_tr(tr)
issue <- gh(
"POST /repos/{owner}/{repo}/issues",
title = glue("Release {project_name()} {version}"),
body = paste0(checklist, "\n", collapse = "")
)
view_url(issue$html_url)
}
release_checklist <- function(version, on_cran) {
type <- release_type(version)
cran_results <- cran_results_url()
has_src <- dir_exists(proj_path("src"))
has_news <- file_exists(proj_path("NEWS.md"))
has_pkgdown <- uses_pkgdown()
has_readme <- file_exists(proj_path("README.Rmd"))
has_extra <- exists("release_bullets", parent.env(globalenv()))
todo <- function(x, cond = TRUE) {
x <- glue(x, .envir = parent.frame())
if (cond) {
paste0("* [ ] ", x)
}
}
c(
"Prepare for release:",
"",
todo("Check that description is informative", !on_cran),
todo("Check licensing of included files", !on_cran),
todo("`devtools::build_readme()`", has_readme),
todo("`usethis::use_cran_comments()`", !on_cran),
todo("Check [current CRAN check results]({cran_results})", on_cran),
todo("`devtools::check(remote = TRUE, manual = TRUE)`"),
todo("`devtools::check_win_devel()`"),
todo("`rhub::check_for_cran()`"),
todo("`rhub::check(platform = 'ubuntu-rchk')`", has_src),
todo("`rhub::check_with_sanitizers()`", has_src),
todo("`revdepcheck::revdep_check(num_workers = 4)`", on_cran),
todo("`urlchecker::url_check()`"),
todo("Update `cran-comments.md`"),
todo("[Polish NEWS](https://style.tidyverse.org/news.html#news-release)", on_cran),
todo("Review pkgdown reference index for, e.g., missing topics", has_pkgdown && type != "patch"),
todo("Draft blog post", type != "patch"),
if (has_extra) paste0("* [ ] ", get("release_bullets", parent.env(globalenv()))()),
"",
"Submit to CRAN:",
"",
todo("`usethis::use_version('{type}')`"),
todo("`devtools::submit_cran()`"),
todo("Approve email"),
"",
"Wait for CRAN...",
"",
todo("Accepted :tada:"),
todo("`usethis::use_news_md()`", !has_news),
todo("`usethis::use_github_release()`"),
todo("`usethis::use_dev_version()`"),
todo("Update install instructions in README", !on_cran),
todo("Finish blog post", type != "patch"),
todo("Tweet", type != "patch"),
todo("Add link to blog post in pkgdown news menu", type != "patch")
)
}
release_type <- function(version) {
x <- unclass(numeric_version(version))[[1]]
n <- length(x)
if (n >= 3 && x[[3]] != 0L) {
"patch"
} else if (n >= 2 && x[[2]] != 0L) {
"minor"
} else {
"major"
}
}
#' Draft a GitHub release
#'
#' Creates a __draft__ GitHub release for the current package using the current
#' version and `NEWS.md`. If you are comfortable that it is correct, you will
#' need to publish the release from GitHub. It also deletes `CRAN-RELEASE` and
#' checks that you've pushed all commits to GitHub.
#'
#' @param host,auth_token \lifecycle{defunct}: No longer consulted now that
#' usethis allows the gh package to lookup a token based on a URL determined
#' from the current project's GitHub remotes.
#' @export
use_github_release <- function(host = deprecated(),
auth_token = deprecated()) {
if (lifecycle::is_present(host)) {
deprecate_warn_host("use_github_release")
}
if (lifecycle::is_present(auth_token)) {
deprecate_warn_auth_token("use_github_release")
}
tr <- target_repo(github_get = TRUE)
if (!isTRUE(tr$can_push)) {
ui_stop("
You don't seem to have push access for {ui_value(tr$repo_spec)}, which \\
is required to draft a release.")
}
challenge_non_default_branch(
"Are you sure you want to create a release on a non-default branch?"
)
check_branch_pushed()
path <- proj_path("NEWS.md")
if (!file_exists(path)) {
ui_stop("{ui_path('NEWS.md')} not found")
}
cran_release <- proj_path("CRAN-RELEASE")
if (file_exists(cran_release)) {
file_delete(cran_release)
}
news <- news_latest(read_utf8(path))
package <- package_data()
gh <- gh_tr(tr)
release <- gh(
"POST /repos/{owner}/{repo}/releases",
tag_name = paste0("v", package$Version),
target_commitish = gert::git_info(repo = git_repo())$commit,
name = paste0(package$Package, " ", package$Version),
body = news, draft = TRUE
)
view_url(release$html_url)
}
cran_version <- function(package = project_name(),
available = utils::available.packages()) {
idx <- available[, "Package"] == package
if (any(idx)) {
as.package_version(available[package, "Version"])
} else {
NULL
}
}
cran_results_url <- function(package = project_name()) {
glue("https://cran.rstudio.org/web/checks/check_results_{package}.html")
}
news_latest <- function(lines) {
headings <- which(grepl("^#\\s+", lines))
if (length(headings) == 0) {
ui_stop("No top-level headings found in {ui_value('NEWS.md')}")
} else if (length(headings) == 1) {
news <- lines[seq2(headings + 1, length(lines))]
} else {
news <- lines[seq2(headings[[1]] + 1, headings[[2]] - 1)]
}
# Remove leading and trailing empty lines
text <- which(news != "")
if (length(text) == 0) {
return("")
}
news <- news[text[[1]]:text[[length(text)]]]
paste0(news, "\n", collapse = "")
}