Context-Free R Package Release Checklist Generation with usethis

The release checklist generation function described in this post is also available in this GitHub Gist.

Photo by David Marcu.
Photo by David Marcu.

Software release at scale

An agile, automated, and reliable release process is a key component of continuous delivery for modern software engineering teams. In particular, releasing new versions of R packages often involves going through a sequence of manual and automated checks to ensure quality and compliance. To scale up such efforts within a development team, it is crucial to follow a consistent release checklist.

The usethis solution

Without a doubt, usethis::use_release_issue() provides an excellent option for automating the creation of R package release checklist as GitHub issues, where one can check off the items formatted as a task list. Since it first became available in April 2019 (usethis 1.5.0), I have successfully used a digested version of the checklist for releasing many of the R packages I maintain or contribute to.

The only minor inconvenience I had before was that the context required for using this function can be a bit challenging to meet sometimes. As of June 2023, running the function assumes the following context from the project directory, Git setup, and GitHub configuration:

  1. An R project (package) directory.
  2. A Git directory that has been initialized.
  3. A Git directory that has been configured with (GitHub) remote.
  4. Has internet connection, can push to the remote GitHub repository and create a GitHub issue via GitHub API.

Besides, I am a bit paranoid about anything that automates to the extent that they speak or push buttons for me. To me, “zero trust” is a good principle to follow in such cases, as the time to review and copy-paste is probably negligible.

A context-free release checklist generator

Fortunately, after understanding how the usethis function works, we can build a sandboxed version: a new function that returns release checklists without requiring any project or version control context and does not interact with GitHub.

The new function can be built by leveraging usethis:::release_checklist() and creating minimal mocking artifacts that we used in developing the R Markdown and Quarto project link checker. We can also set a small number of parameters with sensible default values for flexibility.

Here is how this new function use_release_checklist() could look like:

#' Create a release checklist without requiring context
#'
#' @param package Package name.
#' @param version Release version number.
#' @param on_cran Is the package already on CRAN? Default is `TRUE`.
#' @param has_news Does the package use `NEWS.md`? Default is `TRUE`.
#' @param has_readme Does the package use `README.Rmd`? Default is `FALSE`.
#' @param has_lifecycle Does the package use lifecycle? Default is `FALSE`.
#'
#' @return Release checklist in Markdown format (invisibly).
#'
#' @examples
#' use_release_checklist("pkgname", version = "0.2.0")
#' use_release_checklist("pkgname", version = "0.1.0", on_cran = FALSE)
use_release_checklist <- function(
    package,
    version,
    on_cran = TRUE,
    has_news = TRUE,
    has_readme = FALSE,
    has_lifecycle = FALSE) {
  checklist <- getFromNamespace("release_checklist", "usethis")

  dir <- tempfile()
  dir.create(dir)
  on.exit(unlink(dir, recursive = TRUE), add = TRUE)

  withr::with_dir(dir, {
    write(paste("Package:", package), "DESCRIPTION")
    if (has_news) file.create("NEWS.md")
    if (has_lifecycle) write("Imports: lifecycle", "DESCRIPTION", append = TRUE)
    if (has_readme) file.create("README.Rmd")
  })

  mkd <- usethis::with_project(
    dir, checklist(version = version, on_cran = on_cran),
    quiet = TRUE
  )

  cat(mkd, sep = "\n")

  invisible(mkd)
}

For regular releases, one can run this function anywhere to get a release checklist:

use_release_checklist("pkgname", version = "0.2.0")
#> Prepare for release:
#> 
#> * [ ] `git pull`
#> * [ ] Check [current CRAN check results](https://cran.rstudio.org/web/checks/check_results_pkgname.html)
#> * [ ] [Polish NEWS](https://style.tidyverse.org/news.html#news-release)
#> * [ ] `usethis::use_github_links()`
#> * [ ] `urlchecker::url_check()`
#> * [ ] `devtools::check(remote = TRUE, manual = TRUE)`
#> * [ ] `devtools::check_win_devel()`
#> * [ ] `revdepcheck::revdep_check(num_workers = 4)`
#> * [ ] Update `cran-comments.md`
#> * [ ] `git push`
#> * [ ] Draft blog post
#> 
#> Submit to CRAN:
#> 
#> * [ ] `usethis::use_version('minor')`
#> * [ ] `devtools::submit_cran()`
#> * [ ] Approve email
#> 
#> Wait for CRAN...
#> 
#> * [ ] Accepted :tada:
#> * [ ] Add preemptive link to blog post in pkgdown news menu
#> * [ ] `usethis::use_github_release()`
#> * [ ] `usethis::use_dev_version(push = TRUE)`
#> * [ ] Finish blog post
#> * [ ] Tweet

Similarly, for first releases that require additional checks, one can use:

use_release_checklist("pkgname", version = "0.1.0", on_cran = FALSE)
#> First release:
#> 
#> * [ ] `usethis::use_cran_comments()`
#> * [ ] Update (aspirational) install instructions in README
#> * [ ] Proofread `Title:` and `Description:`
#> * [ ] Check that all exported functions have `@return` and `@examples`
#> * [ ] Check that `Authors@R:` includes a copyright holder (role 'cph')
#> * [ ] Check [licensing of included files](https://r-pkgs.org/license.html#sec-code-you-bundle)
#> * [ ] Review <https://github.com/DavisVaughan/extrachecks>
#> 
#> Prepare for release:
#> 
#> * [ ] `git pull`
#> * [ ] `usethis::use_github_links()`
#> * [ ] `urlchecker::url_check()`
#> * [ ] `devtools::check(remote = TRUE, manual = TRUE)`
#> * [ ] `devtools::check_win_devel()`
#> * [ ] `git push`
#> * [ ] Draft blog post
#> 
#> Submit to CRAN:
#> 
#> * [ ] `usethis::use_version('minor')`
#> * [ ] `devtools::submit_cran()`
#> * [ ] Approve email
#> 
#> Wait for CRAN...
#> 
#> * [ ] Accepted :tada:
#> * [ ] Add preemptive link to blog post in pkgdown news menu
#> * [ ] `usethis::use_github_release()`
#> * [ ] `usethis::use_dev_version(push = TRUE)`
#> * [ ] Finish blog post
#> * [ ] Tweet

Closing thoughts

You can adjust the other input arguments to suit the particular needs of your packages. In addition, you may consider customizing the returned character vector of Markdown text to incorporate package-specific or organization-specific workflows, and even copy the returned text to clipboard automatically.

Of course, to enforce the same standard across team members, proper training and communication is essential to ensure that everyone understands what each checklist item means and how to handle common and new situations.