Skip to content

Commit 514acba

Browse files
authoredNov 12, 2020
Introduce use_github_pages() and use_tidy_pkgdown() (r-lib#1271)
* Introduce use_github_pages() Closes r-lib#224 * CNAME details * Logo modernization * Introduce use_tidy_pkgdown() * Might as well update use_pkgdown_travis() * Update pkgdown config * Fix an Rd link * Another Rd link * Work on docs, soften `path` enforcement * Tweaks based on more manual testing * Suggestions from code review * Update WORDLIST * Re-document() * Rework handling of CNAME * Tweaks re: editing pkgdown config * Return site metadata * Don't expose cname/URL stuff in use_tidy_pkgdown() * Update WORDLIST * Polishing
1 parent 881c146 commit 514acba

19 files changed

+441
-45
lines changed
 

‎DESCRIPTION

+2-2
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ Imports:
3131
desc,
3232
fs (>= 1.3.0),
3333
gert (>= 1.0.2),
34-
gh (>= 1.1.0.9001),
34+
gh (>= 1.1.0.9002),
3535
glue (>= 1.3.0),
3636
lifecycle,
3737
purrr,
@@ -57,7 +57,7 @@ RdMacros:
5757
lifecycle
5858
Remotes:
5959
r-lib/gert,
60-
r-lib/gh
60+
r-lib/gh#139
6161
Encoding: UTF-8
6262
Language: en-US
6363
LazyData: true

‎NAMESPACE

+2
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ export(use_github_actions)
129129
export(use_github_actions_badge)
130130
export(use_github_labels)
131131
export(use_github_links)
132+
export(use_github_pages)
132133
export(use_github_release)
133134
export(use_gitlab_ci)
134135
export(use_gpl3_license)
@@ -176,6 +177,7 @@ export(use_tidy_github)
176177
export(use_tidy_github_actions)
177178
export(use_tidy_issue_template)
178179
export(use_tidy_labels)
180+
export(use_tidy_pkgdown)
179181
export(use_tidy_release_test_env)
180182
export(use_tidy_style)
181183
export(use_tidy_support)

‎NEWS.md

+4
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,10 @@ The default Git protocol is now "https" and we no longer provide an interactive
8888

8989
* `browse_github_actions()` is a new function to open the Actions page of the respective repo on GitHub, similar to existing `browse_*()` functions (@pat-s, #1102).
9090

91+
* `use_github_pages()` is a new function to activate or reconfigure the GitHub Pages site associated with a repository (#224).
92+
93+
* `use_tidy_pkgdown()` implements the complete pkgdown configuration used by the tidyverse team (#224).
94+
9195
`pr_sync()` is defunct and can be replicated by calling `pr_pull()`, `pr_merge_main()`, then `pr_push()`.
9296

9397
## Licensing improvements

‎R/github-pages.R

+169
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
#' Configure a GitHub Pages site
2+
#'
3+
#' Activates or reconfigures a GitHub Pages site for a project hosted on GitHub.
4+
#' This function anticipates two specific usage modes:
5+
#' * Publish from the root directory of a `gh-pages` branch, which is assumed to
6+
#' be only (or at least primarily) a remote branch. Typically the `gh-pages`
7+
#' branch is managed by an automatic "build and deploy" job, such as the one
8+
#' configured by [`use_github_action("pkgdown")`][use_github_action()].
9+
#' * Publish from the `"/docs"` directory of a "regular" branch, probably the
10+
#' repo's default branch. The user is assumed to have a plan for how they will
11+
#' manage the content below `"/docs"`.
12+
#'
13+
14+
#' @param branch,path Branch and path for the site source. The default of
15+
#' `branch = "gh-pages"` and `path = "/"` reflects strong GitHub support for
16+
#' this configuration: when a `gh-pages` branch is first created, it is
17+
#' *automatically* published to Pages, using the source found in `"/"`. If a
18+
#' `gh-pages` branch does not yet exist on the host, `use_github_pages()`
19+
#' creates an empty, orphan remote branch.
20+
#'
21+
#' The most common alternative is to use the repo's default branch, coupled
22+
#' with `path = "/docs"`. It is the user's responsibility to ensure that this
23+
#' `branch` pre-exists on the host.
24+
#'
25+
#' Note that GitHub does not support an arbitrary `path` and, at the time of
26+
#' writing, only `"/"` or `"/docs"` are accepted.
27+
28+
#' @param cname Optional, custom domain name. The `NA` default means "don't set
29+
#' or change this", whereas a value of `NULL` removes any previously
30+
#' configured custom domain.
31+
#'
32+
#' Note that this *can* add or modify a CNAME file in your repository. If you
33+
#' are using Pages to host a pkgdown site, it is better to specify its URL in
34+
#' the pkgdown config file and let pkgdown manage CNAME.
35+
#'
36+
37+
#' @seealso
38+
#' * [use_tidy_pkgdown()] combines `use_github_pages()` with other functions to
39+
#' fully configure a pkgdown site
40+
#' * <https://docs.github.com/en/free-pro-team@latest/github/working-with-github-pages>
41+
#' * <https://docs.github.com/en/free-pro-team@latest/rest/reference/repos#pages>
42+
43+
#' @return Site metadata returned by the GitHub API, invisibly
44+
#' @export
45+
#'
46+
#' @examples
47+
#' \dontrun{
48+
#' use_github_pages()
49+
#' use_github_pages(branch = git_branch_default(), path = "/docs")
50+
#' }
51+
use_github_pages <- function(branch = "gh-pages", path = "/", cname = NA) {
52+
stopifnot(is_string(branch), is_string(path))
53+
stopifnot(is.na(cname) || is.null(cname) || is_string(cname))
54+
tr <- target_repo(github_get = TRUE)
55+
if (!isTRUE(tr$can_push)) {
56+
ui_stop("
57+
You don't seem to have push access for {ui_value(tr$repo_spec)}, which \\
58+
is required to turn on GitHub Pages.")
59+
}
60+
61+
gh <- function(endpoint, ...) {
62+
gh::gh(
63+
endpoint,
64+
...,
65+
owner = tr$repo_owner, repo = tr$repo_name, .api_url = tr$api_url
66+
)
67+
}
68+
safe_gh <- purrr::safely(gh)
69+
70+
if (branch == "gh-pages") {
71+
new_branch <- create_gh_pages_branch(tr, branch = "gh-pages")
72+
if (new_branch) {
73+
# merely creating gh-pages branch automatically activates publishing
74+
# BUT we need to give the servers time to sync up before a new GET
75+
# retrieves accurate info... ask me how I know
76+
Sys.sleep(2)
77+
}
78+
}
79+
80+
site <- safe_gh("GET /repos/{owner}/{repo}/pages")[["result"]]
81+
82+
if (is.null(site)) {
83+
ui_done("Activating GitHub Pages for {ui_value(tr$repo_spec)}")
84+
site <- gh(
85+
"POST /repos/{owner}/{repo}/pages",
86+
source = list(branch = branch, path = path),
87+
.accept = "application/vnd.github.switcheroo-preview+json"
88+
)
89+
}
90+
91+
need_update <-
92+
site$source$branch != branch ||
93+
site$source$path != path ||
94+
(is.null(cname) && !is.null(site$cname)) ||
95+
(is_string(cname) && (is.null(site$cname) || cname != site$cname))
96+
97+
if (need_update) {
98+
args <- list(
99+
endpoint = "PUT /repos/{owner}/{repo}/pages",
100+
source = list(branch = branch, path = path)
101+
)
102+
if (is.null(cname) && !is.null(site$cname)) {
103+
# this goes out as a JSON `null`, which is necessary to clear cname
104+
args$cname <- NA
105+
}
106+
if (is_string(cname) && (is.null(site$cname) || cname != site$cname)) {
107+
args$cname <- cname
108+
}
109+
Sys.sleep(2)
110+
exec(gh, !!!args)
111+
Sys.sleep(2)
112+
site <- safe_gh("GET /repos/{owner}/{repo}/pages")[["result"]]
113+
}
114+
115+
ui_done("GitHub Pages is publishing from:")
116+
if (!is.null(site$cname)) {
117+
kv_line("Custom domain", site$cname)
118+
}
119+
kv_line("URL", site$html_url)
120+
kv_line("Branch", site$source$branch)
121+
kv_line("Path", site$source$path)
122+
123+
invisible(site)
124+
}
125+
126+
# returns FALSE if it does NOT create the branch (because it already exists)
127+
# returns TRUE if it does create the branch
128+
create_gh_pages_branch <- function(tr, branch = "gh-pages") {
129+
gh <- function(endpoint, ...) {
130+
gh::gh(
131+
endpoint,
132+
...,
133+
owner = tr$repo_owner, repo = tr$repo_name, .api_url = tr$api_url
134+
)
135+
}
136+
safe_gh <- purrr::safely(gh)
137+
138+
branch_GET <- safe_gh(
139+
"GET /repos/{owner}/{repo}/branches/{branch}",
140+
branch = branch
141+
)
142+
143+
if (!inherits(branch_GET$error, "http_error_404")) {
144+
return(FALSE)
145+
}
146+
147+
ui_done("
148+
Initializing empty, orphan {ui_value(branch)} branch in GitHub repo \\
149+
{ui_value(tr$repo_spec)}")
150+
151+
# git hash-object -t tree /dev/null
152+
sha_empty_tree <- "4b825dc642cb6eb9a060e54bf8d69288fbee4904"
153+
154+
# Create commit with empty tree
155+
res <- gh(
156+
"POST /repos/{owner}/{repo}/git/commits",
157+
message = "first commit",
158+
tree = sha_empty_tree
159+
)
160+
161+
# Assign ref to above commit
162+
gh(
163+
"POST /repos/{owner}/{repo}/git/refs",
164+
ref = "refs/heads/gh-pages",
165+
sha = res$sha
166+
)
167+
168+
TRUE
169+
}

‎R/pkgdown.R

+97-38
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,102 @@ use_pkgdown <- function(config_file = "_pkgdown.yml", destdir = "docs") {
4747
invisible(TRUE)
4848
}
4949

50+
# tidyverse pkgdown setup ------------------------------------------------------
51+
52+
#' @details
53+
#' * `use_tidy_pkgdown()`: Implements the pkgdown setup used for most tidyverse
54+
#' and r-lib packages:
55+
#' - [use_pkgdown()] does basic local setup
56+
#' - [use_github_pages()] prepares to publish the pkgdown site from the
57+
#' `github-pages` branch
58+
#' - [`use_github_action("pkgdown")`][use_github_action()] configures a
59+
#' GitHub Action to automatically build the pkgdown site and deploy it via
60+
#' GitHub Pages
61+
#' - The pkgdown site's URL is added to the pkgdown configuration file,
62+
#' to the URL field of DESCRIPTION, and to the GitHub repo.
63+
#'
64+
#' @rdname tidyverse
65+
#' @export
66+
use_tidy_pkgdown <- function() {
67+
tr <- target_repo(github_get = TRUE)
68+
69+
use_pkgdown()
70+
site <- use_github_pages()
71+
use_github_action("pkgdown")
72+
73+
site_url <- sub("/$", "", site$html_url)
74+
site_url <- tidyverse_url(url = site_url, tr = tr)
75+
use_pkgdown_url(url = site_url, tr = tr)
76+
}
77+
78+
# helpers ----------------------------------------------------------------------
79+
use_pkgdown_url <- function(url, tr = NULL) {
80+
tr <- tr %||% target_repo(github_get = TRUE)
81+
82+
config <- pkgdown_config_path()
83+
config_lines <- read_utf8(config)
84+
url_line <- paste0("url: ", url)
85+
if (!any(grepl(url_line, config_lines))) {
86+
ui_done("
87+
Recording {ui_value(url)} as site's {ui_field('url')} in \\
88+
{ui_path(config)}")
89+
config_lines <- config_lines[!grepl("^url:", config_lines)]
90+
write_utf8(config, c(
91+
url_line,
92+
if (length(config_lines) && nzchar(config_lines[[1]])) "",
93+
config_lines
94+
))
95+
}
96+
97+
urls <- desc::desc_get_urls()
98+
if (!url %in% urls) {
99+
ui_done("Adding {ui_value(url)} to {ui_field('URL')} field in DESCRIPTION")
100+
ui_silence(
101+
use_description_field(
102+
"URL",
103+
glue_collapse(c(url, urls), ", "),
104+
overwrite = TRUE
105+
)
106+
)
107+
}
108+
109+
gh <- function(endpoint, ...) {
110+
gh::gh(
111+
endpoint,
112+
...,
113+
owner = tr$repo_owner, repo = tr$repo_name, .api_url = tr$api_url
114+
)
115+
}
116+
homepage <- gh("GET /repos/{owner}/{repo}")[["homepage"]]
117+
if (is.null(homepage) || homepage != url) {
118+
ui_done("Setting {ui_value(url)} as homepage of GitHub repo \\
119+
{ui_value(tr$repo_spec)}")
120+
gh("PATCH /repos/{owner}/{repo}", homepage = url)
121+
}
122+
123+
invisible()
124+
}
125+
126+
tidyverse_url <- function(url, tr = NULL) {
127+
tr <- tr %||% target_repo(github_get = TRUE)
128+
if (!is_interactive() || !tr$repo_owner %in% c("tidyverse", "r-lib")) {
129+
return(url)
130+
}
131+
custom_url <- glue("https://{tr$repo_name}.{tr$repo_owner}.org")
132+
if (url == custom_url) {
133+
return(url)
134+
}
135+
if (ui_yeah("
136+
{ui_value(tr$repo_name)} is owned by the {ui_value(tr$repo_owner)} GitHub \\
137+
organization
138+
Shall we configure {ui_value(custom_url)} as the (eventual) \\
139+
pkgdown URL?")) {
140+
custom_url
141+
} else {
142+
url
143+
}
144+
}
145+
50146
pkgdown_config_path <- function(base_path = proj_get()) {
51147
path_first_existing(
52148
base_path,
@@ -132,13 +228,7 @@ use_pkgdown_travis <- function() {
132228
"
133229
)
134230

135-
if (!gert::git_branch_exists("origin/gh-pages", local = FALSE, repo = git_repo())) {
136-
create_gh_pages_branch(tr)
137-
}
138-
139-
ui_todo("
140-
Turn on GitHub pages at \\
141-
<https://github.com/{tr$repo_spec}/settings> (using gh-pages as source)")
231+
use_github_pages()
142232

143233
invisible()
144234
}
@@ -148,34 +238,3 @@ use_pkgdown_travis <- function() {
148238
pkgdown_build_favicons <- function(...) {
149239
get("build_favicons", asNamespace("pkgdown"), mode = "function")(...)
150240
}
151-
152-
create_gh_pages_branch <- function(tr) {
153-
ui_done("
154-
Initializing empty gh-pages branch in GitHub repo {ui_value(tr$repo_spec)}")
155-
156-
# git hash-object -t tree /dev/null.
157-
sha_empty_tree <- "4b825dc642cb6eb9a060e54bf8d69288fbee4904"
158-
159-
gh <- function(endpoint, ...) {
160-
gh::gh(
161-
endpoint,
162-
...,
163-
owner = tr$repo_owner, repo = tr$repo_name,
164-
.api_url = tr$api_url
165-
)
166-
}
167-
168-
# Create commit with empty tree
169-
res <- gh(
170-
"POST /repos/:owner/:repo/git/commits",
171-
message = "first commit",
172-
tree = sha_empty_tree
173-
)
174-
175-
# Assign ref to above commit
176-
gh(
177-
"POST /repos/:owner/:repo/git/refs",
178-
ref = "refs/heads/gh-pages",
179-
sha = res$sha
180-
)
181-
}

‎_pkgdown.yml

+1-3
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ reference:
118118
contents:
119119
- create_from_github
120120
- use_git
121-
- use_github
121+
- starts_with("use_github")
122122
- git_sitrep
123123
- create_github_token
124124
- gh_token_help
@@ -127,9 +127,7 @@ reference:
127127
- use_git_ignore
128128
- use_git_protocol
129129
- use_git_remote
130-
- use_github_links
131130
- use_git_hook
132-
- use_github_labels
133131
- use_code_of_conduct
134132
- use_readme_rmd
135133
- git_branch_default

‎inst/WORDLIST

+2-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ BugReports
66
CCBY
77
CLA
88
CMD
9+
CNAME
910
CircleCI
1011
CoC
1112
Codecov
@@ -81,7 +82,6 @@ devtools
8182
discoverable
8283
else's
8384
emacs
84-
env
8585
eval
8686
favicons
8787
favour
@@ -136,6 +136,7 @@ r's
136136
rOpenSci
137137
rcmdcheck
138138
rebase
139+
reconfigures
139140
redirections
140141
repo
141142
repo's

‎man/tidyverse.Rd

+19-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎man/use_github_pages.Rd

+61
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
0 Bytes
Loading
0 Bytes
Loading
0 Bytes
Loading
0 Bytes
Loading
0 Bytes
Loading

‎pkgdown/favicon/apple-touch-icon.png

0 Bytes
Loading

‎pkgdown/favicon/favicon-16x16.png

0 Bytes
Loading

‎pkgdown/favicon/favicon-32x32.png

0 Bytes
Loading
+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
devtools::load_all("~/rrr/usethis")
2+
library(testthat)
3+
library(fs)
4+
5+
# comment / uncomment to test against GHE
6+
# Sys.setenv(GITHUB_API_URL = "https://github.ubc.ca")
7+
# lesson learned: UBC is still running GHE 2.21 and the syntax around
8+
# source branch and path has changed in GHE 2.22, which seems to match
9+
# github.com
10+
# some of this stuff works, but not all
11+
# not going to worry about supporting older versions of GHE fully
12+
13+
repo_name <- "stacey"
14+
gh_account <- gh::gh_whoami()
15+
(me <- gh_account$login)
16+
17+
# remove any pre-existing repo and local project
18+
gh::gh(
19+
"DELETE /repos/{username}/{pkg}",
20+
username = me, pkg = repo_name
21+
)
22+
dir_delete(path_home("tmp", repo_name))
23+
expect_false(dir_exists(path_home("tmp", repo_name)))
24+
25+
# create the package
26+
create_local_package(path_home("tmp", "stacey"))
27+
use_git()
28+
use_github()
29+
30+
# should fail because this branch does not exist
31+
use_github_pages(branch = "nope")
32+
33+
# should work
34+
use_github_pages()
35+
36+
# change branch and path
37+
use_github_pages(branch = git_branch_default(), path = "/docs")
38+
39+
# go back to default branch and path
40+
use_github_pages()
41+
42+
# customize domain name
43+
use_github_pages(cname = "example.org")
44+
45+
# clear custom domain name, change path
46+
use_github_pages(path = "/docs", cname = NULL)
47+
48+
# clean up
49+
gh::gh(
50+
"DELETE /repos/{username}/{pkg}",
51+
username = me, pkg = repo_name
52+
)
53+
withr::deferred_run()
54+
expect_false(dir_exists(path_home("tmp", repo_name)))
+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
devtools::load_all("~/rrr/usethis")
2+
library(testthat)
3+
library(fs)
4+
5+
repo_name <- "stacey"
6+
gh_account <- gh::gh_whoami()
7+
(me <- gh_account$login)
8+
9+
# remove any pre-existing repo and local project
10+
gh::gh(
11+
"DELETE /repos/{username}/{pkg}",
12+
username = me, pkg = repo_name
13+
)
14+
dir_delete(path_home("tmp", repo_name))
15+
expect_false(dir_exists(path_home("tmp", repo_name)))
16+
17+
# create the package
18+
create_local_package(path_home("tmp", "stacey"))
19+
use_git()
20+
use_github()
21+
22+
use_tidy_pkgdown()
23+
24+
# clean up
25+
gh::gh(
26+
"DELETE /repos/{username}/{pkg}",
27+
username = me, pkg = repo_name
28+
)
29+
withr::deferred_run()
30+
expect_false(dir_exists(path_home("tmp", repo_name)))

0 commit comments

Comments
 (0)