diff --git a/.github/workflows/R-CMD-check.yaml b/.github/workflows/R-CMD-check.yaml index 25cb2c5..efb3807 100644 --- a/.github/workflows/R-CMD-check.yaml +++ b/.github/workflows/R-CMD-check.yaml @@ -28,7 +28,7 @@ jobs: steps: - uses: actions/checkout@v6 - - uses: ./.github/workflows/install + - uses: api2r/actions/install@v1 with: use-container: "${{ matrix.config.container != '' }}" token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/copilot-setup-steps.yml b/.github/workflows/copilot-setup-steps.yml index 5360a1e..e1907e1 100644 --- a/.github/workflows/copilot-setup-steps.yml +++ b/.github/workflows/copilot-setup-steps.yml @@ -22,7 +22,7 @@ jobs: steps: - uses: actions/checkout@v6 - - uses: ./.github/workflows/install + - uses: api2r/actions/install@v1 with: token: ${{ secrets.GITHUB_TOKEN }} cache-version: copilot diff --git a/.github/workflows/install/action.yml b/.github/workflows/install/action.yml deleted file mode 100644 index ff4ba5e..0000000 --- a/.github/workflows/install/action.yml +++ /dev/null @@ -1,118 +0,0 @@ -name: "Install R and dependencies" -description: "Set up pandoc, R, and package dependencies" -inputs: - token: - description: "GitHub token, set to secrets.GITHUB_TOKEN" - required: true - r-version: - description: "R version, passed to r-lib/actions/setup-r@v2" - required: false - default: release - http-user-agent: - description: "HTTP user agent, passed to r-lib/actions/setup-r@v2" - required: false - default: "" - needs: - description: "Config/Needs tag(s), passed to r-lib/actions/setup-r-dependencies@v2" - required: false - default: "" - extra-packages: - description: "Extra packages, passed to r-lib/actions/setup-r-dependencies@v2" - required: false - default: any::rcmdcheck - optional-packages: - description: "Optional extra packages; installed individually, warn (not error) on failure" - required: false - default: "" - cache-version: - description: "Cache version for r-lib/actions/setup-r-dependencies@v2" - required: false - default: "1" - use-container: - description: "Set to 'true' when running inside a pre-warmed container (skips R and pandoc setup)" - required: false - default: "false" - -runs: - using: "composite" - steps: - - if: inputs.use-container != 'true' - uses: r-lib/actions/setup-pandoc@v2 - - - uses: r-lib/actions/setup-r@v2 - with: - install-r: ${{ inputs.use-container != 'true' }} - r-version: ${{ inputs.r-version }} - http-user-agent: ${{ inputs.http-user-agent }} - use-public-rspm: true - - # In pre-warmed containers every package may already be in the system - # library, so nothing is ever written to R_LIBS_USER. Ensure the - # directory exists with a sentinel so the actions/cache glob matches - # and the "Path Validation Error" warning doesn't fire at cache-save. - - if: inputs.use-container == 'true' - name: Ensure R_LIBS_USER exists - shell: bash - run: | - lib="$(Rscript -e 'cat(Sys.getenv("R_LIBS_USER"))')" - mkdir -p "$lib" - touch "$lib/.keep" - - # Container path: call pak::pak() directly so it checks .libPaths() and - # only installs/updates packages that are genuinely missing or outdated. - # setup-r-dependencies always runs a full lockfile-create → lockfile-install - # cycle which reinstalls everything even when the container already has it. - - if: inputs.use-container == 'true' - name: Update R package dependencies (container) - shell: Rscript {0} - env: - GITHUB_PAT: ${{ inputs.token }} - run: | - # Replicate needs → Config/Needs/ expansion from r-lib/actions - needs_parts <- strsplit("${{ inputs.needs }}", "[[:space:],]+")[[1]] - needs_parts <- needs_parts[nzchar(needs_parts)] - needs <- sprintf("Config/Needs/%s", needs_parts) - - extra_deps <- strsplit("${{ inputs.extra-packages }}", "[[:space:],]+")[[1]] - extra_deps <- extra_deps[nzchar(extra_deps)] - - # Over-protect for strangeness in the container - if (!requireNamespace("pak", quietly = TRUE)) { - install.packages("pak") - } - - pak::pak( - c("deps::.", extra_deps), - dependencies = c(needs, "all"), - upgrade = TRUE, - ask = FALSE - ) - - # Non-container path: standard setup-r-dependencies flow with caching. - - if: inputs.use-container != 'true' - uses: r-lib/actions/setup-r-dependencies@v2 - env: - GITHUB_PAT: ${{ inputs.token }} - with: - needs: ${{ inputs.needs }} - extra-packages: ${{ inputs.extra-packages }} - cache-version: ${{ inputs.cache-version }} - - - if: inputs.optional-packages != '' - name: Install optional packages - shell: Rscript {0} - env: - GITHUB_PAT: ${{ inputs.token }} - run: | - pkgs <- strsplit("${{ inputs.optional-packages }}", "\\s+")[[1]] - pkgs <- pkgs[nzchar(pkgs)] - for (pkg in pkgs) { - tryCatch( - pak::pak(pkg), - error = function(e) { - msg <- sprintf("Failed to install optional package %s: %s", pkg, conditionMessage(e)) - msg <- gsub("[\r\n]+", " ", msg) - cat("::warning::", msg, "\n", sep = "") - } - ) - } diff --git a/.github/workflows/pkgdown.yaml b/.github/workflows/pkgdown.yaml index afc5f44..7f4f411 100644 --- a/.github/workflows/pkgdown.yaml +++ b/.github/workflows/pkgdown.yaml @@ -27,7 +27,7 @@ jobs: steps: - uses: actions/checkout@v6 - - uses: ./.github/workflows/install + - uses: api2r/actions/install@v1 with: use-container: "true" token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/pr-commands.yaml b/.github/workflows/pr-commands.yaml index 8c94262..8c2f575 100644 --- a/.github/workflows/pr-commands.yaml +++ b/.github/workflows/pr-commands.yaml @@ -22,7 +22,7 @@ jobs: with: repo-token: ${{ secrets.GITHUB_TOKEN }} - - uses: ./.github/workflows/install + - uses: api2r/actions/install@v1 with: use-container: "true" token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/qcthat.yaml b/.github/workflows/qcthat.yaml index a6ea54c..2adec84 100644 --- a/.github/workflows/qcthat.yaml +++ b/.github/workflows/qcthat.yaml @@ -58,7 +58,7 @@ jobs: steps: - uses: actions/checkout@v6 - - uses: ./.github/workflows/install + - uses: api2r/actions/install@v1 with: use-container: "true" token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/test-coverage.yaml b/.github/workflows/test-coverage.yaml index 6878a49..8b7a5f3 100644 --- a/.github/workflows/test-coverage.yaml +++ b/.github/workflows/test-coverage.yaml @@ -16,7 +16,7 @@ jobs: steps: - uses: actions/checkout@v6 - - uses: ./.github/workflows/install + - uses: api2r/actions/install@v1 with: use-container: "true" token: ${{ secrets.GITHUB_TOKEN }} diff --git a/NEWS.md b/NEWS.md index 28150a6..c8e9f05 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,7 @@ # pkgskills (development version) +* `use_github_copilot()` now uses the external, stable `api2r/actions/install@v1` composite workflow instead of the local `install` action (#84). + * `use_ai()` no longer fails when `DESCRIPTION` has no `BugReports` field; it now falls back to git remotes (`upstream` then `origin`) to construct the URL and writes it to `DESCRIPTION` automatically (#82). * `use_github_copilot_whitelist()` configures the Copilot coding agent firewall allowlist (#79). diff --git a/R/use_github_copilot.R b/R/use_github_copilot.R index 42cd03b..9301680 100644 --- a/R/use_github_copilot.R +++ b/R/use_github_copilot.R @@ -1,9 +1,8 @@ #' Install GitHub Copilot setup workflow into a project #' -#' Installs a `copilot-setup-steps.yml` workflow and its companion reusable -#' `install` action into the project's `.github/workflows/` directory. Also -#' calls [use_github_copilot_whitelist()] to configure the coding agent firewall -#' allowlist. +#' Installs a `copilot-setup-steps.yml` workflow into the project's +#' `.github/workflows/` directory. Also calls [use_github_copilot_whitelist()] +#' to configure the coding agent firewall allowlist. #' #' @param overwrite (`logical(1)`) Whether to overwrite existing files. Defaults #' to `FALSE`. @@ -39,21 +38,12 @@ use_github_copilot <- function( ".github/workflows/copilot-setup-steps.yml", overwrite ) - path_install_abs <- .path_proj_save_as( - ".github/workflows/install/action.yml", - overwrite - ) .use_template_as_is( "workflows/copilot-setup-steps.yml", ".github/workflows/copilot-setup-steps.yml", open = open ) - .use_template_as_is( - "workflows/install/action.yml", - ".github/workflows/install/action.yml", - open = FALSE - ) use_github_copilot_whitelist(allowlist = allowlist, gh_token = gh_token) diff --git a/inst/templates/workflows/copilot-setup-steps.yml b/inst/templates/workflows/copilot-setup-steps.yml index ae9a04b..611b08f 100644 --- a/inst/templates/workflows/copilot-setup-steps.yml +++ b/inst/templates/workflows/copilot-setup-steps.yml @@ -22,7 +22,7 @@ jobs: steps: - uses: actions/checkout@v6 - - uses: ./.github/workflows/install + - uses: api2r/actions/install@v1 with: token: ${{ secrets.GITHUB_TOKEN }} cache-version: copilot diff --git a/inst/templates/workflows/install/action.yml b/inst/templates/workflows/install/action.yml deleted file mode 100644 index ff4ba5e..0000000 --- a/inst/templates/workflows/install/action.yml +++ /dev/null @@ -1,118 +0,0 @@ -name: "Install R and dependencies" -description: "Set up pandoc, R, and package dependencies" -inputs: - token: - description: "GitHub token, set to secrets.GITHUB_TOKEN" - required: true - r-version: - description: "R version, passed to r-lib/actions/setup-r@v2" - required: false - default: release - http-user-agent: - description: "HTTP user agent, passed to r-lib/actions/setup-r@v2" - required: false - default: "" - needs: - description: "Config/Needs tag(s), passed to r-lib/actions/setup-r-dependencies@v2" - required: false - default: "" - extra-packages: - description: "Extra packages, passed to r-lib/actions/setup-r-dependencies@v2" - required: false - default: any::rcmdcheck - optional-packages: - description: "Optional extra packages; installed individually, warn (not error) on failure" - required: false - default: "" - cache-version: - description: "Cache version for r-lib/actions/setup-r-dependencies@v2" - required: false - default: "1" - use-container: - description: "Set to 'true' when running inside a pre-warmed container (skips R and pandoc setup)" - required: false - default: "false" - -runs: - using: "composite" - steps: - - if: inputs.use-container != 'true' - uses: r-lib/actions/setup-pandoc@v2 - - - uses: r-lib/actions/setup-r@v2 - with: - install-r: ${{ inputs.use-container != 'true' }} - r-version: ${{ inputs.r-version }} - http-user-agent: ${{ inputs.http-user-agent }} - use-public-rspm: true - - # In pre-warmed containers every package may already be in the system - # library, so nothing is ever written to R_LIBS_USER. Ensure the - # directory exists with a sentinel so the actions/cache glob matches - # and the "Path Validation Error" warning doesn't fire at cache-save. - - if: inputs.use-container == 'true' - name: Ensure R_LIBS_USER exists - shell: bash - run: | - lib="$(Rscript -e 'cat(Sys.getenv("R_LIBS_USER"))')" - mkdir -p "$lib" - touch "$lib/.keep" - - # Container path: call pak::pak() directly so it checks .libPaths() and - # only installs/updates packages that are genuinely missing or outdated. - # setup-r-dependencies always runs a full lockfile-create → lockfile-install - # cycle which reinstalls everything even when the container already has it. - - if: inputs.use-container == 'true' - name: Update R package dependencies (container) - shell: Rscript {0} - env: - GITHUB_PAT: ${{ inputs.token }} - run: | - # Replicate needs → Config/Needs/ expansion from r-lib/actions - needs_parts <- strsplit("${{ inputs.needs }}", "[[:space:],]+")[[1]] - needs_parts <- needs_parts[nzchar(needs_parts)] - needs <- sprintf("Config/Needs/%s", needs_parts) - - extra_deps <- strsplit("${{ inputs.extra-packages }}", "[[:space:],]+")[[1]] - extra_deps <- extra_deps[nzchar(extra_deps)] - - # Over-protect for strangeness in the container - if (!requireNamespace("pak", quietly = TRUE)) { - install.packages("pak") - } - - pak::pak( - c("deps::.", extra_deps), - dependencies = c(needs, "all"), - upgrade = TRUE, - ask = FALSE - ) - - # Non-container path: standard setup-r-dependencies flow with caching. - - if: inputs.use-container != 'true' - uses: r-lib/actions/setup-r-dependencies@v2 - env: - GITHUB_PAT: ${{ inputs.token }} - with: - needs: ${{ inputs.needs }} - extra-packages: ${{ inputs.extra-packages }} - cache-version: ${{ inputs.cache-version }} - - - if: inputs.optional-packages != '' - name: Install optional packages - shell: Rscript {0} - env: - GITHUB_PAT: ${{ inputs.token }} - run: | - pkgs <- strsplit("${{ inputs.optional-packages }}", "\\s+")[[1]] - pkgs <- pkgs[nzchar(pkgs)] - for (pkg in pkgs) { - tryCatch( - pak::pak(pkg), - error = function(e) { - msg <- sprintf("Failed to install optional package %s: %s", pkg, conditionMessage(e)) - msg <- gsub("[\r\n]+", " ", msg) - cat("::warning::", msg, "\n", sep = "") - } - ) - } diff --git a/tests/testthat/test-use_github_copilot.R b/tests/testthat/test-use_github_copilot.R index d17da02..15daddf 100644 --- a/tests/testthat/test-use_github_copilot.R +++ b/tests/testthat/test-use_github_copilot.R @@ -1,4 +1,4 @@ -test_that("use_github_copilot() installs copilot-setup-steps.yml (#25)", { +test_that("use_github_copilot() installs copilot-setup-steps.yml (#25, #84)", { proj_dir <- local_pkg() local_gh_mock() suppressWarnings(suppressMessages(use_github_copilot(open = FALSE))) @@ -9,17 +9,6 @@ test_that("use_github_copilot() installs copilot-setup-steps.yml (#25)", { ) }) -test_that("use_github_copilot() installs install/action.yml (#25)", { - proj_dir <- local_pkg() - local_gh_mock() - suppressWarnings(suppressMessages(use_github_copilot(open = FALSE))) - expect_true( - fs::file_exists( - fs::path(proj_dir, ".github/workflows/install/action.yml") - ) - ) -}) - test_that("use_github_copilot() returns path to copilot-setup-steps.yml invisibly (#25)", { proj_dir <- local_pkg() local_gh_mock() @@ -45,19 +34,9 @@ test_that("use_github_copilot() errors if copilot-setup-steps.yml exists and ove ) }) -test_that("use_github_copilot() errors if install/action.yml exists and overwrite = FALSE (#25)", { - local_pkg(".github/workflows/install/action.yml" = "# existing") - local_gh_mock() - expect_error( - suppressWarnings(suppressMessages(use_github_copilot(open = FALSE))), - class = "pkgskills-error-file_exists" - ) -}) - -test_that("use_github_copilot() overwrites files when overwrite = TRUE (#25)", { +test_that("use_github_copilot() overwrites files when overwrite = TRUE (#25, #84)", { proj_dir <- local_pkg( - ".github/workflows/copilot-setup-steps.yml" = "# old", - ".github/workflows/install/action.yml" = "# old" + ".github/workflows/copilot-setup-steps.yml" = "# old" ) local_gh_mock() suppressWarnings(suppressMessages(use_github_copilot( @@ -79,29 +58,3 @@ test_that("use_github_copilot() preserves ${{ }} in copilot-setup-steps.yml (#44 ) expect_true(any(grepl("${{", workflow_content, fixed = TRUE))) }) - -test_that("use_github_copilot() preserves ${{ }} in install/action.yml (#44)", { - proj_dir <- local_pkg() - local_gh_mock() - suppressWarnings(suppressMessages(use_github_copilot(open = FALSE))) - action_content <- readLines( - fs::path(proj_dir, ".github/workflows/install/action.yml") - ) - expect_true(any(grepl("${{", action_content, fixed = TRUE))) -}) - -test_that("use_github_copilot() checks both paths before writing either (#25)", { - # install/action.yml exists, copilot-setup-steps.yml does not - # Should error before writing copilot-setup-steps.yml - proj_dir <- local_pkg(".github/workflows/install/action.yml" = "# existing") - local_gh_mock() - expect_error( - suppressWarnings(suppressMessages(use_github_copilot(open = FALSE))), - class = "pkgskills-error-file_exists" - ) - expect_false( - fs::file_exists( - fs::path(proj_dir, ".github/workflows/copilot-setup-steps.yml") - ) - ) -}) diff --git a/vignettes/pkgskills.Rmd b/vignettes/pkgskills.Rmd index 5bfb566..f9eb3ba 100644 --- a/vignettes/pkgskills.Rmd +++ b/vignettes/pkgskills.Rmd @@ -47,8 +47,8 @@ This call installs three things: agent reads. It describes the repository layout, standard workflow, and where to find skills. 2. **A GitHub Copilot workflow file** (via `use_github_copilot()`): a - `copilot-setup-steps.yml` workflow and its companion reusable `install` - action, so Copilot Coding Agent can work on your repository. + `copilot-setup-steps.yml` workflow so Copilot Coding Agent can work on your + repository. 3. **Seven skills** installed to `.github/skills/` by default: `create-issue`, `document`, `github`, `implement-issue`, `r-code`, `search-code`, and `tdd-workflow`.