diff --git a/.github/workflows/R-CMD-check.yaml b/.github/workflows/R-CMD-check.yaml index 1da97de..9f9476b 100644 --- a/.github/workflows/R-CMD-check.yaml +++ b/.github/workflows/R-CMD-check.yaml @@ -22,61 +22,33 @@ jobs: fail-fast: false matrix: config: - - {os: macOS-latest, r: 'release'} - - {os: ubuntu-20.04, r: 'release', rspm: "https://packagemanager.rstudio.com/cran/__linux__/focal/latest"} + - {os: macos-latest, r: 'release'} + - {os: ubuntu-latest, r: 'release'} - {os: windows-latest, r: 'devel'} - - {os: windows-latest, r: 'oldrel'} + - {os: windows-latest, r: 'oldrel-1'} - {os: windows-latest, r: 'release'} env: GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} - R_REMOTES_NO_ERRORS_FROM_WARNINGS: true - RSPM: ${{ matrix.config.rspm }} + R_KEEP_PKG_SOURCE: yes steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 + + - uses: r-lib/actions/setup-pandoc@v2 - uses: r-lib/actions/setup-r@v2 with: r-version: ${{ matrix.config.r }} + http-user-agent: ${{ matrix.config.http-user-agent }} + use-public-rspm: true - - uses: r-lib/actions/setup-pandoc@v2 - - - name: Query dependencies - run: | - install.packages('remotes') - saveRDS(remotes::dev_package_deps(dependencies = TRUE), "depends.Rds", version = 2) - shell: Rscript {0} - - - name: Cache R packages - if: runner.os != 'Windows' - uses: actions/cache@v1 + - uses: r-lib/actions/setup-r-dependencies@v2 with: - path: ${{ env.R_LIBS_USER }} - key: ${{ runner.os }}-r-${{ matrix.config.r }}-3-${{ hashFiles('depends.Rds') }} - restore-keys: ${{ runner.os }}-r-${{ matrix.config.r }}-3- - - - name: Install system dependencies - if: runner.os == 'Linux' - env: - RHUB_PLATFORM: linux-x86_64-ubuntu-gcc - run: | - Rscript -e "remotes::install_github('r-hub/sysreqs')" - sysreqs=$(Rscript -e "cat(sysreqs::sysreq_commands('DESCRIPTION'))") - sudo -s eval "$sysreqs" - - name: Install dependencies - run: | - remotes::install_deps(dependencies = TRUE) - remotes::install_cran("rcmdcheck") - shell: Rscript {0} - - - name: Check - run: rcmdcheck::rcmdcheck(args = "--no-manual", error_on = "warning", check_dir = "check") - shell: Rscript {0} + extra-packages: any::rcmdcheck + needs: check - - name: Upload check results - if: failure() - uses: actions/upload-artifact@main + - uses: r-lib/actions/check-r-package@v2 with: - name: ${{ runner.os }}-r${{ matrix.config.r }}-results - path: check + upload-snapshots: true + args: 'c("--no-manual", "--as-cran")' diff --git a/.github/workflows/claude-code-review.yaml b/.github/workflows/claude-code-review.yaml new file mode 100644 index 0000000..10d2e3d --- /dev/null +++ b/.github/workflows/claude-code-review.yaml @@ -0,0 +1,40 @@ +name: Claude Code Review + +on: + pull_request: + types: [opened, synchronize] + +jobs: + claude-review: + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: read + issues: read + id-token: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Run Claude Code Review + id: claude-review + uses: anthropics/claude-code-action@v1 + with: + claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} + prompt: | + REPO: ${{ github.repository }} + PR NUMBER: ${{ github.event.pull_request.number }} + + Please review this pull request and think hard about correctness, + edge cases, and consistency with the rest of the codebase. Use the + repository's CLAUDE.md for guidance if available. + Focus on: + - Code quality and R package best practices + - Potential bugs or logic issues + - Test coverage + - Documentation (roxygen2) completeness + - Consistency with the existing codebase + claude_args: '--model claude-opus-4-7 --allowed-tools "mcp__github_inline_comment__create_inline_comment,Bash(gh issue view:*),Bash(gh search:*),Bash(gh issue list:*),Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*),Bash(gh pr list:*)"' diff --git a/.github/workflows/claude.yaml b/.github/workflows/claude.yaml new file mode 100644 index 0000000..49f70bf --- /dev/null +++ b/.github/workflows/claude.yaml @@ -0,0 +1,38 @@ +name: Claude Code + +on: + issue_comment: + types: [created] + pull_request_review_comment: + types: [created] + issues: + types: [opened, assigned] + pull_request_review: + types: [submitted] + +jobs: + claude: + if: | + (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) || + (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) || + (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) || + (github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude'))) + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: read + issues: read + id-token: write + actions: read + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Run Claude Code + id: claude + uses: anthropics/claude-code-action@v1 + with: + claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} + claude_args: '--model claude-opus-4-7' diff --git a/.github/workflows/pkgdown.yaml b/.github/workflows/pkgdown.yaml index 3b2a357..2d703f6 100644 --- a/.github/workflows/pkgdown.yaml +++ b/.github/workflows/pkgdown.yaml @@ -4,48 +4,49 @@ on: - main - master - dev + pull_request: + branches: + - main + - master + - dev + release: + types: [published] + workflow_dispatch: name: pkgdown jobs: pkgdown: - runs-on: windows-latest + runs-on: ubuntu-latest + # Only restrict concurrency for non-PR jobs + concurrency: + group: pkgdown-${{ github.event_name != 'pull_request' || github.run_id }} env: - CURL_SSL_BACKEND: "openssl" GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} + permissions: + contents: write steps: - - uses: actions/checkout@v2 - - - uses: r-lib/actions/setup-r@v2 + - uses: actions/checkout@v4 - uses: r-lib/actions/setup-pandoc@v2 - - name: Query dependencies - run: | - install.packages('remotes') - saveRDS(remotes::dev_package_deps(dependencies = TRUE), ".github/depends.Rds", version = 2) - writeLines(sprintf("R-%i.%i", getRversion()$major, getRversion()$minor), ".github/R-version") - shell: Rscript {0} + - uses: r-lib/actions/setup-r@v2 + with: + use-public-rspm: true - - name: Cache R packages - uses: actions/cache@v2 + - uses: r-lib/actions/setup-r-dependencies@v2 with: - path: ${{ env.R_LIBS_USER }} - key: ${{ runner.os }}-${{ hashFiles('.github/R-version') }}-1-${{ hashFiles('.github/depends.Rds') }} - restore-keys: ${{ runner.os }}-${{ hashFiles('.github/R-version') }}-1- + extra-packages: any::pkgdown, local::. + needs: website - - name: Install dependencies - run: | - remotes::install_deps(dependencies = TRUE) - install.packages("pkgdown", type = "binary") + - name: Build site + run: pkgdown::build_site_github_pages(new_process = FALSE, install = FALSE) shell: Rscript {0} - - name: Install package - run: R CMD INSTALL . - shell: cmd - - - name: Deploy package - run: | - git config --local user.email "actions@github.com" - git config --local user.name "GitHub Actions" - Rscript -e 'pkgdown::deploy_to_branch(new_process = FALSE)' + - name: Deploy to GitHub pages + if: github.event_name != 'pull_request' + uses: JamesIves/github-pages-deploy-action@v4.5.0 + with: + clean: false + branch: gh-pages + folder: docs diff --git a/.github/workflows/pr-commands.yaml b/.github/workflows/pr-commands.yaml index 0d3cb71..33cfb6c 100644 --- a/.github/workflows/pr-commands.yaml +++ b/.github/workflows/pr-commands.yaml @@ -1,51 +1,85 @@ on: issue_comment: types: [created] + name: Commands + jobs: document: - if: startsWith(github.event.comment.body, '/document') + if: ${{ github.event.issue.pull_request && startsWith(github.event.comment.body, '/document') }} name: document - runs-on: macOS-latest + runs-on: ubuntu-latest env: GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} + permissions: + contents: write + pull-requests: write steps: - - uses: actions/checkout@v2 - - uses: r-lib/actions/pr-fetch@master + - uses: actions/checkout@v4 + + - uses: r-lib/actions/pr-fetch@v2 with: repo-token: ${{ secrets.GITHUB_TOKEN }} - - uses: r-lib/actions/setup-r@master - - name: Install dependencies - run: Rscript -e 'install.packages(c("remotes", "roxygen2"))' -e 'remotes::install_deps(dependencies = TRUE)' + + - uses: r-lib/actions/setup-r@v2 + with: + use-public-rspm: true + + - uses: r-lib/actions/setup-r-dependencies@v2 + with: + extra-packages: any::roxygen2 + needs: pr-document + - name: Document - run: Rscript -e 'roxygen2::roxygenise()' + run: roxygen2::roxygenise() + shell: Rscript {0} + - name: commit run: | + git config --local user.name "$GITHUB_ACTOR" + git config --local user.email "$GITHUB_ACTOR@users.noreply.github.com" git add man/\* NAMESPACE git commit -m 'Document' - - uses: r-lib/actions/pr-push@master + + - uses: r-lib/actions/pr-push@v2 with: repo-token: ${{ secrets.GITHUB_TOKEN }} + style: - if: startsWith(github.event.comment.body, '/style') + if: ${{ github.event.issue.pull_request && startsWith(github.event.comment.body, '/style') }} name: style - runs-on: macOS-latest + runs-on: ubuntu-latest env: GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} + permissions: + contents: write + pull-requests: write steps: - - uses: actions/checkout@v2 - - uses: r-lib/actions/pr-fetch@master + - uses: actions/checkout@v4 + + - uses: r-lib/actions/pr-fetch@v2 with: repo-token: ${{ secrets.GITHUB_TOKEN }} - - uses: r-lib/actions/setup-r@master + + - uses: r-lib/actions/setup-r@v2 + with: + use-public-rspm: true + - name: Install dependencies - run: Rscript -e 'install.packages("styler")' + run: install.packages("styler") + shell: Rscript {0} + - name: Style - run: Rscript -e 'styler::style_pkg()' + run: styler::style_pkg() + shell: Rscript {0} + - name: commit run: | + git config --local user.name "$GITHUB_ACTOR" + git config --local user.email "$GITHUB_ACTOR@users.noreply.github.com" git add \*.R git commit -m 'Style' - - uses: r-lib/actions/pr-push@master + + - uses: r-lib/actions/pr-push@v2 with: repo-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/test-coverage.yaml b/.github/workflows/test-coverage.yaml index 0f014df..254c6db 100644 --- a/.github/workflows/test-coverage.yaml +++ b/.github/workflows/test-coverage.yaml @@ -12,37 +12,50 @@ name: test-coverage jobs: test-coverage: - runs-on: windows-latest + runs-on: ubuntu-latest env: GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} + steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - uses: r-lib/actions/setup-r@v2 + with: + use-public-rspm: true - - uses: r-lib/actions/setup-pandoc@v2 + - uses: r-lib/actions/setup-r-dependencies@v2 + with: + extra-packages: any::covr, any::xml2 + needs: coverage - - name: Query dependencies + - name: Test coverage run: | - install.packages('remotes') - saveRDS(remotes::dev_package_deps(dependencies = TRUE), ".github/depends.Rds", version = 2) - writeLines(sprintf("R-%i.%i", getRversion()$major, getRversion()$minor), ".github/R-version") + cov <- covr::package_coverage( + quiet = FALSE, + clean = FALSE, + install_path = file.path(normalizePath(Sys.getenv("RUNNER_TEMP"), winslash = "/"), "package") + ) + covr::to_cobertura(cov) shell: Rscript {0} - - name: Cache R packages - uses: actions/cache@v1 + - uses: codecov/codecov-action@v4 with: - path: ${{ env.R_LIBS_USER }} - key: ${{ runner.os }}-${{ hashFiles('.github/R-version') }}-1-${{ hashFiles('.github/depends.Rds') }} - restore-keys: ${{ runner.os }}-${{ hashFiles('.github/R-version') }}-1- - - - name: Install dependencies + fail_ci_if_error: ${{ github.event_name != 'pull_request' && true || false }} + file: ./cobertura.xml + plugin: noop + disable_search: true + token: ${{ secrets.CODECOV_TOKEN }} + + - name: Show testthat output + if: always() run: | - install.packages(c("remotes")) - remotes::install_deps(dependencies = TRUE) - remotes::install_cran("covr") - shell: Rscript {0} + ## -------------------------------------------------------------------- + find '${{ runner.temp }}/package' -name 'testthat.Rout*' -exec cat '{}' \; || true + shell: bash - - name: Test coverage - run: covr::codecov() - shell: Rscript {0} + - name: Upload test results + if: failure() + uses: actions/upload-artifact@v4 + with: + name: coverage-test-failures + path: ${{ runner.temp }}/package diff --git a/DESCRIPTION b/DESCRIPTION index 5d19095..82531ae 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: kwb.pkgbuild Title: R package for standardised development at KWB -Version: 0.2.3 +Version: 0.3.0 Authors@R: c(person(given = "Michael", family = "Rustler", @@ -35,7 +35,6 @@ Imports: processx, rematch2, sessioninfo, - stringr, usethis, withr, yaml @@ -53,4 +52,4 @@ Remotes: ByteCompile: true Encoding: UTF-8 LazyData: true -RoxygenNote: 7.2.1 +RoxygenNote: 7.3.1 diff --git a/NAMESPACE b/NAMESPACE index 2704fe2..bb41de4 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -24,6 +24,7 @@ export(use_badge_travis) export(use_codecov) export(use_description) export(use_ghactions) +export(use_ghactions_claude) export(use_gitlab_ci_blogdown) export(use_gitlab_ci_docs) export(use_gitlab_ci_ghpages) @@ -64,15 +65,11 @@ importFrom(pkgdown,build_site) importFrom(processx,run) importFrom(rematch2,re_match) importFrom(sessioninfo,package_info) -importFrom(stringr,str_detect) -importFrom(stringr,str_remove) -importFrom(stringr,str_replace) -importFrom(stringr,str_split) importFrom(tools,toTitleCase) importFrom(usethis,use_description) importFrom(usethis,use_git_ignore) importFrom(usethis,use_mit_license) importFrom(usethis,use_pkgdown) importFrom(usethis,use_template) -importFrom(utils,tail) +importFrom(utils,getFromNamespace) importFrom(withr,with_dir) diff --git a/NEWS.md b/NEWS.md index 0bad1ce..52fa618 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,90 @@ +# [kwb.pkgbuild 0.3.0](https://github.com/KWB-R/kwb.pkgbuild/releases/tag/v0.3.0) 2026-05-07 + +* Modernise GitHub Actions workflows (both in `.github/workflows/` of this + repo and the templates in `inst/templates/ci_github-actions/`) so that + they run on current GitHub-hosted runners again: + + - Bump action versions: `actions/checkout@v4`, `actions/upload-artifact@v4`, + `codecov/codecov-action@v4`, and replace the deprecated `r-lib/actions@master` + references with `r-lib/actions@v2`. + + - Switch from the retired `ubuntu-20.04` runner to `ubuntu-latest` and + rename the matrix entry `oldrel` to `oldrel-1`. + + - Replace the archived `r-hub/sysreqs` step with + `r-lib/actions/setup-r-dependencies@v2`, which also handles dependency + caching out of the box. + + - Fix a stale `if: runner.os == 'Linux (no, try without!)'` condition that + silently disabled the Linux system-dependency step. + + - `pkgdown` workflow: deploy via `JamesIves/github-pages-deploy-action@v4` + on `ubuntu-latest`, with explicit `permissions: contents: write` and a + concurrency group. + + - `test-coverage` workflow: produce a Cobertura report and upload via the + new `codecov/codecov-action@v4`; upload test artefacts on failure. + + - `pr-commands` workflow: gate `/document` and `/style` jobs on + `github.event.issue.pull_request` so they no longer fire on plain issue + comments. + +* Add optional Claude Code GitHub Actions workflows (analogue to + [kwb.raindrop](https://github.com/KWB-R/kwb.raindrop/tree/main/.github/workflows)): + + - New templates in `inst/templates/ci_github-actions-claude/`: + `claude.yaml` (responds to `@claude` mentions in issues and PR reviews) + and `claude-code-review.yaml` (automatic PR review on `opened` / + `synchronize`). + + - New exported function `use_ghactions_claude()` to add only the Claude + workflows to an existing package. + + - `use_ghactions()` and `use_pkg()` gain a `claude = FALSE` toggle that + additionally installs the Claude workflows when set to `TRUE`. The + workflows expect a `CLAUDE_CODE_OAUTH_TOKEN` repository secret to be + configured in GitHub. + +* Documentation cleanup: + + - Remove dead duplicate definitions of `git()`, `construct_commit_message()` + and `github_push()` in `R/deploy_site_github_with_extra_files.R` (they + were silently overwritten by later definitions in the same file). + + - Fix copy/paste errors in `@return` for `use_index_md()`, + `use_badge_ghactions_rcmdcheck()` and `use_badge_runiverse()` (which + referenced "travis" / "codecov" badges instead of the actual return + value). + + - Mark internal helpers (`use_installation()`, `read_description()`, + `kwb_author()`, `kwb_package()`) with `@noRd` so they no longer create + public Rd entries. + + - Rewrite the titles / descriptions for `use_pkgdown()`, `use_readme_md()` + and `use_index_md()` so the man pages describe what the function + actually does. + + - Fix typos (`releveant`, `directoy`, `aleady`, `DESCIPTION`, + `(default: KWB-R")`, `file patern`) across roxygen blocks and Rd files. + +* `use_pkgdown()`: expose the KWB logo as parameters `kwb_logo_url` + (default: `https://logos.kompetenz-wasser.io/KWB_Logo_M_Blau_RGB.svg`) + and `kwb_logo_href` (default: `https://www.kompetenz-wasser.de`) so the + default logo URL is no longer hardcoded inside the function body and can + be overridden per package without forking the function. + +* Vignettes: + + - New vignette `vignette("github-actions", package = "kwb.pkgbuild")` + describing the default workflows, the optional Claude Code integration, + the `CLAUDE_CODE_OAUTH_TOKEN` secret, and how to refresh workflows in + existing packages. + + - Update `vignette("tutorial")`: replace stale Travis / AppVeyor + references with the GitHub Actions workflow set, document the new + `claude = TRUE` switch in `use_pkg()`, and fix the `kwb.pkgdown` / + `kwb.pkddown` typos. + # [kwb.pkgbuild 0.2.3](https://github.com/KWB-R/kwb.pkgbuild/releases/tag/v0.2.3) 2022-10-24 * Fix GitHub action worfklows: diff --git a/R/.add_authors.R b/R/.add_authors.R index bb92833..7902bcc 100644 --- a/R/.add_authors.R +++ b/R/.add_authors.R @@ -79,7 +79,7 @@ add_authors <- function(author_meta, role = "ctb", path = getwd()) #' @export #' @importFrom purrr transpose convert_author_metadata_tolist <- function( - author_metadata_df = create_author_metadata_from_orcid() + author_metadata_df = create_author_metadata_from_orcid() ) { setNames( @@ -94,31 +94,24 @@ convert_author_metadata_tolist <- function( #' @param orcids named character vector with ORCIDs and names correspondig to #' to "given_name family name" (defaults: kwb.orcid::get_kwb_orcids()) #' @importFrom kwb.orcid get_kwb_orcids -#' @importFrom stringr str_trim str_split -#' @importFrom magrittr %>% +#' @importFrom kwb.utils extractSubstring setColumns #' @return data frame with required metadata for R package DESCRIPTION as #' required by desc::desc_add_author() #' @export create_author_metadata_from_orcid <- function( - orcids = kwb.orcid::get_kwb_orcids() + orcids = kwb.orcid::get_kwb_orcids() ) { - orc_ids <- orcids[order(orcids)] - orc_names <- names(orcids) + orc_ids <- sort(orcids) - orc_names_matrix <- orc_names %>% - stringr::str_trim() %>% - stringr::str_split(pattern = "\\s+", simplify = TRUE, n = 2) - - kwb.utils::noFactorDataFrame( - given = orc_names_matrix[,1], - family = orc_names_matrix[,2], - email = sprintf( - "%s.%s@kompetenz-wasser.de", - tolower(orc_names_matrix[, 1]), - tolower(orc_names_matrix[, 2]) - ), - orcid = orc_ids, - row.names = NULL - ) + names(orcids) %>% + kwb.utils::extractSubstring( + pattern = "^\\s*(\\S+)\\s+(\\S+)\\s*$", + index = c(given = 1L, family = 2L) + ) %>% + kwb.utils::setColumns( + email = email_kwb(.[["given"]], .[["family"]]), + orcid = orc_ids, + dbg = FALSE + ) } diff --git a/R/create_pkg_dir.R b/R/create_pkg_dir.R index 567cba2..5bb228d 100644 --- a/R/create_pkg_dir.R +++ b/R/create_pkg_dir.R @@ -45,22 +45,21 @@ create_pkg_dir <- function(pkg_dir) #' @noRd #' @keywords internal -#' @importFrom stringr str_split -#' @importFrom utils tail check_pkg_dir_nested <- function(pkg_dir) { - last_folders <- utils::tail(n = 2, as.character( - stringr::str_split(pkg_dir, pattern = "/", simplify = TRUE) - )) + leaf_folder <- basename(pkg_dir) + parent_folder <- basename(dirname(pkg_dir)) - if (last_folders[1] == last_folders[2]) clean_stop( - sprintf("Package skeleton for '%s' cannot be created, ", last_folders[2]), + if (parent_folder == leaf_folder) clean_stop( + sprintf("Package skeleton for '%s' cannot be created, ", leaf_folder), sprintf("as it would be nested in subfolder '%s'.\n\n", pkg_dir), "Workaround: specify a different 'root_dir' in function use_pkg_skeleton()" ) message(sprintf( - "%s is a valid 'root_dir' for pkg '%s'", pkg_dir, last_folders[2] + "%s is a valid 'root_dir' for pkg '%s'", + pkg_dir, + leaf_folder )) pkg_dir diff --git a/R/deploy_site_github_with_extra_files.R b/R/deploy_site_github_with_extra_files.R index 0120b6c..9c2a639 100644 --- a/R/deploy_site_github_with_extra_files.R +++ b/R/deploy_site_github_with_extra_files.R @@ -35,26 +35,6 @@ github_url_rx <- function() { # pkgdown functions (missing in pkgdown >= 1.5.0) ------------------------------ -#' git -#' @importFrom processx run -#' @keywords internal -#' @noRd -#' -git <- function(...) { - processx::run("git", c(...), echo_cmd = TRUE, echo = TRUE) -} - -#' construct_commit_message -#' -#' @keywords internal -#' @noRd -#' -construct_commit_message <- function(pkg, commit = Sys.getenv("TRAVIS_COMMIT")) { - pkg <- pkgdown::as_pkgdown(pkg) - - sprintf("Built site for %s: %s@%s", pkg$package, pkg$version, substr(commit, 1, 7)) -} - #' rule #' @importFrom cli cat_rule #' @importFrom crayon bold @@ -83,27 +63,6 @@ github_clone <- function(dir, repo_slug) { } -#' github_push -#' -#' @importFrom withr with_dir -#' @keywords internal -#' -github_push <- function(dir, commit_message) { - # force execution before changing working directory - force(commit_message) - - rule("Commiting updated site", line = 1) - - withr::with_dir(dir, { - git("add", "-A", ".") - git("commit", "--allow-empty", "-m", commit_message) - - rule("Deploying to GitHub Pages", line = 1) - git("remote", "-v") - git("push", "--force", "origin", "HEAD:gh-pages") - }) -} - # deploy_site_github_with_extra_files ------------------------------------------ #' deploy_site_github_with_extra_files @@ -111,7 +70,7 @@ github_push <- function(dir, commit_message) { #' @description for details see pkgdown::deploy_site_github(), only parameter #' vignettes_file_pattern_to_copy added #' @param pkg "." -#' @param vignettes_file_pattern_to_copy file patern for copying files from +#' @param vignettes_file_pattern_to_copy file pattern for copying files from #' vignettes directory into deploy directory (default: "\\.json$") #' @param install Optionally, opt-out of automatic installation. This is #' necessary if the package you're documenting is a dependency of pkgdown @@ -211,7 +170,7 @@ copy_files_from_vignettes_dir_to_deploy_dir <- function( #' Assumes that you're in a git clone of the project, and the package is #' already installed. #' @param pkg "." -#' @param vignettes_file_pattern_to_copy file patern for copying files from +#' @param vignettes_file_pattern_to_copy file pattern for copying files from #' vignettes directory into deploy directory (default: "\\.json$") #' @param branch The git branch to deploy to #' @param remote The git remote to deploy to @@ -258,7 +217,7 @@ deploy_to_branch_with_extra_files <- function(pkg = ".", if (clean) { rule("Cleaning files from old site", line = 1) - pkgdown:::clean_site(pkg) + pkgdown::clean_site(pkg) } pkgdown::build_site(pkg, devel = FALSE, preview = FALSE, install = FALSE, ...) @@ -271,7 +230,7 @@ deploy_to_branch_with_extra_files <- function(pkg = ".", ) if (github_pages) { - pkgdown:::build_github_pages(pkg) + getFromNamespace("build_github_pages", "pkgdown")(pkg) } github_push(dest_dir, commit_message, remote, branch) diff --git a/R/helper.R b/R/helper.R index 9837341..4b04fc9 100644 --- a/R/helper.R +++ b/R/helper.R @@ -4,13 +4,27 @@ clean_stop <- function(...) stop(..., call. = FALSE) } +# email_kwb -------------------------------------------------------------------- +email_kwb <- function( + given, + family, + full_name = paste0(given, family, sep = ".") +) +{ + paste0(tolower(full_name), "@kompetenz-wasser.de") +} + +# get_from_namespace ----------------------------------------------------------- +#' @importFrom utils getFromNamespace +get_from_namespace <- utils::getFromNamespace + # get_pkgname ------------------------------------------------------------------ #' Helper Function: Get Package Name #' -#' @param pkgname either package name or NULL. In this -#' case the DESCRIPTION file in the current working -#' directory is read and is package name ues (default: NULL) +#' @param pkgname either package name or NULL. In the latter case the +#' DESCRIPTION file in the current working directory is read and the +#' package name from there is used (default: NULL) #' #' @return package name #' @export @@ -56,10 +70,13 @@ git_check_if_windows <- function(git_exe) # kwb_author ------------------------------------------------------------------- -#' Get Information About KWB Author +#' Get information about a KWB author #' +#' @param who key in the author registry (currently only "rustler") +#' @return list with elements `name`, `orcid`, `url` #' @importFrom kwb.utils selectElements #' @keywords internal +#' @noRd kwb_author <- function(who) { kwb.utils::selectElements(elements = who, x = list( @@ -71,10 +88,13 @@ kwb_author <- function(who) )) } -#' Get (Default) Information About KWB-R Package +#' Get (default) information about a KWB-R package #' +#' @param pkg key in the package registry (currently only "kwb.umberto") +#' @return list with elements `name`, `title`, `desc` #' @importFrom kwb.utils selectElements #' @keywords internal +#' @noRd kwb_package <- function(pkg) { kwb.utils::selectElements(elements = pkg, x = list( @@ -121,7 +141,6 @@ path_to_git <- function() #' @param dbg print debug messages (default: TRUE) #' @return sets globally user.name and user.email in Git #' @export -#' @importFrom stringr str_replace set_github_user <- function( git_username = "kwb.pkgbuild::use_autopkgdown()", git_fullname = "kwb.pkgbuild::use_autopkgdown()", @@ -132,13 +151,9 @@ set_github_user <- function( dbg = TRUE ) { + # Compose KWB e-mail address if (is.null(git_email)) { - - # Replace space with dot - dot_name <- stringr::str_replace(git_fullname, "\\s+", ".") - - # Compose KWB e-mail address - git_email <- paste0(tolower(dot_name), "@kompetenz-wasser.de") + git_email <- email_kwb(full_name = gsub("\\s+", ".", git_fullname)) } git_exe <- git_check_if_windows(git_exe) diff --git a/R/read_description.R b/R/read_description.R index 721a61b..670f6b0 100644 --- a/R/read_description.R +++ b/R/read_description.R @@ -1,15 +1,17 @@ # read_description ------------------------------------------------------------- -#' Helper function: read_description +#' Helper: read selected fields from a `DESCRIPTION` file #' -#' @param file path to DESCRIPTION file (default: DESCRIPTION) +#' @param file path to DESCRIPTION file (default: "DESCRIPTION") #' @importFrom desc desc -#' @return list with pkg "name", "title", "desc", "version" +#' @return list with elements `name`, `title`, `desc`, `version` +#' @keywords internal +#' @noRd read_description <- function(file = "DESCRIPTION") { if (! file.exists(file)) clean_stop( - sprintf("DESCIPTION file not found at: %s.\n", file.path(getwd(), file)), + sprintf("DESCRIPTION file not found at: %s.\n", file.path(getwd(), file)), "Please set working directory with function setwd() properly!" ) diff --git a/R/use_badges.R b/R/use_badges.R index 9255450..be7942b 100644 --- a/R/use_badges.R +++ b/R/use_badges.R @@ -3,7 +3,7 @@ #' Badge appveyor #' @param repo name of repository (default: NULL) #' @param user user name or organisation under which repository defined in -#' parameter "repo" is hosted (default: KWB-R") +#' parameter "repo" is hosted (default: "KWB-R") #' @param domain under which repository is hosted (default: "github") #' #' @return generates appveyor badge link @@ -28,7 +28,7 @@ use_badge_appveyor <- function(repo = NULL, user = "KWB-R", domain = "github") #' Badge travis #' @param repo name of repository (default: NULL) #' @param user user name or organisation under which repository defined in -#' parameter "repo" is hosted (default: KWB-R") +#' parameter "repo" is hosted (default: "KWB-R") #' @return generates travis badge link #' @export use_badge_travis <- function(repo = NULL, user = "KWB-R") @@ -47,10 +47,10 @@ use_badge_travis <- function(repo = NULL, user = "KWB-R") #' Badge Github Actions RCMD Check #' @param repo name of repository (default: NULL) #' @param user user name or organisation under which repository defined in -#' parameter "repo" is hosted (default: KWB-R") +#' parameter "repo" is hosted (default: "KWB-R") #' @param branch default: NULL (i.e. use "default" branch) or user defined branch #' (e.g. "dev") -#' @return generates travis badge link +#' @return generates GitHub Actions R-CMD-check badge markdown #' @export use_badge_ghactions_rcmdcheck <- function(repo = NULL, user = "KWB-R", branch = NULL) @@ -70,7 +70,7 @@ use_badge_ghactions_rcmdcheck <- function(repo = NULL, user = "KWB-R", #' Badge Github Actions Pkgdown #' @param repo name of repository (default: NULL) #' @param user user name or organisation under which repository defined in -#' parameter "repo" is hosted (default: KWB-R") +#' parameter "repo" is hosted (default: "KWB-R") #' @param branch default: NULL (i.e. use "default" branch) or user defined branch #' (e.g. "dev") #' @return generates Github Actions Pkgdown badge link @@ -93,7 +93,7 @@ use_badge_ghactions_pkgdown <- function(repo = NULL, user = "KWB-R", branch = NU #' Badge Github Actions #' @param repo name of repository (default: NULL) #' @param user user name or organisation under which repository defined in -#' parameter "repo" is hosted (default: KWB-R") +#' parameter "repo" is hosted (default: "KWB-R") #' @param branch default: NULL (i.e. use "default" branch) or user defined branch #' (e.g. "dev") #' @return generates Github Actions badges link @@ -112,7 +112,7 @@ use_badge_ghactions <- function(repo = NULL, user = "KWB-R", branch = NULL) #' Badge codecov #' @param repo name of repository (default: NULL) #' @param user user name or organisation under which repository defined in -#' parameter "repo" is hosted (default: KWB-R") +#' parameter "repo" is hosted (default: "KWB-R") #' @param domain under which repository is hosted (default: "github") #' @return generates codecov badge link #' @export @@ -140,7 +140,7 @@ use_badge_codecov <- function(repo = NULL, user = "KWB-R", domain = "github") #' @export use_badge_lifecycle <- function(stage = "experimental") { - stages <- usethis:::stages + stages <- getFromNamespace("stages", "usethis") stage <- match.arg(tolower(stage), names(stages)) kwb.utils::resolve( @@ -215,8 +215,8 @@ is_on_cran <- function(cran_link) #' Badge R-Universe #' @param repo name of repository (default: NULL) #' @param user user name or organisation under which repository defined in -#' parameter "repo" is hosted (default: KWB-R") -#' @return generates codecov badge link +#' parameter "repo" is hosted (default: "KWB-R") +#' @return generates R-Universe badge markdown #' @export use_badge_runiverse <- function(repo = NULL, user = "KWB-R") { diff --git a/R/use_codecov.R b/R/use_codecov.R index cda178a..d824391 100644 --- a/R/use_codecov.R +++ b/R/use_codecov.R @@ -6,8 +6,8 @@ #' @export use_codecov <- function() { - usethis:::check_uses_github_actions() - usethis:::use_dependency("covr", "Suggests") + getFromNamespace("check_uses_github_actions", "usethis")() + getFromNamespace("use_dependency", "usethis")("covr", "Suggests") if (! usethis::use_template("codecov.yml", ignore = TRUE)) { return(invisible(FALSE)) diff --git a/R/use_description.R b/R/use_description.R index 1a776a2..e7a7a4b 100644 --- a/R/use_description.R +++ b/R/use_description.R @@ -16,7 +16,6 @@ #' @return writes DESCRIPTION file using usethis::use_description() with KWB #' style #' @importFrom usethis use_description -#' @importFrom stringr str_split #' @importFrom tools toTitleCase #' @export use_description <- function( @@ -37,13 +36,11 @@ use_description <- function( pkg$title <- tools::toTitleCase(pkg$title) - full_name <- stringr::str_split(string = author$name,pattern = "\\s+")[[1]] - author_name <- full_name[1] - author_surname <- full_name[2] + full_name <- strsplit(author$name, "\\s+")[[1L]] + author_name <- full_name[1L] + author_surname <- full_name[2L] - author_email <- sprintf( - '%s.%s@kompetenz-wasser.de', tolower(author_name), tolower(author_surname) - ) + author_email <- email_kwb(given = author_name, family = author_surname) author_comment <- if (is.null(author$orcid)) { "NULL" diff --git a/R/use_github_ci.R b/R/use_github_ci.R index 8c65ce6..f69eca1 100644 --- a/R/use_github_ci.R +++ b/R/use_github_ci.R @@ -1,17 +1,57 @@ # use_ghactions----------------------------------------------------------------- #' Adds default .github/workflows/ -#' @return writes .github/workflows/ and adds it .Rbuildignore +#' +#' Copies the default GitHub Actions workflows (R-CMD-check, pkgdown, +#' pr-commands, test-coverage) into `.github/workflows/`. Optionally adds the +#' Claude Code workflows (`claude.yaml`, `claude-code-review.yaml`) when +#' `claude = TRUE`. Those workflows require a `CLAUDE_CODE_OAUTH_TOKEN` +#' repository secret to be configured in GitHub. +#' +#' @param claude if `TRUE`, additionally copies the Claude Code workflows +#' (`claude.yaml` and `claude-code-review.yaml`) into `.github/workflows/`. +#' Defaults to `FALSE`. +#' @return writes `.github/workflows/` and adds it to `.Rbuildignore` #' @importFrom fs dir_copy #' @export +use_ghactions <- function(claude = FALSE) +{ + fs::dir_copy( + path = system.file("templates/ci_github-actions/", package = "kwb.pkgbuild"), + new_path = ".github/workflows", + overwrite = TRUE + ) + + if (isTRUE(claude)) { + use_ghactions_claude() + } + + write_to_rbuildignore(ignore_pattern = "^\\.github$") +} + +# use_ghactions_claude---------------------------------------------------------- -use_ghactions <- function() +#' Adds Claude Code workflows to .github/workflows/ +#' +#' Copies the Claude Code workflows (`claude.yaml` and `claude-code-review.yaml`) +#' into `.github/workflows/`. Requires a `CLAUDE_CODE_OAUTH_TOKEN` repository +#' secret to be configured in GitHub for the workflows to function. +#' +#' @return writes Claude workflow YAML files into `.github/workflows/` +#' @importFrom fs dir_create file_copy dir_ls +#' @export +use_ghactions_claude <- function() { + fs::dir_create(".github/workflows") -fs::dir_copy(path = system.file("templates/ci_github-actions/", - package = "kwb.pkgbuild"), - new_path = ".github/workflows", - overwrite = TRUE) + src_dir <- system.file( + "templates/ci_github-actions-claude/", + package = "kwb.pkgbuild" + ) -write_to_rbuildignore(ignore_pattern = "^\\.github$") + fs::file_copy( + path = fs::dir_ls(src_dir, glob = "*.yaml"), + new_path = ".github/workflows/", + overwrite = TRUE + ) } diff --git a/R/use_gitlab_ci.R b/R/use_gitlab_ci.R index 22f9433..f0feb5a 100644 --- a/R/use_gitlab_ci.R +++ b/R/use_gitlab_ci.R @@ -2,7 +2,7 @@ #' Adds .gitlab-ci.yml (if repo contains a "docs" subfolder) #' -#' @param dest_dir directoy to write (default: getwd()) +#' @param dest_dir directory to write (default: getwd()) #' @param yml_vector a yml imported as string vector (default: #' gitlab_ci_template_docs()) #' @return writes .gitlab-ci.yml and adds it .Rbuildignore @@ -26,7 +26,7 @@ use_gitlab_ci_docs <- function( # use_gitlab_ci_ghpages--------------------------------------------------------- #' Adds .gitlab-ci.yml (which should be saved in root dir of "gh-pages" branch) -#' @param dest_dir directoy to write (default: +#' @param dest_dir directory to write (default: #' getwd()) #' @param yml_vector a yml imported as string vector (default: gitlab_ci_template_ghpages()) #' @return writes .gitlab-ci.yml @@ -43,7 +43,7 @@ use_gitlab_ci_ghpages <- function( # use_gitlab_ci_blogdown-------------------------------------------------------- #' Adds .gitlab-ci.yml (if repo contains on root in a "gh-pages" branch) -#' @param dest_dir directoy to write (default: getwd()) +#' @param dest_dir directory to write (default: getwd()) #' @param yml_vector a yml imported as string vector (default: #' gitlab_ci_template_blogdown()) #' @return writes .gitlab-ci.yml @@ -61,14 +61,13 @@ use_gitlab_ci_blogdown <- function( #' Adds .gitlab-ci.yml #' -#' @param dest_dir directoy to write (default: getwd()) +#' @param dest_dir directory to write (default: getwd()) #' @param yml_vector a yml imported as string vector (default: #' gitlab_ci_template_pkgdown(), where "/" is replaced with value #' from DESCRIPTION specified in field URL, e.g. #' https://github.com/KWB-R/kwb.pkgbuild" sets "KWB-R/kwb.pkgbuild" as #' "/") #' @return writes .gitlab-ci.yml -#' @importFrom stringr str_remove #' @importFrom desc desc_get #' @export @@ -76,10 +75,8 @@ use_gitlab_ci_pkgdown <- function( dest_dir = getwd(), yml_vector = gitlab_ci_template_pkgdown() ) { - repo <- stringr::str_remove(desc::desc_get("URL"), "^http(s)?://github.com/") - - yml_vector <- stringr::str_replace(yml_vector, "/", repo) - + owner_repo <- gsub("^http(s)?://github.com/", "", desc::desc_get("URL")) + yml_vector <- gsub("/", owner_repo, yml_vector) write_gitlab_ci(yml_vector, dest_dir = dest_dir, ignore = TRUE) } diff --git a/R/use_index_md.R b/R/use_index_md.R index ab50a5a..2b59377 100644 --- a/R/use_index_md.R +++ b/R/use_index_md.R @@ -1,14 +1,21 @@ # use_index_md ---------------------------------------------------------------- -#' Use index.md (used for pkgdown::build_home()) -#' @param user user name or organisation under which repository defined in\cr -#' parameter "repo" is hosted (default: "KWB-R")\cr -#' @param domain under which repository is hosted (default: "github") -#' @param stage badge declares the developmental stage of a package, according -#' to [https://www.tidyverse.org/lifecycle/](https://www.tidyverse.org/lifecycle/), -#' valid arguments are: "experimental", "maturing", "stable", "retired", -#' "archived", "dormant", "questioning"), (default: "experiment") -#' @return generates travis badge link +#' Create KWB-styled \code{index.md} +#' +#' Generates an \code{index.md} (used by \code{pkgdown::build_home()}) with the +#' KWB default badge set (GitHub Actions, codecov, lifecycle, CRAN, R-universe), +#' the package description from \code{DESCRIPTION} and an installation snippet. +#' @param user user name or organisation under which the repository is hosted +#' (default: "KWB-R") +#' @param domain under which the repository is hosted (default: "github") +#' @param stage badge declaring the developmental stage of the package +#' according to +#' [https://www.tidyverse.org/lifecycle/](https://www.tidyverse.org/lifecycle/); +#' valid values are "experimental", "maturing", "stable", "retired", +#' "archived", "dormant", "questioning" (default: "experimental") +#' @return writes \code{index.md} (used as `pkgdown` home) and adds the +#' pattern to \code{.Rbuildignore}. Invisibly returns the character vector +#' that was written. #' @export #' @importFrom desc desc diff --git a/R/use_installation.R b/R/use_installation.R index fbd3e56..490040f 100644 --- a/R/use_installation.R +++ b/R/use_installation.R @@ -1,11 +1,13 @@ # use_installation ------------------------------------------------------------- -#' Helper function: describe pkg installation in index.Rmd / README.md +#' Helper: describe pkg installation in index.md / README.md #' #' @param pkgname package name -#' @param user user name or organisation under which repository defined in -#' parameter "repo" is hosted -#' @param domain under which repository is hosted +#' @param user user/organisation hosting the repository +#' @param domain under which repository is hosted (e.g. "github") +#' @return character vector with markdown lines for the installation section +#' @keywords internal +#' @noRd use_installation <- function(pkgname, user, domain) { url_tutorial_install <- diff --git a/R/use_mit_license.R b/R/use_mit_license.R index f34f3c4..882fbd6 100644 --- a/R/use_mit_license.R +++ b/R/use_mit_license.R @@ -6,48 +6,48 @@ #' (default: "list(name = kwb.pkgbuild:::kwb_string(), start_year = NULL)") #' @return creates MIT licence file #' @importFrom usethis use_mit_license -#' @importFrom stringr str_detect #' @importFrom fs file_copy #' @importFrom kwb.utils catAndRun #' @export use_mit_license <- function( - copyright_holder = list(name = kwb_string(), start_year = NULL) + copyright_holder = list(name = kwb_string(), start_year = NULL) ) { - kwb.utils::catAndRun("Creating KWB MIT LICENSE/LICENSE.md files", - expr = { - usethis::use_mit_license(copyright_holder = copyright_holder$name)}) + kwb.utils::catAndRun("Creating KWB MIT LICENSE/LICENSE.md files", { + usethis::use_mit_license(copyright_holder = copyright_holder$name) + }) + this_year <- format(Sys.Date(), format = "%Y") - copyright_years <- ifelse(!is.null(copyright_holder$start_year), - sprintf("%s-%s", as.character(copyright_holder$start_year), - format(Sys.Date(), format = "%Y")), - format(Sys.Date(), format = "%Y")) + copyright_years <- ifelse( + is.null(copyright_holder$start_year), + this_year, + sprintf("%s-%s", as.character(copyright_holder$start_year), this_year) + ) - if (! is.null(copyright_holder$start_year)) { + if (is.null(copyright_holder$start_year)) { + return() + } - kwb.utils::catAndRun("Modifying start year in MIT LICENSE (CRAN version)", - expr = { - writeLines(text = sprintf("YEAR: %s\nCOPYRIGHT HOLDER: %s", - copyright_years, - copyright_holder$name), - con = "LICENSE") - }) + kwb.utils::catAndRun("Modifying start year in MIT LICENSE (CRAN version)", { + writeLines( + text = sprintf( + "YEAR: %s\nCOPYRIGHT HOLDER: %s", + copyright_years, + copyright_holder$name + ), + con = "LICENSE" + ) + }) - kwb.utils::catAndRun("Modifying start year in MIT LICENSE.md", - expr = { + kwb.utils::catAndRun("Modifying start year in MIT LICENSE.md", { lic_txt <- readLines("LICENSE.md") - index <- stringr::str_detect(lic_txt,pattern = "^Copyright\\s+\\(c\\)") - + index <- grep("^Copyright\\s+\\(c\\)", lic_txt) lic_txt[index] <- sprintf( "Copyright (c) %s %s", copyright_years, copyright_holder$name ) - - writeLines(lic_txt, "LICENSE.md")}) - - - } - + writeLines(lic_txt, "LICENSE.md") + }) } diff --git a/R/use_news_md.R b/R/use_news_md.R index 8dabafc..ebbdd57 100644 --- a/R/use_news_md.R +++ b/R/use_news_md.R @@ -32,7 +32,7 @@ use_news_md <- function( if (fs::file_exists(news_md)) { warning( "No 'NEWS.md' created by kwb.pkgbuild::use_news_md(),\n", - "because 'NEWS.md' is aleady existing. Please delete it first!" + "because 'NEWS.md' already exists. Please delete it first!" ) } else { writeLines(news_txt, con = "NEWS.md") diff --git a/R/use_pkg.R b/R/use_pkg.R index 9ab845d..266980a 100644 --- a/R/use_pkg.R +++ b/R/use_pkg.R @@ -26,12 +26,14 @@ #' "archived", "dormant", "questioning"), (default: "experiment") #' @return writes DESCRIPTION file using usethis::use_description() with KWB #' style -#' @importFrom stringr str_detect #' @importFrom fs dir_create #' @param auto_build_pkgdown prepare Travis for pkgdown::build_site() (default: #' FALSE), only possible if GITHUB repo already existing +#' @param claude if TRUE, additionally adds the Claude Code GitHub Actions +#' workflows (claude.yaml, claude-code-review.yaml). Requires the repository +#' secret CLAUDE_CODE_OAUTH_TOKEN to be configured in GitHub. (default: FALSE) #' @param dbg print debug messages (default: TRUE) -#' @param ... additional arguments passed to use_autopkgdown() (only releveant +#' @param ... additional arguments passed to use_autopkgdown() (only relevant #' if "auto_build_pkgdown" == TRUE) #' @export use_pkg <- function( @@ -45,6 +47,7 @@ use_pkg <- function( domain = "github", stage = "experimental", auto_build_pkgdown = FALSE, + claude = FALSE, dbg = TRUE, ... ) @@ -55,10 +58,8 @@ use_pkg <- function( copyright_holder_name = copyright_holder$name, funder = funder ) - # Create MIT LICENSE file - mit_licence <- stringr::str_detect(string = license, pattern = "MIT") - - if (mit_licence) { + # Create MIT LICENSE file if applicable + if (grepl("MIT", license)) { use_mit_license(copyright_holder) } @@ -66,7 +67,7 @@ use_pkg <- function( use_pkgdown(author, copyright_holder$name, pkg$name, user, domain) # Update Github Actions - use_ghactions() + use_ghactions(claude = claude) # Use codecov use_codecov() diff --git a/R/use_pkg_skeleton.R b/R/use_pkg_skeleton.R index c981b85..a72072f 100644 --- a/R/use_pkg_skeleton.R +++ b/R/use_pkg_skeleton.R @@ -27,8 +27,7 @@ use_pkg_skeleton <- function(pkg_name) usethis::use_git_ignore(".Rproj.user") - if (usethis:::is_package()) { - + if (getFromNamespace("is_package", "usethis")()) { usethis::use_build_ignore(c( rproj_file, ".Rhistory", ".RData", ".Rproj.user" , ".here" )) diff --git a/R/use_pkgdown.R b/R/use_pkgdown.R index cc124a4..dd2341d 100644 --- a/R/use_pkgdown.R +++ b/R/use_pkgdown.R @@ -1,14 +1,23 @@ -#' pkgdown for KWB +#' Set up `pkgdown` with KWB styling +#' +#' Calls `usethis::use_pkgdown()` and additionally writes a `_pkgdown.yml` +#' configured with KWB defaults (Bootstrap 5 + cerulean theme, KWB authors +#' block, copyright holder logo). #' #' @param author list of author attributes (default: #' kwb.pkgbuild:::kwb_author("rustler")) #' @param copyright_holder_name name of copyright holder -#' (default: kwb.pkgbuild:::kwb_string()) +#' (default: kwb.pkgbuild:::kwb_string()) #' @param pkg name of KWB package (default: get_pkgname()) #' @param user name of GitHub user/organisation (default: 'kwb-r') #' @param domain name of domain for webpage publishing (default: 'github') -#' @return performs usethis::use_pkgdown() and additionally writes _pkgdown.yml -#' based on KWB styling +#' @param kwb_logo_url URL of the KWB logo image embedded in the copyright +#' holder's `pkgdown` author block (default: +#' `"https://logos.kompetenz-wasser.io/KWB_Logo_M_Blau_RGB.svg"`) +#' @param kwb_logo_href URL the KWB logo links to (default: +#' `"https://www.kompetenz-wasser.de"`) +#' @return invisibly; as a side effect writes `_pkgdown.yml` with KWB styling +#' and adds it to `.Rbuildignore`. #' @importFrom usethis use_pkgdown #' @importFrom kwb.utils isNaOrEmpty #' @export @@ -17,7 +26,9 @@ use_pkgdown <- function( copyright_holder_name = kwb_string(), pkg = get_pkgname(), user = "kwb-r", - domain = "github" + domain = "github", + kwb_logo_url = "https://logos.kompetenz-wasser.io/KWB_Logo_M_Blau_RGB.svg", + kwb_logo_href = "https://www.kompetenz-wasser.de" ) { usethis::use_pkgdown() @@ -36,10 +47,11 @@ use_pkgdown <- function( if (copyright_holder_name == kwb_string()) { authors <- c(authors, stats::setNames(nm = copyright_holder_name, list(list( - href = "http://www.kompetenz-wasser.de", - html = paste0( - "KWB") + href = kwb_logo_href, + html = sprintf( + "KWB", + kwb_logo_url + ) )))) } diff --git a/R/use_readme_md.R b/R/use_readme_md.R index d3d4f15..384cef6 100644 --- a/R/use_readme_md.R +++ b/R/use_readme_md.R @@ -1,14 +1,20 @@ # use_readme_md ---------------------------------------------------------------- -#' Use README -#' @param user user name or organisation under which repository defined in\cr -#' parameter "repo" is hosted (default: "KWB-R")\cr -#' @param domain under which repository is hosted (default: "github") -#' @param stage badge declares the developmental stage of a package, according -#' to [https://www.tidyverse.org/lifecycle/](https://www.tidyverse.org/lifecycle/), -#' valid arguments are: "experimental", "maturing", "stable", "retired", -#' "archived", "dormant", "questioning"), (default: "experiment") -#' @return generates README.md +#' Create KWB-styled `README.md` +#' +#' Generates a `README.md` with the KWB default badge set, the package +#' description from `DESCRIPTION`, an installation snippet and links to the +#' release and development documentation websites. +#' +#' @param user user name or organisation under which the repository is hosted +#' (default: "KWB-R") +#' @param domain under which the repository is hosted (default: "github") +#' @param stage badge declaring the developmental stage of the package +#' according to +#' [https://www.tidyverse.org/lifecycle/](https://www.tidyverse.org/lifecycle/); +#' valid values are "experimental", "maturing", "stable", "retired", +#' "archived", "dormant", "questioning" (default: "experimental") +#' @return writes `README.md` and adds it to `.Rbuildignore` #' @export #' @importFrom desc desc diff --git a/_pkgdown.yml b/_pkgdown.yml index e193371..f0d8678 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -10,7 +10,7 @@ authors: alt='Project FAKIN' width='72' /> Kompetenzzentrum Wasser Berlin gGmbH (KWB): href: https://www.kompetenz-wasser.de - html: KWB template: diff --git a/inst/extdata/delete-workflow-runs.R b/inst/extdata/delete-workflow-runs.R new file mode 100644 index 0000000..1728d15 --- /dev/null +++ b/inst/extdata/delete-workflow-runs.R @@ -0,0 +1,118 @@ +if (FALSE) +{ + run_info <- get_info_on_last_workflow_runs( + repo = "kwb.nextcloud", + per_page = 50L, + "name", + "conclusion" + ) + + View(run_info) + + nrow(run_info) + + range(run_info$run_started_at) + + delete_runs(urls = run_info$url) +} + +`%>%` <- magrittr::`%>%` + +# get_info_on_last_workflow_runs ----------------------------------------------- +get_info_on_last_workflow_runs <- function(repo, per_page = 30L, ...) +{ + result <- get_workflow_runs(repo) + + page <- last_page(result$total_count, per_page) + + result <- get_workflow_runs(repo, per_page = per_page, page = page) + + x <- result$workflow_runs + + stopifnot(length(x) <= per_page) + + do.call(rbind, lapply(x, function(xx) { + as.data.frame(kwb.utils::selectElements(xx, c( + "url", + "run_started_at", + "status", + ... + ))) + })) +} + +# get_workflow_runs ------------------------------------------------------------ +get_workflow_runs <- function(repo, per_page = 30L, page = 1L) +{ + endpoints <- list( + runs = "/repos///actions/runs?", + runs_pars = "per_page=&page=", + api = "https://api.github.com", + owner = "KWB-R" + ) + + # List workflow runs for a repository + response <- get_response( + url = kwb.utils::resolve( + "runs", + endpoints, + repo = repo, + per_page = per_page, + page = page + ) + ) + + if (httr::status_code(response) == 403L) { + print(response) + } + + httr::content(response) +} + +# get_response ----------------------------------------------------------------- +get_response <- function(url, token = github_token(), ...) +{ + httr::GET(url, headers = create_header(token, ...)) +} + +# github_token ----------------------------------------------------------------- +github_token <- function() +{ + Sys.getenv("GITHUB_PAT") +} + +# create_header ---------------------------------------------------------------- +create_header <- function(token, ...) +{ + list( + Authorization = paste("token", token), + ... + ) +} + +# last_page -------------------------------------------------------------------- +last_page <- function(n_runs, per_page) +{ + ((n_runs - 1L) %/% per_page) + 1L +} + +# delete_runs ------------------------------------------------------------------ +delete_runs <- function(urls, token = github_token()) +{ + invisible(sapply(seq_along(urls), function(i) { + + url <- urls[i] + + kwb.utils::catAndRun( + sprintf("Deleting %d/%d: %s ", i, length(urls), url), + expr = { + config <- do.call(httr::add_headers, create_header(token)) + response <- httr::DELETE(url, config) + if (httr::status_code(response) != 204L) { + print(response) + stop("DELETE failed.") + } + } + ) + })) +} diff --git a/inst/templates/ci_github-actions-claude/claude-code-review.yaml b/inst/templates/ci_github-actions-claude/claude-code-review.yaml new file mode 100644 index 0000000..10d2e3d --- /dev/null +++ b/inst/templates/ci_github-actions-claude/claude-code-review.yaml @@ -0,0 +1,40 @@ +name: Claude Code Review + +on: + pull_request: + types: [opened, synchronize] + +jobs: + claude-review: + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: read + issues: read + id-token: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Run Claude Code Review + id: claude-review + uses: anthropics/claude-code-action@v1 + with: + claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} + prompt: | + REPO: ${{ github.repository }} + PR NUMBER: ${{ github.event.pull_request.number }} + + Please review this pull request and think hard about correctness, + edge cases, and consistency with the rest of the codebase. Use the + repository's CLAUDE.md for guidance if available. + Focus on: + - Code quality and R package best practices + - Potential bugs or logic issues + - Test coverage + - Documentation (roxygen2) completeness + - Consistency with the existing codebase + claude_args: '--model claude-opus-4-7 --allowed-tools "mcp__github_inline_comment__create_inline_comment,Bash(gh issue view:*),Bash(gh search:*),Bash(gh issue list:*),Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*),Bash(gh pr list:*)"' diff --git a/inst/templates/ci_github-actions-claude/claude.yaml b/inst/templates/ci_github-actions-claude/claude.yaml new file mode 100644 index 0000000..49f70bf --- /dev/null +++ b/inst/templates/ci_github-actions-claude/claude.yaml @@ -0,0 +1,38 @@ +name: Claude Code + +on: + issue_comment: + types: [created] + pull_request_review_comment: + types: [created] + issues: + types: [opened, assigned] + pull_request_review: + types: [submitted] + +jobs: + claude: + if: | + (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) || + (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) || + (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) || + (github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude'))) + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: read + issues: read + id-token: write + actions: read + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Run Claude Code + id: claude + uses: anthropics/claude-code-action@v1 + with: + claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} + claude_args: '--model claude-opus-4-7' diff --git a/inst/templates/ci_github-actions/R-CMD-check.yaml b/inst/templates/ci_github-actions/R-CMD-check.yaml index 20bd49a..9f9476b 100644 --- a/inst/templates/ci_github-actions/R-CMD-check.yaml +++ b/inst/templates/ci_github-actions/R-CMD-check.yaml @@ -22,62 +22,33 @@ jobs: fail-fast: false matrix: config: - - {os: macOS-latest, r: 'release'} - - {os: ubuntu-20.04, r: 'release', rspm: "https://packagemanager.rstudio.com/cran/__linux__/focal/latest"} + - {os: macos-latest, r: 'release'} + - {os: ubuntu-latest, r: 'release'} - {os: windows-latest, r: 'devel'} - - {os: windows-latest, r: 'oldrel'} + - {os: windows-latest, r: 'oldrel-1'} - {os: windows-latest, r: 'release'} env: GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} - R_REMOTES_NO_ERRORS_FROM_WARNINGS: true - RSPM: ${{ matrix.config.rspm }} + R_KEEP_PKG_SOURCE: yes steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 + + - uses: r-lib/actions/setup-pandoc@v2 - uses: r-lib/actions/setup-r@v2 with: r-version: ${{ matrix.config.r }} + http-user-agent: ${{ matrix.config.http-user-agent }} use-public-rspm: true - - uses: r-lib/actions/setup-pandoc@v2 - - - name: Query dependencies - run: | - install.packages('remotes') - saveRDS(remotes::dev_package_deps(dependencies = TRUE), "depends.Rds", version = 2) - shell: Rscript {0} - - - name: Cache R packages - if: runner.os != 'Windows' - uses: actions/cache@v1 + - uses: r-lib/actions/setup-r-dependencies@v2 with: - path: ${{ env.R_LIBS_USER }} - key: ${{ runner.os }}-r-${{ matrix.config.r }}-3-${{ hashFiles('depends.Rds') }} - restore-keys: ${{ runner.os }}-r-${{ matrix.config.r }}-3- - - - name: Install system dependencies - if: runner.os == 'Linux' - env: - RHUB_PLATFORM: linux-x86_64-ubuntu-gcc - run: | - Rscript -e "remotes::install_github('r-hub/sysreqs')" - sysreqs=$(Rscript -e "cat(sysreqs::sysreq_commands('DESCRIPTION'))") - sudo -s eval "$sysreqs" - - name: Install dependencies - run: | - remotes::install_deps(dependencies = TRUE) - remotes::install_cran("rcmdcheck") - shell: Rscript {0} - - - name: Check - run: rcmdcheck::rcmdcheck(args = "--no-manual", error_on = "warning", check_dir = "check") - shell: Rscript {0} + extra-packages: any::rcmdcheck + needs: check - - name: Upload check results - if: failure() - uses: actions/upload-artifact@main + - uses: r-lib/actions/check-r-package@v2 with: - name: ${{ runner.os }}-r${{ matrix.config.r }}-results - path: check + upload-snapshots: true + args: 'c("--no-manual", "--as-cran")' diff --git a/inst/templates/ci_github-actions/pkgdown.yaml b/inst/templates/ci_github-actions/pkgdown.yaml index ac3ba07..2d703f6 100644 --- a/inst/templates/ci_github-actions/pkgdown.yaml +++ b/inst/templates/ci_github-actions/pkgdown.yaml @@ -4,50 +4,49 @@ on: - main - master - dev + pull_request: + branches: + - main + - master + - dev + release: + types: [published] + workflow_dispatch: name: pkgdown jobs: pkgdown: - runs-on: windows-latest + runs-on: ubuntu-latest + # Only restrict concurrency for non-PR jobs + concurrency: + group: pkgdown-${{ github.event_name != 'pull_request' || github.run_id }} env: - CURL_SSL_BACKEND: "openssl" GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} + permissions: + contents: write steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 + + - uses: r-lib/actions/setup-pandoc@v2 - uses: r-lib/actions/setup-r@v2 with: use-public-rspm: true - - uses: r-lib/actions/setup-pandoc@v2 - - - name: Query dependencies - run: | - install.packages('remotes') - saveRDS(remotes::dev_package_deps(dependencies = TRUE), ".github/depends.Rds", version = 2) - writeLines(sprintf("R-%i.%i", getRversion()$major, getRversion()$minor), ".github/R-version") - shell: Rscript {0} - - - name: Cache R packages - uses: actions/cache@v2 + - uses: r-lib/actions/setup-r-dependencies@v2 with: - path: ${{ env.R_LIBS_USER }} - key: ${{ runner.os }}-${{ hashFiles('.github/R-version') }}-1-${{ hashFiles('.github/depends.Rds') }} - restore-keys: ${{ runner.os }}-${{ hashFiles('.github/R-version') }}-1- + extra-packages: any::pkgdown, local::. + needs: website - - name: Install dependencies - run: | - remotes::install_deps(dependencies = TRUE) - install.packages("pkgdown", type = "binary") + - name: Build site + run: pkgdown::build_site_github_pages(new_process = FALSE, install = FALSE) shell: Rscript {0} - - name: Install package - run: R CMD INSTALL . - shell: cmd - - - name: Deploy package - run: | - git config --local user.email "actions@github.com" - git config --local user.name "GitHub Actions" - Rscript -e 'pkgdown::deploy_to_branch(new_process = FALSE)' + - name: Deploy to GitHub pages + if: github.event_name != 'pull_request' + uses: JamesIves/github-pages-deploy-action@v4.5.0 + with: + clean: false + branch: gh-pages + folder: docs diff --git a/inst/templates/ci_github-actions/pr-commands.yaml b/inst/templates/ci_github-actions/pr-commands.yaml index 0d3cb71..33cfb6c 100644 --- a/inst/templates/ci_github-actions/pr-commands.yaml +++ b/inst/templates/ci_github-actions/pr-commands.yaml @@ -1,51 +1,85 @@ on: issue_comment: types: [created] + name: Commands + jobs: document: - if: startsWith(github.event.comment.body, '/document') + if: ${{ github.event.issue.pull_request && startsWith(github.event.comment.body, '/document') }} name: document - runs-on: macOS-latest + runs-on: ubuntu-latest env: GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} + permissions: + contents: write + pull-requests: write steps: - - uses: actions/checkout@v2 - - uses: r-lib/actions/pr-fetch@master + - uses: actions/checkout@v4 + + - uses: r-lib/actions/pr-fetch@v2 with: repo-token: ${{ secrets.GITHUB_TOKEN }} - - uses: r-lib/actions/setup-r@master - - name: Install dependencies - run: Rscript -e 'install.packages(c("remotes", "roxygen2"))' -e 'remotes::install_deps(dependencies = TRUE)' + + - uses: r-lib/actions/setup-r@v2 + with: + use-public-rspm: true + + - uses: r-lib/actions/setup-r-dependencies@v2 + with: + extra-packages: any::roxygen2 + needs: pr-document + - name: Document - run: Rscript -e 'roxygen2::roxygenise()' + run: roxygen2::roxygenise() + shell: Rscript {0} + - name: commit run: | + git config --local user.name "$GITHUB_ACTOR" + git config --local user.email "$GITHUB_ACTOR@users.noreply.github.com" git add man/\* NAMESPACE git commit -m 'Document' - - uses: r-lib/actions/pr-push@master + + - uses: r-lib/actions/pr-push@v2 with: repo-token: ${{ secrets.GITHUB_TOKEN }} + style: - if: startsWith(github.event.comment.body, '/style') + if: ${{ github.event.issue.pull_request && startsWith(github.event.comment.body, '/style') }} name: style - runs-on: macOS-latest + runs-on: ubuntu-latest env: GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} + permissions: + contents: write + pull-requests: write steps: - - uses: actions/checkout@v2 - - uses: r-lib/actions/pr-fetch@master + - uses: actions/checkout@v4 + + - uses: r-lib/actions/pr-fetch@v2 with: repo-token: ${{ secrets.GITHUB_TOKEN }} - - uses: r-lib/actions/setup-r@master + + - uses: r-lib/actions/setup-r@v2 + with: + use-public-rspm: true + - name: Install dependencies - run: Rscript -e 'install.packages("styler")' + run: install.packages("styler") + shell: Rscript {0} + - name: Style - run: Rscript -e 'styler::style_pkg()' + run: styler::style_pkg() + shell: Rscript {0} + - name: commit run: | + git config --local user.name "$GITHUB_ACTOR" + git config --local user.email "$GITHUB_ACTOR@users.noreply.github.com" git add \*.R git commit -m 'Style' - - uses: r-lib/actions/pr-push@master + + - uses: r-lib/actions/pr-push@v2 with: repo-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/inst/templates/ci_github-actions/test-coverage.yaml b/inst/templates/ci_github-actions/test-coverage.yaml index 0f014df..254c6db 100644 --- a/inst/templates/ci_github-actions/test-coverage.yaml +++ b/inst/templates/ci_github-actions/test-coverage.yaml @@ -12,37 +12,50 @@ name: test-coverage jobs: test-coverage: - runs-on: windows-latest + runs-on: ubuntu-latest env: GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} + steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - uses: r-lib/actions/setup-r@v2 + with: + use-public-rspm: true - - uses: r-lib/actions/setup-pandoc@v2 + - uses: r-lib/actions/setup-r-dependencies@v2 + with: + extra-packages: any::covr, any::xml2 + needs: coverage - - name: Query dependencies + - name: Test coverage run: | - install.packages('remotes') - saveRDS(remotes::dev_package_deps(dependencies = TRUE), ".github/depends.Rds", version = 2) - writeLines(sprintf("R-%i.%i", getRversion()$major, getRversion()$minor), ".github/R-version") + cov <- covr::package_coverage( + quiet = FALSE, + clean = FALSE, + install_path = file.path(normalizePath(Sys.getenv("RUNNER_TEMP"), winslash = "/"), "package") + ) + covr::to_cobertura(cov) shell: Rscript {0} - - name: Cache R packages - uses: actions/cache@v1 + - uses: codecov/codecov-action@v4 with: - path: ${{ env.R_LIBS_USER }} - key: ${{ runner.os }}-${{ hashFiles('.github/R-version') }}-1-${{ hashFiles('.github/depends.Rds') }} - restore-keys: ${{ runner.os }}-${{ hashFiles('.github/R-version') }}-1- - - - name: Install dependencies + fail_ci_if_error: ${{ github.event_name != 'pull_request' && true || false }} + file: ./cobertura.xml + plugin: noop + disable_search: true + token: ${{ secrets.CODECOV_TOKEN }} + + - name: Show testthat output + if: always() run: | - install.packages(c("remotes")) - remotes::install_deps(dependencies = TRUE) - remotes::install_cran("covr") - shell: Rscript {0} + ## -------------------------------------------------------------------- + find '${{ runner.temp }}/package' -name 'testthat.Rout*' -exec cat '{}' \; || true + shell: bash - - name: Test coverage - run: covr::codecov() - shell: Rscript {0} + - name: Upload test results + if: failure() + uses: actions/upload-artifact@v4 + with: + name: coverage-test-failures + path: ${{ runner.temp }}/package diff --git a/man/get_pkgname.Rd b/man/get_pkgname.Rd index 12f9da5..d1b5568 100644 --- a/man/get_pkgname.Rd +++ b/man/get_pkgname.Rd @@ -7,9 +7,9 @@ get_pkgname(pkgname = NULL) } \arguments{ -\item{pkgname}{either package name or NULL. In this -case the DESCRIPTION file in the current working -directory is read and is package name ues (default: NULL)} +\item{pkgname}{either package name or NULL. In the latter case the +DESCRIPTION file in the current working directory is read and the +package name from there is used (default: NULL)} } \value{ package name diff --git a/man/github_push.Rd b/man/github_push.Rd deleted file mode 100644 index f148a96..0000000 --- a/man/github_push.Rd +++ /dev/null @@ -1,12 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/deploy_site_github_with_extra_files.R -\name{github_push} -\alias{github_push} -\title{github_push} -\usage{ -github_push(dir, commit_message, remote, branch) -} -\description{ -github_push -} -\keyword{internal} diff --git a/man/kwb_author.Rd b/man/kwb_author.Rd deleted file mode 100644 index 0ae53aa..0000000 --- a/man/kwb_author.Rd +++ /dev/null @@ -1,12 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/helper.R -\name{kwb_author} -\alias{kwb_author} -\title{Get Information About KWB Author} -\usage{ -kwb_author(who) -} -\description{ -Get Information About KWB Author -} -\keyword{internal} diff --git a/man/kwb_package.Rd b/man/kwb_package.Rd deleted file mode 100644 index 9e8520c..0000000 --- a/man/kwb_package.Rd +++ /dev/null @@ -1,12 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/helper.R -\name{kwb_package} -\alias{kwb_package} -\title{Get (Default) Information About KWB-R Package} -\usage{ -kwb_package(pkg) -} -\description{ -Get (Default) Information About KWB-R Package -} -\keyword{internal} diff --git a/man/read_description.Rd b/man/read_description.Rd deleted file mode 100644 index 81a2585..0000000 --- a/man/read_description.Rd +++ /dev/null @@ -1,17 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/read_description.R -\name{read_description} -\alias{read_description} -\title{Helper function: read_description} -\usage{ -read_description(file = "DESCRIPTION") -} -\arguments{ -\item{file}{path to DESCRIPTION file (default: DESCRIPTION)} -} -\value{ -list with pkg "name", "title", "desc", "version" -} -\description{ -Helper function: read_description -} diff --git a/man/use_badge_appveyor.Rd b/man/use_badge_appveyor.Rd index 0aebcf9..ada8c9d 100644 --- a/man/use_badge_appveyor.Rd +++ b/man/use_badge_appveyor.Rd @@ -10,7 +10,7 @@ use_badge_appveyor(repo = NULL, user = "KWB-R", domain = "github") \item{repo}{name of repository (default: NULL)} \item{user}{user name or organisation under which repository defined in -parameter "repo" is hosted (default: KWB-R")} +parameter "repo" is hosted (default: "KWB-R")} \item{domain}{under which repository is hosted (default: "github")} } diff --git a/man/use_badge_codecov.Rd b/man/use_badge_codecov.Rd index 9a24ff8..03123f7 100644 --- a/man/use_badge_codecov.Rd +++ b/man/use_badge_codecov.Rd @@ -10,7 +10,7 @@ use_badge_codecov(repo = NULL, user = "KWB-R", domain = "github") \item{repo}{name of repository (default: NULL)} \item{user}{user name or organisation under which repository defined in -parameter "repo" is hosted (default: KWB-R")} +parameter "repo" is hosted (default: "KWB-R")} \item{domain}{under which repository is hosted (default: "github")} } diff --git a/man/use_badge_ghactions.Rd b/man/use_badge_ghactions.Rd index 52e5d21..364d76d 100644 --- a/man/use_badge_ghactions.Rd +++ b/man/use_badge_ghactions.Rd @@ -10,7 +10,7 @@ use_badge_ghactions(repo = NULL, user = "KWB-R", branch = NULL) \item{repo}{name of repository (default: NULL)} \item{user}{user name or organisation under which repository defined in -parameter "repo" is hosted (default: KWB-R")} +parameter "repo" is hosted (default: "KWB-R")} \item{branch}{default: NULL (i.e. use "default" branch) or user defined branch (e.g. "dev")} diff --git a/man/use_badge_ghactions_pkgdown.Rd b/man/use_badge_ghactions_pkgdown.Rd index 9cce992..a6b8146 100644 --- a/man/use_badge_ghactions_pkgdown.Rd +++ b/man/use_badge_ghactions_pkgdown.Rd @@ -10,7 +10,7 @@ use_badge_ghactions_pkgdown(repo = NULL, user = "KWB-R", branch = NULL) \item{repo}{name of repository (default: NULL)} \item{user}{user name or organisation under which repository defined in -parameter "repo" is hosted (default: KWB-R")} +parameter "repo" is hosted (default: "KWB-R")} \item{branch}{default: NULL (i.e. use "default" branch) or user defined branch (e.g. "dev")} diff --git a/man/use_badge_ghactions_rcmdcheck.Rd b/man/use_badge_ghactions_rcmdcheck.Rd index 1213649..e845a37 100644 --- a/man/use_badge_ghactions_rcmdcheck.Rd +++ b/man/use_badge_ghactions_rcmdcheck.Rd @@ -10,13 +10,13 @@ use_badge_ghactions_rcmdcheck(repo = NULL, user = "KWB-R", branch = NULL) \item{repo}{name of repository (default: NULL)} \item{user}{user name or organisation under which repository defined in -parameter "repo" is hosted (default: KWB-R")} +parameter "repo" is hosted (default: "KWB-R")} \item{branch}{default: NULL (i.e. use "default" branch) or user defined branch (e.g. "dev")} } \value{ -generates travis badge link +generates GitHub Actions R-CMD-check badge markdown } \description{ Badge Github Actions RCMD Check diff --git a/man/use_badge_runiverse.Rd b/man/use_badge_runiverse.Rd index 7c2b382..d8bae9f 100644 --- a/man/use_badge_runiverse.Rd +++ b/man/use_badge_runiverse.Rd @@ -10,10 +10,10 @@ use_badge_runiverse(repo = NULL, user = "KWB-R") \item{repo}{name of repository (default: NULL)} \item{user}{user name or organisation under which repository defined in -parameter "repo" is hosted (default: KWB-R")} +parameter "repo" is hosted (default: "KWB-R")} } \value{ -generates codecov badge link +generates R-Universe badge markdown } \description{ Badge R-Universe diff --git a/man/use_badge_travis.Rd b/man/use_badge_travis.Rd index 15b7843..e68027d 100644 --- a/man/use_badge_travis.Rd +++ b/man/use_badge_travis.Rd @@ -10,7 +10,7 @@ use_badge_travis(repo = NULL, user = "KWB-R") \item{repo}{name of repository (default: NULL)} \item{user}{user name or organisation under which repository defined in -parameter "repo" is hosted (default: KWB-R")} +parameter "repo" is hosted (default: "KWB-R")} } \value{ generates travis badge link diff --git a/man/use_ghactions.Rd b/man/use_ghactions.Rd index c1930a8..5d047b9 100644 --- a/man/use_ghactions.Rd +++ b/man/use_ghactions.Rd @@ -4,11 +4,20 @@ \alias{use_ghactions} \title{Adds default .github/workflows/} \usage{ -use_ghactions() +use_ghactions(claude = FALSE) +} +\arguments{ +\item{claude}{if \code{TRUE}, additionally copies the Claude Code workflows +(\code{claude.yaml} and \code{claude-code-review.yaml}) into \code{.github/workflows/}. +Defaults to \code{FALSE}.} } \value{ -writes .github/workflows/ and adds it .Rbuildignore +writes \code{.github/workflows/} and adds it to \code{.Rbuildignore} } \description{ -Adds default .github/workflows/ +Copies the default GitHub Actions workflows (R-CMD-check, pkgdown, +pr-commands, test-coverage) into \code{.github/workflows/}. Optionally adds the +Claude Code workflows (\code{claude.yaml}, \code{claude-code-review.yaml}) when +\code{claude = TRUE}. Those workflows require a \code{CLAUDE_CODE_OAUTH_TOKEN} +repository secret to be configured in GitHub. } diff --git a/man/use_ghactions_claude.Rd b/man/use_ghactions_claude.Rd new file mode 100644 index 0000000..aa473c3 --- /dev/null +++ b/man/use_ghactions_claude.Rd @@ -0,0 +1,16 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/use_github_ci.R +\name{use_ghactions_claude} +\alias{use_ghactions_claude} +\title{Adds Claude Code workflows to .github/workflows/} +\usage{ +use_ghactions_claude() +} +\value{ +writes Claude workflow YAML files into \code{.github/workflows/} +} +\description{ +Copies the Claude Code workflows (\code{claude.yaml} and \code{claude-code-review.yaml}) +into \code{.github/workflows/}. Requires a \code{CLAUDE_CODE_OAUTH_TOKEN} repository +secret to be configured in GitHub for the workflows to function. +} diff --git a/man/use_gitlab_ci_blogdown.Rd b/man/use_gitlab_ci_blogdown.Rd index 086d596..76145ee 100644 --- a/man/use_gitlab_ci_blogdown.Rd +++ b/man/use_gitlab_ci_blogdown.Rd @@ -10,7 +10,7 @@ use_gitlab_ci_blogdown( ) } \arguments{ -\item{dest_dir}{directoy to write (default: getwd())} +\item{dest_dir}{directory to write (default: getwd())} \item{yml_vector}{a yml imported as string vector (default: gitlab_ci_template_blogdown())} diff --git a/man/use_gitlab_ci_docs.Rd b/man/use_gitlab_ci_docs.Rd index 79deacc..8a577d3 100644 --- a/man/use_gitlab_ci_docs.Rd +++ b/man/use_gitlab_ci_docs.Rd @@ -7,7 +7,7 @@ use_gitlab_ci_docs(dest_dir = getwd(), yml_vector = gitlab_ci_template_docs()) } \arguments{ -\item{dest_dir}{directoy to write (default: getwd())} +\item{dest_dir}{directory to write (default: getwd())} \item{yml_vector}{a yml imported as string vector (default: gitlab_ci_template_docs())} diff --git a/man/use_gitlab_ci_ghpages.Rd b/man/use_gitlab_ci_ghpages.Rd index 94d90c1..e453184 100644 --- a/man/use_gitlab_ci_ghpages.Rd +++ b/man/use_gitlab_ci_ghpages.Rd @@ -10,7 +10,7 @@ use_gitlab_ci_ghpages( ) } \arguments{ -\item{dest_dir}{directoy to write (default: +\item{dest_dir}{directory to write (default: getwd())} \item{yml_vector}{a yml imported as string vector (default: gitlab_ci_template_ghpages())} diff --git a/man/use_gitlab_ci_pkgdown.Rd b/man/use_gitlab_ci_pkgdown.Rd index 02c7ba9..08db798 100644 --- a/man/use_gitlab_ci_pkgdown.Rd +++ b/man/use_gitlab_ci_pkgdown.Rd @@ -10,7 +10,7 @@ use_gitlab_ci_pkgdown( ) } \arguments{ -\item{dest_dir}{directoy to write (default: getwd())} +\item{dest_dir}{directory to write (default: getwd())} \item{yml_vector}{a yml imported as string vector (default: gitlab_ci_template_pkgdown(), where "/" is replaced with value diff --git a/man/use_index_md.Rd b/man/use_index_md.Rd index 082a3da..43246a3 100644 --- a/man/use_index_md.Rd +++ b/man/use_index_md.Rd @@ -2,24 +2,29 @@ % Please edit documentation in R/use_index_md.R \name{use_index_md} \alias{use_index_md} -\title{Use index.md (used for pkgdown::build_home())} +\title{Create KWB-styled \code{index.md}} \usage{ use_index_md(user = "KWB-R", domain = "github", stage = "experimental") } \arguments{ -\item{user}{user name or organisation under which repository defined in\cr -parameter "repo" is hosted (default: "KWB-R")\cr} +\item{user}{user name or organisation under which the repository is hosted +(default: "KWB-R")} -\item{domain}{under which repository is hosted (default: "github")} +\item{domain}{under which the repository is hosted (default: "github")} -\item{stage}{badge declares the developmental stage of a package, according -to [https://www.tidyverse.org/lifecycle/](https://www.tidyverse.org/lifecycle/), -valid arguments are: "experimental", "maturing", "stable", "retired", -"archived", "dormant", "questioning"), (default: "experiment")} +\item{stage}{badge declaring the developmental stage of the package +according to +\url{https://www.tidyverse.org/lifecycle/}; +valid values are "experimental", "maturing", "stable", "retired", +"archived", "dormant", "questioning" (default: "experimental")} } \value{ -generates travis badge link +writes \code{index.md} (used as \code{pkgdown} home) and adds the +pattern to \code{.Rbuildignore}. Invisibly returns the character vector +that was written. } \description{ -Use index.md (used for pkgdown::build_home()) +Generates an \code{index.md} (used by \code{pkgdown::build_home()}) with the +KWB default badge set (GitHub Actions, codecov, lifecycle, CRAN, R-universe), +the package description from \code{DESCRIPTION} and an installation snippet. } diff --git a/man/use_installation.Rd b/man/use_installation.Rd deleted file mode 100644 index f797ba0..0000000 --- a/man/use_installation.Rd +++ /dev/null @@ -1,19 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/use_installation.R -\name{use_installation} -\alias{use_installation} -\title{Helper function: describe pkg installation in index.Rmd / README.md} -\usage{ -use_installation(pkgname, user, domain) -} -\arguments{ -\item{pkgname}{package name} - -\item{user}{user name or organisation under which repository defined in -parameter "repo" is hosted} - -\item{domain}{under which repository is hosted} -} -\description{ -Helper function: describe pkg installation in index.Rmd / README.md -} diff --git a/man/use_pkg.Rd b/man/use_pkg.Rd index ac44d3b..263259d 100644 --- a/man/use_pkg.Rd +++ b/man/use_pkg.Rd @@ -15,6 +15,7 @@ use_pkg( domain = "github", stage = "experimental", auto_build_pkgdown = FALSE, + claude = FALSE, dbg = TRUE, ... ) @@ -54,9 +55,13 @@ to \item{auto_build_pkgdown}{prepare Travis for pkgdown::build_site() (default: FALSE), only possible if GITHUB repo already existing} +\item{claude}{if TRUE, additionally adds the Claude Code GitHub Actions +workflows (claude.yaml, claude-code-review.yaml). Requires the repository +secret CLAUDE_CODE_OAUTH_TOKEN to be configured in GitHub. (default: FALSE)} + \item{dbg}{print debug messages (default: TRUE)} -\item{...}{additional arguments passed to use_autopkgdown() (only releveant +\item{...}{additional arguments passed to use_autopkgdown() (only relevant if "auto_build_pkgdown" == TRUE)} } \value{ diff --git a/man/use_pkgdown.Rd b/man/use_pkgdown.Rd index 9ab4c1b..4f8981b 100644 --- a/man/use_pkgdown.Rd +++ b/man/use_pkgdown.Rd @@ -2,14 +2,16 @@ % Please edit documentation in R/use_pkgdown.R \name{use_pkgdown} \alias{use_pkgdown} -\title{pkgdown for KWB} +\title{Set up \code{pkgdown} with KWB styling} \usage{ use_pkgdown( author = kwb_author("rustler"), copyright_holder_name = kwb_string(), pkg = get_pkgname(), user = "kwb-r", - domain = "github" + domain = "github", + kwb_logo_url = "https://logos.kompetenz-wasser.io/KWB_Logo_M_Blau_RGB.svg", + kwb_logo_href = "https://www.kompetenz-wasser.de" ) } \arguments{ @@ -24,11 +26,20 @@ kwb.pkgbuild:::kwb_author("rustler"))} \item{user}{name of GitHub user/organisation (default: 'kwb-r')} \item{domain}{name of domain for webpage publishing (default: 'github')} + +\item{kwb_logo_url}{URL of the KWB logo image embedded in the copyright +holder's \code{pkgdown} author block (default: +\code{"https://logos.kompetenz-wasser.io/KWB_Logo_M_Blau_RGB.svg"})} + +\item{kwb_logo_href}{URL the KWB logo links to (default: +\code{"https://www.kompetenz-wasser.de"})} } \value{ -performs usethis::use_pkgdown() and additionally writes _pkgdown.yml -based on KWB styling +invisibly; as a side effect writes \code{_pkgdown.yml} with KWB styling +and adds it to \code{.Rbuildignore}. } \description{ -pkgdown for KWB +Calls \code{usethis::use_pkgdown()} and additionally writes a \code{_pkgdown.yml} +configured with KWB defaults (Bootstrap 5 + cerulean theme, KWB authors +block, copyright holder logo). } diff --git a/man/use_readme_md.Rd b/man/use_readme_md.Rd index ee56a7a..a1aca0e 100644 --- a/man/use_readme_md.Rd +++ b/man/use_readme_md.Rd @@ -2,24 +2,27 @@ % Please edit documentation in R/use_readme_md.R \name{use_readme_md} \alias{use_readme_md} -\title{Use README} +\title{Create KWB-styled \code{README.md}} \usage{ use_readme_md(user = "KWB-R", domain = "github", stage = "experimental") } \arguments{ -\item{user}{user name or organisation under which repository defined in\cr -parameter "repo" is hosted (default: "KWB-R")\cr} +\item{user}{user name or organisation under which the repository is hosted +(default: "KWB-R")} -\item{domain}{under which repository is hosted (default: "github")} +\item{domain}{under which the repository is hosted (default: "github")} -\item{stage}{badge declares the developmental stage of a package, according -to [https://www.tidyverse.org/lifecycle/](https://www.tidyverse.org/lifecycle/), -valid arguments are: "experimental", "maturing", "stable", "retired", -"archived", "dormant", "questioning"), (default: "experiment")} +\item{stage}{badge declaring the developmental stage of the package +according to +\url{https://www.tidyverse.org/lifecycle/}; +valid values are "experimental", "maturing", "stable", "retired", +"archived", "dormant", "questioning" (default: "experimental")} } \value{ -generates README.md +writes \code{README.md} and adds it to \code{.Rbuildignore} } \description{ -Use README +Generates a \code{README.md} with the KWB default badge set, the package +description from \code{DESCRIPTION}, an installation snippet and links to the +release and development documentation websites. } diff --git a/tests/testthat/.gitignore b/tests/testthat/.gitignore new file mode 100644 index 0000000..9e5d89c --- /dev/null +++ b/tests/testthat/.gitignore @@ -0,0 +1,2 @@ +#ignore docs +docs diff --git a/tests/testthat/helper.R b/tests/testthat/helper.R index 8b0d8a2..710ede8 100644 --- a/tests/testthat/helper.R +++ b/tests/testthat/helper.R @@ -1,13 +1,23 @@ ## Helper for creating temporary R package -create_pkg_temp <- function() { -number <- sample(x = 1:10000000,size = 1) -root_dir <- paste0(stringr::str_replace_all(tempdir(),"\\\\", "/"), - sprintf("%d", number)) -pkg_name <- "testpkg" -pkg_dir <- file.path(root_dir, pkg_name) -kwb.pkgbuild::create_pkg_dir(pkg_dir) -withr::with_dir(pkg_dir, {kwb.pkgbuild::use_pkg_skeleton(pkg_name)}) -pkg_dir +create_pkg_temp <- function() +{ + number <- sample(x = 1:10000000, size = 1L) + + root_dir <- paste0( + kwb.utils::rStylePath(tempdir()), + sprintf("%d", number) + ) + + pkg_name <- "testpkg" + pkg_dir <- file.path(root_dir, pkg_name) + + kwb.pkgbuild::create_pkg_dir(pkg_dir) + + withr::with_dir(pkg_dir, { + kwb.pkgbuild::use_pkg_skeleton(pkg_name) + }) + + pkg_dir } # create_pkg_temp <- function(root_dir = tempdir()) { diff --git a/vignettes/github-actions.Rmd b/vignettes/github-actions.Rmd new file mode 100644 index 0000000..f5e34db --- /dev/null +++ b/vignettes/github-actions.Rmd @@ -0,0 +1,156 @@ +--- +title: "GitHub Actions for KWB-R Packages" +author: "Michael Rustler, Hauke Sonnenberg" +date: "`r Sys.Date()`" +output: rmarkdown::html_vignette +vignette: > + %\VignetteIndexEntry{GitHub Actions for KWB-R Packages} + %\VignetteEngine{knitr::rmarkdown} + %\VignetteEncoding{UTF-8} +--- + +```{r setup, include = FALSE} +knitr::opts_chunk$set( + collapse = TRUE, + fig_caption = FALSE, + comment = "#>", + eval = FALSE +) +``` + +`kwb.pkgbuild` ships a curated set of [GitHub +Actions](https://github.com/features/actions) workflows that we use across +all KWB-R packages. The workflows live as templates in +`inst/templates/ci_github-actions/` (default workflows) and +`inst/templates/ci_github-actions-claude/` (optional Claude Code +integration), and are installed into a package's `.github/workflows/` +directory by `use_ghactions()` / `use_pkg()`. + +This vignette explains what the workflows do, how to add or update them, and +how to enable the optional Claude Code integration. + +# 1 Default workflows + +`kwb.pkgbuild::use_ghactions()` (also called from `kwb.pkgbuild::use_pkg()`) +copies four workflows into `.github/workflows/`: + +| File | Purpose | +|:----------------------|:---------------------------------------------------------------------------------------------------------| +| `R-CMD-check.yaml` | Cross-platform `R CMD check` matrix (macOS, Ubuntu, Windows × `release`, `devel`, `oldrel-1`). | +| `pkgdown.yaml` | Builds the `pkgdown` site and deploys it to the `gh-pages` branch on push to `main`/`master`/`dev`. | +| `test-coverage.yaml` | Runs `covr::package_coverage()` and uploads the report to [codecov.io](https://codecov.io). | +| `pr-commands.yaml` | Reacts to `/document` and `/style` comments on pull requests by running `roxygen2` / `styler` for you. | + +All four use the official [`r-lib/actions`](https://github.com/r-lib/actions) +v2 building blocks, which means dependency installation and caching are +handled by `r-lib/actions/setup-r-dependencies@v2`. + +To install or refresh the default workflows in a package, run: + +```{r} +# from the package root +kwb.pkgbuild::use_ghactions() +``` + +# 2 Optional: Claude Code workflows + +If you want [Claude Code](https://docs.claude.com/en/docs/claude-code/) to +review your pull requests and answer `@claude` mentions in issues / PR +comments, set `claude = TRUE` when bootstrapping a new package: + +```{r} +kwb.pkgbuild::use_pkg( + author = author, + pkg = description, + version = "0.0.0.9000", + stage = "experimental", + claude = TRUE +) +``` + +Or add the workflows to an existing package after the fact: + +```{r} +kwb.pkgbuild::use_ghactions_claude() +``` + +This installs two additional workflow files: + +| File | Trigger | +|:---------------------------|:------------------------------------------------------------------------------------------------------------| +| `claude.yaml` | `issue_comment`, `pull_request_review_comment`, `pull_request_review`, `issues` — reacts to `@claude` mentions. | +| `claude-code-review.yaml` | `pull_request` (`opened`, `synchronize`) — automatic PR review. | + +## 2.1 Required repository secret + +Both workflows authenticate via a GitHub Actions secret named +`CLAUDE_CODE_OAUTH_TOKEN`. To create it: + +1. Open the repository on GitHub and go to + **Settings → Secrets and variables → Actions → New repository secret**. +2. Set **Name** to `CLAUDE_CODE_OAUTH_TOKEN` and paste your Claude Code OAuth + token as **Value**. + +Without this secret the Claude jobs will fail at the +`anthropics/claude-code-action@v1` step. + +## 2.2 What Claude does + +`claude.yaml` runs whenever someone mentions `@claude` in an issue body / +title, an issue comment, a PR review or a PR review comment. Claude then +opens a session with read access to issues / pull requests, plans, and +posts a reply. + +`claude-code-review.yaml` runs on every newly opened or updated pull +request. It reviews the diff with focus on: + +- code quality and R package best practices, +- potential bugs or logic issues, +- test coverage, +- `roxygen2` documentation completeness, +- consistency with the existing codebase. + +If your repository contains a `CLAUDE.md` file, Claude will use it as +project-specific guidance. + +# 3 Updating workflows in existing packages + +The workflows in `inst/templates/ci_github-actions/` are the source of truth +for KWB-R packages. When upstream actions are updated (e.g. a new version of +`r-lib/actions`) we bump the templates here in `kwb.pkgbuild` and re-run +`kwb.pkgbuild::use_ghactions()` in each package to refresh the local +workflows. + +To check whether a package is using an outdated version, simply diff its +`.github/workflows/` against the templates in the latest installed +`kwb.pkgbuild`: + +```{r} +template_dir <- system.file( + "templates/ci_github-actions", + package = "kwb.pkgbuild" +) + +# from the package root +list.files(".github/workflows", full.names = TRUE) +list.files(template_dir, full.names = TRUE) +``` + +# 4 Troubleshooting + +- **The pkgdown workflow doesn't deploy.** Make sure the `gh-pages` branch + exists and that **Settings → Pages → Source** points to it. The first + time a package is set up, run `kwb.pkgbuild::use_autopkgdown()` to create + the orphan branch. + +- **`R CMD check` fails on `oldrel-1`.** Either pin a `Depends: R (>= ...)` + in `DESCRIPTION` to the lowest R version you actually want to support, or + remove `oldrel-1` from the matrix in `R-CMD-check.yaml`. + +- **Claude jobs are skipped.** The `if:` clause requires `@claude` to appear + in the comment / review / issue body. The Claude review workflow only + runs on `pull_request`, not on `issue_comment` events. + +- **Codecov upload fails on forks.** Configure the `CODECOV_TOKEN` + repository secret. The workflow has `fail_ci_if_error` set conditionally + so PRs from forks won't break the build. diff --git a/vignettes/tutorial.Rmd b/vignettes/tutorial.Rmd index 02bddeb..6fcf321 100644 --- a/vignettes/tutorial.Rmd +++ b/vignettes/tutorial.Rmd @@ -36,11 +36,11 @@ building and testing of the package. # 1 Install required R packages -Before you can use the package kwb.pkgdown you need to +Before you can use the package `kwb.pkgbuild` you need to -* install packages that kwb.pkgdown relies on from the -[Comprehensive R Archive Network (CRAN)](https://cloud.r-project.org), -* install kwb.pkddown from [GitHub](https://github.com). +* install the packages that `kwb.pkgbuild` relies on from the + [Comprehensive R Archive Network (CRAN)](https://cloud.r-project.org), +* install `kwb.pkgbuild` from [GitHub](https://github.com). Install required packages that are not yet available in your local R user library from CRAN: @@ -59,7 +59,6 @@ packages <- setdiff(required, installed) # Install the packages from CRAN for (package in packages) { - install.packages(package, repos = "https://cloud.r-project.org") } ``` @@ -192,10 +191,10 @@ The package description needs three entries - **name**: name of the package - **title**: title of your R package (which is automatically converted to title -case with the function `tools::toTitleCase()`) + case with the function `tools::toTitleCase()`) -- **desc:** package description. Should be at least one sentence long and -needs to end with a period! +- **desc:** package description. Should be at least one sentence long and needs + to end with a period! ```{r} description <- list( @@ -205,50 +204,73 @@ description <- list( ) ``` -## 2.4 Create R package structure in KWB-R style +## 2.4 Create R package structure in KWB-R style -Running the following code not only creates an R package structure but also adds -some KWB-R specfic styling, e.g.: +Running the following code not only creates an R package structure but also +adds some KWB-R specific styling, e.g.: -- Adding configution files for: - - + Continous integration on windows (https://appveyor.com) and linux - (https://travis-ci.org/KWB-R) - - + Code coverage in R package using the servive [codecov.io](codecov.io) - - + Backup of Github repositories on our mirrored KWB-R group on - [Gitlab](https://gitlab.com/KWB-R) +- Configuration files for: + + + Continuous integration via + [GitHub Actions](https://github.com/features/actions): the workflows + `R-CMD-check`, `pkgdown`, `pr-commands` and `test-coverage` are written + to `.github/workflows/` (see also the GitHub Actions vignette + `vignette("github-actions", package = "kwb.pkgbuild")`). + + + Code coverage via [codecov.io](https://codecov.io) + + + Backup of GitHub repositories on our mirrored KWB-R group on + [GitLab](https://gitlab.com/KWB-R) - Indicates the current lifecycle of the R package according to -https://www.tidyverse.org/lifecycle/ + https://www.tidyverse.org/lifecycle/ - Uses by default the permissive -[![MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) -for all public R packages currently hosted on Github (see: -http://kwb-r.github.io/status/) and lists KWB as copyright holder (see e.g. -[here](https://kwb-r.github.io/kwb.pkgbuild/authors.html)) + [![MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) + for all public R packages currently hosted on GitHub (see: + https://kwb-r.github.io/status/) and lists KWB as copyright holder (see e.g. + [here](https://kwb-r.github.io/kwb.pkgbuild/authors.html)) -- Creates `README` files (`README.Rmd` and `README.md`) for the above mentioned -topics and +- Creates `README.md` and `index.md` files for the above mentioned topics, and -- Prepares a KWB-R flavored documentation website template named -`_packagedown.yml` needed by http://pkgdown.r-lib.org/ +- Prepares a KWB-R flavoured documentation website template named + `_pkgdown.yml` (Bootstrap 5, KWB authors block) needed by + https://pkgdown.r-lib.org/ -Running the following R function will create the R package with the `version` = -`0.0.0.9000` and development stage `experimental` (defined [here](https://www.tidyverse.org/lifecycle/#experimental)). +Running the following R function will create the R package with `version = +"0.0.0.9000"` and development stage `experimental` (defined +[here](https://www.tidyverse.org/lifecycle/#experimental)). ```{r eval = FALSE} setwd(pkg_dir) kwb.pkgbuild::use_pkg( - author, - description, - version = "0.0.0.9000", + author, + description, + version = "0.0.0.9000", stage = "experimental" ) ``` +If you also want the [Claude Code](https://docs.claude.com/en/docs/claude-code/) +GitHub Actions workflows (for `@claude` mentions in issues / PR review +comments and automatic PR review on `opened` / `synchronize`), set +`claude = TRUE`: + +```{r eval = FALSE} +kwb.pkgbuild::use_pkg( + author, + description, + version = "0.0.0.9000", + stage = "experimental", + claude = TRUE +) +``` + +The two extra workflows expect a `CLAUDE_CODE_OAUTH_TOKEN` repository secret +to be configured in GitHub. You can also add the Claude workflows to an +existing package later on with `kwb.pkgbuild::use_ghactions_claude()`. + ## 2.5 Add your R functions Add your R functions in the folder `R`/. By using `usethis::use_r` with the @@ -301,7 +323,7 @@ possible problems (e.g. wrong documentation, missing package dependencies). In case of missing package dependencies as shown below these should be added to the [DESCRIPTION](http://r-pkgs.had.co.nz/description.html) file. -```{r eval = FALSE} +``` Namespace dependencies not required: 'fs' 'httr' 'stringr' 'usethis' 'yaml' See section 'The DESCRIPTION file' in the 'Writing R Extensions' manual. @@ -318,7 +340,7 @@ Namespace dependencies not required: 'fs' 'httr' 'stringr' 'usethis' 'yaml' This can be done using the function `usethis::use_package()` as shown below: -```{r eval = FALSE} +``` pkg_dependencies <- c('fs', 'httr', 'stringr', 'usethis', 'yaml') sapply(pkg_dependencies, usethis::use_package) @@ -338,7 +360,7 @@ sapply(pkg_dependencies, usethis::use_package) Subsequently you should re-click on the `Check` button again and it should finish without errors. -```{r eval = FALSE} +``` R CMD check results 0 errors | 0 warning | 0 note @@ -351,7 +373,7 @@ Now you are ready for building your R package by clicking on the `Install and Restart` button. A successful installation should finish with `Done` as shown below: -```{r eval = FALSE} +``` ** building package indices ** installing vignettes ** testing if installed package can be loaded @@ -385,29 +407,28 @@ knitr::include_graphics("images/package_documentation.jpg") ## 5.2 Automatically -In case you have already a Github repo defined for your R package you can -also automate the process of updating the `pkgdown::build_site()` by with the -wrapper function `kwb.pkgbuild::use_autopkgdown()`, which: - -- creates a new branch "gh-pages" where the documentation site is deployed +If you already have a GitHub repository for your R package you can let the +`pkgdown` workflow build and deploy the documentation site for you on every +push. `kwb.pkgbuild::use_pkg()` already installs the workflow +`.github/workflows/pkgdown.yaml`; the only one-off step that is left to do +is creating the `gh-pages` branch and setting it as the GitHub Pages source. -- sets "docs" folder to ".gitignore" (as these files are now build on Travis) +The wrapper function `kwb.pkgbuild::use_autopkgdown()` does exactly that: -- prepares Github Actions by: +- adds the `docs/` folder to `.gitignore` (since the site is built on + GitHub Actions and pushed to the `gh-pages` branch), - - + creating ".github/workflows/" with `kwb.pkgbuild::use_ghactions()` - - + creating an empty "gh-pages" branch with - `kwb.pkgbuild::create_empty_branch_ghpages()` +- creates an empty `gh-pages` branch via + `kwb.pkgbuild::create_empty_branch_ghpages()`. ```{r eval = FALSE} kwb.pkgbuild::use_autopkgdown() ``` -Finally to need to go to: +Finally, go to -https://github.com/KWB-R/ `kwb.mycoolrpackage` /settings +`https://github.com/KWB-R//settings/pages` -and set the "source" for Github Pages to the branch "gh-pages". After each -successful Travis build the documentation website is now also updated! +and set the GitHub Pages **Source** to the branch `gh-pages`. After every +successful run of the `pkgdown` workflow the documentation website is now +updated automatically.