[mirror] Opinionated R package quickstart
1run_workflow_fixture <- function(setup_AGENTS = TRUE) {
2 tmp <- tempfile("bootstrapper-workflow-")
3 dir.create(tmp)
4 pkg <- "demoPkg"
5 pkg_path <- fs::path(tmp, pkg)
6 dir.create(pkg_path)
7 old <- setwd(tmp)
8 on.exit(setwd(old), add = TRUE)
9 setwd(pkg_path)
10
11 fields <- list("Package" = pkg)
12 state <- new.env(parent = emptyenv())
13 state$action_calls <- list()
14
15 testthat::local_mocked_bindings(
16 create_package = function(path, fields, ...) {
17 expect_identical(path, ".")
18 expect_identical(fields, list("Package" = pkg))
19 expect_identical(list(...), list(open = FALSE))
20
21 writeLines(
22 c("^.*\\.Rproj$", "^\\.Rproj\\.user$", "README\\.Rmd"),
23 ".Rbuildignore"
24 )
25 writeLines(c("Package: demoPkg", "Version: 0.0.0.9000"), "DESCRIPTION")
26 writeLines("project", "demoPkg.Rproj")
27 fs::dir_create(fs::path(".git", "hooks"))
28 NULL
29 },
30 use_testthat = function() {
31 fs::dir_create(fs::path("tests", "testthat"))
32 writeLines(
33 "testthat::test_check(\"demoPkg\")",
34 fs::path("tests", "testthat.R")
35 )
36 NULL
37 },
38 use_readme_md = function(open = FALSE) {
39 expect_false(open)
40 writeLines("# demoPkg", "README.md")
41 NULL
42 },
43 use_news_md = function(open = FALSE) {
44 expect_false(open)
45 writeLines("demoPkg (development version)", "NEWS.md")
46 NULL
47 },
48 use_github_action = function(...) {
49 args <- list(...)
50 state$action_calls <- c(state$action_calls, list(args))
51 fs::dir_create(fs::path(".github", "workflows"))
52
53 if (!is.null(args$url)) {
54 writeLines(
55 c(
56 "name: format-suggest",
57 "steps:",
58 " - uses: actions/checkout@v4"
59 ),
60 fs::path(".github", "workflows", "format-suggest.yaml")
61 )
62 return(invisible(NULL))
63 }
64
65 if (identical(args[[1]], "check-standard")) {
66 writeLines(
67 c(
68 "name: check",
69 "steps:",
70 " - uses: actions/checkout@v4",
71 " - uses: actions/upload-artifact@v4",
72 " - uses: JamesIves/github-pages-deploy-action@v4.5.0"
73 ),
74 fs::path(".github", "workflows", "check-standard.yaml")
75 )
76 return(invisible(NULL))
77 }
78
79 if (identical(args[[1]], "test-coverage")) {
80 writeLines(
81 c(
82 "name: coverage",
83 "permissions: read-all",
84 "steps:",
85 " - token: ${{ secrets.CODECOV_TOKEN }}"
86 ),
87 fs::path(".github", "workflows", "test-coverage.yaml")
88 )
89 return(invisible(NULL))
90 }
91
92 NULL
93 },
94 use_pkgdown_github_pages = function() NULL,
95 use_spell_check = function(error = FALSE) {
96 expect_true(error)
97 NULL
98 },
99 use_air = function() {
100 writeLines("[format]", "air.toml")
101 NULL
102 },
103 use_tidy_description = function() NULL,
104 use_build_ignore = function(path, ...) {
105 writeLines(
106 c(readLines(".Rbuildignore", warn = FALSE), path),
107 ".Rbuildignore"
108 )
109 NULL
110 },
111 .package = "usethis"
112 )
113
114 testthat::local_mocked_bindings(
115 use_license = function() {
116 writeLines("MIT License", "LICENSE.md")
117 NULL
118 },
119 try_air_jarl_format = function() {
120 invisible(c(air = TRUE, jarl = TRUE))
121 },
122 .package = "bootstrapper"
123 )
124
125 testthat::local_mocked_bindings(
126 update_wordlist = function(confirm = FALSE) {
127 expect_false(confirm)
128 NULL
129 },
130 .package = "spelling"
131 )
132
133 expect_null(
134 bootstrapper::bootstrapper(
135 fields = fields,
136 setup_AGENTS = setup_AGENTS,
137 open = FALSE
138 )
139 )
140
141 list(
142 pkg_path = pkg_path,
143 action_calls = state$action_calls
144 )
145}
146
147file_in_pkg <- function(fixture, ...) {
148 fs::path(fixture$pkg_path, ...)
149}
150
151snapshot_name_from_path <- function(path) {
152 name <- gsub("[/\\\\]+", "-", path)
153 name <- sub("^\\.+", "dot-", name)
154 name <- gsub("[^A-Za-z0-9._-]", "-", name)
155
156 if (nzchar(fs::path_ext(path))) {
157 name
158 } else {
159 paste0(name, ".txt")
160 }
161}
162
163snapshot_workflow_files <- function(fixture, files) {
164 for (f in files) {
165 expect_true(file.exists(file_in_pkg(fixture, f)), info = f)
166 expect_snapshot_file(
167 file_in_pkg(fixture, f),
168 compare = testthat::compare_file_text,
169 name = snapshot_name_from_path(f)
170 )
171 }
172}
173
174test_that("workflow step: create_package creates git-backed package skeleton", {
175 fixture <- run_workflow_fixture()
176
177 expect_false(file.exists(file_in_pkg(fixture, "demoPkg.Rproj")))
178
179 old <- setwd(fixture$pkg_path)
180 on.exit(setwd(old), add = TRUE)
181 files <- sort(list.files(
182 ".",
183 recursive = TRUE,
184 all.files = TRUE,
185 no.. = TRUE
186 ))
187 expect_snapshot(files[!grepl("^\\.git/", files)])
188
189 snapshot_workflow_files(
190 fixture,
191 c("DESCRIPTION", "LICENSE.md", ".Rbuildignore")
192 )
193})
194
195test_that("workflow step: pkg_setup creates core package files", {
196 fixture <- run_workflow_fixture()
197
198 snapshot_workflow_files(
199 fixture,
200 c("README.md", "NEWS.md", "tests/testthat.R")
201 )
202})
203
204test_that("workflow step: setup_gha writes and rewrites workflow files", {
205 fixture <- run_workflow_fixture()
206
207 expect_length(fixture$action_calls, 3)
208 expect_identical(fixture$action_calls[[1]][[1]], "check-standard")
209 expect_identical(fixture$action_calls[[2]][[1]], "test-coverage")
210 expect_identical(
211 fixture$action_calls[[3]]$url,
212 "https://github.com/visruthsk/bootstrapper/blob/main/.github/workflows/format-suggest.yaml"
213 )
214
215 snapshot_workflow_files(
216 fixture,
217 c(
218 ".github/workflows/check-standard.yaml",
219 ".github/workflows/test-coverage.yaml",
220 ".github/workflows/format-suggest.yaml"
221 )
222 )
223})
224
225test_that("workflow step: setup templates are copied to expected locations", {
226 fixture <- run_workflow_fixture(setup_AGENTS = TRUE)
227
228 snapshot_workflow_files(
229 fixture,
230 c(
231 ".github/dependabot.yml",
232 "AGENTS.md",
233 "tests/jarl.toml",
234 ".vscode/extensions.json"
235 )
236 )
237 expect_true(file.exists(file_in_pkg(fixture, ".git", "hooks", "pre-commit")))
238})