├── .Rbuildignore ├── .github ├── CODEOWNERS ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE │ ├── bug.yml │ ├── config.yml │ ├── feature.yml │ └── question.yml ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── audit.yaml │ ├── bioccheck.yaml │ ├── branch-cleanup.yaml │ ├── build-check-install.yaml │ ├── check.yaml.shared │ ├── cla.yaml │ ├── cran-status.yaml │ ├── docs.yaml.shared │ ├── gitleaks.yaml │ ├── grammar.yaml │ ├── licenses.yaml │ ├── links.yaml │ ├── linter.yaml │ ├── pkgdown.yaml │ ├── post-release.yaml.shared │ ├── release.yaml │ ├── release.yaml.shared │ ├── revdepcheck.yaml │ ├── rhub.yaml │ ├── roxygen.yaml │ ├── scheduled.yaml.shared │ ├── spelling.yaml │ ├── style.yaml │ ├── test-coverage.yaml │ ├── validation.yaml │ ├── verdepcheck.yaml │ ├── version-bump.yaml │ ├── version.yaml │ └── wasm.yaml ├── .gitignore ├── .lintr ├── .pre-commit-config.yaml ├── DESCRIPTION ├── LICENSE ├── NAMESPACE ├── NEWS.md ├── R └── hello.R ├── README.md ├── SECURITY.md ├── _pkgdown.yml ├── images ├── audit.png ├── bioccheck.png ├── branch-cleanup.png ├── gitleaks.png ├── grammar1.png ├── grammar2.png ├── license-report.png ├── links.png ├── pkgdown.png ├── presidio.png ├── r-cmd-check.png ├── release.png ├── rhub-workflow.png ├── roxygen.png ├── spellcheck.png ├── styler.png ├── superlinter.png ├── validation1.png ├── validation2.png ├── version-bump.png └── version.png ├── init.sh ├── inst ├── WORDLIST └── plumber │ └── hello │ └── plumber.R ├── man ├── hello.Rd ├── plumber_api.Rd └── shiny_app.Rd ├── r.pkg.template.Rproj ├── staged_dependencies.yaml ├── tests ├── testthat.R └── testthat │ ├── shiny-app │ └── app.R │ ├── test-api.R │ ├── test-hello.R │ └── test-shiny.R ├── vignettes └── hello.Rmd └── workflows.md /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^renv$ 2 | ^renv\.lock$ 3 | CODE_OF_CONDUCT.md 4 | SECURITY.md 5 | ^.*\.Rproj$ 6 | ^\.Rproj\.user$ 7 | ^_pkgdown\.yml$ 8 | ^vignettes/hello\.Rmd$ 9 | ^docs$ 10 | ^\.github$ 11 | README.* 12 | ^\.lintr$ 13 | ^staged_dependencies\.yaml$ 14 | coverage.* 15 | ^\.pre-commit-config\.yaml$ 16 | ^codemeta\.json$ 17 | init.sh 18 | workflows.md 19 | images 20 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @insightsengineering/idr 2 | -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | 2 | # Contributor Covenant Code of Conduct 3 | 4 | ## Our Pledge 5 | 6 | We as members, contributors, and leaders pledge to make participation in our 7 | community a harassment-free experience for everyone, regardless of age, body 8 | size, visible or invisible disability, ethnicity, sex characteristics, gender 9 | identity and expression, level of experience, education, socio-economic status, 10 | nationality, personal appearance, race, caste, color, religion, or sexual 11 | identity and orientation. 12 | 13 | We pledge to act and interact in ways that contribute to an open, welcoming, 14 | diverse, inclusive, and healthy community. 15 | 16 | ## Our Standards 17 | 18 | Examples of behavior that contributes to a positive environment for our 19 | community include: 20 | 21 | * Demonstrating empathy and kindness toward other people 22 | * Being respectful of differing opinions, viewpoints, and experiences 23 | * Giving and gracefully accepting constructive feedback 24 | * Accepting responsibility and apologizing to those affected by our mistakes, 25 | and learning from the experience 26 | * Focusing on what is best not just for us as individuals, but for the overall 27 | community 28 | 29 | Examples of unacceptable behavior include: 30 | 31 | * The use of sexualized language or imagery, and sexual attention or advances of 32 | any kind 33 | * Trolling, insulting or derogatory comments, and personal or political attacks 34 | * Public or private harassment 35 | * Publishing others' private information, such as a physical or email address, 36 | without their explicit permission 37 | * Other conduct which could reasonably be considered inappropriate in a 38 | professional setting 39 | 40 | ## Enforcement Responsibilities 41 | 42 | Community leaders are responsible for clarifying and enforcing our standards of 43 | acceptable behavior and will take appropriate and fair corrective action in 44 | response to any behavior that they deem inappropriate, threatening, offensive, 45 | or harmful. 46 | 47 | Community leaders have the right and responsibility to remove, edit, or reject 48 | comments, commits, code, wiki edits, issues, and other contributions that are 49 | not aligned to this Code of Conduct, and will communicate reasons for moderation 50 | decisions when appropriate. 51 | 52 | ## Scope 53 | 54 | This Code of Conduct applies within all community spaces, and also applies when 55 | an individual is officially representing the community in public spaces. 56 | Examples of representing our community include using an official e-mail address, 57 | posting via an official social media account, or acting as an appointed 58 | representative at an online or offline event. 59 | 60 | ## Enforcement 61 | 62 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 63 | reported to the community leaders responsible for enforcement at 64 | [INSERT CONTACT METHOD]. 65 | All complaints will be reviewed and investigated promptly and fairly. 66 | 67 | All community leaders are obligated to respect the privacy and security of the 68 | reporter of any incident. 69 | 70 | ## Enforcement Guidelines 71 | 72 | Community leaders will follow these Community Impact Guidelines in determining 73 | the consequences for any action they deem in violation of this Code of Conduct: 74 | 75 | ### 1. Correction 76 | 77 | **Community Impact**: Use of inappropriate language or other behavior deemed 78 | unprofessional or unwelcome in the community. 79 | 80 | **Consequence**: A private, written warning from community leaders, providing 81 | clarity around the nature of the violation and an explanation of why the 82 | behavior was inappropriate. A public apology may be requested. 83 | 84 | ### 2. Warning 85 | 86 | **Community Impact**: A violation through a single incident or series of 87 | actions. 88 | 89 | **Consequence**: A warning with consequences for continued behavior. No 90 | interaction with the people involved, including unsolicited interaction with 91 | those enforcing the Code of Conduct, for a specified period of time. This 92 | includes avoiding interactions in community spaces as well as external channels 93 | like social media. Violating these terms may lead to a temporary or permanent 94 | ban. 95 | 96 | ### 3. Temporary Ban 97 | 98 | **Community Impact**: A serious violation of community standards, including 99 | sustained inappropriate behavior. 100 | 101 | **Consequence**: A temporary ban from any sort of interaction or public 102 | communication with the community for a specified period of time. No public or 103 | private interaction with the people involved, including unsolicited interaction 104 | with those enforcing the Code of Conduct, is allowed during this period. 105 | Violating these terms may lead to a permanent ban. 106 | 107 | ### 4. Permanent Ban 108 | 109 | **Community Impact**: Demonstrating a pattern of violation of community 110 | standards, including sustained inappropriate behavior, harassment of an 111 | individual, or aggression toward or disparagement of classes of individuals. 112 | 113 | **Consequence**: A permanent ban from any sort of public interaction within the 114 | community. 115 | 116 | ## Attribution 117 | 118 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 119 | version 2.1, available at 120 | [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. 121 | 122 | Community Impact Guidelines were inspired by 123 | [Mozilla's code of conduct enforcement ladder][Mozilla CoC]. 124 | 125 | For answers to common questions about this code of conduct, see the FAQ at 126 | [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at 127 | [https://www.contributor-covenant.org/translations][translations]. 128 | 129 | [homepage]: https://www.contributor-covenant.org 130 | [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html 131 | [Mozilla CoC]: https://github.com/mozilla/diversity 132 | [FAQ]: https://www.contributor-covenant.org/faq 133 | [translations]: https://www.contributor-covenant.org/translations 134 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contribution Guidelines 2 | 3 | 🙏 Thank you for taking the time to contribute! 4 | 5 | Your input is deeply valued, whether an issue, a pull request, or even feedback, regardless of size, content or scope. 6 | 7 | ## Table of contents 8 | 9 | [👶 Getting started](#getting-started) 10 | 11 | [📔 Code of Conduct](#code-of-conduct) 12 | 13 | [🗃 License](#license) 14 | 15 | [📜 Issues](#issues) 16 | 17 | [🚩 Pull requests](#pull-requests) 18 | 19 | [💻 Coding guidelines](#coding-guidelines) 20 | 21 | [🏆 Recognition model](#recognition-model) 22 | 23 | [❓ Questions](#questions) 24 | 25 | ## Getting started 26 | 27 | Please refer the project [documentation][docs] for a brief introduction. Please also see other [articles][articles] within the project documentation for additional information. 28 | 29 | ## Code of Conduct 30 | 31 | A [Code of Conduct](CODE_OF_CONDUCT.md) governs this project. Participants and contributors are expected to follow the rules outlined therein. 32 | 33 | ## License 34 | 35 | All your contributions will be covered by this project's [license][license]. 36 | 37 | ## Issues 38 | 39 | We use GitHub to track issues, feature requests, and bugs. Before submitting a new issue, please check if the issue has already been reported. If the issue already exists, please upvote the existing issue 👍. 40 | 41 | For new feature requests, please elaborate on the context and the benefit the feature will have for users, developers, or other relevant personas. 42 | 43 | ## Pull requests 44 | 45 | ### GitHub Flow 46 | 47 | This repository uses the [GitHub Flow](https://docs.github.com/en/get-started/quickstart/github-flow) model for collaboration. To submit a pull request: 48 | 49 | 1. Create a branch 50 | 51 | Please see the [branch naming convention](#branch-naming-convention) below. If you don't have write access to this repository, please fork it. 52 | 53 | 2. Make changes 54 | 55 | Make sure your code 56 | * passes all checks imposed by GitHub Actions 57 | * is well documented 58 | * is well tested with unit tests sufficiently covering the changes introduced 59 | 60 | 3. Create a pull request (PR) 61 | 62 | In the pull request description, please link the relevant issue (if any), provide a detailed description of the change, and include any assumptions. 63 | 64 | 4. Address review comments, if any 65 | 66 | 5. Post approval 67 | 68 | Merge your PR if you have write access. Otherwise, the reviewer will merge the PR on your behalf. 69 | 70 | 6. Pat yourself on the back 71 | 72 | Congratulations! 🎉 73 | You are now an official contributor to this project! We are grateful for your contribution. 74 | 75 | ### Branch naming convention 76 | 77 | Suppose your changes are related to a current issue in the current project; please name your branch as follows: `_`. Please use underscore (`_`) as a delimiter for word separation. For example, `420_fix_ui_bug` would be a suitable branch name if your change is resolving and UI-related bug reported in issue number `420` in the current project. 78 | 79 | If your change affects multiple repositories, please name your branches as follows: `__`. For example, `69_awesomeproject_fix_spelling_error` would reference issue `69` reported in project `awesomeproject` and aims to resolve one or more spelling errors in multiple (likely related) repositories. 80 | 81 | ### `monorepo` and `staged.dependencies` 82 | 83 | Sometimes you might need to change upstream dependent package(s) to be able to submit a meaningful change. We are using [`staged.dependencies`](https://github.com/openpharma/staged.dependencies) functionality to simulate a `monorepo` behavior. The dependency configuration is already specified in this project's `staged_dependencies.yaml` file. You need to name the feature branches appropriately. _This is the only exception from the branch naming convention described above_. 84 | 85 | Please refer to the [staged.dependencies package documentation](https://openpharma.github.io/staged.dependencies/) for more details. 86 | 87 | ## Coding guidelines 88 | 89 | This repository follows some unified processes and standards adopted by its maintainers to ensure software development is carried out consistently within teams and cohesively across other repositories. 90 | 91 | ### Style guide 92 | 93 | This repository follows the standard [`tidyverse` style guide](https://style.tidyverse.org/) and uses [`lintr`](https://github.com/r-lib/lintr) for lint checks. Customized lint configurations are available in this repository's `.lintr` file. 94 | 95 | ### Dependency management 96 | 97 | Lightweight is the right weight. This repository follows [tinyverse](https://www.tinyverse.org/) recommedations of limiting dependencies to minimum. 98 | 99 | ### Dependency version management 100 | 101 | If the code is not compatible with all (!) historical versions of a given dependenct package, it is required to specify minimal version in the `DESCRIPTION` file. In particular: if the development version requires (imports) the development version of another package - it is required to put `abc (>= 1.2.3.9000)`. 102 | 103 | ### Recommended development environment & tools 104 | 105 | #### R & package versions 106 | 107 | We continuously test our packages against the newest R version along with the most recent dependencies from CRAN and BioConductor. We recommend that your working environment is also set up in the same way. You can find the details about the R version and packages used in the `R CMD check` GitHub Action execution log - there is a step that prints out the R `sessionInfo()`. 108 | 109 | If you discover bugs on older R versions or with an older set of dependencies, please create the relevant bug reports. 110 | 111 | #### `pre-commit` 112 | 113 | We highly recommend that you use the [`pre-commit`](https://pre-commit.com/) tool combined with [`R hooks for pre-commit`](https://github.com/lorenzwalthert/precommit) to execute some of the checks before committing and pushing your changes. 114 | 115 | Pre-commit hooks are already available in this repository's `.pre-commit-config.yaml` file. 116 | 117 | ## Recognition model 118 | 119 | As mentioned previously, all contributions are deeply valued and appreciated. While all contribution data is available as part of the [repository insights][insights], to recognize a _significant_ contribution and hence add the contributor to the package authors list, the following rules are enforced: 120 | 121 | * Minimum 5% of lines of code authored* (determined by `git blame` query) OR 122 | * Being at the top 5 contributors in terms of number of commits OR lines added OR lines removed* 123 | 124 | *Excluding auto-generated code, including but not limited to `roxygen` comments or `renv.lock` files. 125 | 126 | The package maintainer also reserves the right to adjust the criteria to recognize contributions. 127 | 128 | ## Questions 129 | 130 | If you have further questions regarding the contribution guidelines, please contact the package/repository maintainer. 131 | 132 | 133 | [docs]: https://insightsengineering.github.io/r.pkg.template/index.html 134 | [articles]: https://insightsengineering.github.io/r.pkg.template/main/articles/index.html 135 | [license]: https://insightsengineering.github.io/r.pkg.template/main/LICENSE-text.html 136 | [insights]: https://github.com/insightsengineering/r.pkg.template/pulse 137 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🐞 Bug Report 3 | description: File a bug report 4 | title: "[Bug]: " 5 | labels: ["bug"] 6 | body: 7 | - type: markdown 8 | attributes: 9 | value: | 10 | Thanks for taking the time to fill out this bug report! 11 | - type: textarea 12 | id: what-happened 13 | attributes: 14 | label: What happened? 15 | description: Also tell us, what did you expect to happen? 16 | placeholder: Tell us what you see! 17 | value: "A bug happened!" 18 | validations: 19 | required: true 20 | - type: dropdown 21 | id: r-version 22 | attributes: 23 | label: Which version(s) of R were you using? 24 | multiple: true 25 | options: 26 | - "3.6.x" 27 | - "4.1.x" 28 | - "4.2.x" 29 | - "Other" 30 | - type: textarea 31 | id: logs 32 | attributes: 33 | label: Relevant log output 34 | description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks. 35 | render: R 36 | - type: checkboxes 37 | id: code-of-conduct 38 | attributes: 39 | label: Code of Conduct 40 | description: By submitting this issue, you agree to follow our [Code of Conduct](https://insightsengineering.github.io/r.pkg.template/CODE_OF_CONDUCT.html) 41 | options: 42 | - label: I agree to follow this project's Code of Conduct. 43 | required: true 44 | - type: checkboxes 45 | id: contributor-guidelines 46 | attributes: 47 | label: Contribution Guidelines 48 | description: By submitting this issue, you agree to follow our [Contribution Guidelines](https://insightsengineering.github.io/r.pkg.template/CONTRIBUTING.html) 49 | options: 50 | - label: I agree to follow this project's Contribution Guidelines. 51 | required: true 52 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | --- 2 | blank_issues_enabled: true 3 | 4 | contact_links: 5 | - name: We are hiring! 6 | url: https://careers.gene.com/ 7 | about: Genentech and Roche are hiring! 8 | - name: Pharmaverse 9 | url: https://pharmaverse.org/ 10 | about: Related projects @ Pharmaverse.org 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: ✨ Feature Request 3 | description: Request or propose a new feature 4 | title: "[Feature Request]: <title>" 5 | labels: ["enhancement"] 6 | body: 7 | - type: textarea 8 | attributes: 9 | label: Feature description 10 | validations: 11 | required: true 12 | - type: checkboxes 13 | id: code-of-conduct 14 | attributes: 15 | label: Code of Conduct 16 | description: By submitting this issue, you agree to follow our [Code of Conduct](https://insightsengineering.github.io/r.pkg.template/CODE_OF_CONDUCT.html) 17 | options: 18 | - label: I agree to follow this project's Code of Conduct. 19 | required: true 20 | - type: checkboxes 21 | id: contributor-guidelines 22 | attributes: 23 | label: Contribution Guidelines 24 | description: By submitting this issue, you agree to follow our [Contribution Guidelines](https://insightsengineering.github.io/r.pkg.template/CONTRIBUTING.html) 25 | options: 26 | - label: I agree to follow this project's Contribution Guidelines. 27 | required: true 28 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: ❓ Question 3 | description: Question about usage or documentation 4 | title: "[Question]: <title>" 5 | labels: ["question"] 6 | body: 7 | - type: textarea 8 | attributes: 9 | label: What is your question? 10 | validations: 11 | required: true 12 | - type: checkboxes 13 | id: code-of-conduct 14 | attributes: 15 | label: Code of Conduct 16 | description: By submitting this issue, you agree to follow our [Code of Conduct](https://insightsengineering.github.io/r.pkg.template/CODE_OF_CONDUCT.html) 17 | options: 18 | - label: I agree to follow this project's Code of Conduct. 19 | required: true 20 | - type: checkboxes 21 | id: contributor-guidelines 22 | attributes: 23 | label: Contribution Guidelines 24 | description: By submitting this issue, you agree to follow our [Contribution Guidelines](https://insightsengineering.github.io/r.pkg.template/CONTRIBUTING.html) 25 | options: 26 | - label: I agree to follow this project's Contribution Guidelines. 27 | required: true 28 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | # Pull Request 2 | 3 | <!-- Please describe your pull request here --> 4 | -------------------------------------------------------------------------------- /.github/workflows/audit.yaml: -------------------------------------------------------------------------------- 1 | name: Audit Dependencies 🕵️‍♀️ 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | types: 9 | - opened 10 | - synchronize 11 | - reopened 12 | - ready_for_review 13 | branches: 14 | - main 15 | workflow_dispatch: 16 | workflow_call: 17 | inputs: 18 | package-subdirectory: 19 | description: Subdirectory in the repository, where the R package is located. 20 | required: false 21 | type: string 22 | default: "." 23 | allow-failure: 24 | description: Allow workflow to fail if one or more vulnerabilities are found. 25 | required: false 26 | type: boolean 27 | default: false 28 | 29 | jobs: 30 | audit: 31 | runs-on: ubuntu-latest 32 | container: 33 | image: ghcr.io/insightsengineering/rstudio:latest 34 | name: oysteR scan 🦪 35 | if: > 36 | !contains(github.event.commits[0].message, '[skip audit]') 37 | && github.event.pull_request.draft == false 38 | steps: 39 | - name: Get branch names 🌿 40 | id: branch-name 41 | uses: tj-actions/branch-names@v7 42 | 43 | - name: Checkout repo (PR) 🛎 44 | uses: actions/checkout@v4.1.1 45 | if: github.event_name == 'pull_request' 46 | with: 47 | ref: ${{ steps.branch-name.outputs.head_ref_branch }} 48 | repository: ${{ github.event.pull_request.head.repo.full_name }} 49 | fetch-depth: 1 50 | 51 | - name: Checkout repo 🛎 52 | uses: actions/checkout@v4.1.1 53 | if: github.event_name != 'pull_request' 54 | with: 55 | ref: ${{ steps.branch-name.outputs.head_ref_branch }} 56 | fetch-depth: 1 57 | 58 | - name: Check commit message 💬 59 | run: | 60 | git config --global --add safe.directory $(pwd) 61 | export head_commit_message="$(git show -s --format=%B | tr '\r\n' ' ' | tr '\n' ' ')" 62 | echo "head_commit_message = $head_commit_message" 63 | if [[ $head_commit_message == *"$SKIP_INSTRUCTION"* ]]; then 64 | echo "Skip instruction detected - cancelling the workflow." 65 | exit 1 66 | fi 67 | shell: bash 68 | env: 69 | SKIP_INSTRUCTION: "[skip audit]" 70 | 71 | - name: Normalize inputs 🛠️ 72 | id: normalizer 73 | run: | 74 | ALLOW_FAILURE="${{ inputs.allow-failure }}" 75 | if [ "$ALLOW_FAILURE" == "" ] 76 | then { 77 | ALLOW_FAILURE=false 78 | } 79 | fi 80 | echo "allow-failure=$ALLOW_FAILURE" >> "$GITHUB_ENV" 81 | shell: bash 82 | 83 | - name: Run oysteR scan on dependencies 🔍 84 | run: | 85 | tryCatch( 86 | expr = { 87 | dependencies_scan = oysteR::audit_description( 88 | dir = ".", 89 | fields = c("Depends", "Imports", "Suggests"), 90 | verbose = TRUE 91 | ) 92 | print(as.data.frame( 93 | dependencies_scan[c( 94 | "package", 95 | "version", 96 | "vulnerabilities", 97 | "no_of_vulnerabilities" 98 | )] 99 | )) 100 | Sys.sleep(1) 101 | deps_with_vulnerabilities = subset(dependencies_scan, no_of_vulnerabilities > 0) 102 | if(nrow(deps_with_vulnerabilities) > 0) { 103 | message("❗ Vulnerabilities found in the following dependencies:") 104 | message(deps_with_vulnerabilities["package"]) 105 | if ("${{ env.allow-failure }}" == "true") quit(status=1) 106 | } 107 | }, 108 | error = function(e) { 109 | message('🚨 Caught an error!') 110 | print(e) 111 | } 112 | ) 113 | shell: Rscript {0} 114 | working-directory: ${{ inputs.package-subdirectory }} 115 | 116 | - name: Run oysteR scan on renv.lock 🔒 117 | run: | 118 | tryCatch( 119 | expr = { 120 | if (file.exists("renv.lock")) { 121 | renv_lock_scan = oysteR::audit_renv_lock(dir = ".", verbose = TRUE) 122 | print(as.data.frame( 123 | renv_lock_scan[c( 124 | "package", 125 | "version", 126 | "vulnerabilities", 127 | "no_of_vulnerabilities" 128 | )] 129 | )) 130 | Sys.sleep(1) 131 | deps_with_vulnerabilities = subset(renv_lock_scan, no_of_vulnerabilities > 0) 132 | if(nrow(deps_with_vulnerabilities) > 0) { 133 | message("❗ Vulnerabilities found in the following dependencies:") 134 | message(deps_with_vulnerabilities["package"]) 135 | if ("${{ env.allow-failure }}" == "true") quit(status=1) 136 | } 137 | } else { 138 | print("No renv.lock file, not scanning.") 139 | } 140 | }, 141 | error = function(e) { 142 | message('🚨 Caught an error!') 143 | print(e) 144 | } 145 | ) 146 | shell: Rscript {0} 147 | -------------------------------------------------------------------------------- /.github/workflows/bioccheck.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: BiocCheck ☣️ 3 | 4 | on: 5 | push: 6 | tags: 7 | - "v*" 8 | branches: 9 | - main 10 | pull_request: 11 | types: 12 | - opened 13 | - synchronize 14 | - reopened 15 | - ready_for_review 16 | branches: 17 | - main 18 | workflow_dispatch: 19 | workflow_call: 20 | secrets: 21 | REPO_GITHUB_TOKEN: 22 | description: | 23 | Github token with read access to repositories, required for staged.dependencies installation 24 | required: false 25 | inputs: 26 | enable-bioccheck: 27 | description: Enable BiocCheck 28 | required: false 29 | type: boolean 30 | default: false 31 | install-system-dependencies: 32 | description: Check for and install system dependencies 33 | required: false 34 | default: false 35 | type: boolean 36 | enable-staged-dependencies-check: 37 | description: Enable staged dependencies YAML check 38 | required: false 39 | default: false 40 | type: boolean 41 | allow-failure: 42 | description: BiocCheck errors will not fail, but will give a warning 43 | required: false 44 | type: boolean 45 | default: false 46 | sd-direction: 47 | description: The direction to use to install staged dependencies. Choose between 'upstream', 'downstream' and 'all' 48 | required: false 49 | type: string 50 | default: upstream 51 | package-subdirectory: 52 | description: Subdirectory in the repository, where the R package is located. 53 | required: false 54 | type: string 55 | default: "." 56 | deps-installation-method: 57 | description: | 58 | Which method for installing R package dependencies to use? Supported values are: 59 | staged-dependencies 60 | setup-r-dependencies 61 | required: false 62 | type: string 63 | default: staged-dependencies 64 | lookup-refs: 65 | description: | 66 | List of package references to be used by setup-r-dependencies action if deps-installation-method == 'setup-r-dependencies'. 67 | required: false 68 | type: string 69 | default: "" 70 | 71 | concurrency: 72 | group: bioccheck-${{ github.event.pull_request.number || github.ref }} 73 | cancel-in-progress: true 74 | 75 | jobs: 76 | bioccheck: 77 | strategy: 78 | fail-fast: false 79 | matrix: 80 | config: 81 | - image: ghcr.io/insightsengineering/rstudio 82 | tag: latest 83 | name: ${{ matrix.config.image }}, version ${{ matrix.config.tag }} 84 | runs-on: ubuntu-latest 85 | if: > 86 | !contains(github.event.commits[0].message, '[skip bioccheck]') 87 | && contains(inputs.enable-bioccheck, 'true') 88 | && github.event.pull_request.draft == false 89 | container: 90 | image: ${{ matrix.config.image }}:${{ matrix.config.tag }} 91 | 92 | steps: 93 | - name: Setup token 🔑 94 | id: github-token 95 | run: | 96 | if [ "${{ secrets.REPO_GITHUB_TOKEN }}" == "" ]; then 97 | echo "REPO_GITHUB_TOKEN is empty. Substituting it with GITHUB_TOKEN." 98 | echo "token=${{ secrets.GITHUB_TOKEN }}" >> $GITHUB_OUTPUT 99 | else 100 | echo "Using REPO_GITHUB_TOKEN." 101 | echo "token=${{ secrets.REPO_GITHUB_TOKEN }}" >> $GITHUB_OUTPUT 102 | fi 103 | shell: bash 104 | 105 | - name: Get branch names 🌿 106 | id: branch-name 107 | uses: tj-actions/branch-names@v7 108 | 109 | - name: Checkout repo (PR) 🛎 110 | uses: actions/checkout@v4.1.1 111 | if: github.event_name == 'pull_request' 112 | with: 113 | ref: ${{ steps.branch-name.outputs.head_ref_branch }} 114 | path: ${{ github.event.repository.name }} 115 | repository: ${{ github.event.pull_request.head.repo.full_name }} 116 | 117 | - name: Checkout repo 🛎 118 | uses: actions/checkout@v4.1.1 119 | if: github.event_name != 'pull_request' 120 | with: 121 | ref: ${{ steps.branch-name.outputs.head_ref_branch }} 122 | path: ${{ github.event.repository.name }} 123 | 124 | - name: Check commit message 💬 125 | run: | 126 | git config --global --add safe.directory $(pwd) 127 | export head_commit_message="$(git show -s --format=%B | tr '\r\n' ' ' | tr '\n' ' ')" 128 | echo "head_commit_message = $head_commit_message" 129 | if [[ $head_commit_message == *"$SKIP_INSTRUCTION"* ]]; then 130 | echo "Skip instruction detected - cancelling the workflow." 131 | exit 1 132 | fi 133 | shell: bash 134 | working-directory: ${{ github.event.repository.name }} 135 | env: 136 | SKIP_INSTRUCTION: "[skip bioccheck]" 137 | 138 | - name: Restore SD cache 💰 139 | if: >- 140 | inputs.deps-installation-method == 'staged-dependencies' 141 | uses: actions/cache@v4 142 | with: 143 | key: sd-${{ runner.os }}-${{ github.event.repository.name }} 144 | path: ~/.staged.dependencies 145 | 146 | - name: Run Staged dependencies 🎦 147 | if: >- 148 | inputs.deps-installation-method == 'staged-dependencies' 149 | uses: insightsengineering/staged-dependencies-action@v2 150 | env: 151 | GITHUB_PAT: ${{ steps.github-token.outputs.token }} 152 | with: 153 | path: ${{ github.event.repository.name }} 154 | enable-check: ${{ inputs.enable-staged-dependencies-check }} 155 | run-system-dependencies: ${{ inputs.install-system-dependencies }} 156 | direction: ${{ inputs.sd-direction }} 157 | 158 | - name: Setup R dependencies 🎦 159 | if: >- 160 | inputs.deps-installation-method == 'setup-r-dependencies' 161 | uses: insightsengineering/setup-r-dependencies@v1 162 | env: 163 | GITHUB_PAT: ${{ steps.github-token.outputs.token }} 164 | with: 165 | lookup-refs: ${{ inputs.lookup-refs }} 166 | repository-path: ${{ github.event.repository.name }}/${{ inputs.package-subdirectory }} 167 | 168 | - name: Run BiocCheck ☣️ 169 | uses: insightsengineering/bioc-check-action@v1 170 | with: 171 | path: ${{ github.event.repository.name }}/${{ inputs.package-subdirectory }} 172 | no-check-version-num: true 173 | allow-failure: ${{ inputs.allow-failure }} 174 | -------------------------------------------------------------------------------- /.github/workflows/branch-cleanup.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Branch Cleanup 🍃 3 | 4 | on: 5 | workflow_call: 6 | secrets: 7 | REPO_GITHUB_TOKEN: 8 | description: | 9 | Github token with write access to repository 10 | required: false 11 | inputs: 12 | last-commit-age-days: 13 | description: | 14 | The branch will be removed if the last commit was added to it at least this many days ago. 15 | default: 90 16 | required: false 17 | type: number 18 | 19 | jobs: 20 | branch-cleanup: 21 | name: Branch Cleanup 🍃 22 | runs-on: ubuntu-latest 23 | container: 24 | image: ghcr.io/insightsengineering/rstudio:latest 25 | 26 | steps: 27 | - name: Setup token 🔑 28 | id: github-token 29 | run: | 30 | if [ "${{ secrets.REPO_GITHUB_TOKEN }}" == "" ]; then 31 | echo "REPO_GITHUB_TOKEN is empty. Substituting it with GITHUB_TOKEN." 32 | echo "token=${{ secrets.GITHUB_TOKEN }}" >> $GITHUB_OUTPUT 33 | else 34 | echo "Using REPO_GITHUB_TOKEN." 35 | echo "token=${{ secrets.REPO_GITHUB_TOKEN }}" >> $GITHUB_OUTPUT 36 | fi 37 | shell: bash 38 | 39 | - name: Checkout Code 🛎 40 | uses: actions/checkout@v4.1.1 41 | 42 | - name: Cleanup branches 🌿 43 | uses: phpdocker-io/github-actions-delete-abandoned-branches@v1 44 | id: delete-branches 45 | with: 46 | github_token: ${{ steps.github-token.outputs.token }} 47 | last_commit_age_days: ${{ inputs.last-commit-age-days }} 48 | # Additional precaution against deleting main branch. 49 | ignore_branches: main,gh-pages,_xml_coverage_reports,_junit_xml_reports 50 | dry_run: no 51 | 52 | - name: Show deleted branches 🌿 53 | run: "echo 'Deleted branches: ${{ steps.delete-branches.outputs.deleted_branches }}'" 54 | -------------------------------------------------------------------------------- /.github/workflows/check.yaml.shared: -------------------------------------------------------------------------------- 1 | --- 2 | name: Check 🛠 3 | 4 | on: 5 | pull_request: 6 | types: 7 | - opened 8 | - synchronize 9 | - reopened 10 | - ready_for_review 11 | branches: 12 | - main 13 | push: 14 | branches: 15 | - main 16 | workflow_dispatch: 17 | 18 | jobs: 19 | audit: 20 | name: Audit Dependencies 🕵️‍♂️ 21 | uses: insightsengineering/r.pkg.template/.github/workflows/audit.yaml@main 22 | r-cmd: 23 | name: R CMD Check 🧬 24 | uses: insightsengineering/r.pkg.template/.github/workflows/build-check-install.yaml@main 25 | secrets: 26 | REPO_GITHUB_TOKEN: ${{ secrets.REPO_GITHUB_TOKEN }} 27 | with: 28 | additional-env-vars: | 29 | _R_CHECK_CRAN_INCOMING_REMOTE_=false 30 | additional-r-cmd-check-params: --as-cran 31 | enforce-note-blocklist: true 32 | note-blocklist: | 33 | checking dependencies in R code .* NOTE 34 | checking R code for possible problems .* NOTE 35 | checking examples .* NOTE 36 | checking Rd line widths .* NOTE 37 | checking S3 generic/method consistency .* NOTE 38 | checking Rd .usage sections .* NOTE 39 | checking for unstated dependencies in vignettes .* NOTE 40 | checking top-level files .* NOTE 41 | checking files in ‘vignettes’ .* NOTE 42 | r-cmd-non-cran: 43 | name: R CMD Check (non-CRAN) 🧬 44 | uses: insightsengineering/r.pkg.template/.github/workflows/build-check-install.yaml@main 45 | secrets: 46 | REPO_GITHUB_TOKEN: ${{ secrets.REPO_GITHUB_TOKEN }} 47 | with: 48 | additional-env-vars: | 49 | NOT_CRAN=true 50 | enforce-note-blocklist: true 51 | concurrency-group: non-cran 52 | unit-test-report-directory: unit-test-report-non-cran 53 | note-blocklist: | 54 | checking dependencies in R code .* NOTE 55 | checking R code for possible problems .* NOTE 56 | checking examples .* NOTE 57 | checking Rd line widths .* NOTE 58 | checking S3 generic/method consistency .* NOTE 59 | checking Rd .usage sections .* NOTE 60 | checking for unstated dependencies in vignettes .* NOTE 61 | checking top-level files .* NOTE 62 | checking files in ‘vignettes’ .* NOTE 63 | coverage: 64 | name: Coverage 📔 65 | uses: insightsengineering/r.pkg.template/.github/workflows/test-coverage.yaml@main 66 | secrets: 67 | REPO_GITHUB_TOKEN: ${{ secrets.REPO_GITHUB_TOKEN }} 68 | linter: 69 | if: github.event_name != 'push' 70 | name: SuperLinter 🦸‍♀️ 71 | uses: insightsengineering/r.pkg.template/.github/workflows/linter.yaml@main 72 | roxygen: 73 | name: Roxygen 🅾 74 | uses: insightsengineering/r.pkg.template/.github/workflows/roxygen.yaml@main 75 | secrets: 76 | REPO_GITHUB_TOKEN: ${{ secrets.REPO_GITHUB_TOKEN }} 77 | with: 78 | auto-update: true 79 | gitleaks: 80 | name: gitleaks 💧 81 | uses: insightsengineering/r.pkg.template/.github/workflows/gitleaks.yaml@main 82 | spelling: 83 | name: Spell Check 🆎 84 | uses: insightsengineering/r.pkg.template/.github/workflows/spelling.yaml@main 85 | secrets: 86 | REPO_GITHUB_TOKEN: ${{ secrets.REPO_GITHUB_TOKEN }} 87 | links: 88 | if: github.event_name != 'push' 89 | name: Check URLs 🌐 90 | uses: insightsengineering/r.pkg.template/.github/workflows/links.yaml@main 91 | vbump: 92 | name: Version Bump 🤜🤛 93 | if: github.event_name == 'push' 94 | uses: insightsengineering/r.pkg.template/.github/workflows/version-bump.yaml@main 95 | secrets: 96 | REPO_GITHUB_TOKEN: ${{ secrets.REPO_GITHUB_TOKEN }} 97 | version: 98 | name: Version Check 🏁 99 | uses: insightsengineering/r.pkg.template/.github/workflows/version.yaml@main 100 | licenses: 101 | name: License Check 🃏 102 | uses: insightsengineering/r.pkg.template/.github/workflows/licenses.yaml@main 103 | style: 104 | if: github.event_name != 'push' 105 | name: Style Check 👗 106 | uses: insightsengineering/r.pkg.template/.github/workflows/style.yaml@main 107 | with: 108 | auto-update: true 109 | secrets: 110 | REPO_GITHUB_TOKEN: ${{ secrets.REPO_GITHUB_TOKEN }} 111 | -------------------------------------------------------------------------------- /.github/workflows/cla.yaml: -------------------------------------------------------------------------------- 1 | name: CLA 🔏 2 | 3 | on: 4 | issue_comment: 5 | types: 6 | - created 7 | # For PRs that originate from forks 8 | pull_request_target: 9 | types: 10 | - opened 11 | - closed 12 | - synchronize 13 | 14 | jobs: 15 | CLA: 16 | name: CLA 📝 17 | uses: insightsengineering/.github/.github/workflows/cla.yaml@main 18 | secrets: inherit 19 | -------------------------------------------------------------------------------- /.github/workflows/cran-status.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: CRAN Status 3 | 4 | on: 5 | workflow_dispatch: 6 | workflow_call: 7 | inputs: 8 | issue-assignees: 9 | description: | 10 | Whom should the issue be assigned to if errors are 11 | encountered in the CRAN status checks? 12 | This is a comma-separated string of GitHub usernames. 13 | If undefined or empty, no assignments are made. 14 | default: '' 15 | required: false 16 | type: string 17 | statuses: 18 | description: | 19 | Create an issue if one or more of the following 20 | statuses are reported on the check report. 21 | This is a comma-separated string of statuses. 22 | Allowed statuses are 'NOTE', 'WARN', and 'ERROR' 23 | default: 'ERROR' 24 | required: false 25 | type: string 26 | 27 | concurrency: 28 | group: cran-status-${{ github.event.pull_request.number || github.ref }} 29 | cancel-in-progress: true 30 | 31 | jobs: 32 | cran-status: 33 | name: Check Status 34 | runs-on: ubuntu-latest 35 | container: 36 | image: rocker/tidyverse:latest 37 | steps: 38 | - name: Run CRAN Status Action 39 | uses: insightsengineering/cran-status-action@v1 40 | with: 41 | statuses: "${{ inputs.statuses }}" 42 | issue-assignees: "${{ inputs.issue-assignees }}" 43 | -------------------------------------------------------------------------------- /.github/workflows/docs.yaml.shared: -------------------------------------------------------------------------------- 1 | --- 2 | name: Docs 📚 3 | 4 | on: 5 | push: 6 | branches: 7 | - main 8 | paths: 9 | - "inst/templates/**" 10 | - "_pkgdown.*" 11 | - DESCRIPTION 12 | - "**.md" 13 | - "**.Rmd" 14 | - "man/**" 15 | - "LICENSE.*" 16 | - NAMESPACE 17 | pull_request: 18 | types: 19 | - opened 20 | - synchronize 21 | - reopened 22 | - ready_for_review 23 | branches: 24 | - main 25 | paths: 26 | - "inst/templates/**" 27 | - "_pkgdown.*" 28 | - DESCRIPTION 29 | - "**.md" 30 | - "**.Rmd" 31 | - "man/**" 32 | - "LICENSE.*" 33 | - NAMESPACE 34 | workflow_dispatch: 35 | 36 | jobs: 37 | docs: 38 | name: Pkgdown Docs 📚 39 | uses: insightsengineering/r.pkg.template/.github/workflows/pkgdown.yaml@main 40 | secrets: 41 | REPO_GITHUB_TOKEN: ${{ secrets.REPO_GITHUB_TOKEN }} 42 | with: 43 | default-landing-page: latest-tag 44 | additional-unit-test-report-directories: unit-test-report-non-cran 45 | -------------------------------------------------------------------------------- /.github/workflows/gitleaks.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: gitleaks 💧 3 | 4 | on: 5 | push: 6 | branches: 7 | - main 8 | pull_request: 9 | types: 10 | - opened 11 | - synchronize 12 | - reopened 13 | - ready_for_review 14 | branches: 15 | - main 16 | workflow_dispatch: 17 | workflow_call: 18 | inputs: 19 | additional-args: 20 | description: Additional arguments to pass to 'gitleaks detect' 21 | required: false 22 | type: string 23 | default: "" 24 | check-for-pii: 25 | description: Check for any instances of PII data in the repository 26 | required: false 27 | type: boolean 28 | default: false 29 | 30 | concurrency: 31 | group: gitleaks-${{ github.event.pull_request.number || github.ref }} 32 | cancel-in-progress: true 33 | 34 | jobs: 35 | gitleaks: 36 | name: gitleaks 💧 37 | runs-on: ubuntu-latest 38 | if: > 39 | !contains(github.event.commits[0].message, '[skip gitleaks]') 40 | && github.event.pull_request.draft == false 41 | 42 | steps: 43 | - name: Get branch names 🌿 44 | id: branch-name 45 | uses: tj-actions/branch-names@v7 46 | 47 | - name: Checkout repo (PR) 🛎 48 | uses: actions/checkout@v4.1.1 49 | if: github.event_name == 'pull_request' 50 | with: 51 | ref: ${{ steps.branch-name.outputs.head_ref_branch }} 52 | repository: ${{ github.event.pull_request.head.repo.full_name }} 53 | 54 | - name: Checkout repo 🛎 55 | uses: actions/checkout@v4.1.1 56 | if: github.event_name != 'pull_request' 57 | with: 58 | ref: ${{ steps.branch-name.outputs.head_ref_branch }} 59 | 60 | - name: Check commit message 💬 61 | run: | 62 | git config --global --add safe.directory $(pwd) 63 | export head_commit_message="$(git show -s --format=%B | tr '\r\n' ' ' | tr '\n' ' ')" 64 | echo "head_commit_message = $head_commit_message" 65 | if [[ $head_commit_message == *"$SKIP_INSTRUCTION"* ]]; then 66 | echo "Skip instruction detected - cancelling the workflow." 67 | exit 1 68 | fi 69 | shell: bash 70 | env: 71 | SKIP_INSTRUCTION: "[skip gitleaks]" 72 | 73 | - name: Download and install gitleaks 💧 74 | run: | 75 | cd /tmp 76 | sudo wget -q \ 77 | "https://github.com/zricethezav/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz" \ 78 | -O gitleaks.tar.gz || \ 79 | (echo "Error downloading gitleaks ${GITLEAKS_VERSION} tarball" && exit 1) 80 | sudo tar -xvzf gitleaks.tar.gz || \ 81 | (echo "Error unarchiving gitleaks ${GITLEAKS_VERSION} tarball" && exit 1) 82 | sudo mv gitleaks /usr/bin/. || \ 83 | (echo "Error moving gitleaks for /usr/bin" && exit 1) 84 | shell: bash 85 | env: 86 | GITLEAKS_VERSION: "8.18.1" 87 | 88 | - name: Run gitleaks ☔ 89 | run: gitleaks -v detect --no-git ${{ inputs.additional-args }} --source . 90 | shell: bash 91 | 92 | pii-check: 93 | name: PII Check 💳 94 | runs-on: ubuntu-latest 95 | if: > 96 | !contains(github.event.commits[0].message, '[skip pii-check]') 97 | && github.event.pull_request.draft == false 98 | && inputs.check-for-pii == true 99 | 100 | steps: 101 | - name: Get branch names 🌿 102 | id: branch-name 103 | uses: tj-actions/branch-names@v7 104 | 105 | - name: Checkout repo (PR) 🛎 106 | uses: actions/checkout@v4.1.1 107 | if: github.event_name == 'pull_request' 108 | with: 109 | ref: ${{ steps.branch-name.outputs.head_ref_branch }} 110 | repository: ${{ github.event.pull_request.head.repo.full_name }} 111 | fetch-depth: 0 112 | 113 | - name: Checkout repo 🛎 114 | uses: actions/checkout@v4.1.1 115 | if: github.event_name != 'pull_request' 116 | with: 117 | ref: ${{ steps.branch-name.outputs.head_ref_branch }} 118 | fetch-depth: 0 119 | 120 | - name: Check commit message 💬 121 | run: | 122 | git config --global --add safe.directory $(pwd) 123 | export head_commit_message="$(git show -s --format=%B | tr '\r\n' ' ' | tr '\n' ' ')" 124 | echo "head_commit_message = $head_commit_message" 125 | if [[ $head_commit_message == *"$SKIP_INSTRUCTION"* ]]; then 126 | echo "Skip instruction detected - cancelling the workflow." 127 | exit 1 128 | fi 129 | shell: bash 130 | env: 131 | SKIP_INSTRUCTION: "[skip pii-check]" 132 | 133 | - name: Run Presidio to check for PII ☔ 134 | uses: insightsengineering/presidio-action@v1 135 | with: 136 | configuration-data: | 137 | threshold: 0.95 138 | ignore: | 139 | .git 140 | **/*.svg 141 | **/*.png 142 | **/*.rds 143 | **/*.rda 144 | **/*.jpg 145 | **/*.gif 146 | .gitlab-ci.yml 147 | .Rbuildignore 148 | _pkgdown.yml 149 | inst/WORDLIST 150 | **/*.RData 151 | **/*.xlsx 152 | output: "github" 153 | only-changed-files: true 154 | -------------------------------------------------------------------------------- /.github/workflows/grammar.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Grammar 📓 3 | 4 | on: 5 | push: 6 | branches: 7 | - main 8 | pull_request: 9 | types: 10 | - opened 11 | - synchronize 12 | - reopened 13 | - ready_for_review 14 | branches: 15 | - main 16 | workflow_dispatch: 17 | workflow_call: 18 | inputs: 19 | passive-voice: 20 | description: Enable checks for passive voice 21 | required: false 22 | default: false 23 | type: boolean 24 | enable-annotations: 25 | description: Enable creation of GitHub annotations 26 | required: false 27 | default: false 28 | type: boolean 29 | concurrency: 30 | group: grammar-${{ github.event.pull_request.number || github.ref }} 31 | cancel-in-progress: true 32 | 33 | jobs: 34 | grammar: 35 | name: Check 📝 36 | runs-on: ubuntu-latest 37 | if: > 38 | !contains(github.event.commits[0].message, '[skip grammar]') 39 | && github.event.pull_request.draft == false 40 | 41 | steps: 42 | - name: Get branch names 🌿 43 | id: branch-name 44 | uses: tj-actions/branch-names@v7 45 | 46 | - name: Checkout repo (PR) 🛎 47 | uses: actions/checkout@v4.1.1 48 | if: github.event_name == 'pull_request' 49 | with: 50 | ref: ${{ steps.branch-name.outputs.head_ref_branch }} 51 | repository: ${{ github.event.pull_request.head.repo.full_name }} 52 | 53 | - name: Checkout repo 🛎 54 | uses: actions/checkout@v4.1.1 55 | if: github.event_name != 'pull_request' 56 | with: 57 | ref: ${{ steps.branch-name.outputs.head_ref_branch }} 58 | 59 | - name: Check commit message 💬 60 | run: | 61 | git config --global --add safe.directory $(pwd) 62 | export head_commit_message="$(git show -s --format=%B | tr '\r\n' ' ' | tr '\n' ' ')" 63 | echo "head_commit_message = $head_commit_message" 64 | if [[ $head_commit_message == *"$SKIP_INSTRUCTION"* ]]; then 65 | echo "Skip instruction detected - cancelling the workflow." 66 | exit 1 67 | fi 68 | shell: bash 69 | env: 70 | SKIP_INSTRUCTION: "[skip grammar]" 71 | 72 | - name: Restore npm cache 💰 73 | uses: actions/cache@v4 74 | with: 75 | key: npm-${{ runner.os }}-${{ github.job }} 76 | restore-keys: | 77 | npm-${{ runner.os }}- 78 | path: node_modules 79 | 80 | - name: Setup NodeJS ☊ 81 | uses: actions/setup-node@v4 82 | id: npm-cache 83 | with: 84 | node-version: 20 85 | 86 | - name: Install write-good and deps ⏬ 87 | if: steps.npm-cache.outputs.cache-hit != 'true' 88 | run: npm install write-good fs 89 | shell: bash 90 | 91 | - name: Get changed files 🗞 92 | id: changed-files 93 | # v45.0.8 94 | uses: tj-actions/changed-files@a284dc1814e3fd07f2e34267fc8f81227ed29fb8 95 | with: 96 | separator: "," 97 | files: | 98 | **.R 99 | **.Rmd 100 | **.Rnw 101 | **.Rmarkdown 102 | **.qmd 103 | **.md 104 | 105 | - name: Run write-good 🏃‍♀️ 106 | uses: actions/github-script@v7 107 | with: 108 | github-token: ${{ secrets.GITHUB_TOKEN }} 109 | script: | 110 | const writeGood = require('write-good'); 111 | const path = require('path'); 112 | const fs = require('fs'); 113 | const changedFiles = "${{ steps.changed-files.outputs.all_changed_files }}"; 114 | if (!changedFiles || changedFiles.length === 0) { 115 | process.exit(0); 116 | } 117 | let files = changedFiles.split(","); 118 | let allSuggestions = []; 119 | var inputs = ${{ toJSON(inputs) }}; 120 | var passiveVoice = inputs['passive-voice']; 121 | if (typeof passiveVoice === 'undefined') { 122 | passiveVoice = false; 123 | } 124 | files.forEach((file) => { 125 | if (fs.lstatSync(file).isFile() && 126 | !path.dirname(file).startsWith("node_modules")) { 127 | const contents = fs.readFileSync(file, 'utf8'); 128 | const suggestions = writeGood(contents, { passive : passiveVoice }); 129 | const annotations = writeGood.annotate(contents, suggestions, true); 130 | if (annotations.length) { 131 | for (var i=0; i < annotations.length; i++) { 132 | let ann = annotations[i]; 133 | ann['message'] = ann['reason']; 134 | ann['start_column'] = ann['col']; 135 | ann['end_column'] = ann['col']; 136 | ann['start_line'] = ann['line']; 137 | ann['end_line'] = ann['line']; 138 | ann['annotation_level'] = 'notice'; 139 | ann['path'] = file; 140 | delete ann['reason']; 141 | delete ann['col']; 142 | delete ann['line']; 143 | allSuggestions.push(ann); 144 | } 145 | } 146 | } 147 | }); 148 | fs.writeFileSync('./annotations.json', JSON.stringify(allSuggestions, null, 2) , 'utf-8'); 149 | 150 | - name: Check whether annotations exist 💭 151 | id: check-annotations 152 | if: inputs.enable-annotations 153 | uses: andstor/file-existence-action@v3 154 | with: 155 | files: "annotations.json" 156 | 157 | - name: Annotate files for grammar suggestions ✍️ 158 | if: > 159 | steps.check-annotations.outputs.files_exists == 'true' 160 | && inputs.enable-annotations 161 | uses: kibalabs/github-action-create-annotations@v0.2.0 162 | with: 163 | github-token: ${{ secrets.GITHUB_TOKEN }} 164 | json-file-path: ./annotations.json 165 | fail-on-error: false 166 | -------------------------------------------------------------------------------- /.github/workflows/licenses.yaml: -------------------------------------------------------------------------------- 1 | name: License report 🗃 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | types: 9 | - opened 10 | - synchronize 11 | - reopened 12 | - ready_for_review 13 | branches: 14 | - main 15 | workflow_dispatch: 16 | workflow_call: 17 | inputs: 18 | package-subdirectory: 19 | description: Subdirectory in the repository, where the R package is located. 20 | required: false 21 | type: string 22 | default: "." 23 | 24 | concurrency: 25 | group: licenses-${{ github.event.pull_request.number || github.ref }} 26 | cancel-in-progress: true 27 | 28 | jobs: 29 | licenses: 30 | runs-on: ubuntu-latest 31 | name: Check 🚩 32 | if: > 33 | !contains(github.event.commits[0].message, '[skip licenses]') 34 | && github.event.pull_request.draft == false 35 | 36 | steps: 37 | - name: Get branch names 🌿 38 | id: branch-name 39 | uses: tj-actions/branch-names@v7 40 | 41 | - name: Checkout repo (PR) 🛎 42 | uses: actions/checkout@v4.1.1 43 | if: github.event_name == 'pull_request' 44 | with: 45 | ref: ${{ steps.branch-name.outputs.head_ref_branch }} 46 | repository: ${{ github.event.pull_request.head.repo.full_name }} 47 | 48 | - name: Checkout repo 🛎 49 | uses: actions/checkout@v4.1.1 50 | if: github.event_name != 'pull_request' 51 | with: 52 | ref: ${{ steps.branch-name.outputs.head_ref_branch }} 53 | 54 | - name: Check commit message 💬 55 | run: | 56 | git config --global --add safe.directory $(pwd) 57 | export head_commit_message="$(git show -s --format=%B | tr '\r\n' ' ' | tr '\n' ' ')" 58 | echo "head_commit_message = $head_commit_message" 59 | if [[ $head_commit_message == *"$SKIP_INSTRUCTION"* ]]; then 60 | echo "Skip instruction detected - cancelling the workflow." 61 | exit 1 62 | fi 63 | shell: bash 64 | env: 65 | SKIP_INSTRUCTION: "[skip licenses]" 66 | 67 | - name: Generate license report 📜 68 | uses: insightsengineering/r-license-report@v1 69 | with: 70 | regex: "^AGPL.*" 71 | path: ${{ inputs.package-subdirectory }} 72 | -------------------------------------------------------------------------------- /.github/workflows/links.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Check URLs 🌐 3 | 4 | on: 5 | push: 6 | branches: 7 | - main 8 | pull_request: 9 | types: 10 | - opened 11 | - synchronize 12 | - reopened 13 | - ready_for_review 14 | branches: 15 | - main 16 | workflow_dispatch: 17 | workflow_call: 18 | inputs: 19 | lychee-additional-args: 20 | description: Additional arguments to pass to lychee 21 | required: false 22 | type: string 23 | default: "--exclude-private" 24 | lychee-fail: 25 | description: Fail workflow run on error (i.e. when lychee exit code is not 0) 26 | required: false 27 | type: boolean 28 | default: false 29 | package-subdirectory: 30 | description: Subdirectory in the repository, where the R package is located. 31 | required: false 32 | type: string 33 | default: "" 34 | link-checking-method: 35 | description: | 36 | Which link checking method should be used? Supported methods are: 37 | lychee 38 | urlchecker 39 | required: false 40 | type: string 41 | default: "urlchecker" 42 | 43 | concurrency: 44 | group: links-${{ github.event.pull_request.number || github.ref }} 45 | cancel-in-progress: true 46 | 47 | jobs: 48 | links: 49 | name: Validate Links 🔎 50 | runs-on: ubuntu-latest 51 | if: > 52 | !contains(github.event.commits[0].message, '[skip links]') 53 | && github.event.pull_request.draft == false 54 | && inputs.link-checking-method == 'lychee' 55 | steps: 56 | - name: Get branch names 🌿 57 | id: branch-name 58 | uses: tj-actions/branch-names@v7 59 | 60 | - name: Checkout repo (PR) 🛎 61 | uses: actions/checkout@v4.1.1 62 | if: github.event_name == 'pull_request' 63 | with: 64 | ref: ${{ steps.branch-name.outputs.head_ref_branch }} 65 | repository: ${{ github.event.pull_request.head.repo.full_name }} 66 | 67 | - name: Checkout repo 🛎 68 | uses: actions/checkout@v4.1.1 69 | if: github.event_name != 'pull_request' 70 | with: 71 | ref: ${{ steps.branch-name.outputs.head_ref_branch }} 72 | 73 | - name: Check commit message 💬 74 | run: | 75 | git config --global --add safe.directory $(pwd) 76 | export head_commit_message="$(git show -s --format=%B | tr '\r\n' ' ' | tr '\n' ' ')" 77 | echo "head_commit_message = $head_commit_message" 78 | if [[ $head_commit_message == *"$SKIP_INSTRUCTION"* ]]; then 79 | echo "Skip instruction detected - cancelling the workflow." 80 | exit 1 81 | fi 82 | shell: bash 83 | env: 84 | SKIP_INSTRUCTION: "[skip links]" 85 | 86 | - name: Check URLs in docs 🔬 87 | uses: lycheeverse/lychee-action@v1.10.0 88 | with: 89 | args: >- 90 | --exclude "https://github.com.*.git|https://insightsengineering.github.io.*|lewagon.*|knightdave.*|.*users.noreply.github.com|lycheeverse.*" 91 | --verbose 92 | --no-progress 93 | ${{ inputs.lychee-additional-args }} 94 | **/*.md 95 | **/*.html 96 | **/*.Rmd 97 | **/*.yaml 98 | **/*.yml 99 | *.md 100 | fail: ${{ inputs.lychee-fail }} 101 | env: 102 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 103 | 104 | urlchecker: 105 | name: URL Checker 🔎 106 | runs-on: ubuntu-latest 107 | container: 108 | image: rocker/tidyverse:latest 109 | if: > 110 | !contains(github.event.commits[0].message, '[skip links]') 111 | && github.event.pull_request.draft == false 112 | && inputs.link-checking-method == 'urlchecker' 113 | steps: 114 | - name: Get branch names 🌿 115 | id: branch-name 116 | uses: tj-actions/branch-names@v7 117 | 118 | - name: Checkout repo (PR) 🛎 119 | uses: actions/checkout@v4.1.1 120 | if: github.event_name == 'pull_request' 121 | with: 122 | ref: ${{ steps.branch-name.outputs.head_ref_branch }} 123 | repository: ${{ github.event.pull_request.head.repo.full_name }} 124 | 125 | - name: Checkout repo 🛎 126 | uses: actions/checkout@v4.1.1 127 | if: github.event_name != 'pull_request' 128 | with: 129 | ref: ${{ steps.branch-name.outputs.head_ref_branch }} 130 | 131 | - name: Check commit message 💬 132 | run: | 133 | git config --global --add safe.directory $(pwd) 134 | export head_commit_message="$(git show -s --format=%B | tr '\r\n' ' ' | tr '\n' ' ')" 135 | echo "head_commit_message = $head_commit_message" 136 | if [[ $head_commit_message == *"$SKIP_INSTRUCTION"* ]]; then 137 | echo "Skip instruction detected - cancelling the workflow." 138 | exit 1 139 | fi 140 | shell: bash 141 | env: 142 | SKIP_INSTRUCTION: "[skip links]" 143 | 144 | - name: Check URLs with urlchecker 🔬 145 | run: | 146 | # For unexplained reasons, parallel = FALSE is required to prevent some false positives. 147 | bad_urls <- nrow(print(urlchecker::url_check(".", parallel = FALSE))) 148 | if (bad_urls > 0) { 149 | stop("Looks like a total of ", bad_urls, " URL(s) were found! Please correct them.") 150 | } 151 | shell: Rscript {0} 152 | working-directory: ${{ inputs.package-subdirectory }} 153 | -------------------------------------------------------------------------------- /.github/workflows/linter.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: SuperLinter 🦸‍♀️ 3 | 4 | on: 5 | push: 6 | branches: 7 | - main 8 | pull_request: 9 | types: 10 | - opened 11 | - synchronize 12 | - reopened 13 | - ready_for_review 14 | branches: 15 | - main 16 | workflow_dispatch: 17 | workflow_call: 18 | inputs: 19 | lintr_error_on_lint: 20 | description: Produce an error when lints are found 21 | required: false 22 | type: boolean 23 | default: true 24 | lint-all-files: 25 | description: Lint all files every time 26 | default: false 27 | required: false 28 | type: boolean 29 | 30 | concurrency: 31 | group: lint-${{ github.event.pull_request.number || github.ref }} 32 | cancel-in-progress: true 33 | 34 | jobs: 35 | lint: 36 | name: SuperLinter 🦸‍♂️ 37 | runs-on: ubuntu-latest 38 | if: > 39 | !contains(github.event.commits[0].message, '[skip linter]') 40 | && github.event.pull_request.draft == false 41 | 42 | steps: 43 | - name: Get branch names 🌿 44 | id: branch-name 45 | uses: tj-actions/branch-names@v7 46 | 47 | - name: Checkout repo (PR) 🛎 48 | uses: actions/checkout@v4.1.1 49 | if: github.event_name == 'pull_request' 50 | with: 51 | ref: ${{ steps.branch-name.outputs.head_ref_branch }} 52 | repository: ${{ github.event.pull_request.head.repo.full_name }} 53 | fetch-depth: 0 54 | 55 | - name: Checkout repo 🛎 56 | uses: actions/checkout@v4.1.1 57 | if: github.event_name != 'pull_request' 58 | with: 59 | ref: ${{ steps.branch-name.outputs.head_ref_branch }} 60 | fetch-depth: 0 61 | 62 | - name: Check commit message 💬 63 | run: | 64 | git config --global --add safe.directory $(pwd) 65 | export head_commit_message="$(git show -s --format=%B | tr '\r\n' ' ' | tr '\n' ' ')" 66 | echo "head_commit_message = $head_commit_message" 67 | if [[ $head_commit_message == *"$SKIP_INSTRUCTION"* ]]; then 68 | echo "Skip instruction detected - cancelling the workflow." 69 | exit 1 70 | fi 71 | shell: bash 72 | env: 73 | SKIP_INSTRUCTION: "[skip linter]" 74 | 75 | - name: Override SuperLinter configs 💾 76 | run: | 77 | # Override markdownlint default config 78 | [ ! -e .markdownlint.yaml ] && cat > .markdownlint.yaml <<EOF 79 | ############### 80 | # Rules by id # 81 | ############### 82 | MD004: false # Unordered list style 83 | MD007: 84 | indent: 2 # Unordered list indentation 85 | MD013: 86 | line_length: 800 # Line length 87 | MD026: 88 | punctuation: ".,;:!。,;:" # List of not allowed 89 | MD029: false # Ordered list item prefix 90 | MD033: false # Allow inline HTML 91 | MD036: false # Emphasis used instead of a heading 92 | ################# 93 | # Rules by tags # 94 | ################# 95 | blank_lines: true # Error on blank lines 96 | EOF 97 | shell: bash 98 | 99 | - name: Lint Code Base 🧶 100 | uses: super-linter/super-linter/slim@v5 101 | env: 102 | LINTER_RULES_PATH: / 103 | DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} 104 | FILTER_REGEX_EXCLUDE: NEWS.md|design/.*|testthat/_snaps/.* 105 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 106 | VALIDATE_ALL_CODEBASE: false 107 | VALIDATE_BASH: true 108 | VALIDATE_DOCKERFILE: true 109 | VALIDATE_MARKDOWN: true 110 | MARKDOWN_CONFIG_FILE: .markdownlint.yaml 111 | VALIDATE_YAML: true 112 | 113 | lint-r-code: 114 | name: Lint R code 🧶 115 | runs-on: ubuntu-latest 116 | if: > 117 | !contains(github.event.commits[0].message, '[skip linter]') 118 | && github.event.pull_request.draft == false 119 | container: 120 | image: ghcr.io/insightsengineering/rstudio:latest 121 | 122 | steps: 123 | - name: Checkout repo (PR) 🛎 124 | uses: actions/checkout@v4.1.1 125 | if: github.event_name == 'pull_request' 126 | with: 127 | ref: ${{ github.event.pull_request.head.sha }} 128 | fetch-depth: 0 129 | 130 | - name: Checkout repo 🛎 131 | uses: actions/checkout@v4.1.1 132 | if: github.event_name != 'pull_request' 133 | with: 134 | fetch-depth: 0 135 | 136 | - name: Check commit message 💬 137 | run: | 138 | git config --global --add safe.directory $(pwd) 139 | export head_commit_message="$(git show -s --format=%B | tr '\r\n' ' ' | tr '\n' ' ')" 140 | echo "head_commit_message = $head_commit_message" 141 | if [[ $head_commit_message == *"$SKIP_INSTRUCTION"* ]]; then 142 | echo "Skip instruction detected - cancelling the workflow." 143 | exit 1 144 | fi 145 | shell: bash 146 | env: 147 | SKIP_INSTRUCTION: "[skip linter]" 148 | 149 | - name: Changed files 🖋️ 150 | if: github.event_name != 'workflow_dispatch' 151 | id: files 152 | uses: Ana06/get-changed-files@v2.2.0 153 | with: 154 | format: 'json' 155 | filter: '*' 156 | 157 | - name: Lint 🧶 158 | run: | 159 | github_event_name <- "${{ github.event_name }}" 160 | cat(paste0("GitHub event name = ", github_event_name, "\n")) 161 | exclusions_list <- NULL 162 | if (!(identical("${{ inputs.lint-all-files }}", "true") || identical(github_event_name, "workflow_dispatch"))) { 163 | changed_files <- jsonlite::fromJSON('${{ steps.files.outputs.added_modified }}') 164 | all_files <- list.files(recursive = TRUE) 165 | exclusions_list <- as.list(setdiff(all_files, changed_files)) 166 | cat("Linting only changed files:\n") 167 | cat(changed_files) 168 | cat("\n\n") 169 | } else { 170 | cat("Linting all files.\n") 171 | } 172 | lints <- lintr::lint_package(exclusions = exclusions_list) 173 | if (length(lints) > 0L) { 174 | print(lints) 175 | if (identical("${{ inputs.lintr_error_on_lint }}", "true")) { 176 | stop("Lints detected. Please review and adjust code according to the comments provided.", call. = FALSE) 177 | } 178 | } 179 | shell: Rscript {0} 180 | -------------------------------------------------------------------------------- /.github/workflows/pkgdown.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Pkgdown Docs 📚 3 | 4 | on: 5 | push: 6 | tags: 7 | - "v*" 8 | branches: 9 | - main 10 | pull_request: 11 | types: 12 | - opened 13 | - synchronize 14 | - reopened 15 | - ready_for_review 16 | branches: 17 | - main 18 | paths: 19 | - inst/templates/** 20 | - _pkgdown.yaml 21 | - DESCRIPTION 22 | - "**.md" 23 | - "**.Rmd" 24 | - man/** 25 | - LICENSE.* 26 | - NAMESPACE 27 | workflow_dispatch: 28 | workflow_call: 29 | secrets: 30 | REPO_GITHUB_TOKEN: 31 | description: | 32 | Github token with read access to repositories, required for staged.dependencies installation 33 | required: false 34 | inputs: 35 | install-system-dependencies: 36 | description: Check for and install system dependencies 37 | required: false 38 | default: false 39 | type: boolean 40 | fail-pkgdown-on-warnings: 41 | description: Fail the pkgdown workflow if warnings are generated while generating docs 42 | required: false 43 | default: false 44 | type: boolean 45 | enable-staged-dependencies-check: 46 | description: Enable staged dependencies YAML check 47 | required: false 48 | default: false 49 | type: boolean 50 | additional-env-vars: 51 | description: | 52 | Extra environment variables, as a 'key=value' pair, with each pair on a new line. 53 | Example usage: 54 | additional-env-vars: | 55 | ABC=123 56 | XYZ=456 57 | required: false 58 | default: "" 59 | type: string 60 | refs-order: 61 | description: | 62 | The order in which refs should appear in the drop-down list. Versions not in the vector 63 | will appear below refs listed here. 64 | If docs have never been generated for the ref, the ref will not appear in the 65 | drop-down. Similarly, if docs have been generated for the ref, but the ref is not 66 | listed in the vector, it will not appear in the drop-down. 67 | required: false 68 | default: c("devel", "pre-release", "main", "latest-tag") 69 | type: string 70 | default-landing-page: 71 | description: 72 | The default branch or tag on gh-pages that corresponds to the landing page. 73 | For instance, if your root index page on gh-pages is built using the 'main' 74 | branch, then the root page of the website will correspond to this page. 75 | If 'latest-tag' is selected, then the latest version will become the default. 76 | required: false 77 | default: main 78 | type: string 79 | sd-direction: 80 | description: The direction to use to install staged dependencies. Choose between 'upstream', 'downstream' and 'all' 81 | required: false 82 | type: string 83 | default: upstream 84 | # If you provide latest-tag-alt-name and/or release-candidate-alt-name inputs 85 | # to this workflow in some repo, you should also provide these inputs in 86 | # R CMD check and Coverage workflows in order to make the unit test report 87 | # and coverage report available in pkgdown documentation for the alt-names. 88 | # Additionally, branches-or-tags-to-list input should be overridden with 89 | # the additional alt-name inputs. 90 | latest-tag-alt-name: 91 | description: An alternate name for the 'latest-tag' documentation item 92 | required: false 93 | type: string 94 | default: "" 95 | release-candidate-alt-name: 96 | description: An alternate name for the 'release-candidate' documentation item 97 | required: false 98 | type: string 99 | default: "" 100 | branches-or-tags-to-list: 101 | description: Which branches or tags should be listed under the 102 | 'Versions' dropdown menu on the landing page? 103 | This input should be a regular expression in R. 104 | required: false 105 | type: string 106 | default: >- 107 | ^main$|^devel$|^pre-release$|^latest-tag$|^release-candidate$|^develop$|^v([0-9]+\\.)?([0-9]+\\.)?([0-9]+)|^v([0-9]+\\.)?([0-9]+\\.)?([0-9]+)(-rc[0-9]+)$ 108 | package-subdirectory: 109 | description: Subdirectory in the repository, where the R package is located. 110 | required: false 111 | type: string 112 | default: "." 113 | additional-unit-test-report-directories: 114 | description: | 115 | If any *additional* unit test report directories are generated by the build-check-install workflow, 116 | they should be listed as comma-separated directory list. If this input is empty, only coverage-report 117 | and unit-test-report directories will be retained in the generated documentation directory. 118 | Example: 119 | unit-test-report-as-cran,unit-test-report-not-cran 120 | required: false 121 | type: string 122 | default: "" 123 | deps-installation-method: 124 | description: | 125 | Which method for installing R package dependencies to use? Supported values are: 126 | staged-dependencies 127 | setup-r-dependencies 128 | required: false 129 | type: string 130 | default: staged-dependencies 131 | lookup-refs: 132 | description: | 133 | List of package references to be used by setup-r-dependencies action if deps-installation-method == 'setup-r-dependencies'. 134 | required: false 135 | type: string 136 | default: "" 137 | 138 | concurrency: 139 | group: docs-${{ github.event.pull_request.number || github.ref }} 140 | cancel-in-progress: true 141 | 142 | jobs: 143 | docs: 144 | name: Generate 🐣 145 | runs-on: ubuntu-latest 146 | if: > 147 | !contains(github.event.commits[0].message, '[skip docs]') 148 | && github.event.pull_request.draft == false 149 | container: 150 | image: ghcr.io/insightsengineering/rstudio:latest 151 | 152 | steps: 153 | - name: Setup token 🔑 154 | id: github-token 155 | run: | 156 | if [ "${{ secrets.REPO_GITHUB_TOKEN }}" == "" ]; then 157 | echo "REPO_GITHUB_TOKEN is empty. Substituting it with GITHUB_TOKEN." 158 | echo "token=${{ secrets.GITHUB_TOKEN }}" >> $GITHUB_OUTPUT 159 | else 160 | echo "Using REPO_GITHUB_TOKEN." 161 | echo "token=${{ secrets.REPO_GITHUB_TOKEN }}" >> $GITHUB_OUTPUT 162 | fi 163 | shell: bash 164 | 165 | - name: Get branch names 🌿 166 | id: branch-name 167 | uses: tj-actions/branch-names@v7 168 | 169 | - name: Get current branch or tag 🏷️ 170 | id: current-branch-or-tag 171 | run: | 172 | if [ "${{ steps.branch-name.outputs.is_tag }}" == "true" ]; then 173 | echo "Current tag: ${{ steps.branch-name.outputs.tag }}" 174 | echo "ref-name=${{ steps.branch-name.outputs.tag }}" >> $GITHUB_OUTPUT 175 | else 176 | echo "Current branch: ${{ steps.branch-name.outputs.current_branch }}" 177 | echo "ref-name=${{ steps.branch-name.outputs.current_branch }}" >> $GITHUB_OUTPUT 178 | fi 179 | shell: bash 180 | 181 | - name: Checkout repo (PR) 🛎 182 | uses: actions/checkout@v4.1.1 183 | if: github.event_name == 'pull_request' 184 | with: 185 | ref: ${{ steps.branch-name.outputs.head_ref_branch }} 186 | path: ${{ github.event.repository.name }} 187 | repository: ${{ github.event.pull_request.head.repo.full_name }} 188 | 189 | - name: Checkout repo 🛎 190 | uses: actions/checkout@v4.1.1 191 | if: github.event_name != 'pull_request' 192 | with: 193 | ref: ${{ steps.branch-name.outputs.head_ref_branch }} 194 | path: ${{ github.event.repository.name }} 195 | 196 | - name: Check commit message 💬 197 | run: | 198 | git config --global --add safe.directory $(pwd) 199 | export head_commit_message="$(git show -s --format=%B | tr '\r\n' ' ' | tr '\n' ' ')" 200 | echo "head_commit_message = $head_commit_message" 201 | if [[ $head_commit_message == *"$SKIP_INSTRUCTION"* ]]; then 202 | echo "Skip instruction detected - cancelling the workflow." 203 | exit 1 204 | fi 205 | shell: bash 206 | working-directory: ${{ github.event.repository.name }} 207 | env: 208 | SKIP_INSTRUCTION: "[skip docs]" 209 | 210 | - name: Restore SD cache 💰 211 | if: >- 212 | inputs.deps-installation-method == 'staged-dependencies' 213 | uses: actions/cache@v4 214 | with: 215 | key: sd-${{ runner.os }}-${{ github.event.repository.name }} 216 | path: ~/.staged.dependencies 217 | 218 | - name: Run Staged dependencies 🎦 219 | if: >- 220 | inputs.deps-installation-method == 'staged-dependencies' 221 | uses: insightsengineering/staged-dependencies-action@v2 222 | env: 223 | GITHUB_PAT: ${{ steps.github-token.outputs.token }} 224 | with: 225 | path: ${{ github.event.repository.name }}/${{ inputs.package-subdirectory }} 226 | enable-check: ${{ inputs.enable-staged-dependencies-check }} 227 | run-system-dependencies: ${{ inputs.install-system-dependencies }} 228 | direction: ${{ inputs.sd-direction }} 229 | 230 | - name: Setup R dependencies 🎦 231 | if: >- 232 | inputs.deps-installation-method == 'setup-r-dependencies' 233 | uses: insightsengineering/setup-r-dependencies@v1 234 | env: 235 | GITHUB_PAT: ${{ steps.github-token.outputs.token }} 236 | with: 237 | lookup-refs: ${{ inputs.lookup-refs }} 238 | repository-path: ${{ github.event.repository.name }}/${{ inputs.package-subdirectory }} 239 | needs: "website" 240 | 241 | - name: Install R package 🚧 242 | run: | 243 | if (file.exists("renv.lock")) renv::restore() 244 | install.packages(".", repos=NULL, type="source") 245 | shell: Rscript {0} 246 | working-directory: ${{ github.event.repository.name }}/${{ inputs.package-subdirectory }} 247 | 248 | - name: Build docs 🏗 249 | if: > 250 | github.event_name == 'pull_request' || startsWith(github.ref, 'refs/tags/v') 251 | || github.event_name == 'push' 252 | run: | 253 | repo="${{ github.event.repository.name }}" 254 | if [ "${{ inputs.additional-env-vars }}" != "" ] 255 | then { 256 | echo -e "${{ inputs.additional-env-vars }}" > /tmp/dotenv.env 257 | export $(tr '\n' ' ' < /tmp/dotenv.env) 258 | } 259 | fi 260 | Rscript - <<EOF 2>&1 | tee pkgdown_${repo}.log 261 | if (file.exists("renv.lock")) renv::restore() 262 | pkgdown::build_site(devel = TRUE) 263 | EOF 264 | if [ "${{ inputs.fail-pkgdown-on-warnings }}" == "true" ]; then { 265 | grep "Warning message" pkgdown_${repo}.log > pkgdown_warnings_${repo}.log 266 | if [[ $(wc -l <pkgdown_warnings_${repo}.log) -gt 0 ]]; then { 267 | echo "----------------------------------------" 268 | echo "⚠ One or more warnings were generated during the pkgdown build." 269 | echo "Please 🙏 fix the warnings shown above 👆" 270 | exit 1 271 | } 272 | fi 273 | } 274 | fi 275 | shell: bash 276 | working-directory: ${{ github.event.repository.name }}/${{ inputs.package-subdirectory }} 277 | 278 | - name: Checkout gh-pages 🛎 279 | if: startsWith(github.ref, 'refs/tags/v') || github.event_name == 'push' 280 | uses: actions/checkout@v4.1.1 281 | with: 282 | path: "gh-pages" 283 | fetch-depth: 0 284 | ref: "gh-pages" 285 | 286 | - name: Upload docs to gh-pages 📙 287 | if: startsWith(github.ref, 'refs/tags/v') || github.event_name == 'push' 288 | run: | 289 | GH_PAGES_DIR="gh-pages/${{ steps.current-branch-or-tag.outputs.ref-name }}" 290 | mkdir -p $GH_PAGES_DIR 291 | echo "Current contents of $GH_PAGES_DIR:" 292 | ls -l $GH_PAGES_DIR 293 | # Remove any existing documentation for the git tag, but retain coverage report and 294 | # unit test reports which might have already been pushed to the gh-pages branch 295 | # by the coverage and build-check-install workflows respectively. 296 | directories_to_retain="coverage-report,unit-test-report" 297 | if [[ "${{ inputs.additional-unit-test-report-directories }}" != "" ]]; then 298 | directories_to_retain="${directories_to_retain},${{ inputs.additional-unit-test-report-directories }}" 299 | fi 300 | IFS=',' read -ra DIRECTORIES_TO_RETAIN <<< "$directories_to_retain" 301 | echo "The following directories will be retained:" 302 | for dir in "${DIRECTORIES_TO_RETAIN[@]}"; do 303 | echo "$dir" 304 | done 305 | # Remove all files from GH_PAGES_DIR, except any DIRECTORIES_TO_RETAIN. 306 | find $GH_PAGES_DIR -mindepth 1 -maxdepth 1 -print0 | while IFS= read -r -d '' file; do 307 | file_to_be_removed="true" 308 | # Check if the file/directory matches any directory to be retained. 309 | for dir in "${DIRECTORIES_TO_RETAIN[@]}"; do 310 | if [[ "$GH_PAGES_DIR/$dir" == "$file" ]]; then 311 | echo "Not removing $file" 312 | file_to_be_removed="false" 313 | fi 314 | done 315 | if [[ "$file_to_be_removed" == "true" ]]; then 316 | echo "Removing $file" 317 | rm -rf "$file" 318 | fi 319 | done 320 | echo "::group::gh-pages contents" 321 | echo "Current contents of $GH_PAGES_DIR:" 322 | ls -l $GH_PAGES_DIR 323 | echo "::endgroup::" 324 | # Copy generated pkgdown documentation to gh-pages branch. 325 | cp -a ${{ github.event.repository.name }}/${{ inputs.package-subdirectory }}/docs/. $GH_PAGES_DIR 326 | echo "::group::gh-pages contents" 327 | echo "Current contents of $GH_PAGES_DIR:" 328 | ls -l $GH_PAGES_DIR 329 | echo "::endgroup::" 330 | cd gh-pages 331 | git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com" 332 | git config --global user.name "github-actions[bot]" 333 | git config pull.rebase false 334 | git status 335 | # Random delay 336 | sleep $((RANDOM % 10)) 337 | git pull origin gh-pages || true 338 | git add -f . 339 | git commit -m "Update pkgdown documentation ${{ github.sha }}" || true 340 | git push origin gh-pages 341 | shell: bash 342 | 343 | - name: Create documentation artifact 📂 344 | if: github.event_name == 'pull_request' || startsWith(github.ref, 'refs/tags/v') 345 | run: | 346 | pushd ${{ github.event.repository.name }}/${{ inputs.package-subdirectory }}/docs/ 347 | zip -r9 $OLDPWD/pkgdown.zip * 348 | popd 349 | shell: bash 350 | 351 | - name: Upload docs for review ⬆ 352 | if: github.event_name == 'pull_request' || startsWith(github.ref, 'refs/tags/v') 353 | uses: actions/upload-artifact@v4 354 | with: 355 | name: pkgdown.zip 356 | path: pkgdown.zip 357 | 358 | multi-version-docs: 359 | name: Multi-version docs 📑 360 | needs: docs 361 | runs-on: ubuntu-latest 362 | if: > 363 | (github.event_name == 'push' || github.event_name == 'workflow_dispatch') 364 | && !contains(github.event.commits[0].message, '[skip docs]') 365 | steps: 366 | - name: Checkout repo 🛎 367 | uses: actions/checkout@v4.1.1 368 | with: 369 | path: ${{ github.event.repository.name }} 370 | ref: "gh-pages" 371 | 372 | - name: Normalize inputs 📄 373 | id: normalize 374 | run: | 375 | DEFAULT_LANDING_PAGE="${{ inputs.default-landing-page }}" 376 | if [ "$DEFAULT_LANDING_PAGE" == "" ] 377 | then { 378 | DEFAULT_LANDING_PAGE=main 379 | } 380 | fi 381 | echo "default-landing-page=$DEFAULT_LANDING_PAGE" >> "$GITHUB_ENV" 382 | shell: bash 383 | 384 | - name: Create and publish docs ↗️ 385 | uses: insightsengineering/r-pkgdown-multiversion@v3 386 | with: 387 | path: ${{ github.event.repository.name }} 388 | default-landing-page: ${{ env.default-landing-page }} 389 | refs-order: ${{ inputs.refs-order }} 390 | latest-tag-alt-name: ${{ inputs.latest-tag-alt-name }} 391 | release-candidate-alt-name: ${{ inputs.release-candidate-alt-name }} 392 | branches-or-tags-to-list: ${{ inputs.branches-or-tags-to-list }} 393 | 394 | upload-release-assets: 395 | name: Upload documentation assets 🔼 396 | needs: docs 397 | runs-on: ubuntu-latest 398 | if: > 399 | startsWith(github.ref, 'refs/tags/v') 400 | && !contains(github.event.commits[0].message, '[skip docs]') 401 | steps: 402 | - name: Checkout repo 🛎 403 | uses: actions/checkout@v4.1.1 404 | 405 | - name: Download artifact ⏬ 406 | uses: actions/download-artifact@v4 407 | with: 408 | name: pkgdown.zip 409 | 410 | - name: Check if release exists ❓ 411 | id: check-if-release-exists 412 | uses: insightsengineering/release-existence-action@v1 413 | 414 | - name: Upload binaries to release ⤴ 415 | if: >- 416 | steps.check-if-release-exists.outputs.release-exists == 'true' 417 | uses: svenstaro/upload-release-action@v2 418 | with: 419 | repo_token: ${{ secrets.GITHUB_TOKEN }} 420 | file: pkgdown.zip 421 | asset_name: pkgdown.zip 422 | tag: ${{ github.ref }} 423 | overwrite: true 424 | -------------------------------------------------------------------------------- /.github/workflows/post-release.yaml.shared: -------------------------------------------------------------------------------- 1 | --- 2 | name: Post release ✨ 3 | 4 | on: 5 | release: 6 | types: ["released"] 7 | 8 | jobs: 9 | vbump: 10 | name: Version Bump 🤜🤛 11 | uses: insightsengineering/r.pkg.template/.github/workflows/version-bump.yaml@main 12 | secrets: 13 | REPO_GITHUB_TOKEN: ${{ secrets.REPO_GITHUB_TOKEN }} 14 | with: 15 | vbump-after-release: true 16 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Release 🚀 3 | 4 | on: 5 | push: 6 | tags: 7 | - "v*" 8 | workflow_call: 9 | secrets: 10 | REPO_GITHUB_TOKEN: 11 | description: | 12 | Github token with write access to the repository 13 | required: false 14 | inputs: 15 | create-rc-releases: 16 | description: Whether releases for RC tags (e.g. v0.0.1-rc1) should be created? 17 | required: false 18 | default: false 19 | type: boolean 20 | package-subdirectory: 21 | description: Subdirectory in the repository, where the R package is located. 22 | required: false 23 | type: string 24 | default: "." 25 | workflow_dispatch: 26 | 27 | concurrency: 28 | group: release-${{ github.event.pull_request.number || github.ref }} 29 | cancel-in-progress: true 30 | 31 | jobs: 32 | release: 33 | name: Release 🚀 34 | runs-on: ubuntu-latest 35 | if: "! contains(github.event.commits[0].message, '[skip release]')" 36 | permissions: 37 | contents: write 38 | 39 | steps: 40 | - name: Setup token 🔑 41 | id: github-token 42 | run: | 43 | if [ "${{ secrets.REPO_GITHUB_TOKEN }}" == "" ]; then 44 | echo "REPO_GITHUB_TOKEN is empty. Substituting it with GITHUB_TOKEN." 45 | echo "token=${{ secrets.GITHUB_TOKEN }}" >> $GITHUB_OUTPUT 46 | else 47 | echo "Using REPO_GITHUB_TOKEN." 48 | echo "token=${{ secrets.REPO_GITHUB_TOKEN }}" >> $GITHUB_OUTPUT 49 | fi 50 | shell: bash 51 | 52 | - name: Checkout repo 🛎 53 | uses: actions/checkout@v4.1.1 54 | 55 | - name: Check commit message 💬 56 | run: | 57 | git config --global --add safe.directory $(pwd) 58 | export head_commit_message="$(git show -s --format=%B | tr '\r\n' ' ' | tr '\n' ' ')" 59 | echo "head_commit_message = $head_commit_message" 60 | if [[ $head_commit_message == *"$SKIP_INSTRUCTION"* ]]; then 61 | echo "Skip instruction detected - cancelling the workflow." 62 | exit 1 63 | fi 64 | shell: bash 65 | env: 66 | SKIP_INSTRUCTION: "[skip release]" 67 | 68 | - name: Get branch names 🌿 69 | id: branch-name 70 | uses: tj-actions/branch-names@v7 71 | 72 | - name: Check if running for rc tag 🏷️ 73 | id: rc-tag 74 | run: | 75 | echo "Current tag: ${{ steps.branch-name.outputs.tag }}" 76 | current_tag="${{ steps.branch-name.outputs.tag }}" 77 | if [ "$(echo "$current_tag" | grep -E "^v([0-9]+\.)?([0-9]+\.)?([0-9]+)(-rc[0-9]+)$")" != "" ]; then 78 | echo "Running for rc-tag." 79 | echo "is-rc-tag=true" >> $GITHUB_OUTPUT 80 | else 81 | echo "is-rc-tag=false" >> $GITHUB_OUTPUT 82 | fi 83 | shell: bash 84 | 85 | - name: Generate Changelog 📜 86 | run: | 87 | RELEASE_VERSION=$(awk -F: '/Version:/{gsub(/[ ]+/,"") ; print $2}' DESCRIPTION) 88 | REPOSITORY_NAME="${{ github.event.repository.name }}" 89 | (awk "/^#+.*${REPOSITORY_NAME//./\.}.*${RELEASE_VERSION//./\.}$/{flag=1;next}/^#+.*${REPOSITORY_NAME//./\.}.*/{flag=0}flag" NEWS.md | grep -v "^$" || echo "* ${RELEASE_VERSION}") > RELEASE_BODY.txt 90 | working-directory: ${{ inputs.package-subdirectory }} 91 | 92 | - name: Create release 🌟 93 | if: >- 94 | steps.rc-tag.outputs.is-rc-tag == 'false' || 95 | inputs.create-rc-releases == true 96 | # https://github.com/softprops/action-gh-release/pull/406#issuecomment-1934635958 97 | uses: softprops/action-gh-release@4634c16e79c963813287e889244c50009e7f0981 98 | with: 99 | body_path: ${{ inputs.package-subdirectory }}/RELEASE_BODY.txt 100 | token: ${{ steps.github-token.outputs.token }} 101 | generate_release_notes: true 102 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml.shared: -------------------------------------------------------------------------------- 1 | --- 2 | name: Release 🎈 3 | 4 | on: 5 | push: 6 | tags: 7 | - "v*" 8 | workflow_dispatch: 9 | 10 | jobs: 11 | docs: 12 | name: Pkgdown Docs 📚 13 | needs: release 14 | uses: insightsengineering/r.pkg.template/.github/workflows/pkgdown.yaml@main 15 | secrets: 16 | REPO_GITHUB_TOKEN: ${{ secrets.REPO_GITHUB_TOKEN }} 17 | with: 18 | default-landing-page: latest-tag 19 | validation: 20 | name: R Package Validation report 📃 21 | needs: release 22 | uses: insightsengineering/r.pkg.template/.github/workflows/validation.yaml@main 23 | secrets: 24 | REPO_GITHUB_TOKEN: ${{ secrets.REPO_GITHUB_TOKEN }} 25 | release: 26 | name: Create release 🎉 27 | uses: insightsengineering/r.pkg.template/.github/workflows/release.yaml@main 28 | secrets: 29 | REPO_GITHUB_TOKEN: ${{ secrets.REPO_GITHUB_TOKEN }} 30 | build: 31 | name: Build package and reports 🎁 32 | needs: [release, docs] 33 | uses: insightsengineering/r.pkg.template/.github/workflows/build-check-install.yaml@main 34 | secrets: 35 | REPO_GITHUB_TOKEN: ${{ secrets.REPO_GITHUB_TOKEN }} 36 | with: 37 | additional-env-vars: | 38 | _R_CHECK_CRAN_INCOMING_REMOTE_=false 39 | additional-r-cmd-check-params: --as-cran 40 | enforce-note-blocklist: true 41 | note-blocklist: | 42 | checking dependencies in R code .* NOTE 43 | checking R code for possible problems .* NOTE 44 | checking examples .* NOTE 45 | checking Rd line widths .* NOTE 46 | checking top-level files .* NOTE 47 | checking files in ‘vignettes’ .* NOTE 48 | coverage: 49 | name: Coverage 📔 50 | needs: [release, docs] 51 | uses: insightsengineering/r.pkg.template/.github/workflows/test-coverage.yaml@main 52 | secrets: 53 | REPO_GITHUB_TOKEN: ${{ secrets.REPO_GITHUB_TOKEN }} 54 | wasm: 55 | name: Build WASM packages 🧑‍🏭 56 | needs: release 57 | uses: insightsengineering/r.pkg.template/.github/workflows/wasm.yaml@main 58 | -------------------------------------------------------------------------------- /.github/workflows/revdepcheck.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: revdepcheck ⏪ 3 | 4 | on: 5 | workflow_dispatch: 6 | workflow_call: 7 | secrets: 8 | REPO_GITHUB_TOKEN: 9 | description: | 10 | Github token with read access to repositories, required for dependencies installation 11 | required: false 12 | GCHAT_WEBHOOK: 13 | description: | 14 | Google Chat webhook to send failure notifications 15 | required: false 16 | inputs: 17 | additional-env-vars: 18 | description: | 19 | Extra environment variables, as a 'key=value' pair, with each pair on a new line. 20 | Example usage: 21 | additional-env-vars: | 22 | ABC=123 23 | XYZ=456 24 | required: false 25 | default: "" 26 | type: string 27 | lookup-refs: 28 | description: | 29 | Passed to setup-r-dependencies action. 30 | required: false 31 | default: "" 32 | type: string 33 | 34 | jobs: 35 | revdepcheck: 36 | name: revdepcheck ⏪ 37 | runs-on: ubuntu-latest 38 | if: > 39 | !contains(github.event.commits[0].message, '[skip revdepcheck]') 40 | && github.event.pull_request.draft == false 41 | container: 42 | image: ghcr.io/insightsengineering/rstudio:latest 43 | 44 | steps: 45 | - name: Setup token 🔑 46 | id: github-token 47 | run: | 48 | if [ "${{ secrets.REPO_GITHUB_TOKEN }}" == "" ]; then 49 | echo "REPO_GITHUB_TOKEN is empty. Substituting it with GITHUB_TOKEN." 50 | echo "token=${{ secrets.GITHUB_TOKEN }}" >> $GITHUB_OUTPUT 51 | else 52 | echo "Using REPO_GITHUB_TOKEN." 53 | echo "token=${{ secrets.REPO_GITHUB_TOKEN }}" >> $GITHUB_OUTPUT 54 | fi 55 | shell: bash 56 | 57 | - name: Get branch names 🌿 58 | id: branch-name 59 | uses: tj-actions/branch-names@v7 60 | 61 | - name: Checkout repo 🛎 62 | uses: actions/checkout@v4.1.1 63 | with: 64 | ref: ${{ steps.branch-name.outputs.head_ref_branch }} 65 | fetch-depth: 1 66 | 67 | - name: Restore cache 💰 68 | uses: actions/cache@v4 69 | with: 70 | key: revdepcheck-${{ runner.os }}-${{ github.event.repository.name }} 71 | path: | 72 | ~/.cache/R/pkgcache/pkg 73 | ~/.cache/R-crancache 74 | 75 | - name: Check commit message 💬 76 | run: | 77 | git config --global --add safe.directory $(pwd) 78 | export head_commit_message="$(git show -s --format=%B | tr '\r\n' ' ' | tr '\n' ' ')" 79 | echo "head_commit_message = $head_commit_message" 80 | if [[ $head_commit_message == *"$SKIP_INSTRUCTION"* ]]; then 81 | echo "Skip instruction detected - cancelling the workflow." 82 | exit 1 83 | fi 84 | shell: bash 85 | env: 86 | SKIP_INSTRUCTION: "[skip revdepcheck]" 87 | 88 | - name: Normalize variables 📏 89 | run: | 90 | echo "gchat_webhook=${{ secrets.GCHAT_WEBHOOK }}" >> $GITHUB_ENV 91 | shell: bash 92 | 93 | - name: Install dependencies 94 | uses: insightsengineering/setup-r-dependencies@v1 95 | env: 96 | GITHUB_PAT: ${{ steps.github-token.outputs.token }} 97 | with: 98 | lookup-refs: ${{ inputs.lookup-refs }} 99 | skip-desc-branch: true 100 | 101 | - name: revdepcheck 🔄 102 | id: revdepcheck 103 | uses: insightsengineering/r-revdepcheck-action@main 104 | with: 105 | github-token: ${{ steps.github-token.outputs.token }} 106 | additional-env-vars: ${{ inputs.additional-env-vars }} 107 | 108 | - name: GChat notification 🔔 109 | if: (failure() || cancelled()) && steps.revdepcheck.outcome != 'success' && env.gchat_webhook != '' 110 | uses: insightsengineering/google-chat-notification@master 111 | with: 112 | name: ${{ github.event.repository.name }} - revdepcheck - ${{ env.strategy }} 113 | url: ${{ secrets.GCHAT_WEBHOOK }} 114 | status: ${{ job.status }} 115 | 116 | - name: Prepare revdep artifact 🎁 117 | run: | 118 | rm -rf revdep/library.noindex 119 | rm -rf revdep/library 120 | shell: bash 121 | 122 | - name: Upload artifact ⬆ 123 | uses: actions/upload-artifact@v4 124 | with: 125 | name: revdep 126 | path: revdep/ 127 | -------------------------------------------------------------------------------- /.github/workflows/rhub.yaml: -------------------------------------------------------------------------------- 1 | # This workflow is based on R-hub's generic GitHub Actions workflow file. 2 | # Its canonical location is at 3 | # https://github.com/r-hub/actions/blob/main/workflows/rhub.yaml 4 | 5 | name: R-hub 🌐 6 | 7 | on: 8 | workflow_call: 9 | secrets: 10 | REPO_GITHUB_TOKEN: 11 | description: | 12 | Github token with read access to repositories 13 | required: false 14 | inputs: 15 | config: 16 | description: "A comma separated list of R-hub platforms to use." 17 | type: string 18 | # These platforms correspond roughly to CRAN package checks. 19 | # More information: https://github.com/insightsengineering/idr-tasks/issues/781 20 | # All supported R-hub platforms can be viewed by running: rhub::rhub_platforms() 21 | # Container-based platforms (as opposed to VM-based Windows and macOS platforms) 22 | # can be also viewed here: https://r-hub.github.io/containers/containers.html 23 | # gcc14 = r-devel-linux-x86_64-fedora-gcc 24 | # 25 | # "linux" check (linux (R-devel)) is temporarily disabled because of unexplained timeouts 26 | # in setup-r-dependencies step "Modify DESCRIPTION file (development)" 27 | default: >- 28 | r-devel-linux-x86_64-debian-clang, 29 | r-devel-linux-x86_64-debian-gcc, 30 | gcc14, 31 | r-devel-windows-x86_64, 32 | r-patched-linux-x86_64, 33 | r-release-linux-x86_64, 34 | r-release-macos-arm64, 35 | r-release-macos-x86_64, 36 | r-release-windows-x86_64, 37 | r-oldrel-macos-arm64, 38 | r-oldrel-macos-x86_64, 39 | r-oldrel-windows-x86_64, 40 | gcc13, 41 | noSuggests, 42 | donttest 43 | lookup-refs: 44 | description: | 45 | List of package references to be used for the feature branch. 46 | Multiple entries in new lines or separated by commas. 47 | required: false 48 | default: "" 49 | type: string 50 | 51 | concurrency: 52 | group: r-hub-${{ github.event.pull_request.number || github.ref }} 53 | cancel-in-progress: true 54 | 55 | jobs: 56 | setup: 57 | runs-on: ubuntu-latest 58 | outputs: 59 | containers: ${{ steps.rhub-setup.outputs.containers }} 60 | platforms: ${{ steps.rhub-setup.outputs.platforms }} 61 | 62 | steps: 63 | - uses: r-hub/actions/setup@v1 64 | with: 65 | config: ${{ inputs.config }} 66 | id: rhub-setup 67 | 68 | linux-containers: 69 | needs: setup 70 | if: ${{ needs.setup.outputs.containers != '[]' }} 71 | runs-on: ubuntu-latest 72 | name: ${{ matrix.config.label }} 73 | strategy: 74 | fail-fast: false 75 | matrix: 76 | config: ${{ fromJson(needs.setup.outputs.containers) }} 77 | container: 78 | image: ${{ matrix.config.container }} 79 | 80 | steps: 81 | - name: Setup token 🔑 82 | id: github-token 83 | run: | 84 | if [ "${{ secrets.REPO_GITHUB_TOKEN }}" == "" ]; then 85 | echo "REPO_GITHUB_TOKEN is empty. Substituting it with GITHUB_TOKEN." 86 | echo "token=${{ secrets.GITHUB_TOKEN }}" >> $GITHUB_OUTPUT 87 | else 88 | echo "Using REPO_GITHUB_TOKEN." 89 | echo "token=${{ secrets.REPO_GITHUB_TOKEN }}" >> $GITHUB_OUTPUT 90 | fi 91 | shell: bash 92 | 93 | - uses: r-hub/actions/checkout@v1 94 | 95 | - uses: r-hub/actions/platform-info@v1 96 | with: 97 | job-config: ${{ matrix.config.job-config }} 98 | 99 | - uses: insightsengineering/setup-r-dependencies@v1 100 | env: 101 | GITHUB_PAT: ${{ steps.github-token.outputs.token }} 102 | with: 103 | lookup-refs: ${{ inputs.lookup-refs }} 104 | skip-install: true 105 | restore-description: false 106 | install-quarto: "false" 107 | 108 | - uses: r-hub/actions/setup-deps@v1 109 | with: 110 | job-config: ${{ matrix.config.job-config }} 111 | needs: DepsDev,DepsBranch 112 | env: 113 | GITHUB_PAT: ${{ steps.github-token.outputs.token }} 114 | 115 | - uses: r-hub/actions/run-check@v1 116 | with: 117 | job-config: ${{ matrix.config.job-config }} 118 | 119 | other-platforms: 120 | needs: setup 121 | if: ${{ needs.setup.outputs.platforms != '[]' }} 122 | runs-on: ${{ matrix.config.os }} 123 | name: ${{ matrix.config.label }} 124 | strategy: 125 | fail-fast: false 126 | matrix: 127 | config: ${{ fromJson(needs.setup.outputs.platforms) }} 128 | 129 | steps: 130 | - name: Setup token 🔑 131 | id: github-token 132 | run: | 133 | if [ "${{ secrets.REPO_GITHUB_TOKEN }}" == "" ]; then 134 | echo "REPO_GITHUB_TOKEN is empty. Substituting it with GITHUB_TOKEN." 135 | echo "token=${{ secrets.GITHUB_TOKEN }}" >> $GITHUB_OUTPUT 136 | else 137 | echo "Using REPO_GITHUB_TOKEN." 138 | echo "token=${{ secrets.REPO_GITHUB_TOKEN }}" >> $GITHUB_OUTPUT 139 | fi 140 | shell: bash 141 | 142 | - uses: r-hub/actions/checkout@v1 143 | 144 | - uses: r-hub/actions/setup-r@v1 145 | with: 146 | job-config: ${{ matrix.config.job-config }} 147 | 148 | - uses: r-hub/actions/platform-info@v1 149 | with: 150 | job-config: ${{ matrix.config.job-config }} 151 | 152 | - uses: insightsengineering/setup-r-dependencies@v1 153 | env: 154 | GITHUB_PAT: ${{ steps.github-token.outputs.token }} 155 | with: 156 | lookup-refs: ${{ inputs.lookup-refs }} 157 | skip-install: true 158 | restore-description: false 159 | install-quarto: "false" 160 | 161 | - uses: r-hub/actions/setup-deps@v1 162 | with: 163 | job-config: ${{ matrix.config.job-config }} 164 | needs: DepsDev,DepsBranch 165 | env: 166 | GITHUB_PAT: ${{ steps.github-token.outputs.token }} 167 | 168 | - uses: r-hub/actions/run-check@v1 169 | with: 170 | job-config: ${{ matrix.config.job-config }} 171 | -------------------------------------------------------------------------------- /.github/workflows/roxygen.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Roxygen 🅾 3 | 4 | on: 5 | push: 6 | branches: 7 | - main 8 | pull_request: 9 | types: 10 | - opened 11 | - synchronize 12 | - reopened 13 | - ready_for_review 14 | branches: 15 | - main 16 | workflow_dispatch: 17 | workflow_call: 18 | inputs: 19 | install-system-dependencies: 20 | description: Check for and install system dependencies 21 | required: false 22 | default: false 23 | type: boolean 24 | enable-staged-dependencies-check: 25 | description: Enable staged dependencies YAML check 26 | required: false 27 | default: false 28 | type: boolean 29 | auto-update: 30 | description: If man pages are not up-to-date, they will be automatically updated and committed back to the branch. 31 | required: false 32 | default: false 33 | type: boolean 34 | sd-direction: 35 | description: The direction to use to install staged dependencies. Choose between 'upstream', 'downstream' and 'all' 36 | required: false 37 | type: string 38 | default: upstream 39 | package-subdirectory: 40 | description: Subdirectory in the repository, where the R package is located. 41 | required: false 42 | type: string 43 | default: "." 44 | deps-installation-method: 45 | description: | 46 | Which method for installing R package dependencies to use? Supported values are: 47 | staged-dependencies 48 | setup-r-dependencies 49 | required: false 50 | type: string 51 | default: staged-dependencies 52 | lookup-refs: 53 | description: | 54 | Passed to insightsengineering/setup-r-dependencies. See its documentation. 55 | Used only if deps-installation-method == 'setup-r-dependencies'. 56 | required: false 57 | type: string 58 | default: "" 59 | secrets: 60 | REPO_GITHUB_TOKEN: 61 | description: | 62 | Github token with read access to repositories, required for staged.dependencies installation 63 | required: false 64 | 65 | concurrency: 66 | group: roxygen-${{ github.event.pull_request.number || github.ref }} 67 | cancel-in-progress: true 68 | 69 | jobs: 70 | roxygen: 71 | name: Manual pages check 🏁 72 | runs-on: ubuntu-latest 73 | if: > 74 | !contains(github.event.commits[0].message, '[skip roxygen]') 75 | && github.event.pull_request.draft == false 76 | container: 77 | image: ghcr.io/insightsengineering/rstudio:latest 78 | 79 | steps: 80 | - name: Setup token 🔑 81 | id: github-token 82 | run: | 83 | if [ "${{ secrets.REPO_GITHUB_TOKEN }}" == "" ]; then 84 | echo "REPO_GITHUB_TOKEN is empty. Substituting it with GITHUB_TOKEN." 85 | echo "token=${{ secrets.GITHUB_TOKEN }}" >> $GITHUB_OUTPUT 86 | else 87 | echo "Using REPO_GITHUB_TOKEN." 88 | echo "token=${{ secrets.REPO_GITHUB_TOKEN }}" >> $GITHUB_OUTPUT 89 | fi 90 | shell: bash 91 | 92 | - name: Get branch names 🌿 93 | id: branch-name 94 | uses: tj-actions/branch-names@v7 95 | 96 | - name: Checkout repo (PR) 🛎 97 | uses: actions/checkout@v4.1.1 98 | if: github.event_name == 'pull_request' 99 | with: 100 | ref: ${{ steps.branch-name.outputs.head_ref_branch }} 101 | path: ${{ github.event.repository.name }} 102 | repository: ${{ github.event.pull_request.head.repo.full_name }} 103 | token: ${{ steps.github-token.outputs.token }} 104 | 105 | - name: Checkout repo 🛎 106 | uses: actions/checkout@v4.1.1 107 | if: github.event_name != 'pull_request' 108 | with: 109 | ref: ${{ steps.branch-name.outputs.head_ref_branch }} 110 | path: ${{ github.event.repository.name }} 111 | 112 | - name: Check commit message 💬 113 | run: | 114 | git config --global --add safe.directory $(pwd) 115 | export head_commit_message="$(git show -s --format=%B | tr '\r\n' ' ' | tr '\n' ' ')" 116 | echo "head_commit_message = $head_commit_message" 117 | if [[ $head_commit_message == *"$SKIP_INSTRUCTION"* ]]; then 118 | echo "Skip instruction detected - cancelling the workflow." 119 | exit 1 120 | fi 121 | shell: bash 122 | working-directory: ${{ github.event.repository.name }} 123 | env: 124 | SKIP_INSTRUCTION: "[skip roxygen]" 125 | 126 | - name: Normalize variables 📏 127 | run: | 128 | deps_installation_method="${{ inputs.deps-installation-method }}" 129 | echo "deps_installation_method=${deps_installation_method:-staged-dependencies}" >> $GITHUB_ENV 130 | shell: bash 131 | 132 | - name: Restore SD cache 💰 133 | if: >- 134 | env.deps_installation_method == 'staged-dependencies' 135 | uses: actions/cache@v4 136 | with: 137 | key: sd-${{ runner.os }}-${{ github.event.repository.name }} 138 | path: ~/.staged.dependencies 139 | 140 | - name: Run Staged dependencies 🎦 141 | if: >- 142 | env.deps_installation_method == 'staged-dependencies' 143 | uses: insightsengineering/staged-dependencies-action@v2 144 | env: 145 | GITHUB_PAT: ${{ steps.github-token.outputs.token }} 146 | with: 147 | path: ${{ github.event.repository.name }}/${{ inputs.package-subdirectory }} 148 | enable-check: ${{ inputs.enable-staged-dependencies-check }} 149 | run-system-dependencies: ${{ inputs.install-system-dependencies }} 150 | direction: ${{ inputs.sd-direction }} 151 | 152 | - name: Setup R dependencies 🎦 153 | if: >- 154 | env.deps_installation_method == 'setup-r-dependencies' 155 | uses: insightsengineering/setup-r-dependencies@v1 156 | env: 157 | GITHUB_PAT: ${{ steps.github-token.outputs.token }} 158 | with: 159 | lookup-refs: ${{ inputs.lookup-refs }} 160 | repository-path: ${{ github.event.repository.name }}/${{ inputs.package-subdirectory }} 161 | 162 | - name: Generate man pages 📄 163 | run: | 164 | logfile <- "roxygen_${{ github.event.repository.name }}.log" 165 | con <- file(logfile) 166 | sink(con, append = TRUE, split = TRUE) 167 | sink(con, append = TRUE, type = "message") 168 | roxygen2::roxygenize('.') 169 | sink() 170 | sink(type = "message") 171 | logs <- readLines(logfile) 172 | cat("🪵 Log output of 'roxygen2::roxygenize()':\n") 173 | system2("cat", logfile) 174 | error_marker <- grep("Error:", logs) 175 | warnings_marker <- grep("Warning message", logs) 176 | if (length(warnings_marker) > 0) { 177 | cat("⚠ One or more warnings were generated during the roxygen build:\n") 178 | cat(logs[warnings_marker[[1]]:length(logs)], sep = "\n") 179 | stop("Please 🙏 fix the warnings shown below this message 👇") 180 | } 181 | if (length(error_marker) > 0) { 182 | cat("☠ One or more errors were generated during the roxygen build:\n") 183 | cat(logs[error_marker[[1]]:length(logs)], sep = "\n") 184 | stop("Please 🙏 fix the errors shown below this message 👇") 185 | } 186 | shell: Rscript {0} 187 | working-directory: ${{ github.event.repository.name }}/${{ inputs.package-subdirectory }} 188 | env: 189 | LANG: en_US.UTF-8 190 | 191 | - name: Roxygen check 🅾 192 | run: | 193 | AUTO_UPDATE=${{ inputs.auto-update }} 194 | if [[ -n `git status -s | grep -E "man|DESCRIPTION"` ]] 195 | then { 196 | ROXYGEN_VERSION="$(Rscript -e 'packageVersion("roxygen2")' | awk '{print $NF}')" 197 | echo "🙈 Manuals are not up-to-date with roxygen comments!" 198 | echo "🔀 The following differences were noted:" 199 | git diff man/* DESCRIPTION 200 | # Attempt to commit and push man-page updates 201 | if [ "${AUTO_UPDATE}" == "true" ] 202 | then { 203 | echo "Regenerating man pages via auto-update" 204 | git config --global user.name "github-actions" 205 | git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com" 206 | git config pull.rebase false 207 | BRANCH_NAME="${{ steps.branch-name.outputs.head_ref_branch }}" 208 | git pull origin ${BRANCH_NAME} || true 209 | git add -A man/ DESCRIPTION 210 | git commit -m "[skip roxygen] [skip vbump] Roxygen Man Pages Auto Update" 211 | git push -v origin HEAD:${BRANCH_NAME} || \ 212 | (echo "⚠️ Could not push to ${BRANCH_NAME} on $(git remote -v show -n origin | grep Push)" && \ 213 | AUTO_UPDATE=failed) 214 | } 215 | fi 216 | # If auto-update is disabled or is unsuccessful, let 'em know to fix manually 217 | if [ "${AUTO_UPDATE}" != "true" ] 218 | then { 219 | echo -e "\n💻 Please rerun the following command on your workstation and push your changes" 220 | echo "--------------------------------------------------------------------" 221 | echo "roxygen2::roxygenize('.')" 222 | echo "--------------------------------------------------------------------" 223 | echo "ℹ roxygen2 version that was used in this workflow: $ROXYGEN_VERSION" 224 | echo "🙏 Please ensure that the 'RoxygenNote' field in the DESCRIPTION file matches this version" 225 | exit 1 226 | } 227 | fi 228 | } else { 229 | echo "💚 Manuals are up-to-date with roxygen comments" 230 | } 231 | fi 232 | shell: bash 233 | working-directory: ${{ github.event.repository.name }}/${{ inputs.package-subdirectory }} 234 | -------------------------------------------------------------------------------- /.github/workflows/scheduled.yaml.shared: -------------------------------------------------------------------------------- 1 | --- 2 | name: Scheduled 🕰️ 3 | 4 | on: 5 | schedule: 6 | - cron: '45 3 * * 0' 7 | workflow_dispatch: 8 | 9 | jobs: 10 | dependency-test: 11 | strategy: 12 | fail-fast: false 13 | matrix: 14 | test-strategy: ["min_cohort", "min_isolated", "release", "max"] 15 | uses: insightsengineering/r.pkg.template/.github/workflows/verdepcheck.yaml@main 16 | name: Dependency Test - ${{ matrix.test-strategy }} 🔢 17 | secrets: 18 | REPO_GITHUB_TOKEN: ${{ secrets.REPO_GITHUB_TOKEN }} 19 | GCHAT_WEBHOOK: ${{ secrets.GCHAT_WEBHOOK }} 20 | with: 21 | strategy: ${{ matrix.test-strategy }} 22 | additional-env-vars: | 23 | PKG_SYSREQS_DRY_RUN=true 24 | branch-cleanup: 25 | name: Branch Cleanup 🧹 26 | uses: insightsengineering/r.pkg.template/.github/workflows/branch-cleanup.yaml@main 27 | secrets: 28 | REPO_GITHUB_TOKEN: ${{ secrets.REPO_GITHUB_TOKEN }} 29 | revdepcheck: 30 | name: revdepcheck ↩️ 31 | uses: insightsengineering/r.pkg.template/.github/workflows/revdepcheck.yaml@main 32 | rhub: 33 | name: R-hub 🌐 34 | uses: insightsengineering/r.pkg.template/.github/workflows/rhub.yaml@main 35 | -------------------------------------------------------------------------------- /.github/workflows/spelling.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Spelling 🆎 3 | 4 | on: 5 | push: 6 | branches: 7 | - main 8 | pull_request: 9 | types: 10 | - opened 11 | - synchronize 12 | - reopened 13 | - ready_for_review 14 | branches: 15 | - main 16 | workflow_dispatch: 17 | workflow_call: 18 | secrets: 19 | REPO_GITHUB_TOKEN: 20 | description: Github token with write access to the repo 21 | required: false 22 | inputs: 23 | package-subdirectory: 24 | description: Subdirectory in the repository, where the R package is located. 25 | required: false 26 | type: string 27 | default: "." 28 | exclude: 29 | description: Comma separated list of files or folders to exclude from spellcheck. Accepts globs. 30 | type: string 31 | default: "inst/extdata/*" 32 | required: false 33 | 34 | concurrency: 35 | group: spelling-${{ github.event.pull_request.number || github.ref }} 36 | cancel-in-progress: true 37 | 38 | jobs: 39 | spelling: 40 | name: Check spelling 🔠 41 | runs-on: ubuntu-latest 42 | if: > 43 | !contains(github.event.commits[0].message, '[skip spelling]') 44 | && github.event.pull_request.draft == false 45 | container: 46 | image: ghcr.io/insightsengineering/rstudio:latest 47 | 48 | steps: 49 | - name: Setup token 🔑 50 | id: github-token 51 | run: | 52 | if [ "${{ secrets.REPO_GITHUB_TOKEN }}" == "" ]; then 53 | echo "REPO_GITHUB_TOKEN is empty. Substituting it with GITHUB_TOKEN." 54 | echo "token=${{ secrets.GITHUB_TOKEN }}" >> $GITHUB_OUTPUT 55 | else 56 | echo "Using REPO_GITHUB_TOKEN." 57 | echo "token=${{ secrets.REPO_GITHUB_TOKEN }}" >> $GITHUB_OUTPUT 58 | fi 59 | shell: bash 60 | 61 | - name: Get branch names 🌿 62 | id: branch-name 63 | uses: tj-actions/branch-names@v7 64 | 65 | - name: Checkout repo (PR) 🛎 66 | uses: actions/checkout@v4.1.1 67 | if: github.event_name == 'pull_request' 68 | with: 69 | ref: ${{ steps.branch-name.outputs.head_ref_branch }} 70 | repository: ${{ github.event.pull_request.head.repo.full_name }} 71 | fetch-depth: 1 72 | token: ${{ steps.github-token.outputs.token }} 73 | 74 | - name: Checkout repo 🛎 75 | uses: actions/checkout@v4.1.1 76 | if: github.event_name != 'pull_request' 77 | with: 78 | ref: ${{ steps.branch-name.outputs.head_ref_branch }} 79 | token: ${{ steps.github-token.outputs.token }} 80 | 81 | - name: Check commit message 💬 82 | run: | 83 | git config --global --add safe.directory $(pwd) 84 | export head_commit_message="$(git show -s --format=%B | tr '\r\n' ' ' | tr '\n' ' ')" 85 | echo "head_commit_message = $head_commit_message" 86 | if [[ $head_commit_message == *"$SKIP_INSTRUCTION"* ]]; then 87 | echo "Skip instruction detected - cancelling the workflow." 88 | exit 1 89 | fi 90 | shell: bash 91 | env: 92 | SKIP_INSTRUCTION: "[skip spelling]" 93 | 94 | - name: Normalize variables 📏 95 | run: | 96 | package_subdirectory_input="${{ inputs.package-subdirectory }}" 97 | echo "package_subdirectory=${package_subdirectory_input:-.}" >> $GITHUB_ENV 98 | shell: bash 99 | 100 | - name: Run Spellcheck 👟 101 | uses: insightsengineering/r-spellcheck-action@v2 102 | with: 103 | exclude: ${{ inputs.exclude }} 104 | path: ${{ env.package_subdirectory }} 105 | 106 | - name: Clean up WORDLIST 🧼 107 | if: github.event_name == 'push' 108 | run: | 109 | x <- readLines('inst/WORDLIST') 110 | file.remove('inst/WORDLIST') 111 | spelling::update_wordlist(confirm = FALSE) 112 | y <- readLines('inst/WORDLIST') 113 | if (length(setdiff(y, x)) == 0 && length(setdiff(x, y)) > 0) { 114 | message("Unnecessary entries on WORDLIST:") 115 | message(cat(setdiff(x, y), sep='\n')) 116 | } 117 | shell: Rscript {0} 118 | 119 | - name: Checkout to main 🛎 120 | if: github.event_name == 'push' 121 | run: | 122 | git config --global --add safe.directory $(pwd) 123 | git fetch origin main 124 | git checkout main 125 | git pull origin main 126 | 127 | - name: Set file pattern to commit ⚙️ 128 | if: github.event_name == 'push' 129 | id: file-pattern 130 | run: | 131 | if [[ "${{ inputs.package-subdirectory }}" == "." ]]; then 132 | FILE_PATTERN="inst/WORDLIST" 133 | else 134 | FILE_PATTERN="${{ inputs.package-subdirectory }}/inst/WORDLIST" 135 | fi 136 | echo "file-pattern=$FILE_PATTERN" >> $GITHUB_OUTPUT 137 | shell: bash 138 | 139 | - name: Commit and push changes 📌 140 | if: github.event_name == 'push' 141 | uses: stefanzweifel/git-auto-commit-action@v5 142 | with: 143 | commit_message: "[skip ci] Update WORDLIST" 144 | file_pattern: "${{ steps.file-pattern.outputs.file-pattern }}" 145 | commit_user_name: github-actions 146 | commit_user_email: >- 147 | 41898282+github-actions[bot]@users.noreply.github.com 148 | continue-on-error: true 149 | -------------------------------------------------------------------------------- /.github/workflows/style.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Style 🎽 3 | 4 | on: 5 | push: 6 | branches: 7 | - main 8 | pull_request: 9 | types: 10 | - opened 11 | - synchronize 12 | - reopened 13 | - ready_for_review 14 | branches: 15 | - main 16 | workflow_dispatch: 17 | workflow_call: 18 | secrets: 19 | REPO_GITHUB_TOKEN: 20 | description: | 21 | Github token with write access to the repo 22 | required: false 23 | inputs: 24 | auto-update: 25 | description: If R code style is not up-to-date, styling will automatically be applied and restyled files will be automatically committed o the branch. 26 | required: false 27 | default: false 28 | type: boolean 29 | package-subdirectory: 30 | description: Subdirectory in the repository, where the R package is located. 31 | required: false 32 | type: string 33 | default: "." 34 | 35 | concurrency: 36 | group: style-${{ github.event.pull_request.number || github.ref }} 37 | cancel-in-progress: true 38 | 39 | jobs: 40 | style: 41 | name: Check code style 🔠 42 | runs-on: ubuntu-latest 43 | if: > 44 | !contains(github.event.commits[0].message, '[skip style]') 45 | && github.event.pull_request.draft == false 46 | container: 47 | image: ghcr.io/insightsengineering/rstudio:latest 48 | 49 | steps: 50 | - name: Setup token 🔑 51 | id: github-token 52 | run: | 53 | if [ "${{ secrets.REPO_GITHUB_TOKEN }}" == "" ]; then 54 | echo "REPO_GITHUB_TOKEN is empty. Substituting it with GITHUB_TOKEN." 55 | echo "token=${{ secrets.GITHUB_TOKEN }}" >> $GITHUB_OUTPUT 56 | else 57 | echo "Using REPO_GITHUB_TOKEN." 58 | echo "token=${{ secrets.REPO_GITHUB_TOKEN }}" >> $GITHUB_OUTPUT 59 | fi 60 | shell: bash 61 | 62 | - name: Get branch names 🌿 63 | id: branch-name 64 | uses: tj-actions/branch-names@v7 65 | 66 | - name: Checkout repo (PR) 🛎 67 | uses: actions/checkout@v4.1.1 68 | if: github.event_name == 'pull_request' 69 | with: 70 | ref: ${{ steps.branch-name.outputs.head_ref_branch }} 71 | path: ${{ github.event.repository.name }} 72 | repository: ${{ github.event.pull_request.head.repo.full_name }} 73 | fetch-depth: 0 74 | token: ${{ steps.github-token.outputs.token }} 75 | 76 | - name: Checkout repo 🛎 77 | uses: actions/checkout@v4.1.1 78 | if: github.event_name != 'pull_request' 79 | with: 80 | ref: ${{ steps.branch-name.outputs.head_ref_branch }} 81 | path: ${{ github.event.repository.name }} 82 | token: ${{ steps.github-token.outputs.token }} 83 | 84 | - name: Check commit message 💬 85 | run: | 86 | git config --global --add safe.directory $(pwd) 87 | export head_commit_message="$(git show -s --format=%B | tr '\r\n' ' ' | tr '\n' ' ')" 88 | echo "head_commit_message = $head_commit_message" 89 | if [[ $head_commit_message == *"$SKIP_INSTRUCTION"* ]]; then 90 | echo "Skip instruction detected - cancelling the workflow." 91 | exit 1 92 | fi 93 | shell: bash 94 | working-directory: ${{ github.event.repository.name }} 95 | env: 96 | SKIP_INSTRUCTION: "[skip style]" 97 | 98 | - name: Install styler 👚 99 | run: | 100 | install.packages("styler", repos = "https://cloud.r-project.org") 101 | shell: Rscript {0} 102 | 103 | - name: Get changed files 🗞 104 | id: changed-files 105 | # v45.0.8 106 | uses: tj-actions/changed-files@a284dc1814e3fd07f2e34267fc8f81227ed29fb8 107 | with: 108 | path: ${{ github.event.repository.name }} 109 | separator: "," 110 | files: | 111 | **/*.R 112 | **/*.Rmd 113 | **/*.Rnw 114 | **/*.Rmarkdown 115 | **/*.qmd 116 | continue-on-error: true 117 | 118 | - name: Run styler 👟 119 | run: | 120 | changed_files <- unlist(strsplit( 121 | "${{ steps.changed-files.outputs.all_changed_files }}", 122 | split="," 123 | )) 124 | is_r_file <- function(x) { 125 | ext <- tools::file_ext(x) 126 | ext %in% c("R", "Rmd", "Rnw", "Rmarkdown", "qmd") 127 | } 128 | changed_r_files <- Filter(is_r_file, changed_files) 129 | dry <- if(isTRUE(as.logical("${{ inputs.auto-update }}"))) "off" else "on" 130 | detect <- styler::style_file(changed_r_files, dry = dry) 131 | if (TRUE %in% detect$changed) { 132 | problems <- subset(detect$file, detect$changed == T) 133 | dput(problems, file = "/tmp/style-problems.R") 134 | writeLines( 135 | problems, 136 | con = "/tmp/style-problems.txt", 137 | sep = " " 138 | ) 139 | } 140 | shell: Rscript {0} 141 | working-directory: ${{ github.event.repository.name }}/${{ inputs.package-subdirectory }} 142 | env: 143 | LANG: en_US.UTF-8 144 | 145 | - name: Check file existence 🤔 146 | id: check_files 147 | uses: andstor/file-existence-action@v3 148 | with: 149 | files: "/tmp/style-problems.R, /tmp/style-problems.txt" 150 | 151 | - name: Get problematic files 🧟‍♂️ 152 | id: problem-files 153 | if: steps.check_files.outputs.files_exists == 'true' 154 | run: | 155 | perl -p -i -e 's/\R//g;' /tmp/style-problems.R 156 | echo "unstyled-files=$(cat /tmp/style-problems.txt)" >> $GITHUB_OUTPUT 157 | shell: bash 158 | 159 | - name: Autocommit styled files ↗️ 160 | id: autocommit-styled-files 161 | if: > 162 | inputs.auto-update 163 | && steps.check_files.outputs.files_exists == 'true' 164 | run: | 165 | git config --global user.name 'github-actions' 166 | git config --global user.email '41898282+github-actions[bot]@users.noreply.github.com' 167 | git config pull.rebase false 168 | git pull origin ${{ steps.branch-name.outputs.head_ref_branch }} || true 169 | git add ${{ steps.problem-files.outputs.unstyled-files }} 170 | git commit -m '[skip style] [skip vbump] Restyle files' 171 | git push -v origin HEAD:${{ steps.branch-name.outputs.head_ref_branch }} || \ 172 | echo "⚠️ Could not push to ${BRANCH_NAME} on $(git remote -v show -n origin | grep Push)" 173 | shell: bash 174 | working-directory: ${{ github.event.repository.name }}/${{ inputs.package-subdirectory }} 175 | continue-on-error: true 176 | 177 | - name: Styler check summary 🅾 178 | if: > 179 | (inputs.auto-update != true 180 | && steps.check_files.outputs.files_exists == 'true') 181 | || (steps.autocommit-styled-files.outcome != 'success' 182 | && steps.autocommit-styled-files.outcome != 'skipped') 183 | run: | 184 | cat(paste( 185 | "☠ One or more files had styling errors.", 186 | "Please see the log above for remediations,", 187 | "or simply run the following commands", 188 | "for an immediate fix:\n" 189 | )) 190 | if (file.exists("/tmp/style-problems.R")) { 191 | cat("────────────────────────────────────────\n") 192 | cat(paste0( 193 | "styler::style_file(", 194 | readLines("/tmp/style-problems.R", warn=FALSE), 195 | ")\n" 196 | )) 197 | cat("────────────────────────────────────────\n") 198 | } 199 | quit(status = 1) 200 | shell: Rscript {0} 201 | -------------------------------------------------------------------------------- /.github/workflows/test-coverage.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Coverage 📔 3 | 4 | on: 5 | push: 6 | tags: 7 | - "v*" 8 | branches: 9 | - main 10 | pull_request: 11 | types: 12 | - opened 13 | - synchronize 14 | - reopened 15 | - ready_for_review 16 | branches: 17 | - main 18 | workflow_dispatch: 19 | workflow_call: 20 | inputs: 21 | install-system-dependencies: 22 | description: Check for and install system dependencies 23 | required: false 24 | default: false 25 | type: boolean 26 | enable-staged-dependencies-check: 27 | description: Enable staged dependencies YAML check 28 | required: false 29 | default: false 30 | type: boolean 31 | publish-coverage-report: 32 | description: Publish the coverage report as a pull request comment 33 | required: false 34 | default: true 35 | type: boolean 36 | allow-failure: 37 | description: Allow workflow failure if errors from covtracer are generated 38 | required: false 39 | type: boolean 40 | default: false 41 | enable-covtracer: 42 | description: Enable the covtracer job 43 | required: false 44 | type: boolean 45 | default: false 46 | additional-env-vars: 47 | description: | 48 | Extra environment variables, as a 'key=value' pair, with each pair on a new line. 49 | Example usage: 50 | additional-env-vars: | 51 | ABC=123 52 | XYZ=456 53 | required: false 54 | default: "" 55 | type: string 56 | sd-direction: 57 | description: The direction to use to install staged dependencies. Choose between 'upstream', 'downstream' and 'all' 58 | required: false 59 | type: string 60 | default: upstream 61 | publish-coverage-report-gh-pages: 62 | description: Publish HTML coverage report to GitHub pages alongside pkgdown docs. 63 | required: false 64 | type: boolean 65 | default: true 66 | latest-tag-alt-name: 67 | description: | 68 | The name of directory to store coverage report when running for latest tag. 69 | The variable is named this way to keep it consistent with r-pkgdown-multiversion input name. 70 | required: false 71 | type: string 72 | default: "latest-tag" 73 | release-candidate-alt-name: 74 | description: | 75 | The name of directory to store coverage report when running for rc tag. 76 | The variable is named this way to keep it consistent with r-pkgdown-multiversion input name. 77 | required: false 78 | type: string 79 | default: "release-candidate" 80 | package-subdirectory: 81 | description: Subdirectory in the repository, where the R package is located. 82 | required: false 83 | type: string 84 | default: "" 85 | deps-installation-method: 86 | description: | 87 | Which method for installing R package dependencies to use? Supported values are: 88 | staged-dependencies 89 | setup-r-dependencies 90 | required: false 91 | type: string 92 | default: staged-dependencies 93 | lookup-refs: 94 | description: | 95 | List of package references to be used by setup-r-dependencies action if deps-installation-method == 'setup-r-dependencies'. 96 | required: false 97 | type: string 98 | default: "" 99 | secrets: 100 | REPO_GITHUB_TOKEN: 101 | description: | 102 | Github token with read access to repositories, required 103 | for staged.dependencies installation 104 | required: false 105 | 106 | concurrency: 107 | group: coverage-${{ github.event.pull_request.number || github.ref }} 108 | cancel-in-progress: true 109 | 110 | jobs: 111 | coverage: 112 | name: Coverage 📔 113 | runs-on: ubuntu-latest 114 | if: > 115 | !contains(github.event.commits[0].message, '[skip coverage]') 116 | && github.event.pull_request.draft == false 117 | container: 118 | image: ghcr.io/insightsengineering/rstudio:latest 119 | outputs: 120 | publish-coverage-html-report: ${{ steps.coverage-output.outputs.coverage-upload }} 121 | current-branch-or-tag: ${{ steps.current-branch-or-tag.outputs.ref-name }} 122 | is-latest-tag: ${{ steps.current-branch-or-tag.outputs.is-latest-tag }} 123 | is-rc-tag: ${{ steps.current-branch-or-tag.outputs.is-rc-tag }} 124 | multiversion-docs: ${{ steps.current-branch-or-tag.outputs.multiversion-docs }} 125 | 126 | steps: 127 | - name: Setup token 🔑 128 | id: github-token 129 | run: | 130 | if [ "${{ secrets.REPO_GITHUB_TOKEN }}" == "" ]; then 131 | echo "REPO_GITHUB_TOKEN is empty. Substituting it with GITHUB_TOKEN." 132 | echo "token=${{ secrets.GITHUB_TOKEN }}" >> $GITHUB_OUTPUT 133 | else 134 | echo "Using REPO_GITHUB_TOKEN." 135 | echo "token=${{ secrets.REPO_GITHUB_TOKEN }}" >> $GITHUB_OUTPUT 136 | fi 137 | shell: bash 138 | 139 | - name: Get branch names 🌿 140 | id: branch-name 141 | uses: tj-actions/branch-names@v7 142 | 143 | - name: Checkout gh-pages 🛎 144 | if: >- 145 | github.event_name != 'pull_request' 146 | && inputs.publish-coverage-report-gh-pages == true 147 | id: checkout-gh-pages 148 | uses: actions/checkout@v4.1.1 149 | with: 150 | ref: gh-pages 151 | path: gh-pages 152 | repository: ${{ github.event.pull_request.head.repo.full_name }} 153 | 154 | - name: Get current branch or tag 🏷️ 155 | if: >- 156 | github.event_name != 'pull_request' 157 | && inputs.publish-coverage-report-gh-pages == true 158 | id: current-branch-or-tag 159 | run: | 160 | if [ "${{ steps.branch-name.outputs.is_tag }}" == "true" ]; then 161 | echo "Current tag: ${{ steps.branch-name.outputs.tag }}" 162 | echo "ref-name=${{ steps.branch-name.outputs.tag }}" >> $GITHUB_OUTPUT 163 | current_tag="${{ steps.branch-name.outputs.tag }}" 164 | if [ "$(echo "$current_tag" | grep -E "^v([0-9]+\.)?([0-9]+\.)?([0-9]+)$")" != "" ]; then 165 | echo "Running for latest-tag." 166 | echo "is-latest-tag=true" >> $GITHUB_OUTPUT 167 | elif [ "$(echo "$current_tag" | grep -E "^v([0-9]+\.)?([0-9]+\.)?([0-9]+)(-rc[0-9]+)$")" != "" ]; then 168 | echo "Running for rc-tag." 169 | echo "is-rc-tag=true" >> $GITHUB_OUTPUT 170 | fi 171 | else 172 | echo "Current branch: ${{ steps.branch-name.outputs.current_branch }}" 173 | echo "ref-name=${{ steps.branch-name.outputs.current_branch }}" >> $GITHUB_OUTPUT 174 | fi 175 | # Check if pkgdown multiversion docs are used at all. 176 | if [ $(grep -rl '<!-- Generated by pkgdown + https://github.com/insightsengineering/r-pkgdown-multiversion -->' gh-pages | wc -l) -gt 0 ]; then 177 | echo "multiversion-docs=true" >> $GITHUB_OUTPUT 178 | else 179 | echo "multiversion-docs=false" >> $GITHUB_OUTPUT 180 | fi 181 | shell: bash 182 | 183 | - name: Checkout repo (PR) 🛎 184 | uses: actions/checkout@v4.1.1 185 | if: github.event_name == 'pull_request' 186 | with: 187 | ref: ${{ steps.branch-name.outputs.head_ref_branch }} 188 | path: ${{ github.event.repository.name }} 189 | repository: ${{ github.event.pull_request.head.repo.full_name }} 190 | 191 | - name: Checkout repo 🛎 192 | uses: actions/checkout@v4.1.1 193 | if: github.event_name != 'pull_request' 194 | with: 195 | ref: ${{ steps.branch-name.outputs.head_ref_branch }} 196 | path: ${{ github.event.repository.name }} 197 | 198 | - name: Check commit message 💬 199 | run: | 200 | git config --global --add safe.directory $(pwd) 201 | export head_commit_message="$(git show -s --format=%B | tr '\r\n' ' ' | tr '\n' ' ')" 202 | echo "head_commit_message = $head_commit_message" 203 | if [[ $head_commit_message == *"$SKIP_INSTRUCTION"* ]]; then 204 | echo "Skip instruction detected - cancelling the workflow." 205 | exit 1 206 | fi 207 | shell: bash 208 | working-directory: ${{ github.event.repository.name }} 209 | env: 210 | SKIP_INSTRUCTION: "[skip coverage]" 211 | 212 | - name: Normalize inputs ⊳ 213 | id: normalizer 214 | shell: bash 215 | run: | 216 | echo "publish-coverage-report=${{ inputs.publish-coverage-report }}" >> $GITHUB_OUTPUT 217 | if [ "${{ inputs.publish-coverage-report }}" == "" ] 218 | then { 219 | echo "publish-coverage-report=true" >> $GITHUB_OUTPUT 220 | } 221 | fi 222 | 223 | - name: Restore SD cache 💰 224 | if: >- 225 | inputs.deps-installation-method == 'staged-dependencies' 226 | uses: actions/cache@v4 227 | with: 228 | key: sd-${{ runner.os }}-${{ github.event.repository.name }} 229 | path: ~/.staged.dependencies 230 | 231 | - name: Run Staged dependencies 🎦 232 | if: >- 233 | inputs.deps-installation-method == 'staged-dependencies' 234 | uses: insightsengineering/staged-dependencies-action@v2 235 | env: 236 | GITHUB_PAT: ${{ secrets.REPO_GITHUB_TOKEN }} 237 | with: 238 | path: ${{ github.event.repository.name }}/${{ inputs.package-subdirectory }} 239 | enable-check: ${{ inputs.enable-staged-dependencies-check }} 240 | run-system-dependencies: ${{ inputs.install-system-dependencies }} 241 | direction: ${{ inputs.sd-direction }} 242 | 243 | - name: Setup R dependencies 🎦 244 | if: >- 245 | inputs.deps-installation-method == 'setup-r-dependencies' 246 | uses: insightsengineering/setup-r-dependencies@v1 247 | env: 248 | GITHUB_PAT: ${{ steps.github-token.outputs.token }} 249 | with: 250 | lookup-refs: ${{ inputs.lookup-refs }} 251 | repository-path: ${{ github.event.repository.name }}/${{ inputs.package-subdirectory }} 252 | 253 | - name: Install R package 🚧 254 | run: | 255 | if (file.exists("renv.lock")) renv::restore() 256 | install.packages(".", repos=NULL, type="source") 257 | shell: Rscript {0} 258 | working-directory: ${{ github.event.repository.name }}/${{ inputs.package-subdirectory }} 259 | 260 | - name: Install covr 🎪 261 | run: | 262 | if (file.exists("renv.lock")) { 263 | renv::restore() 264 | if (!require("covr")) renv::install("covr") 265 | } 266 | if (!require("covr")) install.packages("covr") 267 | shell: Rscript {0} 268 | 269 | - name: Run coverage 👟 270 | run: | 271 | setwd("${{ github.event.repository.name }}/${{ inputs.package-subdirectory }}") 272 | if (file.exists("renv.lock")) { 273 | renv::restore() 274 | } 275 | # Load extra env vars 276 | extra_env_vars <- "${{ inputs.additional-env-vars }}" 277 | if (extra_env_vars != "") { 278 | writeLines(extra_env_vars, "/tmp/dotenv.env") 279 | if (!require("dotenv")) install.packages("dotenv", repos = "https://cloud.r-project.org") 280 | dotenv::load_dot_env("/tmp/dotenv.env") 281 | } 282 | tryCatch( 283 | expr = { 284 | x <- covr::package_coverage( 285 | clean = FALSE, 286 | quiet = FALSE 287 | ) 288 | print(x) 289 | covr::report( 290 | x, 291 | file = "coverage-report.html", 292 | browse = FALSE 293 | ) 294 | covr::to_cobertura(x, filename = "coverage.xml") 295 | p <- covr::percent_coverage(x) 296 | cat(p, file = "coverage.txt", sep = "") 297 | }, 298 | error = function(e) { 299 | message("Errors generated during coverage analysis:") 300 | print(e) 301 | error_file <- list.files(path = "/tmp", pattern="*.fail$", recursive = T, full.names = T)[1] 302 | if (length(error_file) && file.exists(error_file)) { 303 | cat("__________FULL OUTPUT__________") 304 | writeLines(readLines(error_file)) 305 | } 306 | } 307 | ) 308 | covr_html <- "coverage-report.html" 309 | if (!file.exists(covr_html)) writeLines(c("No coverage report."), covr_html) 310 | shell: Rscript {0} 311 | 312 | - name: Check whether coverage reports exists 💭 313 | id: check_coverage_reports 314 | uses: andstor/file-existence-action@v3 315 | with: 316 | files: >- 317 | ${{ github.event.repository.name }}/${{ inputs.package-subdirectory }}/coverage.xml, 318 | ${{ github.event.repository.name }}/${{ inputs.package-subdirectory }}/coverage.txt, 319 | ${{ github.event.repository.name }}/${{ inputs.package-subdirectory }}/coverage-report.html 320 | 321 | - name: Post coverage report 🗞 322 | if: steps.check_coverage_reports.outputs.files_exists == 'true' 323 | uses: insightsengineering/coverage-action@v2 324 | with: 325 | path: ${{ github.event.repository.name }}/${{ inputs.package-subdirectory }}/coverage.xml 326 | threshold: 80 327 | fail: false 328 | publish: ${{ steps.normalizer.outputs.publish-coverage-report }} 329 | diff: true 330 | continue-on-error: true 331 | 332 | - name: Upload report 🔼 333 | uses: actions/upload-artifact@v4 334 | with: 335 | name: coverage-report 336 | path: | 337 | ${{ github.event.repository.name }}/${{ inputs.package-subdirectory }}/coverage-report.html 338 | ${{ github.event.repository.name }}/${{ inputs.package-subdirectory }}/lib/ 339 | continue-on-error: true 340 | 341 | - name: Set output ⚙️ 342 | id: coverage-output 343 | if: >- 344 | github.event_name != 'pull_request' 345 | && inputs.publish-coverage-report-gh-pages == true 346 | run: echo "coverage-upload=true" >> $GITHUB_OUTPUT 347 | 348 | covtracer: 349 | name: Covtracer 🐄 350 | runs-on: ubuntu-latest 351 | if: > 352 | !contains(github.event.commits[0].message, '[skip coverage]') 353 | && github.event.pull_request.draft == false 354 | && contains(inputs.enable-covtracer, 'true') 355 | container: 356 | image: ghcr.io/insightsengineering/rstudio:latest 357 | 358 | steps: 359 | - name: Get branch names 🌿 360 | id: branch-name 361 | uses: tj-actions/branch-names@v7 362 | 363 | - name: Checkout repo (PR) 🛎 364 | uses: actions/checkout@v4.1.1 365 | if: github.event_name == 'pull_request' 366 | with: 367 | ref: ${{ steps.branch-name.outputs.head_ref_branch }} 368 | path: ${{ github.event.repository.name }} 369 | repository: ${{ github.event.pull_request.head.repo.full_name }} 370 | 371 | - name: Checkout repo 🛎 372 | uses: actions/checkout@v4.1.1 373 | if: github.event_name != 'pull_request' 374 | with: 375 | ref: ${{ steps.branch-name.outputs.head_ref_branch }} 376 | path: ${{ github.event.repository.name }} 377 | 378 | - name: Check commit message 💬 379 | run: | 380 | git config --global --add safe.directory $(pwd) 381 | export head_commit_message="$(git show -s --format=%B | tr '\r\n' ' ' | tr '\n' ' ')" 382 | echo "head_commit_message = $head_commit_message" 383 | if [[ $head_commit_message == *"$SKIP_INSTRUCTION"* ]]; then 384 | echo "Skip instruction detected - cancelling the workflow." 385 | exit 1 386 | fi 387 | shell: bash 388 | working-directory: ${{ github.event.repository.name }} 389 | env: 390 | SKIP_INSTRUCTION: "[skip coverage]" 391 | 392 | - name: Restore SD cache 💰 393 | uses: actions/cache@v4 394 | with: 395 | key: sd-${{ runner.os }}-${{ github.event.repository.name }} 396 | path: ~/.staged.dependencies 397 | 398 | - name: Run Staged dependencies 🎦 399 | uses: insightsengineering/staged-dependencies-action@v2 400 | env: 401 | GITHUB_PAT: ${{ steps.github-token.outputs.token }} 402 | with: 403 | path: ${{ github.event.repository.name }}/${{ inputs.package-subdirectory }} 404 | enable-check: ${{ inputs.enable-staged-dependencies-check }} 405 | run-system-dependencies: ${{ inputs.install-system-dependencies }} 406 | direction: ${{ inputs.sd-direction }} 407 | 408 | - name: Run Covtracer 🐄 409 | uses: insightsengineering/covtracer-action@v1 410 | env: 411 | GITHUB_PAT: ${{ steps.github-token.outputs.token }} 412 | with: 413 | path: ${{ github.event.repository.name }}/${{ inputs.package-subdirectory }} 414 | allow-failure: ${{ inputs.allow-failure }} 415 | 416 | publish-coverage-report: 417 | name: Publish coverage report 📰 418 | runs-on: ubuntu-latest 419 | needs: coverage 420 | if: > 421 | needs.coverage.outputs.publish-coverage-html-report == 'true' 422 | && github.event_name != 'pull_request' 423 | # Only one job can publish to gh-pages branch concurrently. 424 | concurrency: 425 | group: ghpages 426 | steps: 427 | - name: Setup token 🔑 428 | id: github-token 429 | run: | 430 | if [ "${{ secrets.REPO_GITHUB_TOKEN }}" == "" ]; then 431 | echo "REPO_GITHUB_TOKEN is empty. Substituting it with GITHUB_TOKEN." 432 | echo "token=${{ secrets.GITHUB_TOKEN }}" >> $GITHUB_OUTPUT 433 | else 434 | echo "Using REPO_GITHUB_TOKEN." 435 | echo "token=${{ secrets.REPO_GITHUB_TOKEN }}" >> $GITHUB_OUTPUT 436 | fi 437 | shell: bash 438 | 439 | - name: Download coverage report as artifact ⤵️ 440 | uses: actions/download-artifact@v4 441 | with: 442 | name: coverage-report 443 | path: coverage-report 444 | 445 | - name: Rename report ⚙️ 446 | run: mv coverage-report/coverage-report.html coverage-report/index.html 447 | 448 | - name: Upload coverage report to GitHub pages 🗞️ 449 | if: needs.coverage.outputs.multiversion-docs == 'true' 450 | uses: peaceiris/actions-gh-pages@v4 451 | with: 452 | github_token: ${{ steps.github-token.outputs.token }} 453 | publish_dir: ./coverage-report 454 | destination_dir: ${{ needs.coverage.outputs.current-branch-or-tag }}/coverage-report 455 | 456 | - name: Upload coverage report to GitHub pages (latest-tag) 🏷️ 457 | if: > 458 | needs.coverage.outputs.is-latest-tag == 'true' 459 | && needs.coverage.outputs.multiversion-docs == 'true' 460 | uses: peaceiris/actions-gh-pages@v4 461 | with: 462 | github_token: ${{ steps.github-token.outputs.token }} 463 | publish_dir: ./coverage-report 464 | destination_dir: ${{ inputs.latest-tag-alt-name }}/coverage-report 465 | 466 | - name: Upload coverage report to GitHub pages (release-candidate) 🏷️ 467 | if: > 468 | needs.coverage.outputs.is-rc-tag == 'true' 469 | && needs.coverage.outputs.multiversion-docs == 'true' 470 | uses: peaceiris/actions-gh-pages@v4 471 | with: 472 | github_token: ${{ steps.github-token.outputs.token }} 473 | publish_dir: ./coverage-report 474 | destination_dir: ${{ inputs.release-candidate-alt-name }}/coverage-report 475 | 476 | - name: Upload coverage report to GitHub pages (non-multiversion) 🗞️ 477 | if: needs.coverage.outputs.multiversion-docs == 'false' 478 | uses: peaceiris/actions-gh-pages@v4 479 | with: 480 | github_token: ${{ steps.github-token.outputs.token }} 481 | publish_dir: ./coverage-report 482 | destination_dir: coverage-report 483 | -------------------------------------------------------------------------------- /.github/workflows/validation.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: R Package Validation report 📃 3 | 4 | on: 5 | push: 6 | tags: 7 | - "v*" 8 | branches: 9 | - main 10 | pull_request: 11 | types: 12 | - opened 13 | - synchronize 14 | - reopened 15 | - ready_for_review 16 | branches: 17 | - main 18 | workflow_dispatch: 19 | workflow_call: 20 | inputs: 21 | install-system-dependencies: 22 | description: Check for and install system dependencies 23 | required: false 24 | default: false 25 | type: boolean 26 | enable-staged-dependencies-check: 27 | description: Enable staged dependencies YAML check 28 | required: false 29 | default: false 30 | type: boolean 31 | sd-direction: 32 | description: The direction to use to install staged dependencies. Choose between 'upstream', 'downstream' and 'all' 33 | required: false 34 | type: string 35 | default: upstream 36 | package-subdirectory: 37 | description: Subdirectory in the repository, where the R package is located. 38 | required: false 39 | type: string 40 | default: "." 41 | deps-installation-method: 42 | description: | 43 | Which method for installing R package dependencies to use? Supported values are: 44 | staged-dependencies 45 | setup-r-dependencies 46 | required: false 47 | type: string 48 | default: staged-dependencies 49 | lookup-refs: 50 | description: | 51 | List of package references to be used by setup-r-dependencies action if deps-installation-method == 'setup-r-dependencies'. 52 | required: false 53 | type: string 54 | default: "" 55 | secrets: 56 | REPO_GITHUB_TOKEN: 57 | description: | 58 | Github token with read access to repositories, required for staged.dependencies installation 59 | required: false 60 | 61 | concurrency: 62 | group: validation-${{ github.event.pull_request.number || github.ref }} 63 | cancel-in-progress: true 64 | 65 | jobs: 66 | validation: 67 | name: Create report 📃 68 | runs-on: ubuntu-latest 69 | if: > 70 | !contains(github.event.commits[0].message, '[skip validation]') 71 | && github.event.pull_request.draft == false 72 | container: 73 | image: ghcr.io/insightsengineering/rstudio:latest 74 | permissions: 75 | contents: write 76 | packages: write 77 | deployments: write 78 | steps: 79 | - name: Setup token 🔑 80 | id: github-token 81 | run: | 82 | if [ "${{ secrets.REPO_GITHUB_TOKEN }}" == "" ]; then 83 | echo "REPO_GITHUB_TOKEN is empty. Substituting it with GITHUB_TOKEN." 84 | echo "token=${{ secrets.GITHUB_TOKEN }}" >> $GITHUB_OUTPUT 85 | else 86 | echo "Using REPO_GITHUB_TOKEN." 87 | echo "token=${{ secrets.REPO_GITHUB_TOKEN }}" >> $GITHUB_OUTPUT 88 | fi 89 | shell: bash 90 | 91 | - name: Get branch names 🌿 92 | id: branch-name 93 | uses: tj-actions/branch-names@v7 94 | 95 | - name: Checkout repo (PR) 🛎 96 | uses: actions/checkout@v4.1.1 97 | if: github.event_name == 'pull_request' 98 | with: 99 | ref: ${{ steps.branch-name.outputs.head_ref_branch }} 100 | repository: ${{ github.event.pull_request.head.repo.full_name }} 101 | 102 | - name: Checkout repo 🛎 103 | uses: actions/checkout@v4.1.1 104 | if: github.event_name != 'pull_request' 105 | with: 106 | ref: ${{ steps.branch-name.outputs.head_ref_branch }} 107 | 108 | - name: Check commit message 💬 109 | run: | 110 | git config --global --add safe.directory $(pwd) 111 | export head_commit_message="$(git show -s --format=%B | tr '\r\n' ' ' | tr '\n' ' ')" 112 | echo "head_commit_message = $head_commit_message" 113 | if [[ $head_commit_message == *"$SKIP_INSTRUCTION"* ]]; then 114 | echo "Skip instruction detected - cancelling the workflow." 115 | exit 1 116 | fi 117 | shell: bash 118 | env: 119 | SKIP_INSTRUCTION: "[skip validation]" 120 | 121 | - name: Normalize variables 📏 122 | run: | 123 | package_subdirectory_input="${{ inputs.package-subdirectory }}" 124 | echo "package_subdirectory=${package_subdirectory_input:-.}" >> $GITHUB_ENV 125 | shell: bash 126 | 127 | - name: Restore SD cache 💰 128 | if: >- 129 | inputs.deps-installation-method == 'staged-dependencies' 130 | uses: actions/cache@v4 131 | with: 132 | key: sd-${{ runner.os }}-${{ github.event.repository.name }} 133 | path: ~/.staged.dependencies 134 | 135 | - name: Run Staged dependencies 🎦 136 | if: >- 137 | inputs.deps-installation-method == 'staged-dependencies' 138 | uses: insightsengineering/staged-dependencies-action@v2 139 | env: 140 | GITHUB_PAT: ${{ steps.github-token.outputs.token }} 141 | with: 142 | path: ${{ env.package_subdirectory }} 143 | enable-check: ${{ inputs.enable-staged-dependencies-check }} 144 | run-system-dependencies: ${{ inputs.install-system-dependencies }} 145 | direction: ${{ inputs.sd-direction }} 146 | 147 | - name: Setup R dependencies 🎦 148 | if: >- 149 | inputs.deps-installation-method == 'setup-r-dependencies' 150 | uses: insightsengineering/setup-r-dependencies@v1 151 | env: 152 | GITHUB_PAT: ${{ steps.github-token.outputs.token }} 153 | with: 154 | lookup-refs: ${{ inputs.lookup-refs }} 155 | repository-path: ${{ env.package_subdirectory }} 156 | 157 | - name: Build report 🏗 158 | uses: insightsengineering/thevalidatoR@v2 159 | env: 160 | GITHUB_PAT: ${{ steps.github-token.outputs.token }} 161 | with: 162 | report_pkg_dir: ${{ env.package_subdirectory }} 163 | 164 | - name: Upload report for review ⬆ 165 | if: github.ref != 'refs/heads/main' 166 | uses: actions/upload-artifact@v4 167 | with: 168 | name: validation_report.pdf 169 | path: validation_report.pdf 170 | 171 | upload-release-assets: 172 | name: Upload report to release 🔼 173 | needs: validation 174 | runs-on: ubuntu-latest 175 | if: startsWith(github.ref, 'refs/tags/v') 176 | steps: 177 | - name: Checkout repo 🛎 178 | uses: actions/checkout@v4.1.1 179 | 180 | - name: Download artifact ⏬ 181 | uses: actions/download-artifact@v4 182 | with: 183 | name: validation_report.pdf 184 | 185 | - name: Check if release exists 186 | id: check-if-release-exists 187 | uses: insightsengineering/release-existence-action@v1 188 | 189 | - name: Upload report to release 🔼 190 | if: >- 191 | steps.check-if-release-exists.outputs.release-exists == 'true' 192 | uses: svenstaro/upload-release-action@v2 193 | with: 194 | file: ./validation_report.pdf 195 | asset_name: validation-report.pdf 196 | repo_token: ${{ secrets.GITHUB_TOKEN }} 197 | tag: ${{ github.ref }} 198 | overwrite: true 199 | -------------------------------------------------------------------------------- /.github/workflows/verdepcheck.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Dependency Test 🔢 3 | 4 | on: 5 | workflow_call: 6 | secrets: 7 | REPO_GITHUB_TOKEN: 8 | description: | 9 | Github token with read access to repositories, required for dependencies installation 10 | required: false 11 | GCHAT_WEBHOOK: 12 | description: | 13 | Google Chat webhook to send failure notifications 14 | required: false 15 | inputs: 16 | check-args: 17 | description: Additional check arguments. 18 | required: false 19 | default: "" 20 | type: string 21 | build-args: 22 | description: Additional build arguments. 23 | required: false 24 | default: "" 25 | type: string 26 | strategy: 27 | description: | 28 | Strategy to test package dependencies. One of: min_isolate, min_cohort, release, max. 29 | required: true 30 | type: string 31 | extra-deps: 32 | description: | 33 | Extra dependencies specified similarly as in the `DESCRIPTION` file, 34 | i.e. `"<package name> (<operator> <version>)"` where both `<operator>` 35 | and `<version>` are optional. Multiple entries are possible separated by `";"`. 36 | required: false 37 | default: "" 38 | type: string 39 | additional-env-vars: 40 | description: | 41 | Extra environment variables, as a 'key=value' pair, with each pair on a new line. 42 | Example usage: 43 | additional-env-vars: | 44 | ABC=123 45 | XYZ=456 46 | required: false 47 | default: "" 48 | type: string 49 | additional-repos: 50 | description: | 51 | Optional value that add R repositories for a given strategy. Multiple entries are possible separated by `";"`. 52 | additional-repos: https://repo1.example.com;https://repo2.example.com 53 | required: false 54 | default: "" 55 | type: string 56 | 57 | jobs: 58 | dependency-test: 59 | name: Dependency Test 🔢 60 | runs-on: ubuntu-latest 61 | if: > 62 | !contains(github.event.commits[0].message, '[skip dependency-test]') 63 | && github.event.pull_request.draft == false 64 | container: 65 | image: ghcr.io/insightsengineering/rstudio:latest 66 | 67 | steps: 68 | - name: Setup token 🔑 69 | id: github-token 70 | run: | 71 | if [ "${{ secrets.REPO_GITHUB_TOKEN }}" == "" ]; then 72 | echo "REPO_GITHUB_TOKEN is empty. Substituting it with GITHUB_TOKEN." 73 | echo "token=${{ secrets.GITHUB_TOKEN }}" >> $GITHUB_OUTPUT 74 | else 75 | echo "Using REPO_GITHUB_TOKEN." 76 | echo "token=${{ secrets.REPO_GITHUB_TOKEN }}" >> $GITHUB_OUTPUT 77 | fi 78 | shell: bash 79 | 80 | - name: Get branch names 🌿 81 | id: branch-name 82 | uses: tj-actions/branch-names@v7 83 | 84 | - name: Checkout repo 🛎 85 | uses: actions/checkout@v4.1.1 86 | with: 87 | ref: ${{ steps.branch-name.outputs.head_ref_branch }} 88 | fetch-depth: 1 89 | 90 | - name: Check commit message 💬 91 | run: | 92 | git config --global --add safe.directory $(pwd) 93 | export head_commit_message="$(git show -s --format=%B | tr '\r\n' ' ' | tr '\n' ' ')" 94 | echo "head_commit_message = $head_commit_message" 95 | if [[ $head_commit_message == *"$SKIP_INSTRUCTION"* ]]; then 96 | echo "Skip instruction detected - cancelling the workflow." 97 | exit 1 98 | fi 99 | shell: bash 100 | env: 101 | SKIP_INSTRUCTION: "[skip dependency-test]" 102 | 103 | - name: Normalize variables 📏 104 | run: | 105 | strategy="${{ inputs.strategy }}" 106 | echo "strategy=${strategy:-release}" >> $GITHUB_ENV 107 | echo "gchat_webhook=${{ secrets.GCHAT_WEBHOOK }}" >> $GITHUB_ENV 108 | shell: bash 109 | 110 | - name: Restore cache 💰 111 | uses: actions/cache@v4 112 | with: 113 | key: verdepcheck-${{ runner.os }}-${{ github.event.repository.name }}-${{ env.strategy }} 114 | path: | 115 | ~/.cache/R/pkgcache/pkg 116 | 117 | - name: Dependency Test - ${{ env.strategy }} 🔢 118 | id: verdepcheck 119 | uses: insightsengineering/r-verdepcheck-action@main 120 | with: 121 | github-token: ${{ steps.github-token.outputs.token }} 122 | extra-deps: ${{ inputs.extra-deps }} 123 | build-args: ${{ inputs.build-args }} 124 | check-args: ${{ inputs.check-args }} 125 | strategy: ${{ env.strategy }} 126 | additional-env-vars: ${{ inputs.additional-env-vars }} 127 | additional-repos: ${{ inputs.additional-repos }} 128 | 129 | - name: GChat notification 🔔 130 | if: (failure() || cancelled()) && steps.verdepcheck.outcome != 'success' && env.gchat_webhook != '' 131 | uses: insightsengineering/google-chat-notification@master 132 | with: 133 | name: ${{ github.event.repository.name }} - Dependency Test - ${{ env.strategy }} 134 | url: ${{ secrets.GCHAT_WEBHOOK }} 135 | status: ${{ job.status }} 136 | 137 | - name: Upload lock file ⤴️ 138 | if: always() 139 | uses: actions/upload-artifact@v4 140 | with: 141 | name: "lock-file-${{ env.strategy }}" 142 | path: pkg.lock 143 | 144 | - name: Upload output file ⤴️ 145 | if: always() 146 | uses: actions/upload-artifact@v4 147 | with: 148 | name: "res-${{ env.strategy }}" 149 | path: res.RDS 150 | -------------------------------------------------------------------------------- /.github/workflows/version-bump.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Version bump ⬆ 3 | 4 | on: 5 | push: 6 | branches: 7 | - main 8 | workflow_dispatch: 9 | workflow_call: 10 | secrets: 11 | REPO_GITHUB_TOKEN: 12 | description: Github token with write access to the repo 13 | required: false 14 | inputs: 15 | disable-precommit-autoupdate: 16 | description: Disable precommit autoupdate 17 | required: false 18 | default: false 19 | type: boolean 20 | vbump-after-release: 21 | description: Whether the vbump workflow is running after a release has been published 22 | required: false 23 | default: false 24 | type: boolean 25 | package-subdirectory: 26 | description: Subdirectory in the repository, where the R package is located. 27 | required: false 28 | type: string 29 | default: "." 30 | 31 | concurrency: 32 | group: vbump-${{ github.event.pull_request.number || github.ref }} 33 | cancel-in-progress: true 34 | 35 | jobs: 36 | vbump: 37 | name: Bump version ⤴ 38 | runs-on: ubuntu-latest 39 | if: | 40 | !(contains(github.event.commits[0].message, '[skip vbump]') || 41 | contains(github.event.head_commit.message, '[skip vbump]') 42 | ) 43 | container: 44 | image: docker.io/rocker/tidyverse:latest 45 | 46 | steps: 47 | - name: Setup token 🔑 48 | id: github-token 49 | run: | 50 | if [ "${{ secrets.REPO_GITHUB_TOKEN }}" == "" ]; then 51 | echo "REPO_GITHUB_TOKEN is empty. Substituting it with GITHUB_TOKEN." 52 | echo "token=${{ secrets.GITHUB_TOKEN }}" >> $GITHUB_OUTPUT 53 | else 54 | echo "Using REPO_GITHUB_TOKEN." 55 | echo "token=${{ secrets.REPO_GITHUB_TOKEN }}" >> $GITHUB_OUTPUT 56 | fi 57 | shell: bash 58 | 59 | - name: Get branch names 🌿 60 | id: branch-name 61 | uses: tj-actions/branch-names@v7 62 | 63 | - name: Checkout repo 🛎 64 | uses: actions/checkout@v4.1.1 65 | with: 66 | ref: ${{ steps.branch-name.outputs.head_ref_branch }} 67 | token: ${{ steps.github-token.outputs.token }} 68 | 69 | - name: Bump version in DESCRIPTION 📜 70 | if: ${{ (github.event_name == 'push' && github.ref == 'refs/heads/main') || inputs.vbump-after-release == true }} 71 | run: desc::desc_bump_version("dev", normalize = TRUE) 72 | shell: Rscript {0} 73 | working-directory: ${{ inputs.package-subdirectory }} 74 | 75 | - name: Update Date field 📅 76 | run: if (desc::desc_has_fields("Date")) desc::desc_set("Date", Sys.Date()) 77 | shell: Rscript {0} 78 | working-directory: ${{ inputs.package-subdirectory }} 79 | 80 | - name: Bump version in NEWS.md 📰 81 | run: | 82 | if [ -f NEWS.md ] 83 | then { 84 | git config --global --add safe.directory $(pwd) 85 | DESC_VERSION=$(R --slave -e 'cat(paste(desc::desc_get_version()))' | tr -d '\n' | xargs) 86 | NEWS_VERSION=$(awk '/^#+ /{print $3,$4; exit}' NEWS.md | tr -d '\n' | xargs) 87 | FIRST_NEWS_LINE=$(head -1 NEWS.md) 88 | if [ "${{ inputs.vbump-after-release }}" == "true" ]; then 89 | # Add a new section with the released version that will be vbumped below. 90 | printf "$FIRST_NEWS_LINE\n\n" | cat - NEWS.md > temp-news.md 91 | mv temp-news.md NEWS.md 92 | fi 93 | # Replace only the first occurence of $NEWS_VERSION, 94 | # but only if it's not already set to (development version) 95 | if [ $NEWS_VERSION != "(development version)" ] 96 | then { 97 | sed -i "0,/$NEWS_VERSION/s/$NEWS_VERSION/$DESC_VERSION/" NEWS.md 98 | } 99 | fi 100 | echo "NEW_PKG_VERSION=${DESC_VERSION}" >> $GITHUB_ENV 101 | } 102 | fi 103 | shell: bash 104 | working-directory: ${{ inputs.package-subdirectory }} 105 | 106 | - name: Check if a pre-commit config exists 107 | id: precommit-config-exists 108 | uses: andstor/file-existence-action@v3 109 | with: 110 | files: ".pre-commit-config.yaml" 111 | 112 | - name: Setup Python 🐍 113 | if: | 114 | inputs.disable-precommit-autoupdate != 'true' && 115 | steps.precommit-config-exists.outputs.files_exists == 'true' 116 | uses: actions/setup-python@v5 117 | with: 118 | python-version: '3.12' 119 | 120 | - name: Precommit autoupdate 🅿️ 121 | if: | 122 | inputs.disable-precommit-autoupdate != 'true' && 123 | steps.precommit-config-exists.outputs.files_exists == 'true' 124 | run: | 125 | git config --global --add safe.directory $(pwd) 126 | python -m pip -q install pre-commit 127 | pre-commit autoupdate 128 | 129 | - name: Checkout to main 🛎 130 | if: ${{ inputs.vbump-after-release == true }} 131 | run: | 132 | git fetch origin main 133 | git checkout main 134 | git pull origin main 135 | 136 | - name: Set file pattern to commit ⚙️ 137 | id: file-pattern 138 | run: | 139 | if [[ "${{ inputs.package-subdirectory }}" == "." || "${{ inputs.package-subdirectory }}" == "" ]]; then 140 | FILE_PATTERN="NEWS.md DESCRIPTION" 141 | else 142 | FILE_PATTERN="${{ inputs.package-subdirectory }}/NEWS.md ${{ inputs.package-subdirectory }}/DESCRIPTION" 143 | fi 144 | if [[ '${{ steps.precommit-config-exists.outputs.files_exists }}' == 'true' ]]; then 145 | FILE_PATTERN="$FILE_PATTERN .pre-commit-config.yaml" 146 | fi 147 | echo "file-pattern=$FILE_PATTERN" >> $GITHUB_OUTPUT 148 | shell: bash 149 | 150 | - name: Commit and push changes 📌 151 | if: ${{ env.NEW_PKG_VERSION }} 152 | uses: stefanzweifel/git-auto-commit-action@v5 153 | with: 154 | commit_message: "[skip actions] Bump version to ${{ env.NEW_PKG_VERSION }}" 155 | file_pattern: "${{ steps.file-pattern.outputs.file-pattern }}" 156 | commit_user_name: github-actions 157 | commit_user_email: >- 158 | 41898282+github-actions[bot]@users.noreply.github.com 159 | continue-on-error: true 160 | -------------------------------------------------------------------------------- /.github/workflows/version.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Version check 🏁 3 | 4 | on: 5 | push: 6 | branches: 7 | - main 8 | pull_request: 9 | types: 10 | - opened 11 | - synchronize 12 | - reopened 13 | - ready_for_review 14 | branches: 15 | - main 16 | workflow_dispatch: 17 | workflow_call: 18 | inputs: 19 | package-subdirectory: 20 | description: Subdirectory in the repository, where the R package is located. 21 | required: false 22 | type: string 23 | default: "." 24 | 25 | concurrency: 26 | group: version-${{ github.event.pull_request.number || github.ref }} 27 | cancel-in-progress: true 28 | 29 | jobs: 30 | version: 31 | name: Version check 🏁 32 | runs-on: ubuntu-latest 33 | if: > 34 | !contains(github.event.commits[0].message, '[skip version]') 35 | && github.event.pull_request.draft == false 36 | 37 | steps: 38 | - name: Get branch names 🌿 39 | id: branch-name 40 | uses: tj-actions/branch-names@v7 41 | 42 | - name: Checkout repo (PR) 🛎 43 | uses: actions/checkout@v4.1.1 44 | if: github.event_name == 'pull_request' 45 | with: 46 | ref: ${{ steps.branch-name.outputs.head_ref_branch }} 47 | repository: ${{ github.event.pull_request.head.repo.full_name }} 48 | 49 | - name: Checkout repo 🛎 50 | uses: actions/checkout@v4.1.1 51 | if: github.event_name != 'pull_request' 52 | with: 53 | ref: ${{ steps.branch-name.outputs.head_ref_branch }} 54 | 55 | - name: Check commit message 💬 56 | run: | 57 | git config --global --add safe.directory $(pwd) 58 | export head_commit_message="$(git show -s --format=%B | tr '\r\n' ' ' | tr '\n' ' ')" 59 | echo "head_commit_message = $head_commit_message" 60 | if [[ $head_commit_message == *"$SKIP_INSTRUCTION"* ]]; then 61 | echo "Skip instruction detected - cancelling the workflow." 62 | exit 1 63 | fi 64 | shell: bash 65 | env: 66 | SKIP_INSTRUCTION: "[skip version]" 67 | 68 | - name: NEWS.md and DESCRIPTION Version check 🏁 69 | run: | 70 | DESC_VERSION=$(awk -F: '/Version:/{gsub(/[ ]+/,"") ; print $2}' DESCRIPTION | tr -d '\n' | xargs) 71 | NEWS_VERSION=$(awk '/^#+ /{print $3,$4; exit}' NEWS.md | tr -d '\n' | xargs) 72 | DESC_DEV_VERSION=$(echo $DESC_VERSION | awk -F '.' '{print $NF}') 73 | echo "NEWS.md version: $NEWS_VERSION" 74 | echo "DESCRIPTION version: $DESC_VERSION" 75 | if test $DESC_VERSION = $NEWS_VERSION 76 | then { 77 | echo "NEWS.md and DESCRIPTION have the same version 🎉" 78 | exit 0 79 | } 80 | fi 81 | if [[ $DESC_DEV_VERSION -ge 9000 && "${NEWS_VERSION}" == "(development version)" ]] 82 | then { 83 | echo "NEWS.md and DESCRIPTION file versions are okay as package is in development mode." 84 | echo "All is okay 🆗" 85 | exit 0 86 | } 87 | fi 88 | echo "🙈 NEWS.md and DESCRIPTION have different versions!" 89 | echo "🙏 Please fix this." 90 | exit 1 91 | shell: bash 92 | working-directory: ${{ inputs.package-subdirectory }} 93 | 94 | emoji: 95 | name: Emoji in NEWS.md 📰 96 | runs-on: ubuntu-latest 97 | if: > 98 | !contains(github.event.commits[0].message, '[skip version]') 99 | && github.event.pull_request.draft == false 100 | steps: 101 | - name: Get branch names 🌿 102 | id: branch-name 103 | uses: tj-actions/branch-names@v7 104 | 105 | - name: Checkout repo (PR) 🛎 106 | uses: actions/checkout@v4.1.1 107 | if: github.event_name == 'pull_request' 108 | with: 109 | ref: ${{ steps.branch-name.outputs.head_ref_branch }} 110 | repository: ${{ github.event.pull_request.head.repo.full_name }} 111 | 112 | - name: Checkout repo 🛎 113 | uses: actions/checkout@v4.1.1 114 | if: github.event_name != 'pull_request' 115 | with: 116 | ref: ${{ steps.branch-name.outputs.head_ref_branch }} 117 | 118 | - name: Check commit message 💬 119 | run: | 120 | git config --global --add safe.directory $(pwd) 121 | export head_commit_message="$(git show -s --format=%B | tr '\r\n' ' ' | tr '\n' ' ')" 122 | echo "head_commit_message = $head_commit_message" 123 | if [[ $head_commit_message == *"$SKIP_INSTRUCTION"* ]]; then 124 | echo "Skip instruction detected - cancelling the workflow." 125 | exit 1 126 | fi 127 | shell: bash 128 | env: 129 | SKIP_INSTRUCTION: "[skip version]" 130 | 131 | - name: Set up Python 🐍 132 | uses: actions/setup-python@v5 133 | with: 134 | python-version: '3.12' 135 | 136 | - name: Install the regex package 📦 137 | uses: insightsengineering/pip-action@v2 138 | with: 139 | packages: regex 140 | 141 | - name: Check for emojis in NEWS.md 🏁 142 | run: | 143 | import regex 144 | import sys 145 | with open('NEWS.md', 'r') as file: 146 | text = file.read() 147 | lines = text.split('\n') 148 | emoji_pattern = regex.compile(r"(?![\d\p{P}])\p{Emoji}") 149 | for line_num, line in enumerate(lines, start=1): 150 | matches = [(match.group(), match.start(), match.end()) for match in emoji_pattern.finditer(line)] 151 | for emoji, start, end in matches: 152 | print(f"Emoji: {emoji} | Line: {line_num} | Start: {start} | End: {end}") 153 | if matches: 154 | print("🚨 Emojis were found in the NEWS.md file! Please remove them 🙏") 155 | print("ℹ️ Refer to https://github.com/insightsengineering/tern.gee/issues/37#issue-1714621201 for more information") 156 | sys.exit(1) 157 | print("🥰 No emojis found in the NEWS.md, good to go!") 158 | shell: python 159 | working-directory: ${{ inputs.package-subdirectory }} 160 | -------------------------------------------------------------------------------- /.github/workflows/wasm.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: WASM 🧭 3 | 4 | on: 5 | workflow_call: 6 | inputs: 7 | package-subdirectory: 8 | description: Subdirectory in the repository, where the R package is located. 9 | required: false 10 | type: string 11 | default: "" 12 | 13 | jobs: 14 | wasm: 15 | name: WASM 🧭 16 | runs-on: ubuntu-latest 17 | if: startsWith(github.ref, 'refs/tags/v') 18 | steps: 19 | - name: Get branch names 🌿 20 | id: branch-name 21 | uses: tj-actions/branch-names@v7 22 | 23 | - name: Checkout repo 🛎 24 | uses: actions/checkout@v4.1.1 25 | with: 26 | ref: ${{ steps.branch-name.outputs.head_ref_branch }} 27 | path: ${{ github.event.repository.name }} 28 | 29 | - name: Get package name 📦 30 | run: | 31 | echo "PKGNAME=$(echo $(awk -F: '/Package:/{gsub(/[ ]+/,"") ; print $2}' DESCRIPTION))" >> $GITHUB_ENV 32 | shell: bash 33 | working-directory: ${{ github.event.repository.name }}/${{ inputs.package-subdirectory }} 34 | 35 | - name: Build WASM packages 🧭 36 | uses: r-wasm/actions/build-rwasm@v1 37 | with: 38 | packages: | 39 | ${{ github.repository_owner }}/${{ env.PKGNAME }}@${{ steps.branch-name.outputs.tag }} 40 | 41 | - name: Upload WASM packages ⬆ 42 | uses: actions/upload-artifact@v4 43 | with: 44 | name: wasm-${{ env.PKGNAME }} 45 | path: | 46 | _site/bin/emscripten/contrib/*/${{ env.PKGNAME }}*.tgz 47 | _site/src/contrib/${{ env.PKGNAME }}*.tar.gz 48 | 49 | - name: Check if release exists ⚙️ 50 | id: check-if-release-exists 51 | uses: insightsengineering/release-existence-action@v1 52 | 53 | - name: Download artifact ⏬ 54 | if: >- 55 | steps.check-if-release-exists.outputs.release-exists == 'true' 56 | uses: actions/download-artifact@v4 57 | with: 58 | name: wasm-${{ env.PKGNAME }} 59 | path: wasm-${{ env.PKGNAME }} 60 | 61 | - name: Prepare WASM release artifact 🗜️ 62 | if: >- 63 | steps.check-if-release-exists.outputs.release-exists == 'true' 64 | run: | 65 | zip -r9 wasm-${{ env.PKGNAME }}.zip wasm-${{ env.PKGNAME }} 66 | 67 | - name: Upload WASM build to release 🔼 68 | if: >- 69 | steps.check-if-release-exists.outputs.release-exists == 'true' 70 | uses: svenstaro/upload-release-action@v2 71 | with: 72 | file: ./wasm-${{ env.PKGNAME }}.zip 73 | asset_name: wasm-${{ env.PKGNAME }}.zip 74 | repo_token: ${{ secrets.GITHUB_TOKEN }} 75 | tag: ${{ github.ref }} 76 | overwrite: true 77 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .httr-oauth 3 | .project 4 | .RData 5 | .Rhistory 6 | .Rproj.user 7 | .Ruserdata 8 | .settings/** 9 | *.html 10 | *.Rcheck 11 | *.rprof 12 | *.sas.txt 13 | *~ 14 | /.project 15 | devel/* 16 | doc 17 | docs 18 | inst/outputs/* 19 | logs 20 | Meta 21 | packrat/lib*/ 22 | temp 23 | temp_w 24 | templates/ 25 | tmp.* 26 | vignettes/*.html 27 | vignettes/*.md 28 | vignettes/*.R 29 | coverage.* 30 | .vscode/ 31 | node_modules/ 32 | package-lock.json 33 | package.json 34 | -------------------------------------------------------------------------------- /.lintr: -------------------------------------------------------------------------------- 1 | linters: linters_with_defaults( 2 | line_length_linter = line_length_linter(120), 3 | cyclocomp_linter = NULL, 4 | object_usage_linter = NULL 5 | ) 6 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # All available hooks: https://pre-commit.com/hooks.html 3 | # R specific hooks: https://github.com/lorenzwalthert/precommit 4 | repos: 5 | - repo: https://github.com/lorenzwalthert/precommit 6 | rev: v0.4.3.9009 7 | hooks: 8 | - id: style-files 9 | args: [--style_pkg=styler, --style_fun=tidyverse_style] 10 | - id: roxygenize 11 | additional_dependencies: 12 | - plumber 13 | - shiny 14 | # codemeta must be above use-tidy-description when both are used 15 | # - id: codemeta-description-updated 16 | - id: use-tidy-description 17 | - id: spell-check 18 | exclude: > 19 | (?x)^( 20 | data/.*| 21 | (.*/|)\.Rprofile| 22 | (.*/|)\.Renviron| 23 | (.*/|)\.gitignore| 24 | (.*/|)NAMESPACE| 25 | (.*/|)DESCRIPTION| 26 | (.*/|)WORDLIST| 27 | (.*/|)LICENSE| 28 | (.*/|)\.Rbuildignore| 29 | (.*/|)\.lintr| 30 | (.*/|)_pkgdown.yaml| 31 | (.*/|)staged_dependencies.yaml| 32 | (.*/|)\.pre-commit-.*| 33 | \.github/.*| 34 | .*\.[rR]| 35 | .*\.Rproj| 36 | .*\.py| 37 | .*\.png| 38 | .*\.feather| 39 | .*\.rds| 40 | .*\.Rds| 41 | .*\.sh| 42 | .*\.RData 43 | )$ 44 | - id: lintr 45 | - id: readme-rmd-rendered 46 | - id: parsable-R 47 | - id: no-browser-statement 48 | - id: deps-in-desc 49 | - repo: https://github.com/pre-commit/mirrors-prettier 50 | rev: v4.0.0-alpha.8 51 | hooks: 52 | - id: prettier 53 | - repo: https://github.com/pre-commit/pre-commit-hooks 54 | rev: v5.0.0 55 | hooks: 56 | - id: check-added-large-files 57 | args: ["--maxkb=200"] 58 | - id: end-of-file-fixer 59 | exclude: '\.Rd' 60 | - id: trailing-whitespace 61 | exclude: '\.Rd' 62 | - id: check-yaml 63 | - id: no-commit-to-branch 64 | - id: mixed-line-ending 65 | args: ["--fix=lf"] 66 | - id: detect-aws-credentials 67 | args: ["--allow-missing-credentials"] 68 | - id: detect-private-key 69 | - id: forbid-new-submodules 70 | - id: check-symlinks 71 | - repo: local 72 | hooks: 73 | - id: forbid-to-commit 74 | name: Don't commit common R artifacts 75 | entry: Cannot commit .Rhistory, .RData, .Rds or .rds. 76 | language: fail 77 | files: '\.Rhistory|\.RData|\.Rds|\.rds$' 78 | # `exclude: <regex>` to allow committing specific files. 79 | - repo: https://github.com/igorshubovych/markdownlint-cli 80 | rev: v0.45.0 81 | hooks: 82 | - id: markdownlint 83 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Type: Package 2 | Package: r.pkg.template 3 | Title: R Package Template 4 | Version: 0.1.0.9199 5 | Date: 2025-05-19 6 | Authors@R: 7 | person("insightsengineering", , , "insightsengineering@example.com", role = c("aut", "cre")) 8 | Description: R package template with GitHub Actions workflows included. 9 | License: Apache License 2.0 | file LICENSE 10 | URL: https://github.com/insightsengineering/r.pkg.template/ 11 | BugReports: https://github.com/insightsengineering/r.pkg.template/issues 12 | Depends: 13 | R (>= 3.6) 14 | Imports: 15 | plumber, 16 | shiny, 17 | stringr 18 | Suggests: 19 | future, 20 | httr, 21 | knitr, 22 | shinytest2, 23 | testthat (>= 3.0) 24 | VignetteBuilder: 25 | knitr 26 | biocViews: 27 | Encoding: UTF-8 28 | Language: en-US 29 | LazyData: true 30 | Roxygen: list(markdown = TRUE) 31 | RoxygenNote: 7.3.2 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2022 F. Hoffmann-La Roche AG 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | export(hello) 4 | export(plumber_api) 5 | export(shiny_app) 6 | importFrom(utils,packageName) 7 | -------------------------------------------------------------------------------- /NEWS.md: -------------------------------------------------------------------------------- 1 | # r.pkg.template 0.1.0.9199 2 | 3 | ### New features 4 | 5 | * Add an initializer script. 6 | 7 | ### Enhancements 8 | 9 | * Documentation on how to use the initialize a package. 10 | 11 | ### Bug fixes 12 | 13 | * None. -------------------------------------------------------------------------------- /R/hello.R: -------------------------------------------------------------------------------- 1 | #' Personal greeting 2 | #' 3 | #' @description Greet a person and appropriately capitalize their name. 4 | #' 5 | #' @param name Your name (character string; e.g. "john doe"). 6 | #' 7 | #' @return A character string, capitalized to title case. 8 | #' @export 9 | #' 10 | #' @examples 11 | #' hello("james bond") 12 | hello <- function(name = "your name") { 13 | name <- stringr::str_to_title(name) 14 | print(paste("Hello,", name)) 15 | } 16 | 17 | #' Personal greeting as a Shiny app 18 | #' 19 | #' @description Greet a person and appropriately capitalize their name 20 | #' as a Shiny app. 21 | #' 22 | #' @return Shiny app showcasing the personal greeting feature. 23 | #' @export 24 | #' 25 | shiny_app <- function() { 26 | ui <- shiny::fluidPage( 27 | shiny::textInput("name", "What is your name?"), 28 | shiny::actionButton("greet", "Greet"), 29 | shiny::textOutput("greeting") 30 | ) 31 | 32 | server <- function(input, output, session) { 33 | output$greeting <- shiny::renderText({ 34 | shiny::req(input$greet) 35 | hello(shiny::isolate(input$name)) 36 | }) 37 | } 38 | 39 | shiny::shinyApp(ui, server) 40 | } 41 | 42 | #' Personal greeting as a Plumber API 43 | #' 44 | #' @importFrom utils packageName 45 | #' 46 | #' @description Greet a person and appropriately capitalize their name 47 | #' as a Plumber API. 48 | #' 49 | #' @param ... Additional arguments to plumber::pr_run() 50 | #' 51 | #' @return Plumber API showcasing the personal greeting feature. 52 | #' @export 53 | #' 54 | plumber_api <- function(...) { 55 | plumber::pr_run( 56 | plumber::plumb_api( 57 | package = packageName(), name = "hello" 58 | ), 59 | ... 60 | ) 61 | } 62 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # r.pkg.template 2 | 3 | ![GitHub forks](https://img.shields.io/github/forks/insightsengineering/r.pkg.template?style=social) 4 | ![GitHub Repo stars](https://img.shields.io/github/stars/insightsengineering/r.pkg.template?style=social) 5 | 6 | ![GitHub commit activity](https://img.shields.io/github/commit-activity/m/insightsengineering/r.pkg.template) 7 | ![GitHub contributors](https://img.shields.io/github/contributors/insightsengineering/r.pkg.template) 8 | ![GitHub last commit](https://img.shields.io/github/last-commit/insightsengineering/r.pkg.template) 9 | ![GitHub pull requests](https://img.shields.io/github/issues-pr/insightsengineering/r.pkg.template) 10 | ![GitHub repo size](https://img.shields.io/github/repo-size/insightsengineering/r.pkg.template) 11 | ![GitHub language count](https://img.shields.io/github/languages/count/insightsengineering/r.pkg.template) 12 | [![Project Status: Active – The project has reached a stable, usable state and is being actively developed.](https://www.repostatus.org/badges/latest/active.svg)](https://www.repostatus.org/#active) 13 | [![Downloads](https://img.shields.io/github/downloads/insightsengineering/r.pkg.template/latest/total)](https://tooomm.github.io/github-release-stats/?username=insightsengineering\&repository=r.pkg.template) 14 | [![Current Version](https://img.shields.io/github/r-package/v/insightsengineering/r.pkg.template/main?color=purple\&label=package%20version)](https://github.com/insightsengineering/r.pkg.template/tree/main) 15 | [![Open Issues](https://img.shields.io/github/issues-raw/insightsengineering/r.pkg.template?color=red\&label=open%20issues)](https://github.com/insightsengineering/r.pkg.template/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc) 16 | 17 | [![Audit Dependencies](https://github.com/insightsengineering/r.pkg.template/actions/workflows/audit.yaml/badge.svg)](https://github.com/insightsengineering/r.pkg.template/actions/workflows/audit.yaml) 18 | [![BiocCheck](https://github.com/insightsengineering/r.pkg.template/actions/workflows/bioccheck.yaml/badge.svg)](https://github.com/insightsengineering/r.pkg.template/actions/workflows/bioccheck.yaml) 19 | [![Check URLs](https://github.com/insightsengineering/r.pkg.template/actions/workflows/links.yaml/badge.svg)](https://github.com/insightsengineering/r.pkg.template/actions/workflows/links.yaml) 20 | [![Coverage](https://github.com/insightsengineering/r.pkg.template/actions/workflows/test-coverage.yaml/badge.svg)](https://github.com/insightsengineering/r.pkg.template/actions/workflows/test-coverage.yaml) 21 | [![License report](https://github.com/insightsengineering/r.pkg.template/actions/workflows/licenses.yaml/badge.svg)](https://github.com/insightsengineering/r.pkg.template/actions/workflows/licenses.yaml) 22 | [![Pkgdown Docs](https://github.com/insightsengineering/r.pkg.template/actions/workflows/pkgdown.yaml/badge.svg)](https://github.com/insightsengineering/r.pkg.template/actions/workflows/pkgdown.yaml) 23 | [![R CMD Check](https://github.com/insightsengineering/r.pkg.template/actions/workflows/build-check-install.yaml/badge.svg)](https://github.com/insightsengineering/r.pkg.template/actions/workflows/build-check-install.yaml) 24 | [![R Package Validation report](https://github.com/insightsengineering/r.pkg.template/actions/workflows/validation.yaml/badge.svg)](https://github.com/insightsengineering/r.pkg.template/actions/workflows/validation.yaml) 25 | [![Release](https://github.com/insightsengineering/r.pkg.template/actions/workflows/release.yaml/badge.svg)](https://github.com/insightsengineering/r.pkg.template/actions/workflows/release.yaml) 26 | [![Roxygen](https://github.com/insightsengineering/r.pkg.template/actions/workflows/roxygen.yaml/badge.svg)](https://github.com/insightsengineering/r.pkg.template/actions/workflows/roxygen.yaml) 27 | [![Spelling](https://github.com/insightsengineering/r.pkg.template/actions/workflows/spelling.yaml/badge.svg)](https://github.com/insightsengineering/r.pkg.template/actions/workflows/spelling.yaml) 28 | [![Style](https://github.com/insightsengineering/r.pkg.template/actions/workflows/style.yaml/badge.svg)](https://github.com/insightsengineering/r.pkg.template/actions/workflows/style.yaml) 29 | [![SuperLinter](https://github.com/insightsengineering/r.pkg.template/actions/workflows/linter.yaml/badge.svg)](https://github.com/insightsengineering/r.pkg.template/actions/workflows/linter.yaml) 30 | [![Version bump](https://github.com/insightsengineering/r.pkg.template/actions/workflows/version-bump.yaml/badge.svg)](https://github.com/insightsengineering/r.pkg.template/actions/workflows/version-bump.yaml) 31 | [![Version check](https://github.com/insightsengineering/r.pkg.template/actions/workflows/version.yaml/badge.svg)](https://github.com/insightsengineering/r.pkg.template/actions/workflows/version.yaml) 32 | [![gitleaks](https://github.com/insightsengineering/r.pkg.template/actions/workflows/gitleaks.yaml/badge.svg)](https://github.com/insightsengineering/r.pkg.template/actions/workflows/gitleaks.yaml) 33 | 34 | <!-- links --> 35 | 36 | [pre-commit]: https://pre-commit.com 37 | 38 | [pre-commit installation]: https://pre-commit.com/#installation 39 | 40 | [git hooks]: https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks 41 | 42 | An R package template with built-in GitHub Actions-based CI/CD workflows. 43 | 44 | ## Usage 45 | 46 | ### Initialization 47 | 48 | You could initialize this repository in one of two ways: 49 | 50 | #### Clone this template 51 | 52 | * Clone this repository: 53 | 54 | ```bash 55 | git clone https://github.com/insightsengineering/r.pkg.template.git 56 | cd r.pkg.template 57 | ``` 58 | 59 | * Run the initializer script: 60 | 61 | ```bash 62 | ./init.sh 63 | ``` 64 | 65 | #### Use GitHub's template importer 66 | 67 | * Click [here](https://github.com/insightsengineering/r.pkg.template/generate) to generate a copy of this template directly within GitHub. 68 | 69 | * Clone the repository from your account/organization. 70 | 71 | * Run the initializer script: 72 | 73 | ```bash 74 | ./init.sh 75 | ``` 76 | 77 | ### CI/CD Configurations 78 | 79 | All CI/CD jobs are defined in the [.github/workflows](./.github/workflows) directory in the form of GitHub Action workflows. These can be modified per your requirements, but are designed and implemented to follow best practices and to ensure the highest quality standards for your package. 80 | 81 | All workflows originating from this repository can be repurposed by other R package GitHub repositories. 82 | 83 | 👉 For more information including detailed description and screenshots of workflows, please refer to the [Workflows documentation](./workflows.md). 84 | 85 | ### Pre-commit 86 | 87 | This repository contains an example [pre-commit] configuration. 88 | 89 | [pre-commit] is a tool that uses [Git hooks] to identify and resolve simple issues before submission for code review. 90 | [Git hooks] run on every commit to automatically point out and solve issues such as missing semicolons, trailing whitespaces, 91 | code formatting and spell checks. 92 | 93 | ### Setting up pre-commit for R project 94 | 95 | * Install the `pre-commit` framework. Use the official [installation guide][pre-commit installation]. 96 | 97 | * Install R package `precommit` 98 | 99 | ```sh 100 | R -e 'install.packages("precommit")' 101 | ``` 102 | 103 | * Run the `use_precommit()` function to generate an example pre-commit configuration called `.pre-commit-config.yaml`: 104 | 105 | ```sh 106 | [ ! -f ".pre-commit-config.yaml" ] && R -e 'precommit::use_precommit()' 107 | ``` 108 | 109 | * Install the git hooks script: 110 | 111 | ```sh 112 | pre-commit install 113 | ``` 114 | 115 | * From this moment on, all scripts from `.pre-commit-config.yaml` will run before every `git commit` command. If you want to run them manually without committing you can use command `pre-commit run --all-files`. For more information, please refer to the official [pre-commit] documentation. 116 | 117 | > NOTE: Frequently run `pre-commit autoupdate` to update all hooks in the `.pre-commit-config.yaml` configuration file. 118 | 119 | ### Example output from pre-commit 120 | 121 | ```sh 122 | $ git add . 123 | $ git commit -m "Add pre-commit configuration" 124 | [INFO] Installing environment for https://github.com/pre-commit/pre-commit-hooks. 125 | [INFO] Once installed this environment will be reused. 126 | [INFO] This may take a few minutes... 127 | style-files..............................................................Passed 128 | roxygenize...........................................(no files to check)Skipped 129 | use-tidy-description.....................................................Passed 130 | spell-check..............................................................Failed 131 | - hook id: spell-check 132 | - exit code: 1 133 | - files were modified by this hook 134 | 135 | The following spelling errors were found: 136 | WORD FOUND IN 137 | commiting README.md:77 138 | indentify README.md:49 139 | informatoin README.md:77 140 | All spelling errors found were copied to inst/WORDLIST assuming they were not spelling errors and will be ignored in the future. Please review the above list and for each word that is an actual typo: 141 | - fix it in the source code. 142 | - remove it again manually from inst/WORDLIST to make sure it's not 143 | ignored in the future. 144 | Then, try committing again. 145 | Error: Spell check failed 146 | Execution halted 147 | 148 | lintr....................................................................Passed 149 | readme-rmd-rendered......................................................Passed 150 | parsable-R...............................................................Passed 151 | no-browser-statement.....................................................Passed 152 | deps-in-desc.............................................................Passed 153 | prettier.................................................................Failed 154 | - hook id: prettier 155 | - files were modified by this hook 156 | 157 | .pre-commit-config.yaml 158 | README.md 159 | 160 | Check for added large files..............................................Passed 161 | Fix End of Files.........................................................Passed 162 | Trim Trailing Whitespace.................................................Failed 163 | - hook id: trailing-whitespace 164 | - exit code: 1 165 | - files were modified by this hook 166 | 167 | Fixing .pre-commit-config.yaml 168 | Fixing README.md 169 | 170 | Check Yaml...............................................................Passed 171 | Don't commit to branch...................................................Passed 172 | Mixed line ending........................................................Passed 173 | Don't commit common R artifacts......................(no files to check)Skipped 174 | ``` 175 | 176 | ## Stargazers and Forkers 177 | 178 | ### Stargazers over time 179 | 180 | [![Stargazers over time](https://starchart.cc/insightsengineering/r.pkg.template.svg)](https://starchart.cc/insightsengineering/r.pkg.template) 181 | 182 | ### Stargazers 183 | 184 | [![Stargazers repo roster for @insightsengineering/r.pkg.template](https://reporoster.com/stars/insightsengineering/r.pkg.template)](https://github.com/insightsengineering/r.pkg.template/stargazers) 185 | 186 | ### Forkers 187 | 188 | [![Forkers repo roster for @insightsengineering/r.pkg.template](https://reporoster.com/forks/insightsengineering/r.pkg.template)](https://github.com/insightsengineering/r.pkg.template/network/members) 189 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Reporting Security Issues 4 | 5 | If you believe you have found a security vulnerability in any of the repositories in this organization, please report it to us through coordinated disclosure. 6 | 7 | **Please do not report security vulnerabilities through public GitHub issues, discussions, or pull requests.** 8 | 9 | Instead, please send an email to vulnerability.management[@]roche.com. 10 | 11 | Please include as much of the information listed below as you can to help us better understand and resolve the issue: 12 | 13 | * The type of issue (e.g., buffer overflow, SQL injection, or cross-site scripting) 14 | * Full paths of source file(s) related to the manifestation of the issue 15 | * The location of the affected source code (tag/branch/commit or direct URL) 16 | * Any special configuration required to reproduce the issue 17 | * Step-by-step instructions to reproduce the issue 18 | * Proof-of-concept or exploit code (if possible) 19 | * Impact of the issue, including how an attacker might exploit the issue 20 | 21 | This information will help us triage your report more quickly. 22 | 23 | ## Data Security Standards (DSS) 24 | 25 | Please make sure that while reporting issues in the form a bug, feature, or pull request, *all* sensitive information such as [PII](https://www.dhs.gov/privacy-training/what-personally-identifiable-information), [PHI](https://www.hhs.gov/hipaa/for-professionals/security/laws-regulations/index.html), and [PCI](https://www.pcisecuritystandards.org/pci_security/standards_overview) is completely removed from any text and attachments, including pictures and videos. 26 | -------------------------------------------------------------------------------- /_pkgdown.yml: -------------------------------------------------------------------------------- 1 | url: https://insightsengineering.github.io/r.pkg.template 2 | 3 | template: 4 | bootstrap: 5 5 | bootswatch: cerulean 6 | includes: 7 | in_header: | 8 | <!-- Global site tag (gtag.js) - Google Analytics --> 9 | <script async src="https://www.googletagmanager.com/gtag/js?id=UA-125641273-1"></script> 10 | <script> 11 | window.dataLayer = window.dataLayer || []; 12 | function gtag(){dataLayer.push(arguments);} 13 | gtag('js', new Date()); 14 | gtag('config', 'UA-125641273-1'); 15 | </script> 16 | 17 | navbar: 18 | right: 19 | - icon: fa-github 20 | href: https://github.com/insightsengineering/r.pkg.template 21 | -------------------------------------------------------------------------------- /images/audit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insightsengineering/r.pkg.template/4e6fd0f965aa6a5f953eb0c601c0c077a3df3edd/images/audit.png -------------------------------------------------------------------------------- /images/bioccheck.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insightsengineering/r.pkg.template/4e6fd0f965aa6a5f953eb0c601c0c077a3df3edd/images/bioccheck.png -------------------------------------------------------------------------------- /images/branch-cleanup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insightsengineering/r.pkg.template/4e6fd0f965aa6a5f953eb0c601c0c077a3df3edd/images/branch-cleanup.png -------------------------------------------------------------------------------- /images/gitleaks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insightsengineering/r.pkg.template/4e6fd0f965aa6a5f953eb0c601c0c077a3df3edd/images/gitleaks.png -------------------------------------------------------------------------------- /images/grammar1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insightsengineering/r.pkg.template/4e6fd0f965aa6a5f953eb0c601c0c077a3df3edd/images/grammar1.png -------------------------------------------------------------------------------- /images/grammar2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insightsengineering/r.pkg.template/4e6fd0f965aa6a5f953eb0c601c0c077a3df3edd/images/grammar2.png -------------------------------------------------------------------------------- /images/license-report.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insightsengineering/r.pkg.template/4e6fd0f965aa6a5f953eb0c601c0c077a3df3edd/images/license-report.png -------------------------------------------------------------------------------- /images/links.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insightsengineering/r.pkg.template/4e6fd0f965aa6a5f953eb0c601c0c077a3df3edd/images/links.png -------------------------------------------------------------------------------- /images/pkgdown.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insightsengineering/r.pkg.template/4e6fd0f965aa6a5f953eb0c601c0c077a3df3edd/images/pkgdown.png -------------------------------------------------------------------------------- /images/presidio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insightsengineering/r.pkg.template/4e6fd0f965aa6a5f953eb0c601c0c077a3df3edd/images/presidio.png -------------------------------------------------------------------------------- /images/r-cmd-check.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insightsengineering/r.pkg.template/4e6fd0f965aa6a5f953eb0c601c0c077a3df3edd/images/r-cmd-check.png -------------------------------------------------------------------------------- /images/release.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insightsengineering/r.pkg.template/4e6fd0f965aa6a5f953eb0c601c0c077a3df3edd/images/release.png -------------------------------------------------------------------------------- /images/rhub-workflow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insightsengineering/r.pkg.template/4e6fd0f965aa6a5f953eb0c601c0c077a3df3edd/images/rhub-workflow.png -------------------------------------------------------------------------------- /images/roxygen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insightsengineering/r.pkg.template/4e6fd0f965aa6a5f953eb0c601c0c077a3df3edd/images/roxygen.png -------------------------------------------------------------------------------- /images/spellcheck.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insightsengineering/r.pkg.template/4e6fd0f965aa6a5f953eb0c601c0c077a3df3edd/images/spellcheck.png -------------------------------------------------------------------------------- /images/styler.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insightsengineering/r.pkg.template/4e6fd0f965aa6a5f953eb0c601c0c077a3df3edd/images/styler.png -------------------------------------------------------------------------------- /images/superlinter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insightsengineering/r.pkg.template/4e6fd0f965aa6a5f953eb0c601c0c077a3df3edd/images/superlinter.png -------------------------------------------------------------------------------- /images/validation1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insightsengineering/r.pkg.template/4e6fd0f965aa6a5f953eb0c601c0c077a3df3edd/images/validation1.png -------------------------------------------------------------------------------- /images/validation2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insightsengineering/r.pkg.template/4e6fd0f965aa6a5f953eb0c601c0c077a3df3edd/images/validation2.png -------------------------------------------------------------------------------- /images/version-bump.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insightsengineering/r.pkg.template/4e6fd0f965aa6a5f953eb0c601c0c077a3df3edd/images/version-bump.png -------------------------------------------------------------------------------- /images/version.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insightsengineering/r.pkg.template/4e6fd0f965aa6a5f953eb0c601c0c077a3df3edd/images/version.png -------------------------------------------------------------------------------- /init.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ######################################## 4 | # Repository initializer 5 | # 6 | # This is a self-destructing, one-time script 7 | # that initializes your repository template 8 | # to match your requirements. 9 | ######################################## 10 | 11 | set -euo pipefail 12 | 13 | # Green echo 14 | function gecho () { 15 | local green='\033[0;32m' 16 | local no_color='\033[0m' 17 | # shellcheck disable=SC2145 18 | echo -e "${green}$@${no_color}" 19 | } 20 | 21 | # Orange echo 22 | function oecho () { 23 | local orange='\033[0;33m' 24 | local no_color='\033[0m' 25 | # shellcheck disable=SC2145 26 | echo -e "${orange}$@${no_color}" 27 | } 28 | 29 | gecho "Hello! Thank you for using r.pkg.template! Let us initialize your package." 30 | 31 | echo -n "Please enter your GitHub username or organization name: " 32 | read -r owner 33 | 34 | echo -n "Enter your package's name here (eg. awesomeR): " 35 | read -r pkg 36 | 37 | echo -n "Retain the template's git history? Enter either 'yes' or 'no' (defaults to 'no'): " 38 | read -r retain_git_history 39 | retain_git_history=${retain_git_history:-"no"} 40 | 41 | echo -n "Use shared workflows or use the original workflows? Enter 'yes' to use shared workflows (recommended) and 'no' to use the original workflows (defaults to 'yes'): " 42 | read -r use_shared_workflows 43 | use_shared_workflows=${use_shared_workflows:-"yes"} 44 | 45 | gecho "Initializing your package. Standby..." 46 | 47 | oecho "You've chosen '$retain_git_history' for retaining the template's git history" 48 | if [ "$retain_git_history" == "no" ] 49 | then { 50 | oecho "Removing template git history" 51 | rm -rf .git 52 | } else { 53 | oecho "Template's git history retained" 54 | } 55 | fi 56 | 57 | oecho "Replacing template references within files" 58 | grep -rl --exclude=init.sh --exclude=*.shared \ 59 | --exclude-dir=.git "r.pkg.template" . | \ 60 | xargs perl -p -i -e "s/r.pkg.template/${pkg}/g" 61 | perl -p -i -e "s/insightsengineering/${owner}/g" DESCRIPTION 62 | perl -p -i -e "s/insightsengineering/${owner}/g" .github/ISSUE_TEMPLATE/*.yml 63 | perl -p -i -e "s/insightsengineering/${owner}/g" _pkgdown.yml 64 | perl -p -i -e "s/insightsengineering/${owner}/g" staged_dependencies.yaml 65 | grep -rl --exclude=init.sh --exclude=*.shared \ 66 | --exclude-dir=.git "REPO_GITHUB_TOKEN" . | \ 67 | xargs perl -p -i -e 's/REPO_GITHUB_TOKEN/GITHUB_TOKEN/g' 68 | perl -p -i -e 's@secrets.REPO_GITHUB_TOKEN@secrets.GITHUB_TOKEN@g' .github/workflows/*.shared 69 | grep -rl --exclude=init.sh --exclude=*.shared \ 70 | --exclude-dir=.git \ 71 | "68416928+insights-engineering-bot@users.noreply.github.com" .github/workflows/ | \ 72 | xargs perl -p -i -e 's/68416928\+insights-engineering-bot/41898282\+github-actions\[bot\]/g' 73 | grep -rl --exclude=init.sh --exclude=*.shared \ 74 | --exclude-dir=.git "insights-engineering-bot" .github/workflows/ | \ 75 | xargs perl -p -i -e 's/insights-engineering-bot/github-actions/g' 76 | 77 | oecho "Updating file names and removing unnecessary files" 78 | mv r.pkg.template.Rproj "${pkg}.Rproj" 79 | rm -rf \ 80 | .github/CODEOWNERS \ 81 | .github/ISSUE_TEMPLATE \ 82 | .github/CONTRIBUTING.md \ 83 | .github/PULL_REQUEST_TEMPLATE.md \ 84 | .github/CODE_OF_CONDUCT.md \ 85 | SECURITY.md \ 86 | images \ 87 | workflows.md 88 | oecho "You've chosen '$use_shared_workflows' for using the shared workflows" 89 | if [ "$use_shared_workflows" == "yes" ]; 90 | then { 91 | oecho "Removing the original workflows and using the shared workflows" 92 | rm -rf .github/workflows/*.yaml 93 | for shared in check docs release 94 | do { 95 | mv .github/workflows/$shared.yaml.shared .github/workflows/$shared.yaml 96 | } 97 | done 98 | } else { 99 | oecho "Retaining the original workflows and removing the shared workflows" 100 | rm -rf .github/workflows/*.yaml.shared 101 | } 102 | fi 103 | 104 | oecho "Resetting the package version" 105 | reset_version="0.1.0" 106 | perl -p -i -e "s@^Version: .*@Version: ${reset_version}@" DESCRIPTION 107 | perl -p -i -e "s@^\# ${pkg} .*@\# ${pkg} ${reset_version}@" NEWS.md 108 | 109 | oecho "Overwriting the README.md file" 110 | echo -e "# ${pkg}\n\nShort description of the package" > README.md 111 | 112 | gecho "Package successfully initialized!" 113 | oecho "Please update the remainder of the package as you would do typically while developing an R package." 114 | 115 | oecho "This utility will self-destruct in 3 seconds..." 116 | sleep 3 117 | rm -f "$0" 118 | 119 | gecho "Enjoy!" 120 | -------------------------------------------------------------------------------- /inst/WORDLIST: -------------------------------------------------------------------------------- 1 | BiocCheck 2 | CMD 3 | Pkgdown 4 | Pre 5 | Roxygen 6 | SuperLinter 7 | github 8 | gitleaks 9 | initializer 10 | pre 11 | repo 12 | Forkers 13 | -------------------------------------------------------------------------------- /inst/plumber/hello/plumber.R: -------------------------------------------------------------------------------- 1 | #* Personal greeting as a Plumber API 2 | #* @param name Your name (character string; e.g. "john doe"). 3 | #* @get /echo 4 | r.pkg.template::hello 5 | -------------------------------------------------------------------------------- /man/hello.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/hello.R 3 | \name{hello} 4 | \alias{hello} 5 | \title{Personal greeting} 6 | \usage{ 7 | hello(name = "your name") 8 | } 9 | \arguments{ 10 | \item{name}{Your name (character string; e.g. "john doe").} 11 | } 12 | \value{ 13 | A character string, capitalized to title case. 14 | } 15 | \description{ 16 | Greet a person and appropriately capitalize their name. 17 | } 18 | \examples{ 19 | hello("james bond") 20 | } 21 | -------------------------------------------------------------------------------- /man/plumber_api.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/hello.R 3 | \name{plumber_api} 4 | \alias{plumber_api} 5 | \title{Personal greeting as a Plumber API} 6 | \usage{ 7 | plumber_api(...) 8 | } 9 | \arguments{ 10 | \item{...}{Additional arguments to plumber::pr_run()} 11 | } 12 | \value{ 13 | Plumber API showcasing the personal greeting feature. 14 | } 15 | \description{ 16 | Greet a person and appropriately capitalize their name 17 | as a Plumber API. 18 | } 19 | -------------------------------------------------------------------------------- /man/shiny_app.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/hello.R 3 | \name{shiny_app} 4 | \alias{shiny_app} 5 | \title{Personal greeting as a Shiny app} 6 | \usage{ 7 | shiny_app() 8 | } 9 | \value{ 10 | Shiny app showcasing the personal greeting feature. 11 | } 12 | \description{ 13 | Greet a person and appropriately capitalize their name 14 | as a Shiny app. 15 | } 16 | -------------------------------------------------------------------------------- /r.pkg.template.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: No 4 | SaveWorkspace: No 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 2 10 | Encoding: UTF-8 11 | 12 | RnwWeave: Sweave 13 | LaTeX: pdfLaTeX 14 | 15 | AutoAppendNewline: Yes 16 | StripTrailingWhitespace: Yes 17 | 18 | BuildType: Package 19 | PackageUseDevtools: Yes 20 | PackageInstallArgs: --no-multiarch --with-keep.source 21 | PackageRoxygenize: rd,collate,namespace 22 | -------------------------------------------------------------------------------- /staged_dependencies.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # Information about this file: https://github.com/openpharma/staged.dependencies 3 | current_repo: 4 | repo: insightsengineering/r.pkg.template 5 | host: https://github.com 6 | upstream_repos: 7 | downstream_repos: 8 | -------------------------------------------------------------------------------- /tests/testthat.R: -------------------------------------------------------------------------------- 1 | pkg_name <- "r.pkg.template" 2 | library(pkg_name, character.only = TRUE) 3 | testthat::test_check(pkg_name) 4 | -------------------------------------------------------------------------------- /tests/testthat/shiny-app/app.R: -------------------------------------------------------------------------------- 1 | r.pkg.template::shiny_app() 2 | -------------------------------------------------------------------------------- /tests/testthat/test-api.R: -------------------------------------------------------------------------------- 1 | test_that("API greets the person", { 2 | host <- "127.0.0.1" 3 | port <- 9000 4 | 5 | # Start the API 6 | future::plan(future::multisession) 7 | future::future( 8 | r.pkg.template::plumber_api(host = host, port = port) 9 | ) 10 | Sys.sleep(3) 11 | 12 | # Make request 13 | res <- httr::GET( 14 | url = paste0( 15 | "http://", 16 | host, 17 | ":", 18 | port, 19 | "/echo" 20 | ), 21 | query = "name=tim" 22 | ) 23 | 24 | # Get response 25 | result <- httr::content(res)[[1]] 26 | 27 | # Compare 28 | expected <- "Hello, Tim" 29 | expect_identical(result, expected) 30 | }) 31 | -------------------------------------------------------------------------------- /tests/testthat/test-hello.R: -------------------------------------------------------------------------------- 1 | test_that("hello greets the entity", { 2 | result <- hello("foo") 3 | expected <- "Hello, Foo" 4 | expect_identical(result, expected) 5 | }) 6 | -------------------------------------------------------------------------------- /tests/testthat/test-shiny.R: -------------------------------------------------------------------------------- 1 | test_that("The Shiny App returns a proper greeting", { 2 | library(shinytest2) 3 | app <- AppDriver$new( 4 | "shiny-app/", 5 | load_timeout = 1e5, 6 | timeout = 1e5, 7 | seed = 123 8 | ) 9 | app$get_logs() 10 | 11 | # Set input 12 | app$set_inputs(name = "john") 13 | app$click("greet") 14 | 15 | # Get output 16 | output <- app$get_value(output = "greeting") 17 | 18 | # Assert 19 | expect_equal(output, "Hello, John") 20 | 21 | # Stop the app 22 | app$stop() 23 | }) 24 | -------------------------------------------------------------------------------- /vignettes/hello.Rmd: -------------------------------------------------------------------------------- 1 | 2 | --- 3 | title: "Hello" 4 | date: "`r Sys.Date()`" 5 | --- 6 | 7 | Hello World! 8 | 9 | ```{r} 10 | library(r.pkg.template) 11 | hello("r.pkg.template!") 12 | ``` 13 | -------------------------------------------------------------------------------- /workflows.md: -------------------------------------------------------------------------------- 1 | # What these workflows do? 2 | 3 | ## [`audit.yaml`](./.github/workflows/audit.yaml) 4 | 5 | This workflow scans dependencies of your package for vulnerabilities using 6 | [oysteR](https://cran.r-project.org/web/packages/oysteR/index.html). 7 | Dependencies can be retrieved either from `DESCRIPTION` file or from `renv.lock` file. 8 | 9 | <img src="images/audit.png" width="60%"> 10 | 11 | ## [`bioccheck.yaml`](./.github/workflows/bioccheck.yaml) 12 | 13 | This workflow implements Bioconductor-specific R package checks with 14 | [BiocCheck](https://bioconductor.org/packages/release/bioc/html/BiocCheck.html). 15 | 16 | <img src="images/bioccheck.png" width="40%"> 17 | 18 | ## [`branch-cleanup.yaml`](./.github/workflows/branch-cleanup.yaml) 19 | 20 | This workflow checks if any (non-default) branches had the last commit added to them 21 | more than a configurable number of days ago. If yes, such branches are deleted. 22 | 23 | <img src="images/branch-cleanup.png" width="50%"> 24 | 25 | ## [`build-check-install.yaml`](./.github/workflows/build-check-install.yaml) 26 | 27 | This workflow includes the following activities: 28 | 29 | 1. Build an R package. 30 | 2. Run `R CMD check`. 31 | 3. Publish unit test summary. 32 | 4. Catch any notes, warnings etc. in the `R CMD check` output. 33 | 5. Install the package. 34 | 35 | <img src="images/r-cmd-check.png" width="50%"> 36 | 37 | ## [`gitleaks.yaml`](./.github/workflows/gitleaks.yaml) 38 | 39 | This workflow runs [`gitleaks`](https://github.com/zricethezav/gitleaks) on the repo to discover 40 | any secrets that might have been hardcoded. 41 | 42 | <img src="images/gitleaks.png" width="50%"> 43 | 44 | Additionally, it runs [`presidio-cli`](https://github.com/insightsengineering/presidio-cli) to find 45 | any personally identifiable information (PII) within the `git` repo. 46 | 47 | <img src="images/presidio.png" width="50%"> 48 | 49 | ## [`grammar.yaml`](./.github/workflows/grammar.yaml) 50 | 51 | This workflow uses [`write-good`](https://github.com/btford/write-good) to check changed files 52 | with names matching a pattern for English sentences that could be corrected. 53 | Then, it adds annotations to the pull request so that problematic grammar can be reviewed. 54 | 55 | <img src="images/grammar1.png" width="50%"> 56 | <img src="images/grammar2.png" width="50%"> 57 | 58 | ## [`licenses.yaml`](./.github/workflows/licenses.yaml) 59 | 60 | This workflow generates a license report of R package's dependencies for 61 | continuous compliance. 62 | 63 | <img src="images/license-report.png" width="50%"> 64 | 65 | ## [`links.yaml`](./.github/workflows/links.yaml) 66 | 67 | This workflow checks whether URLs embedded in code and documentation are valid. This workflow uses 68 | [`lychee`](https://github.com/lycheeverse/lychee) to detect broken links. Occasionally, this check 69 | will detect false positives of strings that look like URLs. To remedy, please add this false 70 | positive to the `.lycheeignore` file. 71 | 72 | <img src="images/links.png" width="50%"> 73 | 74 | ## [`linter.yaml`](./.github/workflows/linter.yaml) 75 | 76 | This workflow lints the codebase using [`super-linter`](https://github.com/github/super-linter). 77 | 78 | <img src="images/superlinter.png" width="80%"> 79 | 80 | ## [`pkgdown.yaml`](./.github/workflows/pkgdown.yaml) 81 | 82 | Documentation for the R package is generated via this workflow. This workflow uses the 83 | [`pkgdown`](https://pkgdown.r-lib.org/) framework to generate documentation in HTML, 84 | and the HTML pages are then deployed to the `gh-pages` branch. 85 | 86 | Moreover, an additional `Versions` dropdown is generated via the GitHub Action, so that 87 | the end user can view multiple versions of the documentation for the package. 88 | 89 | <img src="images/pkgdown.png" width="30%"> 90 | 91 | ## [`release.yaml`](./.github/workflows/release.yaml) 92 | 93 | This workflow creates a GitHub release from a `git` tag and generates changelog based 94 | on `NEWS.md` file. 95 | 96 | <img src="images/release.png" width="60%"> 97 | 98 | ## [`roxygen.yaml`](./.github/workflows/roxygen.yaml) 99 | 100 | This workflow uses [`roxygen`](https://roxygen2.r-lib.org/) to generate `.Rd` files in 101 | `man/` directory. It also checks if manuals are up-to-date with roxygen comments in the code. 102 | 103 | <img src="images/roxygen.png" width="80%"> 104 | 105 | ## [`spelling.yaml`](./.github/workflows/spelling.yaml) 106 | 107 | Spellchecks are performed by this workflow, and the 108 | [`spelling`](https://docs.ropensci.org/spelling/) R package is used to detect spelling mistakes. 109 | In the `inst/WORDLIST` file, you can add words and/or acronyms that you want the 110 | spell check to ignore. 111 | 112 | <img src="images/spellcheck.png" width="80%"> 113 | 114 | ## [`style.yaml`](./.github/workflows/style.yaml) 115 | 116 | Code style is enforced via the [`styler`](https://styler.r-lib.org/) R package. The workflow 117 | can be configured to commit files that had styling problems automatically, after 118 | remediating the problems. 119 | 120 | <img src="images/styler.png" width="90%"> 121 | 122 | ## [`test-coverage.yaml`](./.github/workflows/test-coverage.yaml) 123 | 124 | This workflow examines the test coverage of given R package with [`covr`](https://covr.r-lib.org/). 125 | Following that, coverage report is added to the PR. Additional feature is the ability 126 | to compare code coverage between branches, so the PR can be declined if the coverage 127 | would decrease following the merge. 128 | 129 | The second part of the workflow runs utilizes `covtracer` to: 130 | 131 | * prepare traceability matrix 132 | * identify untested behavior 133 | * verify directly tested functions 134 | 135 | ## [`validation.yaml`](./.github/workflows/validation.yaml) 136 | 137 | This workflow generates and publishes validation report. 138 | 139 | <img src="images/validation1.png" width="40%"> 140 | <img src="images/validation2.png" width="60%"> 141 | 142 | ## [`version-bump.yaml`](./.github/workflows/version-bump.yaml) 143 | 144 | This workflow increases R package version in `NEWS.md` and `DESCRIPTION` files and 145 | commits this change to the repository. 146 | 147 | <img src="images/version-bump.png" width="60%"> 148 | 149 | ## [`version.yaml`](./.github/workflows/version.yaml) 150 | 151 | This workflow checks if `NEWS.md` and `DESCRPTION` files have the same R package version. 152 | 153 | <img src="images/version.png" width="60%"> 154 | 155 | ## Adding unit test and coverage reports to `pkgdown` documentation 156 | 157 | In order to add unit test reports and coverage reports to the documentation generated by `pkgdown`, 158 | the following steps are needed. 159 | 160 | 1. If you'd like to have a custom branding in unit test report, add `unit-test-report-brand` parameter 161 | to the `build-check-install.yaml` workflow. See examples below. 162 | 1. Don't use the `skip-r-cmd-install` parameter so that unit test report gets generated. 163 | 1. `build-check-install.yaml` and `test-coverage.yaml` should depend on `pkgdown.yaml` workflow. 164 | This is to ensure that race condition where `pkgdown.yaml` workflow overwrites `gh-pages` branch is avoided. 165 | This can be done for example by: 166 | * setting the `needs: [docs]` for `build-check-install.yaml` and `test-coverage.yaml` workflows, 167 | * or if the `build-check-install.yaml` and `test-coverage.yaml` are invoked from another workflow 168 | than `pkgdown.yaml`, additional dependency can be added which will trigger `test-coverage.yaml` 169 | and `build-check-install.yaml` after `pkgdown.yaml` has finished running. See example below. 170 | 1. `_pkgdown.yaml` should be updated with the following contents to ensure that 171 | links to coverage report and unit test report appear in the navbar. 172 | 173 | ```yaml 174 | navbar: 175 | structure: 176 | left: [intro, reference, articles, tutorials, news, reports] 177 | right: [search, github] 178 | components: 179 | reports: 180 | text: Reports 181 | menu: 182 | - text: Coverage report 183 | href: coverage-report/ 184 | - text: Unit test report 185 | href: unit-test-report/ 186 | github: 187 | icon: fa-github 188 | href: <url-to-the-repository> 189 | ``` 190 | 191 | Example configuration for `main` branch: 192 | 193 | ```yaml 194 | name: Check 🛠 195 | on: 196 | push: 197 | branches: 198 | - main 199 | workflow_run: 200 | workflows: ["Docs 📚"] 201 | types: 202 | - completed 203 | jobs: 204 | r-cmd: 205 | name: R CMD Check 🧬 206 | uses: insightsengineering/r.pkg.template/.github/workflows/build-check-install.yaml@main 207 | with: 208 | unit-test-report-brand: >- 209 | https://github.com/insightsengineering/hex-stickers/raw/main/thumbs/tern.png 210 | coverage: 211 | name: Coverage 📔 212 | uses: insightsengineering/r.pkg.template/.github/workflows/test-coverage.yaml@main 213 | ``` 214 | 215 | Example configuration for tags: 216 | 217 | ```yaml 218 | name: Release 🎈 219 | on: 220 | push: 221 | tags: 222 | - "v*" 223 | jobs: 224 | build: 225 | name: Build package 🎁 226 | needs: [release, docs] 227 | uses: insightsengineering/r.pkg.template/.github/workflows/build-check-install.yaml@main 228 | with: 229 | unit-test-report-brand: >- 230 | https://github.com/insightsengineering/hex-stickers/raw/main/thumbs/tern.png 231 | coverage: 232 | name: Coverage 📔 233 | needs: [release, docs] 234 | uses: insightsengineering/r.pkg.template/.github/workflows/test-coverage.yaml@main 235 | docs: 236 | name: Pkgdown Docs 📚 237 | needs: release 238 | uses: insightsengineering/r.pkg.template/.github/workflows/pkgdown.yaml@main 239 | ``` 240 | 241 | ## [`rhub.yaml`](./.github/workflows/rhub.yaml) 242 | 243 | This is a workflow based on the [official R-hub workflow](https://github.com/r-hub/actions/blob/main/workflows/rhub.yaml) with the main goal being to `R CMD check` the package on different environments corresponding to the CRAN checks. 244 | 245 | <img src="images/rhub-workflow.png" width="60%"> 246 | --------------------------------------------------------------------------------