[mirror] Opinionated R package quickstart
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Configure some parts of package setup; fixed tests

VisruthSK 84a0d5c4 34046e74

+259 -59
+74 -17
R/bootstrapper.R
··· 6 6 #' @param fields Named list of `DESCRIPTION` fields passed to 7 7 #' [usethis::create_package()]. See [usethis::use_description()] 8 8 #' @param private Whether to create the GitHub repository as private. Defaults to `TRUE`. 9 + #' @param setup_gha Whether to configure GitHub Actions setup. 10 + #' @param setup_dependabot Whether to write a Dependabot configuration. 11 + #' @param setup_air_jarl Whether to configure Air and Jarl defaults. 9 12 #' @param ... Additional arguments passed to [usethis::create_package()]. 10 13 #' 11 14 #' @return Invisibly returns `NULL`. ··· 14 17 path = ".", 15 18 fields, 16 19 private = TRUE, 20 + setup_gha = TRUE, 21 + setup_dependabot = TRUE, 22 + setup_air_jarl = TRUE, 17 23 ... 18 24 ) { 19 25 create_package(path, fields, private, ...) 20 - pkg_setup() 26 + pkg_setup( 27 + setup_gha = setup_gha, 28 + setup_dependabot = setup_dependabot, 29 + setup_air_jarl = setup_air_jarl 30 + ) 21 31 invisible(NULL) 22 32 } 23 33 ··· 73 83 #' 74 84 #' Run the package setup steps used by `bootstrapper`, including test 75 85 #' infrastructure, README/NEWS creation, GitHub Actions, and linting defaults. 86 + #' Run this in the root directory of your package. 87 + #' 88 + #' @param setup_gha Whether to configure GitHub Actions setup. 89 + #' @param setup_dependabot Whether to write a Dependabot configuration. 90 + #' @param setup_air_jarl Whether to configure Air and Jarl defaults. 76 91 #' 77 92 #' @return Invisibly returns `NULL`. 78 93 #' @export 79 - pkg_setup <- function() { 94 + pkg_setup <- function( 95 + setup_gha = TRUE, 96 + setup_dependabot = TRUE, 97 + setup_air_jarl = TRUE 98 + ) { 80 99 tryCatch( 81 100 usethis::use_testthat(), 82 101 error = function(...) { 83 102 usethis::ui_stop( 84 - "This doesn't appear to be a package. Ensure you are in the right directory, or run {usethis::ui_code('create_package()')}." 103 + "This doesn't appear to be a package. Ensure you are in the right directory, or run {usethis::ui_code('create_package()')} in this directory to start a R package." 85 104 ) 86 105 } 87 106 ) ··· 94 113 fs::path("NEWS.md") 95 114 ) 96 115 97 - # GitHub Actions setup 116 + if (setup_gha) { 117 + configure_gha() 118 + } 119 + if (setup_dependabot) { 120 + configure_dependabot() 121 + } 122 + if (setup_air_jarl) { 123 + configure_air_jarl() 124 + } 125 + 126 + usethis::use_tidy_description() 127 + invisible(NULL) 128 + } 129 + 130 + # Helpers --------------------------------------------------------------------- 131 + 132 + #' Configure GitHub Actions Defaults 133 + #' 134 + #' Sets up standard GitHub Actions used by this package template and updates 135 + #' workflow references. 136 + #' 137 + #' @return Invisibly returns `NULL`. 138 + #' @keywords internal 139 + #' @noRd 140 + configure_gha <- function() { 98 141 usethis::use_github_action("check-standard", badge = TRUE) 99 142 usethis::use_github_action("test-coverage", badge = TRUE) 100 143 usethis::use_github_action( ··· 103 146 usethis::use_pkgdown_github_pages() 104 147 usethis::use_spell_check(error = TRUE) 105 148 106 - # Dependabot 149 + find_replace_in_gha("actions/checkout@v4", "actions/checkout@v6") 150 + find_replace_in_gha( 151 + "JamesIves/github-pages-deploy-action@v4.5.0", 152 + "JamesIves/github-pages-deploy-action@v4" 153 + ) 154 + invisible(NULL) 155 + } 156 + 157 + #' Configure Dependabot Defaults 158 + #' 159 + #' Writes a default Dependabot configuration for GitHub Actions. 160 + #' 161 + #' @return Invisibly returns `NULL`. 162 + #' @keywords internal 163 + #' @noRd 164 + configure_dependabot <- function() { 107 165 c( 108 166 "version: 2", 109 167 "updates:", ··· 113 171 " interval: \"weekly\"" 114 172 ) |> # TODO: move file to inst? 115 173 write_to_path(fs::path(".github", "dependabot.yml")) 174 + invisible(NULL) 175 + } 116 176 117 - # Air and Jarl configs 177 + #' Configure Air and Jarl Defaults 178 + #' 179 + #' Sets up Air, VS Code extension recommendations, and Jarl lint defaults. 180 + #' 181 + #' @return Invisibly returns `NULL`. 182 + #' @keywords internal 183 + #' @noRd 184 + configure_air_jarl <- function() { 118 185 usethis::use_air() 119 186 c( 120 187 "{", ··· 130 197 "extend-select = [\"TESTTHAT\"]" 131 198 ) |> 132 199 write_to_path(fs::path("tests", "jarl.toml")) # TODO: need to make GHA jarl runs respect this 133 - 134 - # Cleanup 135 - find_replace_in_gha("actions/checkout@v4", "actions/checkout@v6") 136 - find_replace_in_gha( 137 - "JamesIves/github-pages-deploy-action@v4.5.0", 138 - "JamesIves/github-pages-deploy-action@v4" 139 - ) 140 - usethis::use_tidy_description() 141 200 invisible(NULL) 142 201 } 143 - 144 - # Helpers --------------------------------------------------------------------- 145 202 146 203 #' Choose and Apply a License 147 204 #' ··· 166 223 "Skip for now" = FALSE 167 224 ) 168 225 usethis::ui_info("Select a license for this package.") 169 - selected_fn <- if (interactive()) { 226 + selected_fn <- if (is_interactive()) { 170 227 unname( 171 228 license_choices[[utils::menu( 172 229 choices = names(license_choices),
+11
R/utils.R
··· 79 79 pattern = "\\.ya?ml$" 80 80 ) 81 81 } 82 + 83 + #' Determine Interactive Mode 84 + #' 85 + #' Wrapper around [base::interactive()] to make interactive branches testable. 86 + #' 87 + #' @return A single logical value. 88 + #' @keywords internal 89 + #' @noRd 90 + is_interactive <- function() { 91 + interactive() 92 + }
+15 -1
man/bootstrapper.Rd
··· 4 4 \alias{bootstrapper} 5 5 \title{Bootstrap a New R Package} 6 6 \usage{ 7 - bootstrapper(path = ".", fields, private = TRUE, ...) 7 + bootstrapper( 8 + path = ".", 9 + fields, 10 + private = TRUE, 11 + setup_gha = TRUE, 12 + setup_dependabot = TRUE, 13 + setup_air_jarl = TRUE, 14 + ... 15 + ) 8 16 } 9 17 \arguments{ 10 18 \item{path}{Path where the package should be created. Defaults to \code{"."}} ··· 13 21 \code{\link[usethis:create_package]{usethis::create_package()}}. See \code{\link[usethis:use_description]{usethis::use_description()}}} 14 22 15 23 \item{private}{Whether to create the GitHub repository as private. Defaults to \code{TRUE}.} 24 + 25 + \item{setup_gha}{Whether to configure GitHub Actions setup.} 26 + 27 + \item{setup_dependabot}{Whether to write a Dependabot configuration.} 28 + 29 + \item{setup_air_jarl}{Whether to configure Air and Jarl defaults.} 16 30 17 31 \item{...}{Additional arguments passed to \code{\link[usethis:create_package]{usethis::create_package()}}.} 18 32 }
+9 -1
man/pkg_setup.Rd
··· 4 4 \alias{pkg_setup} 5 5 \title{Apply Opinionated Package Setup} 6 6 \usage{ 7 - pkg_setup() 7 + pkg_setup(setup_gha = TRUE, setup_dependabot = TRUE, setup_air_jarl = TRUE) 8 + } 9 + \arguments{ 10 + \item{setup_gha}{Whether to configure GitHub Actions setup.} 11 + 12 + \item{setup_dependabot}{Whether to write a Dependabot configuration.} 13 + 14 + \item{setup_air_jarl}{Whether to configure Air and Jarl defaults.} 8 15 } 9 16 \value{ 10 17 Invisibly returns \code{NULL}. ··· 12 19 \description{ 13 20 Run the package setup steps used by \code{bootstrapper}, including test 14 21 infrastructure, README/NEWS creation, GitHub Actions, and linting defaults. 22 + Run this in the root directory of your package. 15 23 }
+150 -40
tests/testthat/test-bootstrapper.R
··· 10 10 expect_identical(list(...), list(open = FALSE)) 11 11 NULL 12 12 }, 13 - pkg_setup = function() { 13 + pkg_setup = function(setup_gha, setup_dependabot, setup_air_jarl) { 14 14 calls <<- c(calls, "pkg_setup") 15 + expect_false(setup_gha) 16 + expect_false(setup_dependabot) 17 + expect_true(setup_air_jarl) 15 18 NULL 16 19 }, 17 20 .package = "bootstrapper" ··· 22 25 path = "pkg", 23 26 fields = list(name = "value"), 24 27 private = FALSE, 28 + setup_gha = FALSE, 29 + setup_dependabot = FALSE, 30 + setup_air_jarl = TRUE, 25 31 open = FALSE 26 32 ) 27 33 ) ··· 81 87 expect_true(isTRUE(seen$license)) 82 88 }) 83 89 84 - test_that("pkg_setup runs expected usethis and helper calls", { 85 - calls <- list( 86 - actions = character(), 87 - github_actions = list(), 88 - writes = character(), 89 - replacements = character() 90 - ) 90 + test_that("pkg_setup runs expected top-level calls and setup sections", { 91 + calls <- list(actions = character(), sections = character(), replaced = FALSE) 91 92 92 93 testthat::local_mocked_bindings( 93 94 use_testthat = function() { ··· 99 100 use_news_md = function(open = FALSE) { 100 101 calls$actions <<- c(calls$actions, paste0("news:", open)) 101 102 }, 102 - use_github_action = function(...) { 103 - calls$github_actions <<- c(calls$github_actions, list(list(...))) 104 - }, 105 - use_pkgdown_github_pages = function() { 106 - calls$actions <<- c(calls$actions, "pkgdown") 107 - }, 108 - use_spell_check = function(error = FALSE) { 109 - calls$actions <<- c(calls$actions, paste0("spell:", error)) 110 - }, 111 - use_air = function() { 112 - calls$actions <<- c(calls$actions, "air") 113 - }, 114 103 use_tidy_description = function() { 115 104 calls$actions <<- c(calls$actions, "tidy_description") 116 105 }, ··· 118 107 ) 119 108 120 109 testthat::local_mocked_bindings( 121 - write_to_path = function(text, filepath) { 122 - calls$writes <<- c(calls$writes, filepath) 110 + configure_gha = function() { 111 + calls$sections <<- c(calls$sections, "gha") 123 112 NULL 124 113 }, 125 - find_replace_in_gha = function(from, to) { 126 - calls$replacements <<- c( 127 - calls$replacements, 128 - paste(from, to, sep = " -> ") 129 - ) 114 + configure_dependabot = function() { 115 + calls$sections <<- c(calls$sections, "dependabot") 116 + NULL 117 + }, 118 + configure_air_jarl = function() { 119 + calls$sections <<- c(calls$sections, "air_jarl") 120 + NULL 121 + }, 122 + find_replace_in_file = function(from, to, file, fixed = TRUE) { 123 + calls$replaced <<- TRUE 124 + expect_identical(from, "(development version)") 125 + expect_identical(to, "0.0.0.9000") 126 + expect_identical(file, fs::path("NEWS.md")) 127 + expect_true(fixed) 130 128 NULL 131 129 }, 132 130 .package = "bootstrapper" ··· 137 135 expect_true("testthat" %in% calls$actions) 138 136 expect_true("readme:FALSE" %in% calls$actions) 139 137 expect_true("news:FALSE" %in% calls$actions) 140 - expect_true("spell:TRUE" %in% calls$actions) 141 - expect_true("air" %in% calls$actions) 142 138 expect_true("tidy_description" %in% calls$actions) 139 + expect_identical(calls$sections, c("gha", "dependabot", "air_jarl")) 140 + expect_true(calls$replaced) 141 + }) 143 142 144 - expect_length(calls$github_actions, 3) 145 - expect_identical(calls$github_actions[[1]][[1]], "check-standard") 146 - expect_true(isTRUE(calls$github_actions[[1]]$badge)) 147 - expect_identical(calls$github_actions[[2]][[1]], "test-coverage") 148 - expect_identical( 149 - calls$github_actions[[3]]$url, 150 - "https://github.com/visruthsk/bootstrapper/blob/main/.github/workflows/format-suggest.yaml" 143 + test_that("pkg_setup skips optional sections when disabled", { 144 + called <- FALSE 145 + 146 + testthat::local_mocked_bindings( 147 + use_testthat = function() NULL, 148 + use_readme_md = function(open = FALSE) NULL, 149 + use_news_md = function(open = FALSE) NULL, 150 + use_tidy_description = function() NULL, 151 + .package = "usethis" 151 152 ) 152 153 153 - expect_setequal( 154 - basename(calls$writes), 155 - c("dependabot.yml", "extensions.json", "jarl.toml") 154 + testthat::local_mocked_bindings( 155 + configure_gha = function() { 156 + called <<- TRUE 157 + NULL 158 + }, 159 + configure_dependabot = function() { 160 + called <<- TRUE 161 + NULL 162 + }, 163 + configure_air_jarl = function() { 164 + called <<- TRUE 165 + NULL 166 + }, 167 + find_replace_in_file = function(from, to, file, fixed = TRUE) NULL, 168 + .package = "bootstrapper" 156 169 ) 170 + 171 + expect_null( 172 + bootstrapper::pkg_setup( 173 + setup_gha = FALSE, 174 + setup_dependabot = FALSE, 175 + setup_air_jarl = FALSE 176 + ) 177 + ) 178 + expect_false(called) 179 + }) 180 + 181 + test_that("configure_gha runs expected usethis and replacement calls", { 182 + configure_gha <- getFromNamespace("configure_gha", "bootstrapper") 183 + actions <- list(github_actions = list(), replacements = character(), spell = FALSE) 184 + 185 + testthat::local_mocked_bindings( 186 + use_github_action = function(...) { 187 + actions$github_actions <<- c(actions$github_actions, list(list(...))) 188 + NULL 189 + }, 190 + use_pkgdown_github_pages = function() NULL, 191 + use_spell_check = function(error = FALSE) { 192 + actions$spell <<- error 193 + NULL 194 + }, 195 + .package = "usethis" 196 + ) 197 + 198 + testthat::local_mocked_bindings( 199 + find_replace_in_gha = function(from, to) { 200 + actions$replacements <<- c(actions$replacements, paste(from, to, sep = " -> ")) 201 + NULL 202 + }, 203 + .package = "bootstrapper" 204 + ) 205 + 206 + expect_null(configure_gha()) 207 + expect_length(actions$github_actions, 3) 208 + expect_identical(actions$github_actions[[1]][[1]], "check-standard") 209 + expect_true(isTRUE(actions$github_actions[[1]]$badge)) 210 + expect_identical(actions$github_actions[[2]][[1]], "test-coverage") 157 211 expect_identical( 158 - calls$replacements, 212 + actions$github_actions[[3]]$url, 213 + "https://github.com/visruthsk/bootstrapper/blob/main/.github/workflows/format-suggest.yaml" 214 + ) 215 + expect_true(actions$spell) 216 + expect_identical( 217 + actions$replacements, 159 218 c( 160 219 "actions/checkout@v4 -> actions/checkout@v6", 161 220 "JamesIves/github-pages-deploy-action@v4.5.0 -> JamesIves/github-pages-deploy-action@v4" ··· 163 222 ) 164 223 }) 165 224 225 + test_that("configure_dependabot writes dependabot config", { 226 + configure_dependabot <- getFromNamespace("configure_dependabot", "bootstrapper") 227 + captured <- list(path = NULL, text = NULL) 228 + 229 + testthat::local_mocked_bindings( 230 + write_to_path = function(text, filepath) { 231 + captured$path <<- filepath 232 + captured$text <<- text 233 + NULL 234 + }, 235 + .package = "bootstrapper" 236 + ) 237 + 238 + expect_null(configure_dependabot()) 239 + expect_identical(captured$path, fs::path(".github", "dependabot.yml")) 240 + expect_true(any(grepl("^version: 2$", captured$text))) 241 + }) 242 + 243 + test_that("configure_air_jarl writes expected files", { 244 + configure_air_jarl <- getFromNamespace("configure_air_jarl", "bootstrapper") 245 + calls <- list(air = FALSE, writes = character()) 246 + 247 + testthat::local_mocked_bindings( 248 + use_air = function() { 249 + calls$air <<- TRUE 250 + NULL 251 + }, 252 + .package = "usethis" 253 + ) 254 + 255 + testthat::local_mocked_bindings( 256 + write_to_path = function(text, filepath) { 257 + calls$writes <<- c(calls$writes, filepath) 258 + NULL 259 + }, 260 + .package = "bootstrapper" 261 + ) 262 + 263 + expect_null(configure_air_jarl()) 264 + expect_true(calls$air) 265 + expect_setequal( 266 + basename(calls$writes), 267 + c("extensions.json", "jarl.toml") 268 + ) 269 + }) 270 + 166 271 test_that("pkg_setup rethrows a generic message when test setup fails", { 167 272 readme_called <- FALSE 168 273 ··· 184 289 185 290 test_that("use_license warns and returns NULL in non-interactive mode", { 186 291 messages <- character() 292 + 293 + testthat::local_mocked_bindings( 294 + is_interactive = function() FALSE, 295 + .package = "bootstrapper" 296 + ) 187 297 188 298 testthat::local_mocked_bindings( 189 299 ui_info = function(message, ...) {